Files
2026-04-14 10:31:56 +02:00

82 lines
1.9 KiB
Go

package telemetry
import (
"context"
"encoding/json"
"time"
otellog "go.opentelemetry.io/otel/log"
sdklog "go.opentelemetry.io/otel/sdk/log"
)
// LogBridge implements io.Writer and forwards zerolog JSON lines to the
// OTel LoggerProvider. It is used as an extra writer in zerolog's MultiWriter
// so that logs go to both file and OTLP.
type LogBridge struct {
provider *sdklog.LoggerProvider
}
func (b *LogBridge) Write(p []byte) (n int, err error) {
var entry map[string]interface{}
if err := json.Unmarshal(p, &entry); err != nil {
return len(p), nil // skip malformed lines
}
logger := b.provider.Logger("zerolog")
var rec otellog.Record
rec.SetTimestamp(time.Now())
if msg, ok := entry["message"].(string); ok {
rec.SetBody(otellog.StringValue(msg))
}
if lvl, ok := entry["level"].(string); ok {
rec.SetSeverity(mapSeverity(lvl))
}
// Forward all fields as attributes
attrs := make([]otellog.KeyValue, 0, len(entry))
for k, v := range entry {
if k == "message" || k == "level" || k == "time" {
continue
}
switch val := v.(type) {
case string:
attrs = append(attrs, otellog.String(k, val))
case float64:
attrs = append(attrs, otellog.Float64(k, val))
case bool:
attrs = append(attrs, otellog.Bool(k, val))
default:
b, _ := json.Marshal(val)
attrs = append(attrs, otellog.String(k, string(b)))
}
}
rec.AddAttributes(attrs...)
logger.Emit(context.Background(), rec)
return len(p), nil
}
func mapSeverity(level string) otellog.Severity {
switch level {
case "trace":
return otellog.SeverityTrace
case "debug":
return otellog.SeverityDebug
case "info":
return otellog.SeverityInfo
case "warn", "warning":
return otellog.SeverityWarn
case "error":
return otellog.SeverityError
case "fatal":
return otellog.SeverityFatal
case "panic":
return otellog.SeverityFatal2
default:
return otellog.SeverityInfo
}
}