Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slog writer #46

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions attrs/attrs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package attrs

import (
"fmt"
"time"
)

type AttrValue[T any] interface {
Key() string
Value() T
}

type attr[T any] struct {
key string
value T
}

func (s attr[T]) Key() string {
return s.key
}

func (s attr[T]) Value() T {
return s.value
}

func String(k, v string) AttrValue[string] {
return attr[string]{key: k, value: v}
}

func Int(k string, v int) AttrValue[int] {
return attr[int]{key: k, value: v}
}

func Int64(k string, v int64) AttrValue[int64] {
return attr[int64]{key: k, value: v}
}

func Uint64(k string, v uint64) AttrValue[uint64] {
return attr[uint64]{key: k, value: v}
}

func Float64(k string, v float64) AttrValue[float64] {
return attr[float64]{key: k, value: v}
}

func Bool(k string, v bool) AttrValue[bool] {
return attr[bool]{key: k, value: v}
}

func Time(k string, v time.Time) AttrValue[time.Time] {
return attr[time.Time]{key: k, value: v}
}

func Duration(k string, v time.Duration) AttrValue[time.Duration] {
return attr[time.Duration]{key: k, value: v}
}

func Any(k string, v any) AttrValue[any] {
return attr[any]{key: k, value: v}
}

func Valid(attrs []any) error {
for _, attr := range attrs {
switch a := attr.(type) {
case AttrValue[string]:
case AttrValue[int]:
case AttrValue[int64]:
case AttrValue[uint64]:
case AttrValue[float64]:
case AttrValue[bool]:
case AttrValue[time.Time]:
case AttrValue[time.Duration]:
case AttrValue[any]:
default:
return fmt.Errorf("invalid attribute type %T", a)
}
}
return nil
}
4 changes: 4 additions & 0 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ type Entry struct {
Message string
// Labels is the label associated with the log message.
Labels []string
// PC is the program counter of the log call.
PC uintptr
// Attrs is the list of attributes associated with the log message.
Attrs []any
}
3 changes: 2 additions & 1 deletion example/first.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package main

import (
"github.com/juju/loggo"
"github.com/juju/loggo/attrs"
)

var first = loggo.GetLogger("first")

func FirstCritical(message string) {
first.Criticalf(message)
first.Critical(message, attrs.String("baz", "boo"))
}

func FirstError(message string) {
Expand Down
31 changes: 25 additions & 6 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,48 @@ package main
import (
"fmt"
"log"
"log/slog"
"os"

"github.com/juju/loggo"
"github.com/juju/loggo/attrs"
loggoslog "github.com/juju/loggo/slog"
)

var rootLogger = loggo.GetLogger("")

func main() {
args := os.Args
if len(args) > 1 {
if len(args) <= 1 {
fmt.Println("Add a parameter to configure the logging:")
fmt.Println(`E.g. "<root>=INFO;first=TRACE" or "<root>=INFO;first=TRACE" "slog"`)
}
num := len(args)
if num > 1 {
if err := loggo.ConfigureLoggers(args[1]); err != nil {
log.Fatal(err)
}
} else {
fmt.Println("Add a parameter to configure the logging:")
fmt.Println("E.g. \"<root>=INFO;first=TRACE\"")
}

fmt.Println("\nCurrent logging levels:")
fmt.Println(loggo.LoggerInfo())

if num > 2 {
if args[2] == "slog" {
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: loggoslog.DefaultLevel(loggo.DefaultContext().Config()),
})
loggo.ReplaceDefaultWriter(loggoslog.NewSlogWriter(handler))

fmt.Println("Using log/slog writer:")
} else {
log.Fatalf("unknown logging type %q", args[2])
}
}

fmt.Println("")

rootLogger.Infof("Start of test.")
rootLogger.Info("Start of test.", attrs.String("foo", "bar"))

FirstCritical("first critical")
FirstError("first error")
Expand All @@ -39,5 +59,4 @@ func main() {
SecondInfo("second info")
SecondDebug("second debug")
SecondTrace("second trace")

}
47 changes: 45 additions & 2 deletions formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,60 @@ import (
"os"
"path/filepath"
"time"

"github.com/juju/loggo/attrs"
)

// DefaultFormatter returns the parameters separated by spaces except for
// filename and line which are separated by a colon. The timestamp is shown
// to second resolution in UTC. For example:
// 2016-07-02 15:04:05
//
// 2016-07-02 15:04:05
func DefaultFormatter(entry Entry) string {
ts := entry.Timestamp.In(time.UTC).Format("2006-01-02 15:04:05")
// Just get the basename from the filename
filename := filepath.Base(entry.Filename)
return fmt.Sprintf("%s %s %s %s:%d %s", ts, entry.Level, entry.Module, filename, entry.Line, entry.Message)

var (
format string
values []any
)
for _, attr := range entry.Attrs {
switch a := attr.(type) {
case attrs.AttrValue[string]:
format += " %s=%s"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[int]:
format += " %s=%d"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[int64]:
format += " %s=%d"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[uint64]:
format += " %s=%d"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[float64]:
format += " %s=%f"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[bool]:
format += " %s=%t"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[time.Time]:
format += " %s=%v"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[time.Duration]:
format += " %s=%v"
values = append(values, a.Key(), a.Value())
case attrs.AttrValue[any]:
format += " %s=%v"
values = append(values, a.Key(), a.Value())
}
}

args := []any{ts, entry.Level, entry.Module, filename, entry.Line, entry.Message}
args = append(args, values...)

return fmt.Sprintf("%s %s %s %s:%d %s"+format, args...)
}

// TimeFormat is the time format used for the default writer.
Expand Down
12 changes: 8 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
module github.com/juju/loggo

go 1.14
go 1.21

require (
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6
github.com/mattn/go-colorable v0.0.6
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2
)

require (
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6 // indirect
github.com/mattn/go-colorable v0.0.6 // indirect
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c // indirect
golang.org/x/sys v0.11.0 // indirect
)
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6 h1:yjdywwaxd8vTEXuA4EdgUBkiCQEQG7YAY3k9S1PaZKg=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mattn/go-colorable v0.0.6 h1:jGqlOoCjqVR4hfTO9H1qrR2xi0xZNYmX2T1xlw7P79c=
github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 h1:+j1SppRob9bAgoYmsdW9NNBdKZfgYuWpqnYHv78Qt8w=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
72 changes: 71 additions & 1 deletion logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"runtime"
"time"

"github.com/juju/loggo/attrs"
)

// A Logger represents a logging module. It has an associated logging
Expand Down Expand Up @@ -127,7 +129,7 @@ func (logger Logger) LogCallf(calldepth int, level Level, message string, args .
now := time.Now() // get this early.
// Param to Caller is the call depth. Since this method is called from
// the Logger methods, we want the place that those were called from.
_, file, line, ok := runtime.Caller(calldepth + 1)
pc, file, line, ok := runtime.Caller(calldepth + 1)
if !ok {
file = "???"
line = 0
Expand All @@ -154,6 +156,7 @@ func (logger Logger) LogCallf(calldepth int, level Level, message string, args .
Timestamp: now,
Message: formattedMessage,
Labels: module.labels,
PC: pc,
})
}

Expand Down Expand Up @@ -222,3 +225,70 @@ func (logger Logger) IsDebugEnabled() bool {
func (logger Logger) IsTraceEnabled() bool {
return logger.IsLevelEnabled(TRACE)
}

// Trace logs the message at trace level.
func (logger Logger) Trace(message string, attrs ...any) error {
return logger.LogCall(1, TRACE, message, attrs...)
}

// Debug logs the message at debug level.
func (logger Logger) Debug(message string, attrs ...any) error {
return logger.LogCall(1, DEBUG, message, attrs...)
}

// Info logs the message at info level.
func (logger Logger) Info(message string, attrs ...any) error {
return logger.LogCall(1, INFO, message, attrs...)
}

// Error logs the message at error level.
func (logger Logger) Error(message string, attrs ...any) error {
return logger.LogCall(1, ERROR, message, attrs...)
}

// Warning logs the message at warning level.
func (logger Logger) Warning(message string, attrs ...any) error {
return logger.LogCall(1, WARNING, message, attrs...)
}

// Critical logs the message at critical level.
func (logger Logger) Critical(message string, attrs ...any) error {
return logger.LogCall(1, CRITICAL, message, attrs...)
}

func (logger Logger) LogCall(calldepth int, level Level, message string, attributes ...any) error {
if err := attrs.Valid(attributes); err != nil {
return err
}

module := logger.getModule()
if !module.willWrite(level) {
return nil
}
// Gather time, and filename, line number.
now := time.Now() // get this early.
// Param to Caller is the call depth. Since this method is called from
// the Logger methods, we want the place that those were called from.
pc, file, line, ok := runtime.Caller(calldepth + 1)
if !ok {
file = "???"
line = 0
}
// Trim newline off format string, following usual
// Go logging conventions.
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[0 : len(message)-1]
}

module.write(Entry{
Level: level,
Filename: file,
Line: line,
Timestamp: now,
Message: message,
Labels: module.labels,
PC: pc,
Attrs: attributes,
})
return nil
}
25 changes: 25 additions & 0 deletions loggocolor/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"fmt"
"io"
"path/filepath"
"time"

"github.com/juju/ansiterm"
"github.com/juju/loggo"
"github.com/juju/loggo/attrs"
)

var (
Expand Down Expand Up @@ -55,4 +57,27 @@ func (w *colorWriter) Write(entry loggo.Entry) {
fmt.Fprintf(w.writer, " %s ", entry.Module)
LocationColor.Fprintf(w.writer, "%s:%d ", filename, entry.Line)
fmt.Fprintln(w.writer, entry.Message)

for _, attr := range entry.Attrs {
switch a := attr.(type) {
case attrs.AttrValue[string]:
fmt.Fprintf(w.writer, " %s=%s\n", a.Key(), a.Value())
case attrs.AttrValue[int]:
fmt.Fprintf(w.writer, " %s=%d\n", a.Key(), a.Value())
case attrs.AttrValue[int64]:
fmt.Fprintf(w.writer, " %s=%d\n", a.Key(), a.Value())
case attrs.AttrValue[uint64]:
fmt.Fprintf(w.writer, " %s=%d\n", a.Key(), a.Value())
case attrs.AttrValue[float64]:
fmt.Fprintf(w.writer, " %s=%f\n", a.Key(), a.Value())
case attrs.AttrValue[bool]:
fmt.Fprintf(w.writer, " %s=%t\n", a.Key(), a.Value())
case attrs.AttrValue[time.Time]:
fmt.Fprintf(w.writer, " %s=%v\n", a.Key(), a.Value())
case attrs.AttrValue[time.Duration]:
fmt.Fprintf(w.writer, " %s=%v\n", a.Key(), a.Value())
case attrs.AttrValue[any]:
fmt.Fprintf(w.writer, " %s=%v\n", a.Key(), a.Value())
}
}
}
Loading