From 0c86d5fe5a9dcc5d19100d3861bf7cd0e59d4f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20F=C3=B6ldi?= Date: Sun, 15 Oct 2023 14:43:16 +0200 Subject: [PATCH 1/4] Implement WFP instrumentation --- README.md | 2 +- src/instrumentation/dispatch-namespace.ts | 81 +++++++++++++++++++++++ src/instrumentation/env.ts | 8 +++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/instrumentation/dispatch-namespace.ts diff --git a/README.md b/README.md index fd7116f..e6dd505 100644 --- a/README.md +++ b/README.md @@ -296,4 +296,4 @@ Bindings: - [ ] mTLS - [ ] Vectorize - [ ] Hyperdrive -- [ ] Workers for Platforms Dispatch +- [x] Workers for Platforms Dispatch diff --git a/src/instrumentation/dispatch-namespace.ts b/src/instrumentation/dispatch-namespace.ts new file mode 100644 index 0000000..40d40f1 --- /dev/null +++ b/src/instrumentation/dispatch-namespace.ts @@ -0,0 +1,81 @@ +import { Attributes, SpanKind, SpanOptions, trace } from '@opentelemetry/api' +import { SemanticAttributes } from '@opentelemetry/semantic-conventions' +import { passthroughGet, wrap } from '../wrap.js' +import { instrumentClientFetch } from './fetch.js' + +type ExtraAttributeFn = (argArray: any[], result: any) => Attributes + +const WFPAttributes: Record = { + get(argArray) { + const attrs: Attributes = {} + const name = argArray[0] + const opts = argArray[2] + attrs['wfp.script_name'] = name + if (typeof opts === 'object') { + const limits = opts.limits + if (typeof limits === 'object') { + const { cpuMs, subRequests } = limits + if (typeof cpuMs === 'number') { + attrs['wfp.limits.cpuMs'] = cpuMs + } + if (typeof subRequests === 'number') { + attrs['wfp.limits.subRequests'] = subRequests + } + } + } + return attrs + } +} + +function instrumentWFPFn(fn: Function, name: string, operation: string) { + const tracer = trace.getTracer('WorkersForPlatforms') + const fnHandler: ProxyHandler = { + apply: (target, thisArg, argArray) => { + const attributes = { + binding_type: 'WorkersForPlatforms', + [SemanticAttributes.CODE_NAMESPACE]: name + } + const options: SpanOptions = { + kind: SpanKind.INTERNAL, + attributes, + } + return tracer.startActiveSpan(`${name} ${operation}`, options, async (span) => { + const result: Fetcher = await Reflect.apply(target, thisArg, argArray) + const extraAttrs = WFPAttributes[operation] ? WFPAttributes[operation](argArray, result) : {} + span.setAttributes(extraAttrs) + span.end() + return instrumentUserWorkerFetcher(result, name, argArray[0]) + }) + }, + } + return wrap(fn, fnHandler) +} + +export function instrumentDispatchNamespace(dataset: DispatchNamespace, name: string): DispatchNamespace { + const datasetHandler: ProxyHandler = { + get: (target, prop, receiver) => { + const operation = String(prop) + const fn = Reflect.get(target, prop, receiver) + return instrumentWFPFn(fn, name, operation) + }, + } + return wrap(dataset, datasetHandler) +} + +export function instrumentUserWorkerFetcher(fetcher: Fetcher, dispatch_namespace: string, worker_name: string): Fetcher { + const fetcherHandler: ProxyHandler = { + get(target, prop) { + if (prop === 'fetch') { + const fetcher = Reflect.get(target, prop) + const attrs = { + dispatch_namespace, + worker_name + } + return instrumentClientFetch(fetcher, () => ({ includeTraceContext: false }), attrs) + } else { + return passthroughGet(target, prop) + } + }, + } + return wrap(fetcher, fetcherHandler) +} diff --git a/src/instrumentation/env.ts b/src/instrumentation/env.ts index 3e6ee43..60c7f86 100644 --- a/src/instrumentation/env.ts +++ b/src/instrumentation/env.ts @@ -4,6 +4,7 @@ import { instrumentKV } from './kv.js' import { instrumentQueueSender } from './queue.js' import { instrumentServiceBinding } from './service.js' import { instrumentAnalyticsEngineDataset } from './analytics-engine.js' +import { instrumentDispatchNamespace } from './dispatch-namespace.js' const isKVNamespace = (item?: unknown): item is KVNamespace => { return !!(item as KVNamespace)?.getWithMetadata @@ -26,6 +27,11 @@ const isAnalyticsEngineDataset = (item?: unknown): item is AnalyticsEngineDatase return !!(item as AnalyticsEngineDataset)?.writeDataPoint } +const isDispatchNamespace = (item?: unknown): item is DispatchNamespace => { + // KV Namespaces and R2 buckets also have .get, but also .put + return !!(item as DispatchNamespace)?.get && !(item as KVNamespace & R2Bucket & DurableObjectState & DurableObjectNamespace)?.put +} + const instrumentEnv = (env: Record): Record => { const envHandler: ProxyHandler> = { get: (target, prop, receiver) => { @@ -43,6 +49,8 @@ const instrumentEnv = (env: Record): Record => return instrumentServiceBinding(item, String(prop)) } else if (isAnalyticsEngineDataset(item)) { return instrumentAnalyticsEngineDataset(item, String(prop)) + } else if (isDispatchNamespace(item)) { + return instrumentDispatchNamespace(item, String(prop)) } else { return item } From 43883891bf3fae2696197dc86572738620741208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20F=C3=B6ldi?= Date: Thu, 2 Nov 2023 09:51:24 +0100 Subject: [PATCH 2/4] Add changeset --- .changeset/chilly-ducks-rush.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilly-ducks-rush.md diff --git a/.changeset/chilly-ducks-rush.md b/.changeset/chilly-ducks-rush.md new file mode 100644 index 0000000..475cae9 --- /dev/null +++ b/.changeset/chilly-ducks-rush.md @@ -0,0 +1,5 @@ +--- +'@microlabs/otel-cf-workers': minor +--- + +Implement auto-instrumentation for Workers for Platforms dispatch namespaces From a4b7f0202518dc34033330dc9655eec8056787b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20F=C3=B6ldi?= Date: Sun, 31 Mar 2024 16:33:10 +0200 Subject: [PATCH 3/4] Run prettier --- src/instrumentation/dispatch-namespace.ts | 12 ++++++++---- src/instrumentation/env.ts | 5 ++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/instrumentation/dispatch-namespace.ts b/src/instrumentation/dispatch-namespace.ts index 40d40f1..355c847 100644 --- a/src/instrumentation/dispatch-namespace.ts +++ b/src/instrumentation/dispatch-namespace.ts @@ -24,7 +24,7 @@ const WFPAttributes: Record = { } } return attrs - } + }, } function instrumentWFPFn(fn: Function, name: string, operation: string) { @@ -33,7 +33,7 @@ function instrumentWFPFn(fn: Function, name: string, operation: string) { apply: (target, thisArg, argArray) => { const attributes = { binding_type: 'WorkersForPlatforms', - [SemanticAttributes.CODE_NAMESPACE]: name + [SemanticAttributes.CODE_NAMESPACE]: name, } const options: SpanOptions = { kind: SpanKind.INTERNAL, @@ -62,14 +62,18 @@ export function instrumentDispatchNamespace(dataset: DispatchNamespace, name: st return wrap(dataset, datasetHandler) } -export function instrumentUserWorkerFetcher(fetcher: Fetcher, dispatch_namespace: string, worker_name: string): Fetcher { +export function instrumentUserWorkerFetcher( + fetcher: Fetcher, + dispatch_namespace: string, + worker_name: string, +): Fetcher { const fetcherHandler: ProxyHandler = { get(target, prop) { if (prop === 'fetch') { const fetcher = Reflect.get(target, prop) const attrs = { dispatch_namespace, - worker_name + worker_name, } return instrumentClientFetch(fetcher, () => ({ includeTraceContext: false }), attrs) } else { diff --git a/src/instrumentation/env.ts b/src/instrumentation/env.ts index 60c7f86..1e95667 100644 --- a/src/instrumentation/env.ts +++ b/src/instrumentation/env.ts @@ -29,7 +29,10 @@ const isAnalyticsEngineDataset = (item?: unknown): item is AnalyticsEngineDatase const isDispatchNamespace = (item?: unknown): item is DispatchNamespace => { // KV Namespaces and R2 buckets also have .get, but also .put - return !!(item as DispatchNamespace)?.get && !(item as KVNamespace & R2Bucket & DurableObjectState & DurableObjectNamespace)?.put + return ( + !!(item as DispatchNamespace)?.get && + !(item as KVNamespace & R2Bucket & DurableObjectState & DurableObjectNamespace)?.put + ) } const instrumentEnv = (env: Record): Record => { From d9a4cf354cfe57e72006015720ae1550341a0bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20F=C3=B6ldi?= Date: Sun, 31 Mar 2024 16:35:43 +0200 Subject: [PATCH 4/4] Kindly tell typescript that it's not undefined --- src/instrumentation/dispatch-namespace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instrumentation/dispatch-namespace.ts b/src/instrumentation/dispatch-namespace.ts index 355c847..bb5972a 100644 --- a/src/instrumentation/dispatch-namespace.ts +++ b/src/instrumentation/dispatch-namespace.ts @@ -41,7 +41,7 @@ function instrumentWFPFn(fn: Function, name: string, operation: string) { } return tracer.startActiveSpan(`${name} ${operation}`, options, async (span) => { const result: Fetcher = await Reflect.apply(target, thisArg, argArray) - const extraAttrs = WFPAttributes[operation] ? WFPAttributes[operation](argArray, result) : {} + const extraAttrs = WFPAttributes[operation] ? WFPAttributes[operation]!(argArray, result) : {} span.setAttributes(extraAttrs) span.end() return instrumentUserWorkerFetcher(result, name, argArray[0])