From 21f08621c457259787fee5c2e94b8f0f97779bac Mon Sep 17 00:00:00 2001 From: Leonardo Dalcin Date: Wed, 10 Jun 2020 16:30:16 -0300 Subject: [PATCH] feat(opentelemetry-exporter-jaeger): http sender (#965) * feat(opentelemetry-exporter-jaeger): http sender * fix: linter * fix(opentelemetry-exporter-jaeger): adds header to avoid infinity loop * test(opentelemetry-exporter-jaeger): verify http sender usage * refactor(opentelemetry-exporter-jaeger): checks if endpoint is setten * feat(opentelemetry-exporter-jaeger): adds nock as dev dependency * test(opentelemetry-exporter-jaeger): adds tests to check req header * fix: tests variable usage * refactor(opentelemetry-exporter-jaeger): removes config parameter change * fix: linter * fix: env var was never called Co-authored-by: Mayur Kale Co-authored-by: Daniel Dyla --- .../package.json | 1 + .../src/jaeger.ts | 31 +++++++++--- .../src/types.ts | 10 ++++ .../src/utils.ts | 17 +++++++ .../test/jaeger.test.ts | 48 +++++++++++++++++++ 5 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 packages/opentelemetry-exporter-jaeger/src/utils.ts diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index 78a0a41572..823c907245 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -47,6 +47,7 @@ "codecov": "3.7.0", "gts": "2.0.2", "mocha": "7.2.0", + "nock": "12.0.3", "nyc": "15.1.0", "rimraf": "3.0.2", "ts-mocha": "7.0.0", diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index 62d1320b4e..a08996ac1d 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -20,6 +20,7 @@ import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import { Socket } from 'dgram'; import { spanToThrift } from './transform'; import * as jaegerTypes from './types'; +import { OT_REQUEST_HEADER } from './utils'; /** * Format and sends span information to Jaeger Exporter. @@ -31,21 +32,39 @@ export class JaegerExporter implements SpanExporter { private readonly _onShutdownFlushTimeout: number; constructor(config: jaegerTypes.ExporterConfig) { - this._logger = config.logger || new NoopLogger(); - const tags: jaegerTypes.Tag[] = config.tags || []; + const localConfig = Object.assign({}, config); + this._logger = localConfig.logger || new NoopLogger(); + const tags: jaegerTypes.Tag[] = localConfig.tags || []; this._onShutdownFlushTimeout = - typeof config.flushTimeout === 'number' ? config.flushTimeout : 2000; + typeof localConfig.flushTimeout === 'number' + ? localConfig.flushTimeout + : 2000; - config.host = config.host || process.env.JAEGER_AGENT_HOST; + // https://github.com/jaegertracing/jaeger-client-node#environment-variables + // By default, the client sends traces via UDP to the agent at localhost:6832. Use JAEGER_AGENT_HOST and + // JAEGER_AGENT_PORT to send UDP traces to a different host:port. If JAEGER_ENDPOINT is set, the client sends traces + // to the endpoint via HTTP, making the JAEGER_AGENT_HOST and JAEGER_AGENT_PORT unused. If JAEGER_ENDPOINT is secured, + // HTTP basic authentication can be performed by setting the JAEGER_USER and JAEGER_PASSWORD environment variables. + localConfig.endpoint = localConfig.endpoint || process.env.JAEGER_ENDPOINT; + localConfig.username = localConfig.username || process.env.JAEGER_USER; + localConfig.password = localConfig.password || process.env.JAEGER_PASSWORD; + localConfig.host = localConfig.host || process.env.JAEGER_AGENT_HOST; + if (localConfig.endpoint) { + this._sender = new jaegerTypes.HTTPSender(localConfig); + this._sender._httpOptions.headers[OT_REQUEST_HEADER] = 1; + } else { + this._sender = localConfig.endpoint = new jaegerTypes.UDPSender( + localConfig + ); + } - this._sender = new jaegerTypes.UDPSender(config); if (this._sender._client instanceof Socket) { // unref socket to prevent it from keeping the process running this._sender._client.unref(); } this._process = { - serviceName: config.serviceName, + serviceName: localConfig.serviceName, tags: jaegerTypes.ThriftUtils.getThriftTags(tags), }; this._sender.setProcess(this._process); diff --git a/packages/opentelemetry-exporter-jaeger/src/types.ts b/packages/opentelemetry-exporter-jaeger/src/types.ts index d55ca41219..b882848e4c 100644 --- a/packages/opentelemetry-exporter-jaeger/src/types.ts +++ b/packages/opentelemetry-exporter-jaeger/src/types.ts @@ -28,6 +28,13 @@ export interface ExporterConfig { maxPacketSize?: number; // default: 65000 /** Time to wait for an onShutdown flush to finish before closing the sender */ flushTimeout?: number; // default: 2000 + //The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces + //If setten will override host and port + endpoint?: string; + //Username to send as part of "Basic" authentication to the collector endpoint + username?: string; + //Password to send as part of "Basic" authentication to the collector endpoint + password?: string; } // Below require is needed as jaeger-client types does not expose the thrift, @@ -38,6 +45,9 @@ export const UDPSender = require('jaeger-client/dist/src/reporters/udp_sender') export const Utils = require('jaeger-client/dist/src/util').default; export const ThriftUtils = require('jaeger-client/dist/src/thrift').default; +export const HTTPSender = require('jaeger-client/dist/src/reporters/http_sender') + .default; + export type TagValue = string | number | boolean; export interface Tag { diff --git a/packages/opentelemetry-exporter-jaeger/src/utils.ts b/packages/opentelemetry-exporter-jaeger/src/utils.ts new file mode 100644 index 0000000000..6e61f4e7bb --- /dev/null +++ b/packages/opentelemetry-exporter-jaeger/src/utils.ts @@ -0,0 +1,17 @@ +/*! + * Copyright 2020, 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. + */ + +export const OT_REQUEST_HEADER = 'x-opentelemetry-outgoing-request'; diff --git a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts index 9ceaf1bc98..926baabdea 100644 --- a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts +++ b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts @@ -22,6 +22,8 @@ import { ThriftProcess } from '../src/types'; import { ReadableSpan } from '@opentelemetry/tracing'; import { TraceFlags } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; +import { OT_REQUEST_HEADER } from '../src/utils'; +import * as nock from 'nock'; describe('JaegerExporter', () => { describe('constructor', () => { @@ -149,5 +151,51 @@ describe('JaegerExporter', () => { assert.strictEqual(result, ExportResult.SUCCESS); }); }); + + it('should use httpSender if config.endpoint is setten and set x-opentelemetry-outgoing-request header', done => { + const mockedEndpoint = 'http://testendpoint'; + nock(mockedEndpoint) + .post('/') + .reply(function () { + assert.strictEqual(this.req.headers[OT_REQUEST_HEADER], 1); + assert.strictEqual( + this.req.headers['content-type'], + 'application/x-thrift' + ); + assert.strictEqual(this.req.headers.host, 'testendpoint'); + done(); + }); + const exporter = new JaegerExporter({ + serviceName: 'opentelemetry', + endpoint: mockedEndpoint, + }); + assert.strictEqual(exporter['_sender'].constructor.name, 'HTTPSender'); + assert.strictEqual( + exporter['_sender']._httpOptions.headers[OT_REQUEST_HEADER], + 1 + ); + const spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }; + const readableSpan: ReadableSpan = { + name: 'my-span1', + kind: api.SpanKind.CLIENT, + spanContext, + startTime: [1566156729, 709], + endTime: [1566156731, 709], + ended: true, + status: { + code: api.CanonicalCode.DATA_LOSS, + }, + attributes: {}, + links: [], + events: [], + duration: [32, 800000000], + resource: Resource.empty(), + }; + exporter.export([readableSpan], () => {}); + }); }); });