Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/assets-script-version-analytics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/workers-shared": minor
---

Emit customer-facing Asset Worker request analytics separately

Asset Worker requests now write a separate customer-facing analytics event that includes the customer Worker version UUID supplied by the assets pipeline. This enables analytics consumers to filter static asset metrics by the versions selected in the Workers dashboard without exposing operational Asset Worker fields.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const normalizeConfiguration = (
has_static_routing: configuration?.has_static_routing ?? false,
account_id: configuration?.account_id ?? -1,
script_id: configuration?.script_id ?? -1,
script_version_id: configuration?.script_version_id ?? "",
debug: configuration?.debug ?? false,
};
};
60 changes: 60 additions & 0 deletions packages/workers-shared/asset-worker/src/customer-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ReadyAnalytics } from "./types";

const VERSION = 1;

type Data = {
accountId?: number;
scriptId?: number;
coloId?: number;
status?: number;
hostname?: string;
cacheStatus?: string;
scriptVersionId?: string;
};

/**
* Customer-facing request analytics. Keep this payload limited to fields that
* are intentionally exposed through the Workers Analytics GraphQL API.
*/
export class CustomerAnalytics {
private data: Data = {};

constructor(private readyAnalytics?: ReadyAnalytics) {}

setData(newData: Partial<Data>) {
this.data = { ...this.data, ...newData };
}

write() {
if (!this.readyAnalytics) {
return;
}

this.readyAnalytics.logEvent({
version: VERSION,
accountId: this.data.accountId,
indexId: this.data.scriptId?.toString(),
doubles: [
undefined, // double1
this.data.coloId, // double2
undefined, // double3
undefined, // double4
this.data.status, // double5
],
blobs: [
this.data.hostname?.substring(0, 256), // blob1
undefined, // blob2
undefined, // blob3
undefined, // blob4
undefined, // blob5
undefined, // blob6
undefined, // blob7
this.data.cacheStatus, // blob8
undefined, // blob9
undefined, // blob10
undefined, // blob11
this.data.scriptVersionId, // blob12
],
});
}
}
16 changes: 16 additions & 0 deletions packages/workers-shared/asset-worker/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { mockJaegerBinding } from "../../utils/tracing";
import { Analytics, EntrypointType, getRequestKind } from "./analytics";
import { AssetsManifest } from "./assets-manifest";
import { normalizeConfiguration } from "./configuration";
import { CustomerAnalytics } from "./customer-analytics";
import { ExperimentAnalytics } from "./experiment-analytics";
import { canFetch, handleRequest } from "./handler";
import { handleError, submitMetrics } from "./utils/final-operations";
Expand Down Expand Up @@ -47,6 +48,7 @@ export type Env = {
ENVIRONMENT: Environment;
EXPERIMENT_ANALYTICS: ReadyAnalytics;
ANALYTICS: ReadyAnalytics;
CUSTOMER_ANALYTICS: ReadyAnalytics;
COLO_METADATA: ColoMetadata;
UNSAFE_PERFORMANCE: UnsafePerformanceTimer;
VERSION_METADATA: WorkerVersionMetadata;
Expand Down Expand Up @@ -264,6 +266,7 @@ async function runFetchRequest(
): Promise<Response> {
let sentry: ReturnType<typeof setupSentry> | undefined;
const analytics = new Analytics(env.ANALYTICS);
const customerAnalytics = new CustomerAnalytics(env.CUSTOMER_ANALYTICS);
const performance = new PerformanceTimer(env.UNSAFE_PERFORMANCE);
const startTimeMs = performance.now();

Expand Down Expand Up @@ -312,6 +315,13 @@ async function runFetchRequest(
cohort: cohort ?? "unknown",
requestKind: getRequestKind(request),
});
customerAnalytics.setData({
accountId: config.account_id,
scriptId: config.script_id,
coloId: env.COLO_METADATA.coloId,
hostname: url.hostname,
scriptVersionId: config.script_version_id || undefined,
});
}

return await env.JAEGER.enterSpan("handleRequest", async (span) => {
Expand All @@ -332,12 +342,18 @@ async function runFetchRequest(
);

analytics.setData({ status: response.status });
customerAnalytics.setData({
status: response.status,
cacheStatus: analytics.getData("cacheStatus"),
});

return response;
});
} catch (err) {
customerAnalytics.setData({ status: 500 });
return handleError(sentry, analytics, err);
} finally {
customerAnalytics.write();
submitMetrics(analytics, performance, startTimeMs);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, vi } from "vitest";
import { CustomerAnalytics } from "../src/customer-analytics";
import type { ReadyAnalyticsEvent } from "../src/types";

describe("[Asset Worker] Customer Analytics", () => {
it("writes only the customer-facing request fields", ({ expect }) => {
let captured: ReadyAnalyticsEvent | undefined;
const analytics = new CustomerAnalytics({
logEvent: vi.fn((event: ReadyAnalyticsEvent) => {
captured = event;
}),
});

analytics.setData({
accountId: 123,
scriptId: 456,
coloId: 789,
status: 200,
hostname: "example.com",
cacheStatus: "HIT",
scriptVersionId: "01234567-89ab-cdef-0123-456789abcdef",
});
analytics.write();

expect(captured).toEqual({
version: 1,
accountId: 123,
indexId: "456",
doubles: [undefined, 789, undefined, undefined, 200],
blobs: [
"example.com",
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
"HIT",
undefined,
undefined,
undefined,
"01234567-89ab-cdef-0123-456789abcdef",
],
});
});
});
1 change: 1 addition & 0 deletions packages/workers-shared/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const HeadersSchema = z
.optional();

export const AssetConfigSchema = z.object({
script_version_id: z.string().optional(),
compatibility_date: z.string().optional(),
compatibility_flags: z.array(z.string()).optional(),
html_handling: z
Expand Down
Loading