1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64import type { TelemetryReporter, QueryMetric } from '../types';
export interface CloudWatchReporterConfig {
/**
* URL of the backend endpoint that proxies to CloudWatch PutMetricData.
* Expected to accept POST with a CloudWatch-shaped JSON body.
*/
endpoint: string;
/** CloudWatch namespace. Default: 'Equalify/Frontend' */
namespace?: string;
/** Extra headers sent with every request (e.g. an API key). */
headers?: Record<string, string>;
}
interface MetricDatum {
MetricName: string;
Dimensions: { Name: string; Value: string }[];
Value: number;
Unit: 'Milliseconds';
Timestamp: string;
}
interface PutMetricDataPayload {
Namespace: string;
MetricData: MetricDatum[];
}
export class CloudWatchReporter implements TelemetryReporter {
private readonly endpoint: string;
private readonly namespace: string;
private readonly headers: Record<string, string>;
constructor(config: CloudWatchReporterConfig) {
this.endpoint = config.endpoint;
this.namespace = config.namespace ?? 'Equalify/Frontend';
this.headers = { 'Content-Type': 'application/json', ...config.headers };
}
async report(metrics: QueryMetric[]): Promise<void> {
const payload: PutMetricDataPayload = {
Namespace: this.namespace,
MetricData: metrics.map((m) => ({
MetricName: 'QueryDuration',
Dimensions: [
{ Name: 'QueryName', Value: m.queryName },
{ Name: 'Environment', Value: m.environment },
{ Name: 'Status', Value: m.status },
],
Value: m.duration,
Unit: 'Milliseconds',
Timestamp: new Date(m.timestamp).toISOString(),
})),
};
await fetch(this.endpoint, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(payload),
// keepalive allows the request to outlive the page (e.g. beforeunload flush)
keepalive: true,
});
}
}