From a4da9de57d2f0524e3f5fb3a652d0ccc192fdd2a Mon Sep 17 00:00:00 2001 From: rezakrimi Date: Sat, 6 Jun 2020 15:05:00 -0400 Subject: [PATCH] feat(opentelemetry-js): infer zipkin service name from resource (#1138) * feat(opentelemetry-js): infer zipkin service name from resource * Update packages/opentelemetry-exporter-zipkin/src/zipkin.ts making service name optional for zipkin exporter Co-authored-by: Daniel Dyla * fix: making serviceName optional for zipkin config * fix: resolving type issues * Update packages/opentelemetry-exporter-zipkin/src/zipkin.ts making config object optional for zipkin Co-authored-by: Daniel Dyla * refactor: making default service name a constant Co-authored-by: Daniel Dyla Co-authored-by: Mayur Kale --- .../opentelemetry-exporter-zipkin/README.md | 2 +- .../src/types.ts | 2 +- .../src/zipkin.ts | 36 +++-- .../test/zipkin.test.ts | 147 ++++++++++++++++++ 4 files changed, 169 insertions(+), 18 deletions(-) diff --git a/packages/opentelemetry-exporter-zipkin/README.md b/packages/opentelemetry-exporter-zipkin/README.md index 9367d0e0d6e..4e55091f528 100644 --- a/packages/opentelemetry-exporter-zipkin/README.md +++ b/packages/opentelemetry-exporter-zipkin/README.md @@ -17,7 +17,7 @@ npm install --save @opentelemetry/exporter-zipkin ## Usage -Install the exporter on your application and pass the options, it must contain a service name. +Install the exporter on your application and pass the options. `serviceName` is an optional string. If omitted, the exporter will first try to get the service name from the Resource. If no service name can be detected on the Resource, a fallback name of "OpenTelemetry Service" will be used. ```js const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); diff --git a/packages/opentelemetry-exporter-zipkin/src/types.ts b/packages/opentelemetry-exporter-zipkin/src/types.ts index 051673d5829..7667ef1783d 100644 --- a/packages/opentelemetry-exporter-zipkin/src/types.ts +++ b/packages/opentelemetry-exporter-zipkin/src/types.ts @@ -21,7 +21,7 @@ import * as api from '@opentelemetry/api'; */ export interface ExporterConfig { logger?: api.Logger; - serviceName: string; + serviceName?: string; url?: string; // Optional mapping overrides for OpenTelemetry status code and description. statusCodeTagName?: string; diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts index 39906b3d76d..5ca0ff7c9dd 100644 --- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts +++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts @@ -27,19 +27,21 @@ import { statusDescriptionTagName, } from './transform'; import { OT_REQUEST_HEADER } from './utils'; +import { SERVICE_RESOURCE } from '@opentelemetry/resources'; /** * Zipkin Exporter */ export class ZipkinExporter implements SpanExporter { static readonly DEFAULT_URL = 'http://localhost:9411/api/v2/spans'; + private readonly DEFAULT_SERVICE_NAME = 'OpenTelemetry Service'; private readonly _logger: api.Logger; - private readonly _serviceName: string; private readonly _statusCodeTagName: string; private readonly _statusDescriptionTagName: string; private readonly _reqOpts: http.RequestOptions; + private _serviceName?: string; private _isShutdown: boolean; - constructor(config: zipkinTypes.ExporterConfig) { + constructor(config: zipkinTypes.ExporterConfig = {}) { const urlStr = config.url || ZipkinExporter.DEFAULT_URL; const urlOpts = url.parse(urlStr); @@ -68,12 +70,18 @@ export class ZipkinExporter implements SpanExporter { spans: ReadableSpan[], resultCallback: (result: ExportResult) => void ) { + if (typeof this._serviceName !== 'string') { + this._serviceName = String( + spans[0].resource.labels[SERVICE_RESOURCE.NAME] || + this.DEFAULT_SERVICE_NAME + ); + } this._logger.debug('Zipkin exporter export'); if (this._isShutdown) { setTimeout(() => resultCallback(ExportResult.FAILED_NOT_RETRYABLE)); return; } - return this._sendSpans(spans, resultCallback); + return this._sendSpans(spans, this._serviceName, resultCallback); } /** @@ -87,26 +95,22 @@ export class ZipkinExporter implements SpanExporter { this._isShutdown = true; } - /** - * Transforms an OpenTelemetry span to a Zipkin span. - */ - private _toZipkinSpan(span: ReadableSpan): zipkinTypes.Span { - return toZipkinSpan( - span, - this._serviceName, - this._statusCodeTagName, - this._statusDescriptionTagName - ); - } - /** * Transform spans and sends to Zipkin service. */ private _sendSpans( spans: ReadableSpan[], + serviceName: string, done?: (result: ExportResult) => void ) { - const zipkinSpans = spans.map(span => this._toZipkinSpan(span)); + const zipkinSpans = spans.map(span => + toZipkinSpan( + span, + serviceName, + this._statusCodeTagName, + this._statusDescriptionTagName + ) + ); return this._send(zipkinSpans, (result: ExportResult) => { if (done) { return done(result); diff --git a/packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts index 35ff329c615..c4a7a24bc94 100644 --- a/packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts @@ -28,6 +28,7 @@ import { ZipkinExporter } from '../src'; import * as zipkinTypes from '../src/types'; import { OT_REQUEST_HEADER } from '../src/utils'; import { TraceFlags } from '@opentelemetry/api'; +import { SERVICE_RESOURCE } from '@opentelemetry/resources'; const MICROS_PER_SECS = 1e6; @@ -331,6 +332,152 @@ describe('ZipkinExporter', () => { done(); }); }); + + it('should set serviceName to "Opentelemetry Service" by default', () => { + const scope = nock('http://localhost:9411') + .post('/api/v2/spans') + .replyWithError(new Error('My Socket Error')); + + const parentSpanId = '5c1c63257de34c67'; + const startTime = 1566156729709; + const duration = 2000; + + const span1: ReadableSpan = { + name: 'my-span', + kind: api.SpanKind.INTERNAL, + parentSpanId, + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }, + startTime: [startTime, 0], + endTime: [startTime + duration, 0], + ended: true, + duration: [duration, 0], + status: { + code: api.CanonicalCode.OK, + }, + attributes: { + key1: 'value1', + key2: 'value2', + }, + links: [], + events: [ + { + name: 'my-event', + time: [startTime + 10, 0], + attributes: { key3: 'value3' }, + }, + ], + resource: Resource.empty(), + }; + const span2: ReadableSpan = { + name: 'my-span', + kind: api.SpanKind.SERVER, + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }, + startTime: [startTime, 0], + endTime: [startTime + duration, 0], + ended: true, + duration: [duration, 0], + status: { + code: api.CanonicalCode.OK, + }, + attributes: {}, + links: [], + events: [], + resource: Resource.empty(), + }; + + const exporter = new ZipkinExporter({}); + + exporter.export([span1, span2], (result: ExportResult) => { + scope.done(); + assert.equal(exporter['_serviceName'], 'OpenTelemetry Service'); + }); + }); + + it('should set serviceName if resource has one', () => { + const resource_service_name = 'resource_service_name'; + + const scope = nock('http://localhost:9411') + .post('/api/v2/spans') + .replyWithError(new Error('My Socket Error')); + + const parentSpanId = '5c1c63257de34c67'; + const startTime = 1566156729709; + const duration = 2000; + + const span1: ReadableSpan = { + name: 'my-span', + kind: api.SpanKind.INTERNAL, + parentSpanId, + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }, + startTime: [startTime, 0], + endTime: [startTime + duration, 0], + ended: true, + duration: [duration, 0], + status: { + code: api.CanonicalCode.OK, + }, + attributes: { + key1: 'value1', + key2: 'value2', + }, + links: [], + events: [ + { + name: 'my-event', + time: [startTime + 10, 0], + attributes: { key3: 'value3' }, + }, + ], + resource: new Resource({ + [SERVICE_RESOURCE.NAME]: resource_service_name, + }), + }; + const span2: ReadableSpan = { + name: 'my-span', + kind: api.SpanKind.SERVER, + spanContext: { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }, + startTime: [startTime, 0], + endTime: [startTime + duration, 0], + ended: true, + duration: [duration, 0], + status: { + code: api.CanonicalCode.OK, + }, + attributes: {}, + links: [], + events: [], + resource: Resource.empty(), + }; + + const exporter = new ZipkinExporter({}); + + exporter.export([span1, span2], (result: ExportResult) => { + scope.done(); + assert.equal(exporter['_serviceName'], resource_service_name); + + // checking if service name remains consistent in further exports + exporter.export([span2], (result: ExportResult) => { + scope.done(); + assert.equal(exporter['_serviceName'], resource_service_name); + }); + }); + }); }); describe('shutdown', () => {