mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-28 11:15:32 -06:00
a8ac7b38bb
It would be useful if users had the ability to enhance the decision log with info from the incoming HTTP request such as headers. This change allows users to configure headers whose values if present in the incoming HTTP request would be surfaced via the decision log. This can be extended in the future to include more context from the request. Fixes: #6693 Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
256 lines
5.9 KiB
Go
256 lines
5.9 KiB
Go
package logging
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
|
|
"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 ...interface{})
|
|
Info(fmt string, a ...interface{})
|
|
Error(fmt string, a ...interface{})
|
|
Warn(fmt string, a ...interface{})
|
|
|
|
WithFields(map[string]interface{}) Logger
|
|
|
|
GetLevel() Level
|
|
SetLevel(Level)
|
|
}
|
|
|
|
// StandardLogger is the default OPA logger implementation.
|
|
type StandardLogger struct {
|
|
logger *logrus.Logger
|
|
fields map[string]interface{}
|
|
}
|
|
|
|
// 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]interface{}) Logger {
|
|
cp := *l
|
|
cp.fields = make(map[string]interface{})
|
|
for k, v := range l.fields {
|
|
cp.fields[k] = v
|
|
}
|
|
for k, v := range fields {
|
|
cp.fields[k] = v
|
|
}
|
|
return &cp
|
|
}
|
|
|
|
// getFields returns additional fields of this logger
|
|
func (l *StandardLogger) getFields() map[string]interface{} {
|
|
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 ...interface{}) {
|
|
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 ...interface{}) {
|
|
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 ...interface{}) {
|
|
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 ...interface{}) {
|
|
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]interface{}
|
|
}
|
|
|
|
// 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]interface{}) Logger {
|
|
cp := *l
|
|
cp.fields = fields
|
|
return &cp
|
|
}
|
|
|
|
// Debug noop
|
|
func (*NoOpLogger) Debug(string, ...interface{}) {}
|
|
|
|
// Info noop
|
|
func (*NoOpLogger) Info(string, ...interface{}) {}
|
|
|
|
// Error noop
|
|
func (*NoOpLogger) Error(string, ...interface{}) {}
|
|
|
|
// Warn noop
|
|
func (*NoOpLogger) Warn(string, ...interface{}) {}
|
|
|
|
// 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 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
|
|
}
|