Skip to content
62 linesCodeBlameRaw
1/**
2 * What each app on g1t.page carries for observability (services/telemetry),
3 * set as it is uploaded:
4 *
5 * - the telemetry service as its Tail Worker, so every request it answers
6 * arrives there with its logs and exceptions;
7 * - tags naming its project and deployment, which arrive with each event;
8 * - OpenTelemetry's standard variables, so an SDK in the app sends spans,
9 * logs and metrics to the project without configuration: the endpoint,
10 * the project's deployments token, the service's name and a resource
11 * naming the environment, deployment and commit. A variable the project
12 * sets itself wins: an app can send elsewhere.
13 * - under its hostname in `DOMAINS`, what the dispatcher writes on each
14 * request's line, so requests a site's files answered are filed too.
15 */
16import { TELEMETRY_OTLP_ENDPOINT, TELEMETRY_TAG } from "@g1t/contracts/telemetry";
17
18/** The telemetry service's Worker, named as each app's Tail Worker. */
19export const TELEMETRY_WORKER = "g1t-telemetry";
20
21export const TAIL_CONSUMERS = [{ service: TELEMETRY_WORKER }];
22
23export function telemetryTags(projectId: string, deploymentId: string): string[] {
24 return [`${TELEMETRY_TAG.project}${projectId}`, `${TELEMETRY_TAG.deployment}${deploymentId}`];
25}
26
27export type AppTelemetry = {
28 project: string;
29 deployment: string;
30 environment: string;
31 service: string;
32 commit: string | null;
33};
34
35type Runtime = { secrets: Record<string, string>; variables: Record<string, string> };
36
37/** OpenTelemetry's resource attributes, written as its variable takes them. */
38export function resourceAttributes(app: AppTelemetry): string {
39 const pairs: [string, string][] = [
40 ["deployment.environment.name", app.environment],
41 ["g1t.deployment.id", app.deployment],
42 ["g1t.project.id", app.project],
43 ];
44 if (app.commit) pairs.push(["vcs.ref.head.revision", app.commit]);
45 return pairs.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join(",");
46}
47
48/** The app's runtime with OpenTelemetry's variables added, where the project set none of its own. */
49export function withOtel(runtime: Runtime, app: AppTelemetry, token: string | null): Runtime {
50 const has = (name: string) => name in runtime.variables || name in runtime.secrets;
51 const variables = { ...runtime.variables };
52 const secrets = { ...runtime.secrets };
53 // An app the project points elsewhere is left to it, whole.
54 if (has("OTEL_EXPORTER_OTLP_ENDPOINT") || !token) return runtime;
55 variables.OTEL_EXPORTER_OTLP_ENDPOINT = TELEMETRY_OTLP_ENDPOINT;
56 if (!has("OTEL_EXPORTER_OTLP_PROTOCOL")) variables.OTEL_EXPORTER_OTLP_PROTOCOL = "http/json";
57 if (!has("OTEL_SERVICE_NAME")) variables.OTEL_SERVICE_NAME = app.service;
58 const resource = resourceAttributes(app);
59 variables.OTEL_RESOURCE_ATTRIBUTES = runtime.variables.OTEL_RESOURCE_ATTRIBUTES ? `${resource},${runtime.variables.OTEL_RESOURCE_ATTRIBUTES}` : resource;
60 if (!has("OTEL_EXPORTER_OTLP_HEADERS")) secrets.OTEL_EXPORTER_OTLP_HEADERS = `authorization=Bearer ${token}`;
61 return { variables, secrets };
62}