85 lines
2.0 KiB
Go
85 lines
2.0 KiB
Go
// Package logging configures the gateway structured logger and provides
|
|
// context-aware helpers for attaching OpenTelemetry trace identifiers.
|
|
package logging
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"galaxy/gateway/internal/config"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
// New constructs the process-wide JSON logger from cfg.
|
|
func New(cfg config.LoggingConfig) (*zap.Logger, error) {
|
|
level := zap.NewAtomicLevel()
|
|
if err := level.UnmarshalText([]byte(strings.TrimSpace(cfg.Level))); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
zapCfg := zap.NewProductionConfig()
|
|
zapCfg.Level = level
|
|
zapCfg.Sampling = nil
|
|
zapCfg.Encoding = "json"
|
|
zapCfg.EncoderConfig.TimeKey = "timestamp"
|
|
zapCfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
zapCfg.OutputPaths = []string{"stdout"}
|
|
zapCfg.ErrorOutputPaths = []string{"stderr"}
|
|
|
|
return zapCfg.Build()
|
|
}
|
|
|
|
// TraceFieldsFromContext returns zap fields for the active OpenTelemetry span
|
|
// when ctx carries a valid span context.
|
|
func TraceFieldsFromContext(ctx context.Context) []zap.Field {
|
|
if ctx == nil {
|
|
return nil
|
|
}
|
|
|
|
spanContext := trace.SpanContextFromContext(ctx)
|
|
if !spanContext.IsValid() {
|
|
return nil
|
|
}
|
|
|
|
return []zap.Field{
|
|
zap.String("otel_trace_id", spanContext.TraceID().String()),
|
|
zap.String("otel_span_id", spanContext.SpanID().String()),
|
|
}
|
|
}
|
|
|
|
// Sync flushes logger and ignores the benign stdout or stderr sync errors
|
|
// commonly returned by containerized or redirected process outputs.
|
|
func Sync(logger *zap.Logger) error {
|
|
if logger == nil {
|
|
return nil
|
|
}
|
|
|
|
err := logger.Sync()
|
|
if err == nil || isIgnorableSyncError(err) {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func isIgnorableSyncError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
message := strings.ToLower(err.Error())
|
|
switch {
|
|
case strings.Contains(message, "invalid argument"):
|
|
return true
|
|
case strings.Contains(message, "bad file descriptor"):
|
|
return true
|
|
case strings.Contains(message, "inappropriate ioctl for device"):
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|