For same-city dual-active and three-center financial deployments, pinpointing failures is the hard part. This piece breaks down split logging, broken traces, and alert storms, with a unified ingestion design and noise-reduction strategy.
When a financial core system moves to same-city dual-active or two-city three-center topologies, the hard part isn't the application refactor — it's answering "which data center, which call path broke" when something fails. Split logging across sites, traces that die at the gateway, and alert storms are the three recurring pains. Here's a unified approach. The goal isn't more dashboards — it's one place where an on-call engineer can trace a single transaction end to end across every data center in under a minute.
All three share one root cause: observability was built per-site, not as one system — so the fix is architectural, not a matter of adding another panel.
Deploy a set of collectors (OTel Collector with local buffering) in each data center, all reporting to a central OBSERVE. The collectors carry a local disk queue, so data isn't lost during network blips and backfills automatically once the link recovers:
exporters:
otlphttp:
endpoint: https://ob.jjhub.cn/otel
sending_queue:
storage: file_storage
queue_size: 10000
extensions:
file_storage:
directory: /var/lib/otelcol
For tracing, require every service to use the same SDK, and propagate the trace context explicitly at the gateway and the message queue — don't carry it only in HTTP headers. The MQ message header must also carry traceparent, otherwise the transaction breaks the moment it crosses onto the queue.
The most common break point is the message queue, because trace context doesn't travel in the payload — it has to ride in the message header. Concretely, the producer injects traceparent into the MQ header, and the consumer's SDK extracts it and starts a child span. On the producer side:
Map<String, String> headers = new HashMap<>();
GlobalOpenTelemetry.getPropagators().getTextMapPropagator()
.inject(Context.current(), headers, (map, k, v) -> map.put(k, v));
producer.send(new Message(payload, headers));
On the consumer side, extract before processing:
Context ctx = GlobalOpenTelemetry.getPropagators().getTextMapPropagator()
.extract(Context.current(), msg.getHeaders(), (map, k) -> map.get(k));
try (Scope s = ctx.makeCurrent()) { process(msg); }
Use the W3C traceparent format everywhere and forbid bespoke header names — mixed formats are how traces silently split. Gateways are the second break point: if your gateway rewrites headers, verify it whitelists the trace headers rather than dropping them.
abs(rate_A - rate_B) / rate_B > 0.1 holds for five minutes.In a three-center setup, plan storage around the busiest site plus headroom, not the sum of all sites — traffic shifts during a failover, and the surviving sites suddenly carry the load. Set retention per data class: raw logs for a few days of active triage, aggregated metrics longer, and sampled traces longest for capacity analysis. Review the sampling policy quarterly; tail-based sampling in particular can drift as traffic patterns change, and a policy tuned last quarter may now be dropping traces you need.
Finance has hard requirements on retention and audit: encrypted log storage, automatic archiving by retention period, and query audit trails. OBSERVE supports field-level masking (card numbers and ID numbers masked automatically) and query auditing, satisfying dengbao and data-security requirements. Plan these during design, not before acceptance — reviewers will ask you to justify cross-site data flows, audit scope, and the masking field list. In a multi-site setup, decide early which fields are masked at the edge (before they leave a data center) versus centrally, because that choice affects both compliance posture and troubleshooting ergonomics.