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 follow bool
var since string
var resource string
logsCmd := &cobra.Command{
Use: "logs",
@ -29,6 +30,11 @@ func newLogsCmd() *cobra.Command {
}
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.
@ -41,6 +47,7 @@ func newLogsCmd() *cobra.Command {
for {
logs, err := backend.GetLogs(stackName, operations.LogQuery{
StartTime: startTime,
Resource: resourceFilter,
})
if err != nil {
return err
@ -72,6 +79,9 @@ func newLogsCmd() *cobra.Command {
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
}

View file

@ -11,12 +11,31 @@ type LogEntry struct {
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.
// All fields are optional, leaving them off returns all logs.
type LogQuery struct {
// StartTime is an optional time indiciating that only logs from after this time should be produced.
StartTime *time.Time
EndTime *time.Time
Query *string
// EndTime is an optional time indiciating that only logs from before this time should be produced.
EndTime *time.Time
// Query is a string indicating a filter to apply to the logs - query syntax TBD
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

View file

@ -87,29 +87,34 @@ var _ Provider = (*resourceOperations)(nil)
// GetLogs gets logs for a Resource
func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) {
opsProvider, err := ops.getOperationsProvider()
if err != nil {
return nil, err
}
if opsProvider != nil {
// If this resource has an operations provider - use it and don't recur into children. It is the responsibility
// of it's GetLogs implementation to aggregate all logs from children, either by passing them through or by
// filtering specific content out.
logsResult, err := opsProvider.GetLogs(query)
// 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()
if err != nil {
return logsResult, err
return nil, err
}
if logsResult != nil {
return logsResult, nil
if opsProvider != nil {
// If this resource has an operations provider - use it and don't recur into children. It is the
// responsibility of it's GetLogs implementation to aggregate all logs from children, either by passing them
// through or by filtering specific content out.
logsResult, err := opsProvider.GetLogs(query)
if err != nil {
return logsResult, err
}
if 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
for _, child := range ops.resource.children {
childOps := &resourceOperations{
resource: child,
config: ops.config,
}
// TODO: Parallelize these calls to child GetLogs
// IDEA: Parallelize these calls to child GetLogs
childLogs, err := childOps.GetLogs(query)
if err != nil {
return &logs, err
@ -146,6 +151,31 @@ func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) {
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
func (ops *resourceOperations) ListMetrics() []MetricName {
return []MetricName{}