144 lines
3.0 KiB
Go
144 lines
3.0 KiB
Go
package log
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type envelope struct {
|
|
log logger
|
|
|
|
lvl level
|
|
|
|
// err is an error attached to the envelope
|
|
err error
|
|
|
|
args []*Argument
|
|
|
|
trace level
|
|
}
|
|
|
|
type Argument struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
|
|
func (a Argument) String() string {
|
|
return "(" + a.Name + "=" + a.Value + ")"
|
|
}
|
|
|
|
func (e *envelope) Msg(f string, args ...any) {
|
|
e.msgT(time.Now(), f, args...)
|
|
}
|
|
|
|
func (e *envelope) msgT(t time.Time, f string, args ...any) {
|
|
if e.log.Level() < e.lvl {
|
|
return // No logging
|
|
}
|
|
// Convert to a public readable Envelope
|
|
E := &envelopeData{*e, t}
|
|
msg := fmt.Sprintf(f, args...)
|
|
// lock
|
|
loggers[e.log].format.Output(msg, E)
|
|
if e.trace > 0 || (e.err != nil && e.log.Autotrace() > 0) {
|
|
e.stack(t)
|
|
}
|
|
// unlock
|
|
e.lvl.hook(msg)
|
|
}
|
|
|
|
var trimpathwarned = false
|
|
|
|
// WithStack dumps a stack trace after the log message
|
|
func (e *envelope) WithStack() *envelope {
|
|
return e.WithStackAs(e.lvl)
|
|
}
|
|
|
|
// WithStackAs dumps a stack trace after the log message using the given log level
|
|
func (e *envelope) WithStackAs(lvl level) *envelope {
|
|
e.trace = lvl
|
|
return e
|
|
}
|
|
|
|
func (e *envelope) stack(et time.Time) {
|
|
if _, file, _, ok := runtime.Caller(1); ok {
|
|
if !trimpathwarned && strings.HasPrefix(file, "/") {
|
|
WARN.Msg("Binary includes build host information.\n")
|
|
WARN.Msg("Consider recompiling using the -trimpath flag.\n")
|
|
trimpathwarned = true
|
|
}
|
|
//e.trace.To(e.log).Msg("called from: %v:%v\n", file, line)
|
|
} else {
|
|
e.trace.To(e.log).Msg("cannot get caller")
|
|
return
|
|
}
|
|
pc := make([]uintptr, 10, 10)
|
|
n := runtime.Callers(2, pc)
|
|
frames := runtime.CallersFrames(pc[:n])
|
|
frame, more := frames.Next()
|
|
|
|
// use autotrace level, but if the envelope defines an explicit level use that one instead:
|
|
elvl := e.log.Autotrace()
|
|
if e.trace > 0 {
|
|
elvl = e.trace
|
|
}
|
|
|
|
for more {
|
|
// skip traces within the log module
|
|
if !strings.HasPrefix(frame.Function, "udico.de/util/log.") {
|
|
elvl.To(e.log).msgT(et, "@%v:%v in %v()\n", frame.File, frame.Line, frame.Function)
|
|
}
|
|
frame, more = frames.Next()
|
|
}
|
|
}
|
|
|
|
func (e *envelope) Arg(name string, value any) *envelope {
|
|
e.args = append(e.args, &Argument{name, fmt.Sprintf("%v", value)})
|
|
return e
|
|
}
|
|
|
|
func (e *envelope) Err(err error) *envelope {
|
|
e.err = err
|
|
return e
|
|
}
|
|
|
|
func (e *envelope) To(l logger) *envelope {
|
|
e.log = l
|
|
return e
|
|
}
|
|
|
|
type Fn func(fmt string, args ...any)
|
|
|
|
func (e *envelope) If(msg func(Fn)) {
|
|
if e.log.Level() >= e.lvl {
|
|
msg(e.Msg)
|
|
}
|
|
}
|
|
|
|
// Explain dumps the current log configuration using the logger and log level from the given envelope.
|
|
func (e *envelope) Explain() {
|
|
for _, v := range loggers {
|
|
name := v.name
|
|
if name == "" {
|
|
name = "default"
|
|
}
|
|
env := e.lvl.To(e.log).Arg("name", name)
|
|
if file, ok := v.target.(*os.File); ok {
|
|
tname := file.Name()
|
|
switch file.Fd() {
|
|
case os.Stdout.Fd():
|
|
tname = "stdout"
|
|
case os.Stderr.Fd():
|
|
tname = "stderr"
|
|
}
|
|
env.Arg("target", tname)
|
|
} else {
|
|
env.Arg("target", "custom")
|
|
}
|
|
env.Arg("formatter", v.format).Msg("Logger %v", name)
|
|
}
|
|
}
|