repo init
This commit is contained in:
commit
ccaedce6f3
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# ---> Go
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*/awcli
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.yaml
|
||||||
|
example/example
|
51
envelope.go
Normal file
51
envelope.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type envelope struct {
|
||||||
|
log logger
|
||||||
|
|
||||||
|
lvl level
|
||||||
|
|
||||||
|
// err is an error attached to the envelope
|
||||||
|
err error
|
||||||
|
|
||||||
|
args map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envelope) Msg(f string, args ...any) {
|
||||||
|
if e.log.Level() < e.lvl {
|
||||||
|
return // No logging
|
||||||
|
}
|
||||||
|
// formatter and output things here
|
||||||
|
E := &envelopeData{*e, time.Now()}
|
||||||
|
msg := fmt.Sprintf(f, args...)
|
||||||
|
loggers[e.log].format.Output(msg, E)
|
||||||
|
e.lvl.Hook(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envelope) Arg(name string, value any) *envelope {
|
||||||
|
e.args[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)
|
||||||
|
}
|
||||||
|
}
|
31
example/example.go
Normal file
31
example/example.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"udico.de/util/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.Logger("MyLogger").SetLevel(log.TRACE)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.DefaultLogger.SetLevel(log.DEBUG)
|
||||||
|
log.WARN.To(logger).Msg("Now doing things :)")
|
||||||
|
|
||||||
|
log.INFO.Msg("hallo %v", "bla")
|
||||||
|
log.TRACE.To(logger).Arg("a", "b").If(func(msg log.Fn) {
|
||||||
|
msg("Calculating important things ...")
|
||||||
|
msg("hi!")
|
||||||
|
})
|
||||||
|
|
||||||
|
log.PANIC.To(logger).Msg("Bad things happened :(")
|
||||||
|
log.NOTICE.Msg("I'm done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
log.INFO.Msg("...", a)
|
||||||
|
log.INFO.Ctx(lo).With("field", "").Msg(...)
|
||||||
|
|
||||||
|
log.TRACE.If(func(log logfn) {
|
||||||
|
// some expensive code goes here
|
||||||
|
log("something")
|
||||||
|
})
|
||||||
|
*/
|
49
formatter.go
Normal file
49
formatter.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Formatter ...
|
||||||
|
// - must be thread safe
|
||||||
|
// TODO: measure if it's faster to have a goroutine consuming a chan, compared to locking using a mutex.
|
||||||
|
//
|
||||||
|
// TODO: what about buffered channels? How/When will the timestamp be added?
|
||||||
|
//
|
||||||
|
// -> 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()
|
||||||
|
type Formatter interface {
|
||||||
|
Output(message string, e Envelope)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope interface {
|
||||||
|
Logger() logger
|
||||||
|
Level() level
|
||||||
|
Error() error
|
||||||
|
Arguments() map[string]string
|
||||||
|
Time() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// envelopeData wraps an envelope to match the Envelope interface.
|
||||||
|
type envelopeData struct {
|
||||||
|
envelope
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e envelopeData) Logger() logger {
|
||||||
|
return e.log
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e envelopeData) Level() level {
|
||||||
|
return e.lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e envelopeData) Error() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e envelopeData) Arguments() map[string]string {
|
||||||
|
return e.args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e envelopeData) Time() time.Time {
|
||||||
|
return e.t
|
||||||
|
}
|
117
formatter_plain.go
Normal file
117
formatter_plain.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/term"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ColorMode indicates if colorized output should be used
|
||||||
|
type ColorMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ColorAuto means to automatically detect if a writer is an interactive terminal and
|
||||||
|
// if so use colorized output.
|
||||||
|
ColorAuto ColorMode = 0
|
||||||
|
|
||||||
|
// ColorNever avoids colorized output at all.
|
||||||
|
ColorNever ColorMode = -1
|
||||||
|
|
||||||
|
// ColorAlways enforces colorized output even if it is routed into some file.
|
||||||
|
ColorAlways ColorMode = 1
|
||||||
|
|
||||||
|
// ColorOn means to enable colorized output.
|
||||||
|
ColorOn ColorMode = 1
|
||||||
|
|
||||||
|
// ColorOff means not to use colorized output.
|
||||||
|
ColorOff ColorMode = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Colorize indicates if colorized output should be used.
|
||||||
|
func (c ColorMode) Colorize() bool {
|
||||||
|
return c > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlainFormatter struct {
|
||||||
|
UseColor ColorMode
|
||||||
|
|
||||||
|
/* avoid checking the output fd on each log message. Only do so if fd changed */
|
||||||
|
lastFd int
|
||||||
|
fdColor ColorMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *PlainFormatter) resolveColorMode(target io.Writer) ColorMode {
|
||||||
|
if f.UseColor == ColorAuto {
|
||||||
|
fd := -1
|
||||||
|
if file, ok := target.(*os.File); ok {
|
||||||
|
fd = int(file.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.fdColor == ColorAuto || fd != f.lastFd {
|
||||||
|
// update cache
|
||||||
|
f.lastFd = fd
|
||||||
|
if fd > -1 && term.IsTerminal(fd) {
|
||||||
|
f.fdColor = ColorOn
|
||||||
|
} else {
|
||||||
|
f.fdColor = ColorOff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.fdColor
|
||||||
|
}
|
||||||
|
return f.UseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
type levelColorData struct {
|
||||||
|
Dark string
|
||||||
|
Light string
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fg bg
|
||||||
|
Black 30/90 40/100
|
||||||
|
Red 31 41
|
||||||
|
Green 32 42
|
||||||
|
Yellow 33 43
|
||||||
|
Blue 34 44
|
||||||
|
Magenta 35 45
|
||||||
|
Cyan 36 46
|
||||||
|
White 37 47
|
||||||
|
*/
|
||||||
|
|
||||||
|
var levelColorDatas = []levelColorData{
|
||||||
|
/* SILENT */ {Dark: "\033[30;107m", Light: ""}, // never ever log it?
|
||||||
|
/* PANIC */ {Dark: "\033[93;101m", Light: ""},
|
||||||
|
/* FATAL */ {Dark: "\033[30;101m", Light: ""},
|
||||||
|
/* ERROR */ {Dark: "\033[91m", Light: ""},
|
||||||
|
/* WARN */ {Dark: "\033[33m", Light: ""},
|
||||||
|
/* NOTICE */ {Dark: "\033[95m", Light: ""},
|
||||||
|
/* INFO */ {Dark: "\033[97m", Light: ""},
|
||||||
|
/* DEBUG */ {Dark: "\033[37m", Light: ""},
|
||||||
|
/* TRACE */ {Dark: "\033[90m", Light: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *PlainFormatter) Output(message string, envelope Envelope) {
|
||||||
|
o := envelope.Logger().Target()
|
||||||
|
cm := f.resolveColorMode(o)
|
||||||
|
msg := ""
|
||||||
|
if cm.Colorize() {
|
||||||
|
msg += levelColorDatas[envelope.Level()].Dark
|
||||||
|
}
|
||||||
|
|
||||||
|
msg += envelope.Time().Format("20060102-150405.000000") + " "
|
||||||
|
if envelope.Logger() != 0 {
|
||||||
|
msg += "[" + envelope.Logger().String() + "] "
|
||||||
|
}
|
||||||
|
msg += envelope.Level().String() + ": "
|
||||||
|
msg += message
|
||||||
|
if cm.Colorize() {
|
||||||
|
msg += "\033[0m"
|
||||||
|
}
|
||||||
|
if msg[len(msg)-1] != '\n' {
|
||||||
|
msg += "\n"
|
||||||
|
}
|
||||||
|
// "time (logger) level message args"
|
||||||
|
|
||||||
|
fmt.Fprint(o, msg)
|
||||||
|
}
|
7
go.mod
Normal file
7
go.mod
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module udico.de/util/log
|
||||||
|
|
||||||
|
go 1.21.5
|
||||||
|
|
||||||
|
require golang.org/x/term v0.16.0
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.16.0 // indirect
|
4
go.sum
Normal file
4
go.sum
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
131
level.go
Normal file
131
level.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type level uint8
|
||||||
|
|
||||||
|
/*
|
||||||
|
Emergency (emerg): indicates that the system is unusable and requires immediate attention.
|
||||||
|
Alert (alert): indicates that immediate action is necessary to resolve a critical issue.
|
||||||
|
Critical (crit): signifies critical conditions in the program that demand intervention to prevent system failure.
|
||||||
|
Error (error): indicates error conditions that impair some operation but are less severe than critical situations.
|
||||||
|
Warning (warn): signifies potential issues that may lead to errors or unexpected behavior in the future if not addressed.
|
||||||
|
Notice (notice): applies to normal but significant conditions that may require monitoring.
|
||||||
|
Informational (info): includes messages that provide a record of the normal operation of the system.
|
||||||
|
Debug (debug): intended for logging detailed information about the system for debugging purposes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SILENT means no log at all. not even panics. We just don't care about the end of the world.
|
||||||
|
SILENT level = iota
|
||||||
|
|
||||||
|
// PANIC (also: Emergency) indicates events which cannot be handled gracefully. Program execution will terminate.
|
||||||
|
PANIC
|
||||||
|
|
||||||
|
// FATAL (also: Critical, Alert) indicates a situation which requires user interaction to resolve.
|
||||||
|
FATAL
|
||||||
|
|
||||||
|
// ERROR indicates error conditions which may result in malfunction.
|
||||||
|
ERROR
|
||||||
|
|
||||||
|
// WARN signifies potential issues that may lead to errors or unexpected behavior in the future if not addressed.
|
||||||
|
WARN
|
||||||
|
|
||||||
|
// NOTICE logs are normal but significant information.
|
||||||
|
NOTICE
|
||||||
|
|
||||||
|
// INFO indicates normal, informational messages.
|
||||||
|
INFO
|
||||||
|
|
||||||
|
// DEBUG is intended for logging detailed information for debugging purposes.
|
||||||
|
DEBUG
|
||||||
|
|
||||||
|
// TRACE is the most detailed level of log output. Might spam your logs.
|
||||||
|
TRACE
|
||||||
|
)
|
||||||
|
|
||||||
|
type levelData struct {
|
||||||
|
Name string
|
||||||
|
Short string
|
||||||
|
Hook func(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelDatas = []levelData{ // No, it's not a map! Indexing into a slice is faster
|
||||||
|
{Name: "SILENT", Short: "SLT", Hook: nil},
|
||||||
|
{Name: "PANIC", Short: "PNC", Hook: func(msg string) { panic(msg) }},
|
||||||
|
{Name: "FATAL", Short: "FTL", Hook: nil},
|
||||||
|
{Name: "ERROR", Short: "ERR", Hook: nil},
|
||||||
|
{Name: "WARN", Short: "WRN", Hook: nil},
|
||||||
|
{Name: "NOTICE", Short: "NOT", Hook: nil},
|
||||||
|
{Name: "INFO", Short: "INF", Hook: nil},
|
||||||
|
{Name: "DEBUG", Short: "DBG", Hook: nil},
|
||||||
|
{Name: "TRACE", Short: "TRC", Hook: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// To sends the resulting log message to a given logger
|
||||||
|
func (l level) To(log logger) *envelope {
|
||||||
|
return &envelope{
|
||||||
|
log: log,
|
||||||
|
lvl: l,
|
||||||
|
err: nil,
|
||||||
|
args: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msg emits the given message to the default logger.
|
||||||
|
func (l level) Msg(format string, args ...any) {
|
||||||
|
l.To(0).Msg(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err attaches an error to the log message.
|
||||||
|
func (l level) Err(err error) *envelope {
|
||||||
|
return l.To(0).Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg attaches a named argument to the log message.
|
||||||
|
func (l level) Arg(name string, value any) *envelope {
|
||||||
|
return l.To(0).Arg(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If executes the given function only, if the logger is set to
|
||||||
|
func (l level) If(msg func(Fn)) {
|
||||||
|
l.To(0).If(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook executes the associated hook, if any
|
||||||
|
func (l level) Hook(msg string) {
|
||||||
|
if levelDatas[l].Hook != nil {
|
||||||
|
levelDatas[l].Hook(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l level) String() string {
|
||||||
|
return levelDatas[l].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLevel(levelstring string) (level, error) {
|
||||||
|
switch strings.ToLower(levelstring) {
|
||||||
|
case "silent", "off", "none":
|
||||||
|
return SILENT, nil
|
||||||
|
case "panic", "emergency", "emerg":
|
||||||
|
return PANIC, nil
|
||||||
|
case "fatal", "crit", "critical", "alert":
|
||||||
|
return FATAL, nil
|
||||||
|
case "error":
|
||||||
|
return ERROR, nil
|
||||||
|
case "warn", "warning":
|
||||||
|
return WARN, nil
|
||||||
|
case "notice":
|
||||||
|
return NOTICE, nil
|
||||||
|
case "info":
|
||||||
|
return INFO, nil
|
||||||
|
case "debug":
|
||||||
|
return DEBUG, nil
|
||||||
|
case "trace":
|
||||||
|
return TRACE, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("invalid loglevel '%v'", levelstring)
|
||||||
|
}
|
91
logger.go
Normal file
91
logger.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// logger is just an index into the loggers pool.
|
||||||
|
type logger int
|
||||||
|
|
||||||
|
const DefaultLogger logger = 0
|
||||||
|
|
||||||
|
// loggers is the pool of available loggers. The first entry always is empty,
|
||||||
|
// describing the default logger.
|
||||||
|
//var loggers []string = make([]string, 1, 10)
|
||||||
|
//var levels []level = make([]level, 1, 10)
|
||||||
|
//var targets []io.Writer = make([]io.Writer, 1, 10)
|
||||||
|
|
||||||
|
type loggerData struct {
|
||||||
|
name string
|
||||||
|
level level
|
||||||
|
target io.Writer
|
||||||
|
format Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
var loggers []loggerData = make([]loggerData, 1, 10)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DefaultLogger.SetLevel(INFO)
|
||||||
|
DefaultLogger.SetTarget(os.Stdout)
|
||||||
|
DefaultLogger.SetFormatter(&PlainFormatter{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger returns a logger with the given name. If there is no logger with this
|
||||||
|
// name, a new one will be created.
|
||||||
|
func Logger(name string) logger {
|
||||||
|
for i, v := range loggers {
|
||||||
|
if v.name == name {
|
||||||
|
return logger(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loggers = append(loggers, loggerData{
|
||||||
|
name: name,
|
||||||
|
level: INFO,
|
||||||
|
target: os.Stdout,
|
||||||
|
format: &PlainFormatter{},
|
||||||
|
})
|
||||||
|
return logger(len(loggers) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level returns the log level this logger is set to.
|
||||||
|
func (l logger) Level() level {
|
||||||
|
return loggers[l].level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the log level for this logger. Returns the logger, which enables chaining during initialization.
|
||||||
|
func (l logger) SetLevel(lvl level) logger {
|
||||||
|
loggers[l].level = lvl
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Target() io.Writer {
|
||||||
|
return loggers[l].target
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTarget sets the logging target. Returns the logger, which enables chaining during initialization.
|
||||||
|
func (l logger) SetTarget(t io.Writer) logger {
|
||||||
|
loggers[l].target = t
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Formatter() Formatter {
|
||||||
|
return loggers[l].format
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) SetFormatter(f Formatter) logger {
|
||||||
|
loggers[l].format = f
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) String() string {
|
||||||
|
return loggers[l].name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure parses a config string, enabling the configuration of multiple loggers at once.
|
||||||
|
// [logger:]level[@target][#format]
|
||||||
|
// target may be a file name
|
||||||
|
func Configure(config string) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user