60 lines
1.7 KiB
Go
60 lines
1.7 KiB
Go
// Package observability provides logging and metrics instrumentation.
|
|
package observability
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
type LogLevel string
|
|
|
|
const (
|
|
LogLevelInfo LogLevel = "info"
|
|
LogLevelWarn LogLevel = "warn"
|
|
LogLevelError LogLevel = "error"
|
|
)
|
|
|
|
type LogEvent struct {
|
|
TS string `json:"ts"`
|
|
Level LogLevel `json:"level"`
|
|
Event string `json:"event"`
|
|
Message string `json:"message,omitempty"`
|
|
Fields map[string]any `json:"fields,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Component string `json:"component,omitempty"`
|
|
}
|
|
|
|
func LogJSON(level LogLevel, event string, component string, message string, fields map[string]any, err error) {
|
|
errorValue := ""
|
|
if err != nil {
|
|
errorValue = err.Error()
|
|
}
|
|
|
|
entry := LogEvent{
|
|
TS: time.Now().UTC().Format(time.RFC3339Nano),
|
|
Level: level,
|
|
Event: event,
|
|
Message: message,
|
|
Fields: fields,
|
|
Error: errorValue,
|
|
Component: component,
|
|
}
|
|
|
|
// Best-effort. If encoding fails, fall back to a minimal line.
|
|
bytes, marshalErr := json.Marshal(entry)
|
|
if marshalErr != nil {
|
|
// Keep output JSON-only even on failures by constructing a minimal entry.
|
|
// Marshal individual strings to ensure proper escaping.
|
|
tsBytes, _ := json.Marshal(time.Now().UTC().Format(time.RFC3339Nano))
|
|
levelBytes, _ := json.Marshal(level)
|
|
eventBytes, _ := json.Marshal("log_marshal_failed")
|
|
componentBytes, _ := json.Marshal(component)
|
|
errBytes, _ := json.Marshal(marshalErr.Error())
|
|
log.Printf(`{"ts":%s,"level":%s,"event":%s,"component":%s,"error":%s}`, tsBytes, levelBytes, eventBytes, componentBytes, errBytes)
|
|
return
|
|
}
|
|
|
|
log.Print(string(bytes))
|
|
}
|