mirror of
https://github.com/safing/portbase
synced 2025-09-04 03:29:59 +00:00
Clean up log package, improve conditional logging, make scheduling optional
This commit is contained in:
parent
e5961b9368
commit
f29bd54976
6 changed files with 111 additions and 51 deletions
|
@ -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 {
|
||||||
|
|
30
log/input.go
30
log/input.go
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
49
log/trace.go
49
log/trace.go
|
@ -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:
|
||||||
|
|
Loading…
Add table
Reference in a new issue