Add --json to pulumi logs

When outputing JSON, if we have a fixed number of log entries (i.e. we
are not `--follow`'ing, we wrap each entry in array. Otherwise, we
just emit each log entry as an object at top level.

As part of this change, I've adopted a slightly more precise time
output format in `pulumi stack ls` when using JSON output. These times
now match the default output from `console.log(new Date())`
This commit is contained in:
Matt Ellis 2018-10-29 11:30:35 -07:00
parent ca540cc736
commit 4ba5901aaf
2 changed files with 56 additions and 8 deletions

View file

@ -39,6 +39,7 @@ func newLogsCmd() *cobra.Command {
var follow bool var follow bool
var since string var since string
var resource string var resource string
var jsonOut bool
logsCmd := &cobra.Command{ logsCmd := &cobra.Command{
Use: "logs", Use: "logs",
@ -64,11 +65,13 @@ func newLogsCmd() *cobra.Command {
resourceFilter = &rf resourceFilter = &rf
} }
fmt.Printf( if !jsonOut {
opts.Color.Colorize(colors.BrightMagenta+"Collecting logs for stack %s since %s.\n\n"+colors.Reset), fmt.Printf(
s.Ref().String(), opts.Color.Colorize(colors.BrightMagenta+"Collecting logs for stack %s since %s.\n\n"+colors.Reset),
startTime.Format(timeFormat), s.Ref().String(),
) startTime.Format(timeFormat),
)
}
// IDEA: This map will grow forever as new log entries are found. We may need to do a more approximate // IDEA: This map will grow forever as new log entries are found. We may need to do a more approximate
// approach here to ensure we don't grow memory unboundedly while following logs. // approach here to ensure we don't grow memory unboundedly while following logs.
@ -86,10 +89,45 @@ func newLogsCmd() *cobra.Command {
return errors.Wrapf(err, "failed to get logs") return errors.Wrapf(err, "failed to get logs")
} }
// When we are emitting a fixed number of log entries, and outputing JSON, wrap them in an array.
if !follow && jsonOut {
entries := make([]logEntryJSON, 0, len(logs))
for _, logEntry := range logs {
if _, shownAlready := shown[logEntry]; !shownAlready {
eventTime := time.Unix(0, logEntry.Timestamp*1000000)
entries = append(entries, logEntryJSON{
ID: logEntry.ID,
Timestamp: eventTime.UTC().Format(timeFormat),
Message: logEntry.Message,
})
shown[logEntry] = true
}
}
return printJSON(entries)
}
for _, logEntry := range logs { for _, logEntry := range logs {
if _, shownAlready := shown[logEntry]; !shownAlready { if _, shownAlready := shown[logEntry]; !shownAlready {
eventTime := time.Unix(0, logEntry.Timestamp*1000000) eventTime := time.Unix(0, logEntry.Timestamp*1000000)
fmt.Printf("%30.30s[%30.30s] %v\n", eventTime.Format(timeFormat), logEntry.ID, logEntry.Message)
if !jsonOut {
fmt.Printf("%30.30s[%30.30s] %v\n", eventTime.Format(timeFormat),
logEntry.ID, logEntry.Message)
} else {
err = printJSON(logEntryJSON{
ID: logEntry.ID,
Timestamp: eventTime.UTC().Format(timeFormat),
Message: logEntry.Message,
})
if err != nil {
return err
}
}
shown[logEntry] = true shown[logEntry] = true
} }
} }
@ -106,6 +144,8 @@ func newLogsCmd() *cobra.Command {
logsCmd.PersistentFlags().StringVarP( logsCmd.PersistentFlags().StringVarP(
&stack, "stack", "s", "", &stack, "stack", "s", "",
"The name of the stack to operate on. Defaults to the current stack") "The name of the stack to operate on. Defaults to the current stack")
logsCmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit outputs as JSON")
logsCmd.PersistentFlags().BoolVarP( logsCmd.PersistentFlags().BoolVarP(
&follow, "follow", "f", false, &follow, "follow", "f", false,
"Follow the log stream in real time (like tail -f)") "Follow the log stream in real time (like tail -f)")
@ -135,3 +175,12 @@ func parseSince(since string, reference time.Time) (*time.Time, error) {
startTime := time.Unix(startTimeSec, startTimeNs) startTime := time.Unix(startTimeSec, startTimeNs)
return &startTime, nil return &startTime, nil
} }
// logEntryJSON is the shape of the --json output of this command. When --json is passed, if we are not following the
// log stream, we print an array of logEntry objects. If we are following the log stream, we instead print each object
// at top level.
type logEntryJSON struct {
ID string
Timestamp string
Message string
}

View file

@ -18,7 +18,6 @@ import (
"fmt" "fmt"
"sort" "sort"
"strconv" "strconv"
"time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -117,7 +116,7 @@ func formatJSON(b backend.Backend, currentStack string, stackSummaries []backend
} }
if summary.LastUpdate() != nil { if summary.LastUpdate() != nil {
summaryJSON.LastUpdate = summary.LastUpdate().UTC().Format(time.RFC3339) summaryJSON.LastUpdate = summary.LastUpdate().UTC().Format(timeFormat)
} }
if httpBackend, ok := b.(httpstate.Backend); ok { if httpBackend, ok := b.(httpstate.Backend); ok {