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
go 1.22.4
go 1.25.0
require (
github.com/davecgh/go-spew v1.1.1
github.com/gookit/color v1.5.4
go.uber.org/atomic v1.11.0
github.com/fatih/color v1.18.0
)
require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.21.0 // indirect
github.com/mattn/go-colorable v0.1.14 // 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
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=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
orly.dev v0.8.7 h1:81Dn93vvZM0Q3GmGxmd+m44PkFOZpmeZBszS33vgk5I=
orly.dev v0.8.7/go.mod h1:jnbkB9+qxjUdsy8bdiR8Mk0t3Y+UeLnS3pY3dS6Oi/M=

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
import (
@@ -5,48 +9,13 @@ import (
"io"
"os"
"runtime"
"strings"
"sync/atomic"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/gookit/color"
"go.uber.org/atomic"
"github.com/fatih/color"
)
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 (
Off = iota
Fatal
@@ -57,24 +26,36 @@ const (
Trace
)
type (
// LevelPrinter defines a set of terminal printing primitives that output with
// extra data, time, log logLevelList, and code location
var LevelNames = []string{
"off",
"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{})
// F prints like fmt.Println surrounded by log details
// F prints like fmt.Println surrounded []byte log details
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{})
// C accepts a function so that the extra computation can be avoided if it is
// not being viewed
// C accepts a function so that the extra computation can be avoided if it is not being
// viewed
C func(closure func() string)
// Chk is a shortcut for printing if there is an error, or returning true
Chk func(e error) bool
// Err is a pass-through function that uses fmt.Errorf to construct an error
// and returns the error after printing it to the log
Err func(format string, a ...interface{}) error
// Err is a pass-through function that uses fmt.Errorf to construct an error and returns the
// error after printing it to the log
Err func(format string, a ...any) error
// LevelPrinter is the set of log printers on each log level.
LevelPrinter struct {
Ln
F
@@ -83,10 +64,12 @@ type (
Chk
Err
}
// LevelSpec is the name, ID and Colorizer for a log level.
LevelSpec struct {
ID int
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
@@ -100,33 +83,98 @@ type (
)
var (
// sep is just a convenient shortcut for this very longwinded expression
sep = string(os.PathSeparator)
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
// 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 = []LevelSpec{
{Off, " ", color.Bit24(0, 0, 0, false).Sprint},
{Fatal, "FTL", color.Bit24(128, 0, 0, false).Sprint},
{Error, "ERR", color.Bit24(255, 0, 0, false).Sprint},
{Warn, "WRN", color.Bit24(0, 255, 0, false).Sprint},
{Info, "INF", color.Bit24(255, 255, 0, false).Sprint},
{Debug, "DBG", color.Bit24(0, 125, 255, false).Sprint},
{Trace, "TRC", color.Bit24(125, 0, 255, false).Sprint},
{Off, "", NoSprint},
{Fatal, "FTL", color.New(color.BgRed, color.FgHiWhite).Sprint},
{Error, "ERR", color.New(color.FgHiRed).Sprint},
{Warn, "WRN", color.New(color.FgHiYellow).Sprint},
{Info, "INF", color.New(color.FgHiGreen).Sprint},
{Debug, "DBG", color.New(color.FgHiBlue).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.
type Log struct {
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 {
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) {
for i := range a {
s += fmt.Sprint(a[i])
@@ -137,78 +185,124 @@ func JoinStrings(a ...any) (s string) {
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{
Ln: func(a ...interface{}) {
fmt.Fprintf(writer,
"%s %s %s %s\n",
UnixNanoAsFloat(),
if Level.Load() < l {
return
}
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
JoinStrings(a...),
GetLoc(2),
msgCol(GetLoc(skip)),
)
},
F: func(format string, a ...interface{}) {
fmt.Fprintf(writer,
"%s %s %s %s\n",
UnixNanoAsFloat(),
if Level.Load() < l {
return
}
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
fmt.Sprintf(format, a...),
GetLoc(2),
msgCol(GetLoc(skip)),
)
},
S: func(a ...interface{}) {
fmt.Fprintf(writer,
"%s %s %s %s\n",
UnixNanoAsFloat(),
if Level.Load() < l {
return
}
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
spew.Sdump(a...),
GetLoc(2),
msgCol(GetLoc(skip)),
)
},
C: func(closure func() string) {
fmt.Fprintf(writer,
"%s %s %s %s\n",
UnixNanoAsFloat(),
if Level.Load() < l {
return
}
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
closure(),
GetLoc(2),
msgCol(GetLoc(skip)),
)
},
Chk: func(e error) bool {
if Level.Load() < l {
return e != nil
}
if e != nil {
fmt.Fprintf(writer,
"%s %s %s %s\n",
UnixNanoAsFloat(),
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
e.Error(),
GetLoc(2),
msgCol(GetLoc(skip)),
)
return true
}
return false
},
Err: func(format string, a ...interface{}) error {
fmt.Fprintf(writer,
"%s %s %s %s\n",
UnixNanoAsFloat(),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name, " "),
fmt.Sprintf(format, a...),
GetLoc(2),
)
if Level.Load() >= l {
_, _ = fmt.Fprintf(
writer,
"%s%s %s %s\n",
msgCol(TimeStamper()),
LevelSpecs[l].Colorizer(LevelSpecs[l].Name),
fmt.Sprintf(format, a...),
msgCol(GetLoc(skip)),
)
}
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{
F: GetPrinter(Fatal, writer),
E: GetPrinter(Error, writer),
W: GetPrinter(Warn, writer),
I: GetPrinter(Info, writer),
D: GetPrinter(Debug, writer),
T: GetPrinter(Trace, writer),
T: GetPrinter(Trace, writer, skip),
D: GetPrinter(Debug, writer, skip),
I: GetPrinter(Info, writer, skip),
W: GetPrinter(Warn, writer, skip),
E: GetPrinter(Error, writer, skip),
F: GetPrinter(Fatal, writer, skip),
}
c = &Check{
F: l.F.Chk,
@@ -218,38 +312,35 @@ func New(writer io.Writer) (l *Log, c *Check) {
D: l.D.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
}
// SetLogLevel sets the log level via a string, which can be truncated down to
// one character, similar to nmcli's argument processor, as the first letter is
// unique. This could be used with a linter to make larger command sets.
func SetLogLevel(l int) {
currentLevel.Store(int32(l))
}
func GetLogLevel() (l int) {
return int(currentLevel.Load())
}
// UnixNanoAsFloat e
func UnixNanoAsFloat() (s string) {
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))
// TimeStamper generates the timestamp for logs.
func TimeStamper() (s string) {
ts := time.Now().Format("150405.000000")
ds := time.Now().Format("2006-01-02")
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:])
s += color.New(color.FgHiBlue).Sprint(ts[0:2])
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:])
s += " "
return
}
// GetLoc returns the code location of the caller.
func GetLoc(skip int) (output string) {
_, file, line, _ := runtime.Caller(skip)
output = color.Bit24(0, 128, 255, false).Sprint(
file, ":", line,
)
output = fmt.Sprintf("%s:%d", file, line)
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 (
"bytes"
"errors"
"os"
"fmt"
"strings"
"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) {
for i := 0; i < 100; i++ {
lol.SetLogLevel(lol.Trace)
log.T.Ln("testing log level", lol.LevelSpecs[lol.Trace].Name)
log.D.Ln("testing log level", lol.LevelSpecs[lol.Debug].Name)
log.I.Ln("testing log level", lol.LevelSpecs[lol.Info].Name)
log.W.Ln("testing log level", lol.LevelSpecs[lol.Warn].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"))
chk.I(errors.New("dummy error as info"))
chk.D(errors.New("dummy error as debug"))
chk.T(errors.New("dummy error as trace"))
log.I.Ln("log.I.Err",
log.I.Err("format string %d '%s'", 5, "testing") != nil)
log.I.Chk(errors.New("dummy information check"))
log.I.Chk(nil)
log.I.S("`backtick wrapped string`", t)
// Test that LevelNames matches the constants
expectedLevelNames := []string{
"off", "fatal", "error", "warn", "info", "debug", "trace",
}
for i, name := range expectedLevelNames {
if LevelNames[i] != name {
t.Errorf("LevelNames[%d] = %s, want %s", i, LevelNames[i], name)
}
}
}
func TestGetLogLevel(t *testing.T) {
tests := []struct {
level string
expected int
}{
{"off", Off},
{"fatal", Fatal},
{"error", Error},
{"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(),
)
}
}