import { Injectable,NestMiddleware } from '@nestjs/common';
import type { NextFunction,Request,Response } from 'express';
import { ObservabilityService } from './observability.service';
/**
* Synthetic Operations is the only server-owned synthetic surface. The
* origin is derived from the canonical route, never from a client header, so
* production requests cannot be relabeled as synthetic telemetry (or vice
* versa) by callers.
*/
export function classifyRequestDataOrigin(path:string): 'production'|'synthetic' {
const normalized=path.split('?')[0]??'';
return /(?:^|\/)admin\/synthetic-operations(?:\/|$)/.test(normalized)
? 'synthetic'
: 'production';
}
@Injectable()
export class ObservabilityMiddleware implements NestMiddleware {
constructor(private readonly metrics:ObservabilityService) {}
use(req:Request,res:Response,next:NextFunction) {
const started=process.hrtime.bigint();
res.on('finish',()=>{
const durationMs=Number(process.hrtime.bigint()-started)/1_000_000;
this.metrics.recordHttp({
method:req.method,
route:req.route?.path??req.path,
status:res.statusCode,
durationMs,
dataOrigin:classifyRequestDataOrigin(req.path),
});
const slow=Number(process.env.SLOW_REQUEST_MS??1000);
if(durationMs>=slow) {
console.warn(JSON.stringify({
level:'warn',event:'slow_request',method:req.method,path:req.path,
status:res.statusCode,durationMs:Math.round(durationMs),
correlationId:(req as any).correlationId??null,
}));
}
});
next();
}
}
