Observability

Log schema contract

Bloodraven emits structured JSON logs from both the operator (bloodraven) and the per-MySQL sidecar (bloodraven-sidecar). This page is the contract that downstream log pipelines key off of: which fields are stable, what the msg values are for the events you care about, and what guarantees we make about changing them.

If you only need one rule of thumb: filter on msg for the event vocabulary in the Event reference below — those strings are stable. Everything else is best-effort.

Streams

Both binaries write to stdout. There are two independent JSON streams; you can tell them apart by the presence of certain keys.

StreamSourceIdentifies asWhat's in it
Operational (slog)Operator and sidecarHas time, level, msgFailover, promotion, bootstrap, recovery, fencing, archiver, sidecar startup, divergence detection — every event a human operator or alerting pipeline would care about
Controller-runtime (zap)Operator onlyHas ts, level, msg, logger, controller, controllerKind, reconcileIDReconcile-loop bookkeeping from controller-runtime: CR fetches, status updates, watch events. Useful for debugging, not a stable interface

The contract on this page applies to the operational stream. The controller-runtime stream is emitted as-is by upstream sigs.k8s.io/controller-runtime and inherits whatever shape that library produces — we don't redefine it.

To filter to operational logs in most pipelines, key on the presence of the time field (slog) or the absence of the logger field (zap).

Common fields

Every record in the operational stream carries:

FieldTypeDescription
timeRFC3339Nano timestamp (string)Event time, normalized to UTC by the binary's slog handler regardless of pod timezone. Always ends in Z.
levelstringOne of DEBUG, INFO, WARN, ERROR.
msgstringThe event identifier. Stable for events listed in the Event reference; may change for ad-hoc debug logs.

Records emitted under a specific failover group also carry:

FieldTypeDescription
fgstringThe MysqlFailoverGroup namespaced name (namespace/name). Present on every operator log scoped to a group. On the sidecar, this is the bare group name passed via BLOODRAVEN_FAILOVER_GROUP.

Sidecar records additionally carry:

FieldTypeDescription
podstringThe pod name (set via BLOODRAVEN_POD_NAME). Disambiguates per-replica logs when shipping multiple sites' sidecars to one stream.

Levels

LevelWhen
DEBUGPer-poll bookkeeping (status no-ops, transient probe errors, archiver tick events). Off by default — the operator's slog handler is set to INFO.
INFOState changes the operator deliberately took: failover, promotion, bootstrap, recovery, sidecar lifecycle. Most of the event vocabulary lives here.
WARNDegraded but not fatal: a single retry, a peer briefly unreachable, a non-critical operation that failed (connection kill, taint patch). The operator continues.
ERROROperator-affecting failure: failover failed, self-fence triggered, status update rejected by the API server, CronJob-pod startup validation failed. Always paired with an error field (a string carrying either the underlying error or, for validation failures, a description of what was missing).

DEBUG records may appear or disappear without notice. INFO/WARN/ERROR msg strings listed below are stable.

Field naming convention

  • Keys are camelCase. Common keys: site, fg, error, peer, count, source, donor, recipient.
  • Site identifiers (site, oldPrimary, newPrimary, promotedSite, donor, recipient, activeSite, authoritativeActiveSite) all carry the bare site name as defined in spec.sites[].name.
  • GTID fields (promotionGtid, divergentGtid, oldPrimaryGtid, newPrimaryGtid, followerGtid, activeGtid) carry MySQL GTID-set strings exactly as MySQL returns them — never parsed or canonicalised.
  • Counts (count, divergentTransactions, attempt, maxRetries) are JSON numbers, not strings.
  • Durations (leaseTimeout, pollInterval, delay, timeout) are emitted by slog's default time.Duration rendering — currently a string like "30s". Treat as opaque if you need to parse, prefer the metric of the same name.

Event reference

This is the stable vocabulary. msg strings here will not change without a deprecation note in CHANGELOG.md.

Failover

The four events that trace one failover, in order:

LevelmsgFieldsFired when
INFOinitiating failovercandidate, oldPrimary, fgOperator has chosen a promotion target and is about to run the promotion sequence. DNS flips only after promotion succeeds and the target is verified writable.
INFOfailover completepromotedSite, promotionGtid, fgExecute finished: candidate is writable. promotionGtid is the candidate's gtid_executed snapshot taken just before clearing super_read_only — the upper bound on data that survived.
INFOpromotion confirmed: site is writablesite, fgNext poll observes the promoted site is writable. The internal post-failover guard clears here.
ERRORfailover failederror, fgThe promotion sequence returned an error. The operator does not retry automatically; the next eligible state-transition tick will re-evaluate.
ERRORpromotion succeeded but writable confirmation failed; DNS not flippedsite, error, fgExecute returned successfully but the promoted site did not report writable within the confirmation window. DNS is not flipped and no failover state is recorded — the promotion is treated as unconfirmed and re-evaluated on the next tick.
ERRORDNS flip failed after successful promotionsite, error, fgPromotion and writable confirmation both succeeded, so the failover state (cooldown, split-brain target, promotionGtidExecuted) and bloodraven_failovers_total are already recorded; only the DNS update failed. bloodraven_dns_flips_total is left unincremented. The poll loop reconciles DNS against the current active site (see DNS reconciled to active site below), so a transient failure such as an RBAC denial self-heals once the write is permitted again — MySQL has already promoted regardless.
WARNDNS reconcile failedsite, target, error, fgThe poll-driven DNS reconcile tried to point the record at the current active site and the write was rejected. Logged once per failing episode, not once per poll: while the failure persists the retry continues silently (DEBUG DNS reconcile still failing) and MySQL is unaffected.
INFODNS reconciled to active sitesite, target, fgThe DNS record diverged from the current active site and was repaired — a promotion-time flip that had failed, a record left stale by an operator restart, or an out-of-band edit. bloodraven_dns_flips_total{site} increments here, and only when the record's value actually changed. No promotion is re-run and MySQL is not touched.

Supporting events emitted inside Execute:

LevelmsgFields
INFOfenced old primary with super_read_only=ONfg
WARNfailed to fence old primary (may be unreachable)error, fg
INFOkilled app connections on old primarycount, fg
WARNfailed to kill app connections on old primaryerror, fg
INFOrelay log drain completefg
WARNrelay log drain did not complete cleanly, proceeding with promotionerror, fg

Divergence and recovery

Fired after an emergency failover when the operator inspects the returning old primary.

LevelmsgFieldsNotes
INFOinitiating old primary recoveryoldPrimary, newPrimary, fgRecovery sequence starting.
INFOno GTID divergence, auto-recovering old primary as replicasite, fgOld primary's GTID set is a subset of the new primary's — safe to attach as replica.
WARNdivergence detectedsite, divergentTransactions, divergentGtid, oldPrimaryGtid, newPrimaryGtid, fgOld primary has committed transactions the new primary never saw. Operator does not auto-recover — the admin must reclone the site or replay the divergent set onto the new primary (containment then auto-rejoins). Emitted when divergence is first detected and again whenever the periodic re-verification finds the set CHANGED — an unchanged report is not re-logged each cycle. Mirrored by the bloodraven_divergent_transactions gauge and the DataLossDetected Kubernetes Event.
INFOold primary recovery completesite, source, fgOld primary is now replicating from the new primary. source is the new primary's host.
ERRORold primary recovery failedsite, error, fgOne step of the recovery sequence (fence / GTID query / CHANGE REPLICATION SOURCE / START REPLICA) returned an error.
INFOrecovery state cleared (site is now replicating)site, fgThe site is a healthy, source-converged replica again, so its recovery marker is dropped and bloodraven_divergent_transactions{site} returns to 0. The normal terminal state for both RecoveryInProgress and an externally resolved RecoveryBlocked.
INFOrecovery state cleared (site is writable)site, fgThe site is writable, so it is no longer "recovering as a replica". A RecoveryBlocked report is not dropped merely because the site turned writable — a rogue-writable site keeps its live divergence report (split-brain fencing re-fences it, and the periodic re-verification refreshes the report). The one case a blocked report clears here is when the site is the operator's own failover target and the unique writable primary, which makes the report definitionally stale.

Replication source convergence

After topology changes, Bloodraven verifies that every follower replicates directly from the uniquely confirmed active primary. These events cover candidate, dr-only, and read-only followers; they are separate from the old-primary recovery events above.

LevelmsgFieldsNotes
INFOreplication source convergence startedsite, activeSite, currentSource, expectedSource, fgA follower needs a source or thread-state correction and passed the initial mutation gates.
INFOreplication source convergence completesite, source, fgThe canonical source is the active primary and both replication threads are running.
WARNreplication source convergence blockedsite, activeSite, stage, followerGtid, activeGtid, fgGTID containment failed before or after stopping replication. No source change is issued.
ERRORreplication source convergence failedsite, activeSite, stage, error, fgA bounded source mutation or verification attempt failed. The next poll can retry safely.

Stable stage values include pre-stop-gtid, post-stop-gtid, stop, change-source, start, and verify. Use the status sourceConvergenceState and sourceConvergenceReason for current state; use these logs for the detailed failure and GTID evidence.

Bootstrap and reclone

starting bootstrap is the single canonical event for "we are about to clone a replica". The source field disambiguates why:

source valueMeaning
fresh-deployInitial bootstrap of a new failover group; donor is the seed site.
auto-cloneOperator detected an empty replica during steady-state and is recovering it without an admin trigger.
recloneAdmin set the bloodraven.shipstream.io/reclone-site=<name> annotation, the safety interlock passed, and the operator is wiping the named site. This is the reclone-started event.
LevelmsgFields
INFOstarting bootstrapsource, donor, recipient, donorHost, fg
INFOcloning from primarydonor, fg
INFOclone completed successfullyreplica, fg
INFOsetting up replicationsource, fg
INFOreplication started successfullysource, fg
INFObootstrap completed successfullysource, fg
ERRORbootstrap failedsource, error, fg
INFOclone returned expected connection drop, waiting for restarterror, fg
INFOreplica already has primary data (prior clone detected), skipping clone phasefg

A reclone-only narrative is therefore: filter msg="starting bootstrap" AND source="reclone" for the trigger event, then watch for bootstrap completed successfully (source="reclone") or bootstrap failed (source="reclone").

State transitions

Every per-site state change emits one record. Use this to replay the topology timeline.

LevelmsgFields
INFOstate transitionsite, from, to, fg

from and to values: unknown, unreachable, read-only, writable. Mirrored by the bloodraven_state_transitions_total counter.

Topology decisions

LevelmsgFieldsNotes
WARNALERTmessage, fgA cross-site EvalCrossSite action returned an alert string (split brain, no primary, total loss). The same conditions emit SplitBrainDetected / NoPrimaryDetected / TotalLossDetected Kubernetes Events.
WARNsplit-brain auto-resolve: fencing non-preferred site per spec.splitBrainPolicy.sitePriorities(context)Opt-in splitBrainPolicy is fencing the lower-priority site. The poll-driven retry of a persistent split-brain re-emits this at WARN at most every 30s (DEBUG in between).
INFOfencing returning old primary (split brain after failover)site, fgA second site came back writable after a failover; the operator is fencing every writable site except the one holding live primary authority. Emitted at INFO on the first fence of a given split-brain and at DEBUG on the poll-driven retries in between.
ERRORfailed to fence returning old primarysite, error, fgThe super_read_only=ON write above failed. The next poll retries. DEBUG on retries, matching the INFO/DEBUG pairing of the fence itself.
INFOpost-fence application connections evictedsite, count, fgOperator-side recovery removed one or more sessions that survived the sidecar's best-effort fence eviction. It retries on later topology polls until an empty pass or timeout, without blocking the poll loop between passes.
WARNpost-fence application connection drain retry failedsite, error, fgA recovery drain pass could not enumerate or kill sessions. The operator retries within spec.connectionDrainTimeout.
INFOpost-fence application connection drain completesite, fgAn empty eviction pass proved no application sessions remain. Old-primary recovery may now complete.
WARNpost-fence application connection drain timed outsite, timeout, fgThe dedicated drain budget expired. Recovery proceeds because super_read_only=ON still prevents writes; a surviving session is limited to stale reads.
WARNre-asserting fenced promoted primary: no site is writable and the last failover target is GTID-complete; restoring writabilitysite, fgThe last failover target was found fenced (read-only) with every site reachable and nothing writable — typically its own sidecar re-fenced it with a stale lease right after a promotion. The operator restores writability on the target. Mirrored by bloodraven_primary_reassert_total. Rate-limited to once per failoverCooldown.
WARNprimary re-assert refused: peer has transactions the target lacks — divergence needs human reviewsite, peerGtid, targetGtid, fgThe no-writable-site wedge was detected but restoring the last failover target would abandon peer transactions. The group stays read-only until an admin resolves the divergence.
WARNprimary re-assert refused: target no longer contains the recorded promotion GTID set (wiped or restored since promotion?)site, promotionGtid, targetGtid, fgThe failover history no longer describes the target's data lineage; the operator will not restore writability automatically.
WARNprimary re-assert refused: recorded promotion GTID set failed to parse — status corrupted or manually edited?site, promotionGtid, error, fgstatus.promotionGtidExecuted is non-empty but malformed. The operator wrote this value from MySQL itself, so a parse failure means corruption or manual tampering — the re-assert safety argument depends on it, so the operator refuses.
INFOfailover blocked by anti-flap cooldown(context)A failover decision was deferred because failoverCooldown has not elapsed since the last one.
WARNignored out-of-order local failover recordtarget, lastFailover, currentTarget, currentLastFailover, fgTwo local promotion paths reported out of timestamp order; the newer in-process anti-flap pair remains authoritative.
ERRORout-of-band anti-flap state write failed; retrying every pollfg, target, lastFailover, errorThe annotation write was rejected. The newest record stays pending and is retried on each poll.
INFOcross-site action deferred: in-place restore in progressfgDecisions are paused while restoreInPlace runs.
INFOcross-site action deferred: planned failover in progressfgDecisions are paused while a planned-failover annotation is being processed.

Sidecar fencing

The per-MySQL sidecar emits these in its operational stream. SELF-FENCING: is a stable prefix — msg strings that begin with it indicate the sidecar wrote super_read_only=ON to its local MySQL without operator instruction.

LevelmsgFieldsNotes
ERRORSELF-FENCING: topology mismatch — operator-authoritative active site disagrees with our site, setting super_read_only=ONsite, authoritativeActiveSite, observedAt, podThe operator (or a peer relaying the operator's view) reports a different active site than this sidecar is on. Fired even when the operator is reachable.
ERRORSELF-FENCING: Bloodraven and every peer unreachable beyond lease timeout, setting super_read_only=ONbloodravenLastOk, latestPeerOk, peers, leaseTimeout, podBackstop rule: nothing is reachable, so we can't be sure we're still primary.
INFOSELF-FENCING: killed app connectionscount, podApplication sessions evicted after fencing succeeded. Server-internal threads are spared, so a fenced replica keeps applying from the authoritative primary.
WARNSELF-FENCING: failed to kill connections after fencingerror, count, podThe fence write landed but eviction was incomplete. count is how many sessions were killed — 0 when the process-list query itself failed, so no session was ever enumerated. error joins every cause that applied: the listing query failed, iteration ended early, some rows would not scan, some KILLs were refused, or the fence hit its 20s deadline. super_read_only=ON still holds, so surviving sessions cannot write. The sidecar does not retry because it cannot safely distinguish a pooled operator promotion session. After promotion, the operator drains the fenced former primary until a pass finds no sessions or spec.connectionDrainTimeout expires. The sidecar's /status reports self_fenced, scoped to the current sidecar process.
WARNSELF-FENCING: super_read_only write failed but the fence is in place; skipping connection evictionerror, podThe SET GLOBAL returned an error, but a follow-up @@super_read_only read proved the write landed. The fence counts as established: /status reports self_fenced=true and SELF-FENCED follows. Eviction is skipped because the fence budget is spent; operator-side recovery performs the bounded follow-up drain.
ERRORSELF-FENCING: super_read_only write outcome is unconfirmed; will probe againerror, probeError, podBoth the SET GLOBAL result and its independent confirmation probe were unavailable. The monitor retains a process-local pending marker and retries @@super_read_only on later ticks instead of getting stuck behind read_only=ON with self_fenced=false.
WARNfencing: could not resolve pending super_read_only writeerror, podA later tick still could not read @@super_read_only; the pending result remains armed for another tick.
WARNSELF-FENCING: previously unconfirmed super_read_only write is now confirmedpodA later tick proved the ambiguous write established super_read_only=ON; /status now reports self_fenced=true.
ERRORSELF-FENCING FAILED: could not set super_read_onlyerror, podThe fence write failed and a follow-up @@super_read_only read confirmed the instance was not super-fenced. The sidecar retries on the next tick.
WARNfencing: could not confirm whether the super_read_only write landederror, podThe immediate @@super_read_only confirmation read failed. The pending outcome is retained and retried on later ticks.
ERRORSELF-FENCED: super_read_only=ON has been set, only Bloodraven can restorepodFinal status; the sidecar will not unfence on its own. The next operator promotion clears it.
INFOfencing: MySQL is writable after prior self-fence; rearming monitorpodAn actor with SUPER privileges (the operator, per the restore contract) made MySQL writable again after a self-fence. The monitor re-arms with a fresh lease window — it will not re-fence until a full leaseTimeout passes with the operator and every peer unreachable again.
INFOfencing: adopted active-site view from peerpeer, activeSite, observedAt, podPeer sidecar relayed a fresher view than what this sidecar had cached. Drives the topology-mismatch rule.

Safety-net events (sidecar startup):

LevelmsgFields
INFOsafety net: set super_read_only=ON as precaution on startuppod
INFOsafety net: this is the active site, clearing super_read_onlysite, pod
INFOsafety net: confirmed standby site, staying fencedsite, activeSite, pod
INFOsafety net: no active site reported by operator, staying fencedpod
WARNsafety net: could not query active site, staying fencederror, pod
ERRORsafety net: failed to clear super_read_only on active siteerror, pod

PITR archiver

Emitted by the sidecar's BinlogArchiver.

LevelmsgFields
INFObinlog archiver startingstorageType, binlogDir, binlogIndex, pollInterval, pod
INFOarchived sealed binlogscount, pod
INFOretention sweep complete(sweep stats), pod
WARNarchive binlogfile, error, pod
WARNretention: delete objectkey, error, pod

Per-upload success/failure is also reflected in the bloodraven_archiver_upload_failures and bloodraven_archiver_last_upload_timestamp_seconds metrics — prefer those for alerting.

Data-at-rest encryption

Emitted when spec.encryptionAtRest.enabled=true. See Data-at-rest encryption for the lifecycle these events trace.

Operator events:

LevelmsgFieldsNotes
INFOkeyring phase transitionsite, from, to, reason, version, fgThe per-site keyring lifecycle advanced. The normal sequence is Pending → Unsealed → Escrowed → Sealed. Any transition to Unsealed from Sealed means the site is deliberately running with a writable keyring (clone or rotation).
INFOkeyring escrowednamespace, group, site, version, secret, digestThe operator accepted a sidecar escrow push and stored a new immutable Secret version. Also emitted by the sidecar with site, version, secret, digest, pod.
INFOrefusing to enable encryption at rest on a live failover groupactiveSite, fgspec.encryptionAtRest.enabled was turned on for a group that is already serving. Rendering is unchanged; existing tablespaces would have stayed plaintext.
WARNkeyring escrow rejectednamespace, group, site, reasonA push to /keyring/escrow failed authentication. The response to the caller is deliberately vague; the reason is only here.
WARNkeyring escrow digest mismatchnamespace, group, site, claimed, computedAn escrow push carried a digest that did not match its payload. Nothing was stored.
ERRORkeyring escrow: store failednamespace, group, site, errorThe operator could not write the escrow Secret. The site stays unsealed and the sidecar keeps retrying.

Sidecar events:

LevelmsgFieldsNotes
INFOkeyring agent enabledpath, escrowArmed, rotate, podSidecar startup. escrowArmed=false means this pod renders a sealed keyring and only reports its digest.
INFOrotated innodb master keysite, podALTER INSTANCE ROTATE INNODB MASTER KEY succeeded.
INFOencrypted the mysql system tablespacesite, podALTER TABLESPACE mysql ENCRYPTION='Y' succeeded on the writable site.
WARNkeyring escrow push failed, will retryerror, site, digest, podThe push was rejected or the operator echoed a digest that did not match. Escrow is retried until it succeeds; the site is not sealed in the meantime.
WARNcould not encrypt the mysql system tablespaceerror, site, podRetried on the next tick.
ERRORkeyring rotation failederror, site, podThe rotation statement failed. The agent still escrows whatever is on disk, because a failed rotation may already have written a key.

Kubernetes Event reasons emitted on the MysqlFailoverGroup:

ReasonTypeWhen
KeyringPhaseNormal / WarningAny keyring phase transition. Warning when the new phase is Failed.
KeyringUnsealedNormalA site was unsealed so a CLONE INSTANCE can rewrap tablespace keys.
KeyringRotationRefusedWarningA rotation was requested for the active primary, or while an ordered update or planned failover was in flight.
KeyringEscrowMissingWarningA sealed site's escrow Secret no longer exists. That site cannot restart. See the DR runbook.
KeyringEscrowCorruptWarningA sealed site's escrow Secret no longer hashes to the recorded digest.
KeyringDigestMismatchWarningA pod rendered sealed is running a keyring that does not match its escrow; the operator refuses to call it sealed.
KeyringNotReadOnlyWarningMySQL reports a writable keyring on a site the operator considers sealed.
EncryptionAdoptionRefusedWarningEncryption was enabled on a group that is already serving.

Dragonfly

Bloodraven optionally co-manages per-site Dragonfly instances and emits the following events when spec.dragonfly.enabled=true. Mirrored by the bloodraven_dragonfly_site_up gauge and the bloodraven_dragonfly_promotions_total{result} counter, plus the matching Dragonfly* Kubernetes Events on the MysqlFailoverGroup.

LevelmsgFieldsNotes
INFOdragonfly: configured replicasite, host, port, fgOperator issued REPLICAOF against a non-active site to align it with the active master.
WARNdragonfly: stale master on non-active sitesite, active, fgA site reports role=master but is not the active site. Auto-rejoin is attempted only when the stale instance has connected_slaves=0 AND master_repl_offset=0 (provably never accepted writes); otherwise the stale master is shed from the active Service via the traffic-label gate and left for human intervention.
INFOstale-master reconfigure: REPLICAOF appliedsite, host, port, fgAuto-rejoin succeeded: the stale master is now linked as a replica of the active master.
WARNstale-master reconfigure: REPLICAOF failedsite, host, port, error, fgAuto-rejoin attempt failed; the next tick retries.
INFOclient-kill: evicted clients from old mastersite, fgAfter a planned-failover Dragonfly promotion, the operator issued CLIENT KILL TYPE NORMAL against the demoted source so application clients reconnect through the active Service.
INFOdragonfly/mysql active-site drift: promoting Dragonfly replica to match MySQLoldSource, target, mysqlActiveSite, fgMySQL active site and Dragonfly master diverged; the manager is promoting the synced Dragonfly replica on the MySQL active site.
INFOdragonfly-only emergency: active master unreachable; promoting replicaoldSource, target, fgDragonfly master failed without a MySQL failover; the manager is promoting the single healthy replica and leaving MySQL status.activeSite unchanged.
INFOdragonfly emergency: REPLTAKEOVER succeededsite, fgAfter an emergency MySQL failover, Dragonfly was promoted with sessions preserved.
WARNdragonfly emergency: REPLTAKEOVER failed; falling backsite, error, fgEmergency promote could not preserve sessions; falling back to REPLICAOF NO ONE.
INFOdragonfly emergency: target promoted via REPLICAOF NO ONE (sessions lost)site, fgEmergency promote completed with empty cache.
WARNdragonfly emergency: REPLICAOF NO ONE failedsite, error, fgBoth promotion paths failed; cache is unavailable. MySQL emergency failover was not affected.
WARNdragonfly emergency: target unreachable; skipping promotionsite, error, fgBounded budget expired before the operator could reach the target.

Kubernetes Event reasons emitted on the MysqlFailoverGroup (visible via kubectl describe):

ReasonWhen
DragonflyPromotionStartedPlanned-failover state machine entered PromotingDragonfly.
DragonflyPromotionCompletedDragonfly target was promoted (planned or emergency).
DragonflyPromotionFailedPromotion command failed; behavior depends on spec.dragonfly.plannedFailover.onSyncTimeout (planned) or is best-effort (emergency).
DragonflyStaleMasterDetectedA non-active site reports master role. Logged + dedup'd in 5-minute windows. Auto-rejoin is attempted in reconcileReplication when connected_slaves=0 AND master_repl_offset=0.
DragonflyOldSiteReconfiguredA stale master passed the auto-rejoin gate and was attached as a replica of the active master via REPLICAOF.
DragonflySyncTimeoutWaitingForDragonflySync exhausted spec.dragonfly.plannedFailover.maxSyncWait.
DragonflyUpgradeStartedSnapshot-restore Dragonfly upgrade annotation was accepted and status.dragonfly.upgrade was initialized.
DragonflyUpgradeRejectedSnapshot-restore upgrade request was invalid or another coordinated operation was running.
DragonflyUpgradeSnapshotStartedActive Dragonfly traffic was shed and the operator is about to issue SAVE.
DragonflyUpgradeSnapshotCompletedSAVE completed against the active Dragonfly master using spec.dragonfly.snapshot.dir.
DragonflyUpgradeCompletedActive and replica Dragonfly pods are on the target image, active traffic is restored, and replicas are linked.
DragonflyUpgradeFailedSnapshot-restore upgrade reached a terminal failure; the operator best-effort restored active traffic.

Lifecycle

LevelmsgFields
INFOstarting bloodraven manager(none)
INFOstarting auxiliary HTTP serveraddr
INFOtopology manager runner starting(none)
INFOstarting topology managerfg
INFOtopology manager stoppedfg
INFOstopping topology managerfg
INFOconfig changed, restarting topology managerfg
INFOrestored lastFailoverTarget from CR statusfg, target
INFOrestored lastFailover from CR statusfg, lastFailover
WARNrestored lastFailoverTarget from out-of-band annotationsfg, target, statusTarget
WARNrestored lastFailover from out-of-band annotationsfg, lastFailover, statusLastFailover
ERRORout-of-band anti-flap annotation unreadable; falling back to CR statusfg, error
ERRORCR status anti-flap state unreadable; ignoring unsafe copyfg, error
INFOstarting graceful shutdownfg
INFOCR deleted — DNSEndpoint will be garbage-collected(none)
INFOsidecar image version differs from the operatoroperatorImage, sidecarImage, operatorTag, sidecarTag
INFOsidecar startinglistenAddr, peerAddresses, bloodravenAddress, leaseTimeout, peerCheckInterval, site, namespace, fg, pod
INFOsidecar stoppedpod
INFOreceived signal, shutting downsignal, pod

Kubernetes Event reasons emitted on the MysqlFailoverGroup:

ReasonTypeWhen
SidecarVersionSkewWarningspec.sidecarImage is tagged with a different release than the running operator. The two halves share a rendering contract, so a mismatch can break the sidecar silently — on a spec.tls group an older sidecar cannot reach MySQL at all. Advisory only: the operator reports skew but never blocks on it, because a re-tagged or locally built image is indistinguishable from a real mismatch.

Stability commitments

WhatStability
msg strings listed in the Event referenceStable. Changes go through a deprecation note in CHANGELOG.md.
Field names listed alongside a stable msgStable. New fields may be added to existing events; existing fields will not be renamed or removed without a deprecation note.
Field value shapes (strings, numbers, durations)Stable for the values listed. GTIDs are passed through verbatim from MySQL — their shape is whatever MySQL emits.
time, level, msg field names themselvesStable. Tied to log/slog defaults.
DEBUG-level recordsUnstable. May appear, disappear, or change shape without notice. Disabled by default.
Ad-hoc INFO/WARN/ERROR records not listed above (e.g. retry warnings, transient probe errors)Best-effort. Field set is intended to be useful but not contractual. Don't build alerts that key on the exact msg string.
Controller-runtime (zap) streamInherited from upstream. Bloodraven does not redefine this stream's shape.

Pipeline integration tips

Filtering operational vs. controller-runtime

Most aggregators (Loki, Elasticsearch, Vector) let you split streams by JSON shape. A reliable predicate:

$.time && $.msg   // operational (slog)
$.ts && $.logger  // controller-runtime (zap)

Per-event alerts

Because every key event has a stable msg, pipeline alerts can be expressed as exact-match filters rather than fragile regexes. Examples for Loki:

# Failover started
{app="bloodraven"} | json | msg = "initiating failover"

# Failover failed (escalate)
{app="bloodraven"} | json | level = "ERROR" and msg = "failover failed"

# Divergence requires manual reclone
{app="bloodraven"} | json | msg = "divergence detected"

# Reclone triggered (track who/what asked for it via fg + recipient)
{app="bloodraven"} | json | msg = "starting bootstrap" and source = "reclone"

# Sidecar self-fenced — page on this
{app="bloodraven-sidecar"} | json | msg =~ "^SELF-FENCING:"

Correlating with metrics and Kubernetes Events

Several stable log events are mirrored by other observable signals — when one fires, the others fire too:

Log eventMetricKubernetes Event
failover completebloodraven_failovers_total{target_site}FailoverExecuted
divergence detectedbloodraven_divergent_transactions{site} > 0DataLossDetected
old primary recovery completebloodraven_divergent_transactions{site} returns to 0RecoveryComplete
state transitionbloodraven_state_transitions_total{site, from, to}(none — too noisy for events)

Prefer metrics for alert thresholds and Kubernetes Events for human notification routing; logs are richest for forensics and timeline reconstruction.

Useful structured fields to index

If your pipeline supports indexing specific fields, the high-value ones are:

  • fg — partitions everything by failover group
  • site (and oldPrimary / newPrimary / promotedSite / donor / recipient) — for per-site timelines
  • level — for severity routing
  • source — for bootstrap/reclone disambiguation
  • error — full error string from the operator's error chain
Copyright © 2026