Skip to content

Commit

Permalink
1st pass slog support
Browse files Browse the repository at this point in the history
  • Loading branch information
deankarn committed Aug 11, 2023
1 parent d2677a2 commit fd8fc12
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 55 deletions.
56 changes: 1 addition & 55 deletions log.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package log

import (
"bufio"
"context"
"io"
stdlog "log"
"os"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -48,9 +44,7 @@ var (
}{
name: "log",
}
rw sync.RWMutex
stdLogWriter *io.PipeWriter
redirectComplete chan struct{}
rw = new(sync.RWMutex)
)

// Field is a single Field key and value
Expand All @@ -59,54 +53,6 @@ type Field struct {
Value interface{} `json:"value"`
}

// RedirectGoStdLog is used to redirect Go's internal std log output to this logger.
func RedirectGoStdLog(redirect bool) {
if (redirect && stdLogWriter != nil) || (!redirect && stdLogWriter == nil) {
// already redirected or already not redirected
return
}
if !redirect {
stdlog.SetOutput(os.Stderr)
// will stop scanner reading PipeReader
_ = stdLogWriter.Close()
stdLogWriter = nil
<-redirectComplete
return
}

ready := make(chan struct{})
redirectComplete = make(chan struct{})

// last option is to redirect
go func() {
var r *io.PipeReader
r, stdLogWriter = io.Pipe()
defer func() {
_ = r.Close()
}()

stdlog.SetOutput(stdLogWriter)
defer func() {
close(redirectComplete)
redirectComplete = nil
}()

scanner := bufio.NewScanner(r)
close(ready)
for scanner.Scan() {
txt := scanner.Text()
if strings.Contains(txt, "error") {
WithField("stdlog", true).Error(txt)
} else if strings.Contains(txt, "warning") {
WithField("stdlog", true).Warn(txt)
} else {
WithField("stdlog", true).Notice(txt)
}
}
}()
<-ready
}

// SetExitFunc sets the provided function as the exit function used in Fatal(),
// Fatalf(), Panic() and Panicf(). This is primarily used when wrapping this library,
// you can set this to enable testing (with coverage) of your Fatal() and Fatalf()
Expand Down
59 changes: 59 additions & 0 deletions log_old.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//go:build !go1.21
// +build !go1.21

package log

var (
stdLogWriter *io.PipeWriter

Check failure on line 7 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `io` (typecheck)

Check failure on line 7 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: io
redirectComplete chan struct{}
)

// RedirectGoStdLog is used to redirect Go's internal std log output to this logger AND registers a handler for slog
// that redirects slog output to this logger.
func RedirectGoStdLog(redirect bool) {
slog.SetDefault(slog.New(&slogHandler{e: newEntry()}))

Check failure on line 14 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `slog` (typecheck)

Check failure on line 14 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: slog

Check failure on line 14 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: slogHandler
if (redirect && stdLogWriter != nil) || (!redirect && stdLogWriter == nil) {
// already redirected or already not redirected
return
}
if !redirect {
stdlog.SetOutput(os.Stderr)

Check failure on line 20 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `stdlog` (typecheck)

Check failure on line 20 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: stdlog

Check failure on line 20 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: os
// will stop scanner reading PipeReader
_ = stdLogWriter.Close()
stdLogWriter = nil
<-redirectComplete
return
}

ready := make(chan struct{})
redirectComplete = make(chan struct{})

// last option is to redirect
go func() {
var r *io.PipeReader

Check failure on line 33 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `io` (typecheck)

Check failure on line 33 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: io
r, stdLogWriter = io.Pipe()

Check failure on line 34 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `io` (typecheck)

Check failure on line 34 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: io
defer func() {
_ = r.Close()
}()

stdlog.SetOutput(stdLogWriter)

Check failure on line 39 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `stdlog` (typecheck)

Check failure on line 39 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: stdlog
defer func() {
close(redirectComplete)
redirectComplete = nil
}()

scanner := bufio.NewScanner(r)

Check failure on line 45 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `bufio` (typecheck)

Check failure on line 45 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: bufio
close(ready)
for scanner.Scan() {
txt := scanner.Text()
if strings.Contains(txt, "error") {

Check failure on line 49 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `strings` (typecheck)

Check failure on line 49 in log_old.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

undefined: strings
WithField("stdlog", true).Error(txt)
} else if strings.Contains(txt, "warning") {

Check failure on line 51 in log_old.go

View workflow job for this annotation

GitHub Actions / lint

undeclared name: `strings` (typecheck)
WithField("stdlog", true).Warn(txt)
} else {
WithField("stdlog", true).Notice(txt)
}
}
}()
<-ready
}
144 changes: 144 additions & 0 deletions slog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//go:build go1.21
// +build go1.21

package log

import (
"context"
runtimeext "github.com/go-playground/pkg/v5/runtime"
"log/slog"
"runtime"
)

var _ slog.Handler = (*slogHandler)(nil)

type slogHandler struct {
e Entry
group string
}

// Enabled returns if the current logging level is enabled. In the case of this log package in this Level has a
// handler registered.
func (s *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
rw.RLock()
_, enabled := logHandlers[convertSlogLevel(level)]
rw.RUnlock()
return enabled
}

func (s *slogHandler) Handle(ctx context.Context, record slog.Record) error {

var fields []Field
if record.NumAttrs() > 0 {
fields = make([]Field, 0, record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
if attr.Value.Kind() == slog.KindGroup {
g := attr.Key
if s.group != "" {
g = s.group + "." + g
}
fields = append(fields, s.convertAttrsToFields(g, attr.Value.Group())...)
return true
}
fields = append(fields, s.convertAttrToField(s.group, attr))
return true
})
}
if record.PC != 0 {
fs := runtime.CallersFrames([]uintptr{record.PC})
f, _ := fs.Next()
sourceBuff := BytePool().Get()
sourceBuff.B = extractSource(sourceBuff.B, runtimeext.Frame{Frame: f})
fields = append(fields, Field{Key: "source", Value: string(sourceBuff.B[:len(sourceBuff.B)-1])})
BytePool().Put(sourceBuff)
}
e := s.e.clone(fields...)
e.Message = record.Message
e.Level = convertSlogLevel(record.Level)
HandleEntry(e)
return nil
}

func (s *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &slogHandler{
e: s.e.clone(s.convertAttrsToFields(s.group, attrs)...),
group: s.group,
}
}

func (s *slogHandler) convertAttrsToFields(group string, attrs []slog.Attr) []Field {
fields := make([]Field, 0, len(attrs))

for _, attr := range attrs {
switch attr.Value.Kind() {
case slog.KindGroup:
g := attr.Key
if group != "" {
g = group + "." + g
}
fields = append(fields, s.convertAttrsToFields(g, attr.Value.Group())...)
continue
default:
fields = append(fields, s.convertAttrToField(group, attr))
}
}
return fields
}

func (s *slogHandler) convertAttrToField(group string, attr slog.Attr) Field {
var value any

switch attr.Value.Kind() {
case slog.KindLogValuer:
value = attr.Value.LogValuer().LogValue()
default:
value = attr.Value.Any()
}
if group == "" {
return Field{Key: attr.Key, Value: value}
} else {
return Field{Key: group + "." + attr.Key, Value: value}
}
}

func (s *slogHandler) WithGroup(name string) slog.Handler {
return &slogHandler{
e: s.e.clone(),
group: name,
}
}

func convertSlogLevel(level slog.Level) Level {
switch level {
case slog.LevelDebug:
return DebugLevel
case slog.LevelInfo:
return InfoLevel
case slog.LevelWarn:
return WarnLevel
case slog.LevelError:
return ErrorLevel
default:
switch {
case level > slog.LevelInfo && level < slog.LevelWarn:
return NoticeLevel
case level > slog.LevelError && level < slog.LevelError+4:
return PanicLevel
case level > slog.LevelError+4 && level < slog.LevelError+8:
return AlertLevel
case level > slog.LevelError+8 && level < slog.LevelError+16:
return FatalLevel
}
return ErrorLevel
}
}

// RedirectGoStdLog is used to redirect Go's internal std log output to this logger AND registers a handler for slog
// that redirects slog output to this logger.
func RedirectGoStdLog(redirect bool) {
if redirect {
slog.SetDefault(slog.New(&slogHandler{e: newEntry()}))
} else {
slog.SetDefault(slog.Default())
}
}

0 comments on commit fd8fc12

Please sign in to comment.