package shared import ( "context" "log/slog" "galaxy/user/internal/logging" ) // LogServiceOutcome writes one structured service-level outcome log with a // stable severity derived from err and with trace fields attached when ctx // carries an active span. func LogServiceOutcome(logger *slog.Logger, ctx context.Context, message string, err error, attrs ...any) { if logger == nil { logger = slog.Default() } attrs = append(attrs, logging.TraceAttrsFromContext(ctx)...) switch { case err == nil: logger.InfoContext(ctx, message, attrs...) case isExpectedServiceErrorCode(CodeOf(err)): logger.WarnContext(ctx, message, append(attrs, "error", err.Error())...) default: logger.ErrorContext(ctx, message, append(attrs, "error", err.Error())...) } } // MetricOutcome returns the stable low-cardinality outcome label derived from // err for service metrics. func MetricOutcome(err error) string { if err == nil { return "success" } code := CodeOf(err) if code == "" { return ErrorCodeInternalError } return code } // LogEventPublicationFailure writes one structured error log for an auxiliary // post-commit event publication failure. func LogEventPublicationFailure(logger *slog.Logger, ctx context.Context, eventType string, err error, attrs ...any) { if err == nil { return } if logger == nil { logger = slog.Default() } attrs = append(attrs, "event_type", eventType, "error", err.Error(), ) attrs = append(attrs, logging.TraceAttrsFromContext(ctx)...) logger.ErrorContext(ctx, "auxiliary event publication failed", attrs...) } func isExpectedServiceErrorCode(code string) bool { switch code { case ErrorCodeInvalidRequest, ErrorCodeConflict, ErrorCodeSubjectNotFound: return true default: return false } }