update to what is current

This commit is contained in:
2025-08-19 16:05:34 +01:00
parent 3f1a2cbd93
commit 4f353f1170
7 changed files with 556 additions and 169 deletions

13
chk/chk.go Normal file
View File

@@ -0,0 +1,13 @@
// Package chk is a convenience shortcut to use shorter names to access the lol.Logger.
package chk
import (
"github.com/mleku/lol"
)
var F, E, W, I, D, T lol.Chk
func init() {
F, E, W, I, D, T = lol.Main.Check.F, lol.Main.Check.E, lol.Main.Check.W, lol.Main.Check.I,
lol.Main.Check.D, lol.Main.Check.T
}

12
errorf/errorf.go Normal file
View File

@@ -0,0 +1,12 @@
// Package errorf is a convenience shortcut to use shorter names to access the lol.Logger.
package errorf
import (
"github.com/mleku/lol"
)
var F, E, W, I, D, T lol.Err
func init() {
F, E, W, I, D, T = lol.Main.Errorf.F, lol.Main.Errorf.E, lol.Main.Errorf.W, lol.Main.Errorf.I, lol.Main.Errorf.D, lol.Main.Errorf.T
}

10
go.mod
View File

@@ -1,14 +1,14 @@
module github.com/mleku/lol module github.com/mleku/lol
go 1.22.4 go 1.25.0
require ( require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/gookit/color v1.5.4 github.com/fatih/color v1.18.0
go.uber.org/atomic v1.11.0
) )
require ( require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/mattn/go-colorable v0.1.14 // indirect
golang.org/x/sys v0.21.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.35.0 // indirect
) )

27
go.sum
View File

@@ -1,18 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= orly.dev v0.8.7 h1:81Dn93vvZM0Q3GmGxmd+m44PkFOZpmeZBszS33vgk5I=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= orly.dev v0.8.7/go.mod h1:jnbkB9+qxjUdsy8bdiR8Mk0t3Y+UeLnS3pY3dS6Oi/M=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

337
log.go
View File

@@ -1,3 +1,7 @@
// Package lol (log of location) is a simple logging library that prints a high
// precision unix timestamp and the source location of a log print to make
// tracing errors simpler. Includes a set of logging levels and the ability to
// filter out higher log levels for a more quiet output.
package lol package lol
import ( import (
@@ -5,48 +9,13 @@ import (
"io" "io"
"os" "os"
"runtime" "runtime"
"strings" "sync/atomic"
"time" "time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/gookit/color" "github.com/fatih/color"
"go.uber.org/atomic"
) )
var l = GetStd()
func GetStd() (ll *Log) {
ll, _ = New(os.Stdout)
return
}
func init() {
switch strings.ToUpper(os.Getenv("GODEBUG")) {
case "1", "TRUE", "ON":
SetLogLevel(Debug)
l.D.Ln("printing logs at this level and lower")
case "INFO":
SetLogLevel(Info)
case "DEBUG":
SetLogLevel(Debug)
l.D.Ln("printing logs at this level and lower")
case "TRACE":
SetLogLevel(Trace)
l.T.Ln("printing logs at this level and lower")
case "WARN":
SetLogLevel(Warn)
case "ERROR":
SetLogLevel(Error)
case "FATAL":
SetLogLevel(Fatal)
case "0", "OFF", "FALSE":
SetLogLevel(Off)
default:
SetLogLevel(Info)
}
}
const ( const (
Off = iota Off = iota
Fatal Fatal
@@ -57,24 +26,36 @@ const (
Trace Trace
) )
type ( var LevelNames = []string{
// LevelPrinter defines a set of terminal printing primitives that output with "off",
// extra data, time, log logLevelList, and code location "fatal",
"error",
"warn",
"info",
"debug",
"trace",
}
// Ln prints lists of interfaces with spaces in between type (
// LevelPrinter defines a set of terminal printing primitives that output
// with extra data, time, log logLevelList, and code location
// Ln prints lists of server with spaces in between
Ln func(a ...interface{}) Ln func(a ...interface{})
// F prints like fmt.Println surrounded by log details // F prints like fmt.Println surrounded []byte log details
F func(format string, a ...interface{}) F func(format string, a ...interface{})
// S prints a spew.Sdump for an interface slice // S prints a spew.Sdump for an enveloper slice
S func(a ...interface{}) S func(a ...interface{})
// C accepts a function so that the extra computation can be avoided if it is // C accepts a function so that the extra computation can be avoided if it is not being
// not being viewed // viewed
C func(closure func() string) C func(closure func() string)
// Chk is a shortcut for printing if there is an error, or returning true // Chk is a shortcut for printing if there is an error, or returning true
Chk func(e error) bool Chk func(e error) bool
// Err is a pass-through function that uses fmt.Errorf to construct an error // Err is a pass-through function that uses fmt.Errorf to construct an error and returns the
// and returns the error after printing it to the log // error after printing it to the log
Err func(format string, a ...interface{}) error Err func(format string, a ...any) error
// LevelPrinter is the set of log printers on each log level.
LevelPrinter struct { LevelPrinter struct {
Ln Ln
F F
@@ -83,10 +64,12 @@ type (
Chk Chk
Err Err
} }
// LevelSpec is the name, ID and Colorizer for a log level.
LevelSpec struct { LevelSpec struct {
ID int ID int
Name string Name string
Colorizer func(a ...interface{}) string Colorizer func(a ...any) string
} }
// Entry is a log entry to be printed as json to the log file // Entry is a log entry to be printed as json to the log file
@@ -100,33 +83,98 @@ type (
) )
var ( var (
// sep is just a convenient shortcut for this very longwinded expression // Writer can be swapped out for any io.*Writer* that you want to use instead of stdout.
sep = string(os.PathSeparator) Writer io.Writer = os.Stderr
currentLevel = atomic.NewInt32(Info)
// writer can be swapped out for any io.*writer* that you want to use instead of
// stdout.
writer io.Writer = os.Stderr
// LevelSpecs specifies the id, string name and color-printing function // LevelSpecs specifies the id, string name and color-printing function
LevelSpecs = []LevelSpec{ LevelSpecs = []LevelSpec{
{Off, " ", color.Bit24(0, 0, 0, false).Sprint}, {Off, "", NoSprint},
{Fatal, "FTL", color.Bit24(128, 0, 0, false).Sprint}, {Fatal, "FTL", color.New(color.BgRed, color.FgHiWhite).Sprint},
{Error, "ERR", color.Bit24(255, 0, 0, false).Sprint}, {Error, "ERR", color.New(color.FgHiRed).Sprint},
{Warn, "WRN", color.Bit24(0, 255, 0, false).Sprint}, {Warn, "WRN", color.New(color.FgHiYellow).Sprint},
{Info, "INF", color.Bit24(255, 255, 0, false).Sprint}, {Info, "INF", color.New(color.FgHiGreen).Sprint},
{Debug, "DBG", color.Bit24(0, 125, 255, false).Sprint}, {Debug, "DBG", color.New(color.FgHiBlue).Sprint},
{Trace, "TRC", color.Bit24(125, 0, 255, false).Sprint}, {Trace, "TRC", color.New(color.FgHiMagenta).Sprint},
} }
) )
// NoSprint is a noop for sprint (it returns nothing no matter what is given to it).
func NoSprint(_ ...any) string { return "" }
// Log is a set of log printers for the various Level items. // Log is a set of log printers for the various Level items.
type Log struct { type Log struct {
F, E, W, I, D, T LevelPrinter F, E, W, I, D, T LevelPrinter
} }
// Check is the set of log levels for a Check operation (prints an error if the error is not
// nil).
type Check struct { type Check struct {
F, E, W, I, D, T Chk F, E, W, I, D, T Chk
} }
// Errorf prints an error that is also returned as an error, so the error is logged at the site.
type Errorf struct {
F, E, W, I, D, T Err
}
// Logger is a collection of things that creates a logger, including levels.
type Logger struct {
*Log
*Check
*Errorf
}
// Level is the level that the logger is printing at.
var Level atomic.Int32
// Main is the main logger.
var Main = &Logger{}
func init() {
// Main = &Logger{}
Main.Log, Main.Check, Main.Errorf = New(os.Stderr, 2)
ll := os.Getenv("LOG_LEVEL")
if ll == "" {
SetLogLevel("info")
} else {
for i := range LevelNames {
if ll == LevelNames[i] {
SetLoggers(i)
return
}
}
SetLoggers(Info)
}
}
// SetLoggers configures a log level.
func SetLoggers(level int) {
Main.Log.T.F("log level %s", LevelSpecs[level].Colorizer(LevelNames[level]))
Level.Store(int32(level))
}
// GetLogLevel returns the log level number of a string log level.
func GetLogLevel(level string) (i int) {
for i = range LevelNames {
if level == LevelNames[i] {
return i
}
}
return Info
}
// SetLogLevel sets the log level of the logger.
func SetLogLevel(level string) {
for i := range LevelNames {
if level == LevelNames[i] {
SetLoggers(i)
return
}
}
SetLoggers(Trace)
}
// JoinStrings joins together anything into a set of strings with space separating the items.
func JoinStrings(a ...any) (s string) { func JoinStrings(a ...any) (s string) {
for i := range a { for i := range a {
s += fmt.Sprint(a[i]) s += fmt.Sprint(a[i])
@@ -137,78 +185,124 @@ func JoinStrings(a ...any) (s string) {
return return
} }
func GetPrinter(l int32, writer io.Writer) LevelPrinter { var msgCol = color.New(color.FgBlue).Sprint
// GetPrinter returns a full logger that writes to the provided io.Writer.
func GetPrinter(l int32, writer io.Writer, skip int) LevelPrinter {
return LevelPrinter{ return LevelPrinter{
Ln: func(a ...interface{}) { Ln: func(a ...interface{}) {
fmt.Fprintf(writer, if Level.Load() < l {
"%s %s %s %s\n", return
UnixNanoAsFloat(), }
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name), LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
JoinStrings(a...), JoinStrings(a...),
GetLoc(2), msgCol(GetLoc(skip)),
) )
}, },
F: func(format string, a ...interface{}) { F: func(format string, a ...interface{}) {
fmt.Fprintf(writer, if Level.Load() < l {
"%s %s %s %s\n", return
UnixNanoAsFloat(), }
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name), LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
fmt.Sprintf(format, a...), fmt.Sprintf(format, a...),
GetLoc(2), msgCol(GetLoc(skip)),
) )
}, },
S: func(a ...interface{}) { S: func(a ...interface{}) {
fmt.Fprintf(writer, if Level.Load() < l {
"%s %s %s %s\n", return
UnixNanoAsFloat(), }
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name), LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
spew.Sdump(a...), spew.Sdump(a...),
GetLoc(2), msgCol(GetLoc(skip)),
) )
}, },
C: func(closure func() string) { C: func(closure func() string) {
fmt.Fprintf(writer, if Level.Load() < l {
"%s %s %s %s\n", return
UnixNanoAsFloat(), }
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name), LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
closure(), closure(),
GetLoc(2), msgCol(GetLoc(skip)),
) )
}, },
Chk: func(e error) bool { Chk: func(e error) bool {
if Level.Load() < l {
return e != nil
}
if e != nil { if e != nil {
fmt.Fprintf(writer, _, _ = fmt.Fprintf(
"%s %s %s %s\n", writer,
UnixNanoAsFloat(), "%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name), LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
e.Error(), e.Error(),
GetLoc(2), msgCol(GetLoc(skip)),
) )
return true return true
} }
return false return false
}, },
Err: func(format string, a ...interface{}) error { Err: func(format string, a ...interface{}) error {
fmt.Fprintf(writer, if Level.Load() >= l {
"%s %s %s %s\n", _, _ = fmt.Fprintf(
UnixNanoAsFloat(), writer,
LevelSpecs[l].Colorizer(LevelSpecs[l].Name, " "), "%s%s %s %s\n",
fmt.Sprintf(format, a...), msgCol(TimeStamper()),
GetLoc(2), LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
) fmt.Sprintf(format, a...),
msgCol(GetLoc(skip)),
)
}
return fmt.Errorf(format, a...) return fmt.Errorf(format, a...)
}, },
} }
} }
func New(writer io.Writer) (l *Log, c *Check) { // GetNullPrinter is a logger that doesn't log.
func GetNullPrinter() LevelPrinter {
return LevelPrinter{
Ln: func(a ...interface{}) {},
F: func(format string, a ...interface{}) {},
S: func(a ...interface{}) {},
C: func(closure func() string) {},
Chk: func(e error) bool { return e != nil },
Err: func(
format string, a ...interface{},
) error {
return fmt.Errorf(format, a...)
},
}
}
// New creates a new logger with all the levels and things.
func New(writer io.Writer, skip int) (l *Log, c *Check, errorf *Errorf) {
if writer == nil {
writer = Writer
}
l = &Log{ l = &Log{
F: GetPrinter(Fatal, writer), T: GetPrinter(Trace, writer, skip),
E: GetPrinter(Error, writer), D: GetPrinter(Debug, writer, skip),
W: GetPrinter(Warn, writer), I: GetPrinter(Info, writer, skip),
I: GetPrinter(Info, writer), W: GetPrinter(Warn, writer, skip),
D: GetPrinter(Debug, writer), E: GetPrinter(Error, writer, skip),
T: GetPrinter(Trace, writer), F: GetPrinter(Fatal, writer, skip),
} }
c = &Check{ c = &Check{
F: l.F.Chk, F: l.F.Chk,
@@ -218,38 +312,35 @@ func New(writer io.Writer) (l *Log, c *Check) {
D: l.D.Chk, D: l.D.Chk,
T: l.T.Chk, T: l.T.Chk,
} }
errorf = &Errorf{
F: l.F.Err,
E: l.E.Err,
W: l.W.Err,
I: l.I.Err,
D: l.D.Err,
T: l.T.Err,
}
return return
} }
// SetLogLevel sets the log level via a string, which can be truncated down to // TimeStamper generates the timestamp for logs.
// one character, similar to nmcli's argument processor, as the first letter is func TimeStamper() (s string) {
// unique. This could be used with a linter to make larger command sets. ts := time.Now().Format("150405.000000")
func SetLogLevel(l int) { ds := time.Now().Format("2006-01-02")
currentLevel.Store(int32(l)) s += color.New(color.FgBlue).Sprint(ds[0:4])
} s += color.New(color.FgHiBlue).Sprint(ds[5:7])
s += color.New(color.FgBlue).Sprint(ds[8:])
func GetLogLevel() (l int) { s += color.New(color.FgHiBlue).Sprint(ts[0:2])
return int(currentLevel.Load()) s += color.New(color.FgBlue).Sprint(ts[2:4])
} s += color.New(color.FgHiBlue).Sprint(ts[4:6])
s += color.New(color.FgBlue).Sprint(ts[7:])
// UnixNanoAsFloat e s += " "
func UnixNanoAsFloat() (s string) { return
timeText := fmt.Sprint(time.Now().UnixNano())
lt := len(timeText)
lb := lt + 1
var timeBytes = make([]byte, lb)
copy(timeBytes[lb-9:lb], timeText[lt-9:lt])
timeBytes[lb-10] = '.'
lb -= 10
lt -= 9
copy(timeBytes[:lb], timeText[:lt])
return color.Bit24(0, 128, 255, false).Sprint(string(timeBytes))
} }
// GetLoc returns the code location of the caller.
func GetLoc(skip int) (output string) { func GetLoc(skip int) (output string) {
_, file, line, _ := runtime.Caller(skip) _, file, line, _ := runtime.Caller(skip)
output = color.Bit24(0, 128, 255, false).Sprint( output = fmt.Sprintf("%s:%d", file, line)
file, ":", line,
)
return return
} }

12
log/log.go Normal file
View File

@@ -0,0 +1,12 @@
// Package log is a convenience shortcut to use shorter names to access the lol.Logger.
package log
import (
"github.com/mleku/lol"
)
var F, E, W, I, D, T lol.LevelPrinter
func init() {
F, E, W, I, D, T = lol.Main.Log.F, lol.Main.Log.E, lol.Main.Log.W, lol.Main.Log.I, lol.Main.Log.D, lol.Main.Log.T
}

View File

@@ -1,34 +1,298 @@
package lol_test package lol
import ( import (
"bytes"
"errors" "errors"
"os" "fmt"
"strings"
"testing" "testing"
"github.com/mleku/lol"
) )
var log, chk = lol.New(os.Stdout) func TestLogLevels(t *testing.T) {
// Test that log levels are correctly ordered
if !(Off < Fatal && Fatal < Error && Error < Warn && Warn < Info && Info < Debug && Debug < Trace) {
t.Error("Log levels are not correctly ordered")
}
func TestGetLogger(t *testing.T) { // Test that LevelNames matches the constants
for i := 0; i < 100; i++ { expectedLevelNames := []string{
lol.SetLogLevel(lol.Trace) "off", "fatal", "error", "warn", "info", "debug", "trace",
log.T.Ln("testing log level", lol.LevelSpecs[lol.Trace].Name) }
log.D.Ln("testing log level", lol.LevelSpecs[lol.Debug].Name) for i, name := range expectedLevelNames {
log.I.Ln("testing log level", lol.LevelSpecs[lol.Info].Name) if LevelNames[i] != name {
log.W.Ln("testing log level", lol.LevelSpecs[lol.Warn].Name) t.Errorf("LevelNames[%d] = %s, want %s", i, LevelNames[i], name)
log.E.F("testing log level %s", lol.LevelSpecs[lol.Error].Name) }
log.F.Ln("testing log level", lol.LevelSpecs[lol.Fatal].Name) }
chk.F(errors.New("dummy error as fatal")) }
chk.E(errors.New("dummy error as error"))
chk.W(errors.New("dummy error as warning")) func TestGetLogLevel(t *testing.T) {
chk.I(errors.New("dummy error as info")) tests := []struct {
chk.D(errors.New("dummy error as debug")) level string
chk.T(errors.New("dummy error as trace")) expected int
log.I.Ln("log.I.Err", }{
log.I.Err("format string %d '%s'", 5, "testing") != nil) {"off", Off},
log.I.Chk(errors.New("dummy information check")) {"fatal", Fatal},
log.I.Chk(nil) {"error", Error},
log.I.S("`backtick wrapped string`", t) {"warn", Warn},
{"info", Info},
{"debug", Debug},
{"trace", Trace},
{"unknown", Info}, // Default to Info for unknown levels
}
for _, test := range tests {
t.Run(
test.level, func(t *testing.T) {
result := GetLogLevel(test.level)
if result != test.expected {
t.Errorf(
"GetLogLevel(%q) = %d, want %d", test.level, result,
test.expected,
)
}
},
)
}
}
func TestSetLogLevel(t *testing.T) {
// Save original level
originalLevel := Level.Load()
defer SetLoggers(int(originalLevel)) // Restore original level after test
tests := []struct {
level string
expected int32
}{
{"off", Off},
{"fatal", Fatal},
{"error", Error},
{"warn", Warn},
{"info", Info},
{"debug", Debug},
{"trace", Trace},
{"unknown", Trace}, // Should default to Trace for unknown levels
}
for _, test := range tests {
t.Run(
test.level, func(t *testing.T) {
SetLogLevel(test.level)
result := Level.Load()
if result != test.expected {
t.Errorf(
"After SetLogLevel(%q), Level = %d, want %d",
test.level, result, test.expected,
)
}
},
)
}
}
func TestJoinStrings(t *testing.T) {
tests := []struct {
args []any
expected string
}{
{[]any{}, ""},
{[]any{"hello"}, "hello"},
{[]any{"hello", "world"}, "hello world"},
{[]any{1, 2, 3}, "1 2 3"},
{[]any{1, "hello", 3.14}, "1 hello 3.14"},
}
for i, test := range tests {
t.Run(
fmt.Sprintf("case_%d", i), func(t *testing.T) {
result := JoinStrings(test.args...)
if result != test.expected {
t.Errorf(
"JoinStrings(%v) = %q, want %q", test.args, result,
test.expected,
)
}
},
)
}
}
func TestGetLoc(t *testing.T) {
loc := GetLoc(1)
if !strings.Contains(loc, "log_test.go") {
t.Errorf("GetLoc(1) = %q, expected to contain 'log_test.go'", loc)
}
}
func TestGetPrinter(t *testing.T) {
// Create a buffer to capture output
var buf bytes.Buffer
// Set log level to Info
originalLevel := Level.Load()
Level.Store(int32(Info))
defer Level.Store(originalLevel) // Restore original level
// Create a printer for Debug level
printer := GetPrinter(int32(Debug), &buf, 1)
// Test Ln method - should not print because Debug > Info
buf.Reset()
printer.Ln("test message")
if buf.String() != "" {
t.Errorf(
"printer.Ln() printed when level is too high: %q", buf.String(),
)
}
// Set log level to Debug
Level.Store(int32(Debug))
// Test Ln method - should print now
buf.Reset()
printer.Ln("test message")
output := buf.String()
if output == "" {
t.Error("printer.Ln() did not print when it should have")
}
if !strings.Contains(output, "test message") {
t.Errorf(
"printer.Ln() output %q does not contain 'test message'", output,
)
}
// Test F method
buf.Reset()
printer.F("formatted %s", "message")
output = buf.String()
if !strings.Contains(output, "formatted message") {
t.Errorf(
"printer.F() output %q does not contain 'formatted message'",
output,
)
}
// Test S method
buf.Reset()
printer.S("spew message")
output = buf.String()
if !strings.Contains(output, "spew message") {
t.Errorf(
"printer.S() output %q does not contain 'spew message'", output,
)
}
// Test C method
buf.Reset()
printer.C(func() string { return "closure message" })
output = buf.String()
if !strings.Contains(output, "closure message") {
t.Errorf(
"printer.C() output %q does not contain 'closure message'", output,
)
}
// Test Chk method with nil error
buf.Reset()
result := printer.Chk(nil)
if result != false {
t.Error("printer.Chk(nil) returned true, expected false")
}
if buf.String() != "" {
t.Errorf("printer.Chk(nil) printed output: %q", buf.String())
}
// Test Chk method with error
buf.Reset()
testErr := errors.New("test error")
result = printer.Chk(testErr)
if result != true {
t.Error("printer.Chk(error) returned false, expected true")
}
if !strings.Contains(buf.String(), "test error") {
t.Errorf(
"printer.Chk(error) output %q does not contain 'test error'",
buf.String(),
)
}
// Test Err method
buf.Reset()
err := printer.Err("error %s", "message")
if err == nil {
t.Error("printer.Err() returned nil error")
}
if err.Error() != "error message" {
t.Errorf(
"printer.Err() returned error with message %q, expected 'error message'",
err.Error(),
)
}
// Check if the message was logged
if !strings.Contains(buf.String(), "error message") {
t.Errorf(
"printer.Err() output %q does not contain 'error message'",
buf.String(),
)
}
}
func TestGetNullPrinter(t *testing.T) {
printer := GetNullPrinter()
// Test that Ln, F, S, C methods don't panic
printer.Ln("test")
printer.F("test %s", "format")
printer.S("test")
printer.C(func() string { return "test" })
// Test Chk method
if !printer.Chk(errors.New("test")) {
t.Error("GetNullPrinter().Chk(error) returned false, expected true")
}
if printer.Chk(nil) {
t.Error("GetNullPrinter().Chk(nil) returned true, expected false")
}
// Test Err method
err := printer.Err("test %s", "error")
if err == nil {
t.Error("GetNullPrinter().Err() returned nil error")
}
if err.Error() != "test error" {
t.Errorf(
"GetNullPrinter().Err() returned error with message %q, expected 'test error'",
err.Error(),
)
}
}
func TestNew(t *testing.T) {
var buf bytes.Buffer
log, check, errorf := New(&buf, 1)
// Verify that all components are created
if log == nil {
t.Error("New() returned nil Log")
}
if check == nil {
t.Error("New() returned nil Check")
}
if errorf == nil {
t.Error("New() returned nil Errorf")
}
// Test that the log functions work
originalLevel := Level.Load()
Level.Store(int32(Debug))
defer Level.Store(originalLevel)
buf.Reset()
log.D.Ln("test message")
if !strings.Contains(buf.String(), "test message") {
t.Errorf(
"log.D.Ln() output %q doesn't contain 'test message'",
buf.String(),
)
} }
} }