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