Skip to content

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:

VariableDescription
APM_ENABLEDSet to true to enable
APM_TYPEMust be otlp
APM_ENDPOINTGrafana OTLP gateway URL
APM_INSTANCE_IDGrafana Cloud stack ID (used as Basic auth username)
APM_API_KEYGrafana 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();
  }
});