Add Prometheus metrics, structured logs, and Sentry integration to a NestJS endpoint.
Use this skill when:
A step-by-step guide to making any backend endpoint fully observable: metrics → logs → Sentry → verification.
Inject MetricsService and record counters + histograms:
@Injectable()
export class MyService {
constructor(
private readonly metrics: MetricsService,
// ...
) {}
async doWork(type: string): Promise<Result> {
const start = Date.now();
try {
const result = await this.actualWork();
const duration = Date.now() - start;
// ✅ Record after success
this.metrics.scenarioRunsTotal.inc({ type, status: 'success' });
this.metrics.scenarioRunDurationSeconds.observe({ type }, duration / 1000);
return result;
} catch (err) {
// ✅ Record on failure
this.metrics.scenarioRunsTotal.inc({ type, status: 'error' });
throw err;
}
}
}
Naming rules (from observability-conventions rule):
<domain>_<action>_total<domain>_<action>_secondstype and statusIf you need a NEW metric (not scenario-related), add it to MetricsService constructor:
this.myNewCounter = new client.Counter({
name: 'my_domain_events_total',
help: 'Description of what this counts',
labelNames: ['type', 'status'],
registers: [this.registry],
});
Inject AppLogger and use logScenario for domain events:
constructor(
private readonly logger: AppLogger,
) {}
// ✅ Success path
this.logger.logScenario('info', 'Operation completed', {
scenarioType: type,
scenarioId: result.id,
duration,
});
// ✅ Warn path (expected failure)
this.logger.logScenario('warn', 'Validation failed', {
scenarioType: type,
error: 'Reason for failure',
});
// ✅ Error path (unexpected)
this.logger.logScenario('error', 'Unexpected failure', {
scenarioType: type,
error: err.message,
});
Required fields in every scenario log: scenarioType, scenarioId (if available), duration (if applicable).
Logs are JSON to stdout → collected by Promtail → queryable in Loki as {app="signal-lab"}.
import * as Sentry from '@sentry/node';
// For unexpected exceptions (5xx):
const error = new Error(`Descriptive message [context=${ctx}]`);
Sentry.captureException(error);
throw new InternalServerErrorException(error.message);
// For expected warnings (4xx):
Sentry.addBreadcrumb({
category: 'my-domain',
message: `Validation failed: ${reason}`,
level: 'warning',
});
// Then throw BadRequestException (don't captureException for 4xx)
After adding observability, verify manually:
curl -s localhost:3001/metrics | grep my_domain — metric should appeardocker logs signal-lab-backend-1 | tail -5 — JSON log should appear{app="signal-lab"} | json | scenarioType="my-type"GET /metrics responsescenarioType label filter