package decorator import ( "context" "encoding/json" "fmt" "log/slog" "strings" "time" "git.loyso.art/frx/kurious/internal/common/xcontext" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) var ( nameAttribute = attribute.Key("cq.name") argsAttribute = attribute.Key("cq.args") apiTracer = otel.Tracer("api") ) type commandLoggingDecorator[T any] struct { base CommandHandler[T] log *slog.Logger } func (c commandLoggingDecorator[T]) Handle(ctx context.Context, cmd T) (err error) { handlerName := getTypeName[T]() ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName)) var argsBuilder strings.Builder _ = json.NewEncoder(&argsBuilder).Encode(cmd) var span trace.Span ctx, span = apiTracer.Start(ctx, "command "+handlerName) span.SetAttributes( nameAttribute.String(handlerName), argsAttribute.String(argsBuilder.String()), ) xcontext.LogDebug(ctx, c.log, "executing command") start := time.Now() defer func() { elapsed := slog.Duration("elapsed", time.Since(start)) if err == nil { xcontext.LogInfo(ctx, c.log, "command executed successfuly", elapsed) } else { xcontext.LogError(ctx, c.log, "command execution failed", elapsed, slog.Any("err", err)) span.RecordError(err) } span.End() }() return c.base.Handle(ctx, cmd) } type queryLoggingDecorator[Q, U any] struct { base QueryHandler[Q, U] log *slog.Logger } func (q queryLoggingDecorator[Q, U]) Handle(ctx context.Context, query Q) (entity U, err error) { handlerName := getTypeName[Q]() ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName)) var argsBuilder strings.Builder _ = json.NewEncoder(&argsBuilder).Encode(query) var span trace.Span ctx, span = apiTracer.Start(ctx, "query "+handlerName) span.SetAttributes( nameAttribute.String(handlerName), argsAttribute.String(argsBuilder.String()), ) xcontext.LogDebug(ctx, q.log, "executing command") start := time.Now() defer func() { elapsed := slog.Duration("elapsed", time.Since(start)) if err == nil { xcontext.LogInfo(ctx, q.log, "command executed successfuly", elapsed) } else { xcontext.LogError(ctx, q.log, "command execution failed", elapsed, slog.Any("err", err)) span.RecordError(err) } now := time.Now() span.End(trace.WithTimestamp(now)) }() return q.base.Handle(ctx, query) } func getTypeName[T any]() string { var t T out := fmt.Sprintf("%T", t) return out }