-
Notifications
You must be signed in to change notification settings - Fork 25
/
log.go
217 lines (188 loc) · 5.54 KB
/
log.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package config
import (
"context"
"io"
"os"
"path"
"runtime"
"strings"
"github.com/fluxninja/lumberjack"
"github.com/rs/zerolog"
"github.com/rs/zerolog/diode"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/zap"
"github.com/fluxninja/aperture/pkg/info"
"github.com/fluxninja/aperture/pkg/log"
"github.com/fluxninja/aperture/pkg/panichandler"
)
// defaultLogFilePath is the default path for the log files to be stored.
var defaultLogFilePath = path.Join(DefaultLogDirectory, info.Service+".log")
const (
configKey = "log"
stdOutFile = "stdout"
stdErrFile = "stderr"
defaultFile = "default"
)
// LogModule is a fx module that provides a logger and invokes setting global and standard loggers.
func LogModule() fx.Option {
return fx.Options(
LoggerConstructor{ConfigKey: configKey, IsGlobal: true}.Annotate(),
fx.WithLogger(FxLogger()),
)
}
// swagger:operation POST /log common-configuration Log
// ---
// x-fn-config-env: true
// parameters:
// - in: body
// schema:
// "$ref": "#/definitions/LogConfig"
// LogConfig holds configuration for a logger and log writers.
// swagger:model
// +kubebuilder:object:generate=true
type LogConfig struct {
// Log level
LogLevel string `json:"level" validate:"oneof=debug DEBUG info INFO warn WARN error ERROR fatal FATAL panic PANIC trace TRACE disabled DISABLED" default:"info"`
// Log writers
Writers []LogWriterConfig `json:"writers,omitempty" validate:"omitempty,dive,omitempty"`
// Use non-blocking log writer (can lose logs at high throughput)
NonBlocking bool `json:"non_blocking" default:"true"`
// Additional log writer: pretty console (stdout) logging (not recommended for prod environments)
PrettyConsole bool `json:"pretty_console" default:"false"`
}
// LogWriterConfig holds configuration for a log writer.
// swagger:model
// +kubebuilder:object:generate=true
type LogWriterConfig struct {
// Output file for logs. Keywords allowed - ["stderr", "default"]. "default" maps to `/var/log/fluxninja/<service>.log`
File string `json:"file" default:"stderr"`
// Log file max size in MB
MaxSize int `json:"max_size" validate:"gte=0" default:"50"`
// Max log file backups
MaxBackups int `json:"max_backups" validate:"gte=0" default:"3"`
// Max age in days for log files
MaxAge int `json:"max_age" validate:"gte=0" default:"7"`
// Compress
Compress bool `json:"compress" default:"false"`
}
// LoggerConstructor holds fields used to create an annotated instance of a logger.
type LoggerConstructor struct {
// Name of logger instance
Name string
// Config key
ConfigKey string
// Default Config
DefaultConfig LogConfig
// Global logger
IsGlobal bool
}
// Annotate creates an annotated instance of loggers which can be used to create multiple loggers.
func (constructor LoggerConstructor) Annotate() fx.Option {
var name, group string
name = NameTag(constructor.Name)
if constructor.Name == "" {
group = GroupTag("main-logger")
} else {
group = GroupTag(constructor.Name)
}
return fx.Provide(
fx.Annotate(
constructor.provideLogger,
fx.ParamTags(group),
fx.ResultTags(name),
),
)
}
func (constructor LoggerConstructor) provideLogger(w []io.Writer,
unmarshaller Unmarshaller,
lifecycle fx.Lifecycle,
) (*log.Logger, error) {
config := constructor.DefaultConfig
if err := unmarshaller.UnmarshalKey(constructor.ConfigKey, &config); err != nil {
log.Panic().Err(err).Msg("Unable to deserialize log configuration!")
}
var writers []io.Writer
// append file writers
for _, writerConfig := range config.Writers {
var writer io.Writer
if writerConfig.File != "" {
switch writerConfig.File {
case stdErrFile:
writer = os.Stderr
case stdOutFile:
writer = os.Stdout
default:
if writerConfig.File == defaultFile {
writerConfig.File = defaultLogFilePath
}
lj := &lumberjack.Logger{
Filename: writerConfig.File,
MaxSize: writerConfig.MaxSize,
MaxBackups: writerConfig.MaxBackups,
MaxAge: writerConfig.MaxAge,
Compress: writerConfig.Compress,
}
// Set finalizer to automatically close file writers
runtime.SetFinalizer(lj, func(lj *lumberjack.Logger) {
log.Debug().Msg("Closing lumberjack file writer")
_ = lj.Close()
})
writer = lj
}
writers = append(writers, writer)
}
}
if config.PrettyConsole {
writers = append(writers, log.GetPrettyConsoleWriter())
}
multi := zerolog.MultiLevelWriter(writers...)
var wr io.Writer
if config.NonBlocking {
// Use diode writer
dr := diode.NewWriter(multi, 1000, 0, func(missed int) {
log.Printf("Dropped %d messages", missed)
})
wr = dr
} else {
// use sync writer
wr = zerolog.SyncWriter(multi)
}
logger := log.NewLogger(wr, strings.ToLower(config.LogLevel))
// append writers provided via Fx
writers = append(writers, w...)
if constructor.IsGlobal {
// set global logger
log.SetGlobalLogger(logger)
// set standard loggers
log.SetStdLogger(logger)
}
lifecycle.Append(fx.Hook{
OnStart: func(context.Context) error {
return nil
},
OnStop: func(context.Context) error {
panichandler.Go(func() {
log.WaitFlush()
// close diode writer
if config.NonBlocking {
dr := wr.(diode.Writer)
dr.Close()
}
for _, writer := range writers {
if closer, ok := writer.(io.Closer); ok {
_ = closer.Close()
}
}
})
return nil
},
})
return logger, nil
}
// FxLogger overrides fx default logger.
func FxLogger() func(lg *log.Logger) fxevent.Logger {
return func(lg *log.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: zap.New(log.NewZapAdapter(lg, "fx"))}
}
}