Initial QSfera import
This commit is contained in:
+182
@@ -0,0 +1,182 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logEntry struct {
|
||||
level Level
|
||||
message string
|
||||
fields map[string]any
|
||||
time time.Time
|
||||
}
|
||||
|
||||
// BufferedLogger captures log entries in memory until Flush is called,
|
||||
// at which point it replays all buffered entries to the target logger.
|
||||
// After Flush() is called, the BufferedLogger should not be used anymore.
|
||||
type BufferedLogger struct {
|
||||
mu sync.Mutex
|
||||
buffer []logEntry
|
||||
maxEntries int
|
||||
currentLevel Level
|
||||
}
|
||||
|
||||
// NewBufferedLogger creates a new buffered logger that will buffer up to maxEntries.
|
||||
func NewBufferedLogger(maxEntries int) *BufferedLogger {
|
||||
if maxEntries <= 0 {
|
||||
maxEntries = 1000
|
||||
}
|
||||
return &BufferedLogger{
|
||||
buffer: make([]logEntry, 0, maxEntries),
|
||||
maxEntries: maxEntries,
|
||||
currentLevel: Info,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BufferedLogger) addToBuffer(level Level, format string, args []any, fields map[string]any) {
|
||||
message := format
|
||||
if len(args) > 0 {
|
||||
message = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
entry := logEntry{
|
||||
level: level,
|
||||
message: message,
|
||||
fields: fields,
|
||||
time: time.Now(),
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if len(b.buffer) >= b.maxEntries {
|
||||
b.buffer = b.buffer[1:]
|
||||
}
|
||||
b.buffer = append(b.buffer, entry)
|
||||
}
|
||||
|
||||
func (*BufferedLogger) logToTarget(target Logger, entry logEntry) {
|
||||
fields := make(map[string]any, len(entry.fields))
|
||||
maps.Copy(fields, entry.fields)
|
||||
fields["time"] = entry.time
|
||||
|
||||
logger := target.WithFields(fields)
|
||||
|
||||
switch entry.level {
|
||||
case Debug:
|
||||
logger.Debug("%s", entry.message)
|
||||
case Info:
|
||||
logger.Info("%s", entry.message)
|
||||
case Warn:
|
||||
logger.Warn("%s", entry.message)
|
||||
case Error:
|
||||
logger.Error("%s", entry.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BufferedLogger) Debug(format string, args ...any) {
|
||||
b.addToBuffer(Debug, format, args, nil)
|
||||
}
|
||||
|
||||
func (b *BufferedLogger) Info(format string, args ...any) {
|
||||
b.addToBuffer(Info, format, args, nil)
|
||||
}
|
||||
|
||||
func (b *BufferedLogger) Warn(format string, args ...any) {
|
||||
b.addToBuffer(Warn, format, args, nil)
|
||||
}
|
||||
|
||||
func (b *BufferedLogger) Error(format string, args ...any) {
|
||||
b.addToBuffer(Error, format, args, nil)
|
||||
}
|
||||
|
||||
// WithFields returns a new logger with additional fields.
|
||||
func (b *BufferedLogger) WithFields(fields map[string]any) Logger {
|
||||
return &bufferedLoggerWithFields{
|
||||
parent: b,
|
||||
fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLevel returns the current log level.
|
||||
func (b *BufferedLogger) GetLevel() Level {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.currentLevel
|
||||
}
|
||||
|
||||
// SetLevel sets the log level.
|
||||
func (b *BufferedLogger) SetLevel(level Level) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.currentLevel = level
|
||||
}
|
||||
|
||||
// Close discards all buffered entries without flushing them.
|
||||
// After calling Close, the BufferedLogger should not be used anymore.
|
||||
func (b *BufferedLogger) Close() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.buffer = nil
|
||||
}
|
||||
|
||||
// Flush replays all buffered entries to the target logger.
|
||||
// After calling Flush, the BufferedLogger should not be used anymore.
|
||||
// The caller should switch to using the target logger directly.
|
||||
func (b *BufferedLogger) Flush(targetLogger Logger) {
|
||||
if targetLogger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
targetLogger.SetLevel(b.currentLevel)
|
||||
entries := b.buffer
|
||||
b.buffer = nil
|
||||
b.mu.Unlock()
|
||||
|
||||
for _, entry := range entries {
|
||||
b.logToTarget(targetLogger, entry)
|
||||
}
|
||||
}
|
||||
|
||||
type bufferedLoggerWithFields struct {
|
||||
parent *BufferedLogger
|
||||
fields map[string]any
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) Debug(format string, args ...any) {
|
||||
b.parent.addToBuffer(Debug, format, args, b.fields)
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) Info(format string, args ...any) {
|
||||
b.parent.addToBuffer(Info, format, args, b.fields)
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) Warn(format string, args ...any) {
|
||||
b.parent.addToBuffer(Warn, format, args, b.fields)
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) Error(format string, args ...any) {
|
||||
b.parent.addToBuffer(Error, format, args, b.fields)
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) WithFields(fields map[string]any) Logger {
|
||||
merged := make(map[string]any, len(b.fields)+len(fields))
|
||||
maps.Copy(merged, b.fields)
|
||||
maps.Copy(merged, fields)
|
||||
return &bufferedLoggerWithFields{
|
||||
parent: b.parent,
|
||||
fields: merged,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) GetLevel() Level {
|
||||
return b.parent.GetLevel()
|
||||
}
|
||||
|
||||
func (b *bufferedLoggerWithFields) SetLevel(level Level) {
|
||||
b.parent.SetLevel(level)
|
||||
}
|
||||
+470
@@ -0,0 +1,470 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Level log level for Logger
|
||||
type Level uint8
|
||||
|
||||
const (
|
||||
// Error error log level
|
||||
Error Level = iota
|
||||
// Warn warn log level
|
||||
Warn
|
||||
// Info info log level
|
||||
Info
|
||||
// Debug debug log level
|
||||
Debug
|
||||
)
|
||||
|
||||
// Logger provides interface for OPA logger implementations
|
||||
type Logger interface {
|
||||
Debug(fmt string, a ...any)
|
||||
Info(fmt string, a ...any)
|
||||
Error(fmt string, a ...any)
|
||||
Warn(fmt string, a ...any)
|
||||
|
||||
WithFields(map[string]any) Logger
|
||||
|
||||
GetLevel() Level
|
||||
SetLevel(Level)
|
||||
}
|
||||
|
||||
// LoggerWithContext is an optional interface that Logger implementations
|
||||
// can implement to support extracting trace information from a context.
|
||||
// Use WithContext to call this method on a Logger if it is supported.
|
||||
type LoggerWithContext interface {
|
||||
WithContext(context.Context) Logger
|
||||
}
|
||||
|
||||
// WithContext returns a logger with context information if the logger
|
||||
// supports it (i.e., implements LoggerWithContext). Otherwise, the
|
||||
// logger is returned unchanged.
|
||||
func WithContext(logger Logger, ctx context.Context) Logger {
|
||||
if lc, ok := logger.(LoggerWithContext); ok {
|
||||
return lc.WithContext(ctx)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// StandardLogger is the default OPA logger implementation.
|
||||
type StandardLogger struct {
|
||||
logger *logrus.Logger
|
||||
fields map[string]any
|
||||
}
|
||||
|
||||
// New returns a new standard logger.
|
||||
func New() *StandardLogger {
|
||||
return &StandardLogger{
|
||||
logger: logrus.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the standard logger used throughout OPA.
|
||||
//
|
||||
// Deprecated. Do not rely on the global logger.
|
||||
func Get() *StandardLogger {
|
||||
return &StandardLogger{
|
||||
logger: logrus.StandardLogger(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetOutput sets the underlying logrus output.
|
||||
func (l *StandardLogger) SetOutput(w io.Writer) {
|
||||
l.logger.SetOutput(w)
|
||||
}
|
||||
|
||||
// SetFormatter sets the underlying logrus formatter.
|
||||
func (l *StandardLogger) SetFormatter(formatter logrus.Formatter) {
|
||||
l.logger.SetFormatter(formatter)
|
||||
}
|
||||
|
||||
// WithFields provides additional fields to include in log output
|
||||
func (l *StandardLogger) WithFields(fields map[string]any) Logger {
|
||||
cp := *l
|
||||
cp.fields = make(map[string]any)
|
||||
maps.Copy(cp.fields, l.fields)
|
||||
maps.Copy(cp.fields, fields)
|
||||
return &cp
|
||||
}
|
||||
|
||||
// getFields returns additional fields of this logger
|
||||
func (l *StandardLogger) getFields() map[string]any {
|
||||
return l.fields
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func (l *StandardLogger) SetLevel(level Level) {
|
||||
var logrusLevel logrus.Level
|
||||
switch level {
|
||||
case Error: // set logging level report Warn or higher (includes Error)
|
||||
logrusLevel = logrus.WarnLevel
|
||||
case Warn:
|
||||
logrusLevel = logrus.WarnLevel
|
||||
case Info:
|
||||
logrusLevel = logrus.InfoLevel
|
||||
case Debug:
|
||||
logrusLevel = logrus.DebugLevel
|
||||
default:
|
||||
l.Warn("unknown log level %v", level)
|
||||
logrusLevel = logrus.InfoLevel
|
||||
}
|
||||
|
||||
l.logger.SetLevel(logrusLevel)
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func (l *StandardLogger) GetLevel() Level {
|
||||
logrusLevel := l.logger.GetLevel()
|
||||
|
||||
var level Level
|
||||
switch logrusLevel {
|
||||
case logrus.WarnLevel:
|
||||
level = Error
|
||||
case logrus.InfoLevel:
|
||||
level = Info
|
||||
case logrus.DebugLevel:
|
||||
level = Debug
|
||||
default:
|
||||
l.Warn("unknown log level %v", logrusLevel)
|
||||
level = Info
|
||||
}
|
||||
|
||||
return level
|
||||
}
|
||||
|
||||
// Debug logs at debug level
|
||||
func (l *StandardLogger) Debug(fmt string, a ...any) {
|
||||
if len(a) == 0 {
|
||||
l.logger.WithFields(l.getFields()).Debug(fmt)
|
||||
return
|
||||
}
|
||||
l.logger.WithFields(l.getFields()).Debugf(fmt, a...)
|
||||
}
|
||||
|
||||
// Info logs at info level
|
||||
func (l *StandardLogger) Info(fmt string, a ...any) {
|
||||
if len(a) == 0 {
|
||||
l.logger.WithFields(l.getFields()).Info(fmt)
|
||||
return
|
||||
}
|
||||
l.logger.WithFields(l.getFields()).Infof(fmt, a...)
|
||||
}
|
||||
|
||||
// Error logs at error level
|
||||
func (l *StandardLogger) Error(fmt string, a ...any) {
|
||||
if len(a) == 0 {
|
||||
l.logger.WithFields(l.getFields()).Error(fmt)
|
||||
return
|
||||
}
|
||||
l.logger.WithFields(l.getFields()).Errorf(fmt, a...)
|
||||
}
|
||||
|
||||
// Warn logs at warn level
|
||||
func (l *StandardLogger) Warn(fmt string, a ...any) {
|
||||
if len(a) == 0 {
|
||||
l.logger.WithFields(l.getFields()).Warn(fmt)
|
||||
return
|
||||
}
|
||||
l.logger.WithFields(l.getFields()).Warnf(fmt, a...)
|
||||
}
|
||||
|
||||
// NoOpLogger logging implementation that does nothing
|
||||
type NoOpLogger struct {
|
||||
level Level
|
||||
fields map[string]any
|
||||
}
|
||||
|
||||
// NewNoOpLogger instantiates new NoOpLogger
|
||||
func NewNoOpLogger() *NoOpLogger {
|
||||
return &NoOpLogger{
|
||||
level: Info,
|
||||
}
|
||||
}
|
||||
|
||||
// WithFields provides additional fields to include in log output.
|
||||
// Implemented here primarily to be able to switch between implementations without loss of data.
|
||||
func (l *NoOpLogger) WithFields(fields map[string]any) Logger {
|
||||
cp := *l
|
||||
cp.fields = fields
|
||||
return &cp
|
||||
}
|
||||
|
||||
// Debug noop
|
||||
func (*NoOpLogger) Debug(string, ...any) {}
|
||||
|
||||
// Info noop
|
||||
func (*NoOpLogger) Info(string, ...any) {}
|
||||
|
||||
// Error noop
|
||||
func (*NoOpLogger) Error(string, ...any) {}
|
||||
|
||||
// Warn noop
|
||||
func (*NoOpLogger) Warn(string, ...any) {}
|
||||
|
||||
// SetLevel set log level
|
||||
func (l *NoOpLogger) SetLevel(level Level) {
|
||||
l.level = level
|
||||
}
|
||||
|
||||
// GetLevel get log level
|
||||
func (l *NoOpLogger) GetLevel() Level {
|
||||
return l.level
|
||||
}
|
||||
|
||||
type requestContextKey string
|
||||
|
||||
const reqCtxKey = requestContextKey("request-context-key")
|
||||
|
||||
// RequestContext represents the request context used to store data
|
||||
// related to the request that could be used on logs.
|
||||
type RequestContext struct {
|
||||
ClientAddr string
|
||||
ReqID uint64
|
||||
ReqMethod string
|
||||
ReqPath string
|
||||
HTTPRequestContext HTTPRequestContext
|
||||
}
|
||||
|
||||
type HTTPRequestContext struct {
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
// Fields adapts the RequestContext fields to logrus.Fields.
|
||||
func (rctx RequestContext) Fields() logrus.Fields {
|
||||
return logrus.Fields{
|
||||
"client_addr": rctx.ClientAddr,
|
||||
"req_id": rctx.ReqID,
|
||||
"req_method": rctx.ReqMethod,
|
||||
"req_path": rctx.ReqPath,
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext returns a copy of parent with an associated RequestContext.
|
||||
func NewContext(parent context.Context, val *RequestContext) context.Context {
|
||||
return context.WithValue(parent, reqCtxKey, val)
|
||||
}
|
||||
|
||||
// FromContext returns the RequestContext associated with ctx, if any.
|
||||
func FromContext(ctx context.Context) (*RequestContext, bool) {
|
||||
requestContext, ok := ctx.Value(reqCtxKey).(*RequestContext)
|
||||
return requestContext, ok
|
||||
}
|
||||
|
||||
const httpReqCtxKey = requestContextKey("http-request-context-key")
|
||||
|
||||
func WithHTTPRequestContext(parent context.Context, val *HTTPRequestContext) context.Context {
|
||||
return context.WithValue(parent, httpReqCtxKey, val)
|
||||
}
|
||||
|
||||
func HTTPRequestContextFromContext(ctx context.Context) (*HTTPRequestContext, bool) {
|
||||
requestContext, ok := ctx.Value(httpReqCtxKey).(*HTTPRequestContext)
|
||||
return requestContext, ok
|
||||
}
|
||||
|
||||
const decisionCtxKey = requestContextKey("decision_id")
|
||||
|
||||
func WithDecisionID(parent context.Context, id string) context.Context {
|
||||
return context.WithValue(parent, decisionCtxKey, id)
|
||||
}
|
||||
|
||||
func DecisionIDFromContext(ctx context.Context) (string, bool) {
|
||||
s, ok := ctx.Value(decisionCtxKey).(string)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
const batchDecisionCtxKey = requestContextKey("batch_decision_id")
|
||||
|
||||
func WithBatchDecisionID(parent context.Context, id string) context.Context {
|
||||
return context.WithValue(parent, batchDecisionCtxKey, id)
|
||||
}
|
||||
|
||||
func BatchDecisionIDFromContext(ctx context.Context) (string, bool) {
|
||||
s, ok := ctx.Value(batchDecisionCtxKey).(string)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
// SlogHandler adapts a Logger to slog.Handler interface
|
||||
type SlogHandler struct {
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewSlogHandler creates an slog.Handler from a Logger
|
||||
func NewSlogHandler(logger Logger) slog.Handler {
|
||||
return &SlogHandler{logger: logger}
|
||||
}
|
||||
|
||||
func (h *SlogHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
lvl := h.logger.GetLevel()
|
||||
switch level {
|
||||
case slog.LevelDebug:
|
||||
return lvl >= Debug
|
||||
case slog.LevelInfo:
|
||||
return lvl >= Info
|
||||
case slog.LevelWarn:
|
||||
return lvl >= Warn
|
||||
case slog.LevelError:
|
||||
return lvl >= Error
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *SlogHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||
attrs := make(map[string]any)
|
||||
record.Attrs(func(a slog.Attr) bool {
|
||||
attrs[a.Key] = a.Value.Any()
|
||||
return true
|
||||
})
|
||||
|
||||
logger := h.logger
|
||||
if len(attrs) > 0 {
|
||||
logger = logger.WithFields(attrs)
|
||||
}
|
||||
if ctx != nil {
|
||||
logger = WithContext(logger, ctx)
|
||||
}
|
||||
|
||||
msg := record.Message
|
||||
switch record.Level {
|
||||
case slog.LevelDebug:
|
||||
logger.Debug(msg)
|
||||
case slog.LevelInfo:
|
||||
logger.Info(msg)
|
||||
case slog.LevelWarn:
|
||||
logger.Warn(msg)
|
||||
case slog.LevelError:
|
||||
logger.Error(msg)
|
||||
default:
|
||||
logger.Info(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
fields := make(map[string]any, len(attrs))
|
||||
for _, a := range attrs {
|
||||
fields[a.Key] = a.Value.Any()
|
||||
}
|
||||
return &SlogHandler{
|
||||
logger: h.logger.WithFields(fields),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SlogHandler) WithGroup(name string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
// loggerFromSlogHandler wraps an slog.Handler to implement the Logger interface
|
||||
type loggerFromSlogHandler struct {
|
||||
handler slog.Handler
|
||||
level Level
|
||||
fields map[string]any
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var _ LoggerWithContext = (*loggerFromSlogHandler)(nil)
|
||||
|
||||
// NewLoggerFromSlogHandler creates a Logger from an slog.Handler
|
||||
func NewLoggerFromSlogHandler(handler slog.Handler, level Level) Logger {
|
||||
return &loggerFromSlogHandler{
|
||||
handler: handler,
|
||||
level: level,
|
||||
fields: make(map[string]any),
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) log(level slog.Level, format string, args ...any) {
|
||||
if !l.handler.Enabled(l.ctx, level) {
|
||||
return
|
||||
}
|
||||
msg := format
|
||||
if len(args) > 0 {
|
||||
msg = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
attrs := make([]slog.Attr, 0, len(l.fields))
|
||||
for k, v := range l.fields {
|
||||
var attr slog.Attr
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
attr = slog.String(k, val)
|
||||
case int:
|
||||
attr = slog.Int(k, val)
|
||||
case int64:
|
||||
attr = slog.Int64(k, val)
|
||||
case uint64:
|
||||
attr = slog.Uint64(k, val)
|
||||
case float64:
|
||||
attr = slog.Float64(k, val)
|
||||
case bool:
|
||||
attr = slog.Bool(k, val)
|
||||
case time.Time:
|
||||
attr = slog.Time(k, val)
|
||||
case time.Duration:
|
||||
attr = slog.Duration(k, val)
|
||||
default:
|
||||
attr = slog.Any(k, v)
|
||||
}
|
||||
attrs = append(attrs, attr)
|
||||
}
|
||||
|
||||
record := slog.NewRecord(time.Now(), level, msg, 0)
|
||||
record.AddAttrs(attrs...)
|
||||
|
||||
_ = l.handler.Handle(l.ctx, record)
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) Debug(format string, args ...any) {
|
||||
l.log(slog.LevelDebug, format, args...)
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) Info(format string, args ...any) {
|
||||
l.log(slog.LevelInfo, format, args...)
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) Warn(format string, args ...any) {
|
||||
l.log(slog.LevelWarn, format, args...)
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) Error(format string, args ...any) {
|
||||
l.log(slog.LevelError, format, args...)
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) WithFields(fields map[string]any) Logger {
|
||||
merged := make(map[string]any, len(l.fields)+len(fields))
|
||||
maps.Copy(merged, l.fields)
|
||||
maps.Copy(merged, fields)
|
||||
return &loggerFromSlogHandler{
|
||||
handler: l.handler,
|
||||
level: l.level,
|
||||
fields: merged,
|
||||
ctx: l.ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) WithContext(ctx context.Context) Logger {
|
||||
return &loggerFromSlogHandler{
|
||||
handler: l.handler,
|
||||
level: l.level,
|
||||
fields: l.fields,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) GetLevel() Level {
|
||||
return l.level
|
||||
}
|
||||
|
||||
func (l *loggerFromSlogHandler) SetLevel(level Level) {
|
||||
l.level = level
|
||||
}
|
||||
Reference in New Issue
Block a user