import { NodeSDK } from '@opentelemetry/sdk-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { trace, metrics } from '@opentelemetry/api'; import { logs } from '@opentelemetry/api-logs'; import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'; import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; export function startTelemetry() { if(!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return null; const sdk = new NodeSDK({serviceName:'safetynet-control-plane',traceExporter:new OTLPTraceExporter(), metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter(),exportIntervalMillis:10000})], logRecordProcessors:[new BatchLogRecordProcessor({exporter:new OTLPLogExporter()})]}); sdk.start(); return sdk; } export const tracer=trace.getTracer('safetynet','0.1.0'); export function recordSecurityEvent(kind,metadata,eventId){ // Only bounded, allowlisted fields. Never emit body text, rules, vectors or prompts. const decision=metadata.decision??(kind.includes('block')?'block':'other'); metrics.getMeter('safetynet').createCounter('safetynet.security.events').add(1,{'event.action':kind,'dlp.decision':decision}); logs.getLogger('safetynet.security').emit({severityNumber:kind.includes('block')?13:9,severityText:kind.includes('block')?'WARN':'INFO',body:kind, attributes:{'event.id':eventId,'event.action':kind,'dlp.decision':decision,...(metadata.ruleId?{'rule.id':metadata.ruleId}:{}),...(metadata.policyVersion?{'safetynet.policy.version':metadata.policyVersion}:{})}}); }