Skip to content

Instantly share code, notes, and snippets.

@pellared
Last active October 31, 2019 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pellared/7f4ba31ab633f0f5ae492745ff00024c to your computer and use it in GitHub Desktop.
Save pellared/7f4ba31ab633f0f5ae492745ff00024c to your computer and use it in GitHub Desktop.
Go: Contextual logging with logrus, chi
package chilogrus
import (
"context"
"fmt"
"net/http"
"time"
"github.com/go-chi/chi/middleware"
"github.com/sirupsen/logrus"
"github.com/yourrepo/pkg/logctx"
)
// NewStructuredLoggerMiddleware creates a chi middleware for logging requests using logrus
func NewStructuredLoggerMiddleware(logger *logrus.Logger) func(next http.Handler) http.Handler {
return middleware.RequestLogger(&structuredLogger{logger})
}
// structuredLogger implements middleware.RequestLogger for logrus
type structuredLogger struct {
Logger *logrus.Logger
}
// logEntry implements middleware.LogEntry for logrus
type logEntry struct {
entry *logrus.Entry
}
// NewLogEntry is required to implement the interface required by chi's middleware.RequestLogger
func (l *structuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
logFields := logrus.Fields{
"http_method": r.Method,
"uri": fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI),
"remote_addr": r.RemoteAddr,
}
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
logFields["request_id"] = reqID
}
entry := logrus.NewEntry(l.Logger).WithFields(logFields)
entry.Trace("request started")
ctx := logctx.New(r.Context(), entry)
*r = *r.WithContext(ctx)
return &logEntry{entry}
}
// Write is executed at the end of the request
func (l *logEntry) Write(status, bytes int, elapsed time.Duration) {
l.entry.WithFields(logrus.Fields{
"response_status": status,
"response_bytes_length": bytes,
"response_elapsed_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
}).Trace("request completed")
}
// Panic is handled executed when a panic occurs
func (l *logEntry) Panic(v interface{}, stack []byte) {
l.entry.WithFields(logrus.Fields{
"stack": string(stack),
"panic": fmt.Sprintf("%+v", v),
}).Error("request panicked")
}
package logctx
import (
"context"
"io/ioutil"
"github.com/sirupsen/logrus"
)
// DefaultLogger is used to create a new LogEntry
// if there is no LogEntry within the context.
// Set it in your application's Compostion Root.
var DefaultLogger *logrus.Logger = logrus.New()
type contextKey struct{}
// New returns a a copy of parent context and adds the provided log entry.
// Used to set the contextual log entry.
func New(ctx context.Context, logEntry *logrus.Entry) context.Context {
return context.WithValue(ctx, contextKey{}, logEntry)
}
// From returns the contextual log entry.
func From(ctx context.Context) *logrus.Entry {
if entry, ok := ctx.Value(contextKey{}).(*logrus.Entry); ok {
return entry
}
// handling case when WithLogger was not invoked for given context
return logrus.NewEntry(DefaultLogger)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment