Skip to main content

Execution report logging

Since 1.4.0

After every run, Flamingock emits a structured execution report through SLF4J under the logger name FK-Report — at INFO on success and ERROR on failure. The report shows the overall status, duration, per-stage breakdown, and — when something went wrong — the failing change IDs and error details.

This is enabled by default. If you're happy seeing the report in your logs, you don't need to configure anything.

Related: Events.

What the report looks like

On a successful run:

========================================================================
Flamingock execution report — SUCCESS
========================================================================
Started: 2026-05-15T08:00:00Z
Finished: 2026-05-15T08:00:00.180Z
Duration: 180 ms

Stages: 2 total — 2 completed, 0 failed, 0 up to date, 0 not reached
Changes: 3 total — 3 newly applied, 0 already applied, 0 failed, 0 not reached

Per-stage breakdown:

[COMPLETED] user-database-stage (75 ms)
changes: 2 newly applied, 0 already applied, 0 failed, 0 not reached

[COMPLETED] kafka-topics-stage (105 ms)
changes: 1 newly applied, 0 already applied, 0 failed, 0 not reached

========================================================================

On a failed run:

========================================================================
Flamingock execution report — FAILED
========================================================================
Started: 2026-05-15T08:00:00Z
Finished: 2026-05-15T08:00:00.312Z
Duration: 312 ms

Stages: 2 total — 1 completed, 1 failed, 0 up to date, 0 not reached
Changes: 3 total — 2 newly applied, 0 already applied, 1 failed, 0 not reached

Per-stage breakdown:

[COMPLETED] user-database-stage (75 ms)
changes: 2 newly applied, 0 already applied, 0 failed, 0 not reached

[FAILED] kafka-topics-stage (237 ms)
changes: 0 newly applied, 0 already applied, 1 failed, 0 not reached
error: TopicCreationFailed
Broker rejected creation: replication factor 3 > available brokers 1
failed change(s): create-orders-topic

========================================================================

Reading the counters

Each run reports two count lines, and both always add up to the total:

  • Stages: completed + failed + up to date + not reached = total.
  • Changes: newly applied + already applied + failed + not reached = total.

What each bucket means:

  • newly applied — change executed and applied during this run.
  • already applied — change was found already applied (confirmed against the audit log), so nothing ran for it.
  • failed — change failed this run. A transactional change that failed and was automatically rolled back is also counted here.
  • not reached — change was never processed this run (for example, it sits after a failed change in the same stage, or in a stage the run never got to).

Other run shapes

Beyond the success and failure examples above, the report adapts to what actually happened:

  • NO CHANGES — the headline reads Flamingock execution report — NO CHANGES when nothing needed applying (every change is already applied, or there was nothing to do). This is the common steady-state report you'll see on most application restarts after the first.
  • [UP TO DATE] stages — a stage Flamingock confirmed was already applied without running anything appears with the [UP TO DATE] label and no duration, e.g. [UP TO DATE] user-database-stage (3 changes already applied).
  • [NOT REACHED] stages — when a run stops early (for example after a failure), the stages it never got to are summarised in a separate Not reached (N): section that lists each stage name with its change count.
  • [BLOCKED — manual intervention required] stages — a stage that needs manual intervention shows this distinct label, followed by a change(s) requiring intervention: line listing the change IDs to resolve.
note

If your code catches the exception raised when stages fail, its getMessage() returns a single-line summary of the same data — failed stage names plus the change counters and duration, for example:

Flamingock execution failed: 1 of 2 stage(s) failed [kafka-topics-stage]; changes newly_applied=2, already_applied=0, failed=1, not_reached=0; duration=312ms

When a stage is blocked for manual intervention, the line ends with ; manual intervention required for change(s): <ids>. The multi-line report above is emitted by the SLF4J listener, never by the exception itself, so log aggregators don't see duplicated dumps on printStackTrace.

Set the FK-Report logger level to OFF. No code change, no rebuild — purely a logging-configuration change.

<configuration>
<logger name="FK-Report" level="OFF"/>
</configuration>

Use WARN (any level between INFO and ERROR)" instead of OFF to keep failure reports while suppressing successful runs — the failure listener logs at ERROR, the success listener at INFO.

Disabling the report via the builder flag

Set the flag to false on the runner builder. Default is true.

Flamingock.builder()
.setEnableDefaultExecutionReport(false)
.build()
.run();

This is the right choice when you want no default report at all — for instance, when you're registering your own listener and want only that to fire.

Bring your own listener

Register your own listener with the existing builder setters (setPipelineCompletedListener, setPipelineFailedListener, etc.). See Events for the full listener-registration surface and the payload reference.

When both the default and a custom listener are registered, both fire (additive). To have only your listener fire, set setEnableDefaultExecutionReport(false) as shown above.

A minimal failure listener that reads the typed result and logs the IDs of any change blocked for manual intervention:

public class PipelineFailedListener implements Consumer<IPipelineFailedEvent> {

private static final Logger logger = LoggerFactory.getLogger(PipelineFailedListener.class);

@Override
public void accept(IPipelineFailedEvent event) {
ExecuteResponseData result = event.getResult();
logger.error("Flamingock failed: {} failed stage(s), {} failed change(s), {} ms",
result.getFailedStages(),
result.getFailedChanges(),
result.getTotalDurationMs());

result.getStages().stream()
.filter(stage -> stage.getState().isBlockedForManualIntervention())
.flatMap(stage -> stage.getState().getRecoveryIssues().stream())
.map(RecoveryIssue::getChangeId)
.forEach(id -> logger.error("Manual intervention required for change: {}", id));
}
}

Reusing the canonical report formatter

If you want to emit the same multi-line block that the default listener writes — but to a different logger, sink, or external system (Slack, metrics, JSON) — call ExecutionReportFormatter.report(...):

String report = ExecutionReportFormatter.report(event.getResult());
mySink.publish(report);

The same class also exposes summary(...), which produces the one-line variant.

Spring Boot

The default FK-Report listener is wired by the core builder and fires the same way in Spring Boot — silence it via the SLF4J configuration above or the builder flag.

Spring listeners receive the same typed payload through Flamingock's Spring ApplicationEvent wrappers. A minimal @EventListener handler for failures:

@Configuration
public class FlamingockListeners {

private static final Logger logger = LoggerFactory.getLogger(FlamingockListeners.class);

@EventListener
public void onFlamingockFailure(SpringPipelineFailedEvent event) {
ExecuteResponseData result = event.getResult();
logger.error("Flamingock failed: {} failed stage(s) in {} ms",
result.getFailedStages(), result.getTotalDurationMs());
}
}