Skip to content

Commit

Permalink
Merge branch 'feat/examples'
Browse files Browse the repository at this point in the history
  • Loading branch information
lvlcn-t committed Feb 24, 2024
1 parent 5df65fe commit 961e038
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 70 deletions.
37 changes: 37 additions & 0 deletions examples/simple/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"os"

"github.com/lvlcn-t/loggerhead/logger"
)

func main() {
err := os.Setenv("LOG_FORMAT", "text")
if err != nil {
panic(err)
}

log := logger.NewLogger()

log.Debug("This is a debug message!")
log.Debugf("This is a %s message!", "debug")

log.Info("Hello, world!")
log.Infof("Hello, %s!", "world")

log.Warn("This is a warning!")
log.Warnf("This is a %s!", "warning")

log.Error("This is an error!")
log.Errorf("This is an %s!", "error")

func() {
defer func() {
if r := recover(); r != nil {
log.Fatal("This is a fatal error!")
}
}()
log.Panic("This is a panic!")
}()
}
116 changes: 65 additions & 51 deletions internal/logger/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,108 +5,122 @@ import (
"fmt"
"log/slog"
"os"
"strings"
)

// Level is the type of log levels.
type Level = slog.Level

const (
LevelTrace Level = slog.Level(-8)
LevelDebug Level = slog.LevelDebug
LevelInfo Level = slog.LevelInfo
LevelNotice Level = slog.Level(2)
LevelWarn Level = slog.LevelWarn
LevelError Level = slog.LevelError
LevelPanic Level = slog.Level(12)
LevelFatal Level = slog.Level(16)
"runtime"
"time"
)

// Debugf logs at LevelDebug.
// Arguments are handled in the manner of fmt.Printf.
func (l *logger) Debugf(msg string, args ...any) {
formattedMsg := fmt.Sprintf(msg, args...)
l.Debug(formattedMsg)
if !l.Enabled(context.Background(), LevelDebug) {
return
}
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelDebug, fmt.Sprintf(msg, args...), pc)
_ = l.Handler().Handle(context.Background(), r)
}

// Infof logs at LevelInfo.
// Arguments are handled in the manner of fmt.Printf.
func (l *logger) Infof(msg string, args ...any) {
formattedMsg := fmt.Sprintf(msg, args...)
l.Info(formattedMsg)
if !l.Enabled(context.Background(), LevelInfo) {
return
}
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelInfo, fmt.Sprintf(msg, args...), pc)
_ = l.Handler().Handle(context.Background(), r)
}

// Warnf logs at LevelWarn.
// Arguments are handled in the manner of fmt.Printf.
func (l *logger) Warnf(msg string, args ...any) {
formattedMsg := fmt.Sprintf(msg, args...)
l.Warn(formattedMsg)
if !l.Enabled(context.Background(), LevelWarn) {
return
}
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelWarn, fmt.Sprintf(msg, args...), pc)
_ = l.Handler().Handle(context.Background(), r)
}

// Errorf logs at LevelError.
// Arguments are handled in the manner of fmt.Printf.
func (l *logger) Errorf(msg string, args ...any) {
formattedMsg := fmt.Sprintf(msg, args...)
l.Error(formattedMsg)
if !l.Enabled(context.Background(), LevelError) {
return
}
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelError, fmt.Sprintf(msg, args...), pc)
_ = l.Handler().Handle(context.Background(), r)
}

// Panic logs at [LevelPanic] and then panics.
func (l *logger) Panic(msg string, args ...any) {
l.Log(context.Background(), LevelPanic, msg, args...)
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelPanic, msg, pc)
r.Add(args...)
_ = l.Handler().Handle(context.Background(), r)
panic(msg)
}

// Panicf logs at LevelPanic and then panics.
// Arguments are handled in the manner of fmt.Printf.
func (l *logger) Panicf(msg string, args ...any) {
formattedMsg := fmt.Sprintf(msg, args...)
l.Log(context.Background(), LevelPanic, formattedMsg)
panic(formattedMsg)
fmsg := fmt.Sprintf(msg, args...)
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelPanic, fmsg, pc)
_ = l.Handler().Handle(context.Background(), r)
panic(fmsg)
}

// PanicContext logs at [LevelPanic] with the given context and then panics.
func (l *logger) PanicContext(ctx context.Context, msg string, args ...any) {
l.Log(ctx, LevelPanic, msg, args...)
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelPanic, msg, pc)
r.Add(args...)
_ = l.Handler().Handle(ctx, r)
panic(msg)
}

// Fatal logs at [LevelFatal] and then calls os.Exit(1).
func (l *logger) Fatal(msg string, args ...any) {
l.Log(context.Background(), LevelFatal, msg, args...)
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelFatal, msg, pc)
r.Add(args...)
_ = l.Handler().Handle(context.Background(), r)
os.Exit(1)
}

// Fatalf logs at LevelFatal and then calls os.Exit(1).
// Arguments are handled in the manner of fmt.Printf.
func (l *logger) Fatalf(msg string, args ...any) {
formattedMsg := fmt.Sprintf(msg, args...)
l.Log(context.Background(), LevelFatal, formattedMsg)
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelPanic, fmt.Sprintf(msg, args...), pc)
_ = l.Handler().Handle(context.Background(), r)
os.Exit(1)
}

// FatalContext logs at [LevelFatal] with the given context and then calls os.Exit(1).
func (l *logger) FatalContext(ctx context.Context, msg string, args ...any) {
l.Log(ctx, LevelFatal, msg, args...)
pc := getCaller(2)
r := slog.NewRecord(time.Now(), LevelFatal, msg, pc)
r.Add(args...)
_ = l.Handler().Handle(ctx, r)
os.Exit(1)
}

// getLevel takes a level string and maps it to the corresponding Level
// Returns the level if no mapped level is found it returns info level
func getLevel(level string) int {
switch strings.ToUpper(level) {
case "TRACE":
return int(LevelTrace)
case "DEBUG":
return int(LevelDebug)
case "INFO":
return int(LevelInfo)
case "NOTICE":
return int(LevelNotice)
case "WARN", "WARNING":
return int(LevelWarn)
case "ERROR":
return int(LevelError)
default:
return int(LevelInfo)
}
// getCaller returns the program counter of the caller at a given depth.
// The depth is the number of stack frames to ascend, with 0 identifying the
// getCaller function itself, 1 identifying the caller that invoked getCaller,
// and so on.
//
// Example:
//
// pc := getCaller(1) // Returns the program counter of the caller of the function that invoked getCaller.
// pc := getCaller(2) // Returns the program counter of the caller of the function that invoked the function that invoked getCaller.
func getCaller(depth uint8) uintptr { //nolint: unparam
d := int(depth) + 1

var pcs [1]uintptr
runtime.Callers(d, pcs[:])
return pcs[0]
}
60 changes: 60 additions & 0 deletions internal/logger/level.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package logger

import (
"log/slog"
"strings"
)

// Level is the type of log levels.
type Level = slog.Level

const (
LevelTrace Level = slog.Level(-8)
LevelDebug Level = slog.LevelDebug
LevelInfo Level = slog.LevelInfo
LevelNotice Level = slog.Level(2)
LevelWarn Level = slog.LevelWarn
LevelError Level = slog.LevelError
LevelPanic Level = slog.Level(12)
LevelFatal Level = slog.Level(16)
)

var LevelNames = map[Level]string{
LevelTrace: "TRACE",
LevelDebug: "DEBUG",
LevelInfo: "INFO",
LevelNotice: "NOTICE",
LevelWarn: "WARN",
LevelError: "ERROR",
LevelPanic: "PANIC",
LevelFatal: "FATAL",
}

// getLevel returns the integer value of the given level string.
// If the level is not recognized, it returns LevelInfo.
func getLevel(level string) int {
switch strings.ToUpper(level) {
case "TRACE":
return int(LevelTrace)
case "DEBUG":
return int(LevelDebug)
case "INFO":
return int(LevelInfo)
case "NOTICE":
return int(LevelNotice)
case "WARN", "WARNING":
return int(LevelWarn)
case "ERROR":
return int(LevelError)
default:
return int(LevelInfo)
}
}

// getLevelString returns the string value of the given level.
func getLevelString(level Level) string {
if s, ok := LevelNames[level]; ok {
return s
}
return "UNKNOWN"
}
File renamed without changes.
36 changes: 31 additions & 5 deletions internal/logger/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,32 @@ import (
// It extends the Core interface with additional logging methods.
type Logger interface {
Core
// Debugf logs at LevelDebug.
// Arguments are handled in the manner of fmt.Printf.
Debugf(msg string, args ...any)
// Infof logs at LevelInfo.
// Arguments are handled in the manner of fmt.Printf.
Infof(msg string, args ...any)
// Warnf logs at LevelWarn.
// Arguments are handled in the manner of fmt.Printf.
Warnf(msg string, args ...any)
// Errorf logs at LevelError.
// Arguments are handled in the manner of fmt.Printf.
Errorf(msg string, args ...any)
Fatal(msg string, args ...any)
Fatalf(msg string, args ...any)
FatalContext(ctx context.Context, msg string, args ...any)
// Panic logs at LevelPanic and then panics with the given message.
Panic(msg string, args ...any)
// Panicf logs at LevelPanic and then panics.
// Arguments are handled in the manner of fmt.Printf.
Panicf(msg string, args ...any)
// PanicContext logs at LevelPanic with the given context and then panics with the given message.
PanicContext(ctx context.Context, msg string, args ...any)
// Fatal logs at LevelFatal and then calls os.Exit(1).
Fatal(msg string, args ...any)
// Fatalf logs at LevelFatal and then calls os.Exit(1).
// Arguments are handled in the manner of fmt.Printf.
Fatalf(msg string, args ...any)
// FatalContext logs at LevelFatal with the given context and then calls os.Exit(1).
FatalContext(ctx context.Context, msg string, args ...any)
}

// logger implements the Logger interface.
Expand Down Expand Up @@ -118,7 +134,17 @@ func newBaseHandler() slog.Handler {
}

return slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.Level(l),
AddSource: true,
Level: Level(l),
ReplaceAttr: replaceAttr,
})
}

// replaceAttr is the replacement function for slog.HandlerOptions.
func replaceAttr(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.LevelKey {
lev := a.Value.Any().(Level)
a.Value = slog.StringValue(getLevelString(lev))
}
return a
}
29 changes: 15 additions & 14 deletions internal/logger/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,32 +127,33 @@ func TestNewContextWithLogger(t *testing.T) {

func TestFromContext(t *testing.T) {
tests := []struct {
name string
ctx context.Context
expect Logger
name string
ctx context.Context
want Logger
}{
{
name: "Context with logger",
ctx: IntoContext(context.Background(), NewLogger(slog.NewJSONHandler(os.Stdout, nil))),
expect: NewLogger(slog.NewJSONHandler(os.Stdout, nil)),
name: "Context with logger",
ctx: IntoContext(context.Background(), NewLogger(slog.NewJSONHandler(os.Stdout, nil))),
want: NewLogger(slog.NewJSONHandler(os.Stdout, nil)),
},
{
name: "Context without logger",
ctx: context.Background(),
expect: NewLogger(),
name: "Context without logger",
ctx: context.Background(),
want: NewLogger(),
},
{
name: "Nil context",
ctx: nil,
expect: NewLogger(),
name: "Nil context",
ctx: nil,
want: NewLogger(),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FromContext(tt.ctx)
if !reflect.DeepEqual(got, tt.expect) {
t.Errorf("FromContext() = %v, want %v", got, tt.expect)
_, ok := got.(*logger)
if !ok {
t.Errorf("FromContext() = %T, want %T", got, tt.want)
}
})
}
Expand Down

0 comments on commit 961e038

Please sign in to comment.