update to what is current
This commit is contained in:
13
chk/chk.go
Normal file
13
chk/chk.go
Normal 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
12
errorf/errorf.go
Normal 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
10
go.mod
@@ -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
27
go.sum
@@ -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
337
log.go
@@ -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
12
log/log.go
Normal 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
|
||||||
|
}
|
||||||
314
log_test.go
314
log_test.go
@@ -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(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user