feat(server): add heartbeat gauge + first-export-attempt log for OTLP (#20230)

## Summary
- Adds a trivial always-on observable gauge (`twenty.heartbeat = 1`) so
the OTLP exporter fires on every 10s collection tick, even on idle pods.
Without this, `PeriodicExportingMetricReader` skips the export when
`scopeMetrics` is empty, so the process-log wrapper never runs and we
can't prove OTLP connectivity.
- Adds a one-time "first periodic export attempt" log line inside the
wrapped exporter, completing the startup log sequence: `OTLP reader
enabled` → `first periodic export attempt` → `first export ok` / `export
failed`.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Charles Bochet
2026-05-04 12:30:59 +02:00
committed by GitHub
parent fa8304d323
commit a3cf565ba3
+23
View File
@@ -70,12 +70,19 @@ const serializeOtlExportFailure = (error: unknown): string => {
};
let otlpMetricsFirstExportSuccessLogged = false;
let otlpMetricsFirstExportAttemptLogged = false;
const wrapOtlMetricExporterWithProcessLogs = (
inner: OTLPMetricExporter,
otlpEndpointForLog: string,
): PushMetricExporter => ({
export(metrics: ResourceMetrics, resultCallback) {
if (!otlpMetricsFirstExportAttemptLogged) {
otlpMetricsFirstExportAttemptLogged = true;
console.log(
`${OTLP_METRICS_LOG_PREFIX} first periodic export attempt | endpoint=${otlpEndpointForLog}`,
);
}
const totalMetricData = metrics.scopeMetrics.reduce(
(accumulator, scopeMetric) => accumulator + scopeMetric.metrics.length,
0,
@@ -177,3 +184,19 @@ const meterProvider = new MeterProvider({
});
opentelemetry.metrics.setGlobalMeterProvider(meterProvider);
// Always-on gauge so the OTLP exporter fires on every collection tick,
// even when the process is otherwise idle. This guarantees the process-log
// wrapper above can prove connectivity (first export ok / export failed).
if (meterDrivers.includes(MeterDriver.OpenTelemetry)) {
const heartbeatMeter = opentelemetry.metrics.getMeter(
'twenty-server-heartbeat',
);
const heartbeat = heartbeatMeter.createObservableGauge('twenty.heartbeat', {
description: 'Always-on gauge (1) to prove OTLP export pipeline',
});
heartbeat.addCallback((observableResult) => {
observableResult.observe(1);
});
}