Last active
October 31, 2019 14:05
-
-
Save pellared/7f4ba31ab633f0f5ae492745ff00024c to your computer and use it in GitHub Desktop.
Go: Contextual logging with logrus, chi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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