Skip to content
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

[logp] Add typed loggers allowing log entries to go to different outputs #171

Merged
merged 26 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
53c78d0
[logp] Allow a logger to change its output
belimawr Dec 19, 2023
d6fdd93
Add tests for WithFileOutput
belimawr Jan 16, 2024
ef009ef
PR improvements
belimawr Jan 22, 2024
d2d4622
update to use file or stderr output
belimawr Jan 22, 2024
da04db0
[WIP] Make MakeFileOutput public
belimawr Jan 24, 2024
c5b6944
[WIP] Implement typed outputs
belimawr Jan 25, 2024
f9e923b
Better documentation and cleaning up
belimawr Jan 25, 2024
024f465
Refactoring
belimawr Jan 25, 2024
f14af85
Add defaults for LoggingWithTypedOutputs
belimawr Jan 25, 2024
946ba23
PR improvements
belimawr Jan 26, 2024
1f43dc7
refactoring and improvements
belimawr Jan 26, 2024
e7db2ce
add test case
belimawr Jan 26, 2024
d409684
reduce code duplication and improve comments
belimawr Jan 26, 2024
143752d
fix lint errors
belimawr Jan 26, 2024
0f96b6b
fix typedLoggerCore Check and Write methods
belimawr Jan 26, 2024
211b1e7
more fixes and tests
belimawr Jan 29, 2024
7fa40e2
make lint happy
belimawr Jan 29, 2024
8cd2a27
Refactoring
belimawr Mar 28, 2024
93843ee
Test both logger outputs/files
belimawr Apr 8, 2024
9027d03
Add licence headers
belimawr Apr 8, 2024
f58d98e
Add default config and values
belimawr Apr 8, 2024
75c2402
LoggingWithOutputs does not need typed config
belimawr Apr 8, 2024
ba46f6c
Fix behaviour of typedCore.With
belimawr Apr 9, 2024
ea0f5c4
Correctly handle log selectors when using TypedCore
belimawr Apr 11, 2024
d950786
Add error check
belimawr Apr 12, 2024
8391f6f
Break loop early
belimawr Apr 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions logp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ func DefaultConfig(environment Environment) Config {
}
}

// DefaultEventConfig returns the default config options for the event logger in
// a given environment the Beat is supposed to be run within.
func DefaultEventConfig(environment Environment) Config {
return Config{
Level: defaultLevel,
ToFiles: true,
ToStderr: false,
Files: FileConfig{
MaxSize: 5 * 1024 * 1024, // 5Mb
MaxBackups: 2,
Permissions: 0600,
Interval: 0,
RotateOnStartup: false,
RedirectStderr: false,
Name: "event-data",
},
Metrics: MetricsConfig{
Enabled: false,
},
environment: environment,
addCaller: true,
}
}

// LogFilename returns the base filename to which logs will be written for
// the "files" log output. If another log output is used, or `logging.files.name`
// is unspecified, then the beat name will be returned.
Expand Down
37 changes: 37 additions & 0 deletions logp/configure/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ func init() {
flag.Var((*environmentVar)(&environment), "environment", "set environment being ran in")
}

func GetEnvironment() logp.Environment {
return environment
}

// Logging builds a logp.Config based on the given common.Config and the specified
// CLI flags.
func Logging(beatName string, cfg *config.C) error {
Expand Down Expand Up @@ -75,6 +79,39 @@ func LoggingWithOutputs(beatName string, cfg *config.C, outputs ...zapcore.Core)
return logp.ConfigureWithOutputs(config, outputs...)
}

// LoggingWithTypedOutputs applies some defaults then calls ConfigureWithTypedOutputs
func LoggingWithTypedOutputs(beatName string, cfg, typedCfg *config.C, logKey, kind string, outputs ...zapcore.Core) error {
config := logp.DefaultConfig(environment)
config.Beat = beatName
if cfg != nil {
if err := cfg.Unpack(&config); err != nil {
return err
}
}

applyFlags(&config)

typedLogpConfig := logp.DefaultEventConfig(environment)
defaultName := typedLogpConfig.Files.Name
typedLogpConfig.Beat = beatName
if typedCfg != nil {
if err := typedCfg.Unpack(&typedLogpConfig); err != nil {
return fmt.Errorf("cannot unpack typed output config: %w", err)
}
}

// Make sure we're always running on the same log level
typedLogpConfig.Level = config.Level
typedLogpConfig.Selectors = config.Selectors

// If the name has not been configured, make it {beatName}-events-data
if typedLogpConfig.Files.Name == defaultName {
typedLogpConfig.Files.Name = beatName + "-events-data"
}

return logp.ConfigureWithTypedOutput(config, typedLogpConfig, logKey, kind, outputs...)
}

func applyFlags(cfg *logp.Config) {
if toStderr {
cfg.ToStderr = true
Expand Down
93 changes: 78 additions & 15 deletions logp/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"errors"
"flag"
"fmt"
"io/ioutil"
"io"
golog "log"
"os"
"path/filepath"
Expand Down Expand Up @@ -68,36 +68,33 @@ func Configure(cfg Config) error {
return ConfigureWithOutputs(cfg)
}

// ConfigureWithOutputs XXX: is used by elastic-agent only (See file: x-pack/elastic-agent/pkg/core/logger/logger.go).
// The agent requires that the output specified in the config object is configured and merged with the
// logging outputs given.
func ConfigureWithOutputs(cfg Config, outputs ...zapcore.Core) error {
func createSink(defaultLoggerCfg Config, outputs ...zapcore.Core) (zapcore.Core, zap.AtomicLevel, *observer.ObservedLogs, map[string]struct{}, error) {
var (
sink zapcore.Core
observedLogs *observer.ObservedLogs
err error
level zap.AtomicLevel
)

level = zap.NewAtomicLevelAt(cfg.Level.ZapLevel())
level = zap.NewAtomicLevelAt(defaultLoggerCfg.Level.ZapLevel())
// Build a single output (stderr has priority if more than one are enabled).
if cfg.toObserver {
if defaultLoggerCfg.toObserver {
sink, observedLogs = observer.New(level)
} else {
sink, err = createLogOutput(cfg, level)
sink, err = createLogOutput(defaultLoggerCfg, level)
}
if err != nil {
return fmt.Errorf("failed to build log output: %w", err)
return nil, level, nil, nil, fmt.Errorf("failed to build log output: %w", err)
Copy link
Contributor

@michalpristas michalpristas Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks wild. would it make sense to return (struct {core, level, logs}, error)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe so. It's an internal function and those values are not all tightly correlated.

}

// Default logger is always discard, debug level below will
// possibly re-enable it.
golog.SetOutput(ioutil.Discard)
golog.SetOutput(io.Discard)

// Enabled selectors when debug is enabled.
selectors := make(map[string]struct{}, len(cfg.Selectors))
if cfg.Level.Enabled(DebugLevel) && len(cfg.Selectors) > 0 {
for _, sel := range cfg.Selectors {
selectors := make(map[string]struct{}, len(defaultLoggerCfg.Selectors))
if defaultLoggerCfg.Level.Enabled(DebugLevel) && len(defaultLoggerCfg.Selectors) > 0 {
for _, sel := range defaultLoggerCfg.Selectors {
selectors[strings.TrimSpace(sel)] = struct{}{}
}

Expand All @@ -118,7 +115,73 @@ func ConfigureWithOutputs(cfg Config, outputs ...zapcore.Core) error {
}

sink = newMultiCore(append(outputs, sink)...)
root := zap.New(sink, makeOptions(cfg)...)

return sink, level, observedLogs, selectors, err
}

// ConfigureWithOutputs configures the global logger to use an output created
// from `defaultLoggerCfg` and all the outputs passed by `outputs`.
// This function needs to be exported because it's used by `logp/configure`
func ConfigureWithOutputs(defaultLoggerCfg Config, outputs ...zapcore.Core) error {
sink, level, observedLogs, selectors, err := createSink(defaultLoggerCfg, outputs...)
if err != nil {
return err
}
root := zap.New(sink, makeOptions(defaultLoggerCfg)...)
storeLogger(&coreLogger{
selectors: selectors,
rootLogger: root,
globalLogger: root.WithOptions(zap.AddCallerSkip(1)),
logger: newLogger(root, ""),
level: level,
observedLogs: observedLogs,
})
return nil
}

// ConfigureWithTypedOutput configures the global logger to use typed outputs.
//
// If a log entry matches the defined key/value, this entry is logged using the
// core generated from `typedLoggerCfg`, otherwise it will be logged by all
// cores in `outputs` and the one generated from `defaultLoggerCfg`.
// Arguments:
// - `defaultLoggerCfg` is used to create a new core that will be the default
// output from the logger
// - `typedLoggerCfg` is used to create a new output that will only be used
// when the log entry matches `entry[logKey] = kind`
// - `key` is the key the typed logger will look at
// - `value` is the value compared against the `logKey` entry
// - `outputs` is a list of cores that will be added together with the core
// generated by `defaultLoggerCfg` as the default output for the loggger.
//
// If `defaultLoggerCfg.toObserver` is true, then `typedLoggerCfg` is ignored
// and a single sink is used so all logs can be observed.
func ConfigureWithTypedOutput(defaultLoggerCfg, typedLoggerCfg Config, key, value string, outputs ...zapcore.Core) error {
sink, level, observedLogs, selectors, err := createSink(defaultLoggerCfg, outputs...)
if err != nil {
return err
}

var typedCore zapcore.Core
if defaultLoggerCfg.toObserver {
typedCore = sink
} else {
typedCore, err = createLogOutput(typedLoggerCfg, level)
}
if err != nil {
return fmt.Errorf("could not create typed logger output: %w", err)
}

sink = &typedLoggerCore{
defaultCore: sink,
typedCore: typedCore,
key: key,
value: value,
}

sink = selectiveWrapper(sink, selectors)

root := zap.New(sink, makeOptions(defaultLoggerCfg)...)
storeLogger(&coreLogger{
selectors: selectors,
rootLogger: root,
Expand Down Expand Up @@ -215,7 +278,7 @@ func makeStderrOutput(cfg Config, enab zapcore.LevelEnabler) (zapcore.Core, erro
}

func makeDiscardOutput(cfg Config, enab zapcore.LevelEnabler) (zapcore.Core, error) {
discard := zapcore.AddSync(ioutil.Discard)
discard := zapcore.AddSync(io.Discard)
return newCore(buildEncoder(cfg), discard, enab), nil
}

Expand Down
Loading
Loading