From 3949289bb0362383e45b16c04b5324e6e451400e Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 26 Mar 2021 22:12:40 +0100 Subject: [PATCH] chore: adding interceptor for getting headers before each request --- examples/tracer-web/examples/zipkin/index.js | 9 +++- .../opentelemetry-exporter-zipkin/README.md | 12 ++++- .../src/platform/browser/util.ts | 3 ++ .../src/platform/node/util.ts | 3 ++ .../src/types.ts | 5 +- .../src/utils.ts | 24 +++++++++ .../src/zipkin.ts | 26 +++++++++- .../test/browser/zipkin.test.ts | 34 +++++++++++++ .../test/common/zipkin.test.ts | 50 +++++++++++++++++++ 9 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 packages/opentelemetry-exporter-zipkin/src/utils.ts create mode 100644 packages/opentelemetry-exporter-zipkin/test/common/zipkin.test.ts diff --git a/examples/tracer-web/examples/zipkin/index.js b/examples/tracer-web/examples/zipkin/index.js index 410749b00af..3f85d49abe5 100644 --- a/examples/tracer-web/examples/zipkin/index.js +++ b/examples/tracer-web/examples/zipkin/index.js @@ -4,7 +4,14 @@ import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; const provider = new WebTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); -provider.addSpanProcessor(new SimpleSpanProcessor(new ZipkinExporter())); +provider.addSpanProcessor(new SimpleSpanProcessor(new ZipkinExporter({ + // testing interceptor + // getHeadersBeforeSend: ()=> { + // return { + // foo: 'bar', + // } + // } +}))); provider.register(); diff --git a/packages/opentelemetry-exporter-zipkin/README.md b/packages/opentelemetry-exporter-zipkin/README.md index e7857413761..59f43204d54 100644 --- a/packages/opentelemetry-exporter-zipkin/README.md +++ b/packages/opentelemetry-exporter-zipkin/README.md @@ -30,7 +30,13 @@ const options = { 'my-header': 'header-value', }, url: 'your-zipkin-url', - serviceName: 'your-application-name' + serviceName: 'your-application-name', + // optional interceptor + getHeadersBeforeSend: () => { + return { + 'my-header': 'header-value', + } + } } const exporter = new ZipkinExporter(options); ``` @@ -46,6 +52,10 @@ You can use built-in `SimpleSpanProcessor` or `BatchSpanProcessor` or write your - [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`. - [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization. +### Options +- **getHeadersBeforeSend** - optional interceptor that allows adding new headers everytime time the exporter is going to send spans. +This is optional and can be used if headers are changing over time. This is a sync callback. + ## Viewing your traces Please visit the Zipkin UI endpoint diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts b/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts index f0409d982d2..2505c3eec83 100644 --- a/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts +++ b/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts @@ -24,6 +24,9 @@ import * as zipkinTypes from '../../types'; /** * Prepares send function that will send spans to the remote Zipkin service. + * @param urlStr - url to send spans + * @param headers - headers + * send */ export function prepareSend(urlStr: string, headers?: Record) { let xhrHeaders: Record; diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts b/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts index f59301669fe..abcd0789630 100644 --- a/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts +++ b/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts @@ -23,6 +23,9 @@ import * as zipkinTypes from '../../types'; /** * Prepares send function that will send spans to the remote Zipkin service. + * @param urlStr - url to send spans + * @param headers - headers + * send */ export function prepareSend(urlStr: string, headers?: Record) { const urlOpts = url.parse(urlStr); diff --git a/packages/opentelemetry-exporter-zipkin/src/types.ts b/packages/opentelemetry-exporter-zipkin/src/types.ts index 3e06fd3e88a..6145f05180f 100644 --- a/packages/opentelemetry-exporter-zipkin/src/types.ts +++ b/packages/opentelemetry-exporter-zipkin/src/types.ts @@ -20,12 +20,13 @@ import { ExportResult } from '@opentelemetry/core'; * Exporter config */ export interface ExporterConfig { - headers?: { [key: string]: string }; + headers?: Record; serviceName?: string; url?: string; // Optional mapping overrides for OpenTelemetry status code and description. statusCodeTagName?: string; statusDescriptionTagName?: string; + getHeadersBeforeSend?: () => Record | undefined; } /** @@ -184,3 +185,5 @@ export type SendFunction = ( zipkinSpans: Span[], done: (result: ExportResult) => void ) => void; + +export type GetHeaders = () => Record | undefined; diff --git a/packages/opentelemetry-exporter-zipkin/src/utils.ts b/packages/opentelemetry-exporter-zipkin/src/utils.ts new file mode 100644 index 00000000000..1f6c7aeecf9 --- /dev/null +++ b/packages/opentelemetry-exporter-zipkin/src/utils.ts @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { GetHeaders } from './types'; + +export function prepareGetHeaders( + getHeadersBeforeSend: GetHeaders +): () => Record | undefined { + return function () { + return getHeadersBeforeSend(); + }; +} diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts index 0f6e84ebc15..026419778fb 100644 --- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts +++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts @@ -25,6 +25,7 @@ import { statusDescriptionTagName, } from './transform'; import { SERVICE_RESOURCE } from '@opentelemetry/resources'; +import { prepareGetHeaders } from './utils'; /** * Zipkin Exporter @@ -34,19 +35,27 @@ export class ZipkinExporter implements SpanExporter { private readonly DEFAULT_SERVICE_NAME = 'OpenTelemetry Service'; private readonly _statusCodeTagName: string; private readonly _statusDescriptionTagName: string; + private _urlStr: string; private _send: zipkinTypes.SendFunction; + private _getHeaders: zipkinTypes.GetHeaders | undefined; private _serviceName?: string; private _isShutdown: boolean; private _sendingPromises: Promise[] = []; constructor(config: zipkinTypes.ExporterConfig = {}) { - const urlStr = config.url || ZipkinExporter.DEFAULT_URL; - this._send = prepareSend(urlStr, config.headers); + this._urlStr = config.url || ZipkinExporter.DEFAULT_URL; + this._send = prepareSend(this._urlStr, config.headers); this._serviceName = config.serviceName; this._statusCodeTagName = config.statusCodeTagName || statusCodeTagName; this._statusDescriptionTagName = config.statusDescriptionTagName || statusDescriptionTagName; this._isShutdown = false; + if (typeof config.getHeadersBeforeSend === 'function') { + this._getHeaders = prepareGetHeaders(config.getHeadersBeforeSend); + } else { + // noop + this._beforeSend = function () {}; + } } /** @@ -96,6 +105,18 @@ export class ZipkinExporter implements SpanExporter { }); } + /** + * if user defines getHeadersBeforeSend in config then this will be called + * everytime before send, otherwise it will be replaced with noop in + * constructor + * @default noop + */ + private _beforeSend() { + if (this._getHeaders) { + this._send = prepareSend(this._urlStr, this._getHeaders()); + } + } + /** * Transform spans and sends to Zipkin service. */ @@ -116,6 +137,7 @@ export class ZipkinExporter implements SpanExporter { this._statusDescriptionTagName ) ); + this._beforeSend(); return this._send(zipkinSpans, (result: ExportResult) => { if (done) { return done(result); diff --git a/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts index 684dae9fb2e..5944677ebf4 100644 --- a/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts @@ -97,6 +97,40 @@ describe('Zipkin Exporter - web', () => { }); }); }); + describe('when getHeadersBeforeSend is defined', () => { + let server: any; + beforeEach(() => { + server = sinon.fakeServer.create(); + spySend.restore(); + }); + + afterEach(() => { + server.restore(); + }); + + it('should add headers from callback', done => { + zipkinExporter = new ZipkinExporter({ + getHeadersBeforeSend: () => { + return { + foo1: 'bar1', + foo2: 'bar2', + }; + }, + }); + zipkinExporter.export(spans, () => {}); + + setTimeout(() => { + const [{ requestHeaders }] = server.requests; + + ensureHeadersContain(requestHeaders, { + foo1: 'bar1', + foo2: 'bar2', + }); + + done(); + }); + }); + }); describe('export with custom headers', () => { let server: any; diff --git a/packages/opentelemetry-exporter-zipkin/test/common/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/common/zipkin.test.ts new file mode 100644 index 00000000000..805454b24bb --- /dev/null +++ b/packages/opentelemetry-exporter-zipkin/test/common/zipkin.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import { ZipkinExporter } from '../../src'; +import * as sinon from 'sinon'; +import { mockedReadableSpan } from '../helper'; + +describe('zipkin - header interceptor', () => { + describe('getHeadersBeforeSend', () => { + describe('when callback is defined', () => { + it('should call callback before sending', () => { + const getHeadersBeforeSend = sinon.spy(); + const span = Object.assign({}, mockedReadableSpan); + const exporter = new ZipkinExporter({ + getHeadersBeforeSend, + }); + const oldFunction = exporter['_send']; + exporter.export([span], () => {}); + + assert.strictEqual(getHeadersBeforeSend.callCount, 1); + assert.notStrictEqual(exporter['_getHeaders'], undefined); + assert.notStrictEqual(oldFunction, exporter['_send']); + }); + }); + describe('when callback is NOT defined', () => { + it('should call callback before sending', () => { + const span = Object.assign({}, mockedReadableSpan); + const exporter = new ZipkinExporter(); + const oldFunction = exporter['_send']; + assert.strictEqual(exporter['_getHeaders'], undefined); + exporter.export([span], () => {}); + assert.strictEqual(oldFunction, exporter['_send']); + }); + }); + }); +});