snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
293
oryx/logrusx/helper.go
Normal file
293
oryx/logrusx/helper.go
Normal file
@@ -0,0 +1,293 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logrusx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ory/x/errorsx"
|
||||
)
|
||||
|
||||
// loggerConfig holds the mutable state that can change after construction via
|
||||
// UseConfig. It is accessed via a pointer so all shallow copies of Logger
|
||||
// (WithField, WithFields, WithContext, NewEntry) share and see the same config.
|
||||
type loggerConfig struct {
|
||||
mu sync.RWMutex
|
||||
leakSensitive bool
|
||||
redactionText string
|
||||
additionalRedactedHeaders map[string]struct{}
|
||||
}
|
||||
|
||||
type (
|
||||
Logger struct {
|
||||
*logrus.Entry
|
||||
cfg *loggerConfig
|
||||
opts []Option
|
||||
}
|
||||
Provider interface {
|
||||
Logger() *Logger
|
||||
}
|
||||
)
|
||||
|
||||
var opts = otelhttptrace.WithPropagators(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
|
||||
func (l *Logger) Logrus() *logrus.Logger {
|
||||
return l.Entry.Logger
|
||||
}
|
||||
|
||||
func (l *Logger) NewEntry() *Logger {
|
||||
ll := *l
|
||||
ll.Entry = logrus.NewEntry(l.Logger)
|
||||
return &ll
|
||||
}
|
||||
|
||||
func (l *Logger) WithContext(ctx context.Context) *Logger {
|
||||
ll := *l
|
||||
ll.Entry = l.Logger.WithContext(ctx)
|
||||
return &ll
|
||||
}
|
||||
|
||||
func (l *Logger) HTTPHeadersRedacted(h http.Header) map[string]any {
|
||||
// Snapshot the mutable config fields once under the read lock so we neither
|
||||
// hold the lock during iteration nor risk a concurrent map write via UseConfig.
|
||||
l.cfg.mu.RLock()
|
||||
leakSensitive := l.cfg.leakSensitive
|
||||
redactionText := l.cfg.redactionText
|
||||
additionalHeaders := l.cfg.additionalRedactedHeaders
|
||||
l.cfg.mu.RUnlock()
|
||||
|
||||
headers := map[string]any{}
|
||||
|
||||
for key, value := range h {
|
||||
switch keyLower := strings.ToLower(key); keyLower {
|
||||
case "authorization", "cookie", "set-cookie", "x-session-token":
|
||||
headers[keyLower] = maybeRedactWith(value, leakSensitive, redactionText)
|
||||
case "location":
|
||||
locationURL, err := url.Parse(h.Get("Location"))
|
||||
if err != nil {
|
||||
headers[keyLower] = maybeRedactWith(value, leakSensitive, redactionText)
|
||||
continue
|
||||
}
|
||||
if leakSensitive {
|
||||
headers[keyLower] = locationURL.String()
|
||||
} else {
|
||||
locationURL.RawQuery = ""
|
||||
locationURL.Fragment = ""
|
||||
headers[keyLower] = locationURL.Redacted()
|
||||
}
|
||||
default:
|
||||
if _, ok := additionalHeaders[keyLower]; ok {
|
||||
headers[keyLower] = maybeRedactWith(value, leakSensitive, redactionText)
|
||||
continue
|
||||
}
|
||||
headers[keyLower] = h.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
func (l *Logger) WithRequest(r *http.Request) *Logger {
|
||||
headers := l.HTTPHeadersRedacted(r.Header)
|
||||
if ua := r.UserAgent(); len(ua) > 0 {
|
||||
headers["user-agent"] = ua
|
||||
}
|
||||
|
||||
scheme := "https"
|
||||
if r.TLS == nil {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
ll := l.WithField("http_request", map[string]any{
|
||||
"remote": r.RemoteAddr,
|
||||
"method": r.Method,
|
||||
"path": r.URL.EscapedPath(),
|
||||
"query": l.maybeRedact(r.URL.RawQuery),
|
||||
"scheme": scheme,
|
||||
"host": r.Host,
|
||||
"headers": headers,
|
||||
})
|
||||
|
||||
spanCtx := trace.SpanContextFromContext(r.Context())
|
||||
if !spanCtx.IsValid() {
|
||||
_, _, spanCtx = otelhttptrace.Extract(r.Context(), r, opts)
|
||||
}
|
||||
if spanCtx.IsValid() {
|
||||
traces := make(map[string]string, 2)
|
||||
if spanCtx.HasTraceID() {
|
||||
traces["trace_id"] = spanCtx.TraceID().String()
|
||||
}
|
||||
if spanCtx.HasSpanID() {
|
||||
traces["span_id"] = spanCtx.SpanID().String()
|
||||
}
|
||||
ll = ll.WithField("otel", traces)
|
||||
}
|
||||
return ll
|
||||
}
|
||||
|
||||
func (l *Logger) WithSpanFromContext(ctx context.Context) *Logger {
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if !spanCtx.IsValid() {
|
||||
return l
|
||||
}
|
||||
|
||||
traces := make(map[string]string, 2)
|
||||
if spanCtx.HasTraceID() {
|
||||
traces["trace_id"] = spanCtx.TraceID().String()
|
||||
}
|
||||
if spanCtx.HasSpanID() {
|
||||
traces["span_id"] = spanCtx.SpanID().String()
|
||||
}
|
||||
return l.WithField("otel", traces)
|
||||
}
|
||||
|
||||
func (l *Logger) Logf(level logrus.Level, format string, args ...any) {
|
||||
l.cfg.mu.RLock()
|
||||
leakSensitive := l.cfg.leakSensitive
|
||||
l.cfg.mu.RUnlock()
|
||||
|
||||
if !leakSensitive {
|
||||
for i, arg := range args {
|
||||
switch urlArg := arg.(type) {
|
||||
case url.URL:
|
||||
urlCopy := url.URL{Scheme: urlArg.Scheme, Host: urlArg.Host, Path: urlArg.Path}
|
||||
args[i] = urlCopy
|
||||
case *url.URL:
|
||||
urlCopy := url.URL{Scheme: urlArg.Scheme, Host: urlArg.Host, Path: urlArg.Path}
|
||||
args[i] = &urlCopy
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
l.Entry.Logf(level, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Tracef(format string, args ...any) {
|
||||
l.Logf(logrus.TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Debugf(format string, args ...any) {
|
||||
l.Logf(logrus.DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Infof(format string, args ...any) {
|
||||
l.Logf(logrus.InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warnf(format string, args ...any) {
|
||||
l.Logf(logrus.WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Errorf(format string, args ...any) {
|
||||
l.Logf(logrus.ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalf(format string, args ...any) {
|
||||
l.Logf(logrus.FatalLevel, format, args...)
|
||||
l.Entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Panicf(format string, args ...any) {
|
||||
l.Logf(logrus.PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) WithFields(f logrus.Fields) *Logger {
|
||||
ll := *l
|
||||
ll.Entry = l.Entry.WithFields(f)
|
||||
return &ll
|
||||
}
|
||||
|
||||
func (l *Logger) WithField(key string, value any) *Logger {
|
||||
ll := *l
|
||||
ll.Entry = l.Entry.WithField(key, value)
|
||||
return &ll
|
||||
}
|
||||
|
||||
// maybeRedactWith is the lock-free redaction helper; callers must supply
|
||||
// pre-snapshotted values of leakSensitive and redactionText.
|
||||
func maybeRedactWith(value any, leakSensitive bool, redactionText string) any {
|
||||
if fmt.Sprintf("%v", value) == "" || value == nil {
|
||||
return nil
|
||||
}
|
||||
if !leakSensitive {
|
||||
return redactionText
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (l *Logger) maybeRedact(value any) any {
|
||||
l.cfg.mu.RLock()
|
||||
leak := l.cfg.leakSensitive
|
||||
text := l.cfg.redactionText
|
||||
l.cfg.mu.RUnlock()
|
||||
return maybeRedactWith(value, leak, text)
|
||||
}
|
||||
|
||||
func (l *Logger) WithSensitiveField(key string, value any) *Logger {
|
||||
return l.WithField(key, l.maybeRedact(value))
|
||||
}
|
||||
|
||||
func (l *Logger) WithError(err error) *Logger {
|
||||
if err == nil {
|
||||
return l
|
||||
}
|
||||
|
||||
ctx := map[string]any{"message": err.Error()}
|
||||
if l.Entry.Logger.IsLevelEnabled(logrus.DebugLevel) {
|
||||
if e, ok := err.(errorsx.StackTracer); ok {
|
||||
ctx["stack_trace"] = fmt.Sprintf("%+v", e.StackTrace())
|
||||
} else {
|
||||
ctx["stack_trace"] = fmt.Sprintf("stack trace could not be recovered from error type %s", reflect.TypeOf(err))
|
||||
}
|
||||
}
|
||||
if c := errorsx.ReasonCarrier(nil); errors.As(err, &c) {
|
||||
ctx["reason"] = c.Reason()
|
||||
}
|
||||
if c := errorsx.RequestIDCarrier(nil); errors.As(err, &c) && c.RequestID() != "" {
|
||||
ctx["request_id"] = c.RequestID()
|
||||
}
|
||||
if c := errorsx.DetailsCarrier(nil); errors.As(err, &c) && c.Details() != nil {
|
||||
ctx["details"] = c.Details()
|
||||
}
|
||||
if c := errorsx.StatusCarrier(nil); errors.As(err, &c) && c.Status() != "" {
|
||||
ctx["status"] = c.Status()
|
||||
}
|
||||
if c := errorsx.StatusCodeCarrier(nil); errors.As(err, &c) && c.StatusCode() != 0 {
|
||||
ctx["status_code"] = c.StatusCode()
|
||||
}
|
||||
if c := errorsx.DebugCarrier(nil); errors.As(err, &c) {
|
||||
ctx["debug"] = c.Debug()
|
||||
}
|
||||
|
||||
return l.WithField("error", ctx)
|
||||
}
|
||||
|
||||
func (l *Logger) StdLogger(lvl logrus.Level) *log.Logger {
|
||||
return log.New(writer{l.Logger, lvl}, "", 0)
|
||||
}
|
||||
|
||||
type writer struct {
|
||||
l *logrus.Logger
|
||||
lvl logrus.Level
|
||||
}
|
||||
|
||||
func (w writer) Write(p []byte) (n int, err error) {
|
||||
w.l.Log(w.lvl, strings.TrimSuffix(string(p), "\n"))
|
||||
return len(p), nil
|
||||
}
|
||||
Reference in New Issue
Block a user