-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandler.go
320 lines (252 loc) · 7.59 KB
/
handler.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
package soba
import (
"fmt"
"os"
"strings"
"sync"
"github.com/pkg/errors"
)
// A Handler provides an alternative way to obtain loggers if the context based approach doesn't
// fit your requirements.
type Handler interface {
// New creates a new Logger using given name.
New(name string) Logger
// Close recycles the handler appenders.
Close() error
}
// Create provides an alternative way to obtain loggers if the context based approach doesn't
// fit your requirements.
//
// It relies on conventions and default configurations:
// - First, it will lookup from environment variable if a configuration path is defined.
// - Then, it will lookup from current directory if a configuration file exists.
// - Finally, it will create a new instance with default configurations.
//
// For specific configurations, please uses either CreateWithConfig or CreateWithFile.
func Create() (Handler, error) {
path := os.Getenv(EnvConfigPath)
if path != "" && CheckPath(path) {
return CreateWithFile(path)
}
if CheckPath(DefaultConfigPath) {
return CreateWithFile(DefaultConfigPath)
}
return CreateWithConfig(NewDefaultConfig())
}
// CreateWithFile provides an alternative way to obtain loggers if the context based approach doesn't
// fit your requirements. It will creates a new handler using given file path.
func CreateWithFile(path string) (Handler, error) {
conf, err := ParseConfig(path)
if err != nil {
return nil, err
}
return create(conf)
}
// CreateWithConfig provides an alternative way to obtain loggers if the context based approach doesn't
// fit your requirements. It will creates a new handler using given configuration.
func CreateWithConfig(conf *Config) (Handler, error) {
err := ValidateConfig(conf)
if err != nil {
return nil, errors.Wrap(err, "configuration is invalid")
}
return create(conf)
}
// A handler contains every required components to provides loggers.
type handler struct {
conf Config
appenders map[string]Appender
loggers sync.Map
}
// create a handler using given configuration.
func create(conf *Config) (*handler, error) {
handler := &handler{
conf: *conf,
appenders: map[string]Appender{},
loggers: sync.Map{},
}
err := createAppenders(conf, handler)
if err != nil {
return nil, errors.Wrap(err, "cannot create soba handler")
}
err = createRootLogger(conf, handler)
if err != nil {
return nil, errors.Wrap(err, "cannot create soba handler")
}
err = createChildLoggers(conf, handler)
if err != nil {
return nil, errors.Wrap(err, "cannot create soba handler")
}
return handler, nil
}
func closePreviousAppender(name string, handler *handler) {
// In case there is a duplication in appenders name, we close the previous one.
appender, ok := handler.appenders[name]
if ok && appender != nil {
// Silent the error.
_ = appender.Close()
}
}
func createAppenders(conf *Config, handler *handler) error {
for name := range conf.Appenders {
closePreviousAppender(name, handler)
appender, err := NewAppender(name, conf.Appenders[name])
if err != nil {
return err
}
handler.appenders[name] = appender
}
plMutex.Lock()
defer plMutex.Unlock()
for name, appender := range plAppenders {
closePreviousAppender(name, handler)
handler.appenders[name] = appender
}
return nil
}
func createRootLogger(conf *Config, handler *handler) error {
level, err := getLoggerLevel(conf.Root, "root")
if err != nil {
return err
}
appenders, err := getAppendersForRootLogger(conf, handler)
if err != nil {
return err
}
handler.loggers.Store("", NewLogger("root", level, appenders))
return nil
}
func getLoggerLevel(conf ConfigLogger, name string) (Level, error) {
level, ok := ParseLevel(conf.Level)
if !ok {
return UnknownLevel, errors.Errorf("unknown level for logger '%s': %s", name, conf.Level)
}
return level, nil
}
func getParentAppendersForLogger(conf *Config, handler *handler, hierarchy []string, result map[string]Appender) error {
length := len(hierarchy)
for i := 1; i < length; i++ {
cursor := length - i
list := hierarchy[0:cursor]
current := strings.Join(list, ".")
parent, ok := conf.Loggers[current]
if ok {
return getLocalAppendersForLogger(parent, handler, result)
}
}
return getLocalAppendersForLogger(conf.Root, handler, result)
}
func getLocalAppendersForLogger(conf ConfigLogger, handler *handler, result map[string]Appender) error {
for _, name := range conf.Appenders {
appender, ok := handler.appenders[name]
if !ok {
return errors.Errorf("unknown appender name: '%s'", name)
}
result[name] = appender
}
return nil
}
func getAppendersForRootLogger(conf *Config, handler *handler) ([]Appender, error) {
appenders := map[string]Appender{}
err := getLocalAppendersForLogger(conf.Root, handler, appenders)
if err != nil {
return nil, err
}
list := []Appender{}
for _, appender := range appenders {
list = append(list, appender)
}
return list, nil
}
func getAppendersForChildLogger(conf *Config, handler *handler, name string) ([]Appender, error) {
appenders := map[string]Appender{}
err := getLocalAppendersForLogger(conf.Loggers[name], handler, appenders)
if err != nil {
return nil, err
}
if isChildLoggerAdditive(conf, name, appenders) {
hierarchy := strings.Split(name, ".")
err = getParentAppendersForLogger(conf, handler, hierarchy, appenders)
if err != nil {
return nil, err
}
}
list := []Appender{}
for _, appender := range appenders {
list = append(list, appender)
}
return list, nil
}
func isChildLoggerAdditive(conf *Config, name string, appenders map[string]Appender) bool {
// If logger is defined as additive, then it is.
if conf.Loggers[name].Additive {
return true
}
// If logger is defined as disabled, it's not additive.
level, ok := ParseLevel(conf.Loggers[name].Level)
if ok && level == NoLevel {
return false
}
// If there is no appender defined and the logger is not disabled, use the parent appenders as default.
if len(appenders) == 0 {
return true
}
return false
}
func createChildLoggers(conf *Config, handler *handler) error {
for name := range conf.Loggers {
level, err := getLoggerLevel(conf.Loggers[name], name)
if err != nil {
return err
}
appenders, err := getAppendersForChildLogger(conf, handler, name)
if err != nil {
return err
}
handler.loggers.Store(name, NewLogger(name, level, appenders))
}
return nil
}
func (handler *handler) New(name string) Logger {
if !IsLoggerNameValid(name) {
panic(fmt.Sprintf("soba: invalid logger name format: %s", name))
}
// First, try to find the logger identified by given name.
val, ok := handler.loggers.Load(name)
if ok {
return val.(Logger)
}
// Next, try to find a ancestor one by moving up to the hierarchy.
hierarchy := strings.Split(name, ".")
length := len(hierarchy)
for i := 1; i < length; i++ {
cursor := length - i
list := hierarchy[0:cursor]
current := strings.Join(list, ".")
val, ok = handler.loggers.Load(current)
if ok {
copy := val.(Logger).copyWithName(name)
val, _ = handler.loggers.LoadOrStore(name, copy)
return val.(Logger)
}
}
// Finally, use root logger as default.
val, ok = handler.loggers.Load("")
if !ok {
panic("soba: root logger must be defined")
}
copy := val.(Logger).copyWithName(name)
val, _ = handler.loggers.LoadOrStore(name, copy)
return val.(Logger)
}
// Close recycles the handler appenders.
// If case of one or multiple errors, we return the first one.
func (handler *handler) Close() error {
var err error
for name, appender := range handler.appenders {
thr := appender.Close()
if thr != nil && err == nil {
err = errors.Wrapf(thr, "cannot close appender %s", name)
}
}
return err
}