Skip to content

Commit 683de4d

Browse files
authored
fix(telemetry): wire otel log processor and shutdown flush (#364)
1 parent 7099f3f commit 683de4d

3 files changed

Lines changed: 92 additions & 9 deletions

File tree

src/telemetry/TROUBLESHOOTING.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# OTEL Logs Missing While Traces Work
2+
3+
Use this runbook when traces are visible but application logs are missing in
4+
Grafana Loki.
5+
6+
## Symptoms
7+
8+
- Traces for a service are visible in Tempo/Grafana.
9+
- Loki mostly shows infrastructure logs (for example `traefik`).
10+
- Application logs for the same service are absent.
11+
12+
## Decision Tree
13+
14+
1. **Collector receives logs?**
15+
- If no: issue is app emission or app-to-collector transport.
16+
- If yes: continue.
17+
2. **Collector exports logs successfully?**
18+
- If no: issue is collector exporter config/connectivity.
19+
- If yes: continue.
20+
3. **Loki indexes expected service label?**
21+
- If no: issue is label/resource mapping.
22+
- If yes: continue.
23+
4. **Direct Loki `query_range` returns service logs?**
24+
- If no: issue is ingest/drop path or tenant mismatch.
25+
- If yes: issue is Grafana query/time range/datasource config.
26+
27+
## Commands
28+
29+
Run on the observability host (example machine: `pophub`).
30+
31+
```bash
32+
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' \
33+
| egrep -i 'loki|tempo|otel|collector|traefik|edge-graphql'
34+
35+
docker logs --since 15m otel-collector 2>&1 \
36+
| grep -Ei 'otlp|logs|export|loki|error|dropped|failed'
37+
38+
docker logs --since 15m loki 2>&1 \
39+
| grep -Ei 'error|warn|otlp|push|query'
40+
```
41+
42+
If Loki is not bound on host `localhost:3100`, run queries from a container on
43+
the same Docker network:
44+
45+
```bash
46+
docker run --rm --network traefik curlimages/curl:8.10.1 \
47+
-sG 'http://loki:3100/loki/api/v1/label/service_name/values'
48+
```
49+
50+
Use range queries for log streams:
51+
52+
```bash
53+
START_NS=$(($(date -u +%s)-1800))000000000
54+
END_NS=$(date -u +%s)000000000
55+
56+
docker run --rm --network traefik curlimages/curl:8.10.1 \
57+
-sG 'http://loki:3100/loki/api/v1/query_range' \
58+
--data-urlencode 'query={service_name="on-the-edge"}' \
59+
--data-urlencode "start=${START_NS}" \
60+
--data-urlencode "end=${END_NS}" \
61+
--data-urlencode 'limit=100'
62+
```
63+
64+
## Application-Side Root Cause Seen In This Repo
65+
66+
`OtelStream` emits logs through the global OpenTelemetry `LoggerProvider`.
67+
If that provider is created without processors, `emit()` calls do not export.
68+
69+
Required provider wiring:
70+
71+
- Create `BatchLogRecordProcessor` with `OTLPLogExporter`.
72+
- Pass it to `LoggerProvider` via `processors: [...]`.
73+
- Set the provider globally with `logs.setGlobalLoggerProvider(...)`.
74+
- Shutdown the provider on app close to flush batched records.
75+
76+
## Remediation Checklist
77+
78+
- Verify `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` is reachable from app runtime.
79+
- Verify collector logs pipeline has receiver + processors + Loki exporter.
80+
- Verify Loki label keys used in queries (`service_name` vs `service.name`).
81+
- Verify app logger provider includes processors.
82+
- Verify graceful shutdown flush for logger provider.

src/telemetry/telemetry.factory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class TelemetryFactory {
4141
);
4242
this.loggerProvider = new LoggerProvider({
4343
resource: this.resource,
44+
processors: [this.batchLogProcessor],
4445
});
4546
this.logger.log('Setting global logger provider for OpenTelemetry');
4647
logs.setGlobalLoggerProvider(this.loggerProvider);

src/telemetry/telemetry.service.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@ export class TelemetryService implements OnAppBootstrap, OnAppClose {
1616
private readonly sdk?: NodeSDK;
1717
private readonly enabled: boolean;
1818

19-
constructor(factory: TelemetryFactory, secret: SecretService) {
19+
constructor(
20+
private readonly factory: TelemetryFactory,
21+
secret: SecretService,
22+
) {
2023
this.enabled = !secret.isCI();
2124
if (!this.enabled) {
2225
this.logger.log('OpenTelemetry disabled in CI mode');
2326
return;
2427
}
25-
this.sdk = this.initializeSDK(factory, secret);
28+
this.sdk = this.initializeSDK(secret);
2629
}
2730

28-
private initializeSDK(
29-
factory: TelemetryFactory,
30-
secret: SecretService,
31-
): NodeSDK {
31+
private initializeSDK(secret: SecretService): NodeSDK {
3232
return new NodeSDK({
33-
resource: factory.resource,
33+
resource: this.factory.resource,
3434
traceExporter: new OTLPTraceExporter({
3535
url: secret.get('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'),
3636
}),
@@ -40,7 +40,6 @@ export class TelemetryService implements OnAppBootstrap, OnAppClose {
4040
}),
4141
exportIntervalMillis: 60000 * 5, // Export metrics every 5 minutes
4242
}),
43-
logRecordProcessors: [factory.batchLogProcessor],
4443
instrumentations: [
4544
new HttpInstrumentation(),
4645
new MongoDBInstrumentation(),
@@ -61,6 +60,7 @@ export class TelemetryService implements OnAppBootstrap, OnAppClose {
6160
return;
6261
}
6362
this.logger.log('Shutting down OpenTelemetry');
64-
return this.sdk.shutdown();
63+
await this.sdk.shutdown();
64+
await this.factory.loggerProvider.shutdown();
6565
}
6666
}

0 commit comments

Comments
 (0)