Skip to content

Commit

Permalink
levellog: dirt-simple leveled text logger
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Koch <[email protected]>
  • Loading branch information
hugelgupf committed Feb 9, 2024
1 parent 2ba503b commit b3d14b9
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 0 deletions.
20 changes: 20 additions & 0 deletions llog/default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package llog_test

import (
"log/slog"

"github.com/u-root/uio/llog"
)

func ExampleDefault_withtime() {
l := llog.Default()
l.Infof("An INFO level string")
l.Debugf("A DEBUG level that does not appear")

l.Level = slog.LevelDebug
l.Debugf("A DEBUG level that appears")
}
27 changes: 27 additions & 0 deletions llog/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2024 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package llog_test

import (
"flag"
"log/slog"

"github.com/u-root/uio/llog"
)

func someFunc(v llog.Printf) {
v("logs at the given level %s", "foo")
}

func Example() {
l := llog.Default()
// If -v is set, l.Level becomes slog.LevelDebug.
l.RegisterDebugFlag(flag.CommandLine, "v")
flag.Parse()

someFunc(l.Debugf)
someFunc(l.AtLevelFunc(slog.LevelWarn))
someFunc(l.Infof)
}
168 changes: 168 additions & 0 deletions llog/levellog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright 2024 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package llog is a dirt-simple leveled text logger.
package llog

import (
"flag"
"fmt"
"log"
"log/slog"
"math"
"strconv"
"testing"
)

// Default is the stdlib default log sink.
func Default() *Logger {
return &Logger{Sink: SinkFor(log.Printf)}
}

// Test is a logger that prints every level to t.Logf.
func Test(tb testing.TB) *Logger {
tb.Helper()
return &Logger{Sink: SinkFor(tb.Logf), Level: math.MinInt32}
}

// Debug prints to log.Printf at the debug level.
func Debug() *Logger {
return &Logger{Sink: SinkFor(log.Printf), Level: slog.LevelDebug}
}

// Printf is a logger printf function.
type Printf func(format string, v ...any)

// Printfer is an interface implementing Printf.
type Printfer interface {
Printf(format string, v ...any)
}

// Sink is the output for Logger.
type Sink func(level slog.Level, format string, v ...any)

// SinkFor prepends the log with a log level and outputs to p.
func SinkFor(p Printf) Sink {
return func(level slog.Level, format string, args ...any) {
// Prepend log level.
format = "%s " + format
args = append([]any{level}, args...)
p(format, args...)
}
}

// Logger is a dirt-simple leveled logger.
//
// If the log level is >= Level, it logs to the given Sink.
//
// Logger or Sink may be nil in order to log nothing.
type Logger struct {
Sink Sink
Level slog.Level
}

// New creates a logger from p which prepends the log level to the output and
// uses l as the default log level.
//
// Logs with level >= l will be printed using p.
func New(l slog.Level, p Printf) *Logger {
return &Logger{
Sink: SinkFor(p),
Level: l,
}
}

// RegisterLevelFlag registers a flag that sets the given numeric level as the level.
func (l *Logger) RegisterLevelFlag(f *flag.FlagSet, flagName string) {
f.IntVar((*int)(&l.Level), flagName, int(l.Level), "Level to log at. Lower level emits more logs. -4 = DEBUG, 0 = INFO, 4 = WARN, 8 = ERROR")
}

// RegisterVerboseFlag registers a boolean flag that, if set, assigns verboseLevel as the level.
func (l *Logger) RegisterVerboseFlag(f *flag.FlagSet, flagName string, verboseLevel slog.Level) {
f.BoolFunc(flagName, fmt.Sprintf("If set, logs at %d level", verboseLevel), func(val string) error {
b, err := strconv.ParseBool(val)
if err != nil {
return err
}
if b {
l.Level = verboseLevel
}
return nil
})
}

// RegisterDebugFlag registers a boolean flag that, if set, assigns LevelDebug as the level.
func (l *Logger) RegisterDebugFlag(f *flag.FlagSet, flagName string) {
l.RegisterVerboseFlag(f, flagName, slog.LevelDebug)
}

// AtLevelFunc returns a Printf that can be passed around to log at the given level.
//
// AtLevelFunc never returns nil.
func (l *Logger) AtLevelFunc(level slog.Level) Printf {
if l == nil || l.Sink == nil {
return func(fmt string, args ...any) {}
}
return func(fmt string, args ...any) {
l.Logf(level, fmt, args...)
}
}

type printfer struct {
printf Printf
}

// Printf implements Printfer.
func (p printfer) Printf(format string, v ...any) {
p.printf(format, v...)
}

// AtLevel returns a Printfer that can be passed around to log at the given level.
//
// AtLevel never returns nil.
func (l *Logger) AtLevel(level slog.Level) Printfer {
return printfer{printf: l.AtLevelFunc(level)}
}

// Debugf is a printf function that logs at the Debug level.
func (l *Logger) Debugf(fmt string, args ...any) {
if l == nil {
return
}
l.Logf(slog.LevelDebug, fmt, args...)
}

// Infof is a printf function that logs at the Info level.
func (l *Logger) Infof(fmt string, args ...any) {
if l == nil {
return
}
l.Logf(slog.LevelInfo, fmt, args...)
}

// Warnf is a printf function that logs at the Warn level.
func (l *Logger) Warnf(fmt string, args ...any) {
if l == nil {
return
}
l.Logf(slog.LevelWarn, fmt, args...)
}

// Errorf is a printf function that logs at the Error level.
func (l *Logger) Errorf(fmt string, args ...any) {
if l == nil {
return
}
l.Logf(slog.LevelError, fmt, args...)
}

// Logf logs at the given level.
func (l *Logger) Logf(level slog.Level, fmt string, args ...any) {
if l == nil || l.Sink == nil {
return
}
if level >= l.Level {
l.Sink(level, fmt, args...)
}
}
Loading

0 comments on commit b3d14b9

Please sign in to comment.