Saving context logger work

main
Drew Bednar 6 days ago
parent c9da5ba970
commit 465e54ae92

@ -62,4 +62,12 @@ curl -w '\nTime: %{time_total}s \n' localhost:5002/v1/movies/4
} }
Time: 8.009385s Time: 8.009385s
```
## Timeout a Curl Request
We can timeout our curl request like so
```bash
curl --max-time 2 localhost:5002/v1/movies/4
``` ```

@ -260,4 +260,9 @@ If we want to perform some kind of custom decoding for our type, this can be don
type Unmarshaler interface { type Unmarshaler interface {
UnmarshalJSON([]byte) error UnmarshalJSON([]byte) error
} }
``` ```
## Additional Resources
- https://betterstack.com/community/guides/scaling-go/postgresql-pgx-golang/
- https://betterstack.com/community/guides/scaling-go/golang-testify/

@ -243,6 +243,11 @@ func (app *application) deleteMovieHandler(w http.ResponseWriter, r *http.Reques
} }
func (app *application) listMoviesHandler(w http.ResponseWriter, r *http.Request) { func (app *application) listMoviesHandler(w http.ResponseWriter, r *http.Request) {
qparams := r.URL.Query()
if len(qparams) != 0 {
app.logger.Debug("query params", "qp", qparams)
}
movies, err := app.models.Movies.List(r.Context()) movies, err := app.models.Movies.List(r.Context())
if err != nil { if err != nil {
app.serverErrorResponse(w, r, err) app.serverErrorResponse(w, r, err)

@ -28,3 +28,17 @@ func (app *application) recoverPanic(next http.Handler) http.Handler {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
} }
func (app *application) RquestLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
ip = r.RemoteAddr
proto = r.Proto
method = r.Method
uri = r.URL.RequestURI()
)
app.logger.Info("recieved request", "ip", ip, "proto", proto, "method", method, "uri", uri)
next.ServeHTTP(w, r)
})
}

@ -30,5 +30,5 @@ func (app *application) routes() http.Handler {
router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler) router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
// middleware // middleware
return app.recoverPanic(router) return app.recoverPanic(app.RquestLoggingMiddleware(router))
} }

@ -1,6 +1,10 @@
package config package config
import "github.com/kelseyhightower/envconfig" import (
"flag"
"github.com/kelseyhightower/envconfig"
)
type ServiceConfig struct { type ServiceConfig struct {
LogLevel string `default:"INFO"` LogLevel string `default:"INFO"`
@ -12,5 +16,13 @@ type ServiceConfig struct {
func GetServiceConfig() ServiceConfig { func GetServiceConfig() ServiceConfig {
var sc ServiceConfig var sc ServiceConfig
envconfig.MustProcess("PULLEY", &sc) envconfig.MustProcess("PULLEY", &sc)
// Flag overrides
port := flag.Int("port", sc.Port, "Service port. Default: '5002'")
logLevel := flag.String("log-level", sc.LogLevel, "Logging level Default:'INFO'")
flag.Parse()
sc.Port = *port
sc.LogLevel = *logLevel
return sc return sc
} }

@ -1,11 +1,20 @@
package logging package logging
import ( import (
"context"
"io" "io"
"log/slog" "log/slog"
"strings" "strings"
) )
type CtxKey int
const (
_ CtxKey = iota
CtxKeyLogger
CtxKeyTraceID
)
func parseLogLevel(levelStr string) slog.Level { func parseLogLevel(levelStr string) slog.Level {
switch strings.ToUpper(levelStr) { switch strings.ToUpper(levelStr) {
case "DEBUG": case "DEBUG":
@ -34,3 +43,13 @@ func InitLogging(level string, w io.Writer, addSource bool) *slog.Logger {
slog.SetDefault(logger) slog.SetDefault(logger)
return logger return logger
} }
func LoggerFromCtx(ctx context.Context) *slog.Logger {
// context doesn't enforce type when context.WithValue(ctx, ctxKeyLogger, logger) is used
// it is stored as empty interface. So use type assertion .(*slog.Logger) to retrieve the
// value
if l, ok := ctx.Value(CtxKeyLogger).(*slog.Logger); ok && l != nil {
return l
}
return slog.Default()
}

@ -2,6 +2,7 @@ package logging
import ( import (
"bytes" "bytes"
"context"
"log/slog" "log/slog"
"testing" "testing"
@ -63,3 +64,24 @@ func TestInitLogging(t *testing.T) {
assert.StringContains(t, content, msg) assert.StringContains(t, content, msg)
} }
func TestLoggerFromCtx(t *testing.T) {
defaultLogger := slog.Default()
subLogger := defaultLogger.With("testLogger", "this is a test")
t.Run("test logger returned is default", func(t *testing.T) {
assert.Equal(t, LoggerFromCtx(context.TODO()), defaultLogger)
})
t.Run("test logger returns saved logger", func(t *testing.T) {
assert.Equal(t, LoggerFromCtx(context.WithValue(context.TODO(), CtxKeyLogger, subLogger)), subLogger)
})
t.Run("test default logger when nil", func(t *testing.T) {
assert.Equal(t, LoggerFromCtx(context.WithValue(context.TODO(), CtxKeyLogger, nil)), defaultLogger)
})
t.Run("test default logger when wrong type", func(t *testing.T) {
assert.Equal(t, LoggerFromCtx(context.WithValue(context.TODO(), CtxKeyLogger, "bad type")), defaultLogger)
})
}

Loading…
Cancel
Save