// Package logging configures the Mail Service process logger and provides // context-aware helpers for trace, delivery, attempt, and command fields. package logging import ( "context" "fmt" "log/slog" "os" "strings" "galaxy/mail/internal/api/streamcommand" "galaxy/mail/internal/domain/attempt" deliverydomain "galaxy/mail/internal/domain/delivery" "go.opentelemetry.io/otel/trace" ) // New constructs the process-wide JSON logger from level. func New(level string) (*slog.Logger, error) { var slogLevel slog.Level if err := slogLevel.UnmarshalText([]byte(strings.TrimSpace(level))); err != nil { return nil, fmt.Errorf("build logger: %w", err) } return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slogLevel, })), nil } // TraceAttrsFromContext returns slog key-value pairs for the active // OpenTelemetry span when ctx carries a valid span context. func TraceAttrsFromContext(ctx context.Context) []any { if ctx == nil { return nil } spanContext := trace.SpanContextFromContext(ctx) if !spanContext.IsValid() { return nil } return []any{ "otel_trace_id", spanContext.TraceID().String(), "otel_span_id", spanContext.SpanID().String(), } } // DeliveryAttrs returns structured delivery-identifying log fields. func DeliveryAttrs(record deliverydomain.Delivery) []any { attrs := []any{ "delivery_id", record.DeliveryID.String(), "source", string(record.Source), } if !record.TemplateID.IsZero() { attrs = append(attrs, "template_id", record.TemplateID.String()) } return attrs } // AttemptAttrs returns structured attempt-identifying log fields. func AttemptAttrs(record attempt.Attempt) []any { return []any{ "delivery_id", record.DeliveryID.String(), "attempt_no", record.AttemptNo, } } // DeliveryAttemptAttrs returns structured delivery and attempt fields. func DeliveryAttemptAttrs(deliveryRecord deliverydomain.Delivery, attemptRecord attempt.Attempt) []any { attrs := DeliveryAttrs(deliveryRecord) attrs = append(attrs, "attempt_no", attemptRecord.AttemptNo) return attrs } // CommandAttrs returns structured generic-command log fields. func CommandAttrs(command streamcommand.Command) []any { attrs := []any{ "delivery_id", command.DeliveryID.String(), "source", string(command.Source), } if !command.TemplateID.IsZero() { attrs = append(attrs, "template_id", command.TemplateID.String()) } if strings.TrimSpace(command.TraceID) != "" { attrs = append(attrs, "trace_id", command.TraceID) } return attrs }