safing-portmaster/base/log/trace.go
Daniel Hååvi 80664d1a27
Restructure modules ()
* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
2024-08-09 18:15:48 +03:00

280 lines
7.7 KiB
Go

package log
import (
"context"
"fmt"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
)
// ContextTracerKey is the key used for the context key/value storage.
type ContextTracerKey struct{}
// ContextTracer is attached to a context in order bind logs to a context.
type ContextTracer struct {
sync.Mutex
logs []*logLine
}
var key = ContextTracerKey{}
// 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) {
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)
if !ok {
// add and return new tracer
tracer := &ContextTracer{}
return context.WithValue(ctx, key, tracer), tracer
}
}
return ctx, nil
}
// Tracer returns the ContextTracer previously added to the given Context.
func Tracer(ctx context.Context) *ContextTracer {
if ctx != nil {
tracer, ok := ctx.Value(key).(*ContextTracer)
if ok {
return tracer
}
}
return nil
}
// Submit collected logs on the context for further processing/outputting. Does nothing if called on a nil ContextTracer.
func (tracer *ContextTracer) Submit() {
if tracer == nil {
return
}
if !started.IsSet() {
// a bit resource intense, but keeps logs before logging started.
// TODO: create option to disable logging
go func() {
<-startedSignal
tracer.Submit()
}()
return
}
if len(tracer.logs) == 0 {
return
}
// extract last line as main line
mainLine := tracer.logs[len(tracer.logs)-1]
tracer.logs = tracer.logs[:len(tracer.logs)-1]
// create log object
log := &logLine{
msg: mainLine.msg,
tracer: tracer,
level: mainLine.level,
timestamp: mainLine.timestamp,
file: mainLine.file,
line: mainLine.line,
}
// send log to processing
select {
case logBuffer <- log:
default:
forceEmptyingLoop:
// force empty buffer until we can send to it
for {
select {
case forceEmptyingOfBuffer <- struct{}{}:
case logBuffer <- log:
break forceEmptyingLoop
}
}
}
// wake up writer if necessary
if logsWaitingFlag.SetToIf(false, true) {
logsWaiting <- struct{}{}
}
}
func (tracer *ContextTracer) log(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 = ""
}
}
tracer.Lock()
defer tracer.Unlock()
tracer.logs = append(tracer.logs, &logLine{
timestamp: time.Now(),
level: level,
msg: msg,
file: file,
line: line,
})
}
// Trace is used to log tiny steps. Log traces to context if you can!
func (tracer *ContextTracer) Trace(msg string) {
switch {
case tracer != nil:
tracer.log(TraceLevel, msg)
case fastcheck(TraceLevel):
log(TraceLevel, msg, nil)
}
}
// Tracef is used to log tiny steps. Log traces to context if you can!
func (tracer *ContextTracer) Tracef(format string, things ...interface{}) {
switch {
case tracer != nil:
tracer.log(TraceLevel, fmt.Sprintf(format, things...))
case fastcheck(TraceLevel):
log(TraceLevel, fmt.Sprintf(format, things...), nil)
}
}
// 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) {
switch {
case tracer != nil:
tracer.log(DebugLevel, msg)
case fastcheck(DebugLevel):
log(DebugLevel, msg, nil)
}
}
// 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{}) {
switch {
case tracer != nil:
tracer.log(DebugLevel, fmt.Sprintf(format, things...))
case fastcheck(DebugLevel):
log(DebugLevel, fmt.Sprintf(format, things...), nil)
}
}
// Info is used to log mildly significant events. Should be used to inform about somewhat bigger or user affecting events that happen.
func (tracer *ContextTracer) Info(msg string) {
switch {
case tracer != nil:
tracer.log(InfoLevel, msg)
case fastcheck(InfoLevel):
log(InfoLevel, msg, nil)
}
}
// Infof is used to log mildly significant events. Should be used to inform about somewhat bigger or user affecting events that happen.
func (tracer *ContextTracer) Infof(format string, things ...interface{}) {
switch {
case tracer != nil:
tracer.log(InfoLevel, fmt.Sprintf(format, things...))
case fastcheck(InfoLevel):
log(InfoLevel, fmt.Sprintf(format, things...), nil)
}
}
// Warning is used to log (potentially) bad events, but nothing broke (even a little) and there is no need to panic yet.
func (tracer *ContextTracer) Warning(msg string) {
switch {
case tracer != nil:
tracer.log(WarningLevel, msg)
case fastcheck(WarningLevel):
log(WarningLevel, msg, nil)
}
}
// Warningf is used to log (potentially) bad events, but nothing broke (even a little) and there is no need to panic yet.
func (tracer *ContextTracer) Warningf(format string, things ...interface{}) {
switch {
case tracer != nil:
tracer.log(WarningLevel, fmt.Sprintf(format, things...))
case fastcheck(WarningLevel):
log(WarningLevel, fmt.Sprintf(format, things...), nil)
}
}
// Error is used to log errors that break or impair functionality. The task/process may have to be aborted and tried again later. The system is still operational. Maybe User/Admin should be informed.
func (tracer *ContextTracer) Error(msg string) {
switch {
case tracer != nil:
tracer.log(ErrorLevel, msg)
case fastcheck(ErrorLevel):
log(ErrorLevel, msg, nil)
}
}
// Errorf is used to log errors that break or impair functionality. The task/process may have to be aborted and tried again later. The system is still operational.
func (tracer *ContextTracer) Errorf(format string, things ...interface{}) {
switch {
case tracer != nil:
tracer.log(ErrorLevel, fmt.Sprintf(format, things...))
case fastcheck(ErrorLevel):
log(ErrorLevel, fmt.Sprintf(format, things...), nil)
}
}
// Critical is used to log events that completely break the system. Operation connot continue. User/Admin must be informed.
func (tracer *ContextTracer) Critical(msg string) {
switch {
case tracer != nil:
tracer.log(CriticalLevel, msg)
case fastcheck(CriticalLevel):
log(CriticalLevel, msg, nil)
}
}
// Criticalf is used to log events that completely break the system. Operation connot continue. User/Admin must be informed.
func (tracer *ContextTracer) Criticalf(format string, things ...interface{}) {
switch {
case tracer != nil:
tracer.log(CriticalLevel, fmt.Sprintf(format, things...))
case fastcheck(CriticalLevel):
log(CriticalLevel, fmt.Sprintf(format, things...), nil)
}
}