Skip to content
35 changes: 35 additions & 0 deletions logp/configure/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func LoggingWithOutputs(beatName string, cfg *config.C, outputs ...zapcore.Core)
}

// LoggingWithTypedOutputs applies some defaults then calls ConfigureWithTypedOutputs
//
// Deprecated: Prefer using localized loggers. Use logp.LoggingWithTypedOutputsLocal.
func LoggingWithTypedOutputs(beatName string, cfg, typedCfg *config.C, logKey, kind string, outputs ...zapcore.Core) error {
config := logp.DefaultConfig(environment)
config.Beat = beatName
Expand Down Expand Up @@ -112,6 +114,39 @@ func LoggingWithTypedOutputs(beatName string, cfg, typedCfg *config.C, logKey, k
return logp.ConfigureWithTypedOutput(config, typedLogpConfig, logKey, kind, outputs...)
}

// LoggingWithTypedOutputs applies some defaults and returns a logger instance
func LoggingWithTypedOutputsLocal(beatName string, cfg, typedCfg *config.C, logKey, kind string, outputs ...zapcore.Core) (*logp.Logger, error) {
config := logp.DefaultConfig(environment)
config.Beat = beatName
if cfg != nil {
if err := cfg.Unpack(&config); err != nil {
return nil, 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 nil, 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.ConfigureWithTypedOutputLocal(config, typedLogpConfig, logKey, kind, outputs...)
}

func applyFlags(cfg *logp.Config) {
if toStderr {
cfg.ToStderr = true
Expand Down
109 changes: 109 additions & 0 deletions logp/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ func ConfigureWithOutputs(defaultLoggerCfg Config, outputs ...zapcore.Core) erro
// only used to set selectors and level. This is useful if a part of
// your code uses logp but the log output is already handled. Normal
// use cases should use Configure or ConfigureWithOutput.
//
// Deprecated: Prefer using localized loggers. Use logp.ConfigureWithCoreLocal.
func ConfigureWithCore(loggerCfg Config, core zapcore.Core) error {
var (
sink zapcore.Core
Expand Down Expand Up @@ -202,6 +204,56 @@ func ConfigureWithCore(loggerCfg Config, core zapcore.Core) error {
return nil
}

// ConfigureWithCoreLocal returns a logger using passed in
// core. It is assumed that an output has already been defined with
// the core and a new one should not be created. The loggerCfg is
// only used to set selectors and level.
func ConfigureWithCoreLocal(loggerCfg Config, core zapcore.Core) (*Logger, error) {
var (
sink zapcore.Core
level zap.AtomicLevel
)

level = zap.NewAtomicLevelAt(loggerCfg.Level.ZapLevel())

if loggerCfg.WithFields != nil {
fields := make([]zapcore.Field, 0, len(loggerCfg.WithFields))
for key, value := range loggerCfg.WithFields {
fields = append(fields, zap.Any(key, value))
}
core = core.With(fields)
}

sink = wrappedCore(core)

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

// Default to all enabled if no selectors are specified.
if len(selectors) == 0 {
selectors["*"] = struct{}{}
}

sink = selectiveWrapper(sink, selectors)
}

root := zap.New(sink, makeOptions(loggerCfg)...)
// TODO: Remove this when there is no more global logger dependency
storeLogger(&coreLogger{
selectors: selectors,
rootLogger: root,
globalLogger: root.WithOptions(zap.AddCallerSkip(1)),
logger: newLogger(root, ""),
level: level,
observedLogs: nil,
})
return newLogger(root, ""), 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
Expand All @@ -219,6 +271,8 @@ func ConfigureWithCore(loggerCfg Config, core zapcore.Core) error {
//
// If `defaultLoggerCfg.toObserver` is true, then `typedLoggerCfg` is ignored
// and a single sink is used so all logs can be observed.
//
// Deprecated: Prefer using localized loggers. Use logp.ConfigureWithTypedOutputLocal.
func ConfigureWithTypedOutput(defaultLoggerCfg, typedLoggerCfg Config, key, value string, outputs ...zapcore.Core) error {
sink, level, observedLogs, selectors, err := createSink(defaultLoggerCfg, outputs...)
if err != nil {
Expand Down Expand Up @@ -256,6 +310,57 @@ func ConfigureWithTypedOutput(defaultLoggerCfg, typedLoggerCfg Config, key, valu
return nil
}

// ConfigureWithTypedOutputLocal returns a logger that uses 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.
func ConfigureWithTypedOutputLocal(defaultLoggerCfg, typedLoggerCfg Config, key, value string, outputs ...zapcore.Core) (*Logger, error) {
sink, level, observedLogs, selectors, err := createSink(defaultLoggerCfg, outputs...)
if err != nil {
return nil, err
}

var typedCore zapcore.Core
typedCore, err = createLogOutput(typedLoggerCfg, level)

if err != nil {
return nil, 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)...)

// TODO: Remove this when there is no more global logger dependency
storeLogger(&coreLogger{
selectors: selectors,
rootLogger: root,
globalLogger: root.WithOptions(zap.AddCallerSkip(1)),
logger: newLogger(root, ""),
level: level,
observedLogs: observedLogs,
})
logger := newLogger(root, "")
return logger, nil
}

func createLogOutput(cfg Config, enab zapcore.LevelEnabler) (zapcore.Core, error) {
switch {
case cfg.toIODiscard:
Expand All @@ -282,6 +387,8 @@ func createLogOutput(cfg Config, enab zapcore.LevelEnabler) (zapcore.Core, error

// DevelopmentSetup configures the logger in development mode at debug level.
// By default the output goes to stderr.
//
// Deprecated: Prefer using localized loggers. Use logp.NewDevelopmentLogger.
func DevelopmentSetup(options ...Option) error {
cfg := Config{
Level: DebugLevel,
Expand All @@ -297,6 +404,8 @@ func DevelopmentSetup(options ...Option) error {

// TestingSetup configures logging by calling DevelopmentSetup if and only if
// verbose testing is enabled (as in 'go test -v').
//
// Deprecated: Prefer using localized loggers. Use logp.NewTestingLogger.
func TestingSetup(options ...Option) error {
// Use the flag to avoid a dependency on the testing package.
f := flag.Lookup("test.v")
Expand Down
60 changes: 60 additions & 0 deletions logp/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@ import (
"bytes"
"fmt"
"io"
"testing"

"go.elastic.co/ecszap"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zaptest"
)

// LogOption configures a Logger.
type LogOption = zap.Option
type LogTestOption = zaptest.LoggerOption

// Logger logs messages to the configured output.
type Logger struct {
Expand All @@ -53,12 +56,41 @@ func NewLogger(selector string, options ...LogOption) *Logger {
return newLogger(loadLogger().rootLogger, selector, options...)
}

// NewProductionLogger returns a production suitable logp.Logger
func NewProductionLogger(selector string, options ...LogOption) (*Logger, error) {
log, err := zap.NewProduction(options...)
log = log.Named(selector)
if err != nil {
return nil, err
}
return &Logger{log, log.Sugar()}, nil
}

// NewDevelopmentLogger returns a development suitable logp.Logger
func NewDevelopmentLogger(selector string, options ...LogOption) (*Logger, error) {
log, err := zap.NewDevelopment(options...)
log = log.Named(selector)
if err != nil {
return nil, err
}
return &Logger{log, log.Sugar()}, nil
}

// NewTestingLogger returns a testing suitable logp.Logger.
func NewTestingLogger(t *testing.T, selector string, options ...LogTestOption) *Logger {
log := zaptest.NewLogger(t, options...)
log = log.Named(selector)
return &Logger{log, log.Sugar()}
}

// NewInMemory returns a new in-memory logger along with the buffer to which it
// logs. It's goroutine safe, but operating directly on the returned buffer is not.
// This logger is primary intended for short and simple use-cases such as printing
// the full logs only when an operation fails.
// encCfg configures the log format, use logp.ConsoleEncoderConfig for console
// format, logp.JSONEncoderConfig for JSON or any other valid zapcore.EncoderConfig.
//
// Deprecated: Prefer using localized loggers. Use logp.NewInMemoryLocal.
func NewInMemory(selector string, encCfg zapcore.EncoderConfig) (*Logger, *bytes.Buffer) {
buff := bytes.Buffer{}

Expand All @@ -81,6 +113,34 @@ func NewInMemory(selector string, encCfg zapcore.EncoderConfig) (*Logger, *bytes
return logger, &buff
}

// NewInMemory returns a new in-memory logger along with the buffer to which it
// logs. It's goroutine safe, but operating directly on the returned buffer is not.
// This logger is primary intended for short and simple use-cases such as printing
// the full logs only when an operation fails.
// encCfg configures the log format, use logp.ConsoleEncoderConfig for console
// format, logp.JSONEncoderConfig for JSON or any other valid zapcore.EncoderConfig.
func NewInMemoryLocal(selector string, encCfg zapcore.EncoderConfig) (*Logger, *bytes.Buffer) {
buff := bytes.Buffer{}

encoderConfig := ecszap.ECSCompatibleEncoderConfig(encCfg)
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)

core := zapcore.NewCore(
encoder,
zapcore.Lock(zapcore.AddSync(&buff)),
zap.NewAtomicLevelAt(zap.DebugLevel))
ecszap.ECSCompatibleEncoderConfig(ConsoleEncoderConfig())

logger, _ := NewDevelopmentLogger(
selector,
zap.WrapCore(func(in zapcore.Core) zapcore.Core {
return core
}))

return logger, &buff
}

// WithOptions returns a clone of l with options applied.
func (l *Logger) WithOptions(options ...LogOption) *Logger {
cloned := l.logger.WithOptions(options...)
Expand Down
Loading