Skip to content

Commit e8635df

Browse files
committed
feat(otlp-exporter-base): add retrying transport
1 parent 4d785ce commit e8635df

File tree

10 files changed

+326
-44
lines changed

10 files changed

+326
-44
lines changed

experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ describe('OTLPLogExporter', () => {
8787
it('should include user-agent header by default', () => {
8888
const exporter = new OTLPLogExporter();
8989
assert.strictEqual(
90-
exporter['_transport']['_parameters']['headers']['User-Agent'],
90+
exporter['_transport']['_transport']['_parameters']['headers']['User-Agent'],
9191
`OTel-OTLP-Exporter-JavaScript/${VERSION}`
9292
);
9393
});
9494

9595
it('should use headers defined via env', () => {
9696
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar';
9797
const exporter = new OTLPLogExporter();
98-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'bar');
98+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'bar');
9999
delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS;
100100
});
101101

@@ -115,8 +115,8 @@ describe('OTLPLogExporter', () => {
115115
foo: 'constructor',
116116
},
117117
});
118-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'constructor');
119-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
118+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'constructor');
119+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
120120
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
121121
});
122122
});

experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogExporter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,22 +156,22 @@ describe('OTLPLogExporter - node with proto over http', () => {
156156
it('should include user-agent header by default', () => {
157157
const exporter = new OTLPLogExporter();
158158
assert.strictEqual(
159-
exporter['_transport']['_parameters']['headers']['User-Agent'],
159+
exporter['_transport']['_transport']['_parameters']['headers']['User-Agent'],
160160
`OTel-OTLP-Exporter-JavaScript/${VERSION}`
161161
);
162162
});
163163
it('should use headers defined via env', () => {
164164
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar';
165165
const exporter = new OTLPLogExporter();
166-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'bar');
166+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'bar');
167167
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
168168
});
169169
it('should override global headers config with signal headers defined via env', () => {
170170
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo';
171171
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo';
172172
const exporter = new OTLPLogExporter();
173-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'boo');
174-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
173+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'boo');
174+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
175175
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = '';
176176
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
177177
});
@@ -182,8 +182,8 @@ describe('OTLPLogExporter - node with proto over http', () => {
182182
foo: 'constructor',
183183
},
184184
});
185-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'constructor');
186-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
185+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'constructor');
186+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
187187
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
188188
});
189189
});

experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,22 +170,22 @@ describe('OTLPTraceExporter - node with json over http', () => {
170170
it('should use headers defined via env', () => {
171171
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
172172
const exporter = new OTLPTraceExporter();
173-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'bar');
173+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'bar');
174174
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
175175
});
176176
it('should include user agent in header', () => {
177177
const exporter = new OTLPTraceExporter();
178178
assert.strictEqual(
179-
exporter['_transport']['_parameters']['headers']['User-Agent'],
179+
exporter['_transport']['_transport']['_parameters']['headers']['User-Agent'],
180180
`OTel-OTLP-Exporter-JavaScript/${VERSION}`
181181
);
182182
});
183183
it('should override global headers config with signal headers defined via env', () => {
184184
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo';
185185
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo';
186186
const exporter = new OTLPTraceExporter();
187-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'boo');
188-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
187+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'boo');
188+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
189189
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = '';
190190
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
191191
});
@@ -196,8 +196,8 @@ describe('OTLPTraceExporter - node with json over http', () => {
196196
foo: 'constructor',
197197
},
198198
});
199-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'constructor');
200-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
199+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'constructor');
200+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
201201
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
202202
});
203203
});

experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe('OTLPTraceExporter - node with proto over http', () => {
7474
const exporter = new OTLPTraceExporter();
7575
it('should include user agent in header', () => {
7676
assert.strictEqual(
77-
exporter['_transport']['_parameters']['headers']['User-Agent'],
77+
exporter['_transport']['_transport']['_parameters']['headers']['User-Agent'],
7878
`OTel-OTLP-Exporter-JavaScript/${VERSION}`
7979
);
8080
});
@@ -169,15 +169,15 @@ describe('OTLPTraceExporter - node with proto over http', () => {
169169
it('should use headers defined via env', () => {
170170
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
171171
const exporter = new OTLPTraceExporter();
172-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'bar');
172+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'bar');
173173
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
174174
});
175175
it('should override global headers config with signal headers defined via env', () => {
176176
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo';
177177
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo';
178178
const exporter = new OTLPTraceExporter();
179-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'boo');
180-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
179+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'boo');
180+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
181181
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = '';
182182
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
183183
});
@@ -188,8 +188,8 @@ describe('OTLPTraceExporter - node with proto over http', () => {
188188
foo: 'constructor',
189189
},
190190
});
191-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['foo'], 'constructor');
192-
assert.strictEqual(exporter['_transport']['_parameters']['headers']['bar'], 'foo');
191+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['foo'], 'constructor');
192+
assert.strictEqual(exporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
193193
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
194194
});
195195
});

experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,15 +343,15 @@ describe('OTLPMetricExporter - node with json over http', () => {
343343
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
344344
const exporter = new OTLPMetricExporter();
345345
assert.strictEqual(
346-
exporter._otlpExporter['_transport']['_parameters']['headers']['foo'],
346+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['foo'],
347347
'bar'
348348
);
349349
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
350350
});
351351
it('should include user agent in header', () => {
352352
const exporter = new OTLPMetricExporter();
353353
assert.strictEqual(
354-
exporter._otlpExporter['_transport']['_parameters']['headers']['User-Agent'],
354+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['User-Agent'],
355355
`OTel-OTLP-Exporter-JavaScript/${VERSION}`
356356
);
357357
});
@@ -360,11 +360,11 @@ describe('OTLPMetricExporter - node with json over http', () => {
360360
envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'foo=boo';
361361
const exporter = new OTLPMetricExporter();
362362
assert.strictEqual(
363-
exporter._otlpExporter['_transport']['_parameters']['headers']['foo'],
363+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['foo'],
364364
'boo'
365365
);
366366
assert.strictEqual(
367-
exporter._otlpExporter['_transport']['_parameters']['headers']['bar'],
367+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['bar'],
368368
'foo'
369369
);
370370
envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = '';
@@ -378,11 +378,11 @@ describe('OTLPMetricExporter - node with json over http', () => {
378378
},
379379
});
380380
assert.strictEqual(
381-
exporter._otlpExporter['_transport']['_parameters']['headers']['foo'],
381+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['foo'],
382382
'constructor'
383383
);
384384
assert.strictEqual(
385-
exporter._otlpExporter['_transport']['_parameters']['headers']['bar'],
385+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['bar'],
386386
'foo'
387387
);
388388
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';

experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('OTLPMetricExporter - node with proto over http', () => {
8181
const exporter = new OTLPMetricExporter();
8282
it('should include user agent in header', () => {
8383
assert.strictEqual(
84-
exporter._otlpExporter['_transport']['_parameters']['headers']['User-Agent'],
84+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['User-Agent'],
8585
`OTel-OTLP-Exporter-JavaScript/${VERSION}`
8686
);
8787
});
@@ -181,7 +181,7 @@ describe('OTLPMetricExporter - node with proto over http', () => {
181181
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
182182
const exporter = new OTLPMetricExporter();
183183
assert.strictEqual(
184-
exporter._otlpExporter['_transport']['_parameters']['headers']['foo'],
184+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['foo'],
185185
'bar'
186186
);
187187
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
@@ -191,11 +191,11 @@ describe('OTLPMetricExporter - node with proto over http', () => {
191191
envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'foo=boo';
192192
const exporter = new OTLPMetricExporter();
193193
assert.strictEqual(
194-
exporter._otlpExporter['_transport']['_parameters']['headers']['foo'],
194+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['foo'],
195195
'boo'
196196
);
197197
assert.strictEqual(
198-
exporter._otlpExporter['_transport']['_parameters']['headers']['bar'],
198+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['bar'],
199199
'foo'
200200
);
201201
envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = '';
@@ -209,10 +209,10 @@ describe('OTLPMetricExporter - node with proto over http', () => {
209209
},
210210
});
211211
assert.strictEqual(
212-
exporter._otlpExporter['_transport']['_parameters']['headers']['foo'],
212+
exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['foo'],
213213
'constructor'
214214
);
215-
assert.strictEqual(exporter._otlpExporter['_transport']['_parameters']['headers']['bar'], 'foo');
215+
assert.strictEqual(exporter._otlpExporter['_transport']['_transport']['_parameters']['headers']['bar'], 'foo');
216216
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
217217
});
218218
});

experimental/packages/otlp-exporter-base/src/platform/node/OTLPExporterNodeBase.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ISerializer } from '@opentelemetry/otlp-transformer';
2323
import { IExporterTransport } from '../../exporter-transport';
2424
import { createHttpExporterTransport } from './http-exporter-transport';
2525
import { OTLPExporterError } from '../../types';
26+
import { createRetryingTransport } from '../../retryable-transport';
2627

2728
/**
2829
* Collector Metric Exporter abstract base class
@@ -65,16 +66,18 @@ export abstract class OTLPExporterNodeBase<
6566
getEnv().OTEL_EXPORTER_OTLP_HEADERS
6667
);
6768

68-
this._transport = createHttpExporterTransport({
69-
agentOptions: config.httpAgentOptions ?? { keepAlive: true },
70-
compression: configureCompression(config.compression),
71-
headers: Object.assign(
72-
{},
73-
nonSignalSpecificHeaders,
74-
signalSpecificHeaders
75-
),
76-
url: this.url,
77-
timeoutMillis: this.timeoutMillis,
69+
this._transport = createRetryingTransport({
70+
transport: createHttpExporterTransport({
71+
agentOptions: config.httpAgentOptions ?? { keepAlive: true },
72+
compression: configureCompression(config.compression),
73+
headers: Object.assign(
74+
{},
75+
nonSignalSpecificHeaders,
76+
signalSpecificHeaders
77+
),
78+
url: this.url,
79+
timeoutMillis: this.timeoutMillis,
80+
}),
7881
});
7982
}
8083

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { IExporterTransport } from './exporter-transport';
18+
import { ExportResponse } from './export-response';
19+
20+
const MAX_ATTEMPTS = 5;
21+
const INITIAL_BACKOFF = 1000;
22+
const MAX_BACKOFF = 5000;
23+
const BACKOFF_MULTIPLIER = 1.5;
24+
25+
class RetryingTransport implements IExporterTransport {
26+
constructor(private _transport: IExporterTransport) {}
27+
28+
private retry(data: Uint8Array, inMillis: number): Promise<ExportResponse> {
29+
return new Promise((resolve, reject) => {
30+
setTimeout(() => {
31+
this._transport.send(data).then(resolve, reject);
32+
}, inMillis);
33+
});
34+
}
35+
36+
async send(data: Uint8Array): Promise<ExportResponse> {
37+
let result = await this._transport.send(data);
38+
let attempts = MAX_ATTEMPTS;
39+
let nextBackoff = INITIAL_BACKOFF;
40+
41+
// TODO: I'm not 100% sure this is correct, please review in-depth.
42+
while (result.status === 'retryable' && attempts > 0) {
43+
attempts--;
44+
const upperBound = Math.min(nextBackoff, MAX_BACKOFF);
45+
const backoff = Math.random() * upperBound;
46+
nextBackoff = nextBackoff * BACKOFF_MULTIPLIER;
47+
result = await this.retry(data, result.retryInMillis ?? backoff);
48+
}
49+
50+
return result;
51+
}
52+
53+
shutdown() {
54+
return this._transport.shutdown();
55+
}
56+
}
57+
58+
/**
59+
* Creates an Exporter Transport that retries on 'retryable' response.
60+
*/
61+
export function createRetryingTransport(options: {
62+
// Underlying transport to wrap.
63+
transport: IExporterTransport;
64+
}): IExporterTransport {
65+
return new RetryingTransport(options.transport);
66+
}

0 commit comments

Comments
 (0)