Add stack traces, errors, a simple formatter
This commit is contained in:
parent
ccaedce6f3
commit
9b65dc612f
102
envelope.go
102
envelope.go
|
@ -2,6 +2,9 @@ package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,22 +16,87 @@ type envelope struct {
|
||||||
// err is an error attached to the envelope
|
// err is an error attached to the envelope
|
||||||
err error
|
err error
|
||||||
|
|
||||||
args map[string]string
|
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) {
|
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 {
|
if e.log.Level() < e.lvl {
|
||||||
return // No logging
|
return // No logging
|
||||||
}
|
}
|
||||||
// formatter and output things here
|
// Convert to a public readable Envelope
|
||||||
E := &envelopeData{*e, time.Now()}
|
E := &envelopeData{*e, t}
|
||||||
msg := fmt.Sprintf(f, args...)
|
msg := fmt.Sprintf(f, args...)
|
||||||
|
// lock
|
||||||
loggers[e.log].format.Output(msg, E)
|
loggers[e.log].format.Output(msg, E)
|
||||||
e.lvl.Hook(msg)
|
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 {
|
func (e *envelope) Arg(name string, value any) *envelope {
|
||||||
e.args[name] = fmt.Sprintf("%v", value)
|
e.args = append(e.args, &Argument{name, fmt.Sprintf("%v", value)})
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,3 +117,27 @@ func (e *envelope) If(msg func(Fn)) {
|
||||||
msg(e.Msg)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"udico.de/util/log"
|
"udico.de/util/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = log.Logger("MyLogger").SetLevel(log.TRACE)
|
var logger = log.Logger("MyLogger").SetLevel(log.TRACE).SetAutotrace(log.TRACE)
|
||||||
|
|
||||||
|
func anotherFunc() {
|
||||||
|
log.NOTICE.WithStackAs(log.INFO).Msg("Let's dump a stack trace!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func aFunc() {
|
||||||
|
log.INFO.Msg("in aFunc")
|
||||||
|
anotherFunc()
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.DefaultLogger.SetLevel(log.DEBUG)
|
log.DefaultLogger.SetLevel(log.DEBUG).SetAutotrace(log.TRACE)
|
||||||
|
// dump logger config:
|
||||||
|
log.INFO.Explain()
|
||||||
log.WARN.To(logger).Msg("Now doing things :)")
|
log.WARN.To(logger).Msg("Now doing things :)")
|
||||||
|
|
||||||
log.INFO.Msg("hallo %v", "bla")
|
log.INFO.Msg("hallo %v", "bla")
|
||||||
|
@ -16,8 +28,11 @@ func main() {
|
||||||
msg("hi!")
|
msg("hi!")
|
||||||
})
|
})
|
||||||
|
|
||||||
log.PANIC.To(logger).Msg("Bad things happened :(")
|
aFunc()
|
||||||
|
|
||||||
|
log.ERROR.To(logger).WithStackAs(log.ERROR).Msg("Bad things happened :(")
|
||||||
log.NOTICE.Msg("I'm done!")
|
log.NOTICE.Msg("I'm done!")
|
||||||
|
log.INFO.Err(fmt.Errorf("oh, an error!")).Msg("what's this?")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -11,6 +11,8 @@ import "time"
|
||||||
// -> Answer: should be added to the Envelope right before the call to Output()
|
// -> Answer: should be added to the Envelope right before the call to Output()
|
||||||
// -> There should be an in between layer doing the channel thing calling Output()
|
// -> There should be an in between layer doing the channel thing calling Output()
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
|
|
||||||
|
// Output generates a log entry based on the given message and Envelope
|
||||||
Output(message string, e Envelope)
|
Output(message string, e Envelope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +20,7 @@ type Envelope interface {
|
||||||
Logger() logger
|
Logger() logger
|
||||||
Level() level
|
Level() level
|
||||||
Error() error
|
Error() error
|
||||||
Arguments() map[string]string
|
Arguments() []*Argument
|
||||||
Time() time.Time
|
Time() time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ func (e envelopeData) Error() error {
|
||||||
return e.err
|
return e.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e envelopeData) Arguments() map[string]string {
|
func (e envelopeData) Arguments() []*Argument {
|
||||||
return e.args
|
return e.args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ColorMode indicates if colorized output should be used
|
// ColorMode indicates if colorized output should be used
|
||||||
|
@ -91,6 +92,10 @@ var levelColorDatas = []levelColorData{
|
||||||
/* TRACE */ {Dark: "\033[90m", Light: ""},
|
/* TRACE */ {Dark: "\033[90m", Light: ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *PlainFormatter) String() string {
|
||||||
|
return "PlainFormatter"
|
||||||
|
}
|
||||||
|
|
||||||
func (f *PlainFormatter) Output(message string, envelope Envelope) {
|
func (f *PlainFormatter) Output(message string, envelope Envelope) {
|
||||||
o := envelope.Logger().Target()
|
o := envelope.Logger().Target()
|
||||||
cm := f.resolveColorMode(o)
|
cm := f.resolveColorMode(o)
|
||||||
|
@ -104,13 +109,18 @@ func (f *PlainFormatter) Output(message string, envelope Envelope) {
|
||||||
msg += "[" + envelope.Logger().String() + "] "
|
msg += "[" + envelope.Logger().String() + "] "
|
||||||
}
|
}
|
||||||
msg += envelope.Level().String() + ": "
|
msg += envelope.Level().String() + ": "
|
||||||
msg += message
|
msg += strings.TrimSpace(message)
|
||||||
|
if envelope.Error() != nil {
|
||||||
|
msg += fmt.Sprintf(" [err=%v]", envelope.Error())
|
||||||
|
}
|
||||||
|
for _, v := range envelope.Arguments() {
|
||||||
|
msg += " " + v.String()
|
||||||
|
}
|
||||||
if cm.Colorize() {
|
if cm.Colorize() {
|
||||||
msg += "\033[0m"
|
msg += "\033[0m"
|
||||||
}
|
}
|
||||||
if msg[len(msg)-1] != '\n' {
|
|
||||||
msg += "\n"
|
msg += "\n"
|
||||||
}
|
|
||||||
// "time (logger) level message args"
|
// "time (logger) level message args"
|
||||||
|
|
||||||
fmt.Fprint(o, msg)
|
fmt.Fprint(o, msg)
|
||||||
|
|
20
level.go
20
level.go
|
@ -71,7 +71,6 @@ func (l level) To(log logger) *envelope {
|
||||||
log: log,
|
log: log,
|
||||||
lvl: l,
|
lvl: l,
|
||||||
err: nil,
|
err: nil,
|
||||||
args: make(map[string]string),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +79,16 @@ func (l level) Msg(format string, args ...any) {
|
||||||
l.To(0).Msg(format, args...)
|
l.To(0).Msg(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStack dumps a stack trace after the log message
|
||||||
|
func (l level) WithStack() *envelope {
|
||||||
|
return l.To(0).WithStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStackAs dumps a stack trace after the log message using the given log level
|
||||||
|
func (l level) WithStackAs(lvl level) *envelope {
|
||||||
|
return l.To(0).WithStackAs(lvl)
|
||||||
|
}
|
||||||
|
|
||||||
// Err attaches an error to the log message.
|
// Err attaches an error to the log message.
|
||||||
func (l level) Err(err error) *envelope {
|
func (l level) Err(err error) *envelope {
|
||||||
return l.To(0).Err(err)
|
return l.To(0).Err(err)
|
||||||
|
@ -95,8 +104,8 @@ func (l level) If(msg func(Fn)) {
|
||||||
l.To(0).If(msg)
|
l.To(0).If(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook executes the associated hook, if any
|
// hook executes the associated hook, if any
|
||||||
func (l level) Hook(msg string) {
|
func (l level) hook(msg string) {
|
||||||
if levelDatas[l].Hook != nil {
|
if levelDatas[l].Hook != nil {
|
||||||
levelDatas[l].Hook(msg)
|
levelDatas[l].Hook(msg)
|
||||||
}
|
}
|
||||||
|
@ -106,6 +115,11 @@ func (l level) String() string {
|
||||||
return levelDatas[l].Name
|
return levelDatas[l].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explain dumps the current log configuration to the default logger using the given log level
|
||||||
|
func (l level) Explain() {
|
||||||
|
l.To(0).Explain()
|
||||||
|
}
|
||||||
|
|
||||||
func ParseLevel(levelstring string) (level, error) {
|
func ParseLevel(levelstring string) (level, error) {
|
||||||
switch strings.ToLower(levelstring) {
|
switch strings.ToLower(levelstring) {
|
||||||
case "silent", "off", "none":
|
case "silent", "off", "none":
|
||||||
|
|
20
logger.go
20
logger.go
|
@ -21,14 +21,17 @@ type loggerData struct {
|
||||||
level level
|
level level
|
||||||
target io.Writer
|
target io.Writer
|
||||||
format Formatter
|
format Formatter
|
||||||
|
autotrace level
|
||||||
}
|
}
|
||||||
|
|
||||||
var loggers []loggerData = make([]loggerData, 1, 10)
|
var loggers []loggerData = make([]loggerData, 1, 10)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
DefaultLogger.SetLevel(INFO)
|
DefaultLogger.
|
||||||
DefaultLogger.SetTarget(os.Stdout)
|
SetLevel(INFO).
|
||||||
DefaultLogger.SetFormatter(&PlainFormatter{})
|
SetTarget(os.Stdout).
|
||||||
|
SetFormatter(&PlainFormatter{}).
|
||||||
|
SetAutotrace(DEBUG)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger returns a logger with the given name. If there is no logger with this
|
// Logger returns a logger with the given name. If there is no logger with this
|
||||||
|
@ -78,6 +81,17 @@ func (l logger) SetFormatter(f Formatter) logger {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l logger) Autotrace() level {
|
||||||
|
return loggers[l].autotrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAutotrace sets the level to send the stack trace to in case an error is logged. (which is a
|
||||||
|
// log message with an error attached to it).
|
||||||
|
func (l logger) SetAutotrace(lvl level) logger {
|
||||||
|
loggers[l].autotrace = lvl
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
func (l logger) String() string {
|
func (l logger) String() string {
|
||||||
return loggers[l].name
|
return loggers[l].name
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user