Skip to content

Commit 6e8c0ef

Browse files
authored
Merge pull request #31 from strvcom/feat/migrate-to-slog
feat: migrate to slog.Logger
2 parents da2103d + 13501ff commit 6e8c0ef

File tree

13 files changed

+161
-142
lines changed

13 files changed

+161
-142
lines changed

.github/actions/setup-go/action.yml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ description: |
33
Setup Go
44
55
inputs:
6-
go-version:
7-
description: Used Go version
8-
default: '1.20'
6+
cache:
7+
description: Cache
8+
required: false
9+
default: "true"
910

1011
runs:
1112
using: "composite"
@@ -15,10 +16,11 @@ runs:
1516
echo "Go version is set to ${{ inputs.go-version }}"
1617
echo "GO_VERSION=${{ inputs.go-version }}" >> $GITHUB_ENV
1718
shell: bash
19+
name: Setup Go
1820
- id: go-setup
19-
uses: actions/setup-go@v3
21+
uses: actions/setup-go@v4
2022
with:
21-
go-version: ${{ env.GO_VERSION }}
22-
- run: |
23-
go mod download
24-
shell: bash
23+
go-version-file: go.mod
24+
check-latest: true
25+
cache: ${{ inputs.cache }}
26+

.github/workflows/lint.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ jobs:
2323
- name: Run golangci-lint
2424
uses: golangci/golangci-lint-action@v3
2525
with:
26-
version: v1.51.1
26+
version: v1.56.1

CHANGELOG.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,28 @@ How to release a new version:
55

66
## [Unreleased]
77

8-
## [0.6.2] - 2022-06-27
8+
## [0.7.0] - 2024-03-11
9+
### Changed
10+
- Logging interface changes to `log/slog`.
11+
12+
NOTE: This is version presents a BREAKING CHANGE in the server's logger interface. The server now accepts a `slog.Logger` instead of a custom `ServerLogger`.
13+
- Change in the logging middleware. The middleware now nests request specific data under the "request" group.
14+
- Updated from Go 1.20 to Go 1.22.
15+
- Updated packages:
16+
```diff
17+
- github.com/go-chi/chi/v5 v5.0.8
18+
- github.com/google/uuid v1.3.0
19+
- github.com/stretchr/testify v1.8.0
20+
+ github.com/go-chi/chi/v5 v5.0.12
21+
+ github.com/google/uuid v1.6.0
22+
+ github.com/stretchr/testify v1.9.0
23+
```
24+
25+
## [0.6.2] - 2023-06-27
926
### Fixed
1027
- Error logging when terminating HTTP server.
1128

12-
## [0.6.1] - 2022-03-28
29+
## [0.6.1] - 2023-03-28
1330
### Changed
1431
- package `http/param` does not zero the field if not tagged with any relevant tags
1532

@@ -18,15 +35,15 @@ How to release a new version:
1835
- package `http/signature` to simplify defining http handler functions
1936
- package `http/param` to simplify parsing http path and query parameters
2037

21-
## [0.5.0] - 2022-01-20
38+
## [0.5.0] - 2023-01-20
2239
### Added
2340
- `ErrorResponseOptions` contains public error message.
2441
- `ErrorResponseOptions` contains request ID.
2542
- Error response options:
2643
- `WithErrorMessage`
2744
- `WithRequestID`
2845

29-
## [0.4.0] - 2022-01-12
46+
## [0.4.0] - 2023-01-12
3047
### Changed
3148
- JSON tags in `ErrorResponseOptions`.
3249

@@ -53,7 +70,8 @@ How to release a new version:
5370
### Added
5471
- Added Changelog.
5572

56-
[Unreleased]: https://github.com/strvcom/strv-backend-go-net/compare/v0.6.2...HEAD
73+
[Unreleased]: https://github.com/strvcom/strv-backend-go-net/compare/v0.7.0...HEAD
74+
[0.7.0]: https://github.com/strvcom/strv-backend-go-net/compare/v0.6.2...v0.7.0
5775
[0.6.2]: https://github.com/strvcom/strv-backend-go-net/compare/v0.6.1...v0.6.2
5876
[0.6.1]: https://github.com/strvcom/strv-backend-go-net/compare/v0.6.0...v0.6.1
5977
[0.6.0]: https://github.com/strvcom/strv-backend-go-net/compare/v0.5.0...v0.6.0

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,13 @@ Go package facilitating writing API applications in a fast and easy manner.
1111
### errors
1212
Definition of common errors.
1313

14-
### logger
15-
Interface `ServerLogger` implements common logging methods.
16-
1714
### net
1815
Common functionality that comes in handy regardless of the used API architecture. `net` currently supports generating request IDs with some helper methods.
1916

2017
### http
2118
Wrapper around the Go native http server. `http` defines the `Server` that can be configured by the `ServerConfig`. Implemented features:
2219
- Started http server can be easily stopped by cancelling the context that is passed by the `Run` method.
23-
- The `Server` can be configured with a logger for logging important information during starting/ending of the server.
20+
- The `Server` can be configured with a slog.Logger for logging important information during starting/ending of the server.
2421
- The `Server` listens for `SIGINT` and `SIGTERM` signals so it can be stopped by firing the signal.
2522
- By the `ServerConfig` can be configured functions to be called before the `Server` ends.
2623

@@ -46,7 +43,16 @@ import (
4643

4744
func main() {
4845
...
49-
46+
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
47+
Level: level,
48+
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
49+
if a.Key == slog.TimeKey {
50+
a.Value = slog.StringValue(a.Value.Time().Format("2006-01-02T15:04:05.000Z"))
51+
}
52+
return a
53+
},
54+
})
55+
l := slog.New(h)
5056
serverConfig := httpx.ServerConfig{
5157
Addr: ":8080",
5258
Handler: handler(), // define your http handler
@@ -58,11 +64,11 @@ func main() {
5864
},
5965
},
6066
Limits: nil,
61-
Logger: util.NewServerLogger("httpx.Server"), // wrapper around zap logger to implement httpx logging interface
67+
Logger: l.WithGroup("httpx.Server"), // the server expects *slog.Logger
6268
}
6369
server := httpx.NewServer(&serverConfig)
6470
if err = server.Start(ctx); err != nil {
65-
logger.Fatal("HTTP server unexpectedly ended", zap.Error(err))
71+
l.Error("HTTP server unexpectedly ended", slog.Any("error", err))
6672
}
6773
}
6874
```

go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
module go.strv.io/net
22

3-
go 1.20
3+
go 1.22
44

55
require (
6-
github.com/go-chi/chi/v5 v5.0.8
7-
github.com/google/uuid v1.3.0
8-
github.com/stretchr/testify v1.8.0
6+
github.com/go-chi/chi/v5 v5.0.12
7+
github.com/google/uuid v1.6.0
8+
github.com/stretchr/testify v1.9.0
99
go.strv.io/time v0.2.0
1010
)
1111

1212
require (
1313
github.com/davecgh/go-spew v1.1.1 // indirect
14-
github.com/kr/pretty v0.3.0 // indirect
14+
github.com/kr/pretty v0.3.1 // indirect
1515
github.com/pmezard/go-difflib v1.0.0 // indirect
16-
github.com/rogpeppe/go-internal v1.8.0 // indirect
16+
github.com/rogpeppe/go-internal v1.12.0 // indirect
1717
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
1818
gopkg.in/yaml.v3 v3.0.1 // indirect
1919
)

go.sum

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,29 @@
11
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
32
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
43
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5-
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
6-
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
7-
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
8-
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
9-
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
4+
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
5+
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
6+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
7+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
108
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
11-
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
12-
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
9+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
10+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
1311
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
1412
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1513
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
1614
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1715
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
1816
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1917
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
20-
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
21-
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
22-
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
23-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
24-
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
25-
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
26-
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
27-
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
18+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
19+
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
20+
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
21+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
22+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
2823
go.strv.io/time v0.2.0 h1:RgCpABq+temfp8+DLM2zqsdimnKpktOSPduUghM8ZIk=
2924
go.strv.io/time v0.2.0/go.mod h1:B/lByAO3oACN3uLOXQaB64cKhkVIMoZjnZBhADFNbFY=
3025
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
31-
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3226
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
3327
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
34-
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
35-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3628
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
3729
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

http/config.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package http
22

33
import (
4+
"log/slog"
45
"net/http"
56

6-
"go.strv.io/net/logger"
7-
87
"go.strv.io/time"
98
)
109

@@ -23,7 +22,7 @@ type ServerConfig struct {
2322
Limits *Limits `json:"limits,omitempty"`
2423

2524
// Logger is server logger.
26-
Logger logger.ServerLogger
25+
Logger *slog.Logger
2726
}
2827

2928
// Limits define timeouts and header restrictions.

http/middleware.go

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package http
22

33
import (
4+
"log/slog"
45
"net/http"
6+
"runtime/debug"
57
"time"
68

79
"go.strv.io/net"
810
"go.strv.io/net/internal"
9-
"go.strv.io/net/logger"
1011
)
1112

1213
const (
@@ -40,10 +41,26 @@ func RequestIDMiddleware(f RequestIDFunc) func(http.Handler) http.Handler {
4041
}
4142
}
4243

44+
type RecoverMiddlewareOptions struct {
45+
enableStackTrace bool
46+
}
47+
48+
type RecoverMiddlewareOption func(*RecoverMiddlewareOptions)
49+
50+
func WithStackTrace() RecoverMiddlewareOption {
51+
return func(opts *RecoverMiddlewareOptions) {
52+
opts.enableStackTrace = true
53+
}
54+
}
55+
4356
// RecoverMiddleware calls next handler and recovers from a panic.
4457
// If a panic occurs, log this event, set http.StatusInternalServerError as a status code
4558
// and save a panic object into the response writer.
46-
func RecoverMiddleware(l logger.ServerLogger) func(http.Handler) http.Handler {
59+
func RecoverMiddleware(l *slog.Logger, opts ...RecoverMiddlewareOption) func(http.Handler) http.Handler {
60+
options := RecoverMiddlewareOptions{}
61+
for _, o := range opts {
62+
o(&options)
63+
}
4764
return func(next http.Handler) http.Handler {
4865
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4966
defer func() {
@@ -56,10 +73,14 @@ func RecoverMiddleware(l logger.ServerLogger) func(http.Handler) http.Handler {
5673
rw.SetPanicObject(re)
5774
rw.WriteHeader(http.StatusInternalServerError)
5875

59-
l.With(
60-
logger.Any("err", re),
61-
logger.Any(requestIDLogFieldName, net.RequestIDFromCtx(r.Context())),
62-
).Error("panic recover", nil)
76+
logAttributes := []slog.Attr{
77+
slog.String(requestIDLogFieldName, net.RequestIDFromCtx(r.Context())),
78+
slog.Any("error", re),
79+
}
80+
if options.enableStackTrace {
81+
logAttributes = append(logAttributes, slog.String("stack_trace", string(debug.Stack())))
82+
}
83+
l.LogAttrs(r.Context(), slog.LevelError, "panic recover", logAttributes...)
6384
}
6485
}()
6586
next.ServeHTTP(w, r)
@@ -77,7 +98,7 @@ func RecoverMiddleware(l logger.ServerLogger) func(http.Handler) http.Handler {
7798
// - Panic object if exists
7899
//
79100
// If the status code >= http.StatusInternalServerError, logs with error level, info otherwise.
80-
func LoggingMiddleware(l logger.ServerLogger) func(http.Handler) http.Handler {
101+
func LoggingMiddleware(l *slog.Logger) func(http.Handler) http.Handler {
81102
return func(next http.Handler) http.Handler {
82103
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83104
rw, ok := w.(*internal.ResponseWriter)
@@ -90,57 +111,59 @@ func LoggingMiddleware(l logger.ServerLogger) func(http.Handler) http.Handler {
90111
statusCode := rw.StatusCode()
91112
requestID := net.RequestIDFromCtx(r.Context())
92113

93-
ld := LogData{
114+
ld := RequestData{
94115
Path: r.URL.EscapedPath(),
95116
Method: r.Method,
96117
RequestID: requestID,
97118
Duration: time.Since(requestStart),
98119
ResponseStatusCode: statusCode,
99-
Err: rw.ErrorObject(),
100-
Panic: rw.PanicObject(),
101120
}
102121

103122
if statusCode >= http.StatusInternalServerError {
104-
WithData(l, ld).Error("request processed", nil)
123+
withRequestData(l, rw, ld).Error("request processed")
105124
} else {
106-
WithData(l, ld).Info("request processed")
125+
withRequestData(l, rw, ld).Info("request processed")
107126
}
108127
})
109128
}
110129
}
111130

112-
// LogData contains processed request data for logging purposes.
131+
// RequestData contains processed request data for logging purposes.
113132
// Path is path from URL of the request.
114133
// Method is HTTP request method.
115134
// Duration is how long it took to process whole request.
116135
// ResponseStatusCode is HTTP status code which was returned.
117136
// RequestID is unique identifier of request.
118137
// Err is error object containing error message.
119138
// Panic is panic object containing error message.
120-
type LogData struct {
139+
type RequestData struct {
121140
Path string
122141
Method string
123142
Duration time.Duration
124143
ResponseStatusCode int
125144
RequestID string
126-
Err error
127-
Panic any
128145
}
129146

130-
// WithData returns logger with filled fields.
131-
func WithData(l logger.ServerLogger, ld LogData) logger.ServerLogger {
132-
l = l.With(
133-
logger.Any("method", ld.Method),
134-
logger.Any("path", ld.Path),
135-
logger.Any("status_code", ld.ResponseStatusCode),
136-
logger.Any("request_id", ld.RequestID),
137-
logger.Any("duration_ms", ld.Duration.Milliseconds()),
138-
)
139-
if ld.Err != nil {
140-
l = l.With(logger.Any("err", ld.Err.Error()))
147+
func (r RequestData) LogValue() slog.Value {
148+
attr := []slog.Attr{
149+
slog.String("id", r.RequestID),
150+
slog.String("method", r.Method),
151+
slog.String("path", r.Path),
152+
slog.Int("status_code", r.ResponseStatusCode),
153+
slog.Duration("duration_ms", r.Duration),
154+
}
155+
return slog.GroupValue(attr...)
156+
}
157+
158+
// withRequestData returns slog with filled fields.
159+
func withRequestData(l *slog.Logger, rw *internal.ResponseWriter, rd RequestData) *slog.Logger {
160+
errorObject := rw.ErrorObject()
161+
panicObject := rw.PanicObject()
162+
if errorObject != nil {
163+
l = l.With("error", errorObject)
141164
}
142-
if ld.Panic != nil {
143-
l = l.With(logger.Any("panic", ld.Panic))
165+
if panicObject != nil {
166+
l = l.With("panic", panicObject)
144167
}
145-
return l
168+
return l.With("request", rd)
146169
}

0 commit comments

Comments
 (0)