From 7482b43813cc44dcfdd9974a50940df2b7658528 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Fri, 10 Jul 2020 22:17:24 +0000 Subject: [PATCH 01/34] Merged from upstream --- .../browser/CollectorTraceExporter.ts | 73 +++-------------- .../src/platform/browser/util.ts | 81 +++++++++++++++++++ 2 files changed, 92 insertions(+), 62 deletions(-) create mode 100644 packages/opentelemetry-exporter-collector/src/platform/browser/util.ts diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts index 661f1e9b872..5d89ebce9ea 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts @@ -19,12 +19,13 @@ import { ReadableSpan } from '@opentelemetry/tracing'; import { toCollectorExportTraceServiceRequest } from '../../transform'; import { CollectorExporterConfigBrowser } from './types'; import * as collectorTypes from '../../types'; +import { sendWithBeacon, sendWithXhr } from './util'; import { parseHeaders } from '../../util'; const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/trace'; /** - * Collector Exporter for Web + * Collector Trace Exporter for Web */ export class CollectorTraceExporter extends CollectorTraceExporterBase< CollectorExporterConfigBrowser @@ -70,68 +71,16 @@ export class CollectorTraceExporter extends CollectorTraceExporterBase< const body = JSON.stringify(exportTraceServiceRequest); if (this._useXHR) { - this._sendSpansWithXhr(body, onSuccess, onError); + sendWithXhr( + body, + this.url, + this._headers, + this.logger, + onSuccess, + onError + ); } else { - this._sendSpansWithBeacon(body, onSuccess, onError); + sendWithBeacon(body, this.url, this.logger, onSuccess, onError); } } - - /** - * send spans using browser navigator.sendBeacon - * @param body - * @param onSuccess - * @param onError - */ - private _sendSpansWithBeacon( - body: string, - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ) { - if (navigator.sendBeacon(this.url, body)) { - this.logger.debug('sendBeacon - can send', body); - onSuccess(); - } else { - this.logger.error('sendBeacon - cannot send', body); - onError({}); - } - } - - /** - * function to send spans using browser XMLHttpRequest - * used when navigator.sendBeacon is not available - * @param body - * @param onSuccess - * @param onError - */ - private _sendSpansWithXhr( - body: string, - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ) { - const xhr = new XMLHttpRequest(); - xhr.open('POST', this.url); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('Content-Type', 'application/json'); - Object.entries(this._headers).forEach(([k, v]) => { - xhr.setRequestHeader(k, v); - }); - - xhr.send(body); - - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - if (xhr.status >= 200 && xhr.status <= 299) { - this.logger.debug('xhr success', body); - onSuccess(); - } else { - this.logger.error('body', body); - this.logger.error('xhr error', xhr); - onError({ - code: xhr.status, - message: xhr.responseText, - }); - } - } - }; - } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/util.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/util.ts new file mode 100644 index 00000000000..433490026fa --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/util.ts @@ -0,0 +1,81 @@ +/* + * 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 collectorTypes from '../../types'; +import { Logger } from '@opentelemetry/api'; + +/** + * Send metrics/spans using browser navigator.sendBeacon + * @param body + * @param onSuccess + * @param onError + */ +export function sendWithBeacon( + body: string, + url: string, + logger: Logger, + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +) { + if (navigator.sendBeacon(url, body)) { + logger.debug('sendBeacon - can send', body); + onSuccess(); + } else { + logger.error('sendBeacon - cannot send', body); + onError({}); + } +} + +/** + * function to send metrics/spans using browser XMLHttpRequest + * used when navigator.sendBeacon is not available + * @param body + * @param onSuccess + * @param onError + */ +export function sendWithXhr( + body: string, + url: string, + headers: { [key: string]: string }, + logger: Logger, + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +) { + const xhr = new XMLHttpRequest(); + xhr.open('POST', url); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader('Content-Type', 'application/json'); + Object.entries(headers).forEach(([k, v]) => { + xhr.setRequestHeader(k, v); + }); + + xhr.send(body); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status >= 200 && xhr.status <= 299) { + logger.debug('xhr success', body); + onSuccess(); + } else { + logger.error('body', body); + logger.error('xhr error', xhr); + onError({ + code: xhr.status, + message: xhr.responseText, + }); + } + } + }; +} From b02949a278de44428e6cae3409790e32bc58d430 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Sat, 11 Jul 2020 00:06:13 +0000 Subject: [PATCH 02/34] Add test --- .../browser/CollectorMetricExporter.ts | 82 ++++ .../src/platform/browser/index.ts | 1 + .../browser/CollectorMetricExporter.test.ts | 416 ++++++++++++++++++ .../test/common/transformMetrics.test.ts | 26 +- .../test/helper.ts | 6 +- 5 files changed, 524 insertions(+), 7 deletions(-) create mode 100644 packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts create mode 100644 packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts new file mode 100644 index 00000000000..e1d923ba014 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts @@ -0,0 +1,82 @@ +/* + * 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 { MetricRecord } from '@opentelemetry/metrics'; +import { CollectorMetricExporterBase } from '../../CollectorMetricExporterBase'; +import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; +import { CollectorExporterError, OT_REQUEST_HEADER } from '../../types'; +import { CollectorExporterConfigBrowser } from './types'; +import { sendWithBeacon, sendWithXhr } from './util'; + +const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/metrics'; + +/** + * Collector Metric Exporter for Web + */ +export class CollectorMetricExporter extends CollectorMetricExporterBase< + CollectorExporterConfigBrowser +> { + DEFAULT_HEADERS: { [key: string]: string } = { + [OT_REQUEST_HEADER]: '1', + }; + private _headers: { [key: string]: string }; + private _useXHR: boolean = false; + /** + * @param config + */ + constructor(config: CollectorExporterConfigBrowser = {}) { + super(config); + this._headers = config.headers || this.DEFAULT_HEADERS; + this._useXHR = + !!config.headers || typeof navigator.sendBeacon !== 'function'; + } + + onInit(): void { + window.addEventListener('unload', this.shutdown); + } + + onShutdown(): void { + window.removeEventListener('unload', this.shutdown); + } + + getDefaultUrl(url: string | undefined): string { + return url || DEFAULT_COLLECTOR_URL; + } + + sendMetrics( + metrics: MetricRecord[], + onSuccess: () => void, + onError: (error: CollectorExporterError) => void + ): void { + const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( + metrics, + this._startTime, + this + ); + const body = JSON.stringify(exportMetricServiceRequest); + if (this._useXHR) { + sendWithXhr( + body, + this.url, + this._headers, + this.logger, + onSuccess, + onError + ); + } else { + sendWithBeacon(body, this.url, this.logger, onSuccess, onError); + } + } +} diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts index 1c17973de2e..fcbe012b52b 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts @@ -15,3 +15,4 @@ */ export * from './CollectorTraceExporter'; +export * from './CollectorMetricExporter'; diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts new file mode 100644 index 00000000000..e8b372462f4 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts @@ -0,0 +1,416 @@ +/* + * 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 { NoopLogger } from '@opentelemetry/core'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { CollectorMetricExporter } from '../../src/platform/browser/index'; +import * as collectorTypes from '../../src/types'; +import { MetricRecord, HistogramAggregator } from '@opentelemetry/metrics'; +import { + mockCounter, + mockObserver, + ensureCounterIsCorrect, + ensureObserverIsCorrect, + ensureWebResourceIsCorrect, + ensureExportMetricsServiceRequestIsSet, + ensureHeadersContain, + mockHistogram, + mockValueRecorder, + ensureValueRecorderIsCorrect, + ensureHistogramIsCorrect, +} from '../helper'; +import { CollectorExporterConfigBrowser } from '../../src/platform/browser/types'; +import { hrTimeToNanoseconds } from '@opentelemetry/core'; +const sendBeacon = navigator.sendBeacon; + +describe('CollectorMetricExporter - web', () => { + let collectorExporter: CollectorMetricExporter; + let spyOpen: any; + let spySend: any; + let spyBeacon: any; + let metrics: MetricRecord[]; + + beforeEach(() => { + spyOpen = sinon.stub(XMLHttpRequest.prototype, 'open'); + spySend = sinon.stub(XMLHttpRequest.prototype, 'send'); + spyBeacon = sinon.stub(navigator, 'sendBeacon'); + metrics = []; + metrics.push(Object.assign({}, mockCounter)); + metrics.push(Object.assign({}, mockObserver)); + metrics.push(Object.assign({}, mockHistogram)); + metrics.push(Object.assign({}, mockValueRecorder)); + + metrics[0].aggregator.update(1); + metrics[1].aggregator.update(10); + metrics[2].aggregator.update(7); + metrics[2].aggregator.update(14); + (metrics[2].aggregator as HistogramAggregator).reset(); + metrics[3].aggregator.update(5); + }); + + afterEach(() => { + metrics[0].aggregator.update(-1); // Aggregator is not deep-copied + (metrics[2].aggregator as HistogramAggregator).reset(); + navigator.sendBeacon = sendBeacon; + spyOpen.restore(); + spySend.restore(); + spyBeacon.restore(); + }); + + describe('export', () => { + describe('when "sendBeacon" is available', () => { + beforeEach(() => { + collectorExporter = new CollectorMetricExporter({ + logger: new NoopLogger(), + url: 'http://foo.bar.com', + serviceName: 'bar', + }); + // Overwrites the start time to make tests consistent + Object.defineProperty(collectorExporter, '_startTime', { + value: 1592602232694000000, + }); + }); + it('should successfully send metrics using sendBeacon', done => { + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const args = spyBeacon.args[0]; + const url = args[0]; + const body = args[1]; + const json = JSON.parse( + body + ) as collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest; + const metric1 = + json.resourceMetrics[0].instrumentationLibraryMetrics[0].metrics[0]; + const metric2 = + json.resourceMetrics[1].instrumentationLibraryMetrics[0].metrics[0]; + const metric3 = + json.resourceMetrics[2].instrumentationLibraryMetrics[0].metrics[0]; + const metric4 = + json.resourceMetrics[3].instrumentationLibraryMetrics[0].metrics[0]; + assert.ok(typeof metric1 !== 'undefined', "metric doesn't exist"); + if (metric1) { + ensureCounterIsCorrect( + metric1, + hrTimeToNanoseconds(metrics[0].aggregator.toPoint().timestamp) + ); + } + + assert.ok( + typeof metric2 !== 'undefined', + "second metric doesn't exist" + ); + if (metric2) { + ensureObserverIsCorrect( + metric2, + hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp) + ); + } + + assert.ok( + typeof metric3 !== 'undefined', + "third metric doesn't exist" + ); + if (metric3) { + ensureHistogramIsCorrect( + metric3, + hrTimeToNanoseconds(metrics[2].aggregator.toPoint().timestamp) + ); + } + + assert.ok( + typeof metric4 !== 'undefined', + "fourth metric doesn't exist" + ); + if (metric4) { + ensureValueRecorderIsCorrect( + metric4, + hrTimeToNanoseconds(metrics[3].aggregator.toPoint().timestamp) + ); + } + + const resource = json.resourceMetrics[0].resource; + assert.ok(typeof resource !== 'undefined', "resource doesn't exist"); + if (resource) { + ensureWebResourceIsCorrect(resource); + } + + assert.strictEqual(url, 'http://foo.bar.com'); + assert.strictEqual(spyBeacon.callCount, 1); + + assert.strictEqual(spyOpen.callCount, 0); + + ensureExportMetricsServiceRequestIsSet(json); + + done(); + }); + }); + + it('should log the successful message', done => { + const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug'); + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + spyBeacon.restore(); + spyBeacon = sinon.stub(window.navigator, 'sendBeacon').returns(true); + + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const response: any = spyLoggerDebug.args[1][0]; + assert.strictEqual(response, 'sendBeacon - can send'); + assert.strictEqual(spyLoggerError.args.length, 0); + + done(); + }); + }); + + it('should log the error message', done => { + const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug'); + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + spyBeacon.restore(); + spyBeacon = sinon.stub(window.navigator, 'sendBeacon').returns(false); + + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const response: any = spyLoggerError.args[0][0]; + assert.strictEqual(response, 'sendBeacon - cannot send'); + assert.strictEqual(spyLoggerDebug.args.length, 1); + + done(); + }); + }); + }); + + describe('when "sendBeacon" is NOT available', () => { + let server: any; + beforeEach(() => { + (window.navigator as any).sendBeacon = false; + collectorExporter = new CollectorMetricExporter({ + logger: new NoopLogger(), + url: 'http://foo.bar.com', + serviceName: 'bar', + }); + // Overwrites the start time to make tests consistent + Object.defineProperty(collectorExporter, '_startTime', { + value: 1592602232694000000, + }); + server = sinon.fakeServer.create(); + }); + afterEach(() => { + server.restore(); + }); + + it('should successfully send the metrics using XMLHttpRequest', done => { + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const request = server.requests[0]; + assert.strictEqual(request.method, 'POST'); + assert.strictEqual(request.url, 'http://foo.bar.com'); + + const body = request.requestBody; + const json = JSON.parse( + body + ) as collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest; + const metric1 = + json.resourceMetrics[0].instrumentationLibraryMetrics[0].metrics[0]; + const metric2 = + json.resourceMetrics[1].instrumentationLibraryMetrics[0].metrics[0]; + const metric3 = + json.resourceMetrics[2].instrumentationLibraryMetrics[0].metrics[0]; + const metric4 = + json.resourceMetrics[3].instrumentationLibraryMetrics[0].metrics[0]; + assert.ok(typeof metric1 !== 'undefined', "metric doesn't exist"); + if (metric1) { + ensureCounterIsCorrect( + metric1, + hrTimeToNanoseconds(metrics[0].aggregator.toPoint().timestamp) + ); + } + assert.ok( + typeof metric2 !== 'undefined', + "second metric doesn't exist" + ); + if (metric2) { + ensureObserverIsCorrect( + metric2, + hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp) + ); + } + + assert.ok( + typeof metric2 !== 'undefined', + "second metric doesn't exist" + ); + if (metric2) { + ensureObserverIsCorrect( + metric2, + hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp) + ); + } + + assert.ok( + typeof metric3 !== 'undefined', + "third metric doesn't exist" + ); + if (metric3) { + ensureHistogramIsCorrect( + metric3, + hrTimeToNanoseconds(metrics[2].aggregator.toPoint().timestamp) + ); + } + + assert.ok( + typeof metric4 !== 'undefined', + "fourth metric doesn't exist" + ); + if (metric4) { + ensureValueRecorderIsCorrect( + metric4, + hrTimeToNanoseconds(metrics[3].aggregator.toPoint().timestamp) + ); + } + + const resource = json.resourceMetrics[0].resource; + assert.ok(typeof resource !== 'undefined', "resource doesn't exist"); + if (resource) { + ensureWebResourceIsCorrect(resource); + } + + assert.strictEqual(spyBeacon.callCount, 0); + ensureExportMetricsServiceRequestIsSet(json); + + done(); + }); + }); + + it('should log the successful message', done => { + const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug'); + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const request = server.requests[0]; + request.respond(200); + + const response: any = spyLoggerDebug.args[1][0]; + assert.strictEqual(response, 'xhr success'); + assert.strictEqual(spyLoggerError.args.length, 0); + + assert.strictEqual(spyBeacon.callCount, 0); + done(); + }); + }); + + it('should log the error message', done => { + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const request = server.requests[0]; + request.respond(400); + + const response1: any = spyLoggerError.args[0][0]; + const response2: any = spyLoggerError.args[1][0]; + assert.strictEqual(response1, 'body'); + assert.strictEqual(response2, 'xhr error'); + + assert.strictEqual(spyBeacon.callCount, 0); + done(); + }); + }); + it('should send custom headers', done => { + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const request = server.requests[0]; + request.respond(200); + + assert.strictEqual(spyBeacon.callCount, 0); + done(); + }); + }); + }); + }); + + describe('export with custom headers', () => { + let server: any; + const customHeaders = { + foo: 'bar', + bar: 'baz', + }; + let collectorExporterConfig: CollectorExporterConfigBrowser; + + beforeEach(() => { + collectorExporterConfig = { + logger: new NoopLogger(), + headers: customHeaders, + }; + server = sinon.fakeServer.create(); + }); + + afterEach(() => { + server.restore(); + }); + + describe('when "sendBeacon" is available', () => { + beforeEach(() => { + collectorExporter = new CollectorMetricExporter( + collectorExporterConfig + ); + }); + it('should successfully send custom headers using XMLHTTPRequest', done => { + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const [{ requestHeaders }] = server.requests; + + ensureHeadersContain(requestHeaders, customHeaders); + assert.strictEqual(spyBeacon.callCount, 0); + assert.strictEqual(spyOpen.callCount, 0); + + done(); + }); + }); + }); + + describe('when "sendBeacon" is NOT available', () => { + beforeEach(() => { + (window.navigator as any).sendBeacon = false; + collectorExporter = new CollectorMetricExporter( + collectorExporterConfig + ); + }); + + it('should successfully send metrics using XMLHttpRequest', done => { + collectorExporter.export(metrics, () => {}); + + setTimeout(() => { + const [{ requestHeaders }] = server.requests; + + ensureHeadersContain(requestHeaders, customHeaders); + assert.strictEqual(spyBeacon.callCount, 0); + assert.strictEqual(spyOpen.callCount, 0); + + done(); + }); + }); + }); + }); +}); diff --git a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts index 6e4563e6b6b..bb5268cf146 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts @@ -33,26 +33,40 @@ import { HistogramAggregator } from '@opentelemetry/metrics'; import { hrTimeToNanoseconds } from '@opentelemetry/core'; describe('transformMetrics', () => { describe('toCollectorMetric', () => { - it('should convert metric', () => { + beforeEach(() => { + // Counter mockCounter.aggregator.update(1); + + // Observer + mockObserver.aggregator.update(10); + + // Histogram + mockHistogram.aggregator.update(7); + mockHistogram.aggregator.update(14); + (mockHistogram.aggregator as HistogramAggregator).reset(); + + // ValueRecorder + mockValueRecorder.aggregator.update(5); + }); + + afterEach(() => { + mockCounter.aggregator.update(-1); // Reset counter + (mockHistogram.aggregator as HistogramAggregator).reset(); + }); + it('should convert metric', () => { ensureCounterIsCorrect( transform.toCollectorMetric(mockCounter, 1592602232694000000), hrTimeToNanoseconds(mockCounter.aggregator.toPoint().timestamp) ); - mockObserver.aggregator.update(10); ensureObserverIsCorrect( transform.toCollectorMetric(mockObserver, 1592602232694000000), hrTimeToNanoseconds(mockObserver.aggregator.toPoint().timestamp) ); - mockHistogram.aggregator.update(7); - mockHistogram.aggregator.update(14); - (mockHistogram.aggregator as HistogramAggregator).reset(); ensureHistogramIsCorrect( transform.toCollectorMetric(mockHistogram, 1592602232694000000), hrTimeToNanoseconds(mockHistogram.aggregator.toPoint().timestamp) ); - mockValueRecorder.aggregator.update(5); ensureValueRecorderIsCorrect( transform.toCollectorMetric(mockValueRecorder, 1592602232694000000), hrTimeToNanoseconds(mockValueRecorder.aggregator.toPoint().timestamp) diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index f2ed9cf0487..a1070257ebb 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -836,7 +836,11 @@ export function ensureExportMetricsServiceRequestIsSet( json: collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest ) { const resourceMetrics = json.resourceMetrics; - assert.strictEqual(resourceMetrics.length, 2, 'resourceMetrics is missing'); + assert.strictEqual( + resourceMetrics.length, + 4, + 'resourceMetrics is the incorrect length' + ); const resource = resourceMetrics[0].resource; assert.strictEqual(!!resource, true, 'resource is missing'); From 5bd4fef0466ad7faacc432302a0a70e239971e68 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Sun, 12 Jul 2020 21:03:58 +0000 Subject: [PATCH 03/34] Basic node --- .../platform/node/CollectorMetricExporter.ts | 137 ++++++++++++ .../src/platform/node/index.ts | 1 + .../src/platform/node/types.ts | 10 + .../src/platform/node/utilWithJson.ts | 56 ++++- .../test/common/transformMetrics.test.ts | 26 ++- .../test/helper.ts | 45 ++++ .../test/node/CollectorMetricExporter.test.ts | 202 ++++++++++++++++++ 7 files changed, 466 insertions(+), 11 deletions(-) create mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts create mode 100644 packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts new file mode 100644 index 00000000000..727a236c018 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -0,0 +1,137 @@ +/* + * 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 { MetricRecord } from '@opentelemetry/metrics'; +import * as collectorTypes from '../../types'; +import { CollectorExporterConfigNode } from './types'; +import { GRPCMetricQueueItem, ServiceClient } from './types'; +import { removeProtocol } from './util'; +import * as path from 'path'; +import * as protoLoader from '@grpc/proto-loader'; +import * as grpc from 'grpc'; +import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; +import { CollectorMetricExporterBase } from '../../CollectorMetricExporterBase'; +import { parseHeaders } from '../../util'; + +const DEFAULT_COLLECTOR_URL = 'localhost:55678'; + +/** + * Collector Metric Exporter for Node + */ +export class CollectorMetricExporter extends CollectorMetricExporterBase< + CollectorExporterConfigNode +> { + DEFAULT_HEADERS: Record = { + [collectorTypes.OT_REQUEST_HEADER]: '1', + }; + isShutDown: boolean = false; + grpcMetricsQueue: GRPCMetricQueueItem[] = []; + metricServiceClient?: ServiceClient = undefined; + credentials: grpc.ChannelCredentials; + metadata?: grpc.Metadata; + headers: Record; + + constructor(config: CollectorExporterConfigNode = {}) { + super(config); + this.grpcMetricsQueue = []; + this.credentials = config.credentials || grpc.credentials.createInsecure(); + this.metadata = config.metadata; + this.headers = + parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; + } + + onShutdown(): void { + this.isShutDown = true; + if (this.metricServiceClient) { + this.metricServiceClient.close(); + } + } + + onInit(): void { + this.isShutDown = false; + const serverAddress = removeProtocol(this.url); + const metricServiceProtoPath = + 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; + const includeDirs = [path.resolve(__dirname, 'protos')]; + protoLoader + .load(metricServiceProtoPath, { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs, + }) + .then(packageDefinition => { + const packageObject: any = grpc.loadPackageDefinition( + packageDefinition + ); + this.metricServiceClient = new packageObject.opentelemetry.proto.collector.metrics.v1.MetricsService( + serverAddress, + this.credentials + ); + if (this.grpcMetricsQueue.length > 0) { + const queue = this.grpcMetricsQueue.splice(0); + queue.forEach((item: GRPCMetricQueueItem) => { + this.sendMetrics(item.metrics, item.onSuccess, item.onError); + }); + } + }); + } + + sendMetrics( + metrics: MetricRecord[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void + ): void { + if (this.isShutDown) { + this.logger.debug('Shutdown already started. Cannot send metrics'); + return; + } + if (this.metricServiceClient) { + const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( + metrics, + this._startTime, + this + ); + this.metricServiceClient.export( + exportMetricServiceRequest, + this.metadata, + (err: collectorTypes.ExportServiceError) => { + if (err) { + this.logger.error( + 'exportMetricServiceRequest', + exportMetricServiceRequest + ); + onError(err); + } else { + onSuccess(); + } + } + ); + } else { + this.grpcMetricsQueue.push({ + metrics, + onSuccess, + onError, + }); + } + } + + getDefaultUrl(url: string | undefined): string { + return url || DEFAULT_COLLECTOR_URL; + } +} diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/index.ts b/packages/opentelemetry-exporter-collector/src/platform/node/index.ts index 1c17973de2e..fcbe012b52b 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/index.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/index.ts @@ -15,3 +15,4 @@ */ export * from './CollectorTraceExporter'; +export * from './CollectorMetricExporter'; diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/types.ts b/packages/opentelemetry-exporter-collector/src/platform/node/types.ts index a7eb8c74b0d..6d074b8af7c 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/types.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/types.ts @@ -17,6 +17,7 @@ import * as grpc from 'grpc'; import { ReadableSpan } from '@opentelemetry/tracing'; import { CollectorProtocolNode } from '../../enums'; +import { MetricRecord } from '@opentelemetry/metrics'; import { CollectorExporterError, CollectorExporterConfigBase, @@ -32,6 +33,15 @@ export interface GRPCSpanQueueItem { onError: (error: CollectorExporterError) => void; } +/** + * Queue item to be used to save temporary metrics + */ +export interface GRPCMetricQueueItem { + metrics: MetricRecord[]; + onSuccess: () => void; + onError: (error: CollectorExporterError) => void; +} + /** * Service Client for sending spans or metrics */ diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts index a37638ac120..593a5672ba5 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts @@ -23,6 +23,10 @@ import * as collectorTypes from '../../types'; import { toCollectorExportTraceServiceRequest } from '../../transform'; import { CollectorTraceExporter } from './CollectorTraceExporter'; import { CollectorExporterConfigNode } from './types'; +import { CollectorMetricExporter } from './CollectorMetricExporter'; +import { MetricRecord } from '@opentelemetry/metrics'; +import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; +import { Logger } from '@opentelemetry/api'; export const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/trace'; @@ -33,6 +37,30 @@ export function onInitWithJson( // nothing to be done for json yet } +export function sendMetricsUsingJson( + collector: CollectorMetricExporter, + metrics: MetricRecord[], + startTime: number, + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +): void { + const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( + metrics, + startTime, + collector + ); + + const body = JSON.stringify(exportMetricServiceRequest); + _sendWithJson( + body, + collector.url, + collector.headers, + collector.logger, + onSuccess, + onError + ); +} + export function sendSpansUsingJson( collector: CollectorTraceExporter, spans: ReadableSpan[], @@ -45,7 +73,25 @@ export function sendSpansUsingJson( ); const body = JSON.stringify(exportTraceServiceRequest); - const parsedUrl = new url.URL(collector.url); + _sendWithJson( + body, + collector.url, + collector.headers, + collector.logger, + onSuccess, + onError + ); +} + +function _sendWithJson( + body: string, + collectorUrl: string, + collectorHeaders: Partial>, + logger: Logger, + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +): void { + const parsedUrl = new url.URL(collectorUrl); const options = { hostname: parsedUrl.hostname, @@ -55,17 +101,17 @@ export function sendSpansUsingJson( headers: { 'Content-Length': Buffer.byteLength(body), 'Content-Type': 'application/json', - ...collector.headers, + ...collectorHeaders, }, }; const request = parsedUrl.protocol === 'http:' ? http.request : https.request; const req = request(options, (res: http.IncomingMessage) => { if (res.statusCode && res.statusCode < 299) { - collector.logger.debug(`statusCode: ${res.statusCode}`); + logger.debug(`statusCode: ${res.statusCode}`); onSuccess(); } else { - collector.logger.error(`statusCode: ${res.statusCode}`); + logger.error(`statusCode: ${res.statusCode}`); onError({ code: res.statusCode, message: res.statusMessage, @@ -74,7 +120,7 @@ export function sendSpansUsingJson( }); req.on('error', (error: Error) => { - collector.logger.error('error', error.message); + logger.error('error', error.message); onError({ message: error.message, }); diff --git a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts index 6e4563e6b6b..bb5268cf146 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts @@ -33,26 +33,40 @@ import { HistogramAggregator } from '@opentelemetry/metrics'; import { hrTimeToNanoseconds } from '@opentelemetry/core'; describe('transformMetrics', () => { describe('toCollectorMetric', () => { - it('should convert metric', () => { + beforeEach(() => { + // Counter mockCounter.aggregator.update(1); + + // Observer + mockObserver.aggregator.update(10); + + // Histogram + mockHistogram.aggregator.update(7); + mockHistogram.aggregator.update(14); + (mockHistogram.aggregator as HistogramAggregator).reset(); + + // ValueRecorder + mockValueRecorder.aggregator.update(5); + }); + + afterEach(() => { + mockCounter.aggregator.update(-1); // Reset counter + (mockHistogram.aggregator as HistogramAggregator).reset(); + }); + it('should convert metric', () => { ensureCounterIsCorrect( transform.toCollectorMetric(mockCounter, 1592602232694000000), hrTimeToNanoseconds(mockCounter.aggregator.toPoint().timestamp) ); - mockObserver.aggregator.update(10); ensureObserverIsCorrect( transform.toCollectorMetric(mockObserver, 1592602232694000000), hrTimeToNanoseconds(mockObserver.aggregator.toPoint().timestamp) ); - mockHistogram.aggregator.update(7); - mockHistogram.aggregator.update(14); - (mockHistogram.aggregator as HistogramAggregator).reset(); ensureHistogramIsCorrect( transform.toCollectorMetric(mockHistogram, 1592602232694000000), hrTimeToNanoseconds(mockHistogram.aggregator.toPoint().timestamp) ); - mockValueRecorder.aggregator.update(5); ensureValueRecorderIsCorrect( transform.toCollectorMetric(mockValueRecorder, 1592602232694000000), hrTimeToNanoseconds(mockValueRecorder.aggregator.toPoint().timestamp) diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index f2ed9cf0487..62b556bb521 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -757,6 +757,51 @@ export function ensureHistogramIsCorrect( }); } +export function ensureExportedCounterIsCorrect( + metric: collectorTypes.opentelemetryProto.metrics.v1.Metric +) { + assert.deepStrictEqual(metric.metricDescriptor, { + name: 'test-counter', + description: 'sample counter description', + unit: '1', + type: 'MONOTONIC_INT64', + temporality: 'CUMULATIVE', + }); + assert.deepStrictEqual(metric.doubleDataPoints, []); + assert.deepStrictEqual(metric.summaryDataPoints, []); + assert.deepStrictEqual(metric.histogramDataPoints, []); + assert.ok(metric.int64DataPoints); + assert.deepStrictEqual(metric.int64DataPoints[0].labels, []); + assert.deepStrictEqual(metric.int64DataPoints[0].value, '1'); + assert.deepStrictEqual( + metric.int64DataPoints[0].startTimeUnixNano, + '1592602232694000128' + ); +} + +export function ensureExportedObserverIsCorrect( + metric: collectorTypes.opentelemetryProto.metrics.v1.Metric +) { + assert.deepStrictEqual(metric.metricDescriptor, { + name: 'test-observer', + description: 'sample observer description', + unit: '2', + type: 'DOUBLE', + temporality: 'DELTA', + }); + + assert.deepStrictEqual(metric.int64DataPoints, []); + assert.deepStrictEqual(metric.summaryDataPoints, []); + assert.deepStrictEqual(metric.histogramDataPoints, []); + assert.ok(metric.doubleDataPoints); + assert.deepStrictEqual(metric.doubleDataPoints[0].labels, []); + assert.deepStrictEqual(metric.doubleDataPoints[0].value, 10); + assert.deepStrictEqual( + metric.doubleDataPoints[0].startTimeUnixNano, + '1592602232694000128' + ); +} + export function ensureResourceIsCorrect( resource: collectorTypes.opentelemetryProto.resource.v1.Resource ) { diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts new file mode 100644 index 00000000000..25bbbbc7e35 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -0,0 +1,202 @@ +/* + * 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 protoLoader from '@grpc/proto-loader'; +import * as grpc from 'grpc'; +import * as path from 'path'; +import * as fs from 'fs'; + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { CollectorMetricExporter } from '../../src/platform/node'; +import * as collectorTypes from '../../src/types'; +import { MetricRecord } from '@opentelemetry/metrics'; +import { + mockCounter, + mockObserver, + ensureExportedCounterIsCorrect, + ensureExportedObserverIsCorrect, + ensureMetadataIsCorrect, + ensureResourceIsCorrect, +} from '../helper'; + +const metricsServiceProtoPath = + 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; +const includeDirs = [path.resolve(__dirname, '../../src/platform/node/protos')]; + +const address = 'localhost:1501'; + +type TestParams = { + useTLS?: boolean; + metadata?: grpc.Metadata; +}; + +const metadata = new grpc.Metadata(); +metadata.set('k', 'v'); + +const testCollectorMetricExporter = (params: TestParams) => + describe(`CollectorMetricExporter - node ${ + params.useTLS ? 'with' : 'without' + } TLS, ${params.metadata ? 'with' : 'without'} metadata`, () => { + let collectorExporter: CollectorMetricExporter; + let server: grpc.Server; + let exportedData: + | collectorTypes.opentelemetryProto.metrics.v1.ResourceMetrics[] + | undefined; + let metrics: MetricRecord[]; + let reqMetadata: grpc.Metadata | undefined; + + before(done => { + server = new grpc.Server(); + protoLoader + .load(metricsServiceProtoPath, { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs, + }) + .then((packageDefinition: protoLoader.PackageDefinition) => { + const packageObject: any = grpc.loadPackageDefinition( + packageDefinition + ); + server.addService( + packageObject.opentelemetry.proto.collector.metrics.v1 + .MetricsService.service, + { + Export: (data: { + request: collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest; + metadata: grpc.Metadata; + }) => { + try { + exportedData = data.request.resourceMetrics; + reqMetadata = data.metadata; + } catch (e) { + exportedData = undefined; + } + }, + } + ); + const credentials = params.useTLS + ? grpc.ServerCredentials.createSsl( + fs.readFileSync('./test/certs/ca.crt'), + [ + { + cert_chain: fs.readFileSync('./test/certs/server.crt'), + private_key: fs.readFileSync('./test/certs/server.key'), + }, + ] + ) + : grpc.ServerCredentials.createInsecure(); + server.bind(address, credentials); + server.start(); + done(); + }); + }); + + after(() => { + server.forceShutdown(); + }); + + beforeEach(done => { + const credentials = params.useTLS + ? grpc.credentials.createSsl( + fs.readFileSync('./test/certs/ca.crt'), + fs.readFileSync('./test/certs/client.key'), + fs.readFileSync('./test/certs/client.crt') + ) + : undefined; + collectorExporter = new CollectorMetricExporter({ + url: address, + credentials, + serviceName: 'basic-service', + metadata: params.metadata, + }); + // Overwrites the start time to make tests consistent + Object.defineProperty(collectorExporter, '_startTime', { + value: 1592602232694000000, + }); + metrics = []; + metrics.push(Object.assign({}, mockCounter)); + metrics.push(Object.assign({}, mockObserver)); + metrics[0].aggregator.update(1); + metrics[1].aggregator.update(10); + done(); + }); + + afterEach(() => { + metrics[0].aggregator.update(-1); // Aggregator is not deep-copied + exportedData = undefined; + reqMetadata = undefined; + }); + + describe('export', () => { + it('should export metrics', done => { + const responseSpy = sinon.spy(); + collectorExporter.export(metrics, responseSpy); + setTimeout(() => { + assert.ok( + typeof exportedData !== 'undefined', + 'resource' + " doesn't exist" + ); + let resource; + if (exportedData) { + resource = exportedData[0].resource; + const counter = + exportedData[0].instrumentationLibraryMetrics[0].metrics[0]; + const observer = + exportedData[1].instrumentationLibraryMetrics[0].metrics[0]; + ensureExportedCounterIsCorrect(counter); + ensureExportedObserverIsCorrect(observer); + assert.ok( + typeof resource !== 'undefined', + "resource doesn't exist" + ); + if (resource) { + ensureResourceIsCorrect(resource); + } + } + if (params.metadata && reqMetadata) { + ensureMetadataIsCorrect(reqMetadata, params.metadata); + } + done(); + }, 500); + }); + }); + }); + +describe('CollectorMetricExporter - node (getDefaultUrl)', () => { + it('should default to localhost', done => { + const collectorExporter = new CollectorMetricExporter({}); + setTimeout(() => { + assert.strictEqual(collectorExporter['url'], 'localhost:55678'); + done(); + }); + }); + it('should keep the URL if included', done => { + const url = 'http://foo.bar.com'; + const collectorExporter = new CollectorMetricExporter({ url }); + setTimeout(() => { + assert.strictEqual(collectorExporter['url'], url); + done(); + }); + }); +}); + +testCollectorMetricExporter({ useTLS: true }); +testCollectorMetricExporter({ useTLS: false }); +testCollectorMetricExporter({ metadata }); From a002c1d3f2b069eb5b4f9a8da590dcb3de270e52 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 13 Jul 2020 16:41:25 +0000 Subject: [PATCH 04/34] Tests and functions --- .../platform/node/CollectorMetricExporter.ts | 86 ++++++------------- .../platform/node/CollectorTraceExporter.ts | 8 +- .../src/platform/node/utilWithGrpc.ts | 79 ++++++++++++++++- .../src/platform/node/utilWithJson.ts | 6 +- .../test/helper.ts | 32 +++++++ .../test/node/CollectorMetricExporter.test.ts | 46 +++++++++- 6 files changed, 191 insertions(+), 66 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts index 727a236c018..7ee1326478d 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -18,13 +18,12 @@ import { MetricRecord } from '@opentelemetry/metrics'; import * as collectorTypes from '../../types'; import { CollectorExporterConfigNode } from './types'; import { GRPCMetricQueueItem, ServiceClient } from './types'; -import { removeProtocol } from './util'; -import * as path from 'path'; -import * as protoLoader from '@grpc/proto-loader'; import * as grpc from 'grpc'; -import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; import { CollectorMetricExporterBase } from '../../CollectorMetricExporterBase'; import { parseHeaders } from '../../util'; +import { CollectorProtocolNode } from '../../enums'; +import { sendMetricsUsingJson, metricInitWithJson } from './utilWithJson'; +import { metricInitWithGrpc, sendMetricsUsingGrpc } from './utilWithGrpc'; const DEFAULT_COLLECTOR_URL = 'localhost:55678'; @@ -43,9 +42,25 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< credentials: grpc.ChannelCredentials; metadata?: grpc.Metadata; headers: Record; + private readonly _protocol: CollectorProtocolNode; constructor(config: CollectorExporterConfigNode = {}) { super(config); + this._protocol = + typeof config.protocolNode !== 'undefined' + ? config.protocolNode + : CollectorProtocolNode.GRPC; + if (this._protocol === CollectorProtocolNode.HTTP_JSON) { + this.logger.debug('CollectorExporter - using json over http'); + if (config.metadata) { + this.logger.warn('Metadata cannot be set when using json'); + } + } else { + this.logger.debug('CollectorExporter - using grpc'); + if (config.headers) { + this.logger.warn('Headers cannot be set when using grpc'); + } + } this.grpcMetricsQueue = []; this.credentials = config.credentials || grpc.credentials.createInsecure(); this.metadata = config.metadata; @@ -62,34 +77,12 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< onInit(): void { this.isShutDown = false; - const serverAddress = removeProtocol(this.url); - const metricServiceProtoPath = - 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; - const includeDirs = [path.resolve(__dirname, 'protos')]; - protoLoader - .load(metricServiceProtoPath, { - keepCase: false, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs, - }) - .then(packageDefinition => { - const packageObject: any = grpc.loadPackageDefinition( - packageDefinition - ); - this.metricServiceClient = new packageObject.opentelemetry.proto.collector.metrics.v1.MetricsService( - serverAddress, - this.credentials - ); - if (this.grpcMetricsQueue.length > 0) { - const queue = this.grpcMetricsQueue.splice(0); - queue.forEach((item: GRPCMetricQueueItem) => { - this.sendMetrics(item.metrics, item.onSuccess, item.onError); - }); - } - }); + + if (this._protocol === CollectorProtocolNode.HTTP_JSON) { + metricInitWithJson(this); + } else { + metricInitWithGrpc(this); + } } sendMetrics( @@ -101,33 +94,10 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< this.logger.debug('Shutdown already started. Cannot send metrics'); return; } - if (this.metricServiceClient) { - const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( - metrics, - this._startTime, - this - ); - this.metricServiceClient.export( - exportMetricServiceRequest, - this.metadata, - (err: collectorTypes.ExportServiceError) => { - if (err) { - this.logger.error( - 'exportMetricServiceRequest', - exportMetricServiceRequest - ); - onError(err); - } else { - onSuccess(); - } - } - ); + if (this._protocol === CollectorProtocolNode.HTTP_JSON) { + sendMetricsUsingJson(this, metrics, this._startTime, onSuccess, onError); } else { - this.grpcMetricsQueue.push({ - metrics, - onSuccess, - onError, - }); + sendMetricsUsingGrpc(this, metrics, this._startTime, onSuccess, onError); } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts index 4cdaf9e41de..c1b4dd861c6 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts @@ -29,12 +29,12 @@ import { import { DEFAULT_COLLECTOR_URL_GRPC, - onInitWithGrpc, + traceInitWithGrpc, sendSpansUsingGrpc, } from './utilWithGrpc'; import { DEFAULT_COLLECTOR_URL_JSON, - onInitWithJson, + traceInitWithJson, sendSpansUsingJson, } from './utilWithJson'; @@ -90,9 +90,9 @@ export class CollectorTraceExporter extends CollectorTraceExporterBase< this.isShutDown = false; if (config.protocolNode === CollectorProtocolNode.HTTP_JSON) { - onInitWithJson(this, config); + traceInitWithJson(this, config); } else { - onInitWithGrpc(this, config); + traceInitWithGrpc(this, config); } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts index 49ca20fc872..2a40170b10a 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts @@ -21,13 +21,20 @@ import * as collectorTypes from '../../types'; import { ReadableSpan } from '@opentelemetry/tracing'; import { toCollectorExportTraceServiceRequest } from '../../transform'; +import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; import { CollectorTraceExporter } from './CollectorTraceExporter'; -import { CollectorExporterConfigNode, GRPCSpanQueueItem } from './types'; +import { + CollectorExporterConfigNode, + GRPCSpanQueueItem, + GRPCMetricQueueItem, +} from './types'; import { removeProtocol } from './util'; +import { CollectorMetricExporter } from './CollectorMetricExporter'; +import { MetricRecord } from '@opentelemetry/metrics'; export const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; -export function onInitWithGrpc( +export function traceInitWithGrpc( collector: CollectorTraceExporter, config: CollectorExporterConfigNode ): void { @@ -64,6 +71,37 @@ export function onInitWithGrpc( }); } +export function metricInitWithGrpc(collector: CollectorMetricExporter): void { + collector.grpcMetricsQueue = []; + collector.isShutDown = false; + const serverAddress = removeProtocol(collector.url); + const metricServiceProtoPath = + 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; + const includeDirs = [path.resolve(__dirname, 'protos')]; + protoLoader + .load(metricServiceProtoPath, { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs, + }) + .then(packageDefinition => { + const packageObject: any = grpc.loadPackageDefinition(packageDefinition); + collector.metricServiceClient = new packageObject.opentelemetry.proto.collector.metrics.v1.MetricsService( + serverAddress, + collector.credentials + ); + if (collector.grpcMetricsQueue.length > 0) { + const queue = collector.grpcMetricsQueue.splice(0); + queue.forEach((item: GRPCMetricQueueItem) => { + collector.sendMetrics(item.metrics, item.onSuccess, item.onError); + }); + } + }); +} + export function sendSpansUsingGrpc( collector: CollectorTraceExporter, spans: ReadableSpan[], @@ -99,3 +137,40 @@ export function sendSpansUsingGrpc( }); } } + +export function sendMetricsUsingGrpc( + collector: CollectorMetricExporter, + metrics: MetricRecord[], + startTime: number, + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +): void { + if (collector.metricServiceClient) { + const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( + metrics, + startTime, + collector + ); + collector.metricServiceClient.export( + exportMetricServiceRequest, + collector.metadata, + (err: collectorTypes.ExportServiceError) => { + if (err) { + collector.logger.error( + 'exportMetricServiceRequest', + exportMetricServiceRequest + ); + onError(err); + } else { + onSuccess(); + } + } + ); + } else { + collector.grpcMetricsQueue.push({ + metrics, + onSuccess, + onError, + }); + } +} diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts index 593a5672ba5..9d949f44839 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts @@ -30,13 +30,17 @@ import { Logger } from '@opentelemetry/api'; export const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/trace'; -export function onInitWithJson( +export function traceInitWithJson( _collector: CollectorTraceExporter, _config: CollectorExporterConfigNode ): void { // nothing to be done for json yet } +export function metricInitWithJson(_collector: CollectorMetricExporter): void { + // nothing to be done for json yet +} + export function sendMetricsUsingJson( collector: CollectorMetricExporter, metrics: MetricRecord[], diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index 62b556bb521..361086701d2 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -802,6 +802,38 @@ export function ensureExportedObserverIsCorrect( ); } +export function ensureExportedHistogramIsCorrect( + metric: collectorTypes.opentelemetryProto.metrics.v1.Metric +) { + assert.deepStrictEqual(metric.metricDescriptor, { + name: 'test-hist', + description: 'sample observer description', + unit: '2', + type: 'HISTOGRAM', + temporality: 'DELTA', + }); + assert.deepStrictEqual(metric.int64DataPoints, []); + assert.deepStrictEqual(metric.summaryDataPoints, []); + assert.deepStrictEqual(metric.doubleDataPoints, []); + assert.ok(metric.histogramDataPoints); + assert.deepStrictEqual(metric.histogramDataPoints[0].labels, []); + assert.deepStrictEqual(metric.histogramDataPoints[0].count, '2'); + assert.deepStrictEqual(metric.histogramDataPoints[0].sum, 21); + assert.deepStrictEqual(metric.histogramDataPoints[0].buckets, [ + { count: '1', exemplar: null }, + { count: '1', exemplar: null }, + { count: '0', exemplar: null }, + ]); + assert.deepStrictEqual(metric.histogramDataPoints[0].explicitBounds, [ + 10, + 20, + ]); + assert.deepStrictEqual( + metric.histogramDataPoints[0].startTimeUnixNano, + '1592602232694000128' + ); +} + export function ensureResourceIsCorrect( resource: collectorTypes.opentelemetryProto.resource.v1.Resource ) { diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts index 25bbbbc7e35..374c12c7e75 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -23,15 +23,19 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { CollectorMetricExporter } from '../../src/platform/node'; import * as collectorTypes from '../../src/types'; -import { MetricRecord } from '@opentelemetry/metrics'; +import { MetricRecord, HistogramAggregator } from '@opentelemetry/metrics'; import { mockCounter, mockObserver, + mockHistogram, ensureExportedCounterIsCorrect, ensureExportedObserverIsCorrect, ensureMetadataIsCorrect, ensureResourceIsCorrect, + ensureExportedHistogramIsCorrect, } from '../helper'; +import { ConsoleLogger, LogLevel } from '@opentelemetry/core'; +import { CollectorProtocolNode } from '../../src'; const metricsServiceProtoPath = 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; @@ -133,17 +137,54 @@ const testCollectorMetricExporter = (params: TestParams) => metrics = []; metrics.push(Object.assign({}, mockCounter)); metrics.push(Object.assign({}, mockObserver)); + metrics.push(Object.assign({}, mockHistogram)); metrics[0].aggregator.update(1); metrics[1].aggregator.update(10); + metrics[2].aggregator.update(7); + metrics[2].aggregator.update(14); + (metrics[2].aggregator as HistogramAggregator).reset(); done(); }); afterEach(() => { metrics[0].aggregator.update(-1); // Aggregator is not deep-copied + (metrics[2].aggregator as HistogramAggregator).reset(); exportedData = undefined; reqMetadata = undefined; }); + describe('instance', () => { + it('should warn about headers when using grpc', () => { + const logger = new ConsoleLogger(LogLevel.DEBUG); + const spyLoggerWarn = sinon.stub(logger, 'warn'); + collectorExporter = new CollectorMetricExporter({ + logger, + serviceName: 'basic-service', + url: address, + headers: { + foo: 'bar', + }, + }); + const args = spyLoggerWarn.args[0]; + assert.strictEqual(args[0], 'Headers cannot be set when using grpc'); + }); + it('should warn about metadata when using json', () => { + const metadata = new grpc.Metadata(); + metadata.set('k', 'v'); + const logger = new ConsoleLogger(LogLevel.DEBUG); + const spyLoggerWarn = sinon.stub(logger, 'warn'); + collectorExporter = new CollectorMetricExporter({ + logger, + serviceName: 'basic-service', + url: address, + metadata, + protocolNode: CollectorProtocolNode.HTTP_JSON, + }); + const args = spyLoggerWarn.args[0]; + assert.strictEqual(args[0], 'Metadata cannot be set when using json'); + }); + }); + describe('export', () => { it('should export metrics', done => { const responseSpy = sinon.spy(); @@ -160,8 +201,11 @@ const testCollectorMetricExporter = (params: TestParams) => exportedData[0].instrumentationLibraryMetrics[0].metrics[0]; const observer = exportedData[1].instrumentationLibraryMetrics[0].metrics[0]; + const histogram = + exportedData[2].instrumentationLibraryMetrics[0].metrics[0]; ensureExportedCounterIsCorrect(counter); ensureExportedObserverIsCorrect(observer); + ensureExportedHistogramIsCorrect(histogram); assert.ok( typeof resource !== 'undefined', "resource doesn't exist" From 1d28df46387f15a3336300e47a084ab5bc160cee Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 13 Jul 2020 18:10:13 +0000 Subject: [PATCH 05/34] minor --- .../src/platform/browser/CollectorMetricExporter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts index e1d923ba014..4f5619d5500 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts @@ -19,6 +19,7 @@ import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; import { CollectorExporterError, OT_REQUEST_HEADER } from '../../types'; import { CollectorExporterConfigBrowser } from './types'; import { sendWithBeacon, sendWithXhr } from './util'; +import { parseHeaders } from '../../util'; const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/metrics'; @@ -33,12 +34,14 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< }; private _headers: { [key: string]: string }; private _useXHR: boolean = false; + /** * @param config */ constructor(config: CollectorExporterConfigBrowser = {}) { super(config); - this._headers = config.headers || this.DEFAULT_HEADERS; + this._headers = + parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; this._useXHR = !!config.headers || typeof navigator.sendBeacon !== 'function'; } From cf100b39353ce982a018ca915613a6de9675fa14 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 13 Jul 2020 19:20:16 +0000 Subject: [PATCH 06/34] new line --- .../src/platform/browser/CollectorMetricExporter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts index 4f5619d5500..3f907b20c6d 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { MetricRecord } from '@opentelemetry/metrics'; import { CollectorMetricExporterBase } from '../../CollectorMetricExporterBase'; import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; From 6a3e9484af6d9ab4a1f4609713fcda7ded7dd84b Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 13 Jul 2020 20:17:24 +0000 Subject: [PATCH 07/34] DefaultURL --- .../src/platform/node/CollectorMetricExporter.ts | 10 ++++++++-- ....test.ts => CollectorTraceExporterWithJson.test.ts} | 0 2 files changed, 8 insertions(+), 2 deletions(-) rename packages/opentelemetry-exporter-collector/test/node/{CollectorExporterWithJson.test.ts => CollectorTraceExporterWithJson.test.ts} (100%) diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts index 7ee1326478d..5ea7e9b7db5 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -25,7 +25,8 @@ import { CollectorProtocolNode } from '../../enums'; import { sendMetricsUsingJson, metricInitWithJson } from './utilWithJson'; import { metricInitWithGrpc, sendMetricsUsingGrpc } from './utilWithGrpc'; -const DEFAULT_COLLECTOR_URL = 'localhost:55678'; +const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; +const DEFAULT_COLLECTOR_URL_JSON = 'localhost:55680/v1/metrics'; /** * Collector Metric Exporter for Node @@ -102,6 +103,11 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< } getDefaultUrl(url: string | undefined): string { - return url || DEFAULT_COLLECTOR_URL; + if (!url) { + return this._protocol === CollectorProtocolNode.HTTP_JSON + ? DEFAULT_COLLECTOR_URL_JSON + : DEFAULT_COLLECTOR_URL_GRPC; + } + return url; } } diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts similarity index 100% rename from packages/opentelemetry-exporter-collector/test/node/CollectorExporterWithJson.test.ts rename to packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts From 4eb3831b108620090760087645f4579b207c68f7 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 13 Jul 2020 21:09:45 +0000 Subject: [PATCH 08/34] Add JSON tests --- .../src/CollectorMetricExporterBase.ts | 4 +- .../platform/node/CollectorMetricExporter.ts | 10 +- .../common/CollectorMetricExporter.test.ts | 4 +- .../test/helper.ts | 27 +- .../test/node/CollectorMetricExporter.test.ts | 10 +- .../CollectorMetricExporterWithJson.test.ts | 233 ++++++++++++++++++ 6 files changed, 277 insertions(+), 11 deletions(-) create mode 100644 packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts diff --git a/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts b/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts index 7580e5f8a73..78ac412763d 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts @@ -42,7 +42,7 @@ export abstract class CollectorMetricExporterBase< constructor(config: T = {} as T) { this.logger = config.logger || new NoopLogger(); this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME; - this.url = this.getDefaultUrl(config.url); + this.url = this.getDefaultUrl(config); this.attributes = config.attributes; if (typeof config.hostname === 'string') { this.hostname = config.hostname; @@ -108,7 +108,7 @@ export abstract class CollectorMetricExporterBase< this.onShutdown(); } - abstract getDefaultUrl(url: string | undefined): string; + abstract getDefaultUrl(config: T): string; abstract onInit(): void; abstract onShutdown(): void; abstract sendMetrics( diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts index 5ea7e9b7db5..126363532f1 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -26,7 +26,7 @@ import { sendMetricsUsingJson, metricInitWithJson } from './utilWithJson'; import { metricInitWithGrpc, sendMetricsUsingGrpc } from './utilWithGrpc'; const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; -const DEFAULT_COLLECTOR_URL_JSON = 'localhost:55680/v1/metrics'; +const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/metrics'; /** * Collector Metric Exporter for Node @@ -102,12 +102,12 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< } } - getDefaultUrl(url: string | undefined): string { - if (!url) { - return this._protocol === CollectorProtocolNode.HTTP_JSON + getDefaultUrl(config: CollectorExporterConfigNode): string { + if (!config.url) { + return config.protocolNode === CollectorProtocolNode.HTTP_JSON ? DEFAULT_COLLECTOR_URL_JSON : DEFAULT_COLLECTOR_URL_GRPC; } - return url; + return config.url; } } diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts index 73e9ddc40eb..3ef67aad71f 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts @@ -29,8 +29,8 @@ class CollectorMetricExporter extends CollectorMetricExporterBase< onInit() {} onShutdown() {} sendMetrics() {} - getDefaultUrl(url: string) { - return url || ''; + getDefaultUrl(config: CollectorExporterConfig) { + return config.url || ''; } } diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index 361086701d2..d7733c2c60b 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -834,6 +834,27 @@ export function ensureExportedHistogramIsCorrect( ); } +export function ensureExportedValueRecorderIsCorrect( + metric: collectorTypes.opentelemetryProto.metrics.v1.Metric +) { + assert.deepStrictEqual(metric.metricDescriptor, { + name: 'test-recorder', + description: 'sample recorder description', + unit: '3', + type: 'INT64', + temporality: 'DELTA', + }); + assert.deepStrictEqual(metric.histogramDataPoints, []); + assert.deepStrictEqual(metric.summaryDataPoints, []); + assert.deepStrictEqual(metric.doubleDataPoints, []); + assert.ok(metric.int64DataPoints); + assert.deepStrictEqual(metric.int64DataPoints[0].labels, []); + assert.deepStrictEqual( + metric.int64DataPoints[0].startTimeUnixNano, + '1592602232694000128' + ); +} + export function ensureResourceIsCorrect( resource: collectorTypes.opentelemetryProto.resource.v1.Resource ) { @@ -913,7 +934,11 @@ export function ensureExportMetricsServiceRequestIsSet( json: collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest ) { const resourceMetrics = json.resourceMetrics; - assert.strictEqual(resourceMetrics.length, 2, 'resourceMetrics is missing'); + assert.strictEqual( + resourceMetrics.length, + 4, + 'resourceMetrics is the incorrect length' + ); const resource = resourceMetrics[0].resource; assert.strictEqual(!!resource, true, 'resource is missing'); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts index 374c12c7e75..7f755126542 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -33,6 +33,8 @@ import { ensureMetadataIsCorrect, ensureResourceIsCorrect, ensureExportedHistogramIsCorrect, + ensureExportedValueRecorderIsCorrect, + mockValueRecorder, } from '../helper'; import { ConsoleLogger, LogLevel } from '@opentelemetry/core'; import { CollectorProtocolNode } from '../../src'; @@ -138,11 +140,14 @@ const testCollectorMetricExporter = (params: TestParams) => metrics.push(Object.assign({}, mockCounter)); metrics.push(Object.assign({}, mockObserver)); metrics.push(Object.assign({}, mockHistogram)); + metrics.push(Object.assign({}, mockValueRecorder)); + metrics[0].aggregator.update(1); metrics[1].aggregator.update(10); metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); (metrics[2].aggregator as HistogramAggregator).reset(); + metrics[3].aggregator.update(5); done(); }); @@ -203,9 +208,12 @@ const testCollectorMetricExporter = (params: TestParams) => exportedData[1].instrumentationLibraryMetrics[0].metrics[0]; const histogram = exportedData[2].instrumentationLibraryMetrics[0].metrics[0]; + const recorder = + exportedData[3].instrumentationLibraryMetrics[0].metrics[0]; ensureExportedCounterIsCorrect(counter); ensureExportedObserverIsCorrect(observer); ensureExportedHistogramIsCorrect(histogram); + ensureExportedValueRecorderIsCorrect(recorder); assert.ok( typeof resource !== 'undefined', "resource doesn't exist" @@ -227,7 +235,7 @@ describe('CollectorMetricExporter - node (getDefaultUrl)', () => { it('should default to localhost', done => { const collectorExporter = new CollectorMetricExporter({}); setTimeout(() => { - assert.strictEqual(collectorExporter['url'], 'localhost:55678'); + assert.strictEqual(collectorExporter['url'], 'localhost:55680'); done(); }); }); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts new file mode 100644 index 00000000000..88170b1e1d9 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts @@ -0,0 +1,233 @@ +/* + * 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 * as http from 'http'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { CollectorProtocolNode } from '../../src/enums'; +import { CollectorMetricExporter } from '../../src/platform/node'; +import { CollectorExporterConfigNode } from '../../src/platform/node/types'; +import * as collectorTypes from '../../src/types'; + +import { + mockCounter, + mockObserver, + mockHistogram, + ensureExportMetricsServiceRequestIsSet, + ensureCounterIsCorrect, + mockValueRecorder, + ensureValueRecorderIsCorrect, + ensureHistogramIsCorrect, + ensureObserverIsCorrect, +} from '../helper'; +import { MetricRecord, HistogramAggregator } from '@opentelemetry/metrics'; + +const fakeRequest = { + end: function () {}, + on: function () {}, + write: function () {}, +}; + +const mockRes = { + statusCode: 200, +}; + +const mockResError = { + statusCode: 400, +}; + +describe('CollectorExporter - node with json over http', () => { + let collectorExporter: CollectorMetricExporter; + let collectorExporterConfig: CollectorExporterConfigNode; + let spyRequest: sinon.SinonSpy; + let spyWrite: sinon.SinonSpy; + let metrics: MetricRecord[]; + describe('export', () => { + beforeEach(() => { + spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any); + spyWrite = sinon.stub(fakeRequest, 'write'); + collectorExporterConfig = { + headers: { + foo: 'bar', + }, + protocolNode: CollectorProtocolNode.HTTP_JSON, + hostname: 'foo', + logger: new core.NoopLogger(), + serviceName: 'bar', + attributes: {}, + url: 'http://foo.bar.com', + }; + collectorExporter = new CollectorMetricExporter(collectorExporterConfig); + // Overwrites the start time to make tests consistent + Object.defineProperty(collectorExporter, '_startTime', { + value: 1592602232694000000, + }); + metrics = []; + metrics.push(Object.assign({}, mockCounter)); + metrics.push(Object.assign({}, mockObserver)); + metrics.push(Object.assign({}, mockHistogram)); + metrics.push(Object.assign({}, mockValueRecorder)); + metrics[0].aggregator.update(1); + metrics[1].aggregator.update(10); + metrics[2].aggregator.update(7); + metrics[2].aggregator.update(14); + (metrics[2].aggregator as HistogramAggregator).reset(); + metrics[3].aggregator.update(5); + }); + afterEach(() => { + metrics[0].aggregator.update(-1); // Aggregator is not deep-copied + (metrics[2].aggregator as HistogramAggregator).reset(); + spyRequest.restore(); + spyWrite.restore(); + }); + + it('should open the connection', done => { + collectorExporter.export(metrics, () => {}); + + 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(metrics, () => {}); + + 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(metrics, () => {}); + + setTimeout(() => { + const writeArgs = spyWrite.args[0]; + const json = JSON.parse( + writeArgs[0] + ) as collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest; + const metric1 = + json.resourceMetrics[0].instrumentationLibraryMetrics[0].metrics[0]; + const metric2 = + json.resourceMetrics[1].instrumentationLibraryMetrics[0].metrics[0]; + const metric3 = + json.resourceMetrics[2].instrumentationLibraryMetrics[0].metrics[0]; + const metric4 = + json.resourceMetrics[3].instrumentationLibraryMetrics[0].metrics[0]; + assert.ok(typeof metric1 !== 'undefined', "counter doesn't exist"); + ensureCounterIsCorrect( + metric1, + core.hrTimeToNanoseconds(metrics[0].aggregator.toPoint().timestamp) + ); + assert.ok(typeof metric2 !== 'undefined', "observer doesn't exist"); + ensureObserverIsCorrect( + metric2, + core.hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp) + ); + assert.ok(typeof metric3 !== 'undefined', "histogram doesn't exist"); + ensureHistogramIsCorrect( + metric3, + core.hrTimeToNanoseconds(metrics[2].aggregator.toPoint().timestamp) + ); + assert.ok( + typeof metric4 !== 'undefined', + "value recorder doesn't exist" + ); + ensureValueRecorderIsCorrect( + metric4, + core.hrTimeToNanoseconds(metrics[3].aggregator.toPoint().timestamp) + ); + + ensureExportMetricsServiceRequestIsSet(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(metrics, 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(metrics, 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 CollectorMetricExporter({ + protocolNode: CollectorProtocolNode.HTTP_JSON, + }); + setTimeout(() => { + assert.strictEqual( + collectorExporter['url'], + 'http://localhost:55680/v1/metrics' + ); + done(); + }); + }); + + it('should keep the URL if included', done => { + const url = 'http://foo.bar.com'; + const collectorExporter = new CollectorMetricExporter({ url }); + setTimeout(() => { + assert.strictEqual(collectorExporter['url'], url); + done(); + }); + }); + }); +}); From 547e63b69eab5ed40e50759b094dacfbe959abbc Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 13 Jul 2020 21:17:44 +0000 Subject: [PATCH 09/34] Test rename --- .../test/node/CollectorMetricExporterWithJson.test.ts | 4 ++-- .../test/node/CollectorTraceExporterWithJson.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts index 88170b1e1d9..9e5155d2cd6 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts @@ -50,7 +50,7 @@ const mockResError = { statusCode: 400, }; -describe('CollectorExporter - node with json over http', () => { +describe('CollectorMetricExporter - node with json over http', () => { let collectorExporter: CollectorMetricExporter; let collectorExporterConfig: CollectorExporterConfigNode; let spyRequest: sinon.SinonSpy; @@ -207,7 +207,7 @@ describe('CollectorExporter - node with json over http', () => { }); }); }); - describe('CollectorTraceExporter - node (getDefaultUrl)', () => { + describe('CollectorMetricExporter - node (getDefaultUrl)', () => { it('should default to localhost', done => { const collectorExporter = new CollectorMetricExporter({ protocolNode: CollectorProtocolNode.HTTP_JSON, diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts index 3b21ea2c638..105e5b346e1 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorTraceExporterWithJson.test.ts @@ -44,7 +44,7 @@ const mockResError = { statusCode: 400, }; -describe('CollectorExporter - node with json over http', () => { +describe('CollectorTraceExporter - node with json over http', () => { let collectorExporter: CollectorTraceExporter; let collectorExporterConfig: CollectorExporterConfigNode; let spyRequest: sinon.SinonSpy; From b08de2c184cc3c8f5805bb2a5d1b44f03a15c3f8 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Tue, 14 Jul 2020 20:06:28 +0000 Subject: [PATCH 10/34] Refactor a lot --- ...porterBase.ts => CollectorExporterBase.ts} | 42 ++-- .../src/CollectorMetricExporterBase.ts | 119 --------- .../browser/CollectorTraceExporter.ts | 32 ++- .../node/CollectorExporterNodeBase.ts | 232 ++++++++++++++++++ .../platform/node/CollectorMetricExporter.ts | 111 +++------ .../platform/node/CollectorTraceExporter.ts | 121 +++------ .../src/platform/node/types.ts | 19 +- .../src/platform/node/utilWithGrpc.ts | 176 ------------- .../src/platform/node/utilWithJson.ts | 134 ---------- .../src/transform.ts | 8 +- .../src/transformMetrics.ts | 12 +- .../common/CollectorMetricExporter.test.ts | 21 +- .../common/CollectorTraceExporter.test.ts | 24 +- 13 files changed, 393 insertions(+), 658 deletions(-) rename packages/opentelemetry-exporter-collector/src/{CollectorTraceExporterBase.ts => CollectorExporterBase.ts} (72%) delete mode 100644 packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts delete mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts delete mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts diff --git a/packages/opentelemetry-exporter-collector/src/CollectorTraceExporterBase.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts similarity index 72% rename from packages/opentelemetry-exporter-collector/src/CollectorTraceExporterBase.ts rename to packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts index f1508ec0beb..1c6ff54f54e 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorTraceExporterBase.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporterBase.ts @@ -16,33 +16,32 @@ import { Attributes, Logger } from '@opentelemetry/api'; import { ExportResult, NoopLogger } from '@opentelemetry/core'; -import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import { CollectorExporterError, CollectorExporterConfigBase, ExportServiceError, } from './types'; -const DEFAULT_SERVICE_NAME = 'collector-exporter'; - /** - * Collector Trace Exporter abstract base class + * Collector Exporter abstract base class */ -export abstract class CollectorTraceExporterBase< - T extends CollectorExporterConfigBase -> implements SpanExporter { +export abstract class CollectorExporterBase< + T extends CollectorExporterConfigBase, + ExportItem, + ServiceRequest +> { public readonly serviceName: string; public readonly url: string; public readonly logger: Logger; public readonly hostname: string | undefined; public readonly attributes?: Attributes; - private _isShutdown: boolean = false; + protected _isShutdown: boolean = false; /** * @param config */ constructor(config: T = {} as T) { - this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME; + this.serviceName = this.getDefaultServiceName(config); this.url = this.getDefaultUrl(config); if (typeof config.hostname === 'string') { this.hostname = config.hostname; @@ -59,20 +58,17 @@ export abstract class CollectorTraceExporterBase< } /** - * Export spans. - * @param spans + * Export items. + * @param items * @param resultCallback */ - export( - spans: ReadableSpan[], - resultCallback: (result: ExportResult) => void - ) { + export(items: ExportItem[], resultCallback: (result: ExportResult) => void) { if (this._isShutdown) { resultCallback(ExportResult.FAILED_NOT_RETRYABLE); return; } - this._exportSpans(spans) + this._export(items) .then(() => { resultCallback(ExportResult.SUCCESS); }) @@ -88,13 +84,11 @@ export abstract class CollectorTraceExporterBase< }); } - private _exportSpans(spans: ReadableSpan[]): Promise { + private _export(items: ExportItem[]): Promise { return new Promise((resolve, reject) => { try { - this.logger.debug('spans to be sent', spans); - // Send spans to [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector} - // it will use the appropriate transport layer automatically depends on platform - this.sendSpans(spans, resolve, reject); + this.logger.debug('items to be sent', items); + this.send(items, resolve, reject); } catch (e) { reject(e); } @@ -118,10 +112,12 @@ export abstract class CollectorTraceExporterBase< abstract onShutdown(): void; abstract onInit(config: T): void; - abstract sendSpans( - spans: ReadableSpan[], + abstract send( + items: ExportItem[], onSuccess: () => void, onError: (error: CollectorExporterError) => void ): void; abstract getDefaultUrl(config: T): string; + abstract getDefaultServiceName(config: T): string; + abstract convert(objects: ExportItem[]): ServiceRequest; } diff --git a/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts b/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts deleted file mode 100644 index 78ac412763d..00000000000 --- a/packages/opentelemetry-exporter-collector/src/CollectorMetricExporterBase.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 { MetricExporter, MetricRecord } from '@opentelemetry/metrics'; -import { Attributes, Logger } from '@opentelemetry/api'; -import { CollectorExporterConfigBase } from './types'; -import { NoopLogger, ExportResult } from '@opentelemetry/core'; -import * as collectorTypes from './types'; - -const DEFAULT_SERVICE_NAME = 'collector-metric-exporter'; - -/** - * Collector Metric Exporter abstract base class - */ -export abstract class CollectorMetricExporterBase< - T extends CollectorExporterConfigBase -> implements MetricExporter { - public readonly serviceName: string; - public readonly url: string; - public readonly logger: Logger; - public readonly hostname: string | undefined; - public readonly attributes?: Attributes; - protected readonly _startTime = new Date().getTime() * 1000000; - private _isShutdown: boolean = false; - - /** - * @param config - */ - constructor(config: T = {} as T) { - this.logger = config.logger || new NoopLogger(); - this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME; - this.url = this.getDefaultUrl(config); - this.attributes = config.attributes; - if (typeof config.hostname === 'string') { - this.hostname = config.hostname; - } - this.onInit(); - } - - /** - * Export metrics - * @param metrics - * @param resultCallback - */ - export( - metrics: MetricRecord[], - resultCallback: (result: ExportResult) => void - ) { - if (this._isShutdown) { - resultCallback(ExportResult.FAILED_NOT_RETRYABLE); - return; - } - - this._exportMetrics(metrics) - .then(() => { - resultCallback(ExportResult.SUCCESS); - }) - .catch((error: collectorTypes.ExportServiceError) => { - if (error.message) { - this.logger.error(error.message); - } - if (error.code && error.code < 500) { - resultCallback(ExportResult.FAILED_NOT_RETRYABLE); - } else { - resultCallback(ExportResult.FAILED_RETRYABLE); - } - }); - } - - private _exportMetrics(metrics: MetricRecord[]): Promise { - return new Promise((resolve, reject) => { - try { - this.logger.debug('metrics to be sent', metrics); - // Send metrics to [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector} - // it will use the appropriate transport layer automatically depends on platform - this.sendMetrics(metrics, resolve, reject); - } catch (e) { - reject(e); - } - }); - } - - /** - * Shutdown the exporter. - */ - shutdown(): void { - if (this._isShutdown) { - this.logger.debug('shutdown already started'); - return; - } - this._isShutdown = true; - this.logger.debug('shutdown started'); - - // platform dependent - this.onShutdown(); - } - - abstract getDefaultUrl(config: T): string; - abstract onInit(): void; - abstract onShutdown(): void; - abstract sendMetrics( - metrics: MetricRecord[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ): void; -} diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts index 661f1e9b872..b836b913f7b 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts @@ -14,21 +14,26 @@ * limitations under the License. */ -import { CollectorTraceExporterBase } from '../../CollectorTraceExporterBase'; -import { ReadableSpan } from '@opentelemetry/tracing'; +import { CollectorExporterBase } from '../../CollectorExporterBase'; +import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import { toCollectorExportTraceServiceRequest } from '../../transform'; import { CollectorExporterConfigBrowser } from './types'; import * as collectorTypes from '../../types'; import { parseHeaders } from '../../util'; const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/trace'; +const DEFAULT_SERVICE_NAME = 'collector-exporter'; /** * Collector Exporter for Web */ -export class CollectorTraceExporter extends CollectorTraceExporterBase< - CollectorExporterConfigBrowser -> { +export class CollectorTraceExporter + extends CollectorExporterBase< + CollectorExporterConfigBrowser, + ReadableSpan, + collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest + > + implements SpanExporter { DEFAULT_HEADERS: Record = { [collectorTypes.OT_REQUEST_HEADER]: '1', }; @@ -58,15 +63,22 @@ export class CollectorTraceExporter extends CollectorTraceExporterBase< return config.url || DEFAULT_COLLECTOR_URL; } - sendSpans( + getDefaultServiceName(config: CollectorExporterConfigBrowser): string { + return config.serviceName || DEFAULT_SERVICE_NAME; + } + + convert( + spans: ReadableSpan[] + ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { + return toCollectorExportTraceServiceRequest(spans, this); + } + + send( spans: ReadableSpan[], onSuccess: () => void, onError: (error: collectorTypes.CollectorExporterError) => void ) { - const exportTraceServiceRequest = toCollectorExportTraceServiceRequest( - spans, - this - ); + const exportTraceServiceRequest = this.convert(spans); const body = JSON.stringify(exportTraceServiceRequest); if (this._useXHR) { diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts new file mode 100644 index 00000000000..daa76e8d4a8 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts @@ -0,0 +1,232 @@ +/* + * 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 { CollectorExporterBase } from '../../CollectorExporterBase'; +import { CollectorExporterConfigNode, GRPCQueueItem } from './types'; +import { ServiceClient } from './types'; +import * as grpc from 'grpc'; +import { CollectorProtocolNode } from '../../enums'; +import * as collectorTypes from '../../types'; +import { parseHeaders } from '../../util'; +import * as url from 'url'; +import * as http from 'http'; +import * as https from 'https'; +import * as path from 'path'; +import { removeProtocol } from './util'; +import * as protoLoader from '@grpc/proto-loader'; + +const DEFAULT_SERVICE_NAME = 'collector-metric-exporter'; + +/** + * Collector Metric Exporter abstract base class + */ +export abstract class CollectorExporterNodeBase< + ExportItem, + ServiceRequest +> extends CollectorExporterBase< + CollectorExporterConfigNode, + ExportItem, + ServiceRequest +> { + DEFAULT_HEADERS: Record = { + [collectorTypes.OT_REQUEST_HEADER]: '1', + }; + grpcQueue: GRPCQueueItem[]; + serviceClient?: ServiceClient = undefined; + credentials: grpc.ChannelCredentials; + metadata?: grpc.Metadata; + headers: Record; + protected readonly _protocol: CollectorProtocolNode; + + constructor(config: CollectorExporterConfigNode = {}) { + super(config); + this._protocol = + typeof config.protocolNode !== 'undefined' + ? config.protocolNode + : CollectorProtocolNode.GRPC; + if (this._protocol === CollectorProtocolNode.HTTP_JSON) { + this.logger.debug('CollectorExporter - using json over http'); + if (config.metadata) { + this.logger.warn('Metadata cannot be set when using json'); + } + } else { + this.logger.debug('CollectorExporter - using grpc'); + if (config.headers) { + this.logger.warn('Headers cannot be set when using grpc'); + } + } + this.grpcQueue = []; + this.credentials = config.credentials || grpc.credentials.createInsecure(); + this.metadata = config.metadata; + this.headers = + parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; + } + + onInit(config: CollectorExporterConfigNode): void { + this._isShutdown = false; + if (config.protocolNode === CollectorProtocolNode.HTTP_JSON) { + this.initWithJson(); + } else { + this.initWithGrpc(); + } + } + + send( + spans: ExportItem[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void + ): void { + if (this._isShutdown) { + this.logger.debug('Shutdown already started. Cannot send spans'); + return; + } + if (this._protocol === CollectorProtocolNode.HTTP_JSON) { + this.sendWithJson(spans, onSuccess, onError); + } else { + this.sendWithGrpc(spans, onSuccess, onError); + } + } + + onShutdown(): void { + this._isShutdown = true; + if (this.serviceClient) { + this.serviceClient.close(); + } + } + + getDefaultServiceName(config: CollectorExporterConfigNode): string { + return config.serviceName || DEFAULT_SERVICE_NAME; + } + + protected initWithJson(): void { + // Nothing to be done for JSON yet + } + + /** + * Initialize + */ + protected initWithGrpc(): void { + const serverAddress = removeProtocol(this.url); + const includeDirs = [path.resolve(__dirname, 'protos')]; + + protoLoader + .load(this.getServiceProtoPath(), { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs, + }) + .then(packageDefinition => { + const packageObject: any = grpc.loadPackageDefinition( + packageDefinition + ); + this.serviceClient = this.getServiceClient( + packageObject, + serverAddress + ); + if (this.grpcQueue.length > 0) { + const queue = this.grpcQueue.splice(0); + queue.forEach((item: GRPCQueueItem) => { + this.send(item.objects, item.onSuccess, item.onError); + }); + } + }); + } + + protected sendWithGrpc( + objects: ExportItem[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void + ): void { + if (this.serviceClient) { + const serviceRequest = this.convert(objects); + + this.serviceClient.export( + serviceRequest, + this.metadata, + (err: collectorTypes.ExportServiceError) => { + if (err) { + this.logger.error('Service request', serviceRequest); + onError(err); + } else { + this.logger.debug('spans sent'); + onSuccess(); + } + } + ); + } else { + this.grpcQueue.push({ + objects, + onSuccess, + onError, + }); + } + } + + protected sendWithJson( + objects: ExportItem[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void + ): void { + const serviceRequest = this.convert(objects); + const body = JSON.stringify(serviceRequest); + const parsedUrl = new url.URL(this.url); + + const options = { + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.pathname, + method: 'POST', + headers: { + 'Content-Length': Buffer.byteLength(body), + 'Content-Type': 'application/json', + ...this.headers, + }, + }; + + const request = + parsedUrl.protocol === 'http:' ? http.request : https.request; + const req = request(options, (res: http.IncomingMessage) => { + if (res.statusCode && res.statusCode < 299) { + this.logger.debug(`statusCode: ${res.statusCode}`); + onSuccess(); + } else { + this.logger.error(`statusCode: ${res.statusCode}`); + onError({ + code: res.statusCode, + message: res.statusMessage, + }); + } + }); + + req.on('error', (error: Error) => { + this.logger.error('error', error.message); + onError({ + message: error.message, + }); + }); + req.write(body); + req.end(); + } + + abstract getServiceProtoPath(): string; + abstract getServiceClient( + packageObject: any, + serverAddress: string + ): ServiceClient; +} diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts index 126363532f1..408a2e4d27e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -14,92 +14,36 @@ * limitations under the License. */ -import { MetricRecord } from '@opentelemetry/metrics'; +import { MetricRecord, MetricExporter } from '@opentelemetry/metrics'; import * as collectorTypes from '../../types'; -import { CollectorExporterConfigNode } from './types'; -import { GRPCMetricQueueItem, ServiceClient } from './types'; -import * as grpc from 'grpc'; -import { CollectorMetricExporterBase } from '../../CollectorMetricExporterBase'; -import { parseHeaders } from '../../util'; +import { CollectorExporterConfigNode, ServiceClient } from './types'; import { CollectorProtocolNode } from '../../enums'; -import { sendMetricsUsingJson, metricInitWithJson } from './utilWithJson'; -import { metricInitWithGrpc, sendMetricsUsingGrpc } from './utilWithGrpc'; +import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; +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'; /** * Collector Metric Exporter for Node */ -export class CollectorMetricExporter extends CollectorMetricExporterBase< - CollectorExporterConfigNode -> { - DEFAULT_HEADERS: Record = { - [collectorTypes.OT_REQUEST_HEADER]: '1', - }; - isShutDown: boolean = false; - grpcMetricsQueue: GRPCMetricQueueItem[] = []; - metricServiceClient?: ServiceClient = undefined; - credentials: grpc.ChannelCredentials; - metadata?: grpc.Metadata; - headers: Record; - private readonly _protocol: CollectorProtocolNode; +export class CollectorMetricExporter + extends CollectorExporterNodeBase< + MetricRecord, + collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest + > + implements MetricExporter { + protected readonly _startTime = new Date().getTime() * 1000000; - constructor(config: CollectorExporterConfigNode = {}) { - super(config); - this._protocol = - typeof config.protocolNode !== 'undefined' - ? config.protocolNode - : CollectorProtocolNode.GRPC; - if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - this.logger.debug('CollectorExporter - using json over http'); - if (config.metadata) { - this.logger.warn('Metadata cannot be set when using json'); - } - } else { - this.logger.debug('CollectorExporter - using grpc'); - if (config.headers) { - this.logger.warn('Headers cannot be set when using grpc'); - } - } - this.grpcMetricsQueue = []; - this.credentials = config.credentials || grpc.credentials.createInsecure(); - this.metadata = config.metadata; - this.headers = - parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; - } - - onShutdown(): void { - this.isShutDown = true; - if (this.metricServiceClient) { - this.metricServiceClient.close(); - } - } - - onInit(): void { - this.isShutDown = false; - - if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - metricInitWithJson(this); - } else { - metricInitWithGrpc(this); - } - } - - sendMetrics( - metrics: MetricRecord[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ): void { - if (this.isShutDown) { - this.logger.debug('Shutdown already started. Cannot send metrics'); - return; - } - if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - sendMetricsUsingJson(this, metrics, this._startTime, onSuccess, onError); - } else { - sendMetricsUsingGrpc(this, metrics, this._startTime, onSuccess, onError); - } + convert( + metrics: MetricRecord[] + ): collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest { + return toCollectorExportMetricServiceRequest( + metrics, + this._startTime, + this + ); } getDefaultUrl(config: CollectorExporterConfigNode): string { @@ -110,4 +54,19 @@ export class CollectorMetricExporter extends CollectorMetricExporterBase< } return config.url; } + + getDefaultServiceName(config: CollectorExporterConfigNode): string { + return config.serviceName || DEFAULT_SERVICE_NAME; + } + + getServiceClient(packageObject: any, serverAddress: string): ServiceClient { + return new packageObject.opentelemetry.proto.collector.metrics.v1.MetricsService( + serverAddress, + this.credentials + ); + } + + getServiceProtoPath(): string { + return 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; + } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts index c1b4dd861c6..a53ab948b2e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorTraceExporter.ts @@ -14,102 +14,32 @@ * limitations under the License. */ -import { ReadableSpan } from '@opentelemetry/tracing'; -import * as grpc from 'grpc'; -import { CollectorTraceExporterBase } from '../../CollectorTraceExporterBase'; +import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; +import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; import * as collectorTypes from '../../types'; import { CollectorProtocolNode } from '../../enums'; -import { parseHeaders } from '../../util'; -import { - GRPCSpanQueueItem, - ServiceClient, - CollectorExporterConfigNode, -} from './types'; +import { CollectorExporterConfigNode, ServiceClient } from './types'; -import { - DEFAULT_COLLECTOR_URL_GRPC, - traceInitWithGrpc, - sendSpansUsingGrpc, -} from './utilWithGrpc'; -import { - DEFAULT_COLLECTOR_URL_JSON, - traceInitWithJson, - sendSpansUsingJson, -} from './utilWithJson'; +import { toCollectorExportTraceServiceRequest } from '../../transform'; + +const DEFAULT_SERVICE_NAME = 'collector-exporter'; +const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; +const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/trace'; /** * Collector Trace Exporter for Node */ -export class CollectorTraceExporter extends CollectorTraceExporterBase< - CollectorExporterConfigNode -> { - DEFAULT_HEADERS: Record = { - [collectorTypes.OT_REQUEST_HEADER]: '1', - }; - isShutDown: boolean = false; - traceServiceClient?: ServiceClient = undefined; - grpcSpansQueue: GRPCSpanQueueItem[] = []; - metadata?: grpc.Metadata; - headers: Record; - private readonly _protocol: CollectorProtocolNode; - - /** - * @param config - */ - constructor(config: CollectorExporterConfigNode = {}) { - super(config); - this._protocol = - typeof config.protocolNode !== 'undefined' - ? config.protocolNode - : CollectorProtocolNode.GRPC; - if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - this.logger.debug('CollectorExporter - using json over http'); - if (config.metadata) { - this.logger.warn('Metadata cannot be set when using json'); - } - } else { - this.logger.debug('CollectorExporter - using grpc'); - if (config.headers) { - this.logger.warn('Headers cannot be set when using grpc'); - } - } - this.metadata = config.metadata; - this.headers = - parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; - } - - onShutdown(): void { - this.isShutDown = true; - if (this.traceServiceClient) { - this.traceServiceClient.close(); - } - } - - onInit(config: CollectorExporterConfigNode): void { - this.isShutDown = false; - - if (config.protocolNode === CollectorProtocolNode.HTTP_JSON) { - traceInitWithJson(this, config); - } else { - traceInitWithGrpc(this, config); - } - } - - sendSpans( - spans: ReadableSpan[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ): void { - if (this.isShutDown) { - this.logger.debug('Shutdown already started. Cannot send spans'); - return; - } - if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - sendSpansUsingJson(this, spans, onSuccess, onError); - } else { - sendSpansUsingGrpc(this, spans, onSuccess, onError); - } +export class CollectorTraceExporter + extends CollectorExporterNodeBase< + ReadableSpan, + collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest + > + implements SpanExporter { + convert( + spans: ReadableSpan[] + ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { + return toCollectorExportTraceServiceRequest(spans, this); } getDefaultUrl(config: CollectorExporterConfigNode): string { @@ -120,4 +50,19 @@ export class CollectorTraceExporter extends CollectorTraceExporterBase< } return config.url; } + + getDefaultServiceName(config: CollectorExporterConfigNode): string { + return config.serviceName || DEFAULT_SERVICE_NAME; + } + + getServiceClient(packageObject: any, serverAddress: string): ServiceClient { + return new packageObject.opentelemetry.proto.collector.trace.v1.TraceService( + serverAddress, + this.credentials + ); + } + + getServiceProtoPath(): string { + return 'opentelemetry/proto/collector/trace/v1/trace_service.proto'; + } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/types.ts b/packages/opentelemetry-exporter-collector/src/platform/node/types.ts index 6d074b8af7c..59992146daf 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/types.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/types.ts @@ -15,29 +15,18 @@ */ import * as grpc from 'grpc'; -import { ReadableSpan } from '@opentelemetry/tracing'; import { CollectorProtocolNode } from '../../enums'; -import { MetricRecord } from '@opentelemetry/metrics'; import { CollectorExporterError, CollectorExporterConfigBase, } from '../../types'; /** - * Queue item to be used to save temporary spans in case the GRPC service - * hasn't been fully initialised yet + * Queue item to be used to save temporary spans/metrics in case the GRPC service + * hasn't been fully initialized yet */ -export interface GRPCSpanQueueItem { - spans: ReadableSpan[]; - onSuccess: () => void; - onError: (error: CollectorExporterError) => void; -} - -/** - * Queue item to be used to save temporary metrics - */ -export interface GRPCMetricQueueItem { - metrics: MetricRecord[]; +export interface GRPCQueueItem { + objects: ExportedItem[]; onSuccess: () => void; onError: (error: CollectorExporterError) => void; } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts deleted file mode 100644 index 2a40170b10a..00000000000 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 protoLoader from '@grpc/proto-loader'; -import * as grpc from 'grpc'; -import * as path from 'path'; -import * as collectorTypes from '../../types'; - -import { ReadableSpan } from '@opentelemetry/tracing'; -import { toCollectorExportTraceServiceRequest } from '../../transform'; -import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; -import { CollectorTraceExporter } from './CollectorTraceExporter'; -import { - CollectorExporterConfigNode, - GRPCSpanQueueItem, - GRPCMetricQueueItem, -} from './types'; -import { removeProtocol } from './util'; -import { CollectorMetricExporter } from './CollectorMetricExporter'; -import { MetricRecord } from '@opentelemetry/metrics'; - -export const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; - -export function traceInitWithGrpc( - collector: CollectorTraceExporter, - config: CollectorExporterConfigNode -): void { - collector.grpcSpansQueue = []; - const serverAddress = removeProtocol(collector.url); - const credentials: grpc.ChannelCredentials = - config.credentials || grpc.credentials.createInsecure(); - - const traceServiceProtoPath = - 'opentelemetry/proto/collector/trace/v1/trace_service.proto'; - const includeDirs = [path.resolve(__dirname, 'protos')]; - - protoLoader - .load(traceServiceProtoPath, { - keepCase: false, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs, - }) - .then(packageDefinition => { - const packageObject: any = grpc.loadPackageDefinition(packageDefinition); - collector.traceServiceClient = new packageObject.opentelemetry.proto.collector.trace.v1.TraceService( - serverAddress, - credentials - ); - if (collector.grpcSpansQueue.length > 0) { - const queue = collector.grpcSpansQueue.splice(0); - queue.forEach((item: GRPCSpanQueueItem) => { - collector.sendSpans(item.spans, item.onSuccess, item.onError); - }); - } - }); -} - -export function metricInitWithGrpc(collector: CollectorMetricExporter): void { - collector.grpcMetricsQueue = []; - collector.isShutDown = false; - const serverAddress = removeProtocol(collector.url); - const metricServiceProtoPath = - 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; - const includeDirs = [path.resolve(__dirname, 'protos')]; - protoLoader - .load(metricServiceProtoPath, { - keepCase: false, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs, - }) - .then(packageDefinition => { - const packageObject: any = grpc.loadPackageDefinition(packageDefinition); - collector.metricServiceClient = new packageObject.opentelemetry.proto.collector.metrics.v1.MetricsService( - serverAddress, - collector.credentials - ); - if (collector.grpcMetricsQueue.length > 0) { - const queue = collector.grpcMetricsQueue.splice(0); - queue.forEach((item: GRPCMetricQueueItem) => { - collector.sendMetrics(item.metrics, item.onSuccess, item.onError); - }); - } - }); -} - -export function sendSpansUsingGrpc( - collector: CollectorTraceExporter, - spans: ReadableSpan[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void -): void { - if (collector.traceServiceClient) { - const exportTraceServiceRequest = toCollectorExportTraceServiceRequest( - spans, - collector - ); - collector.traceServiceClient.export( - exportTraceServiceRequest, - collector.metadata, - (err: collectorTypes.ExportServiceError) => { - if (err) { - collector.logger.error( - 'exportTraceServiceRequest', - exportTraceServiceRequest - ); - onError(err); - } else { - collector.logger.debug('spans sent'); - onSuccess(); - } - } - ); - } else { - collector.grpcSpansQueue.push({ - spans, - onSuccess, - onError, - }); - } -} - -export function sendMetricsUsingGrpc( - collector: CollectorMetricExporter, - metrics: MetricRecord[], - startTime: number, - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void -): void { - if (collector.metricServiceClient) { - const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( - metrics, - startTime, - collector - ); - collector.metricServiceClient.export( - exportMetricServiceRequest, - collector.metadata, - (err: collectorTypes.ExportServiceError) => { - if (err) { - collector.logger.error( - 'exportMetricServiceRequest', - exportMetricServiceRequest - ); - onError(err); - } else { - onSuccess(); - } - } - ); - } else { - collector.grpcMetricsQueue.push({ - metrics, - onSuccess, - onError, - }); - } -} diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts deleted file mode 100644 index 9d949f44839..00000000000 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 url from 'url'; -import * as http from 'http'; -import * as https from 'https'; - -import { ReadableSpan } from '@opentelemetry/tracing'; -import * as collectorTypes from '../../types'; -import { toCollectorExportTraceServiceRequest } from '../../transform'; -import { CollectorTraceExporter } from './CollectorTraceExporter'; -import { CollectorExporterConfigNode } from './types'; -import { CollectorMetricExporter } from './CollectorMetricExporter'; -import { MetricRecord } from '@opentelemetry/metrics'; -import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; -import { Logger } from '@opentelemetry/api'; - -export const DEFAULT_COLLECTOR_URL_JSON = 'http://localhost:55680/v1/trace'; - -export function traceInitWithJson( - _collector: CollectorTraceExporter, - _config: CollectorExporterConfigNode -): void { - // nothing to be done for json yet -} - -export function metricInitWithJson(_collector: CollectorMetricExporter): void { - // nothing to be done for json yet -} - -export function sendMetricsUsingJson( - collector: CollectorMetricExporter, - metrics: MetricRecord[], - startTime: number, - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void -): void { - const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( - metrics, - startTime, - collector - ); - - const body = JSON.stringify(exportMetricServiceRequest); - _sendWithJson( - body, - collector.url, - collector.headers, - collector.logger, - onSuccess, - onError - ); -} - -export function sendSpansUsingJson( - collector: CollectorTraceExporter, - spans: ReadableSpan[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void -): void { - const exportTraceServiceRequest = toCollectorExportTraceServiceRequest( - spans, - collector - ); - - const body = JSON.stringify(exportTraceServiceRequest); - _sendWithJson( - body, - collector.url, - collector.headers, - collector.logger, - onSuccess, - onError - ); -} - -function _sendWithJson( - body: string, - collectorUrl: string, - collectorHeaders: Partial>, - logger: Logger, - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void -): void { - const parsedUrl = new url.URL(collectorUrl); - - const options = { - hostname: parsedUrl.hostname, - port: parsedUrl.port, - path: parsedUrl.pathname, - method: 'POST', - headers: { - 'Content-Length': Buffer.byteLength(body), - 'Content-Type': 'application/json', - ...collectorHeaders, - }, - }; - - const request = parsedUrl.protocol === 'http:' ? http.request : https.request; - const req = request(options, (res: http.IncomingMessage) => { - if (res.statusCode && res.statusCode < 299) { - logger.debug(`statusCode: ${res.statusCode}`); - onSuccess(); - } else { - logger.error(`statusCode: ${res.statusCode}`); - onError({ - code: res.statusCode, - message: res.statusMessage, - }); - } - }); - - req.on('error', (error: Error) => { - logger.error('error', error.message); - onError({ - message: error.message, - }); - }); - req.write(body); - req.end(); -} diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index c5aca6b9dd4..c2916f0bf53 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -24,7 +24,7 @@ import { import * as core from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; import { ReadableSpan } from '@opentelemetry/tracing'; -import { CollectorTraceExporterBase } from './CollectorTraceExporterBase'; +import { CollectorExporterBase } from './CollectorExporterBase'; import { COLLECTOR_SPAN_KIND_MAPPING, opentelemetryProto, @@ -199,7 +199,11 @@ export function toCollectorExportTraceServiceRequest< T extends CollectorExporterConfigBase >( spans: ReadableSpan[], - collectorTraceExporterBase: CollectorTraceExporterBase + collectorTraceExporterBase: CollectorExporterBase< + T, + ReadableSpan, + opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest + > ): opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { const groupedSpans: Map< Resource, diff --git a/packages/opentelemetry-exporter-collector/src/transformMetrics.ts b/packages/opentelemetry-exporter-collector/src/transformMetrics.ts index be14cea32aa..96263c2d8a6 100644 --- a/packages/opentelemetry-exporter-collector/src/transformMetrics.ts +++ b/packages/opentelemetry-exporter-collector/src/transformMetrics.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { CollectorMetricExporterBase } from './CollectorMetricExporterBase'; import { MetricRecord, MetricKind, @@ -28,6 +27,7 @@ import * as api from '@opentelemetry/api'; import * as core from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; import { toCollectorResource } from './transform'; +import { CollectorExporterBase } from './CollectorExporterBase'; /** * Converts labels @@ -220,7 +220,11 @@ export function toCollectorExportMetricServiceRequest< >( metrics: MetricRecord[], startTime: number, - collectorMetricExporterBase: CollectorMetricExporterBase + collectorExporterBase: CollectorExporterBase< + T, + MetricRecord, + opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest + > ): opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest { const groupedMetrics: Map< Resource, @@ -228,9 +232,9 @@ export function toCollectorExportMetricServiceRequest< > = groupMetricsByResourceAndLibrary(metrics); const additionalAttributes = Object.assign( {}, - collectorMetricExporterBase.attributes, + collectorExporterBase.attributes, { - 'service.name': collectorMetricExporterBase.serviceName, + 'service.name': collectorExporterBase.serviceName, } ); return { diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts index 3ef67aad71f..086a8ea82f9 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts @@ -17,21 +17,32 @@ import { ExportResult, NoopLogger } from '@opentelemetry/core'; import * as assert from 'assert'; import * as sinon from 'sinon'; -import { CollectorMetricExporterBase } from '../../src/CollectorMetricExporterBase'; +import { CollectorExporterBase } from '../../src/CollectorExporterBase'; import { CollectorExporterConfigBase } from '../../src/types'; import { MetricRecord } from '@opentelemetry/metrics'; import { mockCounter, mockObserver } from '../helper'; +import * as collectorTypes from '../../src/types'; type CollectorExporterConfig = CollectorExporterConfigBase; -class CollectorMetricExporter extends CollectorMetricExporterBase< - CollectorExporterConfig +class CollectorMetricExporter extends CollectorExporterBase< + CollectorExporterConfig, + MetricRecord, + collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest > { onInit() {} onShutdown() {} - sendMetrics() {} + send() {} getDefaultUrl(config: CollectorExporterConfig) { return config.url || ''; } + getDefaultServiceName(config: CollectorExporterConfig): string { + return config.serviceName || 'collector-metric-exporter'; + } + convert( + metrics: MetricRecord[] + ): collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest { + return { resourceMetrics: [] }; + } } describe('CollectorMetricExporter - common', () => { @@ -107,7 +118,7 @@ describe('CollectorMetricExporter - common', () => { describe('export', () => { let spySend: any; beforeEach(() => { - spySend = sinon.stub(CollectorMetricExporter.prototype, 'sendMetrics'); + spySend = sinon.stub(CollectorMetricExporter.prototype, 'send'); collectorExporter = new CollectorMetricExporter(collectorExporterConfig); }); afterEach(() => { diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts index 69c4a15de31..5ec87838762 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorTraceExporter.test.ts @@ -18,20 +18,32 @@ import { ExportResult, NoopLogger } from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; import * as assert from 'assert'; import * as sinon from 'sinon'; -import { CollectorTraceExporterBase } from '../../src/CollectorTraceExporterBase'; +import { CollectorExporterBase } from '../../src/CollectorExporterBase'; import { CollectorExporterConfigBase } from '../../src/types'; import { mockedReadableSpan } from '../helper'; +import * as collectorTypes from '../../src/types'; type CollectorExporterConfig = CollectorExporterConfigBase; -class CollectorTraceExporter extends CollectorTraceExporterBase< - CollectorExporterConfig +class CollectorTraceExporter extends CollectorExporterBase< + CollectorExporterConfig, + ReadableSpan, + collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest > { onInit() {} onShutdown() {} - sendSpans() {} - getDefaultUrl(config: CollectorExporterConfig) { + send() {} + getDefaultUrl(config: CollectorExporterConfig): string { return config.url || ''; } + getDefaultServiceName(config: CollectorExporterConfig): string { + return config.serviceName || 'collector-exporter'; + } + + convert( + spans: ReadableSpan[] + ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { + return { resourceSpans: [] }; + } } describe('CollectorTraceExporter - common', () => { @@ -101,7 +113,7 @@ describe('CollectorTraceExporter - common', () => { describe('export', () => { let spySend: any; beforeEach(() => { - spySend = sinon.stub(CollectorTraceExporter.prototype, 'sendSpans'); + spySend = sinon.stub(CollectorTraceExporter.prototype, 'send'); collectorExporter = new CollectorTraceExporter(collectorExporterConfig); }); afterEach(() => { From dffc3dd354a1f3a518807021e552319ed1f64d80 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Tue, 14 Jul 2020 20:55:09 +0000 Subject: [PATCH 11/34] Restored those files --- .../node/CollectorExporterNodeBase.ts | 131 +----------------- .../src/platform/node/utilWithGrpc.ts | 87 ++++++++++++ .../src/platform/node/utilWithJson.ts | 76 ++++++++++ 3 files changed, 170 insertions(+), 124 deletions(-) create mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts index daa76e8d4a8..5945f2c08a3 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts @@ -21,12 +21,8 @@ import * as grpc from 'grpc'; import { CollectorProtocolNode } from '../../enums'; import * as collectorTypes from '../../types'; import { parseHeaders } from '../../util'; -import * as url from 'url'; -import * as http from 'http'; -import * as https from 'https'; -import * as path from 'path'; -import { removeProtocol } from './util'; -import * as protoLoader from '@grpc/proto-loader'; +import { sendWithJson, initWithJson } from './utilWithJson'; +import { sendUsingGrpc, initWithGrpc } from './utilWithGrpc'; const DEFAULT_SERVICE_NAME = 'collector-metric-exporter'; @@ -78,14 +74,14 @@ export abstract class CollectorExporterNodeBase< onInit(config: CollectorExporterConfigNode): void { this._isShutdown = false; if (config.protocolNode === CollectorProtocolNode.HTTP_JSON) { - this.initWithJson(); + initWithJson(this, config); } else { - this.initWithGrpc(); + initWithGrpc(this); } } send( - spans: ExportItem[], + objects: ExportItem[], onSuccess: () => void, onError: (error: collectorTypes.CollectorExporterError) => void ): void { @@ -94,9 +90,9 @@ export abstract class CollectorExporterNodeBase< return; } if (this._protocol === CollectorProtocolNode.HTTP_JSON) { - this.sendWithJson(spans, onSuccess, onError); + sendWithJson(this, objects, onSuccess, onError); } else { - this.sendWithGrpc(spans, onSuccess, onError); + sendUsingGrpc(this, objects, onSuccess, onError); } } @@ -111,119 +107,6 @@ export abstract class CollectorExporterNodeBase< return config.serviceName || DEFAULT_SERVICE_NAME; } - protected initWithJson(): void { - // Nothing to be done for JSON yet - } - - /** - * Initialize - */ - protected initWithGrpc(): void { - const serverAddress = removeProtocol(this.url); - const includeDirs = [path.resolve(__dirname, 'protos')]; - - protoLoader - .load(this.getServiceProtoPath(), { - keepCase: false, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs, - }) - .then(packageDefinition => { - const packageObject: any = grpc.loadPackageDefinition( - packageDefinition - ); - this.serviceClient = this.getServiceClient( - packageObject, - serverAddress - ); - if (this.grpcQueue.length > 0) { - const queue = this.grpcQueue.splice(0); - queue.forEach((item: GRPCQueueItem) => { - this.send(item.objects, item.onSuccess, item.onError); - }); - } - }); - } - - protected sendWithGrpc( - objects: ExportItem[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ): void { - if (this.serviceClient) { - const serviceRequest = this.convert(objects); - - this.serviceClient.export( - serviceRequest, - this.metadata, - (err: collectorTypes.ExportServiceError) => { - if (err) { - this.logger.error('Service request', serviceRequest); - onError(err); - } else { - this.logger.debug('spans sent'); - onSuccess(); - } - } - ); - } else { - this.grpcQueue.push({ - objects, - onSuccess, - onError, - }); - } - } - - protected sendWithJson( - objects: ExportItem[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ): void { - const serviceRequest = this.convert(objects); - const body = JSON.stringify(serviceRequest); - const parsedUrl = new url.URL(this.url); - - const options = { - hostname: parsedUrl.hostname, - port: parsedUrl.port, - path: parsedUrl.pathname, - method: 'POST', - headers: { - 'Content-Length': Buffer.byteLength(body), - 'Content-Type': 'application/json', - ...this.headers, - }, - }; - - const request = - parsedUrl.protocol === 'http:' ? http.request : https.request; - const req = request(options, (res: http.IncomingMessage) => { - if (res.statusCode && res.statusCode < 299) { - this.logger.debug(`statusCode: ${res.statusCode}`); - onSuccess(); - } else { - this.logger.error(`statusCode: ${res.statusCode}`); - onError({ - code: res.statusCode, - message: res.statusMessage, - }); - } - }); - - req.on('error', (error: Error) => { - this.logger.error('error', error.message); - onError({ - message: error.message, - }); - }); - req.write(body); - req.end(); - } - abstract getServiceProtoPath(): string; abstract getServiceClient( packageObject: any, diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts new file mode 100644 index 00000000000..04704aa97f6 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts @@ -0,0 +1,87 @@ +/* + * 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 protoLoader from '@grpc/proto-loader'; +import * as grpc from 'grpc'; +import * as path from 'path'; +import * as collectorTypes from '../../types'; + +import { GRPCQueueItem } from './types'; +import { removeProtocol } from './util'; +import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; + +export const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; + +export function initWithGrpc( + exporter: CollectorExporterNodeBase +): void { + const serverAddress = removeProtocol(exporter.url); + const includeDirs = [path.resolve(__dirname, 'protos')]; + + protoLoader + .load(exporter.getServiceProtoPath(), { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs, + }) + .then(packageDefinition => { + const packageObject: any = grpc.loadPackageDefinition(packageDefinition); + exporter.serviceClient = exporter.getServiceClient( + packageObject, + serverAddress + ); + if (exporter.grpcQueue.length > 0) { + const queue = exporter.grpcQueue.splice(0); + queue.forEach((item: GRPCQueueItem) => { + exporter.send(item.objects, item.onSuccess, item.onError); + }); + } + }); +} + +export function sendUsingGrpc( + collector: CollectorExporterNodeBase, + objects: ExportItem[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +): void { + if (collector.serviceClient) { + const serviceRequest = collector.convert(objects); + + collector.serviceClient.export( + serviceRequest, + collector.metadata, + (err: collectorTypes.ExportServiceError) => { + if (err) { + collector.logger.error('Service request', serviceRequest); + onError(err); + } else { + collector.logger.debug('spans sent'); + onSuccess(); + } + } + ); + } else { + collector.grpcQueue.push({ + objects, + onSuccess, + onError, + }); + } +} diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts new file mode 100644 index 00000000000..fde05ffa4ec --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts @@ -0,0 +1,76 @@ +/* + * 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 url from 'url'; +import * as http from 'http'; +import * as https from 'https'; + +import * as collectorTypes from '../../types'; +import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; +import { CollectorExporterConfigNode } from './types'; + +export function initWithJson( + _collector: CollectorExporterNodeBase, + _config: CollectorExporterConfigNode +): void { + // nothing to be done for json yet +} + +export function sendWithJson( + collector: CollectorExporterNodeBase, + objects: ExportItem[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void +): void { + const serviceRequest = collector.convert(objects); + const body = JSON.stringify(serviceRequest); + const parsedUrl = new url.URL(collector.url); + + const options = { + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.pathname, + method: 'POST', + headers: { + 'Content-Length': Buffer.byteLength(body), + 'Content-Type': 'application/json', + ...collector.headers, + }, + }; + + const request = parsedUrl.protocol === 'http:' ? http.request : https.request; + const req = request(options, (res: http.IncomingMessage) => { + if (res.statusCode && res.statusCode < 299) { + collector.logger.debug(`statusCode: ${res.statusCode}`); + onSuccess(); + } else { + collector.logger.error(`statusCode: ${res.statusCode}`); + onError({ + code: res.statusCode, + message: res.statusMessage, + }); + } + }); + + req.on('error', (error: Error) => { + collector.logger.error('error', error.message); + onError({ + message: error.message, + }); + }); + req.write(body); + req.end(); +} From 9c9abaefd73966c69c62be23c21d7809c058def6 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Tue, 14 Jul 2020 20:59:47 +0000 Subject: [PATCH 12/34] Variable anme --- .../src/platform/node/utilWithGrpc.ts | 20 +++++++++---------- .../src/platform/node/utilWithJson.ts | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts index 04704aa97f6..15f6a8f9a1e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts @@ -25,14 +25,14 @@ import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; export const DEFAULT_COLLECTOR_URL_GRPC = 'localhost:55680'; -export function initWithGrpc( - exporter: CollectorExporterNodeBase +export function initWithGrpc( + collector: CollectorExporterNodeBase ): void { - const serverAddress = removeProtocol(exporter.url); + const serverAddress = removeProtocol(collector.url); const includeDirs = [path.resolve(__dirname, 'protos')]; protoLoader - .load(exporter.getServiceProtoPath(), { + .load(collector.getServiceProtoPath(), { keepCase: false, longs: String, enums: String, @@ -42,21 +42,21 @@ export function initWithGrpc( }) .then(packageDefinition => { const packageObject: any = grpc.loadPackageDefinition(packageDefinition); - exporter.serviceClient = exporter.getServiceClient( + collector.serviceClient = collector.getServiceClient( packageObject, serverAddress ); - if (exporter.grpcQueue.length > 0) { - const queue = exporter.grpcQueue.splice(0); + if (collector.grpcQueue.length > 0) { + const queue = collector.grpcQueue.splice(0); queue.forEach((item: GRPCQueueItem) => { - exporter.send(item.objects, item.onSuccess, item.onError); + collector.send(item.objects, item.onSuccess, item.onError); }); } }); } -export function sendUsingGrpc( - collector: CollectorExporterNodeBase, +export function sendUsingGrpc( + collector: CollectorExporterNodeBase, objects: ExportItem[], onSuccess: () => void, onError: (error: collectorTypes.CollectorExporterError) => void diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts index fde05ffa4ec..028f245d53c 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithJson.ts @@ -29,8 +29,8 @@ export function initWithJson( // nothing to be done for json yet } -export function sendWithJson( - collector: CollectorExporterNodeBase, +export function sendWithJson( + collector: CollectorExporterNodeBase, objects: ExportItem[], onSuccess: () => void, onError: (error: collectorTypes.CollectorExporterError) => void From 6c980955c8ac1a8450d5c65e5829a0623ff69f02 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 15 Jul 2020 15:21:13 +0000 Subject: [PATCH 13/34] Duplicate test --- .../test/browser/CollectorMetricExporter.test.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts index e8b372462f4..1b3936aa72a 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts @@ -252,17 +252,6 @@ describe('CollectorMetricExporter - web', () => { ); } - assert.ok( - typeof metric2 !== 'undefined', - "second metric doesn't exist" - ); - if (metric2) { - ensureObserverIsCorrect( - metric2, - hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp) - ); - } - assert.ok( typeof metric3 !== 'undefined', "third metric doesn't exist" From 0ecb0313ff512aa191e121b572944ca55de120ff Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 15 Jul 2020 18:30:28 +0000 Subject: [PATCH 14/34] Fixed strings --- .../src/platform/node/CollectorExporterNodeBase.ts | 2 +- .../src/platform/node/utilWithGrpc.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts index 5945f2c08a3..2e64bc39a4e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorExporterNodeBase.ts @@ -86,7 +86,7 @@ export abstract class CollectorExporterNodeBase< onError: (error: collectorTypes.CollectorExporterError) => void ): void { if (this._isShutdown) { - this.logger.debug('Shutdown already started. Cannot send spans'); + this.logger.debug('Shutdown already started. Cannot send objects'); return; } if (this._protocol === CollectorProtocolNode.HTTP_JSON) { diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts index 15f6a8f9a1e..6d56ab8757b 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/utilWithGrpc.ts @@ -72,7 +72,7 @@ export function sendUsingGrpc( collector.logger.error('Service request', serviceRequest); onError(err); } else { - collector.logger.debug('spans sent'); + collector.logger.debug('Objects sent'); onSuccess(); } } From 629fa5034a4e5791fbeeff6aa695affad727372b Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 15 Jul 2020 23:34:57 +0000 Subject: [PATCH 15/34] Changed metrics to functions and added summary --- .../src/transformMetrics.ts | 44 +++- .../test/common/transformMetrics.test.ts | 79 ++++-- .../test/helper.ts | 226 +++++++++++------- 3 files changed, 248 insertions(+), 101 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/transformMetrics.ts b/packages/opentelemetry-exporter-collector/src/transformMetrics.ts index be14cea32aa..c5b829abcf2 100644 --- a/packages/opentelemetry-exporter-collector/src/transformMetrics.ts +++ b/packages/opentelemetry-exporter-collector/src/transformMetrics.ts @@ -60,6 +60,9 @@ export function toCollectorType( if (metric.aggregator instanceof HistogramAggregator) { return opentelemetryProto.metrics.v1.MetricDescriptorType.HISTOGRAM; } + if (metric.aggregator instanceof MinMaxLastSumCountAggregator) { + return opentelemetryProto.metrics.v1.MetricDescriptorType.SUMMARY; + } if (metric.descriptor.valueType == api.ValueType.INT) { return opentelemetryProto.metrics.v1.MetricDescriptorType.INT64; } @@ -132,10 +135,7 @@ export function toSingularPoint( timeUnixNano: number; value: number; } { - const pointValue = - metric.aggregator instanceof MinMaxLastSumCountAggregator - ? (metric.aggregator.toPoint().value as Distribution).last - : (metric.aggregator.toPoint().value as number); + const pointValue = metric.aggregator.toPoint().value as number; return { labels: toCollectorLabels(metric.labels), @@ -172,6 +172,31 @@ export function toHistogramPoint( }; } +/** + * Returns a SummaryPoint to the collector + * @param metric + * @param startTime + */ +export function toSummaryPoint( + metric: MetricRecord, + startTime: number +): opentelemetryProto.metrics.v1.SummaryDataPoint { + const distValue = metric.aggregator.toPoint().value as Distribution; + return { + labels: toCollectorLabels(metric.labels), + sum: distValue.sum, + count: distValue.count, + startTimeUnixNano: startTime, + timeUnixNano: core.hrTimeToNanoseconds( + metric.aggregator.toPoint().timestamp + ), + percentileValues: [ + { percentile: 0, value: distValue.min }, + { percentile: 100, value: distValue.max }, + ], + }; +} + /** * Converts a metric to be compatible with the collector * @param metric @@ -190,6 +215,15 @@ export function toCollectorMetric( histogramDataPoints: [toHistogramPoint(metric, startTime)], }; } + if ( + toCollectorType(metric) === + opentelemetryProto.metrics.v1.MetricDescriptorType.SUMMARY + ) { + return { + metricDescriptor: toCollectorMetricDescriptor(metric), + summaryDataPoints: [toSummaryPoint(metric, startTime)], + }; + } if (metric.descriptor.valueType == api.ValueType.INT) { return { metricDescriptor: toCollectorMetricDescriptor(metric), @@ -201,7 +235,7 @@ export function toCollectorMetric( metricDescriptor: toCollectorMetricDescriptor(metric), doubleDataPoints: [toSingularPoint(metric, startTime)], }; - } // TODO: Add support for summary points once implemented + } return { metricDescriptor: toCollectorMetricDescriptor(metric), diff --git a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts index 6e4563e6b6b..8b123a0b51f 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts @@ -17,46 +17,95 @@ import * as assert from 'assert'; import * as transform from '../../src/transformMetrics'; import { mockCounter, + mockDoubleCounter, mockObserver, mockedResources, mockedInstrumentationLibraries, multiResourceMetrics, multiInstrumentationLibraryMetrics, ensureCounterIsCorrect, + ensureDoubleCounterIsCorrect, ensureObserverIsCorrect, mockHistogram, ensureHistogramIsCorrect, ensureValueRecorderIsCorrect, mockValueRecorder, } from '../helper'; -import { HistogramAggregator } from '@opentelemetry/metrics'; +import { + HistogramAggregator, + MetricRecord, + SumAggregator, +} from '@opentelemetry/metrics'; import { hrTimeToNanoseconds } from '@opentelemetry/core'; +import { Resource } from '@opentelemetry/resources'; describe('transformMetrics', () => { describe('toCollectorMetric', () => { + const counter: MetricRecord = mockCounter(); + const doubleCounter: MetricRecord = mockDoubleCounter(); + const observer: MetricRecord = mockObserver(); + const histogram: MetricRecord = mockHistogram(); + const recorder: MetricRecord = mockValueRecorder(); + const invalidMetric: MetricRecord = { + descriptor: { + name: 'name', + description: 'description', + unit: 'unit', + metricKind: 8, // Not a valid metricKind + valueType: 2, // Not double or int + }, + labels: {}, + aggregator: new SumAggregator(), + resource: new Resource({}), + instrumentationLibrary: { name: 'x', version: 'y' }, + }; + beforeEach(() => { + // Counter + counter.aggregator.update(1); + + // Double Counter + doubleCounter.aggregator.update(8); + + // Observer + observer.aggregator.update(3); + observer.aggregator.update(6); + + // Histogram + histogram.aggregator.update(7); + histogram.aggregator.update(14); + (histogram.aggregator as HistogramAggregator).reset(); + + // ValueRecorder + recorder.aggregator.update(5); + }); it('should convert metric', () => { - mockCounter.aggregator.update(1); ensureCounterIsCorrect( - transform.toCollectorMetric(mockCounter, 1592602232694000000), - hrTimeToNanoseconds(mockCounter.aggregator.toPoint().timestamp) + transform.toCollectorMetric(counter, 1592602232694000000), + hrTimeToNanoseconds(counter.aggregator.toPoint().timestamp) ); - mockObserver.aggregator.update(10); ensureObserverIsCorrect( - transform.toCollectorMetric(mockObserver, 1592602232694000000), - hrTimeToNanoseconds(mockObserver.aggregator.toPoint().timestamp) + transform.toCollectorMetric(observer, 1592602232694000000), + hrTimeToNanoseconds(observer.aggregator.toPoint().timestamp) ); - mockHistogram.aggregator.update(7); - mockHistogram.aggregator.update(14); - (mockHistogram.aggregator as HistogramAggregator).reset(); ensureHistogramIsCorrect( - transform.toCollectorMetric(mockHistogram, 1592602232694000000), - hrTimeToNanoseconds(mockHistogram.aggregator.toPoint().timestamp) + transform.toCollectorMetric(histogram, 1592602232694000000), + hrTimeToNanoseconds(histogram.aggregator.toPoint().timestamp) ); - mockValueRecorder.aggregator.update(5); ensureValueRecorderIsCorrect( - transform.toCollectorMetric(mockValueRecorder, 1592602232694000000), - hrTimeToNanoseconds(mockValueRecorder.aggregator.toPoint().timestamp) + transform.toCollectorMetric(recorder, 1592602232694000000), + hrTimeToNanoseconds(recorder.aggregator.toPoint().timestamp) + ); + + ensureDoubleCounterIsCorrect( + transform.toCollectorMetric(doubleCounter, 1592602232694000000), + hrTimeToNanoseconds(doubleCounter.aggregator.toPoint().timestamp) + ); + + const emptyMetric = transform.toCollectorMetric( + invalidMetric, + 1592602232694000000 ); + assert.deepStrictEqual(emptyMetric.int64DataPoints, []); }); }); describe('toCollectorMetricDescriptor', () => { diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index f2ed9cf0487..edf4e766c1b 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -59,77 +59,105 @@ const traceIdArr = [ const spanIdArr = [94, 16, 114, 97, 246, 79, 165, 62]; const parentIdArr = [120, 168, 145, 80, 152, 134, 67, 136]; -export const mockCounter: MetricRecord = { - descriptor: { - name: 'test-counter', - description: 'sample counter description', - unit: '1', - metricKind: MetricKind.COUNTER, - valueType: ValueType.INT, - }, - labels: {}, - aggregator: new SumAggregator(), - resource: new Resource({ - service: 'ui', - version: 1, - cost: 112.12, - }), - instrumentationLibrary: { name: 'default', version: '0.0.1' }, -}; +export function mockCounter(): MetricRecord { + return { + descriptor: { + name: 'test-counter', + description: 'sample counter description', + unit: '1', + metricKind: MetricKind.COUNTER, + valueType: ValueType.INT, + }, + labels: {}, + aggregator: new SumAggregator(), + resource: new Resource({ + service: 'ui', + version: 1, + cost: 112.12, + }), + instrumentationLibrary: { name: 'default', version: '0.0.1' }, + }; +} -export const mockObserver: MetricRecord = { - descriptor: { - name: 'test-observer', - description: 'sample observer description', - unit: '2', - metricKind: MetricKind.VALUE_OBSERVER, - valueType: ValueType.DOUBLE, - }, - labels: {}, - aggregator: new MinMaxLastSumCountAggregator(), - resource: new Resource({ - service: 'ui', - version: 1, - cost: 112.12, - }), - instrumentationLibrary: { name: 'default', version: '0.0.1' }, -}; +export function mockDoubleCounter(): MetricRecord { + return { + descriptor: { + name: 'test-counter', + description: 'sample counter description', + unit: '1', + metricKind: MetricKind.COUNTER, + valueType: ValueType.DOUBLE, + }, + labels: {}, + aggregator: new SumAggregator(), + resource: new Resource({ + service: 'ui', + version: 1, + cost: 112.12, + }), + instrumentationLibrary: { name: 'default', version: '0.0.1' }, + }; +} -export const mockValueRecorder: MetricRecord = { - descriptor: { - name: 'test-recorder', - description: 'sample recorder description', - unit: '3', - metricKind: MetricKind.VALUE_RECORDER, - valueType: ValueType.INT, - }, - labels: {}, - aggregator: new MinMaxLastSumCountAggregator(), - resource: new Resource({ - service: 'ui', - version: 1, - cost: 112.12, - }), - instrumentationLibrary: { name: 'default', version: '0.0.1' }, -}; +export function mockObserver(): MetricRecord { + return { + descriptor: { + name: 'test-observer', + description: 'sample observer description', + unit: '2', + metricKind: MetricKind.VALUE_OBSERVER, + valueType: ValueType.DOUBLE, + }, + labels: {}, + aggregator: new MinMaxLastSumCountAggregator(), + resource: new Resource({ + service: 'ui', + version: 1, + cost: 112.12, + }), + instrumentationLibrary: { name: 'default', version: '0.0.1' }, + }; +} -export const mockHistogram: MetricRecord = { - descriptor: { - name: 'test-hist', - description: 'sample observer description', - unit: '2', - metricKind: MetricKind.VALUE_OBSERVER, - valueType: ValueType.DOUBLE, - }, - labels: {}, - aggregator: new HistogramAggregator([10, 20]), - resource: new Resource({ - service: 'ui', - version: 1, - cost: 112.12, - }), - instrumentationLibrary: { name: 'default', version: '0.0.1' }, -}; +export function mockValueRecorder(): MetricRecord { + return { + descriptor: { + name: 'test-recorder', + description: 'sample recorder description', + unit: '3', + metricKind: MetricKind.VALUE_RECORDER, + valueType: ValueType.INT, + }, + labels: {}, + aggregator: new MinMaxLastSumCountAggregator(), + resource: new Resource({ + service: 'ui', + version: 1, + cost: 112.12, + }), + instrumentationLibrary: { name: 'default', version: '0.0.1' }, + }; +} + +export function mockHistogram(): MetricRecord { + return { + descriptor: { + name: 'test-hist', + description: 'sample observer description', + unit: '2', + metricKind: MetricKind.VALUE_OBSERVER, + valueType: ValueType.DOUBLE, + }, + labels: {}, + aggregator: new HistogramAggregator([10, 20]), + resource: new Resource({ + service: 'ui', + version: 1, + cost: 112.12, + }), + instrumentationLibrary: { name: 'default', version: '0.0.1' }, + }; +} const traceIdBase64 = 'HxAI3I4nDoXECg18OTmyeA=='; const spanIdBase64 = 'XhByYfZPpT4='; @@ -285,17 +313,17 @@ export const multiResourceTrace: ReadableSpan[] = [ export const multiResourceMetrics: MetricRecord[] = [ { - ...mockCounter, + ...mockCounter(), resource: mockedResources[0], instrumentationLibrary: mockedInstrumentationLibraries[0], }, { - ...mockObserver, + ...mockObserver(), resource: mockedResources[1], instrumentationLibrary: mockedInstrumentationLibraries[0], }, { - ...mockCounter, + ...mockCounter(), resource: mockedResources[0], instrumentationLibrary: mockedInstrumentationLibraries[0], }, @@ -303,17 +331,17 @@ export const multiResourceMetrics: MetricRecord[] = [ export const multiInstrumentationLibraryMetrics: MetricRecord[] = [ { - ...mockCounter, + ...mockCounter(), resource: mockedResources[0], instrumentationLibrary: mockedInstrumentationLibraries[0], }, { - ...mockObserver, + ...mockObserver(), resource: mockedResources[0], instrumentationLibrary: mockedInstrumentationLibraries[1], }, { - ...mockCounter, + ...mockCounter(), resource: mockedResources[0], instrumentationLibrary: mockedInstrumentationLibraries[0], }, @@ -685,6 +713,29 @@ export function ensureCounterIsCorrect( }); } +export function ensureDoubleCounterIsCorrect( + metric: collectorTypes.opentelemetryProto.metrics.v1.Metric, + time: number +) { + assert.deepStrictEqual(metric, { + metricDescriptor: { + name: 'test-counter', + description: 'sample counter description', + unit: '1', + type: 4, + temporality: 3, + }, + doubleDataPoints: [ + { + labels: [], + value: 8, + startTimeUnixNano: 1592602232694000000, + timeUnixNano: time, + }, + ], + }); +} + export function ensureObserverIsCorrect( metric: collectorTypes.opentelemetryProto.metrics.v1.Metric, time: number @@ -694,15 +745,23 @@ export function ensureObserverIsCorrect( name: 'test-observer', description: 'sample observer description', unit: '2', - type: 3, + type: 6, temporality: 2, }, - doubleDataPoints: [ + summaryDataPoints: [ { - labels: [], - value: 10, startTimeUnixNano: 1592602232694000000, timeUnixNano: time, + count: 2, + sum: 9, + labels: [], + percentileValues: [ + { + percentile: 0, + value: 3, + }, + { percentile: 100, value: 6 }, + ], }, ], }); @@ -717,13 +776,18 @@ export function ensureValueRecorderIsCorrect( name: 'test-recorder', description: 'sample recorder description', unit: '3', - type: 1, + type: 6, temporality: 2, }, - int64DataPoints: [ + summaryDataPoints: [ { + count: 1, + sum: 5, labels: [], - value: 5, + percentileValues: [ + { percentile: 0, value: 5 }, + { percentile: 100, value: 5 }, + ], startTimeUnixNano: 1592602232694000000, timeUnixNano: time, }, From dc1a4928778758af8b2c43e84ee9e5a938ae4b15 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Thu, 16 Jul 2020 15:55:05 +0000 Subject: [PATCH 16/34] Fixed tests --- .../test/common/CollectorMetricExporter.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts index 73e9ddc40eb..1f9c56e5d6b 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorMetricExporter.test.ts @@ -52,8 +52,8 @@ describe('CollectorMetricExporter - common', () => { }; collectorExporter = new CollectorMetricExporter(collectorExporterConfig); metrics = []; - metrics.push(Object.assign({}, mockCounter)); - metrics.push(Object.assign({}, mockObserver)); + metrics.push(mockCounter()); + metrics.push(mockObserver()); }); afterEach(() => { From 37f154b269ab2ab1a1b28f441f297621b9d879e1 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Fri, 17 Jul 2020 15:48:00 +0000 Subject: [PATCH 17/34] Added browser --- .../browser/CollectorExporterBrowserBase.ts | 83 +++++++++++++++++++ .../browser/CollectorMetricExporter.ts | 75 +++++------------ .../browser/CollectorTraceExporter.ts | 65 +-------------- .../browser/CollectorMetricExporter.test.ts | 13 ++- .../test/helper.ts | 33 +++++--- .../test/node/CollectorMetricExporter.test.ts | 11 +-- .../CollectorMetricExporterWithJson.test.ts | 13 ++- 7 files changed, 146 insertions(+), 147 deletions(-) create mode 100644 packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts new file mode 100644 index 00000000000..20d0cd4bc3e --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts @@ -0,0 +1,83 @@ +/* + * 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 { CollectorExporterBase } from '../../CollectorExporterBase'; +import { CollectorExporterConfigBrowser } from './types'; +import * as collectorTypes from '../../types'; +import { parseHeaders } from '../../util'; +import { sendWithBeacon, sendWithXhr } from './util'; + +const DEFAULT_SERVICE_NAME = 'collector-metric-exporter'; + +/** + * Collector Metric Exporter abstract base class + */ +export abstract class CollectorExporterBrowserBase< + ExportItem, + ServiceRequest +> extends CollectorExporterBase< + CollectorExporterConfigBrowser, + ExportItem, + ServiceRequest +> { + DEFAULT_HEADERS: Record = { + [collectorTypes.OT_REQUEST_HEADER]: '1', + }; + protected _headers: Record; + protected _useXHR: boolean = false; + + constructor(config: CollectorExporterConfigBrowser = {}) { + super(config); + this._headers = + parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; + this._useXHR = + !!config.headers || typeof navigator.sendBeacon !== 'function'; + } + + onInit(): void { + window.addEventListener('unload', this.shutdown); + } + + onShutdown(): void { + window.removeEventListener('unload', this.shutdown); + } + + send( + items: ExportItem[], + onSuccess: () => void, + onError: (error: collectorTypes.CollectorExporterError) => void + ) { + const exportTraceServiceRequest = this.convert(items); + const body = JSON.stringify(exportTraceServiceRequest); + + if (this._useXHR) { + sendWithXhr( + body, + this.url, + this._headers, + this.logger, + onSuccess, + onError + ); + } else { + sendWithBeacon(body, this.url, this.logger, onSuccess, onError); + } + } + + getDefaultServiceName(config: CollectorExporterConfigBrowser): string { + return config.serviceName || DEFAULT_SERVICE_NAME; + } +} diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts index 3f907b20c6d..8a1de1cdf9a 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts @@ -14,73 +14,36 @@ * limitations under the License. */ -import { MetricRecord } from '@opentelemetry/metrics'; -import { CollectorMetricExporterBase } from '../../CollectorMetricExporterBase'; +import { MetricRecord, MetricExporter } from '@opentelemetry/metrics'; +import * as collectorTypes from '../../types'; +import { CollectorExporterBrowserBase } from './CollectorExporterBrowserBase'; import { toCollectorExportMetricServiceRequest } from '../../transformMetrics'; -import { CollectorExporterError, OT_REQUEST_HEADER } from '../../types'; import { CollectorExporterConfigBrowser } from './types'; -import { sendWithBeacon, sendWithXhr } from './util'; -import { parseHeaders } from '../../util'; const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/metrics'; /** * Collector Metric Exporter for Web */ -export class CollectorMetricExporter extends CollectorMetricExporterBase< - CollectorExporterConfigBrowser -> { - DEFAULT_HEADERS: { [key: string]: string } = { - [OT_REQUEST_HEADER]: '1', - }; - private _headers: { [key: string]: string }; - private _useXHR: boolean = false; - - /** - * @param config - */ - constructor(config: CollectorExporterConfigBrowser = {}) { - super(config); - this._headers = - parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; - this._useXHR = - !!config.headers || typeof navigator.sendBeacon !== 'function'; - } - - onInit(): void { - window.addEventListener('unload', this.shutdown); - } - - onShutdown(): void { - window.removeEventListener('unload', this.shutdown); - } - - getDefaultUrl(url: string | undefined): string { - return url || DEFAULT_COLLECTOR_URL; - } - - sendMetrics( - metrics: MetricRecord[], - onSuccess: () => void, - onError: (error: CollectorExporterError) => void - ): void { - const exportMetricServiceRequest = toCollectorExportMetricServiceRequest( +export class CollectorMetricExporter + extends CollectorExporterBrowserBase< + MetricRecord, + collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest + > + implements MetricExporter { + private readonly _startTime = new Date().getTime() * 1000000; + + convert( + metrics: MetricRecord[] + ): collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest { + return toCollectorExportMetricServiceRequest( metrics, this._startTime, this ); - const body = JSON.stringify(exportMetricServiceRequest); - if (this._useXHR) { - sendWithXhr( - body, - this.url, - this._headers, - this.logger, - onSuccess, - onError - ); - } else { - sendWithBeacon(body, this.url, this.logger, onSuccess, onError); - } + } + + getDefaultUrl(config: CollectorExporterConfigBrowser): string { + return config.url || DEFAULT_COLLECTOR_URL; } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts index 23126fc563a..cfeb18563b7 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts @@ -14,85 +14,28 @@ * limitations under the License. */ -import { CollectorExporterBase } from '../../CollectorExporterBase'; import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; +import { CollectorExporterBrowserBase } from './CollectorExporterBrowserBase'; import { toCollectorExportTraceServiceRequest } from '../../transform'; import { CollectorExporterConfigBrowser } from './types'; import * as collectorTypes from '../../types'; -import { sendWithBeacon, sendWithXhr } from './util'; -import { parseHeaders } from '../../util'; const DEFAULT_COLLECTOR_URL = 'http://localhost:55680/v1/trace'; -const DEFAULT_SERVICE_NAME = 'collector-exporter'; - /** * Collector Trace Exporter for Web */ export class CollectorTraceExporter - extends CollectorExporterBase< - CollectorExporterConfigBrowser, + extends CollectorExporterBrowserBase< ReadableSpan, collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest > implements SpanExporter { - DEFAULT_HEADERS: Record = { - [collectorTypes.OT_REQUEST_HEADER]: '1', - }; - private _headers: Record; - private _useXHR: boolean = false; - - /** - * @param config - */ - constructor(config: CollectorExporterConfigBrowser = {}) { - super(config); - this._headers = - parseHeaders(config.headers, this.logger) || this.DEFAULT_HEADERS; - this._useXHR = - !!config.headers || typeof navigator.sendBeacon !== 'function'; - } - - onInit(): void { - window.addEventListener('unload', this.shutdown); - } - - onShutdown(): void { - window.removeEventListener('unload', this.shutdown); - } - - getDefaultUrl(config: CollectorExporterConfigBrowser) { - return config.url || DEFAULT_COLLECTOR_URL; - } - - getDefaultServiceName(config: CollectorExporterConfigBrowser): string { - return config.serviceName || DEFAULT_SERVICE_NAME; - } - convert( spans: ReadableSpan[] ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { return toCollectorExportTraceServiceRequest(spans, this); } - - send( - spans: ReadableSpan[], - onSuccess: () => void, - onError: (error: collectorTypes.CollectorExporterError) => void - ) { - const exportTraceServiceRequest = this.convert(spans); - const body = JSON.stringify(exportTraceServiceRequest); - - if (this._useXHR) { - sendWithXhr( - body, - this.url, - this._headers, - this.logger, - onSuccess, - onError - ); - } else { - sendWithBeacon(body, this.url, this.logger, onSuccess, onError); - } + getDefaultUrl(config: CollectorExporterConfigBrowser) { + return config.url || DEFAULT_COLLECTOR_URL; } } diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts index 1b3936aa72a..a9f6d764fa7 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts @@ -49,13 +49,14 @@ describe('CollectorMetricExporter - web', () => { spySend = sinon.stub(XMLHttpRequest.prototype, 'send'); spyBeacon = sinon.stub(navigator, 'sendBeacon'); metrics = []; - metrics.push(Object.assign({}, mockCounter)); - metrics.push(Object.assign({}, mockObserver)); - metrics.push(Object.assign({}, mockHistogram)); - metrics.push(Object.assign({}, mockValueRecorder)); + metrics.push(mockCounter()); + metrics.push(mockObserver()); + metrics.push(mockHistogram()); + metrics.push(mockValueRecorder()); metrics[0].aggregator.update(1); - metrics[1].aggregator.update(10); + metrics[1].aggregator.update(3); + metrics[1].aggregator.update(6); metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); (metrics[2].aggregator as HistogramAggregator).reset(); @@ -63,8 +64,6 @@ describe('CollectorMetricExporter - web', () => { }); afterEach(() => { - metrics[0].aggregator.update(-1); // Aggregator is not deep-copied - (metrics[2].aggregator as HistogramAggregator).reset(); navigator.sendBeacon = sendBeacon; spyOpen.restore(); spySend.restore(); diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index 9cea4b44ae0..d4c9e38575d 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -850,20 +850,25 @@ export function ensureExportedObserverIsCorrect( name: 'test-observer', description: 'sample observer description', unit: '2', - type: 'DOUBLE', + type: 'SUMMARY', temporality: 'DELTA', }); assert.deepStrictEqual(metric.int64DataPoints, []); - assert.deepStrictEqual(metric.summaryDataPoints, []); + assert.deepStrictEqual(metric.doubleDataPoints, []); assert.deepStrictEqual(metric.histogramDataPoints, []); - assert.ok(metric.doubleDataPoints); - assert.deepStrictEqual(metric.doubleDataPoints[0].labels, []); - assert.deepStrictEqual(metric.doubleDataPoints[0].value, 10); + assert.ok(metric.summaryDataPoints); + assert.deepStrictEqual(metric.summaryDataPoints[0].labels, []); + assert.deepStrictEqual(metric.summaryDataPoints[0].sum, 9); + assert.deepStrictEqual(metric.summaryDataPoints[0].count, '2'); assert.deepStrictEqual( - metric.doubleDataPoints[0].startTimeUnixNano, + metric.summaryDataPoints[0].startTimeUnixNano, '1592602232694000128' ); + assert.deepStrictEqual(metric.summaryDataPoints[0].percentileValues, [ + { percentile: 0, value: 3 }, + { percentile: 100, value: 6 }, + ]); } export function ensureExportedHistogramIsCorrect( @@ -905,18 +910,24 @@ export function ensureExportedValueRecorderIsCorrect( name: 'test-recorder', description: 'sample recorder description', unit: '3', - type: 'INT64', + type: 'SUMMARY', temporality: 'DELTA', }); assert.deepStrictEqual(metric.histogramDataPoints, []); - assert.deepStrictEqual(metric.summaryDataPoints, []); + assert.deepStrictEqual(metric.int64DataPoints, []); assert.deepStrictEqual(metric.doubleDataPoints, []); - assert.ok(metric.int64DataPoints); - assert.deepStrictEqual(metric.int64DataPoints[0].labels, []); + assert.ok(metric.summaryDataPoints); + assert.deepStrictEqual(metric.summaryDataPoints[0].labels, []); assert.deepStrictEqual( - metric.int64DataPoints[0].startTimeUnixNano, + metric.summaryDataPoints[0].startTimeUnixNano, '1592602232694000128' ); + assert.deepStrictEqual(metric.summaryDataPoints[0].percentileValues, [ + { percentile: 0, value: 5 }, + { percentile: 100, value: 5 }, + ]); + assert.deepStrictEqual(metric.summaryDataPoints[0].count, '1'); + assert.deepStrictEqual(metric.summaryDataPoints[0].sum, 5); } export function ensureResourceIsCorrect( diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts index 7f755126542..dad769bf1a1 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -137,13 +137,14 @@ const testCollectorMetricExporter = (params: TestParams) => value: 1592602232694000000, }); metrics = []; - metrics.push(Object.assign({}, mockCounter)); - metrics.push(Object.assign({}, mockObserver)); - metrics.push(Object.assign({}, mockHistogram)); - metrics.push(Object.assign({}, mockValueRecorder)); + metrics.push(mockCounter()); + metrics.push(mockObserver()); + metrics.push(mockHistogram()); + metrics.push(mockValueRecorder()); metrics[0].aggregator.update(1); - metrics[1].aggregator.update(10); + metrics[1].aggregator.update(3); + metrics[1].aggregator.update(6); metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); (metrics[2].aggregator as HistogramAggregator).reset(); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts index 9e5155d2cd6..74a8be63b1f 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts @@ -77,20 +77,19 @@ describe('CollectorMetricExporter - node with json over http', () => { value: 1592602232694000000, }); metrics = []; - metrics.push(Object.assign({}, mockCounter)); - metrics.push(Object.assign({}, mockObserver)); - metrics.push(Object.assign({}, mockHistogram)); - metrics.push(Object.assign({}, mockValueRecorder)); + metrics.push(mockCounter()); + metrics.push(mockObserver()); + metrics.push(mockHistogram()); + metrics.push(mockValueRecorder()); metrics[0].aggregator.update(1); - metrics[1].aggregator.update(10); + metrics[1].aggregator.update(3); + metrics[1].aggregator.update(6); metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); (metrics[2].aggregator as HistogramAggregator).reset(); metrics[3].aggregator.update(5); }); afterEach(() => { - metrics[0].aggregator.update(-1); // Aggregator is not deep-copied - (metrics[2].aggregator as HistogramAggregator).reset(); spyRequest.restore(); spyWrite.restore(); }); From f89378b45c4f8c072c2c798328188819a3463491 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 20 Jul 2020 20:41:31 +0000 Subject: [PATCH 18/34] Lint --- .../test/node/CollectorMetricExporter.test.ts | 2 +- .../test/node/CollectorMetricExporterWithJson.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts index f391c8abea7..7f755126542 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -251,4 +251,4 @@ describe('CollectorMetricExporter - node (getDefaultUrl)', () => { testCollectorMetricExporter({ useTLS: true }); testCollectorMetricExporter({ useTLS: false }); -testCollectorMetricExporter({ metadata }); \ No newline at end of file +testCollectorMetricExporter({ metadata }); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts index c46a3f58060..9e5155d2cd6 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporterWithJson.test.ts @@ -230,4 +230,4 @@ describe('CollectorMetricExporter - node with json over http', () => { }); }); }); -}); \ No newline at end of file +}); From 7b90b2c6881cee645e20343189bc607bad8e5917 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 20 Jul 2020 20:53:00 +0000 Subject: [PATCH 19/34] Rename var --- .../src/platform/browser/CollectorExporterBrowserBase.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts index 7a8af327a47..e3a3422bd3c 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts @@ -58,8 +58,8 @@ export abstract class CollectorExporterBrowserBase< onSuccess: () => void, onError: (error: collectorTypes.CollectorExporterError) => void ) { - const exportTraceServiceRequest = this.convert(items); - const body = JSON.stringify(exportTraceServiceRequest); + const serviceRequest = this.convert(items); + const body = JSON.stringify(serviceRequest); if (this._useXHR) { sendWithXhr( From 07d0b94d923315001895b8152c18c09985ce78d0 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Tue, 21 Jul 2020 18:25:46 +0000 Subject: [PATCH 20/34] Remove reset --- .../test/browser/CollectorMetricExporter.test.ts | 4 +--- .../test/common/transformMetrics.test.ts | 3 --- .../test/node/CollectorMetricExporter.test.ts | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts index 4014513581e..49ac61b6a76 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorMetricExporter.test.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { CollectorMetricExporter } from '../../src/platform/browser/index'; import * as collectorTypes from '../../src/types'; -import { MetricRecord, HistogramAggregator } from '@opentelemetry/metrics'; +import { MetricRecord } from '@opentelemetry/metrics'; import { mockCounter, mockObserver, @@ -58,13 +58,11 @@ describe('CollectorMetricExporter - web', () => { metrics[1].aggregator.update(10); metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); - (metrics[2].aggregator as HistogramAggregator).reset(); metrics[3].aggregator.update(5); }); afterEach(() => { metrics[0].aggregator.update(-1); - (metrics[2].aggregator as HistogramAggregator).reset(); navigator.sendBeacon = sendBeacon; spyOpen.restore(); spySend.restore(); diff --git a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts index bb5268cf146..8cae6cc2cc5 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transformMetrics.test.ts @@ -29,7 +29,6 @@ import { ensureValueRecorderIsCorrect, mockValueRecorder, } from '../helper'; -import { HistogramAggregator } from '@opentelemetry/metrics'; import { hrTimeToNanoseconds } from '@opentelemetry/core'; describe('transformMetrics', () => { describe('toCollectorMetric', () => { @@ -43,7 +42,6 @@ describe('transformMetrics', () => { // Histogram mockHistogram.aggregator.update(7); mockHistogram.aggregator.update(14); - (mockHistogram.aggregator as HistogramAggregator).reset(); // ValueRecorder mockValueRecorder.aggregator.update(5); @@ -51,7 +49,6 @@ describe('transformMetrics', () => { afterEach(() => { mockCounter.aggregator.update(-1); // Reset counter - (mockHistogram.aggregator as HistogramAggregator).reset(); }); it('should convert metric', () => { ensureCounterIsCorrect( diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts index 7f755126542..d59fc0381d9 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorMetricExporter.test.ts @@ -23,7 +23,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { CollectorMetricExporter } from '../../src/platform/node'; import * as collectorTypes from '../../src/types'; -import { MetricRecord, HistogramAggregator } from '@opentelemetry/metrics'; +import { MetricRecord } from '@opentelemetry/metrics'; import { mockCounter, mockObserver, @@ -146,14 +146,12 @@ const testCollectorMetricExporter = (params: TestParams) => metrics[1].aggregator.update(10); metrics[2].aggregator.update(7); metrics[2].aggregator.update(14); - (metrics[2].aggregator as HistogramAggregator).reset(); metrics[3].aggregator.update(5); done(); }); afterEach(() => { metrics[0].aggregator.update(-1); // Aggregator is not deep-copied - (metrics[2].aggregator as HistogramAggregator).reset(); exportedData = undefined; reqMetadata = undefined; }); From b2e40d00896f8e59a8b2e7a1bf9c80b59f1c660c Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 27 Jul 2020 20:31:02 +0000 Subject: [PATCH 21/34] update submodule --- .../opentelemetry-exporter-collector/src/platform/node/protos | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/protos b/packages/opentelemetry-exporter-collector/src/platform/node/protos index b5468856918..9ffeee0ec53 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 From 13eda4a9c13a30ef9a32b7a3e133cd05d4fdab3a Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 27 Jul 2020 20:45:05 +0000 Subject: [PATCH 22/34] Newline --- .../src/platform/browser/CollectorTraceExporter.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts index 4993d129162..0c270ad6198 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts @@ -32,14 +32,17 @@ export class CollectorTraceExporter collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest > implements SpanExporter { + convert( spans: ReadableSpan[] ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { return toCollectorExportTraceServiceRequest(spans, this); } + getDefaultUrl(config: CollectorExporterConfigBrowser) { return config.url || DEFAULT_COLLECTOR_URL; } + getDefaultServiceName(config: CollectorExporterConfigBrowser): string { return config.serviceName || DEFAULT_SERVICE_NAME; } From 99abf6422cf382c5121d61d9b510d20a4712d79d Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 27 Jul 2020 20:57:56 +0000 Subject: [PATCH 23/34] Lint --- .../src/platform/browser/CollectorTraceExporter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts index 0c270ad6198..6fb89b46cf8 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorTraceExporter.ts @@ -32,13 +32,12 @@ export class CollectorTraceExporter collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest > implements SpanExporter { - convert( spans: ReadableSpan[] ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { return toCollectorExportTraceServiceRequest(spans, this); } - + getDefaultUrl(config: CollectorExporterConfigBrowser) { return config.url || DEFAULT_COLLECTOR_URL; } From 3ae02479fe631acc528a07dd0cb502a99bd2fb2d Mon Sep 17 00:00:00 2001 From: davidwitten Date: Tue, 28 Jul 2020 18:20:42 +0000 Subject: [PATCH 24/34] Documentatiom --- .../README.md | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/README.md b/packages/opentelemetry-exporter-collector/README.md index 8233384dd15..2c04c069289 100644 --- a/packages/opentelemetry-exporter-collector/README.md +++ b/packages/opentelemetry-exporter-collector/README.md @@ -14,7 +14,7 @@ This module provides exporter for web and node to be used with [opentelemetry-co npm install --save @opentelemetry/exporter-collector ``` -## Usage in Web +## Traces in Web The CollectorTraceExporter in Web expects the endpoint to end in `/v1/trace`. @@ -36,7 +36,32 @@ provider.register(); ``` -## Usage in Node - GRPC +## Metrics in Web + +The CollectorMetricExporter in Web expects the endpoint to end in `/v1/metrics`. + +```js +import { MetricProvider } from '@opentelemetry/metrics'; +import { CollectorMetricExporter } from '@opentelemetry/exporter-collector'; +const collectorOptions = { + url: '', // url is optional and can be omitted - default is http://localhost:55681/v1/metrics + headers: {}, //an optional object containing custom headers to be sent with each request +}; +const exporter = new CollectorMetricExporter(collectorOptions); + +// Register the exporter +const meter = new MeterProvider({ + exporter, + interval: 60000, +}).getMeter('example-meter'); + +// Now, start recording data +const counter = meter.createCounter('metric_name'); +counter.add(10, { 'key': 'value' }); + +``` + +## Traces in Node - GRPC The CollectorTraceExporter in Node expects the URL to only be the hostname. It will not work with `/v1/trace`. @@ -109,7 +134,7 @@ provider.register(); Note, that this will only work if TLS is also configured on the server. -## Usage in Node - JSON over http +## Traces in Node - JSON over http ```js const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); @@ -132,7 +157,7 @@ provider.register(); ``` -## Usage in Node - PROTO over http +## Traces in Node - PROTO over http ```js const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); @@ -155,26 +180,28 @@ provider.register(); ``` -## Usage in Node - PROTO over http +## Metrics in Node -```js -const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); -const { CollectorExporter, CollectorTransportNode } = require('@opentelemetry/exporter-collector'); +The CollectorTraceExporter in Node expects the URL to only be the hostname. It will not work with `/v1/metrics`. All options that work with trace also work with metrics. +```js +const { MeterProvider } = require('@opentelemetry/metrics'); +const { CollectorMetricExporter } = 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:55680/v1/trace - headers: { - foo: 'bar' - }, //an optional object containing custom headers to be sent with each request will only work with json over http + url: '', // url is optional and can be omitted - default is localhost:55681 }; +const exporter = new CollectorMetricExporter(collectorOptions); -const provider = new BasicTracerProvider(); -const exporter = new CollectorExporter(collectorOptions); -provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); +// Register the exporter +const meter = new MeterProvider({ + exporter, + interval: 60000, +}).getMeter('example-meter'); -provider.register(); +// Now, start recording data +const counter = meter.createCounter('metric_name'); +counter.add(10, { 'key': 'value' }); ``` From 7ba0b663633100dbfd15a2f5c6936f8c1dc45050 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 29 Jul 2020 15:35:24 +0000 Subject: [PATCH 25/34] Example --- examples/collector-exporter-node/README.md | 9 ++++++++ examples/collector-exporter-node/start.js | 26 +++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/examples/collector-exporter-node/README.md b/examples/collector-exporter-node/README.md index 9d61c6b1687..7d07b194961 100644 --- a/examples/collector-exporter-node/README.md +++ b/examples/collector-exporter-node/README.md @@ -10,6 +10,8 @@ This example will export spans data simultaneously using [Exporter Collector](ht # from this directory npm install ``` +How to setup [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_started/) please check +[Setup Prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-prometheus) ## Run the Application @@ -30,6 +32,13 @@ npm install 3. Open page at - you should be able to see the spans in zipkin ![Screenshot of the running example](images/spans.png) +### Prometheus UI + +If you are using the default configurations, the prometheus client will be available at + +

+

+ ## Useful links - For more information on OpenTelemetry, visit: diff --git a/examples/collector-exporter-node/start.js b/examples/collector-exporter-node/start.js index 4e654c7e1b0..63390c0134a 100644 --- a/examples/collector-exporter-node/start.js +++ b/examples/collector-exporter-node/start.js @@ -3,7 +3,7 @@ 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 { CollectorTraceExporter, CollectorMetricExporter, CollectorProtocolNode } = require('@opentelemetry/exporter-collector'); const exporter = new CollectorTraceExporter({ serviceName: 'basic-service', @@ -15,6 +15,10 @@ const exporter = new CollectorTraceExporter({ // protocolNode: CollectorProtocolNode.HTTP_JSON, }); +const metricExporter = new CollectorMetricExporter({ + serviceName: 'basic-metric-service', +}); + const provider = new BasicTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); @@ -64,3 +68,23 @@ function doWork(parent) { // end span span.end(); } + +const meter = new MeterProvider({ + exporter: metricExporter, + interval: 1000, +}).getMeter('example-prometheus'); + +const requestCounter = meter.createCounter('requests', { + description: 'Example of a Counter', +}); + +const upDownCounter = meter.createUpDownCounter('test_up_down_counter', { + description: 'Example of a UpDownCounter', +}); + +const labels = { pid: process.pid, environment: 'staging' }; + +setInterval(() => { + requestCounter.bind(labels).add(1); + upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); +}, 1000); \ No newline at end of file From 9c292d7ba07f9b7079c2d3167245a35104f20e6d Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 29 Jul 2020 13:33:47 -0400 Subject: [PATCH 26/34] fix: verified example --- .../collector-exporter-node/docker/collector-config.yaml | 6 ++++++ .../collector-exporter-node/docker/docker-compose.yaml | 1 + examples/collector-exporter-node/package.json | 9 +++++---- examples/collector-exporter-node/start.js | 4 +++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/collector-exporter-node/docker/collector-config.yaml b/examples/collector-exporter-node/docker/collector-config.yaml index f104677f7eb..e9a909d78fa 100644 --- a/examples/collector-exporter-node/docker/collector-config.yaml +++ b/examples/collector-exporter-node/docker/collector-config.yaml @@ -10,6 +10,8 @@ receivers: exporters: zipkin: endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" + prometheus: + endpoint: "0.0.0.0:9464" processors: batch: @@ -21,3 +23,7 @@ service: receivers: [otlp] exporters: [zipkin] processors: [batch, queued_retry] + metrics: + receivers: [otlp] + exporters: [prometheus] + processors: [batch, queued_retry] diff --git a/examples/collector-exporter-node/docker/docker-compose.yaml b/examples/collector-exporter-node/docker/docker-compose.yaml index 3882379ad35..f19dab14b47 100644 --- a/examples/collector-exporter-node/docker/docker-compose.yaml +++ b/examples/collector-exporter-node/docker/docker-compose.yaml @@ -10,6 +10,7 @@ services: volumes: - ./collector-config.yaml:/conf/collector-config.yaml ports: + - "9464:9464" - "55680:55680" - "55681:55681" depends_on: diff --git a/examples/collector-exporter-node/package.json b/examples/collector-exporter-node/package.json index ac987ef149f..fc0f328fb73 100644 --- a/examples/collector-exporter-node/package.json +++ b/examples/collector-exporter-node/package.json @@ -27,10 +27,11 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { - "@opentelemetry/api": "^0.9.0", - "@opentelemetry/core": "^0.9.0", - "@opentelemetry/exporter-collector": "^0.9.0", - "@opentelemetry/tracing": "^0.9.0" + "@opentelemetry/api": "^0.10.1", + "@opentelemetry/core": "^0.10.1", + "@opentelemetry/exporter-collector": "^0.10.1", + "@opentelemetry/metrics": "^0.10.1", + "@opentelemetry/tracing": "^0.10.1" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme" } diff --git a/examples/collector-exporter-node/start.js b/examples/collector-exporter-node/start.js index 63390c0134a..a65af7d02eb 100644 --- a/examples/collector-exporter-node/start.js +++ b/examples/collector-exporter-node/start.js @@ -4,6 +4,7 @@ const opentelemetry = require('@opentelemetry/api'); // const { ConsoleLogger, LogLevel} = require('@opentelemetry/core'); const { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { CollectorTraceExporter, CollectorMetricExporter, CollectorProtocolNode } = require('@opentelemetry/exporter-collector'); +const { MeterProvider } = require('@opentelemetry/metrics'); const exporter = new CollectorTraceExporter({ serviceName: 'basic-service', @@ -17,6 +18,7 @@ const exporter = new CollectorTraceExporter({ const metricExporter = new CollectorMetricExporter({ serviceName: 'basic-metric-service', + // logger: new ConsoleLogger(LogLevel.DEBUG), }); const provider = new BasicTracerProvider(); @@ -87,4 +89,4 @@ const labels = { pid: process.pid, environment: 'staging' }; setInterval(() => { requestCounter.bind(labels).add(1); upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); -}, 1000); \ No newline at end of file +}, 1000); From 765ddfac25597a8be48af8758d246f4c6701a28e Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 29 Jul 2020 17:56:59 +0000 Subject: [PATCH 27/34] Newline --- examples/collector-exporter-node/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/collector-exporter-node/README.md b/examples/collector-exporter-node/README.md index 7d07b194961..8e694cef4d3 100644 --- a/examples/collector-exporter-node/README.md +++ b/examples/collector-exporter-node/README.md @@ -10,6 +10,7 @@ This example will export spans data simultaneously using [Exporter Collector](ht # from this directory npm install ``` + How to setup [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_started/) please check [Setup Prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-prometheus) From 10236ee2872bd46bb144ec4b0b09afc7d503cfe5 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Wed, 29 Jul 2020 15:58:37 -0400 Subject: [PATCH 28/34] feat: simplified example --- examples/collector-exporter-node/README.md | 6 +++--- .../docker/docker-compose.yaml | 14 ++++++++------ .../collector-exporter-node/docker/prometheus.yaml | 9 +++++++++ 3 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 examples/collector-exporter-node/docker/prometheus.yaml diff --git a/examples/collector-exporter-node/README.md b/examples/collector-exporter-node/README.md index 7d07b194961..39d020f12c3 100644 --- a/examples/collector-exporter-node/README.md +++ b/examples/collector-exporter-node/README.md @@ -10,8 +10,6 @@ This example will export spans data simultaneously using [Exporter Collector](ht # from this directory npm install ``` -How to setup [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_started/) please check -[Setup Prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-prometheus) ## Run the Application @@ -34,7 +32,9 @@ How to setup [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_s ### Prometheus UI -If you are using the default configurations, the prometheus client will be available at +The prometheus client will be available at . + +Note: It may take some time for the application metrics to appear on the Prometheus dashboard.

diff --git a/examples/collector-exporter-node/docker/docker-compose.yaml b/examples/collector-exporter-node/docker/docker-compose.yaml index f19dab14b47..0dfe1a23f70 100644 --- a/examples/collector-exporter-node/docker/docker-compose.yaml +++ b/examples/collector-exporter-node/docker/docker-compose.yaml @@ -5,8 +5,6 @@ services: 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: @@ -19,10 +17,14 @@ services: # Zipkin zipkin-all-in-one: image: openzipkin/zipkin:latest - networks: - - otelcol ports: - "9411:9411" -networks: - otelcol: + # Prometheus + prometheus: + container_name: prometheus + image: prom/prometheus:latest + volumes: + - ./prometheus.yaml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" diff --git a/examples/collector-exporter-node/docker/prometheus.yaml b/examples/collector-exporter-node/docker/prometheus.yaml new file mode 100644 index 00000000000..b027daf9a0b --- /dev/null +++ b/examples/collector-exporter-node/docker/prometheus.yaml @@ -0,0 +1,9 @@ +global: + scrape_interval: 15s # Default is every 1 minute. + +scrape_configs: + - job_name: 'collector' + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + static_configs: + - targets: ['collector:9464'] From 21c9e8be17dc9245ded1d14fa783ac9f3b6d7f63 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Fri, 31 Jul 2020 08:33:30 -0400 Subject: [PATCH 29/34] chore: rename start --- examples/collector-exporter-node/package.json | 2 +- examples/collector-exporter-node/{start.js => tracing.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/collector-exporter-node/{start.js => tracing.js} (100%) diff --git a/examples/collector-exporter-node/package.json b/examples/collector-exporter-node/package.json index 4917f96993c..2a63631026c 100644 --- a/examples/collector-exporter-node/package.json +++ b/examples/collector-exporter-node/package.json @@ -5,7 +5,7 @@ "description": "Example of using @opentelemetry/collector-exporter in Node.js", "main": "index.js", "scripts": { - "start": "node ./start.js", + "start": "node tracing.js", "docker:start": "cd ./docker && docker-compose down && docker-compose up", "docker:startd": "cd ./docker && docker-compose down && docker-compose up -d", "docker:stop": "cd ./docker && docker-compose down" diff --git a/examples/collector-exporter-node/start.js b/examples/collector-exporter-node/tracing.js similarity index 100% rename from examples/collector-exporter-node/start.js rename to examples/collector-exporter-node/tracing.js From a8857d3e35259f9699a171872c6372030b63f8df Mon Sep 17 00:00:00 2001 From: davidwitten Date: Fri, 31 Jul 2020 08:38:44 -0400 Subject: [PATCH 30/34] refactor: split up files --- examples/collector-exporter-node/metrics.js | 30 +++++++++++++++++++ examples/collector-exporter-node/package.json | 2 +- examples/collector-exporter-node/tracing.js | 28 ++--------------- 3 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 examples/collector-exporter-node/metrics.js diff --git a/examples/collector-exporter-node/metrics.js b/examples/collector-exporter-node/metrics.js new file mode 100644 index 00000000000..b0a6d72b00d --- /dev/null +++ b/examples/collector-exporter-node/metrics.js @@ -0,0 +1,30 @@ +'use strict'; + +const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector'); +const { MeterProvider } = require('@opentelemetry/metrics'); + +const metricExporter = new CollectorMetricExporter({ + serviceName: 'basic-metric-service', + // logger: new ConsoleLogger(LogLevel.DEBUG), +}); + + +const meter = new MeterProvider({ + exporter: metricExporter, + interval: 1000, +}).getMeter('example-prometheus'); + +const requestCounter = meter.createCounter('requests', { + description: 'Example of a Counter', +}); + +const upDownCounter = meter.createUpDownCounter('test_up_down_counter', { + description: 'Example of a UpDownCounter', +}); + +const labels = { pid: process.pid, environment: 'staging' }; + +setInterval(() => { + requestCounter.bind(labels).add(1); + upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); +}, 1000); diff --git a/examples/collector-exporter-node/package.json b/examples/collector-exporter-node/package.json index 2a63631026c..49d9bfcf4fa 100644 --- a/examples/collector-exporter-node/package.json +++ b/examples/collector-exporter-node/package.json @@ -5,7 +5,7 @@ "description": "Example of using @opentelemetry/collector-exporter in Node.js", "main": "index.js", "scripts": { - "start": "node tracing.js", + "start": "node tracing.js && node metrics.js", "docker:start": "cd ./docker && docker-compose down && docker-compose up", "docker:startd": "cd ./docker && docker-compose down && docker-compose up -d", "docker:stop": "cd ./docker && docker-compose down" diff --git a/examples/collector-exporter-node/tracing.js b/examples/collector-exporter-node/tracing.js index a65af7d02eb..50af2311119 100644 --- a/examples/collector-exporter-node/tracing.js +++ b/examples/collector-exporter-node/tracing.js @@ -3,8 +3,7 @@ const opentelemetry = require('@opentelemetry/api'); // const { ConsoleLogger, LogLevel} = require('@opentelemetry/core'); const { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } = require('@opentelemetry/tracing'); -const { CollectorTraceExporter, CollectorMetricExporter, CollectorProtocolNode } = require('@opentelemetry/exporter-collector'); -const { MeterProvider } = require('@opentelemetry/metrics'); +const { CollectorTraceExporter, CollectorProtocolNode } = require('@opentelemetry/exporter-collector'); const exporter = new CollectorTraceExporter({ serviceName: 'basic-service', @@ -16,10 +15,7 @@ const exporter = new CollectorTraceExporter({ // protocolNode: CollectorProtocolNode.HTTP_JSON, }); -const metricExporter = new CollectorMetricExporter({ - serviceName: 'basic-metric-service', - // logger: new ConsoleLogger(LogLevel.DEBUG), -}); + const provider = new BasicTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); @@ -70,23 +66,3 @@ function doWork(parent) { // end span span.end(); } - -const meter = new MeterProvider({ - exporter: metricExporter, - interval: 1000, -}).getMeter('example-prometheus'); - -const requestCounter = meter.createCounter('requests', { - description: 'Example of a Counter', -}); - -const upDownCounter = meter.createUpDownCounter('test_up_down_counter', { - description: 'Example of a UpDownCounter', -}); - -const labels = { pid: process.pid, environment: 'staging' }; - -setInterval(() => { - requestCounter.bind(labels).add(1); - upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); -}, 1000); From 15de18baf9849c41db5a32852dadd7e733ea54c5 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Fri, 31 Jul 2020 09:11:11 -0400 Subject: [PATCH 31/34] fix: lint --- examples/collector-exporter-node/metrics.js | 17 ++++++++--------- examples/collector-exporter-node/tracing.js | 2 -- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/collector-exporter-node/metrics.js b/examples/collector-exporter-node/metrics.js index b0a6d72b00d..f91e105c201 100644 --- a/examples/collector-exporter-node/metrics.js +++ b/examples/collector-exporter-node/metrics.js @@ -4,27 +4,26 @@ const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector') const { MeterProvider } = require('@opentelemetry/metrics'); const metricExporter = new CollectorMetricExporter({ - serviceName: 'basic-metric-service', - // logger: new ConsoleLogger(LogLevel.DEBUG), + serviceName: 'basic-metric-service', + // logger: new ConsoleLogger(LogLevel.DEBUG), }); - const meter = new MeterProvider({ - exporter: metricExporter, - interval: 1000, + exporter: metricExporter, + interval: 1000, }).getMeter('example-prometheus'); const requestCounter = meter.createCounter('requests', { - description: 'Example of a Counter', + description: 'Example of a Counter', }); const upDownCounter = meter.createUpDownCounter('test_up_down_counter', { - description: 'Example of a UpDownCounter', + description: 'Example of a UpDownCounter', }); const labels = { pid: process.pid, environment: 'staging' }; setInterval(() => { - requestCounter.bind(labels).add(1); - upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); + requestCounter.bind(labels).add(1); + upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); }, 1000); diff --git a/examples/collector-exporter-node/tracing.js b/examples/collector-exporter-node/tracing.js index 50af2311119..4e654c7e1b0 100644 --- a/examples/collector-exporter-node/tracing.js +++ b/examples/collector-exporter-node/tracing.js @@ -15,8 +15,6 @@ const exporter = new CollectorTraceExporter({ // protocolNode: CollectorProtocolNode.HTTP_JSON, }); - - const provider = new BasicTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); From 175e0503bdde77a919e1730a3a662ad4a6d74154 Mon Sep 17 00:00:00 2001 From: davidwitten Date: Fri, 31 Jul 2020 16:48:24 -0400 Subject: [PATCH 32/34] fix: add comments --- .../src/platform/browser/CollectorExporterBrowserBase.ts | 4 ++-- .../src/platform/browser/CollectorMetricExporter.ts | 1 + .../src/platform/node/CollectorMetricExporter.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts index f9402918196..a716a2d6f7d 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts @@ -31,8 +31,8 @@ export abstract class CollectorExporterBrowserBase< ExportItem, ServiceRequest > { - protected _headers: Record; - protected _useXHR: boolean = false; + _headers: Record; + _useXHR: boolean = false; /** * @param config diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts index fe5f3baf35a..772f7fca297 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorMetricExporter.ts @@ -32,6 +32,7 @@ export class CollectorMetricExporter collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest > implements MetricExporter { + // Converts time to nanoseconds private readonly _startTime = new Date().getTime() * 1000000; convert( diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts index 3f0aa3c8def..8eebc7bdebe 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/CollectorMetricExporter.ts @@ -35,6 +35,7 @@ export class CollectorMetricExporter collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest > implements MetricExporter { + // Converts time to nanoseconds protected readonly _startTime = new Date().getTime() * 1000000; convert( From e526e11d6bd295b67685ae53bb6137e1a0b0678b Mon Sep 17 00:00:00 2001 From: davidwitten Date: Mon, 3 Aug 2020 12:18:56 -0400 Subject: [PATCH 33/34] fix: private --- .../src/platform/browser/CollectorExporterBrowserBase.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts index a716a2d6f7d..ab35cd98ac8 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/CollectorExporterBrowserBase.ts @@ -31,8 +31,8 @@ export abstract class CollectorExporterBrowserBase< ExportItem, ServiceRequest > { - _headers: Record; - _useXHR: boolean = false; + private _headers: Record; + private _useXHR: boolean = false; /** * @param config From ccedacccc105a47443aeb726ff8fbe37afa4e3bd Mon Sep 17 00:00:00 2001 From: davidwitten Date: Tue, 4 Aug 2020 19:26:18 +0000 Subject: [PATCH 34/34] fix: readme --- examples/collector-exporter-node/README.md | 13 ++++++++++--- examples/collector-exporter-node/package.json | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/collector-exporter-node/README.md b/examples/collector-exporter-node/README.md index 39d020f12c3..e1b19ffef95 100644 --- a/examples/collector-exporter-node/README.md +++ b/examples/collector-exporter-node/README.md @@ -20,14 +20,21 @@ npm install npm run docker:start ``` -2. Run app +2. Run tracing app ```shell script # from this directory - npm start + npm start:tracing ``` -3. Open page at - you should be able to see the spans in zipkin +3. Run metrics app + + ```shell script + # from this directory + npm start:metrics + ``` + +4. Open page at - you should be able to see the spans in zipkin ![Screenshot of the running example](images/spans.png) ### Prometheus UI diff --git a/examples/collector-exporter-node/package.json b/examples/collector-exporter-node/package.json index ef8736d4339..7dfeb695fb8 100644 --- a/examples/collector-exporter-node/package.json +++ b/examples/collector-exporter-node/package.json @@ -5,7 +5,8 @@ "description": "Example of using @opentelemetry/collector-exporter in Node.js", "main": "index.js", "scripts": { - "start": "node tracing.js && node metrics.js", + "start:tracing": "node tracing.js", + "start:metrics": "node metrics.js", "docker:start": "cd ./docker && docker-compose down && docker-compose up", "docker:startd": "cd ./docker && docker-compose down && docker-compose up -d", "docker:stop": "cd ./docker && docker-compose down"