Skip to content

Commit 6419b51

Browse files
committed
Initial commit
0 parents  commit 6419b51

File tree

8 files changed

+510
-0
lines changed

8 files changed

+510
-0
lines changed

LICENSE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# MIT License
2+
3+
Copyright (c) 2020 Arun Gopalpuri
4+
Copyright (c) 2024 Christian Neff
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
THE SOFTWARE.

Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.DEFAULT_GOAL := help
2+
SHELL := /bin/bash
3+
4+
help: ## Prints this help
5+
@awk 'BEGIN {FS = ":.*?## "} /^[a-z0-9A-Z_-]+:.*?## / {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
6+
7+
build: ## Build Go app for current OS/Arch
8+
go build ./...
9+
10+
test: ## Run all unit tests
11+
go test ./...
12+
13+
vet: ## Examines the source code and reports suspicious constructs
14+
go list ./... | grep -v /vendor/ | xargs -L1 go vet
15+
16+
format: ## Format the source code
17+
@find ./ -type f -name "*.go" -exec gofmt -w {} \;
18+
19+
lint: ## Lint all go source files
20+
go list ./... | grep -v /vendor/ | xargs -L1 golint -set_exit_status
21+
22+
fmtcheck: ## Check if the source code has been formatted
23+
@mkdir -p target
24+
@find ./ -type f -name "*.go" -exec gofmt -d {} \; | tee target/format.diff
25+
@test ! -s target/format.diff || { echo "ERROR: the source code has not been formatted - please use 'make format' or 'gofmt'"; exit 1; }

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Go Logger interface with implementations
2+
3+
This library provides a common interface for logging in Go. It also provides an implementation of the interface for [slog](https://pkg.go.dev/log/slog),
4+
which is part of the standard library.
5+
6+
Coming soon in independent repositories: Implementations for two other popular log libraries: [Logrus](https://github.com/sirupsen/logrus)
7+
and [zap](https://github.com/uber-go/zap).
8+
9+
## Why is this interface useful?
10+
11+
When we create libraries in general we shouldn't be logging but at times we do have to log, debug what the library is doing or trace the log.
12+
13+
We cannot implement a library with one log library and expect other applications to use the same log library. Here is where this interface comes in.
14+
It allows others to change the log library at any time without changing the code.
15+
16+
## Installation
17+
18+
To install `go-logger`, use the following command:
19+
20+
go get -u github.com/secondtruth/go-logger
21+
22+
## Quick Start
23+
24+
### Example for [slog](https://pkg.go.dev/log/slog)
25+
26+
```go
27+
package main
28+
29+
import (
30+
"os"
31+
"log/slog"
32+
33+
"github.com/secondtruth/go-logger/logger"
34+
)
35+
36+
func main() {
37+
slogHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
38+
Level: slog.LevelDebug,
39+
})
40+
slogLog := slog.New(slogHandler)
41+
log, _ := logger.NewSlogLogger(slogLog)
42+
43+
log.WithFields(logger.Fields{
44+
"foo": "bar",
45+
}).Info("message")
46+
}
47+
```

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/secondtruth/go-logger
2+
3+
go 1.21.0
4+
5+
require github.com/stretchr/testify v1.8.4
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
gopkg.in/yaml.v3 v3.0.1 // indirect
11+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

logger/logger.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package logger
2+
3+
// Fields type to pass when we want to call WithFields for structured logging
4+
type Fields map[string]any
5+
6+
// Logger is our contract for the logger
7+
type Logger interface {
8+
// Debug uses fmt.Sprint to construct and log a message.
9+
Debug(args ...any)
10+
11+
// Info uses fmt.Sprint to construct and log a message.
12+
Info(args ...any)
13+
14+
// Warn uses fmt.Sprint to construct and log a message.
15+
Warn(args ...any)
16+
17+
// Error uses fmt.Sprint to construct and log a message.
18+
Error(args ...any)
19+
20+
// Panic uses fmt.Sprint to construct and log a message, then panics.
21+
Panic(args ...any)
22+
23+
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
24+
Fatal(args ...any)
25+
26+
// Debugf uses fmt.Sprintf to log a templated message.
27+
Debugf(template string, args ...any)
28+
29+
// Infof uses fmt.Sprintf to log a templated message.
30+
Infof(template string, args ...any)
31+
32+
// Warnf uses fmt.Sprintf to log a templated message.
33+
Warnf(template string, args ...any)
34+
35+
// Errorf uses fmt.Sprintf to log a templated message.
36+
Errorf(template string, args ...any)
37+
38+
// Panicf uses fmt.Sprintf to log a templated message, then panics.
39+
Panicf(template string, args ...any)
40+
41+
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
42+
Fatalf(template string, args ...any)
43+
44+
// WithFields adds a map of key-value pairs to the logging context
45+
WithFields(keyValues Fields) Logger
46+
}

logger/slog.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package logger
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"runtime"
9+
"time"
10+
)
11+
12+
type slogLogEntry struct {
13+
base *slogLogger
14+
fields Fields
15+
}
16+
17+
type slogLogger struct {
18+
logger *slog.Logger
19+
}
20+
21+
// NewSlogLogger erstellt einen neuen Logger mit slog Logger
22+
func NewSlogLogger(logger *slog.Logger) (Logger, error) {
23+
return &slogLogger{
24+
logger: logger,
25+
}, nil
26+
}
27+
28+
func (l *slogLogger) doLog(level slog.Level, msg string, attrs ...slog.Attr) {
29+
if !l.logger.Enabled(context.Background(), level) {
30+
return
31+
}
32+
var pcs [1]uintptr
33+
runtime.Callers(2, pcs[:]) // skip [Callers, log]
34+
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
35+
if len(attrs) > 0 {
36+
r.AddAttrs(attrs...)
37+
}
38+
_ = l.logger.Handler().Handle(context.Background(), r)
39+
}
40+
41+
// Debug uses fmt.Sprint to construct and log a message.
42+
func (l *slogLogger) Debug(args ...any) {
43+
l.doLog(slog.LevelDebug, fmt.Sprint(args...))
44+
}
45+
46+
// Info uses fmt.Sprint to construct and log a message.
47+
func (l *slogLogger) Info(args ...any) {
48+
l.doLog(slog.LevelInfo, fmt.Sprint(args...))
49+
}
50+
51+
// Warn uses fmt.Sprint to construct and log a message.
52+
func (l *slogLogger) Warn(args ...any) {
53+
l.doLog(slog.LevelWarn, fmt.Sprint(args...))
54+
}
55+
56+
// Error uses fmt.Sprint to construct and log a message.
57+
func (l *slogLogger) Error(args ...any) {
58+
l.doLog(slog.LevelError, fmt.Sprint(args...))
59+
}
60+
61+
// Panic uses fmt.Sprint to construct and log a message, then panics.
62+
func (l *slogLogger) Panic(args ...any) {
63+
msg := fmt.Sprint(args...)
64+
l.doLog(slog.LevelError, msg)
65+
panic(msg)
66+
}
67+
68+
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
69+
func (l *slogLogger) Fatal(args ...any) {
70+
l.doLog(slog.LevelError, fmt.Sprint(args...))
71+
os.Exit(1)
72+
}
73+
74+
// Debugf uses fmt.Sprintf to log a templated message.
75+
func (l *slogLogger) Debugf(template string, args ...any) {
76+
l.doLog(slog.LevelDebug, fmt.Sprintf(template, args...))
77+
}
78+
79+
func (l *slogLogger) Infof(template string, args ...any) {
80+
l.doLog(slog.LevelInfo, fmt.Sprintf(template, args...))
81+
}
82+
83+
// Warnf uses fmt.Sprintf to log a templated message.
84+
func (l *slogLogger) Warnf(template string, args ...any) {
85+
l.doLog(slog.LevelWarn, fmt.Sprintf(template, args...))
86+
}
87+
88+
// Errorf uses fmt.Sprintf to log a templated message.
89+
func (l *slogLogger) Errorf(template string, args ...any) {
90+
l.doLog(slog.LevelError, fmt.Sprintf(template, args...))
91+
}
92+
93+
// Panicf uses fmt.Sprintf to log a templated message, then panics.
94+
func (l *slogLogger) Panicf(template string, args ...any) {
95+
msg := fmt.Sprintf(template, args...)
96+
l.doLog(slog.LevelError, msg)
97+
panic(msg)
98+
}
99+
100+
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
101+
func (l *slogLogger) Fatalf(template string, args ...any) {
102+
l.doLog(slog.LevelError, fmt.Sprintf(template, args...))
103+
os.Exit(1)
104+
}
105+
106+
// Adds a struct of fields to the log entry. All it does is call `WithField` for
107+
// each `Field`.
108+
func (l *slogLogger) WithFields(fields Fields) Logger {
109+
return &slogLogEntry{
110+
base: l,
111+
fields: fields,
112+
}
113+
}
114+
115+
func (l *slogLogEntry) doLogWithFields(level slog.Level, msg string) {
116+
var attrs []slog.Attr
117+
for k, v := range l.fields {
118+
attrs = append(attrs, slog.Attr{Key: k, Value: slog.AnyValue(v)})
119+
}
120+
l.base.doLog(level, msg, attrs...)
121+
}
122+
123+
// Debug uses fmt.Sprint to construct and log a message.
124+
func (l *slogLogEntry) Debug(args ...any) {
125+
l.doLogWithFields(slog.LevelDebug, fmt.Sprint(args...))
126+
}
127+
128+
// Info uses fmt.Sprint to construct and log a message.
129+
func (l *slogLogEntry) Info(args ...any) {
130+
l.doLogWithFields(slog.LevelInfo, fmt.Sprint(args...))
131+
}
132+
133+
// Warn uses fmt.Sprint to construct and log a message.
134+
func (l *slogLogEntry) Warn(args ...any) {
135+
l.doLogWithFields(slog.LevelWarn, fmt.Sprint(args...))
136+
}
137+
138+
// Error uses fmt.Sprint to construct and log a message.
139+
func (l *slogLogEntry) Error(args ...any) {
140+
l.doLogWithFields(slog.LevelError, fmt.Sprint(args...))
141+
}
142+
143+
// Panic uses fmt.Sprint to construct and log a message, then panics.
144+
func (l *slogLogEntry) Panic(args ...any) {
145+
msg := fmt.Sprint(args...)
146+
l.doLogWithFields(slog.LevelError, msg)
147+
panic(msg)
148+
}
149+
150+
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
151+
func (l *slogLogEntry) Fatal(args ...any) {
152+
l.doLogWithFields(slog.LevelError, fmt.Sprint(args...))
153+
os.Exit(1)
154+
}
155+
156+
// Debugf uses fmt.Sprintf to log a templated message.
157+
func (l *slogLogEntry) Debugf(template string, args ...any) {
158+
l.doLogWithFields(slog.LevelDebug, fmt.Sprintf(template, args...))
159+
}
160+
161+
// Infof uses fmt.Sprintf to log a templated message.
162+
func (l *slogLogEntry) Infof(template string, args ...any) {
163+
l.doLogWithFields(slog.LevelInfo, fmt.Sprintf(template, args...))
164+
}
165+
166+
// Warnf uses fmt.Sprintf to log a templated message.
167+
func (l *slogLogEntry) Warnf(template string, args ...any) {
168+
l.doLogWithFields(slog.LevelWarn, fmt.Sprintf(template, args...))
169+
}
170+
171+
// Errorf uses fmt.Sprintf to log a templated message.
172+
func (l *slogLogEntry) Errorf(template string, args ...any) {
173+
l.doLogWithFields(slog.LevelError, fmt.Sprintf(template, args...))
174+
}
175+
176+
// Panicf uses fmt.Sprintf to log a templated message, then panics.
177+
func (l *slogLogEntry) Panicf(template string, args ...any) {
178+
msg := fmt.Sprintf(template, args...)
179+
l.doLogWithFields(slog.LevelError, msg)
180+
panic(msg)
181+
}
182+
183+
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
184+
func (l *slogLogEntry) Fatalf(template string, args ...any) {
185+
l.doLogWithFields(slog.LevelError, fmt.Sprintf(template, args...))
186+
}
187+
188+
// WithFields adds fields to the logging context
189+
func (l *slogLogEntry) WithFields(fields Fields) Logger {
190+
var allFields = make(Fields, len(l.fields)+len(fields))
191+
for k, v := range fields {
192+
allFields[k] = v
193+
}
194+
for k, v := range l.fields {
195+
allFields[k] = v
196+
}
197+
return l.base.WithFields(allFields)
198+
}

0 commit comments

Comments
 (0)