summaryrefslogtreecommitdiffstats
path: root/pkg/logging/logging.go
blob: e3106956a4af4601a85a88585eb6b3b68d1f8b34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package logging

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
	"sync"
	"time"
)

const (
	CONSOLE = "console"
	JOURNAL = "systemd-journald"
)

// defaultEncConfig defines the default zapcore.EncoderConfig for the logging package.
var defaultEncConfig = zapcore.EncoderConfig{
	TimeKey:        "ts",
	LevelKey:       "level",
	NameKey:        "logger",
	CallerKey:      "caller",
	MessageKey:     "msg",
	StacktraceKey:  "stacktrace",
	LineEnding:     zapcore.DefaultLineEnding,
	EncodeLevel:    zapcore.CapitalLevelEncoder,
	EncodeTime:     zapcore.ISO8601TimeEncoder,
	EncodeDuration: zapcore.StringDurationEncoder,
	EncodeCaller:   zapcore.ShortCallerEncoder,
}

// Options define child loggers with their desired log level.
type Options map[string]zapcore.Level

// Logging implements access to a default logger and named child loggers.
// Log levels can be configured per named child via Options which, if not configured,
// fall back on a default log level.
// Logs either to the console or to systemd-journald.
type Logging struct {
	logger    *Logger
	output    string
	verbosity zap.AtomicLevel
	interval  time.Duration

	// coreFactory creates zapcore.Core based on the log level and the log output.
	coreFactory func(zap.AtomicLevel) zapcore.Core

	mu      sync.Mutex
	loggers map[string]*Logger

	options Options
}

// NewLogging takes the name and log level for the default logger,
// output where log messages are written to,
// options having log levels for named child loggers
// and returns a new Logging.
func NewLogging(name string, level zapcore.Level, output string, options Options, interval time.Duration) (*Logging, error) {
	verbosity := zap.NewAtomicLevelAt(level)

	var coreFactory func(zap.AtomicLevel) zapcore.Core
	switch output {
	case CONSOLE:
		enc := zapcore.NewConsoleEncoder(defaultEncConfig)
		ws := zapcore.Lock(os.Stderr)
		coreFactory = func(verbosity zap.AtomicLevel) zapcore.Core {
			return zapcore.NewCore(enc, ws, verbosity)
		}
	case JOURNAL:
		coreFactory = func(verbosity zap.AtomicLevel) zapcore.Core {
			return NewJournaldCore(name, verbosity)
		}
	default:
		return nil, invalidOutput(output)
	}

	logger := NewLogger(zap.New(coreFactory(verbosity)).Named(name).Sugar(), interval)

	return &Logging{
			logger:      logger,
			output:      output,
			verbosity:   verbosity,
			interval:    interval,
			coreFactory: coreFactory,
			loggers:     make(map[string]*Logger),
			options:     options,
		},
		nil
}

// GetChildLogger returns a named child logger.
// Log levels for named child loggers are obtained from the logging options and, if not found,
// set to the default log level.
func (l *Logging) GetChildLogger(name string) *Logger {
	l.mu.Lock()
	defer l.mu.Unlock()

	if logger, ok := l.loggers[name]; ok {
		return logger
	}

	var verbosity zap.AtomicLevel
	if level, found := l.options[name]; found {
		verbosity = zap.NewAtomicLevelAt(level)
	} else {
		verbosity = l.verbosity
	}

	logger := NewLogger(zap.New(l.coreFactory(verbosity)).Named(name).Sugar(), l.interval)
	l.loggers[name] = logger

	return logger
}

// GetLogger returns the default logger.
func (l *Logging) GetLogger() *Logger {
	return l.logger
}

// AssertOutput returns an error if output is not a valid logger output.
func AssertOutput(o string) error {
	if o == CONSOLE || o == JOURNAL {
		return nil
	}

	return invalidOutput(o)
}

func invalidOutput(o string) error {
	return fmt.Errorf("%s is not a valid logger output. Must be either %q or %q", o, CONSOLE, JOURNAL)
}