Application Performance Monitoring (APM) β
APM stands for Application Performance Monitoring, it allows to see execution traces and improve overall understanding of what happened and when. It is key to be able to debug quickly at scale issues. It is configurable, expandable.
We use OpenTelemetry to trace requests and correlate them with logs. Traces are sent to Grafana Tempo via the OTLP gateway and visible in Grafana β Explore β Tempo.
App setup β
APM is initialised in src/tracing.ts, which is required before NestJS boots. It is enabled via environment variables:
| Variable | Description |
|---|---|
APM_ENABLED | Set to true to enable |
APM_TYPE | Must be otlp |
APM_ENDPOINT | Grafana OTLP gateway URL |
APM_INSTANCE_ID | Grafana Cloud stack ID (used as Basic auth username) |
APM_API_KEY | Grafana Cloud access token with traces:write scope |
Auto-instrumentation covers HTTP, MongoDB, Redis, and BullMQ out of the box. No code changes needed for standard request tracing.
Example: Adding span attributes β
Use the OpenTelemetry API to enrich the current span with user context or business metadata:
typescript
import { trace } from '@opentelemetry/api';
const span = trace.getActiveSpan();
if (span) {
span.setAttributes({
'user.id': user.firebaseUid,
'user.email': userData.email,
'tellia.company_id': userData.companyId ?? 'N/A',
'tellia.phone_number': userData.phoneNumber ?? 'N/A',
});
}- Attributes are visible in Grafana Tempo on the individual span.
- Prefer namespaced keys (
tellia.*) for custom attributes to avoid collisions with OTel semantic conventions.
Example: Creating a custom span β
typescript
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('tellia-agri-backend');
await tracer.startActiveSpan('my-operation', async (span) => {
try {
await doSomething();
} catch (err) {
span.recordException(err as Error);
throw err;
} finally {
span.end();
}
});