mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 02:51:34 +00:00
This commit introduces a comprehensive observability stack to the application, addressing a key objective from the TODO list. The following features have been implemented: - **Centralized Logging:** Replaced the existing custom logger with `zerolog` for structured, leveled, and performant logging. The logger is configured to output human-friendly console logs in development and JSON logs in production. - **Distributed Tracing:** Integrated OpenTelemetry to provide distributed tracing capabilities. A new middleware has been added to create spans for all incoming HTTP requests, and the trace context is propagated. - **Prometheus Metrics:** Added Prometheus metrics to monitor HTTP request latency and total request counts. A new `/metrics` endpoint is exposed on port 9090 to serve these metrics. - **Request ID:** Implemented a middleware to add a unique request ID to every incoming request and response, improving traceability. The new observability components are encapsulated in the `internal/observability` package, and the existing `internal/platform/log` package has been refactored to be a backward-compatible wrapper around the new logger. The main application entry point (`cmd/api/main.go`) has been updated to initialize and gracefully shut down the new observability components.
239 lines
6.1 KiB
Go
239 lines
6.1 KiB
Go
package log
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"tercul/internal/observability"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// LogLevel represents the severity level of a log message.
|
|
type LogLevel int
|
|
|
|
const (
|
|
// DebugLevel for detailed troubleshooting.
|
|
DebugLevel LogLevel = iota
|
|
// InfoLevel for general operational information.
|
|
InfoLevel
|
|
// WarnLevel for potentially harmful situations.
|
|
WarnLevel
|
|
// ErrorLevel for error events that might still allow the application to continue.
|
|
ErrorLevel
|
|
// FatalLevel for severe error events that will lead the application to abort.
|
|
FatalLevel
|
|
)
|
|
|
|
// Field represents a key-value pair for structured logging.
|
|
type Field struct {
|
|
Key string
|
|
Value interface{}
|
|
}
|
|
|
|
// F creates a new Field.
|
|
func F(key string, value interface{}) Field {
|
|
return Field{Key: key, Value: value}
|
|
}
|
|
|
|
// Logger provides structured logging capabilities.
|
|
type Logger struct {
|
|
*observability.Logger
|
|
}
|
|
|
|
var defaultLogger = &Logger{observability.NewLogger("tercul", "development")}
|
|
|
|
// Init re-initializes the default logger. This is useful for applications
|
|
// that need to configure the logger with dynamic values.
|
|
func Init(serviceName, environment string) {
|
|
defaultLogger = &Logger{observability.NewLogger(serviceName, environment)}
|
|
}
|
|
|
|
// SetDefaultLevel sets the log level for the default logger.
|
|
func SetDefaultLevel(level LogLevel) {
|
|
var zlevel zerolog.Level
|
|
switch level {
|
|
case DebugLevel:
|
|
zlevel = zerolog.DebugLevel
|
|
case InfoLevel:
|
|
zlevel = zerolog.InfoLevel
|
|
case WarnLevel:
|
|
zlevel = zerolog.WarnLevel
|
|
case ErrorLevel:
|
|
zlevel = zerolog.ErrorLevel
|
|
case FatalLevel:
|
|
zlevel = zerolog.FatalLevel
|
|
default:
|
|
zlevel = zerolog.InfoLevel
|
|
}
|
|
zerolog.SetGlobalLevel(zlevel)
|
|
}
|
|
|
|
func log(level LogLevel, msg string, fields ...Field) {
|
|
var event *zerolog.Event
|
|
// Access the embedded observability.Logger to get to zerolog's methods.
|
|
zlog := defaultLogger.Logger
|
|
switch level {
|
|
case DebugLevel:
|
|
event = zlog.Debug()
|
|
case InfoLevel:
|
|
event = zlog.Info()
|
|
case WarnLevel:
|
|
event = zlog.Warn()
|
|
case ErrorLevel:
|
|
event = zlog.Error()
|
|
case FatalLevel:
|
|
event = zlog.Fatal()
|
|
default:
|
|
event = zlog.Info()
|
|
}
|
|
|
|
for _, f := range fields {
|
|
event.Interface(f.Key, f.Value)
|
|
}
|
|
event.Msg(msg)
|
|
}
|
|
|
|
// LogDebug logs a message at debug level using the default logger.
|
|
func LogDebug(msg string, fields ...Field) {
|
|
log(DebugLevel, msg, fields...)
|
|
}
|
|
|
|
// LogInfo logs a message at info level using the default logger.
|
|
func LogInfo(msg string, fields ...Field) {
|
|
log(InfoLevel, msg, fields...)
|
|
}
|
|
|
|
// LogWarn logs a message at warn level using the default logger.
|
|
func LogWarn(msg string, fields ...Field) {
|
|
log(WarnLevel, msg, fields...)
|
|
}
|
|
|
|
// LogError logs a message at error level using the default logger.
|
|
func LogError(msg string, fields ...Field) {
|
|
log(ErrorLevel, msg, fields...)
|
|
}
|
|
|
|
// LogFatal logs a message at fatal level using the default logger and then calls os.Exit(1).
|
|
func LogFatal(msg string, fields ...Field) {
|
|
log(FatalLevel, msg, fields...)
|
|
}
|
|
|
|
// WithFields returns a new logger with the given fields added using the default logger.
|
|
func WithFields(fields ...Field) *Logger {
|
|
sublogger := defaultLogger.With().Logger()
|
|
for _, f := range fields {
|
|
sublogger = sublogger.With().Interface(f.Key, f.Value).Logger()
|
|
}
|
|
return &Logger{&observability.Logger{&sublogger}}
|
|
}
|
|
|
|
// WithContext returns a new logger with the given context added using the default logger.
|
|
func WithContext(ctx context.Context) *Logger {
|
|
return &Logger{defaultLogger.Ctx(ctx)}
|
|
}
|
|
|
|
// The following functions are kept for compatibility but are now simplified or deprecated.
|
|
|
|
// SetDefaultLogger is deprecated. Use Init.
|
|
func SetDefaultLogger(logger *Logger) {
|
|
// Deprecated: Logger is now initialized via Init.
|
|
}
|
|
|
|
// String returns the string representation of the log level.
|
|
func (l LogLevel) String() string {
|
|
switch l {
|
|
case DebugLevel:
|
|
return "DEBUG"
|
|
case InfoLevel:
|
|
return "INFO"
|
|
case WarnLevel:
|
|
return "WARN"
|
|
case ErrorLevel:
|
|
return "ERROR"
|
|
case FatalLevel:
|
|
return "FATAL"
|
|
default:
|
|
return "UNKNOWN"
|
|
}
|
|
}
|
|
|
|
// Debug logs a message at debug level.
|
|
func (l *Logger) Debug(msg string, fields ...Field) {
|
|
l.log(DebugLevel, msg, fields...)
|
|
}
|
|
|
|
// Info logs a message at info level.
|
|
func (l *Logger) Info(msg string, fields ...Field) {
|
|
l.log(InfoLevel, msg, fields...)
|
|
}
|
|
|
|
// Warn logs a message at warn level.
|
|
func (l *Logger) Warn(msg string, fields ...Field) {
|
|
l.log(WarnLevel, msg, fields...)
|
|
}
|
|
|
|
// Error logs a message at error level.
|
|
func (l *Logger) Error(msg string, fields ...Field) {
|
|
l.log(ErrorLevel, msg, fields...)
|
|
}
|
|
|
|
// Fatal logs a message at fatal level and then calls os.Exit(1).
|
|
func (l *Logger) Fatal(msg string, fields ...Field) {
|
|
l.log(FatalLevel, msg, fields...)
|
|
}
|
|
|
|
func (l *Logger) log(level LogLevel, msg string, fields ...Field) {
|
|
var event *zerolog.Event
|
|
switch level {
|
|
case DebugLevel:
|
|
event = l.Logger.Debug()
|
|
case InfoLevel:
|
|
event = l.Logger.Info()
|
|
case WarnLevel:
|
|
event = l.Logger.Warn()
|
|
case ErrorLevel:
|
|
event = l.Logger.Error()
|
|
case FatalLevel:
|
|
event = l.Logger.Fatal()
|
|
default:
|
|
event = l.Logger.Info()
|
|
}
|
|
|
|
for _, f := range fields {
|
|
event.Interface(f.Key, f.Value)
|
|
}
|
|
event.Msg(msg)
|
|
}
|
|
|
|
// WithFields returns a new logger with the given fields added.
|
|
func (l *Logger) WithFields(fields ...Field) *Logger {
|
|
sublogger := l.With().Logger()
|
|
for _, f := range fields {
|
|
sublogger = sublogger.With().Interface(f.Key, f.Value).Logger()
|
|
}
|
|
return &Logger{&observability.Logger{&sublogger}}
|
|
}
|
|
|
|
func (l *Logger) WithContext(ctx map[string]interface{}) *Logger {
|
|
// To maintain compatibility with the old API, we will convert the map to a context.
|
|
// This is not ideal and should be refactored in the future.
|
|
zlog := l.Logger.With().Logger()
|
|
for k, v := range ctx {
|
|
zlog = zlog.With().Interface(k, v).Logger()
|
|
}
|
|
return &Logger{&observability.Logger{&zlog}}
|
|
}
|
|
|
|
func (l *Logger) SetLevel(level LogLevel) {
|
|
// This now controls the global log level.
|
|
SetDefaultLevel(level)
|
|
}
|
|
|
|
// Fmt versions for simple string formatting
|
|
func LogInfof(format string, v ...interface{}) {
|
|
log(InfoLevel, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
func LogErrorf(format string, v ...interface{}) {
|
|
log(ErrorLevel, fmt.Sprintf(format, v...))
|
|
} |