This repository has been archived by the owner on Mar 20, 2024. It is now read-only.
generated from benjivesterby/go-template-repo
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
alog.go
519 lines (446 loc) · 12.9 KB
/
alog.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
// Copyright © 2019 Developer Network, LLC
//
// This file is subject to the terms and conditions defined in
// file 'LICENSE', which is part of this source code package.
package alog
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"go.devnw.com/validator"
)
// TODO: Setup so that new destinations can be added at runtime (chan Dest)
// TODO: flag for stack traces on logs with errors?
// streamlog is a constant for the log value of a streaming log when
// an error type is sent
const streamlog = "stream log"
type alog struct {
ctx context.Context
cancel context.CancelFunc
destinations []Destination
// location is the timezone that is used for logging.
// Default: UTC
location *time.Location
// dateformat is the date format that is used in the logging.
// Default: RFC3339
dateformat string
prefix string
buffer int
mutty sync.RWMutex
out map[LogLevel][]chan<- *log
// Indicates that all logs have been cleared to the respective
// destinations during a close
cleaned chan bool
}
func (l *alog) cleanup() {
<-l.ctx.Done()
// Lock the destinations
l.mutty.Lock()
defer l.mutty.Unlock()
defer close(l.cleaned)
// Loop over the destinations and close the channels
for _, destinations := range l.out {
for _, out := range destinations {
ctx, cancel := context.WithTimeout(
l.ctx,
time.Millisecond,
)
// Wait for the channel to empty or timeout
// to elapse, whichever is sooner
go func(
ctx context.Context,
cancel context.CancelFunc,
out chan<- *log,
) {
defer close(out)
defer cancel()
for len(out) > 0 {
select {
case <-ctx.Done():
default:
}
}
}(ctx, cancel, out)
}
}
}
// init starts up the go routines for receiving and publishing logs
// to the available io.Writers
func (l *alog) init() {
// Startup the cleanup go routine to monitor
// for the closed context switch
go l.cleanup()
for _, dest := range l.destinations {
for level := range l.out {
if dest.Types&level <= 0 {
continue
}
l.out[level] = append(
l.out[level],
l.listen(l.ctx, dest),
)
}
}
}
func (l *alog) listen(
ctx context.Context,
destination Destination,
) chan<- *log {
logs := make(chan *log)
go func(ctx context.Context, logs <-chan *log, destination Destination) {
// TODO: handle panic
for {
select {
case <-ctx.Done():
// TODO: setup to close the destination
// if it has a close method
return
case l, ok := <-logs:
if !ok {
return
}
if !validator.Valid(l) {
continue
}
message := l.String()
if destination.Format == JSON {
msg, err := json.Marshal(l)
if err != nil {
// TODO: panic?
panic("error marshaling JSON")
}
// Add a newline to each json log for
// readability
message = string(msg) + "\n"
}
_, err := destination.Writer.Write([]byte(message))
if err != nil {
panic("error writing to destination")
}
}
}
}(ctx, logs, destination)
return logs
}
// send is used to create a go routine thread for fanning out specific
// log types to each of the destinations
func (l *alog) send(ctx context.Context, value *log) {
// TODO: Handle panic here
if value == nil {
return
}
// Break out in the event that the context has been canceled
select {
case <-ctx.Done():
default:
// Lock reads here while pulling channels
l.mutty.RLock()
defer l.mutty.RUnlock()
// Loop over the destinations for this logtype and push onto the
// log channels for each destination
for _, destination := range l.out[value.logtype] {
// Push the log onto the destination channel
select {
case <-ctx.Done():
return
case destination <- value:
}
}
}
}
func (l *alog) buildlog(
logtype LogLevel,
custom string,
err error,
format *string,
t time.Time,
v ...interface{},
) (newlog *log) {
values := v
if format != nil {
values = []interface{}{fmt.Sprintf(*format, v...)}
}
newlog = &log{
logger: l,
logtype: logtype,
customtype: custom,
timestamp: t,
err: err,
values: values,
}
return newlog
}
func (l *alog) clog(
ctx context.Context,
v <-chan interface{},
level LogLevel,
custom string,
) {
go func(
ctx context.Context,
v <-chan interface{},
level LogLevel,
custom string,
) {
// TODO: handle panic
for {
select {
case <-ctx.Done():
return
case value, ok := <-v:
if !ok {
return
}
switch t := value.(type) {
case nil:
case error:
l.send(
ctx,
l.buildlog(
level,
custom,
t,
nil,
time.Now(),
streamlog,
),
)
default:
l.send(
ctx,
l.buildlog(
level,
custom,
nil,
nil,
time.Now(),
t,
),
)
}
}
}
}(ctx, v, level, custom)
}
func (l *alog) sendMultiLine(
level LogLevel,
err error,
values ...interface{},
) {
t := time.Now()
for _, value := range values {
l.send(
l.ctx,
l.buildlog(level, "", err, nil, t, value),
)
}
}
// Printc creates informational logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Printc(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, INFO, "")
}
// Print creates informational logs based on the inputs
func (l *alog) Print(v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(INFO, "", nil, nil, t, v...))
}
// Println prints the data coming in as an informational log on individual lines
func (l *alog) Println(v ...interface{}) {
go l.sendMultiLine(INFO, nil, v...)
}
// Printf creates an informational log using the format and values
func (l *alog) Printf(format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(INFO, "", nil, &format, t, v...))
}
// Debugc creates debug logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Debugc(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, DEBUG, "")
}
// Debug creates debugging logs based on the inputs
func (l *alog) Debug(err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(DEBUG, "", err, nil, t, v...))
}
// Debugln prints the data coming in as a debug log on individual lines
func (l *alog) Debugln(err error, v ...interface{}) {
go l.sendMultiLine(DEBUG, err, v...)
}
// Debugf creates an debugging log using the format and values
func (l *alog) Debugf(err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(DEBUG, "", err, &format, t, v...))
}
// Tracec creates trace logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Tracec(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, TRACE, "")
}
// Trace creates trace logs based on the inputs
func (l *alog) Trace(err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(TRACE, "", err, nil, t, v...))
}
// Traceln prints the data coming in as a trace log on individual lines
func (l *alog) Traceln(err error, v ...interface{}) {
go l.sendMultiLine(TRACE, err, v...)
}
// Tracef creates an trace log using the format and values
func (l *alog) Tracef(err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(TRACE, "", err, &format, t, v...))
}
// Warnc creates warning logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Warnc(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, WARN, "")
}
// Warn creates a warning log using the error passed in along with the
// values passed in
func (l *alog) Warn(err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(WARN, "", err, nil, t, v...))
}
// Warnln creates a warning log using the error and values passed in.
// Each error and value is printed on a different line
func (l *alog) Warnln(err error, v ...interface{}) {
go l.sendMultiLine(WARN, err, v...)
}
// Warnf creates a warning log using the error passed in, along with the string
// formatting and values
func (l *alog) Warnf(err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(WARN, "", err, &format, t, v...))
}
// Errorc creates error logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Errorc(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, ERROR, "")
}
// Error creates an error log using the error and other values passed in
func (l *alog) Error(err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(ERROR, "", err, nil, t, v...))
}
// Errorln creates error logs using the error and other values passed in.
// Each error and value is printed on a different line
func (l *alog) Errorln(err error, v ...interface{}) {
go l.sendMultiLine(ERROR, err, v...)
}
// Errorf creates an error log using the error passed in, along with the string
// formatting and values
func (l *alog) Errorf(err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(ERROR, "", err, &format, t, v...))
}
// Critc creates critical logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Critc(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, CRIT, "")
}
// Crit creates critical logs using the error and other values passed in
func (l *alog) Crit(err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(CRIT, "", err, nil, t, v...))
}
// Critln creates critical logs using the error and other values passed in.
// Each error and value is printed on a different line
func (l *alog) Critln(err error, v ...interface{}) {
go l.sendMultiLine(CRIT, err, v...)
}
// Critf creates a critical log using the error passed in, along with the string
// formatting and values
func (l *alog) Critf(err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(CRIT, "", err, &format, t, v...))
}
// Fatalc creates fatal logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Fatalc(ctx context.Context, v <-chan interface{}) {
l.clog(ctx, v, FATAL, "")
}
// Fatal creates a fatal log using the error and values passed into the method
func (l *alog) Fatal(err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(FATAL, "", err, nil, t, v...))
}
// Fatalln creates fatal logs using the error and other values passed in.
// Each error and value is printed on a different line
func (l *alog) Fatalln(err error, v ...interface{}) {
go l.sendMultiLine(FATAL, err, v...)
}
// Fatalf creates an error log using the error passed in, along with the string
// formatting and values
func (l *alog) Fatalf(err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(FATAL, "", err, &format, t, v...))
}
// Customc creates custom logs based on the data coming from the
// concurrency channel that is passed in for processing
func (l *alog) Customc(ctx context.Context, v <-chan interface{}, ltype string) {
l.clog(ctx, v, CUSTOM, ltype)
}
// Custom creates a custom log using the error and values passed into the method
func (l *alog) Custom(ltype string, err error, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(CUSTOM, ltype, err, nil, t, v...))
}
// Customln creates custom logs using the error and other values passed in.
// Each error and value is printed on a different line
func (l *alog) Customln(ltype string, err error, v ...interface{}) {
t := time.Now()
go func(v ...interface{}) {
for _, value := range v {
l.send(
l.ctx,
l.buildlog(
CUSTOM,
ltype,
err,
nil,
t,
value,
),
)
}
}(v...)
}
// Customf creates a custom log using the error passed in, along with the string
// formatting and values
func (l *alog) Customf(ltype string, err error, format string, v ...interface{}) {
t := time.Now()
go l.send(l.ctx, l.buildlog(CUSTOM, ltype, err, &format, t, v...))
}
// Close cancels the context of the logger internally and breaks out of
// any logging activity. This should always be called in a defer at the top
// level where the logger is initialized to ensure proper closure
func (l *alog) Close() {
if validator.Valid(l) {
// cancel the context of the logger
l.cancel()
}
}
// Validate checks the validity and health of the logger
// to ensure that it can properly log
func (l *alog) Validate() (valid bool) {
if l != nil && l.ctx != nil && l.cancel != nil {
// TODO: ensure there is at least one io.Writer registered and
// that the health checks are passing
valid = true
}
return valid
}
// Wait blocks on the logger context until the context is closed, if the close flag
// is passed then the wait function will close the context of the logger
func (l *alog) Wait(exit bool) {
// Cancel the context if indicated in the call
if exit {
l.Close()
}
// Wait for all of the channels to be closed out
<-l.cleaned
}