-
Notifications
You must be signed in to change notification settings - Fork 209
[Logging] Add support for component scoped log-levels #8477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
peterargue
wants to merge
27
commits into
master
Choose a base branch
from
peter/dynamic-logging
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
9ddc4ba
add design doc for dynamic per-component log levels
peterargue 4d3811a
add implementation plan for dynamic per-component log levels
peterargue 50a9787
add componentLevelWriter for per-component log filtering
peterargue 4779ce9
add LogRegistry with component-level writer and resolution priority
peterargue b40a4d8
use maps.Copy instead of manual copy loop in NewLogRegistry
peterargue b746b9b
add SetLevel, Reset, SetDefaultLevel, Levels to LogRegistry
peterargue 84e0e3d
simplify HasSuffix+TrimSuffix to CutSuffix in registry
peterargue 9a64cc0
add ParseComponentLogLevels for CLI flag parsing
peterargue b598779
use strings.SplitSeq instead of Split for range in ParseComponentLogL…
peterargue 730b0bd
add get-component-log-levels admin command
peterargue 85cd99e
update set-log-level to delegate to LogRegistry.SetDefaultLevel
peterargue 3f8a687
add set-component-log-level admin command
peterargue 09ba164
add reset-component-log-level admin command
peterargue dff0451
wire LogRegistry into NodeConfig, initLogger, and admin commands
peterargue 9d49d64
add end-to-end output filtering tests for LogRegistry
peterargue 88c20f6
rename admin command files
peterargue 0e82100
add pattern validation and normalization to registry, parse, and admi…
peterargue 820b972
add LoggerFrom to preserve parent context fields in child registrations
peterargue 0325ddb
simplify LogRegistry: single Logger(parent, id) method, remove baseLo…
peterargue 9c01da7
tighten ParseComponentLogLevels: reject multiple colons, normalize le…
peterargue 9919aae
fix TestLogRegistry_NormalizesComponentID to actually prove shared re…
peterargue 47f296c
improve logging and tests. add benchmark
peterargue b443251
refactor benchmark test
peterargue ecfb0f0
cleanup godocs
peterargue b2b23c8
move validation into registry
peterargue e62b258
apply review feedback
peterargue d62d609
Merge branch 'master' into peter/dynamic-logging
peterargue File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package common | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/onflow/flow-go/admin" | ||
| "github.com/onflow/flow-go/admin/commands" | ||
| "github.com/onflow/flow-go/utils/logging" | ||
| ) | ||
|
|
||
| var _ commands.AdminCommand = (*ResetComponentLogLevelCommand)(nil) | ||
|
|
||
| // ResetComponentLogLevelCommand removes runtime log level overrides for components matching | ||
| // the specified patterns, restoring them to static config or global default. | ||
| // | ||
| // Input is a JSON array of patterns. ["*"] resets all registered components. | ||
| // "*" may not be mixed with other patterns. | ||
| // | ||
| // Example input: | ||
| // | ||
| // ["hotstuff.voter", "hotstuff.*"] | ||
| // ["*"] | ||
| type ResetComponentLogLevelCommand struct { | ||
| registry *logging.LogRegistry | ||
| } | ||
|
|
||
| // NewResetComponentLogLevelCommand constructs a ResetComponentLogLevelCommand. | ||
| func NewResetComponentLogLevelCommand(registry *logging.LogRegistry) *ResetComponentLogLevelCommand { | ||
| return &ResetComponentLogLevelCommand{registry: registry} | ||
| } | ||
|
|
||
| // Validator validates that the input is a non-empty array of pattern strings. "*" must be | ||
| // the sole element if present. | ||
| // | ||
| // Returns [admin.InvalidAdminReqError] for invalid or malformed requests. | ||
| func (r *ResetComponentLogLevelCommand) Validator(req *admin.CommandRequest) error { | ||
| raw, ok := req.Data.([]interface{}) | ||
| if !ok { | ||
| return admin.NewInvalidAdminReqFormatError("input must be a JSON array of pattern strings") | ||
| } | ||
| if len(raw) == 0 { | ||
| return admin.NewInvalidAdminReqFormatError("input must not be empty") | ||
| } | ||
|
|
||
| patterns := make([]string, 0, len(raw)) | ||
| for _, v := range raw { | ||
| pattern, ok := v.(string) | ||
| if !ok { | ||
| return admin.NewInvalidAdminReqFormatError("each element must be a string") | ||
| } | ||
| pattern = logging.NormalizePattern(strings.TrimSpace(pattern)) | ||
| if pattern == "*" { | ||
| if len(patterns) > 1 { | ||
| return admin.NewInvalidAdminReqErrorf("\"*\" must be the only pattern when resetting all components") | ||
| } | ||
| } else { | ||
| if err := logging.ValidatePattern(pattern); err != nil { | ||
| return admin.NewInvalidAdminReqErrorf("invalid pattern %q: %w", pattern, err) | ||
| } | ||
| } | ||
| patterns = append(patterns, pattern) | ||
| } | ||
|
|
||
| req.ValidatorData = patterns | ||
| return nil | ||
| } | ||
|
|
||
| // Handler removes the specified runtime overrides and returns "ok". | ||
| // | ||
| // No error returns are expected during normal operation. | ||
| func (r *ResetComponentLogLevelCommand) Handler(_ context.Context, req *admin.CommandRequest) (interface{}, error) { | ||
| patterns := req.ValidatorData.([]string) | ||
| if err := r.registry.Reset(patterns...); err != nil { | ||
| return nil, fmt.Errorf("failed to reset component log levels: %w", err) | ||
| } | ||
| return "ok", nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package common | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/rs/zerolog" | ||
|
|
||
| "github.com/onflow/flow-go/admin" | ||
| "github.com/onflow/flow-go/admin/commands" | ||
| "github.com/onflow/flow-go/utils/logging" | ||
| ) | ||
|
|
||
| const ( | ||
| maxPatternLength = 1024 | ||
| ) | ||
|
|
||
| var _ commands.AdminCommand = (*SetComponentLogLevelCommand)(nil) | ||
|
|
||
| // SetComponentLogLevelCommand sets the log level for one or more components identified by | ||
| // exact or wildcard patterns. Input is a JSON object mapping pattern to level string. | ||
| // | ||
| // Example input: | ||
| // | ||
| // {"hotstuff.voter": "debug", "network.*": "warn"} | ||
| type SetComponentLogLevelCommand struct { | ||
| registry *logging.LogRegistry | ||
| } | ||
|
|
||
| // NewSetComponentLogLevelCommand constructs a SetComponentLogLevelCommand. | ||
| func NewSetComponentLogLevelCommand(registry *logging.LogRegistry) *SetComponentLogLevelCommand { | ||
| return &SetComponentLogLevelCommand{registry: registry} | ||
| } | ||
|
|
||
| type parsedComponentLevel struct { | ||
| pattern string | ||
| level zerolog.Level | ||
| } | ||
|
|
||
| // Validator validates that the input is a non-empty map of pattern → level string with | ||
| // recognisable level values. | ||
| // | ||
| // Returns [admin.InvalidAdminReqError] for invalid or malformed requests. | ||
| func (s *SetComponentLogLevelCommand) Validator(req *admin.CommandRequest) error { | ||
| raw, ok := req.Data.(map[string]interface{}) | ||
| if !ok { | ||
| return admin.NewInvalidAdminReqFormatError("input must be a JSON object mapping component pattern to level string") | ||
| } | ||
| if len(raw) == 0 { | ||
| return admin.NewInvalidAdminReqFormatError("input must not be empty") | ||
| } | ||
|
|
||
| parsed := make([]parsedComponentLevel, 0, len(raw)) | ||
| for pattern, val := range raw { | ||
| levelStr, ok := val.(string) | ||
| if !ok { | ||
| return admin.NewInvalidAdminReqErrorf("level for %q must be a string", pattern) | ||
| } | ||
| level, err := zerolog.ParseLevel(levelStr) | ||
| if err != nil { | ||
| return admin.NewInvalidAdminReqErrorf("invalid level %q for component %q: %w", levelStr, pattern, err) | ||
| } | ||
| if len(pattern) > maxPatternLength { | ||
| return admin.NewInvalidAdminReqErrorf("pattern %q is too long (max %d characters)", pattern, maxPatternLength) | ||
| } | ||
| pattern = logging.NormalizePattern(strings.TrimSpace(pattern)) | ||
| if pattern == "*" { | ||
| return admin.NewInvalidAdminReqErrorf("global wildcard \"*\" is not a valid when setting component level logging. use set-log-level instead") | ||
| } | ||
| if err := logging.ValidatePattern(logging.NormalizePattern(pattern)); err != nil { | ||
| return admin.NewInvalidAdminReqErrorf("invalid pattern %q: %w", pattern, err) | ||
| } | ||
|
|
||
| parsed = append(parsed, parsedComponentLevel{pattern: pattern, level: level}) | ||
| } | ||
|
|
||
| req.ValidatorData = parsed | ||
| return nil | ||
| } | ||
|
|
||
| // Handler applies the validated component level overrides and returns the updated patterns. | ||
| // | ||
| // No error returns are expected during normal operation. | ||
| func (s *SetComponentLogLevelCommand) Handler(_ context.Context, req *admin.CommandRequest) (interface{}, error) { | ||
| entries := req.ValidatorData.([]parsedComponentLevel) | ||
|
|
||
| result := make(map[string]string, len(entries)) | ||
| for _, e := range entries { | ||
| if err := s.registry.SetLevel(e.pattern, e.level); err != nil { | ||
| return nil, fmt.Errorf("failed to set level for pattern %q: %w", e.pattern, err) | ||
| } | ||
| result[e.pattern] = fmt.Sprintf("set to %s", e.level) | ||
| } | ||
| return result, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package common | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/rs/zerolog" | ||
|
|
||
| "github.com/onflow/flow-go/admin" | ||
| "github.com/onflow/flow-go/admin/commands" | ||
| "github.com/onflow/flow-go/utils/logging" | ||
| ) | ||
|
|
||
| var _ commands.AdminCommand = (*GetComponentLogLevelsCommand)(nil) | ||
|
|
||
| // GetComponentLogLevelsCommand returns the current log level for every registered component | ||
| // and the global default. | ||
| type GetComponentLogLevelsCommand struct { | ||
| registry *logging.LogRegistry | ||
| } | ||
|
|
||
| // NewGetComponentLogLevelsCommand constructs a GetComponentLogLevelsCommand. | ||
| func NewGetComponentLogLevelsCommand(registry *logging.LogRegistry) *GetComponentLogLevelsCommand { | ||
| return &GetComponentLogLevelsCommand{registry: registry} | ||
| } | ||
|
|
||
| // Validator performs no validation — this command takes no input. | ||
| // | ||
| // No error returns are expected during normal operation. | ||
| func (g *GetComponentLogLevelsCommand) Validator(_ *admin.CommandRequest) error { | ||
| return nil | ||
| } | ||
|
|
||
| // Handler returns a snapshot of all registered component log levels and the registry config. | ||
| // | ||
| // No error returns are expected during normal operation. | ||
| func (g *GetComponentLogLevelsCommand) Handler(_ context.Context, _ *admin.CommandRequest) (interface{}, error) { | ||
| _, levels := g.registry.Levels() | ||
| cfg := g.registry.Config() | ||
|
|
||
| components := make(map[string]interface{}, len(levels)) | ||
| for id, cl := range levels { | ||
| components[id] = map[string]string{ | ||
| "level": zerolog.Level(cl.Level).String(), | ||
| "source": string(cl.Source), | ||
| } | ||
| } | ||
|
|
||
| staticOverrides := make(map[string]string, len(cfg.StaticOverrides)) | ||
| for pattern, level := range cfg.StaticOverrides { | ||
| staticOverrides[pattern] = level.String() | ||
| } | ||
|
|
||
| dynamicOverrides := make(map[string]string, len(cfg.DynamicOverrides)) | ||
| for pattern, level := range cfg.DynamicOverrides { | ||
| dynamicOverrides[pattern] = level.String() | ||
| } | ||
|
|
||
| return map[string]interface{}{ | ||
| "config": map[string]interface{}{ | ||
| "default": cfg.Default.String(), | ||
| "static_overrides": staticOverrides, | ||
| "dynamic_overrides": dynamicOverrides, | ||
| }, | ||
| "components": components, | ||
| }, nil | ||
| } |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.