-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
703 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
name: Build | ||
|
||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
build: | ||
name: Build | ||
runs-on: ubuntu-22.04 | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
with: | ||
ref: ${{ github.event.pull_request.head.sha }} | ||
|
||
- name: Secret Scanning | ||
uses: trufflesecurity/trufflehog@main | ||
with: | ||
extra_args: --only-verified | ||
|
||
- name: Setup Go 1.23 | ||
uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5 | ||
with: | ||
go-version-file: "go.mod" | ||
|
||
- name: Run golangci-lint | ||
if: ${{ github.event_name == 'pull_request' && !contains(env.head_commit_message, '#skip-lint') }} | ||
uses: golangci/[email protected] | ||
with: | ||
args: -v --timeout=5m | ||
version: v1.60.3 | ||
|
||
- name: Test | ||
if: ${{ github.event_name == 'pull_request' && !contains(env.head_commit_message, '#skip-test') }} | ||
run: go test -race -short ./... | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
linters: | ||
disable: | ||
- wrapcheck | ||
- err113 | ||
- contextcheck | ||
- exhaustive | ||
- protogetter | ||
presets: | ||
- bugs | ||
- error | ||
- unused | ||
|
||
run: | ||
tests: false | ||
|
||
issues: | ||
exclude-dirs: | ||
- .github | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Slog based logging for Go | ||
|
||
## Install | ||
|
||
``` | ||
go get github.com/castai/logging | ||
``` | ||
|
||
## Example | ||
|
||
See logging_test.go example test. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package logging | ||
|
||
import ( | ||
"context" | ||
"log/slog" | ||
) | ||
|
||
type ExportHandlerConfig struct { | ||
MinLevel slog.Level // Only export logs for this min log level. | ||
BufferSize int // Logs channel size. | ||
} | ||
|
||
func NewExportHandler(cfg ExportHandlerConfig) *ExportHandler { | ||
if cfg.BufferSize == 0 { | ||
cfg.BufferSize = 1000 | ||
} | ||
|
||
handler := &ExportHandler{ | ||
cfg: cfg, | ||
ch: make(chan slog.Record, cfg.BufferSize), | ||
} | ||
return handler | ||
} | ||
|
||
// ExportHandler exports logs to separate channel available via Records() | ||
type ExportHandler struct { | ||
next slog.Handler | ||
cfg ExportHandlerConfig | ||
|
||
ch chan slog.Record | ||
} | ||
|
||
func (h *ExportHandler) Register(next slog.Handler) slog.Handler { | ||
h.next = next | ||
return h | ||
} | ||
|
||
func (h *ExportHandler) Records() <-chan slog.Record { | ||
return h.ch | ||
} | ||
|
||
func (h *ExportHandler) Enabled(ctx context.Context, level slog.Level) bool { | ||
if h.next == nil { | ||
return true | ||
} | ||
return h.next.Enabled(ctx, level) | ||
} | ||
|
||
func (h *ExportHandler) Handle(ctx context.Context, record slog.Record) error { | ||
if record.Level >= h.cfg.MinLevel { | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
case h.ch <- record: | ||
default: | ||
} | ||
} | ||
if h.next == nil { | ||
return nil | ||
} | ||
return h.next.Handle(ctx, record) | ||
} | ||
|
||
func (h *ExportHandler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
clone := &ExportHandler{ | ||
cfg: h.cfg, | ||
ch: h.ch, | ||
} | ||
if h.next != nil { | ||
clone.next = h.next.WithAttrs(attrs) | ||
} | ||
return clone | ||
} | ||
|
||
func (h *ExportHandler) WithGroup(name string) slog.Handler { | ||
clone := &ExportHandler{ | ||
cfg: h.cfg, | ||
ch: h.ch, | ||
} | ||
if h.next != nil { | ||
clone.next = h.next.WithGroup(name) | ||
} | ||
return clone | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package logging_test | ||
|
||
import ( | ||
"log/slog" | ||
"testing" | ||
|
||
"github.com/castai/logging" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestExportHandler(t *testing.T) { | ||
r := require.New(t) | ||
|
||
exportHandler := logging.NewExportHandler(logging.ExportHandlerConfig{ | ||
MinLevel: slog.LevelWarn, | ||
BufferSize: 2, | ||
}) | ||
log := logging.New(exportHandler) | ||
|
||
log.Debug("msg1") | ||
log.Info("msg2") | ||
log.Warn("msg3") | ||
log.WithField("k", "v").Error("msg4") | ||
log.WithGroup("g").Error("msg5") | ||
|
||
// Only warn and error should be inside the export channel. | ||
msg1 := <-exportHandler.Records() | ||
r.Equal("msg3", msg1.Message) | ||
msg2 := <-exportHandler.Records() | ||
r.Equal("msg4", msg2.Message) | ||
|
||
// Ensure logs are not blocked. | ||
log.Debug("msg5") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module github.com/castai/logging | ||
|
||
go 1.23.2 | ||
|
||
require ( | ||
github.com/stretchr/testify v1.9.0 | ||
golang.org/x/time v0.6.0 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | ||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= | ||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= | ||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= | ||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package logging | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/slog" | ||
"os" | ||
"runtime" | ||
"time" | ||
) | ||
|
||
type HandlerFunc func(next slog.Handler) slog.Handler | ||
|
||
func (h HandlerFunc) Register(next slog.Handler) slog.Handler { | ||
return h(next) | ||
} | ||
|
||
// Handler allows to chain multiple handlers. | ||
// Order of execution is reverse to order of registration meaning first handler is executed last. | ||
type Handler interface { | ||
Register(next slog.Handler) slog.Handler | ||
} | ||
|
||
func MustParseLevel(lvlStr string) slog.Level { | ||
var lvl slog.Level | ||
err := lvl.UnmarshalText([]byte(lvlStr)) | ||
if err != nil { | ||
panic("parsing log level from level string " + lvlStr) | ||
} | ||
return lvl | ||
} | ||
|
||
func New(handlers ...Handler) *Logger { | ||
if len(handlers) == 0 { | ||
handlers = []Handler{ | ||
NewTextHandler(TextHandlerConfig{ | ||
Level: slog.LevelInfo, | ||
Output: os.Stdout, | ||
AddSource: false, | ||
}), | ||
} | ||
} | ||
|
||
// Chain handlers. Execution is in reverse order. | ||
var slogHandler slog.Handler | ||
for _, handler := range handlers { | ||
slogHandler = handler.Register(slogHandler) | ||
} | ||
|
||
log := slog.New(slogHandler) | ||
return &Logger{Log: log} | ||
} | ||
|
||
// Logger is a small wrapper around slog with some extra methods | ||
// for easier migration from logrus. | ||
type Logger struct { | ||
Log *slog.Logger | ||
} | ||
|
||
func (l *Logger) Error(msg string) { | ||
l.doLog(slog.LevelError, msg) //nolint:govet | ||
} | ||
|
||
func (l *Logger) Errorf(format string, a ...any) { | ||
l.doLog(slog.LevelError, format, a...) | ||
} | ||
|
||
func (l *Logger) Infof(format string, a ...any) { | ||
l.doLog(slog.LevelInfo, format, a...) | ||
} | ||
|
||
func (l *Logger) Info(msg string) { | ||
l.doLog(slog.LevelInfo, msg) //nolint:govet | ||
} | ||
|
||
func (l *Logger) Debug(msg string) { | ||
l.doLog(slog.LevelDebug, msg) //nolint:govet | ||
} | ||
|
||
func (l *Logger) Debugf(format string, a ...any) { | ||
l.doLog(slog.LevelDebug, format, a...) | ||
} | ||
|
||
func (l *Logger) Warn(msg string) { | ||
l.doLog(slog.LevelWarn, msg) //nolint:govet | ||
} | ||
|
||
func (l *Logger) Warnf(format string, a ...any) { | ||
l.doLog(slog.LevelWarn, format, a...) | ||
} | ||
|
||
func (l *Logger) Fatal(msg string) { | ||
l.doLog(slog.LevelError, msg) //nolint:govet | ||
os.Exit(1) | ||
} | ||
|
||
func (l *Logger) IsEnabled(lvl slog.Level) bool { | ||
ctx := context.Background() | ||
return l.Log.Handler().Enabled(ctx, lvl) | ||
} | ||
|
||
func (l *Logger) doLog(lvl slog.Level, msg string, args ...any) { | ||
ctx := context.Background() | ||
if !l.Log.Handler().Enabled(ctx, lvl) { | ||
return | ||
} | ||
var pcs [1]uintptr | ||
runtime.Callers(3, pcs[:]) | ||
if len(args) > 0 { | ||
r := slog.NewRecord(time.Now(), lvl, fmt.Sprintf(msg, args...), pcs[0]) | ||
_ = l.Log.Handler().Handle(ctx, r) //nolint:contextcheck | ||
} else { | ||
r := slog.NewRecord(time.Now(), lvl, msg, pcs[0]) | ||
_ = l.Log.Handler().Handle(ctx, r) //nolint:contextcheck | ||
} | ||
} | ||
|
||
func (l *Logger) With(args ...any) *Logger { | ||
return &Logger{Log: l.Log.With(args...)} | ||
} | ||
|
||
func (l *Logger) WithField(k, v string) *Logger { | ||
return &Logger{Log: l.Log.With(slog.String(k, v))} | ||
} | ||
|
||
func (l *Logger) WithGroup(name string) *Logger { | ||
return &Logger{Log: l.Log.WithGroup(name)} | ||
} |
Oops, something went wrong.