snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (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 tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (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:
278
oryx/logrusx/logrus.go
Normal file
278
oryx/logrusx/logrus.go
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logrusx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
_ "embed"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
gelf "github.com/seatgeek/logrus-gelf-formatter"
|
||||
|
||||
"github.com/ory/x/stringsx"
|
||||
)
|
||||
|
||||
type (
|
||||
options struct {
|
||||
l *logrus.Logger
|
||||
level *logrus.Level
|
||||
formatter logrus.Formatter
|
||||
format string
|
||||
reportCaller bool
|
||||
exitFunc func(int)
|
||||
leakSensitive bool
|
||||
redactionText string
|
||||
additionalRedactedHeaders []string
|
||||
hooks []logrus.Hook
|
||||
c configurator
|
||||
}
|
||||
Option func(*options)
|
||||
nullConfigurator struct{}
|
||||
configurator interface {
|
||||
Bool(key string) bool
|
||||
String(key string) string
|
||||
Strings(path string) []string
|
||||
}
|
||||
)
|
||||
|
||||
//go:embed config.schema.json
|
||||
var ConfigSchema string
|
||||
|
||||
const ConfigSchemaID = "ory://logging-config"
|
||||
|
||||
// AddConfigSchema adds the logging schema to the compiler.
|
||||
// The interface is specified instead of `jsonschema.Compiler` to allow the use of any jsonschema library fork or version.
|
||||
func AddConfigSchema(c interface {
|
||||
AddResource(url string, r io.Reader) error
|
||||
},
|
||||
) error {
|
||||
return c.AddResource(ConfigSchemaID, bytes.NewBufferString(ConfigSchema))
|
||||
}
|
||||
|
||||
func newLogger(parent *logrus.Logger, o *options) *logrus.Logger {
|
||||
l := parent
|
||||
if l == nil {
|
||||
l = logrus.New()
|
||||
}
|
||||
|
||||
if o.exitFunc != nil {
|
||||
l.ExitFunc = o.exitFunc
|
||||
}
|
||||
|
||||
for _, hook := range o.hooks {
|
||||
l.AddHook(hook)
|
||||
}
|
||||
|
||||
setLevel(l, o)
|
||||
setFormatter(l, o)
|
||||
|
||||
l.ReportCaller = o.reportCaller || l.IsLevelEnabled(logrus.TraceLevel)
|
||||
return l
|
||||
}
|
||||
|
||||
func setLevel(l *logrus.Logger, o *options) {
|
||||
if o.level != nil {
|
||||
l.SetLevel(*o.level)
|
||||
} else {
|
||||
level, err := logrus.ParseLevel(cmp.Or(
|
||||
o.c.String("log.level"),
|
||||
os.Getenv("LOG_LEVEL")))
|
||||
if err != nil {
|
||||
level = logrus.InfoLevel
|
||||
}
|
||||
l.SetLevel(level)
|
||||
}
|
||||
}
|
||||
|
||||
func setFormatter(l *logrus.Logger, o *options) {
|
||||
if o.formatter != nil {
|
||||
l.SetFormatter(o.formatter)
|
||||
} else {
|
||||
var unknownFormat bool // we first have to set the formatter before we can complain about the unknown format
|
||||
var formatter logrus.Formatter
|
||||
|
||||
format := stringsx.SwitchExact(cmp.Or(o.format, o.c.String("log.format"), os.Getenv("LOG_FORMAT")))
|
||||
switch {
|
||||
case format.AddCase("json"):
|
||||
formatter = &logrus.JSONFormatter{PrettyPrint: false, TimestampFormat: time.RFC3339Nano, DisableHTMLEscape: true}
|
||||
case format.AddCase("json_pretty"):
|
||||
formatter = &logrus.JSONFormatter{PrettyPrint: true, TimestampFormat: time.RFC3339Nano, DisableHTMLEscape: true}
|
||||
case format.AddCase("gelf"):
|
||||
formatter = new(gelf.GelfFormatter)
|
||||
default:
|
||||
unknownFormat = true
|
||||
fallthrough
|
||||
case format.AddCase("text", ""):
|
||||
formatter = &logrus.TextFormatter{
|
||||
DisableQuote: true,
|
||||
DisableTimestamp: false,
|
||||
FullTimestamp: true,
|
||||
}
|
||||
}
|
||||
|
||||
l.SetFormatter(formatter)
|
||||
|
||||
if unknownFormat {
|
||||
l.WithError(format.ToUnknownCaseErr()).Warn("got unknown \"log.format\", falling back to \"text\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ForceLevel(level logrus.Level) Option {
|
||||
return func(o *options) {
|
||||
o.level = &level
|
||||
}
|
||||
}
|
||||
|
||||
func ForceFormatter(formatter logrus.Formatter) Option {
|
||||
return func(o *options) {
|
||||
o.formatter = formatter
|
||||
}
|
||||
}
|
||||
|
||||
func WithConfigurator(c configurator) Option {
|
||||
return func(o *options) {
|
||||
o.c = c
|
||||
}
|
||||
}
|
||||
|
||||
func ForceFormat(format string) Option {
|
||||
return func(o *options) {
|
||||
o.format = format
|
||||
}
|
||||
}
|
||||
|
||||
func WithHook(hook logrus.Hook) Option {
|
||||
return func(o *options) {
|
||||
o.hooks = append(o.hooks, hook)
|
||||
}
|
||||
}
|
||||
|
||||
func WithExitFunc(exitFunc func(int)) Option {
|
||||
return func(o *options) {
|
||||
o.exitFunc = exitFunc
|
||||
}
|
||||
}
|
||||
|
||||
func ReportCaller(reportCaller bool) Option {
|
||||
return func(o *options) {
|
||||
o.reportCaller = reportCaller
|
||||
}
|
||||
}
|
||||
|
||||
func UseLogger(l *logrus.Logger) Option {
|
||||
return func(o *options) {
|
||||
o.l = l
|
||||
}
|
||||
}
|
||||
|
||||
func LeakSensitive() Option {
|
||||
return func(o *options) {
|
||||
o.leakSensitive = true
|
||||
}
|
||||
}
|
||||
|
||||
func RedactionText(text string) Option {
|
||||
return func(o *options) {
|
||||
o.redactionText = text
|
||||
}
|
||||
}
|
||||
|
||||
func WithAdditionalRedactedHeaders(headers []string) Option {
|
||||
return func(o *options) {
|
||||
o.additionalRedactedHeaders = headers
|
||||
}
|
||||
}
|
||||
|
||||
func toHeaderMap(headers []string) map[string]struct{} {
|
||||
m := make(map[string]struct{}, len(headers))
|
||||
for _, h := range headers {
|
||||
m[strings.ToLower(h)] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *nullConfigurator) Bool(_ string) bool { return false }
|
||||
func (c *nullConfigurator) String(_ string) string { return "" }
|
||||
func (c *nullConfigurator) Strings(_ string) []string { return []string{} }
|
||||
|
||||
func newOptions(opts []Option) *options {
|
||||
o := new(options)
|
||||
o.c = new(nullConfigurator)
|
||||
for _, f := range opts {
|
||||
f(o)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// New creates a new logger with all the important fields set.
|
||||
func New(name string, version string, opts ...Option) *Logger {
|
||||
o := newOptions(opts)
|
||||
return &Logger{
|
||||
opts: opts,
|
||||
cfg: &loggerConfig{
|
||||
leakSensitive: o.leakSensitive || o.c.Bool("log.leak_sensitive_values"),
|
||||
redactionText: cmp.Or(o.redactionText, `Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true".`),
|
||||
additionalRedactedHeaders: toHeaderMap(func() []string {
|
||||
if len(o.additionalRedactedHeaders) > 0 {
|
||||
return o.additionalRedactedHeaders
|
||||
}
|
||||
return o.c.Strings("log.additional_redacted_headers")
|
||||
}()),
|
||||
},
|
||||
Entry: newLogger(o.l, o).WithFields(logrus.Fields{
|
||||
"audience": "application", "service_name": name, "service_version": version,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func NewT(t testing.TB, opts ...Option) *Logger {
|
||||
opts = append(opts, LeakSensitive(), WithExitFunc(func(code int) {
|
||||
t.Fatalf("Logger exited with code %d", code)
|
||||
}))
|
||||
l := New(t.Name(), "test", opts...)
|
||||
l.Logger.SetOutput(t.Output())
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logger) UseConfig(c configurator) {
|
||||
// Update logrus level and formatter first, using their mutex-safe methods,
|
||||
// before acquiring our own lock to avoid any lock ordering issues.
|
||||
o := newOptions(append(l.opts, WithConfigurator(c)))
|
||||
setLevel(l.Entry.Logger, o)
|
||||
setFormatter(l.Entry.Logger, o)
|
||||
|
||||
l.cfg.mu.Lock()
|
||||
defer l.cfg.mu.Unlock()
|
||||
l.cfg.leakSensitive = l.cfg.leakSensitive || c.Bool("log.leak_sensitive_values")
|
||||
l.cfg.redactionText = cmp.Or(c.String("log.redaction_text"), l.cfg.redactionText)
|
||||
// Replace the map rather than mutating it in-place so that readers holding
|
||||
// a snapshot of the old pointer can iterate it safely without a lock.
|
||||
newHeaders := maps.Clone(l.cfg.additionalRedactedHeaders)
|
||||
for k := range toHeaderMap(c.Strings("log.additional_redacted_headers")) {
|
||||
newHeaders[k] = struct{}{}
|
||||
}
|
||||
l.cfg.additionalRedactedHeaders = newHeaders
|
||||
}
|
||||
|
||||
func (l *Logger) ReportError(r *http.Request, code int, err error, args ...interface{}) {
|
||||
logger := l.WithError(err).WithRequest(r).WithField("http_response", map[string]interface{}{
|
||||
"status_code": code,
|
||||
})
|
||||
switch {
|
||||
case code < 500:
|
||||
logger.Info(args...)
|
||||
default:
|
||||
logger.Error(args...)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user