diff --git a/args.go b/args.go index fee162667..416e3f1c3 100644 --- a/args.go +++ b/args.go @@ -54,6 +54,8 @@ const ( ArgAppLogFollow = "follow" // ArgAppLogTail tail logs. ArgAppLogTail = "tail" + // ArgNoPrefix no prefix to json logs + ArgNoPrefix = "no-prefix" // ArgAppForceRebuild forces a deployment rebuild ArgAppForceRebuild = "force-rebuild" // ArgAppAlertDestinations is a path to an app alert destination file. diff --git a/commands/apps.go b/commands/apps.go index 5db806fb3..def9e921d 100644 --- a/commands/apps.go +++ b/commands/apps.go @@ -14,6 +14,7 @@ limitations under the License. package commands import ( + "bufio" "bytes" "encoding/json" "fmt" @@ -186,6 +187,8 @@ For more information about logs, see [How to View Logs](https://www.digitalocean AddStringFlag(logs, doctl.ArgAppLogType, "", strings.ToLower(string(godo.AppLogTypeRun)), "Retrieves logs for a specific log type. Defaults to run logs.") AddBoolFlag(logs, doctl.ArgAppLogFollow, "f", false, "Returns logs as they are emitted by the app.") AddIntFlag(logs, doctl.ArgAppLogTail, "", -1, "Specifies the number of lines to show from the end of the log.") + AddBoolFlag(logs, doctl.ArgNoPrefix, "", false, "Removes the prefix from logs. Useful for JSON structured logs") + logs.Example = `The following example retrieves the build logs for the app with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + ` and the component ` + "`" + `web` + "`" + `: doctl apps logs f81d4fae-7dec-11d0-a765-00a0c91e6bf6 web --type build` listRegions := CmdBuilder( @@ -610,6 +613,11 @@ func RunAppsGetLogs(c *CmdConfig) error { return err } + noPrefixFlag, err := c.Doit.GetBool(c.NS, doctl.ArgNoPrefix) + if err != nil { + return err + } + logs, err := c.Apps().GetLogs(appID, deploymentID, component, logType, logFollow, logTail) if err != nil { return err @@ -631,6 +639,18 @@ func RunAppsGetLogs(c *CmdConfig) error { } r := strings.NewReader(data.Data) + if noPrefixFlag { + content, err := io.ReadAll(r) + if err != nil { + return nil, err + } + logParts := strings.SplitN(string(content), " ", 3) + if len(logParts) > 2 { + jsonLog := logParts[2] + return strings.NewReader(jsonLog), nil + } + } + return r, nil } @@ -654,7 +674,20 @@ func RunAppsGetLogs(c *CmdConfig) error { } defer resp.Body.Close() - io.Copy(c.Out, resp.Body) + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + logLine := scanner.Text() + if noPrefixFlag { + logParts := strings.SplitN(logLine, " ", 3) + if len(logParts) > 2 { + logLine = logParts[2] + } + } + fmt.Fprintln(c.Out, logLine) + } + if err := scanner.Err(); err != nil { + return err + } } else { warn("No logs found for app component") } diff --git a/integration/apps_test.go b/integration/apps_test.go index 3f72d776f..531df0005 100644 --- a/integration/apps_test.go +++ b/integration/apps_test.go @@ -923,7 +923,7 @@ var _ = suite("apps/get-logs", func(t *testing.T, when spec.G, it spec.S) { wsServer *httptest.Server upgrader = websocket.Upgrader{} ) - + now := time.Now().Format(time.RFC3339) it.Before(func() { expect = require.New(t) @@ -985,7 +985,7 @@ var _ = suite("apps/get-logs", func(t *testing.T, when spec.G, it spec.S) { data := struct { Data string `json:"data"` }{ - Data: "fake logs\n", + Data: fmt.Sprintf("foo-service %v fake logs\n", now), } buf := new(bytes.Buffer) json.NewEncoder(buf).Encode(data) @@ -1020,6 +1020,28 @@ var _ = suite("apps/get-logs", func(t *testing.T, when spec.G, it spec.S) { output, err := cmd.CombinedOutput() expect.NoError(err) + logLine := fmt.Sprintf("foo-service %v fake logs", now) + expectedOutput := fmt.Sprintf("%s\n%s\n%s\n%s\n%s", logLine, logLine, logLine, logLine, logLine) + expect.Equal(expectedOutput, strings.TrimSpace(string(output))) + }) + it("removes the prefix from an app's logs", func() { + cmd := exec.Command(builtBinaryPath, + "-t", "some-magic-token", + "-u", server.URL, + "apps", + "logs", + testAppUUID, + "service", + "--deployment="+testDeploymentUUID, + "--type=run", + "--tail=1", + "-f", + "--no-prefix", + ) + + output, err := cmd.CombinedOutput() + expect.NoError(err) + expectedOutput := "fake logs\nfake logs\nfake logs\nfake logs\nfake logs" expect.Equal(expectedOutput, strings.TrimSpace(string(output))) })