diff --git a/examples/collector-exporter-node/docker/collector-config.yaml b/examples/collector-exporter-node/docker/collector-config.yaml index 7e0cd7e759..f104677f7e 100644 --- a/examples/collector-exporter-node/docker/collector-config.yaml +++ b/examples/collector-exporter-node/docker/collector-config.yaml @@ -1,15 +1,15 @@ receivers: otlp: - {} -# keep it when upgrading to version 0.5+ -# protocols: -# grpc: -# http: -# endpoint: localhost:55680 + protocols: + grpc: + http: + cors_allowed_origins: + - http://* + - https://* exporters: zipkin: - url: "http://zipkin-all-in-one:9411/api/v2/spans" + endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" processors: batch: diff --git a/examples/collector-exporter-node/docker/docker-compose.yaml b/examples/collector-exporter-node/docker/docker-compose.yaml index df43b97e33..3882379ad3 100644 --- a/examples/collector-exporter-node/docker/docker-compose.yaml +++ b/examples/collector-exporter-node/docker/docker-compose.yaml @@ -2,17 +2,26 @@ version: "3" services: # Collector collector: - image: otel/opentelemetry-collector:0.4.0 + image: otel/opentelemetry-collector:latest +# image: otel/opentelemetry-collector:0.6.0 command: ["--config=/conf/collector-config.yaml", "--log-level=DEBUG"] + networks: + - otelcol volumes: - ./collector-config.yaml:/conf/collector-config.yaml ports: - "55680:55680" + - "55681:55681" depends_on: - zipkin-all-in-one + # Zipkin zipkin-all-in-one: image: openzipkin/zipkin:latest + networks: + - otelcol ports: - "9411:9411" +networks: + otelcol: diff --git a/examples/collector-exporter-node/start.js b/examples/collector-exporter-node/start.js index 138b6b41ec..4e654c7e1b 100644 --- a/examples/collector-exporter-node/start.js +++ b/examples/collector-exporter-node/start.js @@ -1,11 +1,13 @@ 'use strict'; const opentelemetry = require('@opentelemetry/api'); +// const { ConsoleLogger, LogLevel} = require('@opentelemetry/core'); const { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { CollectorTraceExporter, CollectorProtocolNode } = require('@opentelemetry/exporter-collector'); const exporter = new CollectorTraceExporter({ serviceName: 'basic-service', + // logger: new ConsoleLogger(LogLevel.DEBUG), // headers: { // foo: 'bar' // }, @@ -48,6 +50,14 @@ function doWork(parent) { // Set attributes to the span. span.setAttribute('key', 'value'); + span.setAttribute('mapAndArrayValue', [ + 0, 1, 2.25, 'otel', { + foo: 'bar', + baz: 'json', + array: [1, 2, 'boom'], + }, + ]); + // Annotate our span to capture metadata about our operation span.addEvent('invoking doWork'); diff --git a/examples/tracer-web/examples/user-interaction/index.js b/examples/tracer-web/examples/user-interaction/index.js index b15f85a99e..a193a7d17b 100644 --- a/examples/tracer-web/examples/user-interaction/index.js +++ b/examples/tracer-web/examples/user-interaction/index.js @@ -10,7 +10,7 @@ const providerWithZone = new WebTracerProvider({ plugins: [ new UserInteractionPlugin(), new XMLHttpRequestPlugin({ - ignoreUrls: [/localhost:8090\/sockjs-node/], + ignoreUrls: [/localhost/], propagateTraceHeaderCorsUrls: [ 'http://localhost:8090' ] diff --git a/packages/opentelemetry-exporter-collector/README.md b/packages/opentelemetry-exporter-collector/README.md index 89431bdbaf..8233384dd1 100644 --- a/packages/opentelemetry-exporter-collector/README.md +++ b/packages/opentelemetry-exporter-collector/README.md @@ -24,7 +24,7 @@ import { WebTracerProvider } from '@opentelemetry/web'; import { CollectorTraceExporter } from '@opentelemetry/exporter-collector'; const collectorOptions = { - url: '', // url is optional and can be omitted - default is http://localhost:55680/v1/trace + url: '', // url is optional and can be omitted - default is http://localhost:55681/v1/trace headers: {}, //an optional object containing custom headers to be sent with each request }; @@ -118,10 +118,33 @@ const { CollectorExporter, CollectorTransportNode } = require('@opentelemetry/e const collectorOptions = { protocolNode: CollectorTransportNode.HTTP_JSON, serviceName: 'basic-service', - url: '', // url is optional and can be omitted - default is http://localhost:55680/v1/trace + url: '', // url is optional and can be omitted - default is http://localhost:55681/v1/trace headers: { foo: 'bar' - }, //an optional object containing custom headers to be sent with each request will only work with json over http + }, //an optional object containing custom headers to be sent with each request will only work with http +}; + +const provider = new BasicTracerProvider(); +const exporter = new CollectorExporter(collectorOptions); +provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); + +provider.register(); + +``` + +## Usage in Node - PROTO over http + +```js +const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); +const { CollectorExporter, CollectorTransportNode } = require('@opentelemetry/exporter-collector'); + +const collectorOptions = { + protocolNode: CollectorTransportNode.HTTP_PROTO, + serviceName: 'basic-service', + url: '', // url is optional and can be omitted - default is http://localhost:55681/v1/trace + headers: { + foo: 'bar' + }, //an optional object containing custom headers to be sent with each request will only work with http }; const provider = new BasicTracerProvider(); diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts index 6036fe3773..d593e1d44d 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts @@ -23,7 +23,7 @@ import { sendWithBeacon, sendWithXhr } from './util'; import { parseHeaders } from '../../util'; const DEFAULT_SERVICE_NAME = 'collector-trace-exporter'; -const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/trace'; +const DEFAULT_COLLECTOR_URL = 'http://localhost:55681/v1/trace'; /** * Collector Trace Exporter for Web @@ -35,9 +35,6 @@ export class CollectorTraceExporter collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest > implements SpanExporter { - DEFAULT_HEADERS: Record = { - [collectorTypes.OT_REQUEST_HEADER]: '1', - }; private _headers: Record; private _useXHR: boolean = false; @@ -46,10 +43,13 @@ export class CollectorTraceExporter */ constructor(config: CollectorExporterConfigBrowser = {}) { super(config); - this._headers = - parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; this._useXHR = !!config.headers || typeof navigator.sendBeacon !== 'function'; + if (this._useXHR) { + this._headers = parseHeaders(config.headers, this.logger); + } else { + this._headers = {}; + } } onInit(): void { diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts index fbf00089f8..3f0aa3c8de 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -24,7 +24,7 @@ import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; const DEFAULT_SERVICE_NAME = 'collector-metric-exporter'; const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; -const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/metrics'; +const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55681/v1/metrics'; /** * Collector Metric Exporter for Node diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts index 2cabd1768c..45f7194f55 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts @@ -24,8 +24,8 @@ import { toCollectorExportTraceServiceRequest } from '../../transform'; const DEFAULT_SERVICE_NAME = 'collector-trace-exporter'; const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; -const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/trace'; -const DEFAULT_COLLECTOR_URL_JSON_PROTO = 'http://localhost:55680/v1/trace'; +const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55681/v1/trace'; +const DEFAULT_COLLECTOR_URL_JSON_PROTO = 'http://localhost:55681/v1/trace'; /** * Collector Trace Exporter for Node diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/protos b/packages/opentelemetry-exporter-collector/src/platform/node/protos index b546885691..9ffeee0ec5 160000 --- a/packages/opentelemetry-exporter-collector/src/platform/node/protos +++ b/packages/opentelemetry-exporter-collector/src/platform/node/protos @@ -1 +1 @@ -Subproject commit b54688569186e0b862bf7462a983ccf2c50c0547 +Subproject commit 9ffeee0ec532efe02285af84880deb2a53a3eab1 diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/util.ts b/packages/opentelemetry-exporter-collector/src/platform/node/util.ts index a91bc48522..c7a7e24a57 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/util.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/util.ts @@ -31,7 +31,7 @@ export function removeProtocol(url: string): string { * @param onSuccess * @param onError */ -export function sendDataUsingHttp( +export function sendWithHttp( collector: CollectorExporterNodeBase, data: string | Buffer, contentType: string, diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts index 38da393cfe..72e2c3198b 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts @@ -17,7 +17,7 @@ import * as collectorTypes from '../../types'; import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; import { CollectorExporterConfigNode } from './types'; -import { sendDataUsingHttp } from './util'; +import { sendWithHttp } from './util'; export function initWithJson( _collector: CollectorExporterNodeBase, @@ -34,7 +34,7 @@ export function sendWithJson( ): void { const serviceRequest = collector.convert(objects); - sendDataUsingHttp( + sendWithHttp( collector, JSON.stringify(serviceRequest), 'application/json', diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJsonProto.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJsonProto.ts index ba0035bb65..15025ed56e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJsonProto.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJsonProto.ts @@ -20,7 +20,7 @@ import * as protobufjs from 'protobufjs'; import * as collectorTypes from '../../types'; import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; import { CollectorExporterConfigNode } from './types'; -import { sendDataUsingHttp } from './util'; +import { sendWithHttp } from './util'; let ExportTraceServiceRequestProto: Type | undefined; @@ -60,7 +60,7 @@ export function sendWithJsonProto( if (message) { const body = ExportTraceServiceRequestProto?.encode(message).finish(); if (body) { - sendDataUsingHttp( + sendWithHttp( collector, Buffer.from(body), 'application/x-protobuf', diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index c2916f0bf5..4c11bf8c4a 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -30,47 +30,79 @@ import { opentelemetryProto, CollectorExporterConfigBase, } from './types'; -import ValueType = opentelemetryProto.common.v1.ValueType; /** - * Converts attributes + * Converts attributes to KeyValue array * @param attributes */ export function toCollectorAttributes( attributes: Attributes -): opentelemetryProto.common.v1.AttributeKeyValue[] { +): opentelemetryProto.common.v1.KeyValue[] { return Object.keys(attributes).map(key => { return toCollectorAttributeKeyValue(key, attributes[key]); }); } /** - * Converts key and value to AttributeKeyValue + * Converts array of unknown value to ArrayValue + * @param values + */ +export function toCollectorArrayValue( + values: unknown[] +): opentelemetryProto.common.v1.ArrayValue { + return { + values: values.map(value => toCollectorAnyValue(value)), + }; +} + +/** + * Converts attributes to KeyValueList + * @param attributes + */ +export function toCollectorKeyValueList( + attributes: Attributes +): opentelemetryProto.common.v1.KeyValueList { + return { + values: toCollectorAttributes(attributes), + }; +} + +/** + * Converts key and unknown value to KeyValue * @param value event value */ export function toCollectorAttributeKeyValue( key: string, value: unknown -): opentelemetryProto.common.v1.AttributeKeyValue { - let aType: opentelemetryProto.common.v1.ValueType = ValueType.STRING; - const AttributeKeyValue: opentelemetryProto.common.v1.AttributeKeyValue = { +): opentelemetryProto.common.v1.KeyValue { + const anyValue = toCollectorAnyValue(value); + return { key, - type: 0, + value: anyValue, }; +} + +/** + * Converts unknown value to AnyValue + * @param value + */ +export function toCollectorAnyValue( + value: unknown +): opentelemetryProto.common.v1.AnyValue { + const anyValue: opentelemetryProto.common.v1.AnyValue = {}; if (typeof value === 'string') { - AttributeKeyValue.stringValue = value; + anyValue.stringValue = value; } else if (typeof value === 'boolean') { - aType = ValueType.BOOL; - AttributeKeyValue.boolValue = value; + anyValue.boolValue = value; } else if (typeof value === 'number') { // all numbers will be treated as double - aType = ValueType.DOUBLE; - AttributeKeyValue.doubleValue = value; + anyValue.doubleValue = value; + } else if (Array.isArray(value)) { + anyValue.arrayValue = toCollectorArrayValue(value); + } else if (value) { + anyValue.kvlistValue = toCollectorKeyValueList(value as Attributes); } - - AttributeKeyValue.type = aType; - - return AttributeKeyValue; + return anyValue; } /** diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index 3f11157e7d..5d95654fb0 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -41,7 +41,7 @@ export namespace opentelemetryProto { export namespace resource.v1 { export interface Resource { - attributes: opentelemetryProto.common.v1.AttributeKeyValue[]; + attributes: opentelemetryProto.common.v1.KeyValue[]; droppedAttributesCount: number; } } @@ -152,7 +152,7 @@ export namespace opentelemetryProto { export interface Event { timeUnixNano: number; name: string; - attributes?: opentelemetryProto.common.v1.AttributeKeyValue[]; + attributes?: opentelemetryProto.common.v1.KeyValue[]; droppedAttributesCount: number; } @@ -160,7 +160,7 @@ export namespace opentelemetryProto { traceId: string; spanId: string; traceState?: opentelemetryProto.trace.v1.Span.TraceState; - attributes?: opentelemetryProto.common.v1.AttributeKeyValue[]; + attributes?: opentelemetryProto.common.v1.KeyValue[]; droppedAttributesCount: number; } @@ -207,7 +207,7 @@ export namespace opentelemetryProto { kind?: opentelemetryProto.trace.v1.Span.SpanKind; startTimeUnixNano?: number; endTimeUnixNano?: number; - attributes?: opentelemetryProto.common.v1.AttributeKeyValue[]; + attributes?: opentelemetryProto.common.v1.KeyValue[]; droppedAttributesCount: number; events?: opentelemetryProto.trace.v1.Span.Event[]; droppedEventsCount: number; @@ -225,14 +225,27 @@ export namespace opentelemetryProto { } } export namespace common.v1 { - export interface AttributeKeyValue { + export interface KeyValue { key: string; - type: opentelemetryProto.common.v1.ValueType; + value: AnyValue; + } + + export type ArrayValue = { + values: AnyValue[]; + }; + + export interface KeyValueList { + values: KeyValue[]; + } + + export type AnyValue = { stringValue?: string; + boolValue?: boolean; intValue?: number; doubleValue?: number; - boolValue?: boolean; - } + arrayValue?: ArrayValue; + kvlistValue?: KeyValueList; + }; export interface InstrumentationLibrary { name: string; diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorTraceExporter.test.ts index 4893ddcb98..bfb916b333 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorTraceExporter.test.ts @@ -333,7 +333,7 @@ describe('CollectorTraceExporter - browser (getDefaultUrl)', () => { setTimeout(() => { assert.strictEqual( collectorExporter['url'], - 'http://localhost:55680/v1/trace' + 'http://localhost:55681/v1/trace' ); done(); }); diff --git a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts index 2251d80a3f..cd3f48058f 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts @@ -33,7 +33,7 @@ describe('transform', () => { foo: 'bar', }; assert.deepStrictEqual(transform.toCollectorAttributes(attributes), [ - { key: 'foo', type: 0, stringValue: 'bar' }, + { key: 'foo', value: { stringValue: 'bar' } }, ]); }); @@ -42,7 +42,7 @@ describe('transform', () => { foo: 13, }; assert.deepStrictEqual(transform.toCollectorAttributes(attributes), [ - { key: 'foo', type: 2, doubleValue: 13 }, + { key: 'foo', value: { doubleValue: 13 } }, ]); }); @@ -51,7 +51,7 @@ describe('transform', () => { foo: true, }; assert.deepStrictEqual(transform.toCollectorAttributes(attributes), [ - { key: 'foo', type: 3, boolValue: true }, + { key: 'foo', value: { boolValue: true } }, ]); }); @@ -60,7 +60,63 @@ describe('transform', () => { foo: 1.34, }; assert.deepStrictEqual(transform.toCollectorAttributes(attributes), [ - { key: 'foo', type: 2, doubleValue: 1.34 }, + { key: 'foo', value: { doubleValue: 1.34 } }, + ]); + }); + it('should convert attribute mixed with maps and array', () => { + const attributes: Attributes = { + foo: [ + 0, + 1, + 2.25, + 'otel', + { + foo: 'bar', + baz: 'json', + array: [1, 2, 'boom'], + }, + ], + }; + assert.deepStrictEqual(transform.toCollectorAttributes(attributes), [ + { + key: 'foo', + value: { + arrayValue: { + values: [ + { doubleValue: 0 }, + { doubleValue: 1 }, + { doubleValue: 2.25 }, + { stringValue: 'otel' }, + { + kvlistValue: { + values: [ + { + key: 'foo', + value: { stringValue: 'bar' }, + }, + { + key: 'baz', + value: { stringValue: 'json' }, + }, + { + key: 'array', + value: { + arrayValue: { + values: [ + { doubleValue: 1 }, + { doubleValue: 2 }, + { stringValue: 'boom' }, + ], + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, ]); }); }); @@ -79,13 +135,13 @@ describe('transform', () => { { timeUnixNano: 123000000123, name: 'foo', - attributes: [{ key: 'a', type: 0, stringValue: 'b' }], + attributes: [{ key: 'a', value: { stringValue: 'b' } }], droppedAttributesCount: 0, }, { timeUnixNano: 321000000321, name: 'foo2', - attributes: [{ key: 'c', type: 0, stringValue: 'd' }], + attributes: [{ key: 'c', value: { stringValue: 'd' } }], droppedAttributesCount: 0, }, ]); @@ -111,15 +167,13 @@ describe('transform', () => { attributes: [ { key: 'service', - type: 0, - stringValue: 'ui', + value: { stringValue: 'ui' }, }, { key: 'version', - type: 2, - doubleValue: 1, + value: { doubleValue: 1 }, }, - { key: 'success', type: 3, boolValue: true }, + { key: 'success', value: { boolValue: true } }, ], droppedAttributesCount: 0, }); diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index b992867c1c..58d70d28eb 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -398,49 +398,87 @@ export function ensureExportedEventsAreCorrect( } export function ensureExportedAttributesAreCorrect( - attributes: opentelemetryProto.common.v1.AttributeKeyValue[] + attributes: opentelemetryProto.common.v1.KeyValue[], + usingGRPC = false ) { - assert.deepStrictEqual( - attributes, - [ - { - key: 'component', - type: 'STRING', - stringValue: 'document-load', - intValue: '0', - doubleValue: 0, - boolValue: false, - }, - ], - 'exported attributes are incorrect' - ); + if (usingGRPC) { + assert.deepStrictEqual( + attributes, + [ + { + key: 'component', + value: { + stringValue: 'document-load', + value: 'stringValue', + }, + }, + ], + 'exported attributes are incorrect' + ); + } else { + assert.deepStrictEqual( + attributes, + [ + { + key: 'component', + value: { + stringValue: 'document-load', + }, + }, + ], + 'exported attributes are incorrect' + ); + } } export function ensureExportedLinksAreCorrect( - attributes: opentelemetryProto.trace.v1.Span.Link[] + attributes: opentelemetryProto.trace.v1.Span.Link[], + usingGRPC = false ) { - assert.deepStrictEqual( - attributes, - [ - { - attributes: [ - { - key: 'component', - type: 'STRING', - stringValue: 'document-load', - intValue: '0', - doubleValue: 0, - boolValue: false, - }, - ], - traceId: Buffer.from(traceIdArr), - spanId: Buffer.from(parentIdArr), - traceState: '', - droppedAttributesCount: 0, - }, - ], - 'exported links are incorrect' - ); + if (usingGRPC) { + assert.deepStrictEqual( + attributes, + [ + { + attributes: [ + { + key: 'component', + value: { + stringValue: 'document-load', + value: 'stringValue', + }, + }, + ], + traceId: Buffer.from(traceIdArr), + spanId: Buffer.from(parentIdArr), + traceState: '', + droppedAttributesCount: 0, + }, + ], + 'exported links are incorrect' + ); + } else { + assert.deepStrictEqual( + attributes, + [ + { + attributes: [ + { + key: 'component', + value: { + stringValue: 'document-load', + }, + }, + ], + traceId: Buffer.from(traceIdArr), + spanId: Buffer.from(parentIdArr), + traceState: '', + droppedAttributesCount: 0, + }, + ], + 'exported links are incorrect' + ); + } } export function ensureEventsAreCorrect( @@ -554,15 +592,16 @@ export function ensureProtoEventsAreCorrect( } export function ensureAttributesAreCorrect( - attributes: opentelemetryProto.common.v1.AttributeKeyValue[] + attributes: opentelemetryProto.common.v1.KeyValue[] ) { assert.deepStrictEqual( attributes, [ { key: 'component', - type: 0, - stringValue: 'document-load', + value: { + stringValue: 'document-load', + }, }, ], 'attributes are incorrect' @@ -570,15 +609,16 @@ export function ensureAttributesAreCorrect( } export function ensureProtoAttributesAreCorrect( - attributes: opentelemetryProto.common.v1.AttributeKeyValue[] + attributes: opentelemetryProto.common.v1.KeyValue[] ) { assert.deepStrictEqual( attributes, [ { key: 'component', - type: 'STRING', - stringValue: 'document-load', + value: { + stringValue: 'document-load', + }, }, ], 'attributes are incorrect' @@ -597,8 +637,9 @@ export function ensureLinksAreCorrect( attributes: [ { key: 'component', - type: 0, - stringValue: 'document-load', + value: { + stringValue: 'document-load', + }, }, ], droppedAttributesCount: 0, @@ -620,8 +661,9 @@ export function ensureProtoLinksAreCorrect( attributes: [ { key: 'component', - type: 'STRING', - stringValue: 'document-load', + value: { + stringValue: 'document-load', + }, }, ], droppedAttributesCount: 0, @@ -718,16 +760,17 @@ export function ensureProtoSpanIsCorrect( } export function ensureExportedSpanIsCorrect( - span: collectorTypes.opentelemetryProto.trace.v1.Span + span: collectorTypes.opentelemetryProto.trace.v1.Span, + usingGRPC = false ) { if (span.attributes) { - ensureExportedAttributesAreCorrect(span.attributes); + ensureExportedAttributesAreCorrect(span.attributes, usingGRPC); } if (span.events) { ensureExportedEventsAreCorrect(span.events); } if (span.links) { - ensureExportedLinksAreCorrect(span.links); + ensureExportedLinksAreCorrect(span.links, usingGRPC); } assert.deepStrictEqual( span.traceId, @@ -778,19 +821,27 @@ export function ensureWebResourceIsCorrect( attributes: [ { key: 'service.name', - type: 0, - stringValue: 'bar', + value: { + stringValue: 'bar', + }, }, { key: 'service', - type: 0, - stringValue: 'ui', + value: { + stringValue: 'ui', + }, + }, + { + key: 'version', + value: { + doubleValue: 1, + }, }, - { key: 'version', type: 2, doubleValue: 1 }, { key: 'cost', - type: 2, - doubleValue: 112.12, + value: { + doubleValue: 112.12, + }, }, ], droppedAttributesCount: 0, @@ -991,45 +1042,74 @@ export function ensureExportedValueRecorderIsCorrect( } export function ensureResourceIsCorrect( - resource: collectorTypes.opentelemetryProto.resource.v1.Resource + resource: collectorTypes.opentelemetryProto.resource.v1.Resource, + usingGRPC = true ) { - assert.deepStrictEqual(resource, { - attributes: [ - { - key: 'service.name', - type: 'STRING', - stringValue: 'basic-service', - intValue: '0', - doubleValue: 0, - boolValue: false, - }, - { - key: 'service', - type: 'STRING', - stringValue: 'ui', - intValue: '0', - doubleValue: 0, - boolValue: false, - }, - { - key: 'version', - type: 'DOUBLE', - stringValue: '', - intValue: '0', - doubleValue: 1, - boolValue: false, - }, - { - key: 'cost', - type: 'DOUBLE', - stringValue: '', - intValue: '0', - doubleValue: 112.12, - boolValue: false, - }, - ], - droppedAttributesCount: 0, - }); + if (usingGRPC) { + assert.deepStrictEqual(resource, { + attributes: [ + { + key: 'service.name', + value: { + stringValue: 'basic-service', + value: 'stringValue', + }, + }, + { + key: 'service', + value: { + stringValue: 'ui', + value: 'stringValue', + }, + }, + { + key: 'version', + value: { + doubleValue: 1, + value: 'doubleValue', + }, + }, + { + key: 'cost', + value: { + doubleValue: 112.12, + value: 'doubleValue', + }, + }, + ], + droppedAttributesCount: 0, + }); + } else { + assert.deepStrictEqual(resource, { + attributes: [ + { + key: 'service.name', + value: { + stringValue: 'basic-service', + }, + }, + { + key: 'service', + value: { + stringValue: 'ui', + }, + }, + { + key: 'version', + value: { + doubleValue: 1, + }, + }, + { + key: 'cost', + value: { + doubleValue: 112.12, + }, + }, + ], + droppedAttributesCount: 0, + }); + } } export function ensureExportTraceServiceRequestIsSet( diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporterWithProto.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporterWithProto.test.ts index bf009167cb..e847f6bea3 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporterWithProto.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporterWithProto.test.ts @@ -170,7 +170,7 @@ describe('CollectorExporter - node with proto over http', () => { setTimeout(() => { assert.strictEqual( collectorExporter['url'], - 'http://localhost:55680/v1/trace' + 'http://localhost:55681/v1/trace' ); done(); }); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts index 300c257593..ee659923d4 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -219,7 +219,7 @@ const testCollectorMetricExporter = (params: TestParams) => "resource doesn't exist" ); if (resource) { - ensureResourceIsCorrect(resource); + ensureResourceIsCorrect(resource, true); } } if (params.metadata && reqMetadata) { diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts index a3e61d42f8..c8f767c82b 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts @@ -120,7 +120,7 @@ describe('CollectorMetricExporter - node with json over http', () => { }); }); - it('should successfully send the spans', done => { + it('should successfully send metrics', done => { collectorExporter.export(metrics, () => {}); setTimeout(() => { @@ -215,7 +215,7 @@ describe('CollectorMetricExporter - node with json over http', () => { setTimeout(() => { assert.strictEqual( collectorExporter['url'], - 'http://localhost:55680/v1/metrics' + 'http://localhost:55681/v1/metrics' ); done(); }); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporter.test.ts index 9cadd1574c..4c70e068ff 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporter.test.ts @@ -187,14 +187,14 @@ const testCollectorExporter = (params: TestParams) => if (exportedData) { spans = exportedData.instrumentationLibrarySpans[0].spans; resource = exportedData.resource; - ensureExportedSpanIsCorrect(spans[0]); + ensureExportedSpanIsCorrect(spans[0], true); assert.ok( typeof resource !== 'undefined', "resource doesn't exist" ); if (resource) { - ensureResourceIsCorrect(resource); + ensureResourceIsCorrect(resource, true); } } if (params.metadata && reqMetadata) { diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts index 105e5b346e..bb9cd8f0e9 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts @@ -169,7 +169,7 @@ describe('CollectorTraceExporter - node with json over http', () => { setTimeout(() => { assert.strictEqual( collectorExporter['url'], - 'http://localhost:55680/v1/trace' + 'http://localhost:55681/v1/trace' ); done(); }); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithProto.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithProto.test.ts new file mode 100644 index 0000000000..e847f6bea3 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithProto.test.ts @@ -0,0 +1,188 @@ +/* + * 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 core from '@opentelemetry/core'; +import { ReadableSpan } from '@opentelemetry/tracing'; +import * as http from 'http'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { CollectorProtocolNode } from '../../src/enums'; +import { CollectorTraceExporter } from '../../src/platform/node'; +import { CollectorExporterConfigNode } from '../../src/platform/node/types'; +import { getExportTraceServiceRequestProto } from '../../src/platform/node/utilWithJsonProto'; +import * as collectorTypes from '../../src/types'; + +import { + ensureExportTraceServiceRequestIsSet, + ensureProtoSpanIsCorrect, + mockedReadableSpan, +} from '../helper'; + +const fakeRequest = { + end: function () {}, + on: function () {}, + write: function () {}, +}; + +const mockRes = { + statusCode: 200, +}; + +const mockResError = { + statusCode: 400, +}; + +describe('CollectorExporter - node with proto over http', () => { + let collectorExporter: CollectorTraceExporter; + let collectorExporterConfig: CollectorExporterConfigNode; + let spyRequest: sinon.SinonSpy; + let spyWrite: sinon.SinonSpy; + let spans: ReadableSpan[]; + describe('export', () => { + beforeEach(() => { + spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any); + spyWrite = sinon.stub(fakeRequest, 'write'); + collectorExporterConfig = { + headers: { + foo: 'bar', + }, + protocolNode: CollectorProtocolNode.HTTP_PROTO, + hostname: 'foo', + logger: new core.NoopLogger(), + serviceName: 'bar', + attributes: {}, + url: 'http://foo.bar.com', + }; + collectorExporter = new CollectorTraceExporter(collectorExporterConfig); + spans = []; + spans.push(Object.assign({}, mockedReadableSpan)); + }); + afterEach(() => { + spyRequest.restore(); + spyWrite.restore(); + }); + + it('should open the connection', done => { + collectorExporter.export(spans, () => {}); + + setTimeout(() => { + const args = spyRequest.args[0]; + const options = args[0]; + + assert.strictEqual(options.hostname, 'foo.bar.com'); + assert.strictEqual(options.method, 'POST'); + assert.strictEqual(options.path, '/'); + done(); + }); + }); + + it('should set custom headers', done => { + collectorExporter.export(spans, () => {}); + + setTimeout(() => { + const args = spyRequest.args[0]; + const options = args[0]; + assert.strictEqual(options.headers['foo'], 'bar'); + done(); + }); + }); + + it('should successfully send the spans', done => { + collectorExporter.export(spans, () => {}); + + setTimeout(() => { + const writeArgs = spyWrite.args[0]; + const ExportTraceServiceRequestProto = getExportTraceServiceRequestProto(); + const data = ExportTraceServiceRequestProto?.decode(writeArgs[0]); + const json = data?.toJSON() as collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest; + const span1 = + json.resourceSpans[0].instrumentationLibrarySpans[0].spans[0]; + assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); + if (span1) { + ensureProtoSpanIsCorrect(span1); + } + + ensureExportTraceServiceRequestIsSet(json); + + done(); + }); + }); + + it('should log the successful message', done => { + const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug'); + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + + const responseSpy = sinon.spy(); + collectorExporter.export(spans, responseSpy); + + setTimeout(() => { + const args = spyRequest.args[0]; + const callback = args[1]; + callback(mockRes); + setTimeout(() => { + const response: any = spyLoggerDebug.args[1][0]; + assert.strictEqual(response, 'statusCode: 200'); + assert.strictEqual(spyLoggerError.args.length, 0); + assert.strictEqual(responseSpy.args[0][0], 0); + done(); + }); + }); + }); + + it('should log the error message', done => { + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + + const responseSpy = sinon.spy(); + collectorExporter.export(spans, responseSpy); + + setTimeout(() => { + const args = spyRequest.args[0]; + const callback = args[1]; + callback(mockResError); + setTimeout(() => { + const response: any = spyLoggerError.args[0][0]; + assert.strictEqual(response, 'statusCode: 400'); + + assert.strictEqual(responseSpy.args[0][0], 1); + done(); + }); + }); + }); + }); + describe('CollectorTraceExporter - node (getDefaultUrl)', () => { + it('should default to localhost', done => { + const collectorExporter = new CollectorTraceExporter({ + protocolNode: CollectorProtocolNode.HTTP_PROTO, + }); + setTimeout(() => { + assert.strictEqual( + collectorExporter['url'], + 'http://localhost:55681/v1/trace' + ); + done(); + }); + }); + + it('should keep the URL if included', done => { + const url = 'http://foo.bar.com'; + const collectorExporter = new CollectorTraceExporter({ url }); + setTimeout(() => { + assert.strictEqual(collectorExporter['url'], url); + done(); + }); + }); + }); +});