-
Notifications
You must be signed in to change notification settings - Fork 22
/
logger.go
196 lines (164 loc) · 5.36 KB
/
logger.go
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package logger
import (
"errors"
"fmt"
"os"
"strings"
"sync"
"github.com/iotaledger/hive.go/v2/configuration"
"github.com/iotaledger/hive.go/v2/typeutils"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// The Logger uses the sugared logger.
type Logger = zap.SugaredLogger
// A Level is a logging priority. Higher levels are more important.
type Level = zapcore.Level
const (
// LevelDebug logs are typically voluminous, and are usually disabled in production.
LevelDebug = zapcore.DebugLevel
// LevelInfo is the default logging priority.
LevelInfo = zapcore.InfoLevel
// LevelWarn logs are more important than Info, but don't need individual human review.
LevelWarn = zapcore.WarnLevel
// LevelError logs are high-priority.
// If an application is running as expected, there shouldn't be any error-level logs.
LevelError = zapcore.ErrorLevel
// LevelPanic logs a message, then panics.
LevelPanic = zapcore.PanicLevel
// LevelFatal logs a message, then calls os.Exit(1).
LevelFatal = zapcore.FatalLevel
)
// ErrGlobalLoggerAlreadyInitialized is returned when InitGlobalLogger is called more than once.
var ErrGlobalLoggerAlreadyInitialized = errors.New("global logger already initialized")
var (
level = zap.NewAtomicLevel()
logger *Logger
initialized typeutils.AtomicBool // true, if the global logger was successfully initialized
mu sync.Mutex // prevents multiple initializations at the same time
)
// InitGlobalLogger initializes the global logger from the provided config.
func InitGlobalLogger(config *configuration.Configuration) error {
mu.Lock()
defer mu.Unlock()
if initialized.IsSet() {
return ErrGlobalLoggerAlreadyInitialized
}
root, err := NewRootLoggerFromConfiguration(config, level)
if err != nil {
return err
}
logger = root
initialized.Set()
return nil
}
// NewRootLoggerFromConfiguration creates a new root logger from the provided configuration.
func NewRootLoggerFromConfiguration(config *configuration.Configuration, levelOverride ...zap.AtomicLevel) (*Logger, error) {
cfg := defaultCfg
// get config values one by one
// config.UnmarshalKey does not recognize a configuration group when defined with pflags
if val := config.String(ConfigurationKeyLevel); val != "" {
cfg.Level = val
}
if val := config.Get(ConfigurationKeyDisableCaller); val != nil {
cfg.DisableCaller = val.(bool)
}
if val := config.Get(ConfigurationKeyDisableStacktrace); val != nil {
cfg.DisableStacktrace = val.(bool)
}
if val := config.String(ConfigurationKeyEncoding); val != "" {
cfg.Encoding = val
}
if val := config.Strings(ConfigurationKeyOutputPaths); len(val) > 0 {
cfg.OutputPaths = val
}
if val := config.Get(ConfigurationKeyDisableEvents); val != nil {
cfg.DisableEvents = val.(bool)
}
return NewRootLogger(cfg, levelOverride...)
}
// NewRootLogger creates a new root logger from the provided configuration.
func NewRootLogger(cfg Config, levelOverride ...zap.AtomicLevel) (*Logger, error) {
var (
cores []zapcore.Core
opts []zap.Option
level Level
)
if err := level.UnmarshalText([]byte(cfg.Level)); err != nil {
return nil, err
}
enc, err := newEncoder(cfg.Encoding, defaultEncoderConfig)
if err != nil {
return nil, err
}
var enabler zapcore.LevelEnabler = level
if len(levelOverride) > 0 {
atomic := levelOverride[0]
atomic.SetLevel(level)
enabler = atomic
}
// write errors generated by the logger to stderr
opts = append(opts, zap.ErrorOutput(zapcore.Lock(os.Stderr)))
// create the logger only if there is at least one output path
if len(cfg.OutputPaths) > 0 {
ws, _, err := zap.Open(cfg.OutputPaths...)
if err != nil {
return nil, err
}
core := zapcore.NewCore(enc, ws, enabler)
cores = append(cores, core)
// add required options
opts = append(opts, buildOptions(cfg)...)
}
// add the event logging
if !cfg.DisableEvents {
cores = append(cores, NewEventCore(enabler))
}
// create the logger
logger := zap.New(zapcore.NewTee(cores...), opts...)
return logger.Sugar(), nil
}
// SetLevel alters the logging level of the global logger.
func SetLevel(l Level) {
level.SetLevel(l)
}
// NewLogger returns a new named child of the global root logger.
func NewLogger(name string) *Logger {
if !initialized.IsSet() {
panic("global logger not initialized")
}
return logger.Named(name)
}
// NewExampleLogger builds a Logger that's designed to be only used in tests or examples.
// It writes debug and above logs to standard out as JSON, but omits the timestamp and calling function to keep
// example output short and deterministic.
func NewExampleLogger(name string) *Logger {
root := zap.NewExample()
return root.Named(name).Sugar()
}
// NewNopLogger returns a no-op Logger.
// It never writes out logs or internal errors
func NewNopLogger() *Logger {
return zap.NewNop().Sugar()
}
func newEncoder(name string, cfg zapcore.EncoderConfig) (zapcore.Encoder, error) {
switch strings.ToLower(name) {
case "console", "":
return zapcore.NewConsoleEncoder(cfg), nil
case "json":
return zapcore.NewJSONEncoder(cfg), nil
}
return nil, fmt.Errorf("no encoder registered for name %q", name)
}
func buildOptions(cfg Config) []zap.Option {
var opts []zap.Option
if !cfg.DisableCaller {
// add caller to the log
opts = append(opts, zap.AddCaller())
}
if !cfg.DisableStacktrace {
// add stackstrace for error or higher
opts = append(opts, zap.AddStacktrace(zap.ErrorLevel))
}
return opts
}