Clean up log package, improve conditional logging, make scheduling optional

This commit is contained in:
Daniel 2019-09-20 22:03:21 +02:00
parent e5961b9368
commit f29bd54976
6 changed files with 111 additions and 51 deletions

View file

@ -7,7 +7,10 @@ import (
var counter uint16 var counter uint16
const maxCount uint16 = 999 const (
maxCount uint16 = 999
timeFormat string = "060102 15:04:05.000"
)
func (s Severity) String() string { func (s Severity) String() string {
switch s { switch s {
@ -41,14 +44,14 @@ func formatLine(line *logLine, duplicates uint64, useColor bool) string {
var fLine string var fLine string
if line.line == 0 { if line.line == 0 {
fLine = fmt.Sprintf("%s%s ? %s %s %03d%s%s %s", colorStart, line.timestamp.Format("060102 15:04:05.000"), rightArrow, line.level.String(), counter, formatDuplicates(duplicates), colorEnd, line.msg) fLine = fmt.Sprintf("%s%s ? %s %s %03d%s%s %s", colorStart, line.timestamp.Format(timeFormat), rightArrow, line.level.String(), counter, formatDuplicates(duplicates), colorEnd, line.msg)
} else { } else {
fLen := len(line.file) fLen := len(line.file)
fPartStart := fLen - 10 fPartStart := fLen - 10
if fPartStart < 0 { if fPartStart < 0 {
fPartStart = 0 fPartStart = 0
} }
fLine = fmt.Sprintf("%s%s %s:%03d %s %s %03d%s%s %s", colorStart, line.timestamp.Format("060102 15:04:05.000"), line.file[fPartStart:], line.line, rightArrow, line.level.String(), counter, formatDuplicates(duplicates), colorEnd, line.msg) fLine = fmt.Sprintf("%s%s %s:%03d %s %s %03d%s%s %s", colorStart, line.timestamp.Format(timeFormat), line.file[fPartStart:], line.line, rightArrow, line.level.String(), counter, formatDuplicates(duplicates), colorEnd, line.msg)
} }
if line.tracer != nil { if line.tracer != nil {

View file

@ -11,7 +11,7 @@ import (
func log(level Severity, msg string, tracer *ContextTracer) { func log(level Severity, msg string, tracer *ContextTracer) {
if !started.IsSet() { if !started.IsSet() {
// a bit resouce intense, but keeps logs before logging started. // a bit resource intense, but keeps logs before logging started.
// FIXME: create option to disable logging // FIXME: create option to disable logging
go func() { go func() {
<-startedSignal <-startedSignal
@ -20,11 +20,6 @@ func log(level Severity, msg string, tracer *ContextTracer) {
return return
} }
// check if level is enabled
if !pkgLevelsActive.IsSet() && uint32(level) < atomic.LoadUint32(logLevel) {
return
}
// get time // get time
now := time.Now() now := time.Now()
@ -43,20 +38,27 @@ func log(level Severity, msg string, tracer *ContextTracer) {
// check if level is enabled for file or generally // check if level is enabled for file or generally
if pkgLevelsActive.IsSet() { if pkgLevelsActive.IsSet() {
fileOnly := strings.Split(file, "/") pathSegments := strings.Split(file, "/")
if len(fileOnly) < 2 { if len(pathSegments) < 2 {
// file too short for package levels
return return
} }
sev, ok := pkgLevels[fileOnly[len(fileOnly)-2]] pkgLevelsLock.Lock()
severity, ok := pkgLevels[pathSegments[len(pathSegments)-2]]
pkgLevelsLock.Unlock()
if ok { if ok {
if level < sev { if level < severity {
return return
} }
} else { } else {
// no package level set, check against global level
if uint32(level) < atomic.LoadUint32(logLevel) { if uint32(level) < atomic.LoadUint32(logLevel) {
return return
} }
} }
} else if uint32(level) < atomic.LoadUint32(logLevel) {
// no package levels set, check against global level
return
} }
// create log object // create log object
@ -73,13 +75,13 @@ func log(level Severity, msg string, tracer *ContextTracer) {
select { select {
case logBuffer <- log: case logBuffer <- log:
default: default:
forceEmptyingOfBuffer <- true forceEmptyingOfBuffer <- struct{}{}
logBuffer <- log logBuffer <- log
} }
// wake up writer if necessary // wake up writer if necessary
if logsWaitingFlag.SetToIf(false, true) { if logsWaitingFlag.SetToIf(false, true) {
logsWaiting <- true logsWaiting <- struct{}{}
} }
} }
@ -108,14 +110,14 @@ func Tracef(format string, things ...interface{}) {
} }
} }
// Debug is used to log minor errors or unexpected events. These occurences are usually not worth mentioning in itself, but they might hint at a bigger problem. // Debug is used to log minor errors or unexpected events. These occurrences are usually not worth mentioning in itself, but they might hint at a bigger problem.
func Debug(msg string) { func Debug(msg string) {
if fastcheck(DebugLevel) { if fastcheck(DebugLevel) {
log(DebugLevel, msg, nil) log(DebugLevel, msg, nil)
} }
} }
// Debugf is used to log minor errors or unexpected events. These occurences are usually not worth mentioning in itself, but they might hint at a bigger problem. // Debugf is used to log minor errors or unexpected events. These occurrences are usually not worth mentioning in itself, but they might hint at a bigger problem.
func Debugf(format string, things ...interface{}) { func Debugf(format string, things ...interface{}) {
if fastcheck(DebugLevel) { if fastcheck(DebugLevel) {
log(DebugLevel, fmt.Sprintf(format, things...), nil) log(DebugLevel, fmt.Sprintf(format, things...), nil)

View file

@ -70,7 +70,7 @@ const (
var ( var (
logBuffer chan *logLine logBuffer chan *logLine
forceEmptyingOfBuffer chan bool forceEmptyingOfBuffer chan struct{}
logLevelInt = uint32(3) logLevelInt = uint32(3)
logLevel = &logLevelInt logLevel = &logLevelInt
@ -79,17 +79,15 @@ var (
pkgLevels = make(map[string]Severity) pkgLevels = make(map[string]Severity)
pkgLevelsLock sync.Mutex pkgLevelsLock sync.Mutex
logsWaiting = make(chan bool, 1) logsWaiting = make(chan struct{}, 1)
logsWaitingFlag = abool.NewBool(false) logsWaitingFlag = abool.NewBool(false)
shutdownSignal = make(chan struct{}, 0) shutdownSignal = make(chan struct{})
shutdownWaitGroup sync.WaitGroup shutdownWaitGroup sync.WaitGroup
initializing = abool.NewBool(false) initializing = abool.NewBool(false)
started = abool.NewBool(false) started = abool.NewBool(false)
startedSignal = make(chan struct{}, 0) startedSignal = make(chan struct{})
testErrors = abool.NewBool(false)
) )
// SetPkgLevels sets individual log levels for packages. // SetPkgLevels sets individual log levels for packages.
@ -136,8 +134,8 @@ func Start() (err error) {
return nil return nil
} }
logBuffer = make(chan *logLine, 8192) logBuffer = make(chan *logLine, 1024)
forceEmptyingOfBuffer = make(chan bool, 4) forceEmptyingOfBuffer = make(chan struct{}, 16)
initialLogLevel := ParseLevel(logLevelFlag) initialLogLevel := ParseLevel(logLevelFlag)
if initialLogLevel > 0 { if initialLogLevel > 0 {
@ -169,6 +167,9 @@ func Start() (err error) {
SetPkgLevels(newPkgLevels) SetPkgLevels(newPkgLevels)
} }
if !schedulingEnabled {
close(writeTrigger)
}
startWriter() startWriter()
started.Set() started.Set()

View file

@ -1,17 +1,20 @@
package log package log
import ( import (
"fmt"
"testing" "testing"
"time" "time"
) )
// test waiting func init() {
func TestLogging(t *testing.T) {
err := Start() err := Start()
if err != nil { if err != nil {
t.Errorf("start failed: %s", err) panic(fmt.Sprintf("start failed: %s", err))
} }
}
// test waiting
func TestLogging(t *testing.T) {
// skip // skip
if testing.Short() { if testing.Short() {

View file

@ -22,10 +22,16 @@ func TriggerWriter() {
if started.IsSet() && schedulingEnabled { if started.IsSet() && schedulingEnabled {
select { select {
case writeTrigger <- struct{}{}: case writeTrigger <- struct{}{}:
default:
} }
} }
} }
// TriggerWriterChannel returns the channel to trigger log writing. Returned channel will close if EnableScheduling() is not called correctly.
func TriggerWriterChannel() chan struct{} {
return writeTrigger
}
func writeLine(line *logLine, duplicates uint64) { func writeLine(line *logLine, duplicates uint64) {
fmt.Println(formatLine(line, duplicates, true)) fmt.Println(formatLine(line, duplicates, true))
// TODO: implement file logging and setting console/file logging // TODO: implement file logging and setting console/file logging
@ -34,7 +40,7 @@ func writeLine(line *logLine, duplicates uint64) {
func startWriter() { func startWriter() {
shutdownWaitGroup.Add(1) shutdownWaitGroup.Add(1)
fmt.Println(fmt.Sprintf("%s%s %s BOF%s", InfoLevel.color(), time.Now().Format("060102 15:04:05.000"), rightArrow, endColor())) fmt.Println(fmt.Sprintf("%s%s %s BOF%s", InfoLevel.color(), time.Now().Format(timeFormat), rightArrow, endColor()))
go writer() go writer()
} }
@ -47,7 +53,7 @@ func writer() {
for { for {
// reset // reset
line = nil line = nil
lastLine = nil lastLine = nil //nolint:ineffassign // only ineffectual in first loop
duplicates = 0 duplicates = 0
// wait until logs need to be processed // wait until logs need to be processed
@ -55,6 +61,8 @@ func writer() {
case <-logsWaiting: case <-logsWaiting:
logsWaitingFlag.UnSet() logsWaitingFlag.UnSet()
case <-shutdownSignal: case <-shutdownSignal:
finalizeWriting()
return
} }
// wait for timeslot to log, or when buffer is full // wait for timeslot to log, or when buffer is full
@ -62,15 +70,8 @@ func writer() {
case <-writeTrigger: case <-writeTrigger:
case <-forceEmptyingOfBuffer: case <-forceEmptyingOfBuffer:
case <-shutdownSignal: case <-shutdownSignal:
for { finalizeWriting()
select { return
case line = <-logBuffer:
writeLine(line, duplicates)
case <-time.After(10 * time.Millisecond):
fmt.Println(fmt.Sprintf("%s%s %s EOF%s", InfoLevel.color(), time.Now().Format("060102 15:04:05.000"), leftArrow, endColor()))
return
}
}
} }
// write all the logs! // write all the logs!
@ -94,12 +95,11 @@ func writer() {
// deduplication // deduplication
if !line.Equal(lastLine) { if !line.Equal(lastLine) {
// no duplicate // no duplicate
writeLine(lastLine, duplicates) break dedupLoop
duplicates = 0
} else {
// duplicate
duplicates++
} }
// duplicate
duplicates++
} }
// write actual line // write actual line
@ -114,7 +114,21 @@ func writer() {
select { select {
case <-time.After(10 * time.Millisecond): case <-time.After(10 * time.Millisecond):
case <-shutdownSignal: case <-shutdownSignal:
finalizeWriting()
return
} }
} }
} }
func finalizeWriting() {
for {
select {
case line := <-logBuffer:
writeLine(line, 0)
case <-time.After(10 * time.Millisecond):
fmt.Println(fmt.Sprintf("%s%s %s EOF%s", InfoLevel.color(), time.Now().Format(timeFormat), leftArrow, endColor()))
return
}
}
}

View file

@ -4,7 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"runtime" "runtime"
"strings"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
@ -21,11 +23,46 @@ var (
key = ContextTracerKey{} key = ContextTracerKey{}
) )
// AddTracer adds a ContextTracer to the returned Context. Will return a nil ContextTracer if one already exists. Will return a nil context if nil. // AddTracer adds a ContextTracer to the returned Context. Will return a nil ContextTracer if logging level is not set to trace. Will return a nil ContextTracer if one already exists. Will return a nil ContextTracer in case of an error. Will return a nil context if nil.
func AddTracer(ctx context.Context) (context.Context, *ContextTracer) { func AddTracer(ctx context.Context) (context.Context, *ContextTracer) {
if ctx != nil && fastcheck(TraceLevel) { if ctx != nil && fastcheck(TraceLevel) {
// check pkg levels
if pkgLevelsActive.IsSet() {
// get file
_, file, _, ok := runtime.Caller(1)
if !ok {
// cannot get file, ignore
return ctx, nil
}
pathSegments := strings.Split(file, "/")
if len(pathSegments) < 2 {
// file too short for package levels
return ctx, nil
}
pkgLevelsLock.Lock()
severity, ok := pkgLevels[pathSegments[len(pathSegments)-2]]
pkgLevelsLock.Unlock()
if ok {
// check against package level
if TraceLevel < severity {
return ctx, nil
}
} else {
// no package level set, check against global level
if uint32(TraceLevel) < atomic.LoadUint32(logLevel) {
return ctx, nil
}
}
} else if uint32(TraceLevel) < atomic.LoadUint32(logLevel) {
// no package levels set, check against global level
return ctx, nil
}
// check for existing tracer
_, ok := ctx.Value(key).(*ContextTracer) _, ok := ctx.Value(key).(*ContextTracer)
if !ok { if !ok {
// add and return new tracer
tracer := &ContextTracer{} tracer := &ContextTracer{}
return context.WithValue(ctx, key, tracer), tracer return context.WithValue(ctx, key, tracer), tracer
} }
@ -51,7 +88,7 @@ func (tracer *ContextTracer) Submit() {
} }
if !started.IsSet() { if !started.IsSet() {
// a bit resouce intense, but keeps logs before logging started. // a bit resource intense, but keeps logs before logging started.
// FIXME: create option to disable logging // FIXME: create option to disable logging
go func() { go func() {
<-startedSignal <-startedSignal
@ -82,13 +119,13 @@ func (tracer *ContextTracer) Submit() {
select { select {
case logBuffer <- log: case logBuffer <- log:
default: default:
forceEmptyingOfBuffer <- true forceEmptyingOfBuffer <- struct{}{}
logBuffer <- log logBuffer <- log
} }
// wake up writer if necessary // wake up writer if necessary
if logsWaitingFlag.SetToIf(false, true) { if logsWaitingFlag.SetToIf(false, true) {
logsWaiting <- true logsWaiting <- struct{}{}
} }
} }
@ -138,7 +175,7 @@ func (tracer *ContextTracer) Tracef(format string, things ...interface{}) {
} }
} }
// Debug is used to log minor errors or unexpected events. These occurences are usually not worth mentioning in itself, but they might hint at a bigger problem. // Debug is used to log minor errors or unexpected events. These occurrences are usually not worth mentioning in itself, but they might hint at a bigger problem.
func (tracer *ContextTracer) Debug(msg string) { func (tracer *ContextTracer) Debug(msg string) {
switch { switch {
case tracer != nil: case tracer != nil:
@ -148,7 +185,7 @@ func (tracer *ContextTracer) Debug(msg string) {
} }
} }
// Debugf is used to log minor errors or unexpected events. These occurences are usually not worth mentioning in itself, but they might hint at a bigger problem. // Debugf is used to log minor errors or unexpected events. These occurrences are usually not worth mentioning in itself, but they might hint at a bigger problem.
func (tracer *ContextTracer) Debugf(format string, things ...interface{}) { func (tracer *ContextTracer) Debugf(format string, things ...interface{}) {
switch { switch {
case tracer != nil: case tracer != nil: