Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WC-2695 Add better visibility for router-worker #6941

Merged
merged 1 commit into from
Oct 11, 2024
Merged
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
5 changes: 5 additions & 0 deletions .changeset/poor-shoes-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/workers-shared": minor
---

feat: Add observability to router-worker
71 changes: 71 additions & 0 deletions packages/workers-shared/router-worker/src/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Environment, ReadyAnalytics } from "./types";

// This will allow us to make breaking changes to the analytic schema
const VERSION = 1;

export enum DISPATCH_TYPE {
ASSETS = "asset",
WORKER = "worker",
}

// When adding new columns please update the schema
type Data = {
WillTaylorDev marked this conversation as resolved.
Show resolved Hide resolved
// -- Doubles --
// double1 - The time it takes for the whole request to complete in milliseconds
requestTime?: number;
// double2 - Colo ID
coloId?: number;
// double3 - Metal ID
metalId?: number;
// double4 - Colo tier (e.g. tier 1, tier 2, tier 3)
coloTier?: number;

// -- Blobs --
// blob1 - Hostname of the request
hostname?: string;
// blob2 - Dispatch type - what kind of thing did we dispatch
dispatchtype?: DISPATCH_TYPE;
// blob3 - Error message
error?: string;
// blob4 - The current version UUID of router-server
version?: string;
// blob5 - Region of the colo (e.g. WEUR)
coloRegion?: string;
};

export class Analytics {
private data: Data = {};

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

getData(key: keyof Data) {
return this.data[key];
}

write(env: Environment, readyAnalytics?: ReadyAnalytics, hostname?: string) {
if (!readyAnalytics) {
return;
}

readyAnalytics.logEvent({
version: VERSION,
accountId: 0, // TODO: need to plumb through
indexId: hostname,
doubles: [
this.data.requestTime ?? -1, // double1
this.data.coloId ?? -1, // double2
this.data.metalId ?? -1, // double3
this.data.coloTier ?? -1, // double4
],
blobs: [
this.data.hostname?.substring(0, 256), // blob1 - trim to 256 bytes
WillTaylorDev marked this conversation as resolved.
Show resolved Hide resolved
this.data.dispatchtype, // blob2
this.data.error?.substring(0, 256), // blob3 - trim to 256 bytes
this.data.version, // blob4
this.data.coloRegion, // blob5
],
});
}
}
46 changes: 45 additions & 1 deletion packages/workers-shared/router-worker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { setupSentry } from "../../utils/sentry";
import { Analytics, DISPATCH_TYPE } from "./analytics";
import { PerformanceTimer } from "./performance";
import type AssetWorker from "../../asset-worker/src/index";
import type { RoutingConfig } from "../../utils/types";
import type {
ColoMetadata,
Environment,
ReadyAnalytics,
UnsafePerformanceTimer,
} from "./types";

interface Env {
ASSET_WORKER: Service<AssetWorker>;
USER_WORKER: Fetcher;
CONFIG: RoutingConfig;

SENTRY_DSN: string;
ENVIRONMENT: Environment;
WillTaylorDev marked this conversation as resolved.
Show resolved Hide resolved
ANALYTICS: ReadyAnalytics;
COLO_METADATA: ColoMetadata;
UNSAFE_PERFORMANCE: UnsafePerformanceTimer;
VERSION_METADATA: WorkerVersionMetadata;

SENTRY_ACCESS_CLIENT_ID: string;
SENTRY_ACCESS_CLIENT_SECRET: string;
Expand All @@ -16,7 +29,9 @@ interface Env {
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
let sentry: ReturnType<typeof setupSentry> | undefined;
const maybeSecondRequest = request.clone();
const analytics = new Analytics();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: similar to the PerformanceTimer, why not pass in the env.ANALYTICS and env.ENVIRONMENT here in the constructor, rather than in the write method?

const performance = new PerformanceTimer(env.UNSAFE_PERFORMANCE);
const startTimeMs = performance.now();

try {
sentry = setupSentry(
Expand All @@ -27,21 +42,50 @@ export default {
env.SENTRY_ACCESS_CLIENT_SECRET
);

const url = new URL(request.url);
if (sentry) {
sentry.setUser({ username: url.hostname });
sentry.setTag("colo", env.COLO_METADATA.coloId);
sentry.setTag("metal", env.COLO_METADATA.metalId);
}

if (env.COLO_METADATA && env.VERSION_METADATA) {
analytics.setData({
coloId: env.COLO_METADATA.coloId,
metalId: env.COLO_METADATA.metalId,
coloTier: env.COLO_METADATA.coloTier,
coloRegion: env.COLO_METADATA.coloRegion,
hostname: url.hostname,
version: env.VERSION_METADATA.id,
});
}

const maybeSecondRequest = request.clone();
if (env.CONFIG.has_user_worker) {
if (await env.ASSET_WORKER.unstable_canFetch(request)) {
analytics.setData({ dispatchtype: DISPATCH_TYPE.ASSETS });
return await env.ASSET_WORKER.fetch(maybeSecondRequest);
} else {
analytics.setData({ dispatchtype: DISPATCH_TYPE.WORKER });
return env.USER_WORKER.fetch(maybeSecondRequest);
}
}

analytics.setData({ dispatchtype: DISPATCH_TYPE.ASSETS });
return await env.ASSET_WORKER.fetch(request);
} catch (err) {
if (err instanceof Error) {
analytics.setData({ error: err.message });
}

// Log to Sentry if we can
if (sentry) {
sentry.captureException(err);
}
throw err;
} finally {
analytics.setData({ requestTime: performance.now() - startTimeMs });
analytics.write(env.ENVIRONMENT, env.ANALYTICS);
}
},
};
16 changes: 16 additions & 0 deletions packages/workers-shared/router-worker/src/performance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { UnsafePerformanceTimer } from "./types";

export class PerformanceTimer {
private performanceTimer;

constructor(performanceTimer?: UnsafePerformanceTimer) {
this.performanceTimer = performanceTimer;
}

now() {
if (this.performanceTimer) {
return this.performanceTimer.timeOrigin + this.performanceTimer.now();
}
return Date.now();
}
}
25 changes: 25 additions & 0 deletions packages/workers-shared/router-worker/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export type Environment = "production" | "staging";

export interface ReadyAnalytics {
logEvent: (e: ReadyAnalyticsEvent) => void;
}

export interface ColoMetadata {
metalId: number;
coloId: number;
coloRegion: string;
coloTier: number;
}

export interface UnsafePerformanceTimer {
readonly timeOrigin: number;
now: () => number;
}

export interface ReadyAnalyticsEvent {
accountId?: number;
indexId?: string;
version?: number;
doubles?: (number | undefined)[];
blobs?: (string | undefined)[];
}
9 changes: 8 additions & 1 deletion packages/workers-shared/router-worker/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ workers_dev = false
main = "src/index.ts"
compatibility_date = "2024-07-31"

[version_metadata]
binding = "VERSION_METADATA"

[[unsafe.bindings]]
name = "CONFIG"
type = "param"
Expand All @@ -29,4 +32,8 @@ type = "origin"

[unsafe.metadata.build_options]
stable_id = "cloudflare/cf_router_worker"
networks = ["cf","jdc"]
networks = ["cf","jdc"]

[[unsafe.bindings]]
name = "workers-router-worker"
type = "internal_capability_grants"
Loading