|
| 1 | +--- |
| 2 | +sidebar_position: 95 |
| 3 | +--- |
| 4 | + |
| 5 | +import Tabs from '@theme/Tabs'; |
| 6 | +import TabItem from '@theme/TabItem'; |
| 7 | +import VersionBadge from '@site/src/components/VersionBadge'; |
| 8 | + |
| 9 | +# Execution report logging |
| 10 | +<VersionBadge version="1.4.0" variant="subtitle" /> |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +This is enabled by default. **If you're happy seeing the report in your logs, you don't need to configure anything.** |
| 15 | + |
| 16 | +Related: [Events](./events.md). |
| 17 | + |
| 18 | +## What the report looks like |
| 19 | + |
| 20 | +On a successful run: |
| 21 | + |
| 22 | +``` |
| 23 | +======================================================================== |
| 24 | + Flamingock execution report — SUCCESS |
| 25 | +======================================================================== |
| 26 | + Started: 2026-05-15T08:00:00Z |
| 27 | + Finished: 2026-05-15T08:00:00.180Z |
| 28 | + Duration: 180 ms |
| 29 | +
|
| 30 | + Stages: 2 total — 2 completed, 0 failed, 0 up to date, 0 not reached |
| 31 | + Changes: 3 total — 3 newly applied, 0 already applied, 0 failed, 0 not reached |
| 32 | +
|
| 33 | + Per-stage breakdown: |
| 34 | +
|
| 35 | + [COMPLETED] user-database-stage (75 ms) |
| 36 | + changes: 2 newly applied, 0 already applied, 0 failed, 0 not reached |
| 37 | +
|
| 38 | + [COMPLETED] kafka-topics-stage (105 ms) |
| 39 | + changes: 1 newly applied, 0 already applied, 0 failed, 0 not reached |
| 40 | +
|
| 41 | +======================================================================== |
| 42 | +``` |
| 43 | + |
| 44 | +On a failed run: |
| 45 | + |
| 46 | +``` |
| 47 | +======================================================================== |
| 48 | + Flamingock execution report — FAILED |
| 49 | +======================================================================== |
| 50 | + Started: 2026-05-15T08:00:00Z |
| 51 | + Finished: 2026-05-15T08:00:00.312Z |
| 52 | + Duration: 312 ms |
| 53 | +
|
| 54 | + Stages: 2 total — 1 completed, 1 failed, 0 up to date, 0 not reached |
| 55 | + Changes: 3 total — 2 newly applied, 0 already applied, 1 failed, 0 not reached |
| 56 | +
|
| 57 | + Per-stage breakdown: |
| 58 | +
|
| 59 | + [COMPLETED] user-database-stage (75 ms) |
| 60 | + changes: 2 newly applied, 0 already applied, 0 failed, 0 not reached |
| 61 | +
|
| 62 | + [FAILED] kafka-topics-stage (237 ms) |
| 63 | + changes: 0 newly applied, 0 already applied, 1 failed, 0 not reached |
| 64 | + error: TopicCreationFailed |
| 65 | + Broker rejected creation: replication factor 3 > available brokers 1 |
| 66 | + failed change(s): create-orders-topic |
| 67 | +
|
| 68 | +======================================================================== |
| 69 | +``` |
| 70 | + |
| 71 | +## Reading the counters |
| 72 | + |
| 73 | +Each run reports two count lines, and both always add up to the total: |
| 74 | + |
| 75 | +- **Stages:** `completed + failed + up to date + not reached = total`. |
| 76 | +- **Changes:** `newly applied + already applied + failed + not reached = total`. |
| 77 | + |
| 78 | +What each bucket means: |
| 79 | + |
| 80 | +- **newly applied** — change executed and applied during this run. |
| 81 | +- **already applied** — change was found already applied (confirmed against the audit log), so nothing ran for it. |
| 82 | +- **failed** — change failed this run. A transactional change that failed and was automatically rolled back is also counted here. |
| 83 | +- **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). |
| 84 | + |
| 85 | +## Other run shapes |
| 86 | + |
| 87 | +Beyond the success and failure examples above, the report adapts to what actually happened: |
| 88 | + |
| 89 | +- **`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. |
| 90 | +- **`[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)`. |
| 91 | +- **`[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. |
| 92 | +- **`[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. |
| 93 | + |
| 94 | +:::note |
| 95 | +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: |
| 96 | + |
| 97 | +``` |
| 98 | +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 |
| 99 | +``` |
| 100 | + |
| 101 | +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`. |
| 102 | +::: |
| 103 | + |
| 104 | +## Silencing the report via SLF4J (recommended for production) |
| 105 | + |
| 106 | +Set the `FK-Report` logger level to `OFF`. No code change, no rebuild — purely a logging-configuration change. |
| 107 | + |
| 108 | +<Tabs groupId="logging"> |
| 109 | + <TabItem value="logback" label="Logback" default> |
| 110 | +```xml |
| 111 | +<configuration> |
| 112 | + <logger name="FK-Report" level="OFF"/> |
| 113 | +</configuration> |
| 114 | +``` |
| 115 | + </TabItem> |
| 116 | + <TabItem value="log4j2" label="Log4j2"> |
| 117 | +```xml |
| 118 | +<Configuration> |
| 119 | + <Loggers> |
| 120 | + <Logger name="FK-Report" level="OFF" additivity="false"/> |
| 121 | + </Loggers> |
| 122 | +</Configuration> |
| 123 | +``` |
| 124 | + </TabItem> |
| 125 | +</Tabs> |
| 126 | + |
| 127 | +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`. |
| 128 | + |
| 129 | +## Disabling the report via the builder flag |
| 130 | + |
| 131 | +Set the flag to `false` on the runner builder. Default is `true`. |
| 132 | + |
| 133 | +<Tabs groupId="languages"> |
| 134 | + <TabItem value="java" label="Java" default> |
| 135 | +```java |
| 136 | +Flamingock.builder() |
| 137 | + .setEnableDefaultExecutionReport(false) |
| 138 | + .build() |
| 139 | + .run(); |
| 140 | +``` |
| 141 | + </TabItem> |
| 142 | + <TabItem value="kotlin" label="Kotlin"> |
| 143 | +```kotlin |
| 144 | +Flamingock.builder() |
| 145 | + .setEnableDefaultExecutionReport(false) |
| 146 | + .build() |
| 147 | + .run() |
| 148 | +``` |
| 149 | + </TabItem> |
| 150 | +</Tabs> |
| 151 | + |
| 152 | +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. |
| 153 | + |
| 154 | +## Bring your own listener |
| 155 | + |
| 156 | +Register your own listener with the existing builder setters (`setPipelineCompletedListener`, `setPipelineFailedListener`, etc.). See [Events](./events.md) for the full listener-registration surface and the payload reference. |
| 157 | + |
| 158 | +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. |
| 159 | + |
| 160 | +A minimal failure listener that reads the typed result and logs the IDs of any change blocked for manual intervention: |
| 161 | + |
| 162 | +<Tabs groupId="languages"> |
| 163 | + <TabItem value="java" label="Java" default> |
| 164 | +```java |
| 165 | +public class PipelineFailedListener implements Consumer<IPipelineFailedEvent> { |
| 166 | + |
| 167 | + private static final Logger logger = LoggerFactory.getLogger(PipelineFailedListener.class); |
| 168 | + |
| 169 | + @Override |
| 170 | + public void accept(IPipelineFailedEvent event) { |
| 171 | + ExecuteResponseData result = event.getResult(); |
| 172 | + logger.error("Flamingock failed: {} failed stage(s), {} failed change(s), {} ms", |
| 173 | + result.getFailedStages(), |
| 174 | + result.getFailedChanges(), |
| 175 | + result.getTotalDurationMs()); |
| 176 | + |
| 177 | + result.getStages().stream() |
| 178 | + .filter(stage -> stage.getState().isBlockedForManualIntervention()) |
| 179 | + .flatMap(stage -> stage.getState().getRecoveryIssues().stream()) |
| 180 | + .map(RecoveryIssue::getChangeId) |
| 181 | + .forEach(id -> logger.error("Manual intervention required for change: {}", id)); |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | + </TabItem> |
| 186 | + <TabItem value="kotlin" label="Kotlin"> |
| 187 | +```kotlin |
| 188 | +class PipelineFailedListener : (IPipelineFailedEvent) -> Unit { |
| 189 | + |
| 190 | + private val logger = LoggerFactory.getLogger(PipelineFailedListener::class.java) |
| 191 | + |
| 192 | + override fun invoke(event: IPipelineFailedEvent) { |
| 193 | + val result = event.result |
| 194 | + logger.error("Flamingock failed: {} failed stage(s), {} failed change(s), {} ms", |
| 195 | + result.failedStages, result.failedChanges, result.totalDurationMs) |
| 196 | + |
| 197 | + result.stages |
| 198 | + .filter { it.state.isBlockedForManualIntervention } |
| 199 | + .flatMap { it.state.recoveryIssues } |
| 200 | + .map { it.changeId } |
| 201 | + .forEach { logger.error("Manual intervention required for change: {}", it) } |
| 202 | + } |
| 203 | +} |
| 204 | +``` |
| 205 | + </TabItem> |
| 206 | +</Tabs> |
| 207 | + |
| 208 | +### Reusing the canonical report formatter |
| 209 | + |
| 210 | +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(...)`: |
| 211 | + |
| 212 | +<Tabs groupId="languages"> |
| 213 | + <TabItem value="java" label="Java" default> |
| 214 | +```java |
| 215 | +String report = ExecutionReportFormatter.report(event.getResult()); |
| 216 | +mySink.publish(report); |
| 217 | +``` |
| 218 | + </TabItem> |
| 219 | + <TabItem value="kotlin" label="Kotlin"> |
| 220 | +```kotlin |
| 221 | +val report = ExecutionReportFormatter.report(event.result) |
| 222 | +mySink.publish(report) |
| 223 | +``` |
| 224 | + </TabItem> |
| 225 | +</Tabs> |
| 226 | + |
| 227 | +The same class also exposes `summary(...)`, which produces the one-line variant. |
| 228 | + |
| 229 | +## Spring Boot |
| 230 | + |
| 231 | +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. |
| 232 | + |
| 233 | +Spring listeners receive the same typed payload through Flamingock's Spring `ApplicationEvent` wrappers. A minimal `@EventListener` handler for failures: |
| 234 | + |
| 235 | +<Tabs groupId="languages"> |
| 236 | + <TabItem value="java" label="Java" default> |
| 237 | +```java |
| 238 | +@Configuration |
| 239 | +public class FlamingockListeners { |
| 240 | + |
| 241 | + private static final Logger logger = LoggerFactory.getLogger(FlamingockListeners.class); |
| 242 | + |
| 243 | + @EventListener |
| 244 | + public void onFlamingockFailure(SpringPipelineFailedEvent event) { |
| 245 | + ExecuteResponseData result = event.getResult(); |
| 246 | + logger.error("Flamingock failed: {} failed stage(s) in {} ms", |
| 247 | + result.getFailedStages(), result.getTotalDurationMs()); |
| 248 | + } |
| 249 | +} |
| 250 | +``` |
| 251 | + </TabItem> |
| 252 | + <TabItem value="kotlin" label="Kotlin"> |
| 253 | +```kotlin |
| 254 | +@Configuration |
| 255 | +class FlamingockListeners { |
| 256 | + |
| 257 | + private val logger = LoggerFactory.getLogger(FlamingockListeners::class.java) |
| 258 | + |
| 259 | + @EventListener |
| 260 | + fun onFlamingockFailure(event: SpringPipelineFailedEvent) { |
| 261 | + val result = event.result |
| 262 | + logger.error("Flamingock failed: {} failed stage(s) in {} ms", |
| 263 | + result.failedStages, result.totalDurationMs) |
| 264 | + } |
| 265 | +} |
| 266 | +``` |
| 267 | + </TabItem> |
| 268 | +</Tabs> |
0 commit comments