Skip to content

Commit

Permalink
Copy helpers from personal projects
Browse files Browse the repository at this point in the history
  • Loading branch information
mbyio committed Dec 1, 2023
1 parent a0c1b13 commit cea1184
Show file tree
Hide file tree
Showing 10 changed files with 611 additions and 0 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
This module contains helpers for the `log/slog` package. https://pkg.go.dev/log/slog

Right now I am just copy-and-pasting helpers as I write/use them in my own
projects. Please let me know if you are using this module yourself or if you
have any suggestions for improvements.

# Features per third party module/package

## `context` (github.com/mbyio/sloghelpers/pkg/slogcontext)

- a `slog.Handler` that wraps another `slog.Handler` and adds attributes from
the context.Context to the log record.

## `net/http` (github.com/mbyio/sloghelpers/pkg/net/sloghttp)

- a `http.RoundTripper` that emits a log for each outbound HTTP request

- a HTTP middleware that logs information about each inbound HTTP request

## `github.com/jackc/pgx/v5` (github.com/mbyio/sloghelpers/pkg/github.com/jackc/pgx/v5/slogpgxv5)

- a pgx query tracer that logs every SQL query, including the query text, duration, and error (if any)

- query parameters are not logged for security reasons

## `google.golang.org/grpc` (github.com/mbyio/sloghelpers/pkg/google.golang.org/sloggrpc

- dial options to add logging to outbound GRPC requests

- you can use this to add logging to GCP client libraries
23 changes: 23 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module github.com/mbyio/sloghelpers

go 1.21.4

require (
github.com/google/go-cmp v0.6.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1
github.com/jackc/pgx/v5 v5.5.0
go.uber.org/mock v0.3.0
google.golang.org/grpc v1.53.0
)

require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
51 changes: 51 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 h1:HcUWd006luQPljE73d5sk+/VgYPGUReEVz2y1/qylwY=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
48 changes: 48 additions & 0 deletions pkg/github.com/jackc/pgx/v5/slogpgx/slogpgxtracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package slogpgxv5querytracer

import (
"context"
"log/slog"
"time"

"github.com/jackc/pgx/v5"
)

type SlogPgxQueryTracer struct {
logger *slog.Logger
}

func New(logger *slog.Logger) *SlogPgxQueryTracer {
return &SlogPgxQueryTracer{
logger: logger,
}
}

type startDataKeyType struct{}

var startDataKey startDataKeyType

type startData struct {
startAt time.Time
sql string
}

func (s *SlogPgxQueryTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
// Record data in the context. We'll log it when the query ends.
return context.WithValue(ctx, startDataKey, &startData{
startAt: time.Now(),
sql: data.SQL,
})
}

func (s *SlogPgxQueryTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
sd := ctx.Value(startDataKey).(*startData)
endAt := time.Now()
s.logger.DebugContext(ctx, "sql query",
"query", sd.sql,
"startAt", sd.startAt,
"endAt", endAt,
"duration", endAt.Sub(sd.startAt),
"error", data.Err,
)
}
33 changes: 33 additions & 0 deletions pkg/google.golang.org/sloggrpc/sloggrpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package sloggrpc

import (
"context"
"log/slog"

"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"google.golang.org/grpc"
)

// interceptorLogger adapts slog logger to interceptor logger.
// This code is simple enough to be copied and not imported.
//
// Copied from https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/interceptors/logging/examples/slog/example_test.go
func interceptorLogger(l *slog.Logger) logging.Logger {
return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) {
l.Log(ctx, slog.Level(lvl), msg, fields...)
})
}

func UnaryDialOption(logger *slog.Logger) grpc.DialOption {
opts := []logging.Option{
logging.WithLogOnEvents(logging.FinishCall),
}
return grpc.WithUnaryInterceptor(logging.UnaryClientInterceptor(interceptorLogger(logger), opts...))
}

func StreamDialOption(logger *slog.Logger) grpc.DialOption {
opts := []logging.Option{
logging.WithLogOnEvents(logging.FinishCall),
}
return grpc.WithStreamInterceptor(logging.StreamClientInterceptor(interceptorLogger(logger), opts...))
}
44 changes: 44 additions & 0 deletions pkg/net/sloghttp/round_tripper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package sloghttp

import (
"log/slog"
"net/http"
"time"
)

type slogRoundTripper struct {
logger *slog.Logger
rt http.RoundTripper
}

func (srt *slogRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
startAt := time.Now()
defer func() {
endAt := time.Now()
var statusCode int
if resp != nil {
statusCode = resp.StatusCode
}
// TODO record request and response body if available
// TODO consider reporting at TRACE or DEBUG level
srt.logger.DebugContext(
req.Context(),
"outbound HTTP request",
"method", req.Method,
"url", req.URL.String(),
"status_code", statusCode,
"duration", endAt.Sub(startAt),
"error", err,
)
}()
return srt.rt.RoundTrip(req)
}

// NewRoundTripper returns a new http.RoundTripper that wraps another RoundTripper and logs
// requests and responses.
func NewRoundTripper(logger *slog.Logger, rt http.RoundTripper) http.RoundTripper {
return &slogRoundTripper{
logger: logger,
rt: rt,
}
}
3 changes: 3 additions & 0 deletions pkg/slogcontext/go_generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package slogcontexthandler

//go:generate go run go.uber.org/mock/[email protected] -destination mock_handler_test.go -package slogcontexthandler log/slog Handler
96 changes: 96 additions & 0 deletions pkg/slogcontext/mock_handler_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cea1184

Please sign in to comment.