75f7416524
Parallelize collection of logs at each layer of the resource tree walk. Since almost all cost of log collection is at leaf nodes, this should allow the total time for log collection to be close to the time taken for the single longest leaf node, instead of the sum of all leaf nodes.
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/pulumi/pulumi/pkg/operations"
|
|
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
|
)
|
|
|
|
func newLogsCmd() *cobra.Command {
|
|
var stack string
|
|
var follow bool
|
|
var since string
|
|
var resource string
|
|
|
|
logsCmd := &cobra.Command{
|
|
Use: "logs",
|
|
Short: "Show aggregated logs for a project",
|
|
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
|
stackName, err := explicitOrCurrent(stack, backend)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
startTime := parseRelativeDuration(since)
|
|
var resourceFilter *operations.ResourceFilter
|
|
if resource != "" {
|
|
var rf = operations.ResourceFilter(resource)
|
|
resourceFilter = &rf
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// Note: Just tracking latest log date is not sufficient - as stale logs may show up which should have been
|
|
// displayed before previously rendered log entries, but weren't available at the time, so still need to be
|
|
// rendered now even though they are technically out of order.
|
|
shown := map[operations.LogEntry]bool{}
|
|
|
|
for {
|
|
logs, err := backend.GetLogs(stackName, operations.LogQuery{
|
|
StartTime: startTime,
|
|
ResourceFilter: resourceFilter,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, logEntry := range logs {
|
|
if _, shownAlready := shown[logEntry]; !shownAlready {
|
|
eventTime := time.Unix(0, logEntry.Timestamp*1000000)
|
|
fmt.Printf("%30.30s[%30.30s] %v\n", eventTime.Format(time.RFC3339Nano), logEntry.ID, logEntry.Message)
|
|
shown[logEntry] = true
|
|
}
|
|
}
|
|
|
|
if !follow {
|
|
return nil
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
}
|
|
}),
|
|
}
|
|
|
|
logsCmd.PersistentFlags().StringVarP(
|
|
&stack, "stack", "s", "",
|
|
"List configuration for a different stack than the currently selected stack")
|
|
logsCmd.PersistentFlags().BoolVarP(
|
|
&follow, "follow", "f", false,
|
|
"Follow the log stream in real time (like tail -f)")
|
|
logsCmd.PersistentFlags().StringVar(
|
|
&since, "since", "",
|
|
"Only return logs newer than a relative duration ('5s', '2m', '3h'). Defaults to returning all logs.")
|
|
logsCmd.PersistentFlags().StringVarP(
|
|
&resource, "resource", "r", "",
|
|
"Only return logs for the requested resource ('name', 'type::name' or full URN). Defaults to returning all logs.")
|
|
|
|
return logsCmd
|
|
}
|
|
|
|
var durationRegexp = regexp.MustCompile(`(\d+)([y|w|d|h|m|s])`)
|
|
|
|
// parseRelativeDuration extracts a time.Time previous to now by the a relative duration in the format '5s', '2m', '3h'.
|
|
func parseRelativeDuration(duration string) *time.Time {
|
|
now := time.Now()
|
|
if duration == "" {
|
|
return nil
|
|
}
|
|
parts := durationRegexp.FindStringSubmatch(duration)
|
|
if parts == nil {
|
|
fmt.Printf("Warning: duration could not be parsed: '%v'\n", duration)
|
|
return nil
|
|
}
|
|
num, err := strconv.ParseInt(parts[1], 10, 64)
|
|
if err != nil {
|
|
fmt.Printf("Warning: duration could not be parsed: '%v'\n", duration)
|
|
return nil
|
|
}
|
|
d := time.Duration(-num)
|
|
switch parts[2] {
|
|
case "y":
|
|
d *= time.Hour * 24 * 365
|
|
case "w":
|
|
d *= time.Hour * 24 * 7
|
|
case "d":
|
|
d *= time.Hour * 24
|
|
case "h":
|
|
d *= time.Hour
|
|
case "m":
|
|
d *= time.Minute
|
|
case "s":
|
|
d *= time.Second
|
|
default:
|
|
fmt.Printf("Warning: duration could not be parsed: '%v'\n", duration)
|
|
return nil
|
|
}
|
|
ret := now.Add(d)
|
|
return &ret
|
|
}
|