This repository has been archived by the owner on Dec 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlog.go
572 lines (482 loc) · 13.7 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
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
package stackage
import (
"io"
"log"
"os"
)
var (
// io.Writer qualifiers for event dispatch
stdout,
stderr,
devNull,
// actual (default) logger instances, can be
// supplanted by users using SetDefault[X]Logger
sLogDefault,
cLogDefault *log.Logger
// actual (default) loglevel instances, can be
// set by users using SetDefault[X]LogLevel
sLogLevelDefault,
cLogLevelDefault LogLevel
)
type logSystem struct {
lvl logLevels
log *log.Logger
}
type LogLevel uint16
const NoLogLevels LogLevel = 0 // silent
type logLevels uint16
var (
logLevelNames map[LogLevel]string
logLevelMap map[string]LogLevel
)
const (
LogLevel1 LogLevel = 1 << iota // 1 :: builtin: calls
LogLevel2 // 2 :: builtin: policy
LogLevel3 // 4 :: builtin: state
LogLevel4 // 8 :: builtin: debug
LogLevel5 // 16 :: builtin: errors
LogLevel6 // 32 :: builtin: trace
UserLogLevel1 // 64 :: user-defined
UserLogLevel2 // 128 :: user-defined
UserLogLevel3 // 256 :: user-defined
UserLogLevel4 // 512 :: user-defined
UserLogLevel5 // 1024 :: user-defined
UserLogLevel6 // 2048 :: user-defined
UserLogLevel7 // 4096 :: user-defined
UserLogLevel8 // 8192 :: user-defined
UserLogLevel9 // 16384 :: user-defined
UserLogLevel10 // 32768 :: user-defined
AllLogLevels LogLevel = ^LogLevel(0) // 65535 :: log all of the above unconditionally (!!)
)
func (r logLevels) String() string {
if int(r) == int(AllLogLevels) {
return `ALL`
} else if int(r) == 0 {
return `NONE`
}
var levels []string
for i := 0; i < 16; i++ {
lvl := LogLevel(1 << i)
if r.positive(lvl) {
if name, found := logLevelNames[lvl]; found {
levels = append(levels, name)
}
}
}
return join(levels, `,`)
}
/*
shift shall left-shift the bit value of the receiver to include
the addition of one (1) or more LogLevel specifiers (l) in variadic
fashion. Type instances that qualify are as follows:
- string (case-insensitive loglevel "name", e.g.: "trace" or "none")
- LogLevel (actual LogLevel constants, e.g.: NoLoglevels or LogLevel3)
If any of l's values are NoLogLevels, the receiver shall be set to
zero (0) and any remaining shifts shall be discarded. In this context,
"shift zero" translates to "log nothing".
Conversely, if any of l's values are LogLevel16, the receiver shall be
set to ^LogLevel(0) (uint16(65535)) and any remaining shifts shall be
discarded. Predictably, "shift 65535" translates to "log everything".
*/
func (r *logSystem) shift(l ...any) *logSystem {
r.lvl.shift(l...)
return r
}
/*
shift is a private method called by instances of the
logSystem struct type, et al.
*/
func (r *logLevels) shift(l ...any) *logLevels {
for i := 0; i < len(l); i++ {
var ll LogLevel // current iteration's resolved loglevel stored here
var ok bool
// Perform type-switch upon the
// currently iterated 'any' (#i)
switch tv := l[i].(type) {
case string:
// value could be a loglevel NAME. Try to
// resolve it, and pay no regard to case
// folding.
ll, ok = logLevelMap[uc(tv)]
case LogLevel:
// value is a LogLevel instance. Just take
// it at face value.
ll = tv
ok = true
case int:
ll = LogLevel(tv)
ok = true
}
if logLevels(ll) == logLevels(0) {
*r = logLevels(NoLogLevels)
break
} else if logLevels(ll) == ^logLevels(0) {
*r = logLevels(AllLogLevels)
break
}
// Loglevel is neither "all" nor "none",
// meaning is a singular, discrete log
// verbosity specifier; shift it into
// current value, don't clobber.
if ok {
*r |= logLevels(ll)
}
}
return r
}
/*
unshift shall right-shift the bit value of the receiver to effect
the removal of one (1) or more logLevel instances (l) in variadic
fashion.
If any of l's values are NoLogLevels, the loop shall call continue, as
nothing can be done logically with that value here, though it is
not fatal, nor should it terminate processing.
If any of l's values are LogLevel16, the receiver shall be set to zero
(0) and any remaining shifts shall be discarded, as "unshift all"
in this context translates to "log nothing".
*/
func (r *logSystem) unshift(l ...any) *logSystem {
r.lvl.unshift(l...)
return r
}
func (r *logLevels) unshift(l ...any) *logLevels {
for i := 0; i < len(l); i++ {
var ll LogLevel
var ok bool
switch tv := l[i].(type) {
case string:
// value could be a loglevel NAME. Try to
// resolve it, and pay no regard to case
// folding.
ll, ok = logLevelMap[uc(tv)]
case LogLevel:
// value is a LogLevel instance. Just take
// it at face value.
ll = tv
ok = true
case int:
ll = LogLevel(tv)
ok = true
}
if logLevels(ll) == logLevels(0) {
continue
} else if logLevels(ll) == ^logLevels(0) {
break
}
if ok {
*r = (*r &^ logLevels(ll))
}
}
return r
}
/*
positive returns a Boolean value indicative of whether the receiver
contains the bit value for the specified logLevel. In context, this
means "logLevel <X>" is either active (true) or not (false).
*/
func (r logSystem) positive(l any) (posi bool) {
if !r.isZero() {
posi = r.lvl.positive(l)
}
return
}
func (r logLevels) positive(l any) (posi bool) {
if r == logLevels(0) {
return
} else if r == ^logLevels(0) {
posi = true
return
}
var ll LogLevel
var ok bool
switch tv := l.(type) {
case LogLevel:
// value is a LogLevel instance. Just take
// it at face value.
ll = tv
ok = true
}
if ok {
posi = (r & logLevels(ll)) != 0
}
return
}
func (r *logSystem) isZero() (is bool) {
if r != nil {
is = r.log == nil && r.lvl == logLevels(NoLogLevels)
}
return
}
func (r logSystem) logger() (l *log.Logger) {
if !r.isZero() {
l = r.log
}
return
}
func (r *logSystem) setLogger(logger any) *logSystem {
r.log = resolveLogger(logger)
return r
}
func newLogSystem(logger any, l ...any) (lsys *logSystem) {
lgr := devNull
if logger != nil {
lgr = resolveLogger(logger)
}
lsys = new(logSystem)
lsys.log = lgr
lsys.lvl = *new(logLevels).shift(l...)
return
}
/*
SetDefaultConditionLogger is a package-level function that will define
which logging facility new instances of [Condition] or equivalent type
alias shall be assigned during initialization procedures.
Logging is available but is set to discard all events by default. Note
that enabling this will have no effect on instances already created.
An active logging subsystem within any given [Condition] shall supercede
this default package logger.
The following types/values are permitted:
- string: `none`, `off`, `null`, `discard` will turn logging off
- string: `stdout` will set basic STDOUT logging
- string: `stderr` will set basic STDERR logging
- int: 0 will turn logging off
- int: 1 will set basic STDOUT logging
- int: 2 will set basic STDERR logging
- *[log.Logger]: user-defined *[log.Logger] instance will be set
Case is not significant in the string matching process.
Logging may also be set for individual [Condition] instances using the
[Condition.SetLogger] method. Similar semantics apply.
*/
func SetDefaultConditionLogger(logger any) {
cLogDefault = resolveLogger(logger)
}
func DefaultConditionLogLevel() int {
return int(cLogLevelDefault)
}
/*
SetDefaultConditionLogLevel sets the instance of [LogLevel] (lvl)
as a LITERAL value, as the verbosity indicator. When set with
appropriate level identifiers, this will increase or decrease
log verbosity accordingly. This value shall be used for logging
verbosity (or lack thereof) for any newly created (and qualified)
instances.
Note that the input value(s) are NOT shifted. Users are expected
to either sum the values and cast the product as a [LogLevel], OR
settle for one of the predefined [LogLevel] constants.
The default is [NoLogLevels], which implies a loglevel of zero (0).
*/
func SetDefaultConditionLogLevel(lvl any) {
var level LogLevel
switch tv := lvl.(type) {
case string:
ll, found := logLevelMap[uc(tv)]
if found {
level = ll
}
case int:
if 0 <= tv && tv <= int(^uint16(0)) {
level = LogLevel(tv)
}
case LogLevel:
level = tv
default:
level = NoLogLevels
}
cLogLevelDefault = level
}
/*
SetDefaultStackLogger is a package-level function that will define
which logging facility new instances of [Stack] or equivalent type
alias shall be assigned during initialization procedures.
Logging is available but is set to discard all events by default.
Note that enabling this will have no effect on instances already
created.
An active logging subsystem within any given [Stack] shall supersede
this default package logger.
The following types/values are permitted:
- string: `none`, `off`, `null`, `discard` will turn logging off
- string: `stdout` will set basic STDOUT logging
- string: `stderr` will set basic STDERR logging
- int: 0 will turn logging off
- int: 1 will set basic STDOUT logging
- int: 2 will set basic STDERR logging
- *log.Logger: user-defined *log.Logger instance will be set
Case is not significant in the string matching process.
Logging may also be set for individual [Stack] instances using the
[Stack.SetLogger] method. Similar semantics apply.
*/
func SetDefaultStackLogger(logger any) {
sLogDefault = resolveLogger(logger)
}
func DefaultStackLogLevel() int {
return int(sLogLevelDefault)
}
/*
SetDefaultStackLogLevel sets the instance of [LogLevel] (lvl)
as a LITERAL value, as the verbosity indicator. When set with
appropriate level identifiers, this will increase or decrease
log verbosity accordingly. This value shall be used for logging
verbosity (or lack thereof) for any newly created (and qualified)
instances.
Note that the input value(s) are NOT shifted. Users are expected
to either sum the values and cast the product as a [LogLevel], OR
settle for one of the predefined [LogLevel] constants.
The default is [NoLogLevels], which implies a loglevel of zero
(0).
*/
func SetDefaultStackLogLevel(lvl any) {
var level LogLevel
switch tv := lvl.(type) {
case string:
ll, found := logLevelMap[uc(tv)]
if found {
level = ll
}
case int:
if 0 <= tv && tv <= int(^uint16(0)) {
level = LogLevel(tv)
}
case LogLevel:
level = tv
default:
level = NoLogLevels
}
sLogLevelDefault = level
}
func resolveLogger(logger any) (l *log.Logger) {
switch tv := logger.(type) {
case *log.Logger:
l = tv
case int:
l = intResolveLogger(tv)
case string:
l = stringResolveLogger(tv)
}
// We need something to fallback to,
// regardless of the user's logging
// intentions; impose devNull if we
// find ourselves with a *log.Logger
// instance that is nil.
if l == nil || logger == nil {
l = devNull
}
return l
}
func intResolveLogger(logger int) (l *log.Logger) {
switch logger {
case 0:
l = devNull
case 1:
l = stdout
case 2:
l = stderr
}
return
}
func stringResolveLogger(logger string) (l *log.Logger) {
switch lc(logger) {
case `none`, `off`, `null`, `discard`:
l = devNull
case `stderr`:
l = stderr
case `stdout`:
l = stdout
}
return
}
func logDiscard(logger *log.Logger) (is bool) {
if logger != nil {
is = logger.Writer() == io.Discard
}
return
}
/*
Logger returns the *[log.Logger] instance. This can be used for quick
access to the [log.Logger] type's methods in a manner such as:
r.Logger().Fatalf("We died")
It is not recommended to modify the return instance for the purpose
of disabling logging outright (see [Stack.SetLogger] method as well
as the [SetDefaultStackLogger] package-level function for ways of
doing this easily).
*/
func (r Stack) Logger() (l *log.Logger) {
if r.IsInit() {
l = r.stack.logger()
}
return
}
func (r *stack) logger() *log.Logger {
cfg, _ := r.config()
return cfg.log.logger()
}
/*
Logger returns the *[log.Logger] instance. This can be used for quick
access to the [log.Logger] type's methods in a manner such as:
r.Logger().Fatalf("We died")
It is not recommended to modify the return instance for the purpose
of disabling logging outright (see [Condition.SetLogger] method as well
as the [SetDefaultConditionLogger] package-level function for ways of
doing this easily).
*/
func (r Condition) Logger() (l *log.Logger) {
if r.IsInit() {
l = r.condition.logger()
}
return
}
func (r condition) logger() *log.Logger {
return r.cfg.log.logger()
}
func init() {
stderr = log.New(os.Stderr, ``, 0)
stdout = log.New(os.Stdout, ``, 0)
devNull = log.New(io.Discard, ``, 0)
// log events dispatched into oblivion
sLogDefault = devNull
cLogDefault = devNull
// log events are ignored regardless of severity/category
sLogLevelDefault = NoLogLevels
cLogLevelDefault = NoLogLevels
// instance->str
logLevelNames = map[LogLevel]string{
NoLogLevels: `NONE`,
LogLevel1: `CALLS`,
LogLevel2: `POLICY`,
LogLevel3: `STATE`,
LogLevel4: `DEBUG`,
LogLevel5: `ERROR`,
LogLevel6: `TRACE`,
UserLogLevel1: `USER1`,
UserLogLevel2: `USER2`,
UserLogLevel3: `USER3`,
UserLogLevel4: `USER4`,
UserLogLevel5: `USER5`,
UserLogLevel6: `USER6`,
UserLogLevel7: `USER7`,
UserLogLevel8: `USER8`,
UserLogLevel9: `USER9`,
UserLogLevel10: `USER10`,
AllLogLevels: `ALL`,
}
// str->instance
logLevelMap = map[string]LogLevel{
`NONE`: NoLogLevels,
`CALLS`: LogLevel1,
`POLICY`: LogLevel2,
`STATE`: LogLevel3,
`DEBUG`: LogLevel4,
`ERROR`: LogLevel5,
`TRACE`: LogLevel6,
`USER1`: UserLogLevel1,
`USER2`: UserLogLevel2,
`USER3`: UserLogLevel3,
`USER4`: UserLogLevel4,
`USER5`: UserLogLevel5,
`USER6`: UserLogLevel6,
`USER7`: UserLogLevel7,
`USER8`: UserLogLevel8,
`USER9`: UserLogLevel9,
`USER10`: UserLogLevel10,
`ALL`: AllLogLevels,
}
}