mirror of
https://github.com/safing/portbase
synced 2025-09-04 19:50:18 +00:00
Add context based log tracer
This commit is contained in:
parent
475e36d32f
commit
fc58732fd4
6 changed files with 319 additions and 29 deletions
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var counter uint16
|
var counter uint16
|
||||||
|
|
||||||
|
@ -40,14 +43,43 @@ 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.time.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("060102 15:04:05.000"), 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.time.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("060102 15:04:05.000"), line.file[fPartStart:], line.line, rightArrow, line.level.String(), counter, formatDuplicates(duplicates), colorEnd, line.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.trace != nil {
|
||||||
|
// append full trace time
|
||||||
|
if len(line.trace.actions) > 0 {
|
||||||
|
fLine += fmt.Sprintf(" Σ=%s", line.timestamp.Sub(line.trace.actions[0].timestamp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// append all trace actions
|
||||||
|
var d time.Duration
|
||||||
|
for i, action := range line.trace.actions {
|
||||||
|
// set color
|
||||||
|
if useColor {
|
||||||
|
colorStart = action.level.color()
|
||||||
|
}
|
||||||
|
// set filename length
|
||||||
|
fLen := len(action.file)
|
||||||
|
fPartStart := fLen - 10
|
||||||
|
if fPartStart < 0 {
|
||||||
|
fPartStart = 0
|
||||||
|
}
|
||||||
|
// format
|
||||||
|
if i == len(line.trace.actions)-1 { // last
|
||||||
|
d = line.timestamp.Sub(action.timestamp)
|
||||||
|
} else {
|
||||||
|
d = line.trace.actions[i+1].timestamp.Sub(action.timestamp)
|
||||||
|
}
|
||||||
|
fLine += fmt.Sprintf("\n%s%19s %s:%03d %s %s%s %s", colorStart, d, action.file[fPartStart:], action.line, rightArrow, action.level.String(), colorEnd, action.msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if counter >= maxCount {
|
if counter >= maxCount {
|
||||||
|
|
39
log/input.go
39
log/input.go
|
@ -20,14 +20,14 @@ func fastcheck(level severity) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func log(level severity, msg string) {
|
func log(level severity, msg string, trace *ContextTracer) {
|
||||||
|
|
||||||
if !started.IsSet() {
|
if !started.IsSet() {
|
||||||
// a bit resouce intense, but keeps logs before logging started.
|
// a bit resouce 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
|
||||||
log(level, msg)
|
log(level, msg, trace)
|
||||||
}()
|
}()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -70,11 +70,12 @@ func log(level severity, msg string) {
|
||||||
|
|
||||||
// create log object
|
// create log object
|
||||||
log := &logLine{
|
log := &logLine{
|
||||||
msg,
|
msg: msg,
|
||||||
level,
|
trace: trace,
|
||||||
now,
|
level: level,
|
||||||
file,
|
timestamp: now,
|
||||||
line,
|
file: file,
|
||||||
|
line: line,
|
||||||
}
|
}
|
||||||
|
|
||||||
// send log to processing
|
// send log to processing
|
||||||
|
@ -94,73 +95,73 @@ func log(level severity, msg string) {
|
||||||
|
|
||||||
func Tracef(things ...interface{}) {
|
func Tracef(things ...interface{}) {
|
||||||
if fastcheck(TraceLevel) {
|
if fastcheck(TraceLevel) {
|
||||||
log(TraceLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
log(TraceLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Trace(msg string) {
|
func Trace(msg string) {
|
||||||
if fastcheck(TraceLevel) {
|
if fastcheck(TraceLevel) {
|
||||||
log(TraceLevel, msg)
|
log(TraceLevel, msg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(things ...interface{}) {
|
func Debugf(things ...interface{}) {
|
||||||
if fastcheck(DebugLevel) {
|
if fastcheck(DebugLevel) {
|
||||||
log(DebugLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
log(DebugLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug(msg string) {
|
func Debug(msg string) {
|
||||||
if fastcheck(DebugLevel) {
|
if fastcheck(DebugLevel) {
|
||||||
log(DebugLevel, msg)
|
log(DebugLevel, msg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(things ...interface{}) {
|
func Infof(things ...interface{}) {
|
||||||
if fastcheck(InfoLevel) {
|
if fastcheck(InfoLevel) {
|
||||||
log(InfoLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
log(InfoLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info(msg string) {
|
func Info(msg string) {
|
||||||
if fastcheck(InfoLevel) {
|
if fastcheck(InfoLevel) {
|
||||||
log(InfoLevel, msg)
|
log(InfoLevel, msg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warningf(things ...interface{}) {
|
func Warningf(things ...interface{}) {
|
||||||
if fastcheck(WarningLevel) {
|
if fastcheck(WarningLevel) {
|
||||||
log(WarningLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
log(WarningLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warning(msg string) {
|
func Warning(msg string) {
|
||||||
if fastcheck(WarningLevel) {
|
if fastcheck(WarningLevel) {
|
||||||
log(WarningLevel, msg)
|
log(WarningLevel, msg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(things ...interface{}) {
|
func Errorf(things ...interface{}) {
|
||||||
if fastcheck(ErrorLevel) {
|
if fastcheck(ErrorLevel) {
|
||||||
log(ErrorLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
log(ErrorLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(msg string) {
|
func Error(msg string) {
|
||||||
if fastcheck(ErrorLevel) {
|
if fastcheck(ErrorLevel) {
|
||||||
log(ErrorLevel, msg)
|
log(ErrorLevel, msg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Criticalf(things ...interface{}) {
|
func Criticalf(things ...interface{}) {
|
||||||
if fastcheck(CriticalLevel) {
|
if fastcheck(CriticalLevel) {
|
||||||
log(CriticalLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
log(CriticalLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Critical(msg string) {
|
func Critical(msg string) {
|
||||||
if fastcheck(CriticalLevel) {
|
if fastcheck(CriticalLevel) {
|
||||||
log(CriticalLevel, msg)
|
log(CriticalLevel, msg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,17 +35,20 @@ import (
|
||||||
type severity uint32
|
type severity uint32
|
||||||
|
|
||||||
type logLine struct {
|
type logLine struct {
|
||||||
msg string
|
msg string
|
||||||
level severity
|
trace *ContextTracer
|
||||||
time time.Time
|
level severity
|
||||||
file string
|
timestamp time.Time
|
||||||
line int
|
file string
|
||||||
|
line int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ll *logLine) Equal(ol *logLine) bool {
|
func (ll *logLine) Equal(ol *logLine) bool {
|
||||||
switch {
|
switch {
|
||||||
case ll.msg != ol.msg:
|
case ll.msg != ol.msg:
|
||||||
return false
|
return false
|
||||||
|
case ll.trace != nil || ol.trace != nil:
|
||||||
|
return false
|
||||||
case ll.file != ol.file:
|
case ll.file != ol.file:
|
||||||
return false
|
return false
|
||||||
case ll.line != ol.line:
|
case ll.line != ol.line:
|
||||||
|
|
|
@ -17,7 +17,7 @@ func TestLogging(t *testing.T) {
|
||||||
|
|
||||||
// skip
|
// skip
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping test in short mode.")
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// set levels (static random)
|
// set levels (static random)
|
||||||
|
@ -50,7 +50,7 @@ func TestLogging(t *testing.T) {
|
||||||
SetLogLevel(TraceLevel)
|
SetLogLevel(TraceLevel)
|
||||||
|
|
||||||
// log invalid level
|
// log invalid level
|
||||||
log(0xFF, "msg")
|
log(0xFF, "msg", nil)
|
||||||
|
|
||||||
// wait logs to be written
|
// wait logs to be written
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
|
223
log/trace.go
Normal file
223
log/trace.go
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key for context value
|
||||||
|
type ContextTracerKey struct{}
|
||||||
|
|
||||||
|
type ContextTracer struct {
|
||||||
|
sync.Mutex
|
||||||
|
actions []*Action
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
timestamp time.Time
|
||||||
|
level severity
|
||||||
|
msg string
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
key = ContextTracerKey{}
|
||||||
|
nilTracer *ContextTracer
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddTracer(ctx context.Context) context.Context {
|
||||||
|
if ctx != nil && fastcheckLevel(TraceLevel) {
|
||||||
|
_, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if !ok {
|
||||||
|
return context.WithValue(ctx, key, &ContextTracer{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tracer(ctx context.Context) *ContextTracer {
|
||||||
|
if ctx != nil {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok {
|
||||||
|
return tracer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nilTracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) logTrace(level severity, msg string) {
|
||||||
|
// get file and line
|
||||||
|
_, file, line, ok := runtime.Caller(2)
|
||||||
|
if !ok {
|
||||||
|
file = ""
|
||||||
|
line = 0
|
||||||
|
} else {
|
||||||
|
if len(file) > 3 {
|
||||||
|
file = file[:len(file)-3]
|
||||||
|
} else {
|
||||||
|
file = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ct.Lock()
|
||||||
|
defer ct.Unlock()
|
||||||
|
ct.actions = append(ct.actions, &Action{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
level: level,
|
||||||
|
msg: msg,
|
||||||
|
file: file,
|
||||||
|
line: line,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) Tracef(things ...interface{}) (ok bool) {
|
||||||
|
if ct != nil {
|
||||||
|
if fastcheckLevel(TraceLevel) {
|
||||||
|
ct.logTrace(TraceLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) Trace(msg string) (ok bool) {
|
||||||
|
if ct != nil {
|
||||||
|
if fastcheckLevel(TraceLevel) {
|
||||||
|
ct.logTrace(TraceLevel, msg)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) Warningf(things ...interface{}) (ok bool) {
|
||||||
|
if ct != nil {
|
||||||
|
if fastcheckLevel(TraceLevel) {
|
||||||
|
ct.logTrace(WarningLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) Warning(msg string) (ok bool) {
|
||||||
|
if ct != nil {
|
||||||
|
if fastcheckLevel(TraceLevel) {
|
||||||
|
ct.logTrace(WarningLevel, msg)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) Errorf(things ...interface{}) (ok bool) {
|
||||||
|
if ct != nil {
|
||||||
|
if fastcheckLevel(TraceLevel) {
|
||||||
|
ct.logTrace(ErrorLevel, fmt.Sprintf(things[0].(string), things[1:]...))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ContextTracer) Error(msg string) (ok bool) {
|
||||||
|
if ct != nil {
|
||||||
|
if fastcheckLevel(TraceLevel) {
|
||||||
|
ct.logTrace(ErrorLevel, msg)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func DebugTrace(ctx context.Context, msg string) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(DebugLevel, msg, tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(DebugLevel, msg, nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func DebugTracef(ctx context.Context, things ...interface{}) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(DebugLevel, fmt.Sprintf(things[0].(string), things[1:]...), tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(DebugLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func InfoTrace(ctx context.Context, msg string) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(InfoLevel, msg, tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(InfoLevel, msg, nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func InfoTracef(ctx context.Context, things ...interface{}) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(InfoLevel, fmt.Sprintf(things[0].(string), things[1:]...), tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(InfoLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func WarningTrace(ctx context.Context, msg string) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(WarningLevel, msg, tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(WarningLevel, msg, nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func WarningTracef(ctx context.Context, things ...interface{}) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(WarningLevel, fmt.Sprintf(things[0].(string), things[1:]...), tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(WarningLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorTrace(ctx context.Context, msg string) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(ErrorLevel, msg, tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(ErrorLevel, msg, nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorTracef(ctx context.Context, things ...interface{}) (ok bool) {
|
||||||
|
tracer, ok := ctx.Value(key).(*ContextTracer)
|
||||||
|
if ok && fastcheckLevel(TraceLevel) {
|
||||||
|
log(ErrorLevel, fmt.Sprintf(things[0].(string), things[1:]...), tracer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log(ErrorLevel, fmt.Sprintf(things[0].(string), things[1:]...), nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastcheckLevel(level severity) bool {
|
||||||
|
if uint32(level) < atomic.LoadUint32(logLevel) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
31
log/trace_test.go
Normal file
31
log/trace_test.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContextTracer(t *testing.T) {
|
||||||
|
// skip
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := AddTracer(context.Background())
|
||||||
|
|
||||||
|
Tracer(ctx).Trace("api: request received, checking security")
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
Tracer(ctx).Trace("login: logging in user")
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
Tracer(ctx).Trace("database: fetching requested resources")
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
Tracer(ctx).Warning("database: partial failure")
|
||||||
|
time.Sleep(10 * time.Microsecond)
|
||||||
|
Tracer(ctx).Trace("renderer: rendering output")
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
Tracer(ctx).Trace("api: returning request")
|
||||||
|
|
||||||
|
DebugTrace(ctx, "api: completed request")
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue