Reference

Command states

Every traced command ends up in one of six states:

State Meaning

Created

The command was received but has not been executed yet.

Successful

The command completed without errors (HTTP 2xx).

Cancelled

The command was redirected or abandoned (HTTP 3xx).

Rejected

The command was refused due to client error — validation failure, missing data, forbidden (HTTP 4xx).

Conflict

A specific case of rejection indicating a resource conflict. Not automatically derived from HTTP status codes — available for application code to set directly.

Failed

The command failed due to a server error (HTTP 5xx) or an unhandled exception.

How state is determined

In the HTTP path, state is derived from the response status code:

HTTP status State

2xx

Successful

3xx

Cancelled

4xx

Rejected

5xx

Failed

In the direct (non-HTTP) path, state is derived from the Problem response:

  • No problem → Successful

  • Problem with status < 500 or null → Rejected

  • Problem with status >= 500 → Failed

Filtering with CommandStateCategory

The includeStates attribute on @CommandTracing controls which outcomes are persisted:

Category Which states are stored

All

Every command, regardless of outcome (default).

NotSuccessful

Only commands that did not succeed — Cancelled, Rejected, Conflict, Failed.

Failure

Only server-side failures — Failed.

None

Nothing is stored. Useful for disabling persistence while keeping lifecycle hooks active.

Importance and retention

Every command is assigned an importance level, which determines its retention policy.

Importance

Level When to use

Low

Routine operations, background jobs, dry-run executions. Can be cleaned up after a short period.

Normal

Standard user-initiated operations (default). Kept for a reasonable period.

High

Critical operations — submissions, payments, signing. Kept permanently.

Set importance on the annotation:

@CommandTracing(importance = CommandImportance.High)
public Report submitReport(@Valid @Body SubmitReportCommand cmd) { }

Or adjust it dynamically with a ParamsTransformer — for example, reducing importance for dry-run commands or partial-edit autosaves. For very high-volume endpoints, skip persisting successful executions entirely while keeping non-successful ones.

Retention

Retention is derived automatically from importance:

Importance Retention

Low

ShortLived

Normal

LongLived

High

Permanent

The retention value is stored in the command_log table and can be used by a cleanup job to purge old, low-importance entries while preserving critical ones. The library does not include a cleanup job — retention is a policy marker that your application acts on.

Sensitive data

By default, the full command payload is stored in the cmd_body column as JSONB. This is valuable for debugging and replay, but not always appropriate.

The library offers two mechanisms for keeping sensitive payloads out of cmd_body:

  • Redactable — per-cmd, can fully omit or return a payload-redacted copy. Choose this when the cmd type itself knows what is sensitive.

  • ExcludeCmdBody — per-tracing-call, all-or-nothing. Choose this when the decision is endpoint-level (an @CommandTracing attribute) or runtime-contextual (programmatic).

Redactable

A cmd that implements Redactable controls what lands in cmd_body while keeping its identity intact. The framework calls redactedForTracing() immediately before serialising and uses the returned value as the body source.

public record UploadInvoicePdfCommand(
    UUID cmdUuid,
    String cmdSourceRef,
    String tenantId,
    String filename,
    byte[] pdfBytes
) implements TenantCommand, Redactable {

    @Override
    public Optional<Command> redactedForTracing() {
        return Optional.of(new UploadInvoicePdfCommand(
            cmdUuid, cmdSourceRef, tenantId, filename, null
        ));
    }
}

Three shapes the implementation can take:

  • Hide the entire body — return Optional.empty(). The audit row carries identity only; cmd_body is the empty map.

  • Drop a field — return a copy with the sensitive field nulled. Other fields land in cmd_body as usual.

  • Mask a value — return a copy with the sensitive field replaced (e.g. with "*").

Identity (cmd uuid, tenant/user/client refs, timestamps) is always preserved by the framework — the implementation cannot accidentally drop them. Always return a new instance; mutating this will corrupt the in-flight cmd execution.

If the implementation throws, the framework falls back to publishing the un-redacted cmd and emits a warn on logger no.conta.command.tracing.Redaction starting with "Redactable.redactedForTracing threw". This is fail-open by design — redaction must never mask the real outcome of the cmd execution — so set up an alert on that warn line: when it fires, the secret payload just landed in the audit log.

ExcludeCmdBody

CommandTracingOption.ExcludeCmdBody skips the body entirely for a specific traced call. Useful when the cmd type doesn’t know it’s being traced sensitively, or when the decision is endpoint- or context-driven.

On the annotation:

@CommandTracing(options = CommandTracingOption.ExcludeCmdBody)
public Response uploadFile(@Valid @Body UploadFileCommand cmd) { }

Or programmatically:

CommandTracingContext.set(cmd,
    CommandTracingParams.withOptions(CommandTracingOption.ExcludeCmdBody));

In both cases, the cmd is still traced — cmd_class, state, duration, and all other metadata are recorded. Only the body is omitted.

Choosing between them

Use Redactable when the cmd type carries known-sensitive fields and other commands of the same type should always redact. Lives on the cmd; works regardless of how it is invoked.

Use ExcludeCmdBody

when the decision is endpoint- or context-specific. Lives on the @CommandTracing annotation or the CommandTracingContext. Coarser — body is fully omitted, no partial redaction.

Use both

a cmd may implement Redactable and be traced with ExcludeCmdBody. ExcludeCmdBody wins (it skips the body entirely before Redactable would be consulted).

Global schema vs tenant schema

In multi-tenant applications, the global command_log is often kept lean — just enough metadata for cross-tenant insights and monitoring. A more detailed command log in the tenant-specific schema can store the full payload, including sensitive fields that shouldn’t leave the tenant boundary.

This dual-schema pattern is not yet built into the library, but the mechanisms above provide the building blocks: the global publisher excludes or redacts the body, while a tenant-scoped listener persists the full detail.

Problem+JSON integration

The library integrates with the conta-problem-json library to capture structured error information alongside every failed command.

How problems are captured

When a command fails in the HTTP path, the CommandTracingFilter reads the response body. If it contains a problem+json response, it is deserialized into a Problem object and stored in the command log.

This gives you two pieces of information for every failure:

  • problem_code — the application-specific error code (stored as a separate, queryable column)

  • problem — the full problem+json body as JSONB (including status, detail, and any additional info fields)

The two failure paths

The CommandTracingListener has two onCommandFailed overloads because errors surface differently depending on the execution path:

onCommandFailed(Command, Throwable)

Called in the direct (non-HTTP) path, where the original exception is available. Also called as a fallback from the Problem overload.

onCommandFailed(Command, Problem)

Called in the HTTP filter path, where the exception has already been serialized to a problem+json response. By default, this delegates to the Throwable overload via problem.toThrowable(), so listeners that only implement the Throwable variant still receive notifications from both paths.

What this enables

With problem codes stored in the command log, you can:

  • Count how often each error code occurs across the platform

  • Detect new failure patterns as they emerge

  • Track whether a fix actually reduced the error rate

  • Build dashboards that show the most impactful errors — by frequency, by affected tenants, by trend

See Example Queries for SQL patterns that query by problem code.