2025-11-17 11:47:23 +08:00
|
|
|
package log
|
2025-11-16 09:15:37 +08:00
|
|
|
|
|
|
|
|
import "go.uber.org/zap"
|
|
|
|
|
|
|
|
|
|
// ErrorType represents a coarse-grained categorisation of errors for logging and alerting.
|
|
|
|
|
// Typical values:
|
|
|
|
|
// - business: validation failures, domain rule violations, payload issues.
|
|
|
|
|
// - transient: network / DB / NATS glitches that may succeed on retry.
|
|
|
|
|
// - fatal: programming bugs, schema mismatches, or conditions that require operator action.
|
|
|
|
|
type ErrorType string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
ErrorTypeBusiness ErrorType = "business"
|
|
|
|
|
ErrorTypeTransient ErrorType = "transient"
|
|
|
|
|
ErrorTypeFatal ErrorType = "fatal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Canonical logging field names for structured logs produced by the CAATSM
|
|
|
|
|
// receiver. Using constants avoids scattering magic strings and keeps log
|
|
|
|
|
// analysis queries stable over time.
|
|
|
|
|
const (
|
|
|
|
|
FieldService = "service"
|
|
|
|
|
FieldTransportMsgID = "transport_msg_id"
|
|
|
|
|
FieldTelegramMsgID = "telegram_message_id"
|
|
|
|
|
FieldCategory = "category"
|
|
|
|
|
FieldStream = "stream"
|
|
|
|
|
FieldConsumer = "consumer"
|
|
|
|
|
FieldSubject = "subject"
|
|
|
|
|
FieldNATSSequence = "nats_sequence"
|
|
|
|
|
FieldRequestID = "request_id"
|
|
|
|
|
FieldTraceID = "trace_id"
|
|
|
|
|
FieldErrorType = "error_type"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MessageFields captures the common structured logging contract for message-processing logs.
|
|
|
|
|
// All fields are optional; empty values will simply be skipped.
|
|
|
|
|
type MessageFields struct {
|
|
|
|
|
// Identifiers
|
|
|
|
|
Service string
|
|
|
|
|
TransportMsgID string
|
|
|
|
|
BusinessMsgID string
|
|
|
|
|
Category string
|
|
|
|
|
|
|
|
|
|
// NATS / JetStream context
|
|
|
|
|
Stream string
|
|
|
|
|
Consumer string
|
|
|
|
|
Subject string
|
|
|
|
|
JSSequence uint64
|
|
|
|
|
|
|
|
|
|
// Correlation / tracing
|
|
|
|
|
RequestID string
|
|
|
|
|
TraceID string
|
|
|
|
|
|
|
|
|
|
// Error classification
|
|
|
|
|
ErrorType ErrorType
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithMessageContext returns a logger pre-populated with the structured fields defined in MessageFields.
|
|
|
|
|
// This is the primary entry point for enforcing the logging contract in the codebase.
|
|
|
|
|
func WithMessageContext(logger *zap.Logger, mf MessageFields) *zap.Logger {
|
|
|
|
|
if logger == nil {
|
|
|
|
|
return zap.NewNop()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fields := make([]zap.Field, 0, 12)
|
|
|
|
|
|
|
|
|
|
if mf.Service != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldService, mf.Service))
|
|
|
|
|
}
|
|
|
|
|
if mf.TransportMsgID != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldTransportMsgID, mf.TransportMsgID))
|
|
|
|
|
}
|
|
|
|
|
if mf.BusinessMsgID != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldTelegramMsgID, mf.BusinessMsgID))
|
|
|
|
|
}
|
|
|
|
|
if mf.Category != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldCategory, mf.Category))
|
|
|
|
|
}
|
|
|
|
|
if mf.Stream != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldStream, mf.Stream))
|
|
|
|
|
}
|
|
|
|
|
if mf.Consumer != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldConsumer, mf.Consumer))
|
|
|
|
|
}
|
|
|
|
|
if mf.Subject != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldSubject, mf.Subject))
|
|
|
|
|
}
|
|
|
|
|
if mf.JSSequence > 0 {
|
|
|
|
|
fields = append(fields, zap.Uint64(FieldNATSSequence, mf.JSSequence))
|
|
|
|
|
}
|
|
|
|
|
if mf.RequestID != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldRequestID, mf.RequestID))
|
|
|
|
|
}
|
|
|
|
|
if mf.TraceID != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldTraceID, mf.TraceID))
|
|
|
|
|
}
|
|
|
|
|
if mf.ErrorType != "" {
|
|
|
|
|
fields = append(fields, zap.String(FieldErrorType, string(mf.ErrorType)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return logger.With(fields...)
|
|
|
|
|
}
|