From 4334eda98e6ac88b9774547208c22a7bcd9a9b7b Mon Sep 17 00:00:00 2001 From: Grzegorz Daszuta Date: Fri, 18 Oct 2024 12:48:36 +0200 Subject: [PATCH] Allow gzip compression in exporter --- README.md | 9 ++++ examples/queue/wrangler.toml | 2 +- examples/quickstart/QUICKSTART_GUIDE.md | 3 +- examples/quickstart/wrangler.toml | 2 +- examples/worker/wrangler.toml | 2 +- src/exporter.ts | 56 ++++++++++++++++++++++--- 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9f514dae..7362ea60 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,13 @@ npm install @microlabs/otel-cf-workers @opentelemetry/api > To be able to use the Open Telemetry library you have to add the NodeJS compatibility flag in your `wrangler.toml` file. ``` +compatibility_flags = [ "nodejs_compat", "nodejs_zlib" ] +``` + +alternatively, in recent version nodejs_zlib is enabled by default by nodejs_compat + +``` +compatibility_date = "2024-09-23" compatibility_flags = [ "nodejs_compat" ] ``` @@ -44,6 +51,7 @@ const config: ResolveConfigFn = (env: Env, _trigger) => { exporter: { url: 'https://api.honeycomb.io/v1/traces', headers: { 'x-honeycomb-team': env.HONEYCOMB_API_KEY }, + compression: 'gzip', }, service: { name: 'greetings' }, } @@ -263,6 +271,7 @@ One of the advantages of using Open Telemetry is that it makes it easier to do d - The worker runtime does not expose accurate timing information to protect against side-channel attacks such as Spectre and will only update the clock on IO, so any CPU heavy processing will look like it takes 0 milliseconds. - Not everything is auto-instrumented yet. See the lists below for what is and isn't. +- Exporter protocol support is currently limited to http/json. Triggers: diff --git a/examples/queue/wrangler.toml b/examples/queue/wrangler.toml index f12cf5bb..52ff1593 100644 --- a/examples/queue/wrangler.toml +++ b/examples/queue/wrangler.toml @@ -1,7 +1,7 @@ name = "queues-example" main = "src/index.ts" compatibility_date = "2023-04-02" -compatibility_flags = [ "nodejs_compat" ] +compatibility_flags = [ "nodejs_compat", "nodejs_zlib ] # Worker defines a binding, named "QUEUE", which gives it a capability # to send messages to a Queue, named "my-queue". diff --git a/examples/quickstart/QUICKSTART_GUIDE.md b/examples/quickstart/QUICKSTART_GUIDE.md index 662dbad5..1ee5987b 100644 --- a/examples/quickstart/QUICKSTART_GUIDE.md +++ b/examples/quickstart/QUICKSTART_GUIDE.md @@ -12,7 +12,7 @@ npm install @microlabs/otel-cf-workers @opentelemetry/api npx wrangler secret put HONEYCOMB_API_KEY ``` -And set the Node Compatibility flag by adding `compatibility_flags = [ "nodejs_compat" ]` +And set the Node Compatibility flag by adding `compatibility_flags = [ "nodejs_compat", "nodejs_zlib ]` in your `wrangler.toml` ## Example @@ -52,6 +52,7 @@ const config: ResolveConfigFn = (env: Env, _trigger: any) => { exporter: { url: 'https://api.honeycomb.io/v1/traces', headers: { 'x-honeycomb-team': env.HONEYCOMB_API_KEY }, + compression: 'gzip', }, service: { name: 'my-service-name' }, } diff --git a/examples/quickstart/wrangler.toml b/examples/quickstart/wrangler.toml index 214c9b20..3ef7cf23 100644 --- a/examples/quickstart/wrangler.toml +++ b/examples/quickstart/wrangler.toml @@ -2,7 +2,7 @@ name = "otel-quickstart-example" main = "src/index.ts" compatibility_date = "2024-09-09" -compatibility_flags = [ "nodejs_compat" ] +compatibility_flags = [ "nodejs_compat", "nodejs_zlib ] # [vars] HONEYCOMB_API_KEY = "example" diff --git a/examples/worker/wrangler.toml b/examples/worker/wrangler.toml index 73269d2e..04451bd4 100644 --- a/examples/worker/wrangler.toml +++ b/examples/worker/wrangler.toml @@ -1,7 +1,7 @@ name = "otel-test" main = "src/index.ts" compatibility_date = "2023-03-27" -compatibility_flags = [ "nodejs_compat" ] +compatibility_flags = [ "nodejs_compat", "nodejs_zlib ] kv_namespaces = [ { binding = "OTEL_TEST", id = "f124c9696873443da0a277ddb75000ca", preview_id = "3569aab8617645d9b8ed4bd1d45c8d96" } diff --git a/src/exporter.ts b/src/exporter.ts index 10317e8a..688ddc0c 100644 --- a/src/exporter.ts +++ b/src/exporter.ts @@ -1,12 +1,14 @@ -import { createExportTraceServiceRequest } from '@opentelemetry/otlp-transformer' +import { createExportTraceServiceRequest, IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer' import { ExportServiceError, OTLPExporterError } from '@opentelemetry/otlp-exporter-base' import { ExportResult, ExportResultCode } from '@opentelemetry/core' import { SpanExporter } from '@opentelemetry/sdk-trace-base' import { unwrap } from './wrap.js' +import { gzip } from 'node:zlib' export interface OTLPExporterConfig { url: string headers?: Record + compression?: 'gzip' } const defaultHeaders: Record = { @@ -17,9 +19,11 @@ const defaultHeaders: Record = { export class OTLPExporter implements SpanExporter { private headers: Record private url: string + private compression?: string constructor(config: OTLPExporterConfig) { this.url = config.url this.headers = Object.assign({}, defaultHeaders, config.headers) + this.compression = config.compression } export(items: any[], resultCallback: (result: ExportResult) => void): void { @@ -42,19 +46,61 @@ export class OTLPExporter implements SpanExporter { }) } - send(items: any[], onSuccess: () => void, onError: (error: OTLPExporterError) => void): void { + private async gzipCompress(input: string, options = {}): Promise { + const output = (await new Promise((resolve, reject) => { + gzip(input, options, function (error, result) { + if (error) { + reject(error) + } else { + resolve(result) + } + }) + })) as Buffer + + return output + } + + private async getBody(exportMessage: IExportTraceServiceRequest): Promise { + const jsonMessage = JSON.stringify(exportMessage) + + if (this.compression === 'gzip') { + return await this.gzipCompress(jsonMessage) + } + + return jsonMessage + } + + private getHeaders(): HeadersInit { + const headers = { ...this.headers } + + if (this.compression === 'gzip') { + headers['content-encoding'] = 'gzip' + } + + return headers + } + + private async prepareRequest(items: any[]): Promise { const exportMessage = createExportTraceServiceRequest(items, { useHex: true, useLongBits: false, }) - const body = JSON.stringify(exportMessage) + + const body = await this.getBody(exportMessage) + const headers = this.getHeaders() + const params: RequestInit = { method: 'POST', - headers: this.headers, + headers, body, } - unwrap(fetch)(this.url, params) + return params + } + + send(items: any[], onSuccess: () => void, onError: (error: OTLPExporterError) => void): void { + this.prepareRequest(items) + .then((params) => unwrap(fetch)(this.url, params)) .then((response) => { if (response.ok) { onSuccess()