first commit
This commit is contained in:
187
core/logger.go
Normal file
187
core/logger.go
Normal file
@@ -0,0 +1,187 @@
|
||||
// Package bifrost provides the core implementation of the Bifrost system.
|
||||
package bifrost
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
schemas "github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var zerologOnce sync.Once
|
||||
|
||||
// DefaultLogger implements the Logger interface with stdout/stderr printing.
|
||||
// It provides a simple logging implementation that writes to standard output
|
||||
// and error streams with formatted timestamps and log levels.
|
||||
// It is used as the default logger if no logger is provided in the BifrostConfig.
|
||||
type DefaultLogger struct {
|
||||
stderrLogger zerolog.Logger
|
||||
stdoutLogger zerolog.Logger
|
||||
}
|
||||
|
||||
// toZerologLevel converts a Bifrost log level to a Zerolog level.
|
||||
func toZerologLevel(l schemas.LogLevel) zerolog.Level {
|
||||
switch l {
|
||||
case schemas.LogLevelDebug:
|
||||
return zerolog.DebugLevel
|
||||
case schemas.LogLevelInfo:
|
||||
return zerolog.InfoLevel
|
||||
case schemas.LogLevelWarn:
|
||||
return zerolog.WarnLevel
|
||||
case schemas.LogLevelError:
|
||||
return zerolog.ErrorLevel
|
||||
default:
|
||||
return zerolog.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultLogger creates a new DefaultLogger instance with the specified log level.
|
||||
// The log level determines which messages will be output based on their severity.
|
||||
func NewDefaultLogger(level schemas.LogLevel) *DefaultLogger {
|
||||
zerolog.SetGlobalLevel(toZerologLevel(level))
|
||||
zerologOnce.Do(func() {
|
||||
zerolog.DisableSampling(true)
|
||||
zerolog.TimeFieldFormat = time.RFC3339
|
||||
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
|
||||
})
|
||||
return &DefaultLogger{
|
||||
stderrLogger: zerolog.New(os.Stderr).With().Timestamp().Logger(),
|
||||
stdoutLogger: zerolog.New(os.Stdout).With().Timestamp().Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
// Debug logs a debug level message to stdout.
|
||||
// Messages are only output if the logger's level is set to LogLevelDebug.
|
||||
func (logger *DefaultLogger) Debug(msg string, args ...any) {
|
||||
logger.stdoutLogger.Debug().Msgf(msg, args...)
|
||||
}
|
||||
|
||||
// Info logs an info level message to stdout.
|
||||
// Messages are output if the logger's level is LogLevelDebug or LogLevelInfo.
|
||||
func (logger *DefaultLogger) Info(msg string, args ...any) {
|
||||
logger.stdoutLogger.Info().Msgf(msg, args...)
|
||||
}
|
||||
|
||||
// Warn logs a warning level message to stdout.
|
||||
// Messages are output if the logger's level is LogLevelDebug, LogLevelInfo, or LogLevelWarn.
|
||||
func (logger *DefaultLogger) Warn(msg string, args ...any) {
|
||||
logger.stdoutLogger.Warn().Msgf(msg, args...)
|
||||
}
|
||||
|
||||
// Error logs an error level message to stderr.
|
||||
// Error messages are always output regardless of the logger's level.
|
||||
func (logger *DefaultLogger) Error(msg string, args ...any) {
|
||||
logger.stderrLogger.Error().Msgf(msg, args...)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal-level message to stderr.
|
||||
// Fatal messages are always output regardless of the logger's level.
|
||||
func (logger *DefaultLogger) Fatal(msg string, args ...any) {
|
||||
// Check if any of the args is an error and exit with non-zero code if found
|
||||
var errToPass error
|
||||
for i, arg := range args {
|
||||
if err, ok := arg.(error); ok && err != nil {
|
||||
errToPass = err
|
||||
// remove from args
|
||||
args = append(args[:i], args[i+1:]...)
|
||||
}
|
||||
}
|
||||
if errToPass != nil {
|
||||
logger.stderrLogger.Fatal().Msgf(msg, errToPass)
|
||||
} else {
|
||||
logger.stderrLogger.Fatal().Msgf(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level for the logger.
|
||||
// This determines which messages will be output based on their severity.
|
||||
func (logger *DefaultLogger) SetLevel(level schemas.LogLevel) {
|
||||
zerolog.SetGlobalLevel(toZerologLevel(level))
|
||||
}
|
||||
|
||||
// SetOutputType sets the output type for the logger.
|
||||
// This determines the format of the log output.
|
||||
// If the output type is unknown, it defaults to JSON
|
||||
func (logger *DefaultLogger) SetOutputType(outputType schemas.LoggerOutputType) {
|
||||
switch outputType {
|
||||
case schemas.LoggerOutputTypePretty:
|
||||
logger.stdoutLogger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
|
||||
logger.stderrLogger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger()
|
||||
case schemas.LoggerOutputTypeJSON:
|
||||
logger.stdoutLogger = zerolog.New(os.Stdout).With().Timestamp().Logger()
|
||||
logger.stderrLogger = zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
default:
|
||||
logger.stderrLogger.Warn().
|
||||
Str("outputType", string(outputType)).
|
||||
Msg("unknown logger output type; defaulting to JSON")
|
||||
logger.stdoutLogger = zerolog.New(os.Stdout).With().Timestamp().Logger()
|
||||
}
|
||||
}
|
||||
|
||||
// NoOpLogger is a no-op implementation of schemas.Logger.
|
||||
type NoOpLogger struct{}
|
||||
|
||||
// NewNoOpLogger creates a new NoOpLogger instance.
|
||||
func NewNoOpLogger() schemas.Logger {
|
||||
return &NoOpLogger{}
|
||||
}
|
||||
|
||||
func (l *NoOpLogger) Debug(string, ...any) {}
|
||||
func (l *NoOpLogger) Info(string, ...any) {}
|
||||
func (l *NoOpLogger) Warn(string, ...any) {}
|
||||
func (l *NoOpLogger) Error(string, ...any) {}
|
||||
func (l *NoOpLogger) Fatal(string, ...any) {}
|
||||
func (l *NoOpLogger) SetLevel(schemas.LogLevel) {}
|
||||
func (l *NoOpLogger) SetOutputType(schemas.LoggerOutputType) {}
|
||||
func (l *NoOpLogger) LogHTTPRequest(schemas.LogLevel, string) schemas.LogEventBuilder {
|
||||
return schemas.NoopLogEvent
|
||||
}
|
||||
|
||||
// zerologEventBuilder wraps a zerolog.Event to implement schemas.LogEventBuilder.
|
||||
type zerologEventBuilder struct {
|
||||
event *zerolog.Event
|
||||
msg string
|
||||
}
|
||||
|
||||
func (b *zerologEventBuilder) Str(key, val string) schemas.LogEventBuilder {
|
||||
b.event = b.event.Str(key, val)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *zerologEventBuilder) Int(key string, val int) schemas.LogEventBuilder {
|
||||
b.event = b.event.Int(key, val)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *zerologEventBuilder) Int64(key string, val int64) schemas.LogEventBuilder {
|
||||
b.event = b.event.Int64(key, val)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *zerologEventBuilder) Send() {
|
||||
b.event.Msg(b.msg)
|
||||
}
|
||||
|
||||
// LogHTTPRequest returns a LogEventBuilder for structured HTTP access logging.
|
||||
// We are exposing the zerolog loggers directly to allow for more flexibility in logging and also to reduce the number of allocations we do in the logger.
|
||||
func (logger *DefaultLogger) LogHTTPRequest(level schemas.LogLevel, msg string) schemas.LogEventBuilder {
|
||||
l := logger.stdoutLogger
|
||||
if level == schemas.LogLevelError {
|
||||
l = logger.stderrLogger
|
||||
}
|
||||
var event *zerolog.Event
|
||||
switch level {
|
||||
case schemas.LogLevelDebug:
|
||||
event = l.Debug()
|
||||
case schemas.LogLevelWarn:
|
||||
event = l.Warn()
|
||||
case schemas.LogLevelError:
|
||||
event = l.Error()
|
||||
default:
|
||||
event = l.Info()
|
||||
}
|
||||
return &zerologEventBuilder{event: event, msg: msg}
|
||||
}
|
||||
Reference in New Issue
Block a user