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

Allow gzip compression in exporter #176

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
```

Expand Down Expand Up @@ -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' },
}
Expand Down Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion examples/queue/wrangler.toml
Original file line number Diff line number Diff line change
@@ -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".
Expand Down
3 changes: 2 additions & 1 deletion examples/quickstart/QUICKSTART_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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' },
}
Expand Down
2 changes: 1 addition & 1 deletion examples/quickstart/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion examples/worker/wrangler.toml
Original file line number Diff line number Diff line change
@@ -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" }
Expand Down
56 changes: 51 additions & 5 deletions src/exporter.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>
compression?: 'gzip'
}

const defaultHeaders: Record<string, string> = {
Expand All @@ -17,9 +19,11 @@ const defaultHeaders: Record<string, string> = {
export class OTLPExporter implements SpanExporter {
private headers: Record<string, string>
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 {
Expand All @@ -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<Buffer> {
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<string | Buffer> {
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<RequestInit> {
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()
Expand Down