| 118 | |
| 119 | /** Drain the buffer to CloudWatch. Safe to call repeatedly; await before exit. */ |
| 120 | export async function flushHostedKeyMetrics(): Promise<void> { |
| 121 | if (dropped > 0) { |
| 122 | logger.warn('Dropped hosted-key metric points (buffer cap reached)', { dropped }) |
| 123 | dropped = 0 |
| 124 | } |
| 125 | if (!ENABLED || buffer.length === 0) return |
| 126 | const pending = buffer |
| 127 | buffer = [] |
| 128 | for (let i = 0; i < pending.length; i += MAX_BATCH) { |
| 129 | const MetricData = pending.slice(i, i + MAX_BATCH) |
| 130 | try { |
| 131 | await getClient().send(new PutMetricDataCommand({ Namespace: NAMESPACE, MetricData })) |
| 132 | } catch (err) { |
| 133 | // Telemetry must never break the request path — log and drop the batch. |
| 134 | logger.warn('PutMetricData failed; dropping batch', { |
| 135 | count: MetricData.length, |
| 136 | error: err instanceof Error ? err.message : String(err), |
| 137 | }) |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | export const hostedKeyMetrics = { |
| 143 | recordUsed(labels: { provider: string; tool: string; key: string }) { |