Skip to content

Commit

Apps on g1t.page send Cloudflare's own traces to Observability: a span for each fetch, KV, D1, R2 and Durable Object call, with nothing in their code. Each upload names the Workers Observability destination g1t-traces (TRACES_DESTINATION), which sends OTLP to otel.g1t.sh/cloudflare/v1/traces with the telemetry service's export key; traces only, since logs already arrive through the Tail Worker, and nothing is kept in Cloudflare's own storage. The git guide says that with two-factor authentication on, git takes an access token, not the account password.

syntaqxcommitted Parentdf7eb7cBrowse files
7 files+86−60/7 viewed
+4−1
3030 Pushing, and reading private repositories, needs credentials. Use your
3131 username, and as the password either your account password or an
3232 [access token](https://g1t.sh/settings/tokens). Tokens are recommended: they can be revoked
33−individually and they also work for the API.
33+individually and they also work for the API. With
34+[two-factor authentication](/guides/authentication/#two-factor-authentication)
35+on, your password is not accepted for git, since it would get past your
36+second factor: use a token, with Code at read and write to push.
3437
3538 To avoid typing it each time, let git store it:
3639
+11−1
127127 export default instrument({ async fetch(request, env) { /* … */ } }, config);
128128 ```
129129
130+### Cloudflare's own spans
131+
132+Every app on g1t.page is also traced by Cloudflare itself, with nothing in
133+its code: a span for each `fetch` it makes, each KV, D1, R2 and Durable
134+Object call, and its cache, under the request. They arrive in the
135+request's trace, next to any spans of the app's own SDK. Its logs are not
136+sent this way: they already arrive with each request.
137+
130138 Set `OTEL_EXPORTER_OTLP_ENDPOINT` yourself, under the project's
131139 [secrets and variables](/guides/secrets-and-variables/), to send the
132140 app's OpenTelemetry somewhere else instead: g1t then sets none of these.
263271 pages. Log bodies are indexed for full-text search.
264272
265273 - **g1t.page.** The dispatcher in front of every app names the telemetry
266− service as its Tail Worker, and so does each app. The dispatcher's half
274+ service as its Tail Worker, and so does each app. Each app's own
275+ Cloudflare tracing is exported to the service too, over OTLP, and filed
276+ under the app's project and deployment. The dispatcher's half
267277 of a request (status, time, place) and the app's half (logs, exceptions,
268278 CPU time) arrive apart and are joined by the request's id.
269279 - **OTLP.** The endpoint reads the project from the token, checks it
+5−2
99 * else.
1010 */
1111
12−import { TAIL_CONSUMERS } from "./telemetry";
12+import { TAIL_CONSUMERS, observabilityMetadata } from "./telemetry";
1313 import { APP_CLASS, APP_WORKER, REGISTRY_HOST, applicationName, applicationRequest, pushLoginRequest, rolloutRequest } from "./containers";
1414 import { redirectScript } from "./redirect";
1515
5959 private readonly token: string,
6060 private readonly account: string,
6161 readonly namespace: string,
62+ /** The Workers Observability destination apps send their traces to (src/telemetry.ts); none, none sent. */
63+ private readonly tracesDestination: string | null = null,
6264 ) {}
6365
6466 private async call<T>(method: string, path: string, body?: BodyInit, contentType?: string): Promise<T> {
142144 tags,
143145 // Each request it answers, with its logs, goes to observability (src/telemetry.ts).
144146 tail_consumers: TAIL_CONSUMERS,
147+ ...observabilityMetadata(this.tracesDestination),
145148 ...(completionJwt ? { assets: { jwt: completionJwt, config: assetsConfig } } : {}),
146149 }),
147150 );
209212 /** Puts a container app's Worker (`APP_WORKER`, g1t's own) in the namespace, with `metadata`. */
210213 async putContainerWorker(script: string, metadata: Record<string, unknown>): Promise<void> {
211214 const form = new FormData();
212− form.append("metadata", JSON.stringify(metadata));
215+ form.append("metadata", JSON.stringify({ ...metadata, ...observabilityMetadata(this.tracesDestination) }));
213216 form.append("index.js", new File([APP_WORKER], "index.js", { type: "application/javascript+module" }));
214217 await this.call("PUT", this.scriptPath(script), form);
215218 }
+6−1
134134 DOMAINS?: KVNamespace;
135135 /** Observability: each project's deployments token (services/telemetry). */
136136 TELEMETRY?: ServiceBinding;
137+ /**
138+ * The Workers Observability destination apps send Cloudflare's own
139+ * traces to (src/telemetry.ts); empty, none are sent.
140+ */
141+ TRACES_DESTINATION?: string;
137142 /** The g1t.page zone, where custom hostnames are added (Cloudflare for SaaS). */
138143 CUSTOM_HOSTNAMES_ZONE_ID?: string;
139144 /**
373378
374379 private get cloudflare(): Cloudflare | null {
375380 const token = this.env.CLOUDFLARE_API_TOKEN;
376− return token ? new Cloudflare(token, this.env.CLOUDFLARE_ACCOUNT_ID, this.env.DISPATCH_NAMESPACE) : null;
381+ return token ? new Cloudflare(token, this.env.CLOUDFLARE_ACCOUNT_ID, this.env.DISPATCH_NAMESPACE, this.env.TRACES_DESTINATION || null) : null;
377382 }
378383
379384 private get db() {
+34−0
1+import assert from "node:assert/strict";
2+import test from "node:test";
3+
4+import { observabilityMetadata, resourceAttributes, telemetryTags, withOtel } from "./telemetry.ts";
5+
6+const app = { project: "prj_1", deployment: "dpl_1", environment: "production", service: "web", commit: "abc123" };
7+
8+test("an app's OpenTelemetry variables point at the project, unless it set its own", () => {
9+ const given = withOtel({ secrets: {}, variables: { FROM: "config" } }, app, "g1to_prj_1_secret");
10+ assert.equal(given.variables.OTEL_EXPORTER_OTLP_ENDPOINT, "https://otel.g1t.sh");
11+ assert.equal(given.variables.OTEL_SERVICE_NAME, "web");
12+ assert.equal(given.secrets.OTEL_EXPORTER_OTLP_HEADERS, "authorization=Bearer g1to_prj_1_secret");
13+ assert.match(given.variables.OTEL_RESOURCE_ATTRIBUTES, /g1t\.deployment\.id=dpl_1/);
14+ assert.equal(given.variables.FROM, "config");
15+ // The project's own endpoint wins, whole.
16+ const own = { secrets: {}, variables: { OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com" } };
17+ assert.deepEqual(withOtel(own, app, "g1to_x"), own);
18+ // No token (an untrusted build): nothing added.
19+ assert.deepEqual(withOtel({ secrets: {}, variables: {} }, app, null), { secrets: {}, variables: {} });
20+});
21+
22+test("tags and resource attributes name the project and deployment", () => {
23+ assert.deepEqual(telemetryTags("prj_1", "dpl_1"), ["g1t-p:prj_1", "g1t-d:dpl_1"]);
24+ assert.equal(resourceAttributes(app), "deployment.environment.name=production,g1t.deployment.id=dpl_1,g1t.project.id=prj_1,vcs.ref.head.revision=abc123");
25+});
26+
27+test("Cloudflare's traces go to the destination, logs stay with the Tail Worker", () => {
28+ assert.deepEqual(observabilityMetadata(null), {});
29+ const metadata = observabilityMetadata("g1t-traces").observability as Record<string, any>;
30+ assert.equal(metadata.enabled, true);
31+ assert.deepEqual(metadata.traces.destinations, ["g1t-traces"]);
32+ assert.equal(metadata.traces.persist, false);
33+ assert.equal(metadata.logs.enabled, false);
34+});
+20−0
2020
2121 export const TAIL_CONSUMERS = [{ service: TELEMETRY_WORKER }];
2222
23+/**
24+ * Cloudflare's own tracing of an app (a span for each fetch, KV, D1 and
25+ * other call it makes), sent to the Workers Observability destination
26+ * named `destination`, which points at the telemetry service's
27+ * `/cloudflare/v1/traces`. Traces only: the app's logs already arrive
28+ * through its Tail Worker, and nothing is kept in Cloudflare's own
29+ * storage. Nothing at all without a destination.
30+ */
31+export function observabilityMetadata(destination: string | null | undefined): Record<string, unknown> {
32+ if (!destination) return {};
33+ return {
34+ observability: {
35+ enabled: true,
36+ head_sampling_rate: 1,
37+ logs: { enabled: false, invocation_logs: false },
38+ traces: { enabled: true, destinations: [destination], head_sampling_rate: 1, persist: false },
39+ },
40+ };
41+}
42+
2343 export function telemetryTags(projectId: string, deploymentId: string): string[] {
2444 return [`${TELEMETRY_TAG.project}${projectId}`, `${TELEMETRY_TAG.deployment}${deploymentId}`];
2545 }
+6−1
5757 // while off, a container build is refused at its registry step.
5858 "CONTAINERS": "off",
5959 // Where container apps' Workers report how long they ran.
60− "G1T_API": "https://api.g1t.sh"
60+ "G1T_API": "https://api.g1t.sh",
61+ // Cloudflare's own tracing of each app (a span for every fetch, KV,
62+ // D1 and other call), sent to this Workers Observability destination
63+ // on the account, which points at otel.g1t.sh/cloudflare/v1/traces
64+ // with the telemetry service's CLOUDFLARE_EXPORT_KEY. Empty: none.
65+ "TRACES_DESTINATION": "g1t-traces"
6166 },
6267 // Custom domains: hostname to app, written here and read by the
6368 // dispatcher (services/pages). Made by scripts/setup-custom-domains.sh.