Support for filtering logs by resource

This commit is contained in:
Luke Hoban 2017-11-22 20:58:46 -08:00
parent 8fd11f26b8
commit 9648444b05
3 changed files with 74 additions and 15 deletions

View file

@ -18,6 +18,7 @@ func newLogsCmd() *cobra.Command {
var stack string var stack string
var follow bool var follow bool
var since string var since string
var resource string
logsCmd := &cobra.Command{ logsCmd := &cobra.Command{
Use: "logs", Use: "logs",
@ -29,6 +30,11 @@ func newLogsCmd() *cobra.Command {
} }
startTime := parseRelativeDuration(since) 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 // 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.
@ -41,6 +47,7 @@ func newLogsCmd() *cobra.Command {
for { for {
logs, err := backend.GetLogs(stackName, operations.LogQuery{ logs, err := backend.GetLogs(stackName, operations.LogQuery{
StartTime: startTime, StartTime: startTime,
Resource: resourceFilter,
}) })
if err != nil { if err != nil {
return err return err
@ -72,6 +79,9 @@ func newLogsCmd() *cobra.Command {
logsCmd.PersistentFlags().StringVar( logsCmd.PersistentFlags().StringVar(
&since, "since", "", &since, "since", "",
"Only return logs newer than a relative duration ('5s', '2m', '3h'). Defaults to returning all logs.") "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 return logsCmd
} }

View file

@ -11,12 +11,31 @@ type LogEntry struct {
Message string Message string
} }
// ResourceFilter specifies a specific resource or subset of resources. It can be provided in three formats:
// - Full URN: "<namespace>::<alloc>::<type>::<name>"
// - Type + Name: "<type>::<name>"
// - Name: "<name>"
type ResourceFilter string
// Query is a filter on logs, in the format '<name>=<val>,<name>=>val>' where <name> is one of the following:
// - `URN`
// - `QualifiedName`
// - `Name`
// - `Type`
// - `Message`
type Query string
// LogQuery represents the parameters to a log query operation. // LogQuery represents the parameters to a log query operation.
// All fields are optional, leaving them off returns all logs. // All fields are optional, leaving them off returns all logs.
type LogQuery struct { type LogQuery struct {
// StartTime is an optional time indiciating that only logs from after this time should be produced.
StartTime *time.Time StartTime *time.Time
// EndTime is an optional time indiciating that only logs from before this time should be produced.
EndTime *time.Time EndTime *time.Time
// Query is a string indicating a filter to apply to the logs - query syntax TBD
Query *string Query *string
// Resource is a string indicating that logs should be limited toa resource of resoruces
Resource *ResourceFilter
} }
// MetricName is a handle to a metric supported by a Pulumi Framework resources // MetricName is a handle to a metric supported by a Pulumi Framework resources

View file

@ -87,14 +87,17 @@ var _ Provider = (*resourceOperations)(nil)
// GetLogs gets logs for a Resource // GetLogs gets logs for a Resource
func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) { func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) {
// Only get logs for this resource if it matches the resource filter query
if ops.matchesResourceFilter(query.Resource) {
// Try to get an operations provider for this resource, it may be `nil`
opsProvider, err := ops.getOperationsProvider() opsProvider, err := ops.getOperationsProvider()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opsProvider != nil { if opsProvider != nil {
// If this resource has an operations provider - use it and don't recur into children. It is the responsibility // If this resource has an operations provider - use it and don't recur into children. It is the
// of it's GetLogs implementation to aggregate all logs from children, either by passing them through or by // responsibility of it's GetLogs implementation to aggregate all logs from children, either by passing them
// filtering specific content out. // through or by filtering specific content out.
logsResult, err := opsProvider.GetLogs(query) logsResult, err := opsProvider.GetLogs(query)
if err != nil { if err != nil {
return logsResult, err return logsResult, err
@ -103,13 +106,15 @@ func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) {
return logsResult, nil return logsResult, nil
} }
} }
}
// If this resource did not choose to provide it's own logs, recur into children and collect + aggregate their logs.
var logs []LogEntry var logs []LogEntry
for _, child := range ops.resource.children { for _, child := range ops.resource.children {
childOps := &resourceOperations{ childOps := &resourceOperations{
resource: child, resource: child,
config: ops.config, config: ops.config,
} }
// TODO: Parallelize these calls to child GetLogs // IDEA: Parallelize these calls to child GetLogs
childLogs, err := childOps.GetLogs(query) childLogs, err := childOps.GetLogs(query)
if err != nil { if err != nil {
return &logs, err return &logs, err
@ -146,6 +151,31 @@ func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) {
return &retLogs, nil return &retLogs, nil
} }
// matchesResourceFilter determines whether this resource matches the provided resource filter.
func (ops *resourceOperations) matchesResourceFilter(filter *ResourceFilter) bool {
if filter == nil {
// No filter, all resources match it.
return true
}
if ops.resource == nil || ops.resource.state == nil {
return false
}
urn := ops.resource.state.URN
if resource.URN(*filter) == urn {
// The filter matched the full URN
return true
}
if string(*filter) == string(urn.Type())+"::"+string(urn.Name()) {
// The filter matched the '<type>::<name>' part of the URN
return true
}
if tokens.QName(*filter) == urn.Name() {
// The filter matched the '<name>' part of the URN
return true
}
return false
}
// ListMetrics lists metrics for a Resource // ListMetrics lists metrics for a Resource
func (ops *resourceOperations) ListMetrics() []MetricName { func (ops *resourceOperations) ListMetrics() []MetricName {
return []MetricName{} return []MetricName{}