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) } }