diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6875b43758..922a1a4674a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,6 +107,9 @@ npm run clean These commands can also be run for specific packages instead of the whole project, which can speed up compilations while developing. +**NOTE**: To run commands in specific packages (compile, lint, etc), please ensure you are using at least `7.x` +version of `npm`. + ```sh # Build a single module and all of its dependencies cd packages/opentelemetry-module-name diff --git a/examples/prometheus/index.js b/examples/prometheus/index.js index ea3f2e6882e..8f638d66a1c 100644 --- a/examples/prometheus/index.js +++ b/examples/prometheus/index.js @@ -3,16 +3,13 @@ const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); -const exporter = new PrometheusExporter( - { - startServer: true, - }, - () => { - console.log( - `prometheus scrape endpoint: http://localhost:${PrometheusExporter.DEFAULT_OPTIONS.port}${PrometheusExporter.DEFAULT_OPTIONS.endpoint}`, - ); - }, -); +const { endpoint, port } = PrometheusExporter.DEFAULT_OPTIONS; + +const exporter = new PrometheusExporter({}, () => { + console.log( + `prometheus scrape endpoint: http://localhost:${port}${endpoint}`, + ); +}); const meter = new MeterProvider({ exporter, diff --git a/experimental/packages/opentelemetry-api-metrics/package.json b/experimental/packages/opentelemetry-api-metrics/package.json index 2a240615508..eae1a83de51 100644 --- a/experimental/packages/opentelemetry-api-metrics/package.json +++ b/experimental/packages/opentelemetry-api-metrics/package.json @@ -69,7 +69,7 @@ "@types/webpack-env": "1.16.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json index 7992bc1d56b..495d21ae1a8 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json @@ -70,7 +70,7 @@ "codecov": "3.8.3", "cpx": "1.5.0", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/package.json b/experimental/packages/opentelemetry-instrumentation-fetch/package.json index deb80682255..ed3abadff82 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/package.json +++ b/experimental/packages/opentelemetry-instrumentation-fetch/package.json @@ -64,7 +64,7 @@ "babel-loader": "8.2.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json index 731edf6129f..8e7c32d1d4c 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json @@ -64,7 +64,7 @@ "babel-loader": "8.2.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/experimental/packages/opentelemetry-instrumentation/package.json b/experimental/packages/opentelemetry-instrumentation/package.json index 7a089876cc0..ca1b1d39f23 100644 --- a/experimental/packages/opentelemetry-instrumentation/package.json +++ b/experimental/packages/opentelemetry-instrumentation/package.json @@ -84,7 +84,7 @@ "codecov": "3.8.3", "cpx": "1.5.0", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/examples/metrics/metrics/observer.js b/experimental/packages/opentelemetry-sdk-metrics-base/examples/metrics/metrics/observer.js index 2f3d3f9a533..87d0e3817ce 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/examples/metrics/metrics/observer.js +++ b/experimental/packages/opentelemetry-sdk-metrics-base/examples/metrics/metrics/observer.js @@ -7,16 +7,13 @@ const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); // Optional and only needed to see the internal diagnostic logging (during development) diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); -const exporter = new PrometheusExporter( - { - startServer: true, - }, - () => { - console.log( - `prometheus scrape endpoint: http://localhost:${PrometheusExporter.DEFAULT_OPTIONS.port}${PrometheusExporter.DEFAULT_OPTIONS.endpoint}`, - ); - }, -); +const { endpoint, port } = PrometheusExporter.DEFAULT_OPTIONS; + +const exporter = new PrometheusExporter({}, () => { + console.log( + `prometheus scrape endpoint: http://localhost:${port}${endpoint}`, + ); +}); const meter = new MeterProvider({ exporter, @@ -34,12 +31,12 @@ meter.createObservableGauge('cpu_core_usage', { function getAsyncValue() { return new Promise((resolve) => { - setTimeout(()=> { + setTimeout(() => { resolve(Math.random()); }, 100); }); } -setInterval(function(){ +setInterval(function () { console.log("simulating an app being kept open") }, 5000); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/package.json b/experimental/packages/opentelemetry-sdk-metrics-base/package.json index 9bcd19f3d01..4d25223b17b 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/package.json +++ b/experimental/packages/opentelemetry-sdk-metrics-base/package.json @@ -59,7 +59,7 @@ "@types/node": "14.17.33", "@types/sinon": "10.0.6", "codecov": "3.8.3", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/Instruments.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/Instruments.ts index 60dbc9e364a..0d899b29b16 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/Instruments.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/Instruments.ts @@ -20,13 +20,19 @@ import { InstrumentDescriptor } from './InstrumentDescriptor'; import { WritableMetricStorage } from './state/WritableMetricStorage'; export class SyncInstrument { - constructor(private _writableMetricStorage: WritableMetricStorage, private _descriptor: InstrumentDescriptor) { } + constructor(private _writableMetricStorage: WritableMetricStorage, private _descriptor: InstrumentDescriptor) {} getName(): string { return this._descriptor.name; } - aggregate(value: number, attributes: metrics.Attributes = {}, context: api.Context = api.context.active()) { + protected _record(value: number, attributes: metrics.Attributes = {}, context: api.Context = api.context.active()) { + if (this._descriptor.valueType === metrics.ValueType.INT && !Number.isInteger(value)) { + api.diag.warn( + `INT value type cannot accept a floating-point value for ${this._descriptor.name}, ignoring the fractional digits.` + ); + value = Math.trunc(value); + } this._writableMetricStorage.record(value, attributes, context); } } @@ -39,7 +45,7 @@ export class UpDownCounter extends SyncInstrument implements metrics.UpDownCount * Increment value of counter by the input. Inputs may be negative. */ add(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void { - this.aggregate(value, attributes, ctx); + this._record(value, attributes, ctx); } } @@ -56,7 +62,7 @@ export class Counter extends SyncInstrument implements metrics.Counter { return; } - this.aggregate(value, attributes, ctx); + this._record(value, attributes, ctx); } } @@ -68,6 +74,6 @@ export class Histogram extends SyncInstrument implements metrics.Histogram { * Records a measurement. Value of the measurement must not be negative. */ record(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void { - this.aggregate(value, attributes, ctx); + this._record(value, attributes, ctx); } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricExporter.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricExporter.ts index 697f020b285..f86f7d84cde 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricExporter.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/MetricExporter.ts @@ -16,40 +16,34 @@ import { AggregationTemporality } from './AggregationTemporality'; import { MetricData } from './MetricData'; +import { + ExportResult, + ExportResultCode, +} from '@opentelemetry/core'; // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#metricexporter -// TODO should this just be an interface and exporters can implement their own shutdown? -export abstract class MetricExporter { - protected _shutdown = false; +export interface PushMetricExporter { - abstract export(batch: MetricData[]): Promise; + export(batch: MetricData[], resultCallback: (result: ExportResult) => void): void; - abstract forceFlush(): Promise; + forceFlush(): Promise; - abstract getPreferredAggregationTemporality(): AggregationTemporality; + getPreferredAggregationTemporality(): AggregationTemporality; - async shutdown(): Promise { - if (this._shutdown) { - return; - } + shutdown(): Promise; - // Setting _shutdown before flushing might prevent some exporters from flushing - // Waiting until flushing is complete might allow another flush to occur during shutdown - const flushPromise = this.forceFlush(); - this._shutdown = true; - await flushPromise; - } - - isShutdown() { - return this._shutdown; - } } -export class ConsoleMetricExporter extends MetricExporter { - async export(_batch: MetricData[]) { - throw new Error('Method not implemented'); +export class ConsoleMetricExporter implements PushMetricExporter { + protected _shutdown = true; + + export(_batch: MetricData[], resultCallback: (result: ExportResult) => void) { + return resultCallback({ + code: ExportResultCode.FAILED, + error: new Error('Method not implemented') + }); } getPreferredAggregationTemporality() { @@ -58,4 +52,8 @@ export class ConsoleMetricExporter extends MetricExporter { // nothing to do async forceFlush() {} + + async shutdown() { + this._shutdown = true; + } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/PeriodicExportingMetricReader.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/PeriodicExportingMetricReader.ts index 0e5861941af..f5595a743da 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/PeriodicExportingMetricReader.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/PeriodicExportingMetricReader.ts @@ -15,12 +15,13 @@ */ import * as api from '@opentelemetry/api'; +import { ExportResultCode, globalErrorHandler } from '@opentelemetry/core'; import { MetricReader } from './MetricReader'; -import { MetricExporter } from './MetricExporter'; +import { PushMetricExporter } from './MetricExporter'; import { callWithTimeout, TimeoutError } from '../utils'; export type PeriodicExportingMetricReaderOptions = { - exporter: MetricExporter + exporter: PushMetricExporter exportIntervalMillis?: number, exportTimeoutMillis?: number }; @@ -32,7 +33,7 @@ export type PeriodicExportingMetricReaderOptions = { export class PeriodicExportingMetricReader extends MetricReader { private _interval?: ReturnType; - private _exporter: MetricExporter; + private _exporter: PushMetricExporter; private readonly _exportInterval: number; @@ -62,7 +63,20 @@ export class PeriodicExportingMetricReader extends MetricReader { private async _runOnce(): Promise { const metrics = await this.collect({}); - await this._exporter.export(metrics); + return new Promise((resolve, reject) => { + this._exporter.export(metrics, result => { + if (result.code !== ExportResultCode.SUCCESS) { + reject( + result.error ?? + new Error( + `PeriodicExportingMetricReader: metrics export failed (error ${result.error})` + ) + ); + } else { + resolve(); + } + }); + }); } protected override onInitialized(): void { @@ -76,7 +90,7 @@ export class PeriodicExportingMetricReader extends MetricReader { return; } - api.diag.error('Unexpected error during export: %s', err); + globalErrorHandler(err); } }, this._exportInterval); } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts new file mode 100644 index 00000000000..97151c5e425 --- /dev/null +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/Instruments.test.ts @@ -0,0 +1,572 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { InstrumentationLibrary } from '@opentelemetry/core'; +import { Resource } from '@opentelemetry/resources'; +import { AggregationTemporality, InstrumentDescriptor, InstrumentType, MeterProvider, MetricReader, PointData, PointDataType } from '../src'; +import { TestMetricReader } from './export/TestMetricReader'; +import { assertMetricData, assertPointData, commonValues, commonAttributes, defaultResource, defaultInstrumentationLibrary } from './util'; +import { Histogram } from '../src/aggregator/types'; +import { ObservableResult, ValueType } from '@opentelemetry/api-metrics-wip'; + +describe('Instruments', () => { + describe('Counter', () => { + it('should add common values and attributes without exceptions', async () => { + const { meter, cumulativeReader } = setup(); + const counter = meter.createCounter('test', { + description: 'foobar', + unit: 'kB', + }); + + for (const values of commonValues) { + for (const attributes of commonAttributes) { + counter.add(values, attributes); + } + } + + await validateExport(cumulativeReader, { + instrumentDescriptor: { + name: 'test', + description: 'foobar', + unit: 'kB', + type: InstrumentType.COUNTER, + valueType: ValueType.DOUBLE, + }, + }); + }); + + it('should add valid INT values', async () => { + const { meter, cumulativeReader } = setup(); + const counter = meter.createCounter('test', { + valueType: ValueType.INT, + }); + + counter.add(1); + // floating-point value should be trunc-ed. + counter.add(1.1); + counter.add(1, { foo: 'bar' }); + await validateExport(cumulativeReader, { + instrumentDescriptor: { + name: 'test', + description: '', + unit: '1', + type: InstrumentType.COUNTER, + valueType: ValueType.INT, + }, + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + + // add negative values should not be observable. + counter.add(-1.1); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + }); + + it('should add valid DOUBLE values', async () => { + const { meter, cumulativeReader } = setup(); + const counter = meter.createCounter('test', { + valueType: ValueType.DOUBLE, + }); + + counter.add(1); + // add floating-point value. + counter.add(1.1); + counter.add(1, { foo: 'bar' }); + counter.add(1.2, { foo: 'bar' }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2.1, + }, + { + attributes: { foo: 'bar' }, + point: 2.2, + }, + ], + }); + + // add negative values should not be observable. + counter.add(-1.1); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2.1, + }, + { + attributes: { foo: 'bar' }, + point: 2.2, + }, + ], + }); + }); + }); + + describe('UpDownCounter', () => { + it('should add common values and attributes without exceptions', async () => { + const { meter, cumulativeReader } = setup(); + const upDownCounter = meter.createUpDownCounter('test', { + description: 'foobar', + unit: 'kB', + }); + + for (const values of commonValues) { + for (const attributes of commonAttributes) { + upDownCounter.add(values, attributes); + } + } + + await validateExport(cumulativeReader, { + instrumentDescriptor: { + name: 'test', + description: 'foobar', + unit: 'kB', + type: InstrumentType.UP_DOWN_COUNTER, + valueType: ValueType.DOUBLE, + }, + }); + }); + + it('should add INT values', async () => { + const { meter, deltaReader } = setup(); + const upDownCounter = meter.createUpDownCounter('test', { + valueType: ValueType.INT, + }); + + upDownCounter.add(3); + upDownCounter.add(-1.1); + upDownCounter.add(4, { foo: 'bar' }); + upDownCounter.add(1.1, { foo: 'bar' }); + await validateExport(deltaReader, { + instrumentDescriptor: { + name: 'test', + description: '', + unit: '1', + type: InstrumentType.UP_DOWN_COUNTER, + valueType: ValueType.INT, + }, + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2, + }, + { + attributes: { foo: 'bar' }, + point: 5, + } + ], + }); + }); + + it('should add DOUBLE values', async () => { + const { meter, deltaReader } = setup(); + const upDownCounter = meter.createUpDownCounter('test', { + valueType: ValueType.DOUBLE, + }); + + upDownCounter.add(3); + upDownCounter.add(-1.1); + upDownCounter.add(4, { foo: 'bar' }); + upDownCounter.add(1.1, { foo: 'bar' }); + await validateExport(deltaReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 1.9, + }, + { + attributes: { foo: 'bar' }, + point: 5.1, + } + ], + }); + }); + }); + + describe('Histogram', () => { + it('should record common values and attributes without exceptions', async () => { + const { meter, cumulativeReader } = setup(); + const histogram = meter.createHistogram('test', { + description: 'foobar', + unit: 'kB', + }); + + for (const values of commonValues) { + for (const attributes of commonAttributes) { + + histogram.record(values, attributes); + } + } + + await validateExport(cumulativeReader, { + instrumentDescriptor: { + name: 'test', + description: 'foobar', + unit: 'kB', + type: InstrumentType.HISTOGRAM, + valueType: ValueType.DOUBLE, + }, + }); + }); + + it('should record INT values', async () => { + const { meter, deltaReader } = setup(); + const histogram = meter.createHistogram('test', { + valueType: ValueType.INT, + }); + + histogram.record(10); + // -0.1 should be trunc-ed to -0 + histogram.record(-0.1); + histogram.record(100, { foo: 'bar' }); + histogram.record(-0.1, { foo: 'bar' }); + await validateExport(deltaReader, { + instrumentDescriptor: { + name: 'test', + description: '', + unit: '1', + type: InstrumentType.HISTOGRAM, + valueType: ValueType.INT, + }, + pointDataType: PointDataType.HISTOGRAM, + pointData: [ + { + attributes: {}, + point: { + buckets: { + boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], + counts: [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0], + }, + count: 2, + sum: 10, + }, + }, + { + attributes: { foo: 'bar' }, + point: { + buckets: { + boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], + counts: [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0], + }, + count: 2, + sum: 100, + }, + }, + ], + }); + }); + + + it('should record DOUBLE values', async () => { + const { meter, deltaReader } = setup(); + const histogram = meter.createHistogram('test', { + valueType: ValueType.DOUBLE, + }); + + histogram.record(10); + histogram.record(-0.1); + histogram.record(100, { foo: 'bar' }); + histogram.record(-0.1, { foo: 'bar' }); + await validateExport(deltaReader, { + pointDataType: PointDataType.HISTOGRAM, + pointData: [ + { + attributes: {}, + point: { + buckets: { + boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], + counts: [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + }, + count: 2, + sum: 9.9, + }, + }, + { + attributes: { foo: 'bar' }, + point: { + buckets: { + boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], + counts: [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + }, + count: 2, + sum: 99.9, + }, + }, + ], + }); + }); + }); + + describe('ObservableCounter', () => { + it('should observe common values and attributes without exceptions', async () => { + const { meter, deltaReader } = setup(); + const callback = sinon.spy((observableResult: ObservableResult) => { + for (const values of commonValues) { + for (const attributes of commonAttributes) { + observableResult.observe(values, attributes); + } + } + }); + meter.createObservableCounter('test', callback); + + await deltaReader.collect(); + assert.strictEqual(callback.callCount, 1); + }); + + it('should observe values', async () => { + const { meter, cumulativeReader } = setup(); + let callCount = 0; + meter.createObservableCounter('test', observableResult => { + observableResult.observe(++callCount); + observableResult.observe(1, { foo: 'bar' }); + }); + + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 1, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + }); + }); + + describe('ObservableUpDownCounter', () => { + it('should observe common values and attributes without exceptions', async () => { + const { meter, deltaReader } = setup(); + const callback = sinon.spy((observableResult: ObservableResult) => { + for (const values of commonValues) { + for (const attributes of commonAttributes) { + observableResult.observe(values, attributes); + } + } + }); + meter.createObservableUpDownCounter('test', callback); + + await deltaReader.collect(); + assert.strictEqual(callback.callCount, 1); + }); + + it('should observe values', async () => { + const { meter, cumulativeReader } = setup(); + let callCount = 0; + meter.createObservableUpDownCounter('test', observableResult => { + observableResult.observe(++callCount); + observableResult.observe(1, { foo: 'bar' }); + }); + + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 1, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 2, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + }); + }); + + describe('ObservableGauge', () => { + it('should observe common values and attributes without exceptions', async () => { + const { meter, deltaReader } = setup(); + const callback = sinon.spy((observableResult: ObservableResult) => { + for (const values of commonValues) { + for (const attributes of commonAttributes) { + observableResult.observe(values, attributes); + } + } + }); + meter.createObservableGauge('test', callback); + + await deltaReader.collect(); + assert.strictEqual(callback.callCount, 1); + }); + + it('should observe values', async () => { + const { meter, cumulativeReader } = setup(); + let num = 0; + meter.createObservableGauge('test', observableResult => { + num += 10; + if (num === 30) { + observableResult.observe(-1); + } else { + observableResult.observe(num); + } + observableResult.observe(1, { foo: 'bar' }); + }); + + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 10, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: 20, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + await validateExport(cumulativeReader, { + pointDataType: PointDataType.SINGULAR, + pointData: [ + { + attributes: {}, + point: -1, + }, + { + attributes: { foo: 'bar' }, + point: 1, + }, + ], + }); + }); + }); +}); + +function setup() { + const meterProvider = new MeterProvider({ resource: defaultResource }); + const meter = meterProvider.getMeter(defaultInstrumentationLibrary.name, defaultInstrumentationLibrary.version, { + schemaUrl: defaultInstrumentationLibrary.schemaUrl, + }); + const deltaReader = new TestMetricReader(AggregationTemporality.DELTA); + meterProvider.addMetricReader(deltaReader); + const cumulativeReader = new TestMetricReader(AggregationTemporality.CUMULATIVE); + meterProvider.addMetricReader(cumulativeReader); + + return { + meterProvider, + meter, + deltaReader, + cumulativeReader, + }; +} + +interface ValidateMetricData { + resource?: Resource; + instrumentationLibrary?: InstrumentationLibrary; + instrumentDescriptor?: InstrumentDescriptor; + pointDataType?: PointDataType, + pointData?: Partial>>[]; +} + +async function validateExport(reader: MetricReader, expected: ValidateMetricData) { + const metricData = await reader.collect(); + const metric = metricData[0]; + + assertMetricData( + metric, + expected.pointDataType, + expected.instrumentDescriptor ?? null, + expected.instrumentationLibrary, + expected.resource + ); + + if (expected.pointData == null) { + return; + } + assert.strictEqual(metric.pointData.length, expected.pointData.length); + + for (let idx = 0; idx < metric.pointData.length; idx++) { + const expectedPointData = expected.pointData[idx]; + assertPointData( + metric.pointData[idx], + expectedPointData.attributes ?? {}, + expectedPointData.point as any, + expectedPointData.startTime, + expectedPointData.endTime + ); + } +} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/PeriodicExportingMetricReader.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/export/PeriodicExportingMetricReader.test.ts index 4950f1cadc9..586dd379b75 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/PeriodicExportingMetricReader.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/export/PeriodicExportingMetricReader.test.ts @@ -16,29 +16,45 @@ import { PeriodicExportingMetricReader } from '../../src/export/PeriodicExportingMetricReader'; import { AggregationTemporality } from '../../src/export/AggregationTemporality'; -import { MetricExporter } from '../../src'; +import { PushMetricExporter } from '../../src'; import { MetricData } from '../../src/export/MetricData'; import * as assert from 'assert'; import * as sinon from 'sinon'; import { MetricProducer } from '../../src/export/MetricProducer'; import { TimeoutError } from '../../src/utils'; +import { ExportResult, ExportResultCode } from '@opentelemetry/core'; import { assertRejects } from '../test-utils'; const MAX_32_BIT_INT = 2 ** 31 - 1; -class TestMetricExporter extends MetricExporter { +class TestMetricExporter implements PushMetricExporter { public exportTime = 0; public forceFlushTime = 0; public throwException = false; + public failureResult = false; private _batches: MetricData[][] = []; + private _shutdown: boolean = false; - async export(batch: MetricData[]): Promise { + export(batch: MetricData[], resultCallback: (result: ExportResult) => void): void { this._batches.push(batch); if (this.throwException) { throw new Error('Error during export'); } - await new Promise(resolve => setTimeout(resolve, this.exportTime)); + setTimeout(() => { + if (this.failureResult) { + resultCallback({code: ExportResultCode.FAILED, error: new Error('some error') }); + } else { + resultCallback({code: ExportResultCode.SUCCESS }); + } + }, this.exportTime); + } + + async shutdown(): Promise { + if (this._shutdown) return; + const flushPromise = this.forceFlush(); + this._shutdown = true; + await flushPromise; } async forceFlush(): Promise { @@ -176,6 +192,24 @@ describe('PeriodicExportingMetricReader', () => { await reader.shutdown(); }); + it('should keep running on export failure', async () => { + const exporter = new TestMetricExporter(); + exporter.failureResult = true; + const reader = new PeriodicExportingMetricReader({ + exporter: exporter, + exportIntervalMillis: 30, + exportTimeoutMillis: 20 + }); + + reader.setMetricProducer(new TestMetricProducer()); + + const result = await exporter.waitForNumberOfExports(2); + assert.deepStrictEqual(result, [[], []]); + + exporter.failureResult = false; + await reader.shutdown(); + }); + it('should keep exporting on export timeouts', async () => { const exporter = new TestMetricExporter(); // set time longer than timeout. diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts index ea263bdd25a..122bdfd881c 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/state/MetricCollector.test.ts @@ -19,18 +19,22 @@ import * as sinon from 'sinon'; import { MeterProvider } from '../../src'; import { AggregationTemporality } from '../../src/export/AggregationTemporality'; import { MetricData, PointDataType } from '../../src/export/MetricData'; -import { MetricExporter } from '../../src/export/MetricExporter'; +import { PushMetricExporter } from '../../src/export/MetricExporter'; import { MeterProviderSharedState } from '../../src/state/MeterProviderSharedState'; import { MetricCollector } from '../../src/state/MetricCollector'; import { defaultInstrumentationLibrary, defaultResource, assertMetricData, assertPointData } from '../util'; import { TestMetricReader } from '../export/TestMetricReader'; +import { ExportResult, ExportResultCode } from '@opentelemetry/core'; -class TestMetricExporter extends MetricExporter { +class TestMetricExporter implements PushMetricExporter { metricDataList: MetricData[] = []; - async export(batch: MetricData[]): Promise { + async export(batch: MetricData[], resultCallback: (result: ExportResult) => void): Promise { this.metricDataList.push(...batch); + resultCallback({code: ExportResultCode.SUCCESS}); } + async shutdown(): Promise {} + async forceFlush(): Promise {} getPreferredAggregationTemporality(): AggregationTemporality { @@ -63,7 +67,8 @@ describe('MetricCollector', () => { }); describe('collect', () => { - function setupInstruments(exporter: MetricExporter) { + + function setupInstruments(exporter: PushMetricExporter) { const meterProvider = new MeterProvider({ resource: defaultResource }); const reader = new TestMetricReader(exporter.getPreferredAggregationTemporality()); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts index 917277e4363..b50f6ac928a 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/util.ts @@ -56,14 +56,20 @@ export const sleep = (time: number) => export function assertMetricData( actual: unknown, pointDataType?: PointDataType, - instrumentDescriptor: Partial = defaultInstrumentDescriptor, - instrumentationLibrary: Partial = defaultInstrumentationLibrary, - resource: Resource = defaultResource, + instrumentDescriptor: Partial | null = defaultInstrumentDescriptor, + instrumentationLibrary: Partial | null = defaultInstrumentationLibrary, + resource: Resource | null = defaultResource, ): asserts actual is MetricData { const it = actual as MetricData; - assert.deepStrictEqual(it.resource, resource); - assertPartialDeepStrictEqual(it.instrumentDescriptor, instrumentDescriptor); - assertPartialDeepStrictEqual(it.instrumentationLibrary, instrumentationLibrary); + if (resource != null) { + assert.deepStrictEqual(it.resource, resource); + } + if (instrumentDescriptor != null) { + assertPartialDeepStrictEqual(it.instrumentDescriptor, instrumentDescriptor); + } + if (instrumentationLibrary != null) { + assertPartialDeepStrictEqual(it.instrumentationLibrary, instrumentationLibrary); + } if (isNotNullish(pointDataType)) { assert.strictEqual(it.pointDataType, pointDataType); } else { @@ -114,6 +120,6 @@ export function assertPartialDeepStrictEqual(actual: unknown, expected: T, me } const ownNames = Object.getOwnPropertyNames(expected); for (const ownName of ownNames) { - assert.deepStrictEqual((actual as any)[ownName], (expected as any)[ownName], message); + assert.deepStrictEqual((actual as any)[ownName], (expected as any)[ownName], `${ownName} not equals: ${message ?? ''}`); } } diff --git a/package.json b/package.json index daf2842f00e..bc184dd967f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "husky": "4.3.8", "lerna": "3.22.1", "lerna-changelog": "1.0.1", - "linkinator": "2.16.2", + "linkinator": "3.0.3", "markdownlint-cli": "0.29.0", "semver": "7.3.5", "typedoc": "0.22.10", diff --git a/packages/exporter-trace-otlp-grpc/README.md b/packages/exporter-trace-otlp-grpc/README.md index f54acbe43bd..85e7b66316d 100644 --- a/packages/exporter-trace-otlp-grpc/README.md +++ b/packages/exporter-trace-otlp-grpc/README.md @@ -26,8 +26,8 @@ const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/sdk const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); const collectorOptions = { - // url is optional and can be omitted - default is grpc://localhost:4317 - url: 'grpc://:', + // url is optional and can be omitted - default is localhost:4317 + url: ':', }; const provider = new BasicTracerProvider(); @@ -50,8 +50,8 @@ const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/sdk const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); const collectorOptions = { - // url is optional and can be omitted - default is grpc://localhost:4317 - url: 'grpc://:', + // url is optional and can be omitted - default is localhost:4317 + url: ':', credentials: grpc.credentials.createSsl(), }; @@ -90,8 +90,8 @@ const metadata = new grpc.Metadata(); metadata.set('k', 'v'); const collectorOptions = { - // url is optional and can be omitted - default is grpc://localhost:4317 - url: 'grpc://:', + // url is optional and can be omitted - default is localhost:4317 + url: ':', metadata, // // an optional grpc.Metadata object to be sent with each request }; diff --git a/packages/exporter-trace-otlp-http/package.json b/packages/exporter-trace-otlp-http/package.json index 5ae0e36f194..166db5a4710 100644 --- a/packages/exporter-trace-otlp-http/package.json +++ b/packages/exporter-trace-otlp-http/package.json @@ -70,7 +70,7 @@ "codecov": "3.8.3", "cpx": "1.5.0", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-context-zone-peer-dep/package.json b/packages/opentelemetry-context-zone-peer-dep/package.json index fb19b26e6c6..7f108baa38e 100644 --- a/packages/opentelemetry-context-zone-peer-dep/package.json +++ b/packages/opentelemetry-context-zone-peer-dep/package.json @@ -61,7 +61,7 @@ "babel-loader": "8.2.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-context-zone/package.json b/packages/opentelemetry-context-zone/package.json index 1f519f81abb..3425a2ba922 100644 --- a/packages/opentelemetry-context-zone/package.json +++ b/packages/opentelemetry-context-zone/package.json @@ -55,7 +55,7 @@ "@types/webpack-env": "1.16.3", "babel-loader": "8.2.3", "codecov": "3.8.3", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-mocha": "2.0.1", "karma-spec-reporter": "0.0.32", diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index 34dd8ccaa60..303f4adaaad 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -69,7 +69,7 @@ "@types/webpack-env": "1.16.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-core/src/baggage/utils.ts b/packages/opentelemetry-core/src/baggage/utils.ts index 6292866cab9..f75fe0cfc9d 100644 --- a/packages/opentelemetry-core/src/baggage/utils.ts +++ b/packages/opentelemetry-core/src/baggage/utils.ts @@ -32,12 +32,17 @@ export function serializeKeyPairs(keyPairs: string[]): string { } export function getKeyPairs(baggage: Baggage): string[] { - return baggage - .getAllEntries() - .map( - ([key, value]) => - `${encodeURIComponent(key)}=${encodeURIComponent(value.value)}` - ); + return baggage.getAllEntries().map(([key, value]) => { + let entry = `${encodeURIComponent(key)}=${encodeURIComponent(value.value)}`; + + // include opaque metadata if provided + // NOTE: we intentionally don't URI-encode the metadata - that responsibility falls on the metadata implementation + if (value.metadata !== undefined) { + entry += BAGGAGE_PROPERTIES_SEPARATOR + value.metadata.toString(); + } + + return entry; + }); } export function parsePairKeyValue(entry: string): ParsedBaggageKeyValue | undefined { diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index 7df0be698cc..e3bcfd86234 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -34,6 +34,7 @@ const ENVIRONMENT_NUMBERS_KEYS = [ 'OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT', 'OTEL_SPAN_EVENT_COUNT_LIMIT', 'OTEL_SPAN_LINK_COUNT_LIMIT', + 'OTEL_EXPORTER_JAEGER_AGENT_PORT', ] as const; type ENVIRONMENT_NUMBERS = { @@ -109,6 +110,7 @@ export const DEFAULT_ENVIRONMENT: Required = { OTEL_BSP_MAX_QUEUE_SIZE: 2048, OTEL_BSP_SCHEDULE_DELAY: 5000, OTEL_EXPORTER_JAEGER_AGENT_HOST: '', + OTEL_EXPORTER_JAEGER_AGENT_PORT: 6832, OTEL_EXPORTER_JAEGER_ENDPOINT: '', OTEL_EXPORTER_JAEGER_PASSWORD: '', OTEL_EXPORTER_JAEGER_USER: '', diff --git a/packages/opentelemetry-core/test/baggage/W3CBaggagePropagator.test.ts b/packages/opentelemetry-core/test/baggage/W3CBaggagePropagator.test.ts index d00ce0c4b49..38de6680878 100644 --- a/packages/opentelemetry-core/test/baggage/W3CBaggagePropagator.test.ts +++ b/packages/opentelemetry-core/test/baggage/W3CBaggagePropagator.test.ts @@ -20,6 +20,7 @@ import { defaultTextMapGetter, defaultTextMapSetter, propagation, + baggageEntryMetadataFromString } from '@opentelemetry/api'; import { ROOT_CONTEXT } from '@opentelemetry/api'; import * as assert from 'assert'; @@ -39,8 +40,9 @@ describe('W3CBaggagePropagator', () => { it('should set baggage header', () => { const baggage = propagation.createBaggage({ key1: { value: 'd4cda95b652f4a1592b449d5929fda1b' }, - key3: { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }, 'with/slash': { value: 'with spaces' }, + key3: { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }, + key4: { value: 'foo', metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue') } }); httpBaggagePropagator.inject( @@ -50,7 +52,7 @@ describe('W3CBaggagePropagator', () => { ); assert.deepStrictEqual( carrier[BAGGAGE_HEADER], - 'key1=d4cda95b652f4a1592b449d5929fda1b,key3=c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a,with%2Fslash=with%20spaces' + 'key1=d4cda95b652f4a1592b449d5929fda1b,with%2Fslash=with%20spaces,key3=c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a,key4=foo;key4prop1=value1;key4prop2=value2;key4prop3WithNoValue' ); }); diff --git a/packages/opentelemetry-core/test/utils/environment.test.ts b/packages/opentelemetry-core/test/utils/environment.test.ts index 4ab35186457..e336eeeca56 100644 --- a/packages/opentelemetry-core/test/utils/environment.test.ts +++ b/packages/opentelemetry-core/test/utils/environment.test.ts @@ -78,6 +78,7 @@ describe('environment', () => { OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 40, OTEL_BSP_SCHEDULE_DELAY: 50, OTEL_EXPORTER_JAEGER_AGENT_HOST: 'host.domain.com', + OTEL_EXPORTER_JAEGER_AGENT_PORT: 1234, OTEL_EXPORTER_JAEGER_ENDPOINT: 'https://example.com/endpoint', OTEL_EXPORTER_JAEGER_PASSWORD: 'secret', OTEL_EXPORTER_JAEGER_USER: 'whoami', @@ -112,6 +113,7 @@ describe('environment', () => { env.OTEL_EXPORTER_JAEGER_AGENT_HOST, 'host.domain.com' ); + assert.strictEqual(env.OTEL_EXPORTER_JAEGER_AGENT_PORT, 1234); assert.strictEqual( env.ECS_CONTAINER_METADATA_URI_V4, 'https://ecs.uri/v4' diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index ec9634793e1..0ba979db046 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -53,6 +53,7 @@ export class JaegerExporter implements SpanExporter { localConfig.password = localConfig.password || env.OTEL_EXPORTER_JAEGER_PASSWORD; localConfig.host = localConfig.host || env.OTEL_EXPORTER_JAEGER_AGENT_HOST; + localConfig.port = localConfig.port || env.OTEL_EXPORTER_JAEGER_AGENT_PORT; this._localConfig = localConfig; diff --git a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts index 6193796268b..738f9b9ce7e 100644 --- a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts +++ b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts @@ -116,8 +116,9 @@ describe('JaegerExporter', () => { assert.strictEqual(sender._host, 'localhost'); }); - it('should respect jaeger host env variable', () => { + it('should respect jaeger host and port env variable', () => { process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST = 'env-set-host'; + process.env.OTEL_EXPORTER_JAEGER_AGENT_PORT = '1234'; const exporter = new JaegerExporter(); const sender = exporter['_getSender']({ tags: [{ @@ -126,12 +127,15 @@ describe('JaegerExporter', () => { }] } as any); assert.strictEqual(sender._host, 'env-set-host'); + assert.strictEqual(sender._port, 1234); }); - it('should prioritize host option over env variable', () => { + it('should prioritize host and port option over env variable', () => { process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST = 'env-set-host'; + process.env.OTEL_EXPORTER_JAEGER_AGENT_PORT = '1234'; const exporter = new JaegerExporter({ host: 'option-set-host', + port: 5678 }); const sender = exporter['_getSender']({ tags: [{ @@ -140,6 +144,7 @@ describe('JaegerExporter', () => { }] } as any); assert.strictEqual(sender._host, 'option-set-host'); + assert.strictEqual(sender._port, 5678); }); it('should construct an exporter with flushTimeout', () => { diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json index 952e54e22b9..0ee6a22fd9b 100644 --- a/packages/opentelemetry-exporter-zipkin/package.json +++ b/packages/opentelemetry-exporter-zipkin/package.json @@ -67,7 +67,7 @@ "babel-loader": "8.2.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-propagator-jaeger/package.json b/packages/opentelemetry-propagator-jaeger/package.json index 90ea96fb052..fcac77437a9 100644 --- a/packages/opentelemetry-propagator-jaeger/package.json +++ b/packages/opentelemetry-propagator-jaeger/package.json @@ -56,7 +56,7 @@ "@types/webpack-env": "1.16.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index d1270dcfe8e..7c7d3c48084 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -63,7 +63,7 @@ "@types/sinon": "10.0.6", "@types/webpack-env": "1.16.3", "codecov": "3.8.3", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-sdk-trace-base/package.json b/packages/opentelemetry-sdk-trace-base/package.json index 0491168d004..c5e3ca7ed20 100644 --- a/packages/opentelemetry-sdk-trace-base/package.json +++ b/packages/opentelemetry-sdk-trace-base/package.json @@ -67,7 +67,7 @@ "@types/webpack-env": "1.16.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-mocha": "2.0.1", diff --git a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts index 9960243cba2..5d85373a461 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts @@ -71,19 +71,24 @@ export class Tracer implements api.Tracer { return api.trace.wrapSpanContext(api.INVALID_SPAN_CONTEXT); } - const parentContext = getParent(options, context); + // remove span from context in case a root span is requested via options + if (options.root) { + context = api.trace.deleteSpan(context); + } + + const parentSpanContext = api.trace.getSpanContext(context); const spanId = this._idGenerator.generateSpanId(); let traceId; let traceState; let parentSpanId; - if (!parentContext || !api.trace.isSpanContextValid(parentContext)) { + if (!parentSpanContext || !api.trace.isSpanContextValid(parentSpanContext)) { // New root span. traceId = this._idGenerator.generateTraceId(); } else { // New child span. - traceId = parentContext.traceId; - traceState = parentContext.traceState; - parentSpanId = parentContext.spanId; + traceId = parentSpanContext.traceId; + traceState = parentSpanContext.traceState; + parentSpanId = parentSpanContext.spanId; } const spanKind = options.kind ?? api.SpanKind.INTERNAL; @@ -91,9 +96,7 @@ export class Tracer implements api.Tracer { const attributes = sanitizeAttributes(options.attributes); // make sampling decision const samplingResult = this._sampler.shouldSample( - options.root - ? api.trace.setSpanContext(context, api.INVALID_SPAN_CONTEXT) - : context, + context, traceId, name, spanKind, @@ -228,18 +231,3 @@ export class Tracer implements api.Tracer { return this._tracerProvider.getActiveSpanProcessor(); } } - -/** - * Get the parent to assign to a started span. If options.parent is null, - * do not assign a parent. - * - * @param options span options - * @param context context to check for parent - */ -function getParent( - options: api.SpanOptions, - context: api.Context -): api.SpanContext | undefined { - if (options.root) return undefined; - return api.trace.getSpanContext(context); -} diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 1c32c83d894..e6417d84afc 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { context, TraceFlags } from '@opentelemetry/api'; +import { context, Context, TraceFlags } from '@opentelemetry/api'; import { BindOnceFuture, ExportResultCode, @@ -73,7 +73,7 @@ export abstract class BatchSpanProcessorBase implements } // does nothing. - onStart(_span: Span): void {} + onStart(_span: Span, _parentContext: Context): void {} onEnd(span: ReadableSpan): void { if (this._shutdownOnce.isCalled) { diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index c87b8648259..c775bdf6d4c 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { context, TraceFlags } from '@opentelemetry/api'; +import { context, Context, TraceFlags } from '@opentelemetry/api'; import { ExportResultCode, globalErrorHandler, @@ -45,7 +45,7 @@ export class SimpleSpanProcessor implements SpanProcessor { } // does nothing. - onStart(_span: Span): void {} + onStart(_span: Span, _parentContext: Context): void {} onEnd(span: ReadableSpan): void { if (this._shutdownOnce.isCalled) { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts index 893f5219c66..2962d8d7e6e 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/Tracer.test.ts @@ -21,6 +21,7 @@ import { Sampler, SamplingDecision, SpanContext, + SpanKind, trace, TraceFlags } from '@opentelemetry/api'; @@ -32,7 +33,7 @@ import { suppressTracing } from '@opentelemetry/core'; import * as assert from 'assert'; -import { BasicTracerProvider, Span, Tracer } from '../../src'; +import { BasicTracerProvider, Span, SpanProcessor, Tracer } from '../../src'; import { TestStackContextManager } from './export/TestStackContextManager'; import * as sinon from 'sinon'; @@ -53,6 +54,17 @@ describe('Tracer', () => { } } + class DummySpanProcessor implements SpanProcessor { + forceFlush () { + return Promise.resolve(); + } + onStart() {} + onEnd() {} + shutdown() { + return Promise.resolve(); + } + } + beforeEach(() => { const contextManager = new TestStackContextManager().enable(); context.setGlobalContextManager(contextManager); @@ -189,6 +201,54 @@ describe('Tracer', () => { assert.strictEqual((span as Span).parentSpanId, undefined); }); + it('should pass the same context to sampler and spanprocessor', () => { + const parent: SpanContext = { + traceId: '00112233445566778899001122334455', + spanId: '0011223344556677', + traceFlags: TraceFlags.SAMPLED, + }; + const context = trace.setSpanContext(ROOT_CONTEXT, parent); + + const sp: SpanProcessor = new DummySpanProcessor(); + const onStartSpy = sinon.spy(sp, 'onStart'); + const tp = new BasicTracerProvider(); + tp.addSpanProcessor(sp); + + const sampler: Sampler = new AlwaysOnSampler(); + const shouldSampleSpy = sinon.spy(sampler, 'shouldSample'); + const tracer = new Tracer({ name: 'default' }, { sampler }, tp); + const span = tracer.startSpan('a', {}, context) as Span; + assert.strictEqual(span.parentSpanId, parent.spanId); + sinon.assert.calledOnceWithExactly(shouldSampleSpy, context, parent.traceId, 'a', SpanKind.INTERNAL, {}, []); + sinon.assert.calledOnceWithExactly(onStartSpy, span, context); + }); + + it('should pass the same context to sampler and spanprocessor if options.root is true', () => { + const parent: SpanContext = { + traceId: '00112233445566778899001122334455', + spanId: '0011223344556677', + traceFlags: TraceFlags.SAMPLED, + }; + const context = trace.setSpanContext(ROOT_CONTEXT, parent); + + const sp: SpanProcessor = new DummySpanProcessor(); + const onStartSpy = sinon.spy(sp, 'onStart'); + const tp = new BasicTracerProvider(); + tp.addSpanProcessor(sp); + + const sampler: Sampler = new AlwaysOnSampler(); + const shouldSampleSpy = sinon.spy(sampler, 'shouldSample'); + const tracer = new Tracer({ name: 'default' }, { sampler }, tp); + const span = tracer.startSpan('a', { root: true }, context) as Span; + assert.strictEqual(span.parentSpanId, undefined); + sinon.assert.calledOnce(shouldSampleSpy); + sinon.assert.calledOnce(onStartSpy); + const samplerContext = shouldSampleSpy.firstCall.args[0]; + const processorContext = onStartSpy.firstCall.args[1]; + assert.strictEqual(samplerContext, processorContext); + assert.strictEqual(getSpan(samplerContext), undefined); + }); + it('should sample a trace when OTEL_TRACES_SAMPLER_ARG is unset', () => { envSource.OTEL_TRACES_SAMPLER = 'traceidratio'; envSource.OTEL_TRACES_SAMPLER_ARG = ''; diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts index 42a7aa3fd5f..817b5bc3763 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { diag } from '@opentelemetry/api'; +import { diag, ROOT_CONTEXT } from '@opentelemetry/api'; import { AlwaysOnSampler, ExportResultCode, @@ -131,14 +131,14 @@ describe('BatchSpanProcessorBase', () => { const span = createSampledSpan(`${name}_0`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); assert.strictEqual(processor['_finishedSpans'].length, 1); await processor.forceFlush(); assert.strictEqual(exporter.getFinishedSpans().length, 1); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); assert.strictEqual(processor['_finishedSpans'].length, 1); @@ -147,7 +147,7 @@ describe('BatchSpanProcessorBase', () => { assert.strictEqual(spy.args.length, 2); assert.strictEqual(exporter.getFinishedSpans().length, 0); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); assert.strictEqual(spy.args.length, 2); assert.strictEqual(processor['_finishedSpans'].length, 0); @@ -160,7 +160,7 @@ describe('BatchSpanProcessorBase', () => { const span = createUnsampledSpan(`${name}_0`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); await processor.forceFlush(); @@ -174,14 +174,14 @@ describe('BatchSpanProcessorBase', () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); assert.strictEqual(exporter.getFinishedSpans().length, 0); processor.onEnd(span); assert.strictEqual(exporter.getFinishedSpans().length, 0); } const span = createSampledSpan(`${name}_6`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); setTimeout(async () => { @@ -199,7 +199,7 @@ describe('BatchSpanProcessorBase', () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); assert.strictEqual(exporter.getFinishedSpans().length, 0); } @@ -218,7 +218,7 @@ describe('BatchSpanProcessorBase', () => { const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); } assert.strictEqual(exporter.getFinishedSpans().length, 0); @@ -238,7 +238,7 @@ describe('BatchSpanProcessorBase', () => { // start but do not end spans for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) { const span = tracer.startSpan('spanName'); - processor.onStart(span as Span); + processor.onStart(span as Span, ROOT_CONTEXT); } setTimeout(() => { @@ -266,11 +266,11 @@ describe('BatchSpanProcessorBase', () => { const totalSpans = defaultBufferConfig.maxExportBatchSize * 2; for (let i = 0; i < totalSpans; i++) { const span = createSampledSpan(`${name}_${i}`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); } const span = createSampledSpan(`${name}_last`); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); clock.tick(defaultBufferConfig.scheduledDelayMillis + 10); @@ -326,7 +326,7 @@ describe('BatchSpanProcessorBase', () => { it('should call an async callback when flushing is complete', done => { const span = createSampledSpan('test'); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); processor.forceFlush().then(() => { assert.strictEqual(exporter.getFinishedSpans().length, 1); @@ -343,7 +343,7 @@ describe('BatchSpanProcessorBase', () => { }, 0); }); const span = createSampledSpan('test'); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); processor.shutdown().then(() => { @@ -367,7 +367,7 @@ describe('BatchSpanProcessorBase', () => { for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) { const span = createSampledSpan('test'); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); } @@ -402,7 +402,7 @@ describe('BatchSpanProcessorBase', () => { const processor = new BatchSpanProcessor(testTracingExporter); const span = createSampledSpan('test'); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); processor.forceFlush().then(() => { @@ -429,7 +429,7 @@ describe('BatchSpanProcessorBase', () => { it('should drop spans', () => { const span = createSampledSpan('test'); for (let i = 0, j = 20; i < j; i++) { - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); } assert.equal(processor['_finishedSpans'].length, 6); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts index 52fd2979ce5..b127502ad8a 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts @@ -68,7 +68,7 @@ describe('SimpleSpanProcessor', () => { spanContext, SpanKind.CLIENT ); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); assert.strictEqual(exporter.getFinishedSpans().length, 0); processor.onEnd(span); @@ -92,7 +92,7 @@ describe('SimpleSpanProcessor', () => { spanContext, SpanKind.CLIENT ); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); assert.strictEqual(exporter.getFinishedSpans().length, 0); processor.onEnd(span); @@ -117,7 +117,7 @@ describe('SimpleSpanProcessor', () => { spanContext, SpanKind.CLIENT ); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); sinon.stub(exporter, 'export').callsFake((_, callback) => { setTimeout(() => { @@ -195,7 +195,7 @@ describe('SimpleSpanProcessor', () => { SpanKind.CLIENT ); - processor.onStart(span); + processor.onStart(span, ROOT_CONTEXT); processor.onEnd(span); const exporterCreatedSpans = testTracingExporter.getExporterCreatedSpans(); diff --git a/packages/opentelemetry-sdk-trace-web/package.json b/packages/opentelemetry-sdk-trace-web/package.json index cadc33a81eb..aac861079ed 100644 --- a/packages/opentelemetry-sdk-trace-web/package.json +++ b/packages/opentelemetry-sdk-trace-web/package.json @@ -66,7 +66,7 @@ "babel-loader": "8.2.3", "codecov": "3.8.3", "istanbul-instrumenter-loader": "3.0.1", - "karma": "6.3.8", + "karma": "6.3.14", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-jquery": "0.2.4", diff --git a/packages/template/package.json b/packages/template/package.json index 956ba645f3f..1dfeb7dc87a 100644 --- a/packages/template/package.json +++ b/packages/template/package.json @@ -90,6 +90,6 @@ "karma-mocha": "2.0.1", "karma-spec-reporter": "0.0.32", "karma-webpack": "4.0.2", - "webpack": "4.44.2" + "webpack": "4.46.0" } } diff --git a/selenium-tests/package.json b/selenium-tests/package.json index be317b7b57d..6a0bc718c61 100644 --- a/selenium-tests/package.json +++ b/selenium-tests/package.json @@ -40,14 +40,14 @@ "babel-loader": "8.2.3", "babel-polyfill": "6.26.0", "browserstack-local": "1.4.8", - "chromedriver": "97.0.4", + "chromedriver": "98.0.0", "dotenv": "16.0.0", "fast-safe-stringify": "2.1.1", "geckodriver": "2.0.4", "nightwatch": "1.7.12", "selenium-server": "3.141.59", - "terser-webpack-plugin": "5.2.5", - "webpack": "5.64.1", + "terser-webpack-plugin": "4.2.3", + "webpack": "4.46.0", "webpack-cli": "4.9.1", "webpack-dev-server": "4.5.0", "webpack-merge": "5.8.0"