Skip to content

Commit

Permalink
feat(otlp-exporter-base): supports gzip compression in the browser en…
Browse files Browse the repository at this point in the history
…vironment (#4162)
  • Loading branch information
fuaiyi committed Sep 26, 2023
1 parent 2b9832e commit 548cb2e
Show file tree
Hide file tree
Showing 21 changed files with 142 additions and 85 deletions.
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ All notable changes to experimental packages in this project will be documented

* feat(exporter-metrics-otlp-proto): add esm build [#4099](https://github.com/open-telemetry/opentelemetry-js/pull/4099) @pichlermarc
* feat(opencensus-shim): implement OpenCensus metric producer [#4066](https://github.com/open-telemetry/opentelemetry-js/pull/4066) @aabmass
* feat(otlp-exporter-base): supports gzip compression in the browser environment [#4162](https://github.com/open-telemetry/opentelemetry-js/pull/4162) @fuaiyi

### :bug: (Bug Fix)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export class OTLPLogExporter
timeoutMillis: getEnv().OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
...config,
});
this._headers = {
...this._headers,
this.headers = {
...this.headers,
...baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('OTLPLogExporter', () => {
it('should use headers defined via env', () => {
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar';
const exporter = new OTLPLogExporter();
assert.strictEqual(exporter['_headers'].foo, 'bar');
assert.strictEqual(exporter['headers'].foo, 'bar');
delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class OTLPLogExporter
{
constructor(config: OTLPExporterConfigBase = {}) {
super(config);
this._headers = Object.assign(
this._headers,
this.headers = Object.assign(
this.headers,
baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export class OTLPTraceExporter
{
constructor(config: OTLPExporterConfigBase = {}) {
super(config);
this._headers = Object.assign(
this._headers,
this.headers = Object.assign(
this.headers,
baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,18 +566,15 @@ describe('when configuring via environment', () => {
it('should use headers defined via env', () => {
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
const collectorExporter = new OTLPTraceExporter({ headers: {} });
// @ts-expect-error access internal property for testing
assert.strictEqual(collectorExporter._headers.foo, 'bar');
assert.strictEqual(collectorExporter.headers.foo, 'bar');
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
});
it('should override global headers config with signal headers defined via env', () => {
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo';
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo';
const collectorExporter = new OTLPTraceExporter({ headers: {} });
// @ts-expect-error access internal property for testing
assert.strictEqual(collectorExporter._headers.foo, 'boo');
// @ts-expect-error access internal property for testing
assert.strictEqual(collectorExporter._headers.bar, 'foo');
assert.strictEqual(collectorExporter.headers.foo, 'boo');
assert.strictEqual(collectorExporter.headers.bar, 'foo');
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = '';
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export class OTLPTraceExporter
{
constructor(config: OTLPExporterConfigBase = {}) {
super(config);
this._headers = Object.assign(
this._headers,
this.headers = Object.assign(
this.headers,
baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class OTLPExporterBrowserProxy extends OTLPExporterBrowserBase<
> {
constructor(config?: OTLPMetricExporterOptions & OTLPExporterConfigBase) {
super(config);
this._headers = Object.assign(
this._headers,
this.headers = Object.assign(
this.headers,
baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ describe('when configuring via environment', () => {
temporalityPreference: AggregationTemporalityPreference.CUMULATIVE,
});
assert.strictEqual(
collectorExporter['_otlpExporter']['_headers'].foo,
collectorExporter['_otlpExporter']['headers'].foo,
'bar'
);
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
Expand All @@ -534,11 +534,11 @@ describe('when configuring via environment', () => {
temporalityPreference: AggregationTemporalityPreference.CUMULATIVE,
});
assert.strictEqual(
collectorExporter['_otlpExporter']['_headers'].foo,
collectorExporter['_otlpExporter']['headers'].foo,
'boo'
);
assert.strictEqual(
collectorExporter['_otlpExporter']['_headers'].bar,
collectorExporter['_otlpExporter']['headers'].bar,
'foo'
);
envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = '';
Expand Down
4 changes: 3 additions & 1 deletion experimental/packages/otlp-exporter-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,16 @@
"access": "public"
},
"dependencies": {
"@opentelemetry/core": "1.17.0"
"@opentelemetry/core": "1.17.0",
"pako": "^2.1.0"
},
"devDependencies": {
"@babel/core": "7.22.20",
"@opentelemetry/api": "1.6.0",
"@types/mocha": "10.0.1",
"@types/node": "18.6.5",
"@types/sinon": "10.0.16",
"@types/pako": "^2.0.0",
"babel-plugin-istanbul": "6.1.1",
"codecov": "3.8.3",
"cross-var": "1.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/

import { OTLPExporterBase } from '../../OTLPExporterBase';
import { OTLPExporterConfigBase } from '../../types';
import { CompressionAlgorithm, OTLPExporterConfigBase } from '../../types';
import * as otlpTypes from '../../types';
import { parseHeaders } from '../../util';
import { sendWithBeacon, sendWithXhr } from './util';
import { sendWithBeacon, sendWithXhr, configureCompression } from './util';
import { diag } from '@opentelemetry/api';
import { getEnv, baggageUtils } from '@opentelemetry/core';

Expand All @@ -29,7 +29,8 @@ export abstract class OTLPExporterBrowserBase<
ExportItem,
ServiceRequest,
> extends OTLPExporterBase<OTLPExporterConfigBase, ExportItem, ServiceRequest> {
protected _headers: Record<string, string>;
headers: Record<string, string>;
compression: CompressionAlgorithm;
private _useXHR: boolean = false;

/**
Expand All @@ -40,16 +41,17 @@ export abstract class OTLPExporterBrowserBase<
this._useXHR =
!!config.headers || typeof navigator.sendBeacon !== 'function';
if (this._useXHR) {
this._headers = Object.assign(
this.headers = Object.assign(
{},
parseHeaders(config.headers),
baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_HEADERS
)
);
} else {
this._headers = {};
this.headers = {};
}
this.compression = configureCompression(config.compression);
}

onInit(): void {
Expand All @@ -74,14 +76,7 @@ export abstract class OTLPExporterBrowserBase<

const promise = new Promise<void>((resolve, reject) => {
if (this._useXHR) {
sendWithXhr(
body,
this.url,
this._headers,
this.timeoutMillis,
resolve,
reject
);
sendWithXhr(this, body, 'application/json', resolve, reject);
} else {
sendWithBeacon(
body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/
import { diag } from '@opentelemetry/api';
import { OTLPExporterError } from '../../types';
import { gzip } from 'pako';
import { CompressionAlgorithm, OTLPExporterError } from '../../types';
import {
DEFAULT_EXPORT_MAX_ATTEMPTS,
DEFAULT_EXPORT_INITIAL_BACKOFF,
Expand All @@ -23,6 +24,7 @@ import {
isExportRetryable,
parseRetryAfterToMills,
} from '../../util';
import { OTLPExporterBrowserBase } from './OTLPExporterBrowserBase';

/**
* Send metrics/spans using browser navigator.sendBeacon
Expand Down Expand Up @@ -51,17 +53,16 @@ export function sendWithBeacon(
/**
* function to send metrics/spans using browser XMLHttpRequest
* used when navigator.sendBeacon is not available
* @param collector
* @param body
* @param url
* @param headers
* @param contentType
* @param onSuccess
* @param onError
*/
export function sendWithXhr(
body: string | Blob,
url: string,
headers: Record<string, string>,
exporterTimeout: number,
export function sendWithXhr<ExportItem, ServiceRequest>(
collector: OTLPExporterBrowserBase<ExportItem, ServiceRequest>,
body: string | Uint8Array,
contentType: string,
onSuccess: () => void,
onError: (error: OTLPExporterError) => void
): void {
Expand All @@ -79,28 +80,37 @@ export function sendWithXhr(
} else {
xhr.abort();
}
}, exporterTimeout);
}, collector.timeoutMillis);

const sendWithRetry = (
retries = DEFAULT_EXPORT_MAX_ATTEMPTS,
minDelay = DEFAULT_EXPORT_INITIAL_BACKOFF
) => {
xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.open('POST', collector.url);

const defaultHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Content-Type': contentType || 'application/json',
};

Object.entries({
...defaultHeaders,
...headers,
...collector.headers,
}).forEach(([k, v]) => {
xhr.setRequestHeader(k, v);
});

xhr.send(body);
switch (collector.compression) {
case CompressionAlgorithm.GZIP: {
xhr.setRequestHeader('Content-Encoding', 'gzip');
xhr.send(gzip(body));
break;
}
default:
xhr.send(body);
break;
}

xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE && reqIsDestroyed === false) {
Expand Down Expand Up @@ -161,3 +171,9 @@ export function sendWithXhr(

sendWithRetry();
}

export function configureCompression(
compression: CompressionAlgorithm | undefined
): CompressionAlgorithm {
return compression || CompressionAlgorithm.NONE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import type * as http from 'http';
import type * as https from 'https';

import { OTLPExporterBase } from '../../OTLPExporterBase';
import { OTLPExporterNodeConfigBase, CompressionAlgorithm } from './types';
import { OTLPExporterNodeConfigBase } from './types';
import * as otlpTypes from '../../types';
import { parseHeaders } from '../../util';
import { createHttpAgent, sendWithHttp, configureCompression } from './util';
import { diag } from '@opentelemetry/api';
import { getEnv, baggageUtils } from '@opentelemetry/core';
import { CompressionAlgorithm } from '../../types';

/**
* Collector Metric Exporter abstract base class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
*/

export { OTLPExporterNodeBase } from './OTLPExporterNodeBase';
export { sendWithHttp, createHttpAgent, configureCompression } from './util';
export { OTLPExporterNodeConfigBase, CompressionAlgorithm } from './types';
export { sendWithHttp, createHttpAgent } from './util';
export { configureCompression } from './util';
export { OTLPExporterNodeConfigBase } from './types';
export { CompressionAlgorithm } from '../../types';
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,5 @@ import { OTLPExporterConfigBase } from '../../types';
*/
export interface OTLPExporterNodeConfigBase extends OTLPExporterConfigBase {
keepAlive?: boolean;
compression?: CompressionAlgorithm;
httpAgentOptions?: http.AgentOptions | https.AgentOptions;
}

export enum CompressionAlgorithm {
NONE = 'none',
GZIP = 'gzip',
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import { Readable } from 'stream';
import { OTLPExporterNodeBase } from './OTLPExporterNodeBase';
import { OTLPExporterNodeConfigBase } from '.';
import { diag } from '@opentelemetry/api';
import { CompressionAlgorithm } from './types';
import { CompressionAlgorithm, OTLPExporterError } from '../../types';
import { getEnv } from '@opentelemetry/core';
import { OTLPExporterError } from '../../types';

import {
DEFAULT_EXPORT_MAX_ATTEMPTS,
DEFAULT_EXPORT_INITIAL_BACKOFF,
Expand Down
6 changes: 6 additions & 0 deletions experimental/packages/otlp-exporter-base/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ export interface OTLPExporterConfigBase {
hostname?: string;
url?: string;
concurrencyLimit?: number;
compression?: CompressionAlgorithm;
/** Maximum time the OTLP exporter will wait for each batch export.
* The default value is 10000ms. */
timeoutMillis?: number;
}

export enum CompressionAlgorithm {
NONE = 'none',
GZIP = 'gzip',
}
Loading

0 comments on commit 548cb2e

Please sign in to comment.