diff --git a/CHANGELOG.md b/CHANGELOG.md index e447399eeb..5dda81b650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. ### :rocket: (Enhancement) * feat(sdk-trace): re-export sdk-trace-base in sdk-trace-node and web [#3319](https://github.com/open-telemetry/opentelemetry-js/pull/3319) @legendecas +* feat: enable tree shaking [#3329](https://github.com/open-telemetry/opentelemetry-js/pull/3329) @pkanal ### :bug: (Bug Fix) @@ -25,6 +26,11 @@ All notable changes to this project will be documented in this file. * ci: run browser tests without circle [#3328](https://github.com/open-telemetry/opentelemetry-js/pull/3328) @dyladan +## Metrics API 1.0.0 + +Metrics API is now stable and generally available. +There are no changes between 1.0.0 and the previous 0.33.0 version. + ## 1.7.0 ### :bug: (Bug Fix) diff --git a/experimental/packages/opentelemetry-api-metrics/.eslintignore b/api-metrics/.eslintignore similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/.eslintignore rename to api-metrics/.eslintignore diff --git a/experimental/packages/opentelemetry-api-metrics/.eslintrc.js b/api-metrics/.eslintrc.js similarity index 74% rename from experimental/packages/opentelemetry-api-metrics/.eslintrc.js rename to api-metrics/.eslintrc.js index b9004d2025..7d5c10c7f9 100644 --- a/experimental/packages/opentelemetry-api-metrics/.eslintrc.js +++ b/api-metrics/.eslintrc.js @@ -4,5 +4,5 @@ module.exports = { "commonjs": true, "shared-node-browser": true }, - ...require('../../../eslint.config.js') + ...require('../eslint.config.js') } diff --git a/experimental/packages/opentelemetry-api-metrics/LICENSE b/api-metrics/LICENSE similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/LICENSE rename to api-metrics/LICENSE diff --git a/experimental/packages/opentelemetry-api-metrics/README.md b/api-metrics/README.md similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/README.md rename to api-metrics/README.md diff --git a/experimental/packages/opentelemetry-api-metrics/karma.conf.js b/api-metrics/karma.conf.js similarity index 86% rename from experimental/packages/opentelemetry-api-metrics/karma.conf.js rename to api-metrics/karma.conf.js index 6174839d65..29b7b9bc5a 100644 --- a/experimental/packages/opentelemetry-api-metrics/karma.conf.js +++ b/api-metrics/karma.conf.js @@ -14,8 +14,8 @@ * limitations under the License. */ -const karmaWebpackConfig = require('../../../karma.webpack'); -const karmaBaseConfig = require('../../../karma.base'); +const karmaWebpackConfig = require('../karma.webpack'); +const karmaBaseConfig = require('../karma.base'); module.exports = (config) => { config.set(Object.assign({}, karmaBaseConfig, { diff --git a/experimental/packages/opentelemetry-api-metrics/package.json b/api-metrics/package.json similarity index 90% rename from experimental/packages/opentelemetry-api-metrics/package.json rename to api-metrics/package.json index 2bc05ba2d9..fc920cbb19 100644 --- a/experimental/packages/opentelemetry-api-metrics/package.json +++ b/api-metrics/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/api-metrics", - "version": "0.33.0", + "version": "1.0.0", "description": "Public metrics API for OpenTelemetry", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -19,15 +19,15 @@ "clean": "tsc --build --clean tsconfig.all.json", "test": "nyc ts-mocha -p tsconfig.json test/**/*.test.ts", "test:browser": "nyc karma start --single-run", - "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../../", - "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../../", + "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../", + "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../", "build": "npm run compile", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", - "version": "node ../../../scripts/version-update.js", + "version": "node ../scripts/version-update.js", "watch": "tsc --build --watch tsconfig.all.json", "precompile": "lerna run version --scope $(npm pkg get name) --include-dependencies", - "prewatch": "node ../../../scripts/version-update.js" + "prewatch": "node ../scripts/version-update.js" }, "keywords": [ "opentelemetry", @@ -82,5 +82,6 @@ "typescript": "4.4.4", "webpack": "4.46.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-api-metrics" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-api-metrics", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-api-metrics/src/NoopMeter.ts b/api-metrics/src/NoopMeter.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/NoopMeter.ts rename to api-metrics/src/NoopMeter.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/NoopMeterProvider.ts b/api-metrics/src/NoopMeterProvider.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/NoopMeterProvider.ts rename to api-metrics/src/NoopMeterProvider.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/api/metrics.ts b/api-metrics/src/api/metrics.ts similarity index 69% rename from experimental/packages/opentelemetry-api-metrics/src/api/metrics.ts rename to api-metrics/src/api/metrics.ts index 3e5fb6015a..b3f9bac6b6 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/api/metrics.ts +++ b/api-metrics/src/api/metrics.ts @@ -17,12 +17,7 @@ import { Meter, MeterOptions } from '../types/Meter'; import { MeterProvider } from '../types/MeterProvider'; import { NOOP_METER_PROVIDER } from '../NoopMeterProvider'; -import { - API_BACKWARDS_COMPATIBILITY_VERSION, - GLOBAL_METRICS_API_KEY, - makeGetter, - _global, -} from './global-utils'; +import { getGlobal, registerGlobal, unregisterGlobal } from '../internal/global-utils'; /** * Singleton object which represents the entry point to the OpenTelemetry Metrics API @@ -43,31 +38,18 @@ export class MetricsAPI { } /** - * Set the current global meter. Returns the initialized global meter provider. + * Set the current global meter provider. + * Returns true if the meter provider was successfully registered, else false. */ - public setGlobalMeterProvider(provider: MeterProvider): MeterProvider { - if (_global[GLOBAL_METRICS_API_KEY]) { - // global meter provider has already been set - return this.getMeterProvider(); - } - - _global[GLOBAL_METRICS_API_KEY] = makeGetter( - API_BACKWARDS_COMPATIBILITY_VERSION, - provider, - NOOP_METER_PROVIDER - ); - - return provider; + public setGlobalMeterProvider(provider: MeterProvider): boolean { + return registerGlobal('metrics', provider); } /** * Returns the global meter provider. */ public getMeterProvider(): MeterProvider { - return ( - _global[GLOBAL_METRICS_API_KEY]?.(API_BACKWARDS_COMPATIBILITY_VERSION) ?? - NOOP_METER_PROVIDER - ); + return getGlobal('metrics') || NOOP_METER_PROVIDER; } /** @@ -79,6 +61,6 @@ export class MetricsAPI { /** Remove the global meter provider */ public disable(): void { - delete _global[GLOBAL_METRICS_API_KEY]; + unregisterGlobal('metrics'); } } diff --git a/experimental/packages/opentelemetry-api-metrics/src/index.ts b/api-metrics/src/index.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/index.ts rename to api-metrics/src/index.ts diff --git a/api-metrics/src/internal/global-utils.ts b/api-metrics/src/internal/global-utils.ts new file mode 100644 index 0000000000..f83c203398 --- /dev/null +++ b/api-metrics/src/internal/global-utils.ts @@ -0,0 +1,96 @@ +/* + * 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 { diag } from '@opentelemetry/api'; +import { _globalThis } from '../platform'; +import { MeterProvider } from '../types/MeterProvider'; +import { VERSION } from '../version'; +import { isCompatible } from './semver'; + +const major = VERSION.split('.')[0]; +const GLOBAL_OPENTELEMETRY_METRICS_API_KEY = Symbol.for( + `opentelemetry.js.api.metrics.${major}` +); + +const _global = _globalThis as OTelGlobal; + +export function registerGlobal( + type: Type, + instance: OTelGlobalAPI[Type], + allowOverride = false +): boolean { + const api = (_global[GLOBAL_OPENTELEMETRY_METRICS_API_KEY] = _global[ + GLOBAL_OPENTELEMETRY_METRICS_API_KEY + ] ?? { + version: VERSION, + }); + + if (!allowOverride && api[type]) { + // already registered an API of this type + const err = new Error( + `@opentelemetry/api: Attempted duplicate registration of API: ${type}` + ); + diag.error(err.stack || err.message); + return false; + } + + if (api.version !== VERSION) { + // All registered APIs must be of the same version exactly + const err = new Error( + '@opentelemetry/api: All API registration versions must match' + ); + diag.error(err.stack || err.message); + return false; + } + + api[type] = instance; + diag.debug( + `@opentelemetry/api: Registered a global for ${type} v${VERSION}.` + ); + + return true; +} + +export function getGlobal( + type: Type +): OTelGlobalAPI[Type] | undefined { + const globalVersion = _global[GLOBAL_OPENTELEMETRY_METRICS_API_KEY]?.version; + if (!globalVersion || !isCompatible(globalVersion)) { + return; + } + return _global[GLOBAL_OPENTELEMETRY_METRICS_API_KEY]?.[type]; +} + +export function unregisterGlobal(type: keyof OTelGlobalAPI) { + diag.debug( + `@opentelemetry/api-metrics: Unregistering a global for ${type} v${VERSION}.` + ); + const api = _global[GLOBAL_OPENTELEMETRY_METRICS_API_KEY]; + + if (api) { + delete api[type]; + } +} + +type OTelGlobal = { + [GLOBAL_OPENTELEMETRY_METRICS_API_KEY]?: OTelGlobalAPI; +}; + +type OTelGlobalAPI = { + version: string; + + metrics?: MeterProvider; +}; diff --git a/api-metrics/src/internal/semver.ts b/api-metrics/src/internal/semver.ts new file mode 100644 index 0000000000..076f9b9b51 --- /dev/null +++ b/api-metrics/src/internal/semver.ts @@ -0,0 +1,140 @@ +/* + * 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 { VERSION } from '../version'; + +const re = /^(\d+)\.(\d+)\.(\d+)(-(.+))?$/; + +/** + * Create a function to test an API version to see if it is compatible with the provided ownVersion. + * + * The returned function has the following semantics: + * - Exact match is always compatible + * - Major versions must match exactly + * - 1.x package cannot use global 2.x package + * - 2.x package cannot use global 1.x package + * - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API + * - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects + * - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3 + * - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor + * - Patch and build tag differences are not considered at this time + * + * @param ownVersion version which should be checked against + */ +export function _makeCompatibilityCheck( + ownVersion: string +): (globalVersion: string) => boolean { + const acceptedVersions = new Set([ownVersion]); + const rejectedVersions = new Set(); + + const myVersionMatch = ownVersion.match(re); + if (!myVersionMatch) { + // we cannot guarantee compatibility so we always return noop + return () => false; + } + + const ownVersionParsed = { + major: +myVersionMatch[1], + minor: +myVersionMatch[2], + patch: +myVersionMatch[3], + prerelease: myVersionMatch[4], + }; + + // if ownVersion has a prerelease tag, versions must match exactly + if (ownVersionParsed.prerelease != null) { + return function isExactmatch(globalVersion: string): boolean { + return globalVersion === ownVersion; + }; + } + + function _reject(v: string) { + rejectedVersions.add(v); + return false; + } + + function _accept(v: string) { + acceptedVersions.add(v); + return true; + } + + return function isCompatible(globalVersion: string): boolean { + if (acceptedVersions.has(globalVersion)) { + return true; + } + + if (rejectedVersions.has(globalVersion)) { + return false; + } + + const globalVersionMatch = globalVersion.match(re); + if (!globalVersionMatch) { + // cannot parse other version + // we cannot guarantee compatibility so we always noop + return _reject(globalVersion); + } + + const globalVersionParsed = { + major: +globalVersionMatch[1], + minor: +globalVersionMatch[2], + patch: +globalVersionMatch[3], + prerelease: globalVersionMatch[4], + }; + + // if globalVersion has a prerelease tag, versions must match exactly + if (globalVersionParsed.prerelease != null) { + return _reject(globalVersion); + } + + // major versions must match + if (ownVersionParsed.major !== globalVersionParsed.major) { + return _reject(globalVersion); + } + + if (ownVersionParsed.major === 0) { + if ( + ownVersionParsed.minor === globalVersionParsed.minor && + ownVersionParsed.patch <= globalVersionParsed.patch + ) { + return _accept(globalVersion); + } + + return _reject(globalVersion); + } + + if (ownVersionParsed.minor <= globalVersionParsed.minor) { + return _accept(globalVersion); + } + + return _reject(globalVersion); + }; +} + +/** + * Test an API version to see if it is compatible with this API. + * + * - Exact match is always compatible + * - Major versions must match exactly + * - 1.x package cannot use global 2.x package + * - 2.x package cannot use global 1.x package + * - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API + * - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects + * - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3 + * - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor + * - Patch and build tag differences are not considered at this time + * + * @param version version of the API requesting an instance of the global API + */ +export const isCompatible = _makeCompatibilityCheck(VERSION); diff --git a/experimental/packages/opentelemetry-api-metrics/src/platform/browser/globalThis.ts b/api-metrics/src/platform/browser/globalThis.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/platform/browser/globalThis.ts rename to api-metrics/src/platform/browser/globalThis.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/platform/browser/index.ts b/api-metrics/src/platform/browser/index.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/platform/browser/index.ts rename to api-metrics/src/platform/browser/index.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/platform/index.ts b/api-metrics/src/platform/index.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/platform/index.ts rename to api-metrics/src/platform/index.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/platform/node/globalThis.ts b/api-metrics/src/platform/node/globalThis.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/platform/node/globalThis.ts rename to api-metrics/src/platform/node/globalThis.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/platform/node/index.ts b/api-metrics/src/platform/node/index.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/platform/node/index.ts rename to api-metrics/src/platform/node/index.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/Meter.ts b/api-metrics/src/types/Meter.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/types/Meter.ts rename to api-metrics/src/types/Meter.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/MeterProvider.ts b/api-metrics/src/types/MeterProvider.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/types/MeterProvider.ts rename to api-metrics/src/types/MeterProvider.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/Metric.ts b/api-metrics/src/types/Metric.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/types/Metric.ts rename to api-metrics/src/types/Metric.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/ObservableResult.ts b/api-metrics/src/types/ObservableResult.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/src/types/ObservableResult.ts rename to api-metrics/src/types/ObservableResult.ts diff --git a/experimental/packages/opentelemetry-api-metrics/test/api/api.test.ts b/api-metrics/test/api/api.test.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/test/api/api.test.ts rename to api-metrics/test/api/api.test.ts diff --git a/experimental/packages/opentelemetry-api-metrics/test/index-webpack.ts b/api-metrics/test/index-webpack.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/test/index-webpack.ts rename to api-metrics/test/index-webpack.ts diff --git a/experimental/packages/opentelemetry-api-metrics/test/api/global.test.ts b/api-metrics/test/internal/global.test.ts similarity index 50% rename from experimental/packages/opentelemetry-api-metrics/test/api/global.test.ts rename to api-metrics/test/internal/global.test.ts index c48949a3e5..e239728706 100644 --- a/experimental/packages/opentelemetry-api-metrics/test/api/global.test.ts +++ b/api-metrics/test/internal/global.test.ts @@ -15,8 +15,11 @@ */ import * as assert from 'assert'; -import { _global, GLOBAL_METRICS_API_KEY } from '../../src/api/global-utils'; +import { getGlobal } from '../../src/internal/global-utils'; +import { _globalThis } from '../../src/platform'; import { NoopMeterProvider } from '../../src/NoopMeterProvider'; +import sinon = require('sinon'); +import { diag } from '@opentelemetry/api'; const api1 = require('../../src') as typeof import('../../src'); @@ -26,18 +29,31 @@ for (const key of Object.keys(require.cache)) { } const api2 = require('../../src') as typeof import('../../src'); +// This will need to be changed manually on major version changes. +// It is intentionally not autogenerated to ensure the author of the change is aware of what they are doing. +const GLOBAL_METRICS_API_SYMBOL_KEY = 'opentelemetry.js.api.metrics.1'; + +const getMockLogger = () => ({ + verbose: sinon.spy(), + debug: sinon.spy(), + info: sinon.spy(), + warn: sinon.spy(), + error: sinon.spy(), +}); + describe('Global Utils', () => { // prove they are separate instances - assert.notStrictEqual(api1, api2); + assert.notEqual(api1, api2); // that return separate noop instances to start assert.notStrictEqual( api1.metrics.getMeterProvider(), - api2.metrics.getMeterProvider() + api2.metrics.getMeterProvider(), ); beforeEach(() => { api1.metrics.disable(); - api2.metrics.disable(); + // @ts-expect-error we are modifying internals for testing purposes here + delete _globalThis[Symbol.for(GLOBAL_METRICS_API_SYMBOL_KEY)]; }); it('should change the global meter provider', () => { @@ -57,20 +73,46 @@ describe('Global Utils', () => { }); it('should disable both if one is disabled', () => { - const original = api1.metrics.getMeterProvider(); + const manager = new NoopMeterProvider(); + api1.metrics.setGlobalMeterProvider(manager); - api1.metrics.setGlobalMeterProvider(new NoopMeterProvider()); - - assert.notStrictEqual(original, api1.metrics.getMeterProvider()); + assert.strictEqual(manager, api1.metrics.getMeterProvider()); api2.metrics.disable(); - assert.strictEqual(original, api1.metrics.getMeterProvider()); + assert.notStrictEqual(manager, api1.metrics.getMeterProvider()); }); it('should return the module NoOp implementation if the version is a mismatch', () => { - const original = api1.metrics.getMeterProvider(); + const newMeterProvider = new NoopMeterProvider(); + api1.metrics.setGlobalMeterProvider(newMeterProvider); + + // ensure new meter provider is returned + assert.strictEqual(api1.metrics.getMeterProvider(), newMeterProvider); + + const globalInstance = getGlobal('metrics'); + assert.ok(globalInstance); + // @ts-expect-error we are modifying internals for testing purposes here + _globalThis[Symbol.for(GLOBAL_METRICS_API_SYMBOL_KEY)].version = '0.0.1'; + + // ensure new meter provider is not returned because version above is incompatible + assert.notStrictEqual( + api1.metrics.getMeterProvider(), + newMeterProvider + ); + }); + + it('should log an error if there is a duplicate registration', () => { + const logger = getMockLogger(); + diag.setLogger(logger); + + api1.metrics.setGlobalMeterProvider(new NoopMeterProvider()); api1.metrics.setGlobalMeterProvider(new NoopMeterProvider()); - const afterSet = _global[GLOBAL_METRICS_API_KEY]!(-1); - assert.strictEqual(original, afterSet); + sinon.assert.calledOnce(logger.error); + assert.strictEqual(logger.error.firstCall.args.length, 1); + assert.ok( + logger.error.firstCall.args[0].startsWith( + 'Error: @opentelemetry/api: Attempted duplicate registration of API: metrics' + ) + ); }); }); diff --git a/api-metrics/test/internal/semver.test.ts b/api-metrics/test/internal/semver.test.ts new file mode 100644 index 0000000000..e9ac3ec727 --- /dev/null +++ b/api-metrics/test/internal/semver.test.ts @@ -0,0 +1,148 @@ +/* + * 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 { + isCompatible, + _makeCompatibilityCheck, +} from '../../src/internal/semver'; +import { VERSION } from '../../src/version'; + +describe('semver', () => { + it('should be compatible if versions are equal', () => { + assert.ok(isCompatible(VERSION)); + }); + + it('returns false if own version cannot be parsed', () => { + const check = _makeCompatibilityCheck('this is not semver'); + assert.ok(!check('1.0.0')); + }); + + it('incompatible if other version cannot be parsed', () => { + const check = _makeCompatibilityCheck('0.1.2'); + assert.ok(!check('this is not semver')); + }); + + describe('>= 1.0.0', () => { + const globalVersion = '5.5.5'; + const vers: [string, boolean][] = [ + // same major/minor run should be compatible + ['5.5.5', true], + ['5.5.6', true], + ['5.5.4', true], + + // prerelease version should not be compatible + ['5.5.5-rc.0', false], + + // if our version has a minor version increase, we may try to call methods which don't exist on the global + ['5.6.5', false], + ['5.6.6', false], + ['5.6.4', false], + + // if the global version is ahead of us by a minor revision, it has at least the methods we know about + ['5.4.5', true], + ['5.4.6', true], + ['5.4.4', true], + + // major version mismatch is always incompatible + ['6.5.5', false], + ['6.5.6', false], + ['6.5.4', false], + ['6.6.5', false], + ['6.6.6', false], + ['6.6.4', false], + ['6.4.5', false], + ['6.4.6', false], + ['6.4.4', false], + ['4.5.5', false], + ['4.5.6', false], + ['4.5.4', false], + ['4.6.5', false], + ['4.6.6', false], + ['4.6.4', false], + ['4.4.5', false], + ['4.4.6', false], + ['4.4.4', false], + ]; + + test(globalVersion, vers); + }); + + describe('< 1.0.0', () => { + const globalVersion = '0.5.5'; + const vers: [string, boolean][] = [ + // same minor/patch should be compatible + ['0.5.5', true], + + // prerelease version should not be compatible + ['0.5.5-rc.0', false], + + // if our version has a patch version increase, we may try to call methods which don't exist on the global + ['0.5.6', false], + + // if the global version is ahead of us by a patch revision, it has at least the methods we know about + ['0.5.4', true], + + // minor version mismatch is always incompatible + ['0.6.5', false], + ['0.6.6', false], + ['0.6.4', false], + ['0.4.5', false], + ['0.4.6', false], + ['0.4.4', false], + + // major version mismatch is always incompatible + ['1.5.5', false], + ['1.5.6', false], + ['1.5.4', false], + ['1.6.5', false], + ['1.6.6', false], + ['1.6.4', false], + ['1.4.5', false], + ['1.4.6', false], + ['1.4.4', false], + ]; + + test(globalVersion, vers); + }); + + describe('global version is prerelease', () => { + const globalVersion = '1.0.0-rc.3'; + const vers: [string, boolean][] = [ + // must match exactly + ['1.0.0', false], + ['1.0.0-rc.2', false], + ['1.0.0-rc.4', false], + + ['1.0.0-rc.3', true], + ]; + + test(globalVersion, vers); + }); +}); + +function test(globalVersion: string, vers: [string, boolean][]) { + describe(`global version is ${globalVersion}`, () => { + for (const [version, compatible] of vers) { + it(`API version ${version} ${ + compatible ? 'should' : 'should not' + } be able to access global`, () => { + const check = _makeCompatibilityCheck(version); + assert.strictEqual(check(globalVersion), compatible); + }); + } + }); +} diff --git a/api-metrics/test/internal/version.test.ts b/api-metrics/test/internal/version.test.ts new file mode 100644 index 0000000000..4d0e5f2542 --- /dev/null +++ b/api-metrics/test/internal/version.test.ts @@ -0,0 +1,30 @@ +/* + * 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 { VERSION } from '../../src/version'; + +describe('version', () => { + it('should have generated VERSION.ts', () => { + const pjson = require('../../package.json'); + assert.strictEqual(pjson.version, VERSION); + }); + + it('prerelease tag versions are banned', () => { + // see https://github.com/open-telemetry/opentelemetry-js-api/issues/74 + assert.ok(VERSION.match(/^\d+\.\d+\.\d+$/)); + }); +}); diff --git a/experimental/packages/opentelemetry-api-metrics/test/noop-implementations/noop-meter.test.ts b/api-metrics/test/noop-implementations/noop-meter.test.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/test/noop-implementations/noop-meter.test.ts rename to api-metrics/test/noop-implementations/noop-meter.test.ts diff --git a/experimental/packages/opentelemetry-api-metrics/test/types/Metric.test.ts b/api-metrics/test/types/Metric.test.ts similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/test/types/Metric.test.ts rename to api-metrics/test/types/Metric.test.ts diff --git a/experimental/packages/opentelemetry-api-metrics/tsconfig.all.json b/api-metrics/tsconfig.all.json similarity index 78% rename from experimental/packages/opentelemetry-api-metrics/tsconfig.all.json rename to api-metrics/tsconfig.all.json index 06c5491334..4aa747e89f 100644 --- a/experimental/packages/opentelemetry-api-metrics/tsconfig.all.json +++ b/api-metrics/tsconfig.all.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../tsconfig.base.json", "files": [], "references": [ { "path": "./tsconfig.json" }, diff --git a/experimental/packages/opentelemetry-api-metrics/tsconfig.docs.json b/api-metrics/tsconfig.docs.json similarity index 100% rename from experimental/packages/opentelemetry-api-metrics/tsconfig.docs.json rename to api-metrics/tsconfig.docs.json diff --git a/experimental/packages/opentelemetry-api-metrics/tsconfig.esm.json b/api-metrics/tsconfig.esm.json similarity index 78% rename from experimental/packages/opentelemetry-api-metrics/tsconfig.esm.json rename to api-metrics/tsconfig.esm.json index 379f547a46..50611a86af 100644 --- a/experimental/packages/opentelemetry-api-metrics/tsconfig.esm.json +++ b/api-metrics/tsconfig.esm.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.esm.json", + "extends": "../tsconfig.base.esm.json", "compilerOptions": { "rootDir": "src", "outDir": "build/esm", diff --git a/experimental/packages/opentelemetry-api-metrics/tsconfig.esnext.json b/api-metrics/tsconfig.esnext.json similarity index 78% rename from experimental/packages/opentelemetry-api-metrics/tsconfig.esnext.json rename to api-metrics/tsconfig.esnext.json index cb78dd6ff3..0e3427cbc1 100644 --- a/experimental/packages/opentelemetry-api-metrics/tsconfig.esnext.json +++ b/api-metrics/tsconfig.esnext.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.esnext.json", + "extends": "../tsconfig.base.esnext.json", "compilerOptions": { "rootDir": "src", "outDir": "build/esnext", diff --git a/experimental/packages/opentelemetry-api-metrics/tsconfig.json b/api-metrics/tsconfig.json similarity index 69% rename from experimental/packages/opentelemetry-api-metrics/tsconfig.json rename to api-metrics/tsconfig.json index e22548584a..e8b7e27e71 100644 --- a/experimental/packages/opentelemetry-api-metrics/tsconfig.json +++ b/api-metrics/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "rootDir": ".", "outDir": "build" @@ -10,7 +10,7 @@ ], "references": [ { - "path": "../../../api" + "path": "../api" } ] } diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 595add19a3..6d2de9fb60 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -6,10 +6,18 @@ All notable changes to experimental packages in this project will be documented ### :boom: Breaking Change +* Add semver check to metrics API [#3357](https://github.com/open-telemetry/opentelemetry-js/pull/3357) @dyladan + * Previously API versions were only considered compatible if the API was exactly the same + ### :rocket: (Enhancement) +* feat(metrics-sdk): Add tracing suppresing for Metrics Export [#3332](https://github.com/open-telemetry/opentelemetry-js/pull/3332) @hectorhdzg +* feat(instrumentation): implement `require-in-the-middle` singleton [#3161](https://github.com/open-telemetry/opentelemetry-js/pull/3161) @mhassan1 * feat(sdk-node): configure trace exporter with environment variables [#3143](https://github.com/open-telemetry/opentelemetry-js/pull/3143) @svetlanabrennan +* feat: enable tree shaking [#3329](https://github.com/open-telemetry/opentelemetry-js/pull/3329) @pkanal * feat(prometheus): serialize resource as target_info gauge [#3300](https://github.com/open-telemetry/opentelemetry-js/pull/3300) @pichlermarc +* deps: remove unused proto-loader dependencies and update grpc-js and proto-loader versions [#3337](https://github.com/open-telemetry/opentelemetry-js/pull/3337) @seemk +* feat(metrics-exporters): configure temporality via environment variable [#3305](https://github.com/open-telemetry/opentelemetry-js/pull/3305) @pichlermarc ### :bug: (Bug Fix) @@ -23,6 +31,9 @@ All notable changes to experimental packages in this project will be documented ### :house: (Internal) +* ci(instrumentation-http): remove got devDependency + [#3347](https://github.com/open-telemetry/opentelemetry-js/issues/3347) @dyladan + ## 0.33.0 ### :boom: Breaking Change diff --git a/experimental/packages/api-logs/package.json b/experimental/packages/api-logs/package.json index 44955363e6..fe0a0cb73e 100644 --- a/experimental/packages/api-logs/package.json +++ b/experimental/packages/api-logs/package.json @@ -83,5 +83,6 @@ "typescript": "4.4.4", "webpack": "4.46.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/api-logs" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/api-logs", + "sideEffects": false } diff --git a/experimental/packages/exporter-trace-otlp-grpc/package.json b/experimental/packages/exporter-trace-otlp-grpc/package.json index fb00a9d7b0..05ba15892c 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/package.json +++ b/experimental/packages/exporter-trace-otlp-grpc/package.json @@ -48,6 +48,7 @@ }, "devDependencies": { "@babel/core": "7.16.0", + "@grpc/proto-loader": "^0.7.3", "@opentelemetry/api": "^1.0.0", "@opentelemetry/otlp-exporter-base": "0.33.0", "@types/mocha": "10.0.0", @@ -67,13 +68,13 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@grpc/grpc-js": "^1.5.9", - "@grpc/proto-loader": "^0.6.9", + "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "1.7.0", "@opentelemetry/otlp-grpc-exporter-base": "0.33.0", "@opentelemetry/otlp-transformer": "0.33.0", "@opentelemetry/resources": "1.7.0", "@opentelemetry/sdk-trace-base": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-grpc" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-grpc", + "sideEffects": false } diff --git a/experimental/packages/exporter-trace-otlp-http/package.json b/experimental/packages/exporter-trace-otlp-http/package.json index 2f07cb3436..3883b148fb 100644 --- a/experimental/packages/exporter-trace-otlp-http/package.json +++ b/experimental/packages/exporter-trace-otlp-http/package.json @@ -100,5 +100,6 @@ "@opentelemetry/resources": "1.7.0", "@opentelemetry/sdk-trace-base": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http", + "sideEffects": false } diff --git a/experimental/packages/exporter-trace-otlp-proto/package.json b/experimental/packages/exporter-trace-otlp-proto/package.json index 83b387d958..b9a6927640 100644 --- a/experimental/packages/exporter-trace-otlp-proto/package.json +++ b/experimental/packages/exporter-trace-otlp-proto/package.json @@ -66,7 +66,6 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@grpc/proto-loader": "^0.6.9", "@opentelemetry/core": "1.7.0", "@opentelemetry/otlp-exporter-base": "0.33.0", "@opentelemetry/otlp-proto-exporter-base": "0.33.0", @@ -74,5 +73,6 @@ "@opentelemetry/resources": "1.7.0", "@opentelemetry/sdk-trace-base": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-proto" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-proto", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts b/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts deleted file mode 100644 index e371d5165d..0000000000 --- a/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts +++ /dev/null @@ -1,55 +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 { MeterProvider } from '../types/MeterProvider'; -import { _globalThis } from '../platform'; - -export const GLOBAL_METRICS_API_KEY = Symbol.for( - 'io.opentelemetry.js.api.metrics' -); - -type Get = (version: number) => T; -type OtelGlobal = Partial<{ - [GLOBAL_METRICS_API_KEY]: Get; -}>; - -export const _global = _globalThis as OtelGlobal; - -/** - * Make a function which accepts a version integer and returns the instance of an API if the version - * is compatible, or a fallback version (usually NOOP) if it is not. - * - * @param requiredVersion Backwards compatibility version which is required to return the instance - * @param instance Instance which should be returned if the required version is compatible - * @param fallback Fallback instance, usually NOOP, which will be returned if the required version is not compatible - */ -export function makeGetter( - requiredVersion: number, - instance: T, - fallback: T -): Get { - return (version: number): T => - version === requiredVersion ? instance : fallback; -} - -/** - * A number which should be incremented each time a backwards incompatible - * change is made to the API. This number is used when an API package - * attempts to access the global API to ensure it is getting a compatible - * version. If the global API is not compatible with the API package - * attempting to get it, a NOOP API implementation will be returned. - */ -export const API_BACKWARDS_COMPATIBILITY_VERSION = 4; diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/README.md b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/README.md index e00bf05e41..3aee8d08a4 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/README.md +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/README.md @@ -17,16 +17,19 @@ npm install --save @opentelemetry/exporter-metrics-otlp-grpc ## Service Name The OpenTelemetry Collector Exporter does not have a service name configuration. -In order to set the service name, use the `service.name` resource attribute as prescribed in the [OpenTelemetry Resource Semantic Conventions][semconv-resource-service-name]. -To see sample code and documentation for the traces exporter, as well as instructions for using TLS, visit the [Collector Trace Exporter for web and node][trace-exporter-url]. +In order to set the service name, use the `service.name` resource attribute as prescribed in +the [OpenTelemetry Resource Semantic Conventions][semconv-resource-service-name]. +To see sample code and documentation for the traces exporter, as well as instructions for using TLS, visit +the [Collector Trace Exporter for web and node][trace-exporter-url]. ## Metrics in Node - GRPC -The OTLPMetricsExporter 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. +The OTLPMetricsExporter 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, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); -const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc'); +const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc'); const collectorOptions = { // url is optional and can be omitted - default is http://localhost:4317 url: 'http://:', @@ -52,20 +55,24 @@ counter.add(10, { 'key': 'value' }); ## Environment Variable Configuration - | Environment variable | Description | - |----------------------|-------------| - | OTEL_EXPORTER_OTLP_METRICS_COMPRESSION | The compression type to use on OTLP metric requests. Options include gzip. By default no compression will be used. | - | OTEL_EXPORTER_OTLP_COMPRESSION | The compression type to use on OTLP trace, metric, and log requests. Options include gzip. By default no compression will be used. | - | OTEL_EXPORTER_OTLP_METRICS_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for metric requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. | - | OTEL_EXPORTER_OTLP_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for trace, metric and log requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. | - | OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP metric server's TLS credentials. By default the host platform's trusted root certificate is used.| - | OTEL_EXPORTER_OTLP_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP trace, metric, or log server's TLS credentials. By default the host platform's trusted root certificate is used. | - | OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP metric client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. | - | OTEL_EXPORTER_OTLP_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP trace, metric or log client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. | - | OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP metric server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. | - | OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP trace, metric and log server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. | - - > Settings configured programmatically take precedence over environment variables. Per-signal environment variables take precedence over non-per-signal environment variables. +In addition to settings passed to the constructor, the exporter also supports configuration via environment variables: + +| Environment variable | Description | +|----------------------|-------------| +| OTEL_EXPORTER_OTLP_ENDPOINT | The endpoint to send metrics to. This will also be used for the traces exporter if `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is not configured. By default `localhost:4317` will be used. | +| OTEL_EXPORTER_OTLP_METRICS_ENDPOINT | The endpoint to send metrics to. By default `localhost:4317` will be used. | +| OTEL_EXPORTER_OTLP_COMPRESSION | The compression type to use on OTLP trace, metric, and log requests. Options include gzip. By default no compression will be used. | +| OTEL_EXPORTER_OTLP_METRICS_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for metric requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. | +| OTEL_EXPORTER_OTLP_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for trace, metric and log requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. | +| OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP metric server's TLS credentials. By default the host platform's trusted root certificate is used.| +| OTEL_EXPORTER_OTLP_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP trace, metric, or log server's TLS credentials. By default the host platform's trusted root certificate is used. | +| OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP metric client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. | +| OTEL_EXPORTER_OTLP_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP trace, metric or log client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. | +| OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP metric server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. | +| OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP trace, metric and log server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. | +| OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE | The exporters aggregation temporality preference. Valid values are `cumulative`, and `delta`. `cumulative` selects cumulative temporality for all instrument kinds. `delta` selects delta aggregation temporality for Counter, Asynchronous Counter and Histogram instrument kinds, and selects cumulative aggregation for UpDownCounter and Asynchronous UpDownCounter instrument kinds. By default `cumulative` is used. | + +> Settings configured programmatically take precedence over environment variables. Per-signal environment variables take precedence over non-per-signal environment variables. ## Running opentelemetry-collector locally to see the metrics diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json index d0b4b2054b..d7dc5a21a8 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json @@ -48,6 +48,7 @@ }, "devDependencies": { "@babel/core": "7.16.0", + "@grpc/proto-loader": "^0.7.3", "@opentelemetry/api": "^1.0.0", "@opentelemetry/api-metrics": "0.33.0", "@types/mocha": "10.0.0", @@ -67,8 +68,7 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@grpc/grpc-js": "^1.5.9", - "@grpc/proto-loader": "^0.6.9", + "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "1.7.0", "@opentelemetry/exporter-metrics-otlp-http": "0.33.0", "@opentelemetry/otlp-grpc-exporter-base": "0.33.0", @@ -76,5 +76,6 @@ "@opentelemetry/resources": "1.7.0", "@opentelemetry/sdk-metrics": "0.33.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts index 5f7d979fd3..3a512e3df4 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts @@ -15,7 +15,6 @@ */ import { - defaultOptions, OTLPMetricExporterBase, OTLPMetricExporterOptions } from '@opentelemetry/exporter-metrics-otlp-http'; @@ -33,7 +32,7 @@ import { createExportMetricsServiceRequest, IExportMetricsServiceRequest } from class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase { - constructor(config: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions= defaultOptions) { + constructor(config?: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions) { super(config); const headers = baggageUtils.parseKeyPairsIntoRecord(getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS); this.metadata ||= new Metadata(); @@ -73,7 +72,7 @@ class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase{ - constructor(config: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions = defaultOptions) { + constructor(config?: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions) { super(new OTLPMetricExporterProxy(config), config); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/tsconfig.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/tsconfig.json index 1be23dd8b2..c2a6041ceb 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/tsconfig.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/tsconfig.json @@ -13,13 +13,13 @@ "path": "../../../api" }, { - "path": "../../../packages/opentelemetry-core" + "path": "../../../api-metrics" }, { - "path": "../../../packages/opentelemetry-resources" + "path": "../../../packages/opentelemetry-core" }, { - "path": "../opentelemetry-api-metrics" + "path": "../../../packages/opentelemetry-resources" }, { "path": "../opentelemetry-exporter-metrics-otlp-http" diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/README.md b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/README.md index d495e53b38..4dcbda9dae 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/README.md +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/README.md @@ -17,8 +17,10 @@ npm install --save @opentelemetry/exporter-metrics-otlp-http ## Service Name The OpenTelemetry Collector Metrics Exporter does not have a service name configuration. -In order to set the service name, use the `service.name` resource attribute as prescribed in the [OpenTelemetry Resource Semantic Conventions][semconv-resource-service-name]. -To see sample code and documentation for the traces exporter, visit the [Collector Trace Exporter for web and node][trace-exporter-url]. +In order to set the service name, use the `service.name` resource attribute as prescribed in +the [OpenTelemetry Resource Semantic Conventions][semconv-resource-service-name]. +To see sample code and documentation for the traces exporter, visit +the [Collector Trace Exporter for web and node][trace-exporter-url]. ## Metrics in Web @@ -27,6 +29,7 @@ The OTLPMetricExporter in Web expects the endpoint to end in `/v1/metrics`. ```js import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; + const collectorOptions = { url: '', // url is optional and can be omitted - default is http://localhost:4318/v1/metrics headers: {}, // an optional object containing custom headers to be sent with each request @@ -50,7 +53,7 @@ counter.add(10, { 'key': 'value' }); ```js const { MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); -const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http'); +const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http'); const collectorOptions = { url: '', // url is optional and can be omitted - default is http://localhost:4318/v1/metrics concurrencyLimit: 1, // an optional limit on pending requests @@ -70,35 +73,18 @@ counter.add(10, { 'key': 'value' }); ``` -## GRPC - -For exporting metrics with GRPC please check [exporter-metrics-otlp-grpc][npm-url-grpc] - -## PROTOBUF - -For exporting metrics with PROTOBUF please check [exporter-metrics-otlp-proto][npm-url-proto] - -## Configuration options as environment variables - -Instead of providing options to `OTLPMetricExporter` and `OTLPTraceExporter` explicitly, environment variables may be provided instead. - -```sh -OTEL_EXPORTER_OTLP_ENDPOINT=https://localhost:4318 -# this will automatically append the version and signal path -# e.g. https://localhost:4318/v1/traces for `OTLPTraceExporter` and https://localhost:4318/v1/metrics for `OTLPMetricExporter` -``` - -If the trace and metric exporter endpoints have different providers, the env var for per-signal endpoints are available to use +## Environment Variable Configuration -```sh -OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://trace-service:4318/v1/traces -OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metric-service:4318/v1/metrics -# version and signal needs to be explicit -``` +In addition to settings passed to the constructor, the exporter also supports configuration via environment variables: -> The per-signal endpoints take precedence and overrides `OTEL_EXPORTER_OTLP_ENDPOINT` +| Environment variable | Description | +|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| OTEL_EXPORTER_OTLP_ENDPOINT | The endpoint to send metrics to. This will also be used for the traces exporter if `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is not configured. By default `http://localhost:4318` will be used. `/v1/metrics` will be automatically appended to configured values. | +| OTEL_EXPORTER_OTLP_METRICS_ENDPOINT | The endpoint to send metrics to. By default `https://localhost:4318/v1/metrics` will be used. `v1/metrics` will not be appended automatically and has to be added explicitly. | +| OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE | The exporters aggregation temporality preference. Valid values are `cumulative`, and `delta`. `cumulative` selects cumulative temporality for all instrument kinds. `delta` selects delta aggregation temporality for Counter, Asynchronous Counter and Histogram instrument kinds, and selects cumulative aggregation for UpDownCounter and Asynchronous UpDownCounter instrument kinds. By default `cumulative` is used. | -For more details, see [OpenTelemetry Specification on Protocol Exporter][opentelemetry-spec-protocol-exporter]. +> Settings configured programmatically take precedence over environment variables. Per-signal environment variables take +> precedence over non-per-signal environment variables. ## Running opentelemetry-collector locally to see the metrics @@ -110,6 +96,8 @@ For more details, see [OpenTelemetry Specification on Protocol Exporter][opentel - For more information on OpenTelemetry, visit: - For more about OpenTelemetry JavaScript: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] +- For exporting metrics via gRPC please check [exporter-metrics-otlp-grpc][npm-url-grpc] +- For exporting metrics via protobuf please check [exporter-metrics-otlp-proto][npm-url-proto] ## License diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json index 8cae41c826..0740d5913e 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json @@ -101,5 +101,6 @@ "@opentelemetry/resources": "1.7.0", "@opentelemetry/sdk-metrics": "0.33.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-http" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-http", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterBase.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterBase.ts index b970a150d0..fca5a54d25 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterBase.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterBase.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { ExportResult } from '@opentelemetry/core'; +import { + ExportResult, + getEnv +} from '@opentelemetry/core'; import { AggregationTemporality, AggregationTemporalitySelector, @@ -22,9 +25,12 @@ import { PushMetricExporter, ResourceMetrics } from '@opentelemetry/sdk-metrics'; -import { defaultOptions, OTLPMetricExporterOptions } from './OTLPMetricExporterOptions'; +import { + OTLPMetricExporterOptions +} from './OTLPMetricExporterOptions'; import { OTLPExporterBase } from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; +import { diag } from '@opentelemetry/api'; export const CumulativeTemporalitySelector: AggregationTemporalitySelector = () => AggregationTemporality.CUMULATIVE; @@ -41,14 +47,33 @@ export const DeltaTemporalitySelector: AggregationTemporalitySelector = (instrum } }; -function chooseTemporalitySelector(temporalityPreference?: AggregationTemporality): AggregationTemporalitySelector { - if (temporalityPreference === AggregationTemporality.DELTA) { +function chooseTemporalitySelectorFromEnvironment() { + const env = getEnv(); + const configuredTemporality = env.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE.trim().toLowerCase(); + + if (configuredTemporality === 'cumulative') { + return CumulativeTemporalitySelector; + } + if (configuredTemporality === 'delta') { return DeltaTemporalitySelector; } + diag.warn(`OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE is set to '${env.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE}', but only 'cumulative' and 'delta' are allowed. Using default ('cumulative') instead.`); return CumulativeTemporalitySelector; } +function chooseTemporalitySelector(temporalityPreference?: AggregationTemporality): AggregationTemporalitySelector { + // Directly passed preference has priority. + if (temporalityPreference != null) { + if (temporalityPreference === AggregationTemporality.DELTA) { + return DeltaTemporalitySelector; + } + return CumulativeTemporalitySelector; + } + + return chooseTemporalitySelectorFromEnvironment(); +} + export class OTLPMetricExporterBase> @@ -57,9 +82,9 @@ implements PushMetricExporter { protected _aggregationTemporalitySelector: AggregationTemporalitySelector; constructor(exporter: T, - config: OTLPMetricExporterOptions = defaultOptions) { + config?: OTLPMetricExporterOptions) { this._otlpExporter = exporter; - this._aggregationTemporalitySelector = chooseTemporalitySelector(config.temporalityPreference); + this._aggregationTemporalitySelector = chooseTemporalitySelector(config?.temporalityPreference); } export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterOptions.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterOptions.ts index 986a85890b..324c4ea27f 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterOptions.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/OTLPMetricExporterOptions.ts @@ -20,5 +20,3 @@ import { OTLPExporterConfigBase } from '@opentelemetry/otlp-exporter-base'; export interface OTLPMetricExporterOptions extends OTLPExporterConfigBase { temporalityPreference?: AggregationTemporality } -export const defaultExporterTemporality = AggregationTemporality.CUMULATIVE; -export const defaultOptions = {temporalityPreference: defaultExporterTemporality}; diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts index afc42de108..00c7c0c32c 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts @@ -16,7 +16,7 @@ import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import { baggageUtils, getEnv } from '@opentelemetry/core'; -import { defaultOptions, OTLPMetricExporterOptions } from '../../OTLPMetricExporterOptions'; +import { OTLPMetricExporterOptions } from '../../OTLPMetricExporterOptions'; import { OTLPMetricExporterBase } from '../../OTLPMetricExporterBase'; import { OTLPExporterBrowserBase, @@ -31,7 +31,7 @@ const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURC class OTLPExporterBrowserProxy extends OTLPExporterBrowserBase { - constructor(config: OTLPMetricExporterOptions & OTLPExporterConfigBase = defaultOptions) { + constructor(config?: OTLPMetricExporterOptions & OTLPExporterConfigBase) { super(config); this._headers = Object.assign( this._headers, @@ -60,7 +60,7 @@ class OTLPExporterBrowserProxy extends OTLPExporterBrowserBase { - constructor(config: OTLPExporterConfigBase & OTLPMetricExporterOptions = defaultOptions) { + constructor(config?: OTLPExporterConfigBase & OTLPMetricExporterOptions) { super(new OTLPExporterBrowserProxy(config), config); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts index 579fe95696..d09693a253 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts @@ -16,7 +16,7 @@ import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import { getEnv, baggageUtils} from '@opentelemetry/core'; -import { defaultOptions, OTLPMetricExporterOptions } from '../../OTLPMetricExporterOptions'; +import { OTLPMetricExporterOptions } from '../../OTLPMetricExporterOptions'; import { OTLPMetricExporterBase } from '../../OTLPMetricExporterBase'; import { OTLPExporterNodeBase, @@ -31,7 +31,7 @@ const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURC class OTLPExporterNodeProxy extends OTLPExporterNodeBase { - constructor(config: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions = defaultOptions) { + constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { super(config); this.headers = Object.assign( this.headers, @@ -60,7 +60,7 @@ class OTLPExporterNodeProxy extends OTLPExporterNodeBase { - constructor(config: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions = defaultOptions) { + constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { super(new OTLPExporterNodeProxy(config), config); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts index 38c0b3bcc7..406e20f612 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts @@ -20,22 +20,34 @@ import { } from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import * as sinon from 'sinon'; -import { collect, mockCounter, mockObservableGauge, setUp, shutdown } from '../metricsHelper'; -import { OTLPExporterBase, OTLPExporterConfigBase } from '@opentelemetry/otlp-exporter-base'; +import { + collect, + mockCounter, + mockObservableGauge, + setUp, + shutdown +} from '../metricsHelper'; +import { + OTLPExporterBase, + OTLPExporterConfigBase +} from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; type CollectorExporterConfig = OTLPExporterConfigBase; -class OTLPMetricExporter extends OTLPExporterBase< - CollectorExporterConfig, + +class OTLPMetricExporter extends OTLPExporterBase { + IExportMetricsServiceRequest> { onInit() {} + onShutdown() {} + send() {} + getDefaultUrl(config: CollectorExporterConfig) { return config.url || ''; } + convert(metrics: ResourceMetrics[]): IExportMetricsServiceRequest { return { resourceMetrics: [] }; } @@ -125,7 +137,7 @@ describe('OTLPMetricExporter - common', () => { describe('when exporter is shutdown', () => { it( 'should not export anything but return callback with code' + - ' "FailedNotRetryable"', + ' "FailedNotRetryable"', async () => { await collectorExporter.shutdown(); spySend.resetHistory(); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts index 77c5023153..68dd569d89 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts @@ -15,12 +15,17 @@ */ -import { diag, DiagLogger } from '@opentelemetry/api'; +import { + diag, + DiagLogger +} from '@opentelemetry/api'; import * as core from '@opentelemetry/core'; import * as assert from 'assert'; import * as http from 'http'; import * as sinon from 'sinon'; import { + CumulativeTemporalitySelector, + DeltaTemporalitySelector, OTLPMetricExporterOptions } from '../../src'; @@ -28,22 +33,31 @@ import { OTLPMetricExporter } from '../../src/platform/node'; import { + collect, ensureCounterIsCorrect, ensureExportMetricsServiceRequestIsSet, - ensureObservableGaugeIsCorrect, ensureHistogramIsCorrect, + ensureObservableGaugeIsCorrect, + HISTOGRAM_AGGREGATION_VIEW, mockCounter, - mockObservableGauge, mockHistogram, - collect, - shutdown, + mockObservableGauge, setUp, - HISTOGRAM_AGGREGATION_VIEW, + shutdown, } from '../metricsHelper'; import { MockedResponse } from './nodeHelpers'; -import { AggregationTemporality, ResourceMetrics } from '@opentelemetry/sdk-metrics'; -import { Stream, PassThrough } from 'stream'; -import { OTLPExporterError, OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; +import { + AggregationTemporality, + ResourceMetrics +} from '@opentelemetry/sdk-metrics'; +import { + PassThrough, + Stream +} from 'stream'; +import { + OTLPExporterError, + OTLPExporterNodeConfigBase +} from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; let fakeRequest: PassThrough; @@ -190,6 +204,34 @@ describe('OTLPMetricExporter - node with json over http', () => { envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = ''; envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); + it('should use delta temporality defined via env', () => { + for (const envValue of ['delta', 'DELTA', 'DeLTa', 'delta ']) { + envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; + const exporter = new OTLPMetricExporter(); + assert.strictEqual(exporter['_aggregationTemporalitySelector'], DeltaTemporalitySelector); + } + }); + it('should use cumulative temporality defined via env', () => { + for (const envValue of ['cumulative', 'CUMULATIVE', 'CuMULaTIvE', 'cumulative ']) { + envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; + const exporter = new OTLPMetricExporter(); + assert.strictEqual(exporter['_aggregationTemporalitySelector'], CumulativeTemporalitySelector); + } + }); + it('should configure cumulative temporality with invalid value in env', () => { + for (const envValue of ['invalid', ' ']) { + envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; + const exporter = new OTLPMetricExporter(); + assert.strictEqual(exporter['_aggregationTemporalitySelector'], CumulativeTemporalitySelector); + } + }); + it('should respect explicit config over environment variable', () => { + envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = 'cumulative'; + const exporter = new OTLPMetricExporter({ + temporalityPreference: AggregationTemporality.DELTA + }); + assert.strictEqual(exporter['_aggregationTemporalitySelector'], DeltaTemporalitySelector); + }); }); describe('export', () => { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/tsconfig.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/tsconfig.json index 1f981132ba..7a2d809dff 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/tsconfig.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/tsconfig.json @@ -13,13 +13,13 @@ "path": "../../../api" }, { - "path": "../../../packages/opentelemetry-core" + "path": "../../../api-metrics" }, { - "path": "../../../packages/opentelemetry-resources" + "path": "../../../packages/opentelemetry-core" }, { - "path": "../opentelemetry-api-metrics" + "path": "../../../packages/opentelemetry-resources" }, { "path": "../opentelemetry-sdk-metrics" diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/README.md b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/README.md index a5d812236d..6a2e160bac 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/README.md +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/README.md @@ -43,6 +43,19 @@ counter.add(10, { 'key': 'value' }); ``` +## Environment Variable Configuration + +In addition to settings passed to the constructor, the exporter also supports configuration via environment variables: + +| Environment variable | Description | +|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| OTEL_EXPORTER_OTLP_ENDPOINT | The endpoint to send metrics to. This will also be used for the traces exporter if `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is not configured. By default `http://localhost:4318` will be used. `/v1/metrics` will be automatically appended to configured values. | +| OTEL_EXPORTER_OTLP_METRICS_ENDPOINT | The endpoint to send metrics to. By default `https://localhost:4318/v1/metrics` will be used. `v1/metrics` will not be appended automatically and has to be added explicitly. | +| OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE | The exporters aggregation temporality preference. Valid values are `cumulative`, and `delta`. `cumulative` selects cumulative temporality for all instrument kinds. `delta` selects delta aggregation temporality for Counter, Asynchronous Counter and Histogram instrument kinds, and selects cumulative aggregation for UpDownCounter and Asynchronous UpDownCounter instrument kinds. By default `cumulative` is used. | + +> Settings configured programmatically take precedence over environment variables. Per-signal environment variables take +> precedence over non-per-signal environment variables. + ## Running opentelemetry-collector locally to see the metrics 1. Go to `examples/otlp-exporter-node` diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json index 45266a7df1..bad7943e6e 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json @@ -67,7 +67,6 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@grpc/proto-loader": "0.6.9", "@opentelemetry/core": "1.7.0", "@opentelemetry/exporter-metrics-otlp-http": "0.33.0", "@opentelemetry/otlp-exporter-base": "0.33.0", @@ -76,5 +75,6 @@ "@opentelemetry/resources": "1.7.0", "@opentelemetry/sdk-metrics": "0.33.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-proto" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-proto", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts index e3d8a60155..e62a8e0ece 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts @@ -15,7 +15,6 @@ */ import { - defaultOptions, OTLPMetricExporterOptions } from '@opentelemetry/exporter-metrics-otlp-http'; import { ServiceClientType, OTLPProtoExporterNodeBase } from '@opentelemetry/otlp-proto-exporter-base'; @@ -34,7 +33,7 @@ const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURC class OTLPMetricExporterNodeProxy extends OTLPProtoExporterNodeBase { - constructor(config: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions = defaultOptions) { + constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { super(config); this.headers = Object.assign( this.headers, @@ -64,7 +63,7 @@ class OTLPMetricExporterNodeProxy extends OTLPProtoExporterNodeBase { - constructor(config: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions = defaultOptions) { + constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { super(new OTLPMetricExporterNodeProxy(config), config); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/tsconfig.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/tsconfig.json index 63e825da3f..133b1e8bb8 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/tsconfig.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/tsconfig.json @@ -13,13 +13,13 @@ "path": "../../../api" }, { - "path": "../../../packages/opentelemetry-core" + "path": "../../../api-metrics" }, { - "path": "../../../packages/opentelemetry-resources" + "path": "../../../packages/opentelemetry-core" }, { - "path": "../opentelemetry-api-metrics" + "path": "../../../packages/opentelemetry-resources" }, { "path": "../opentelemetry-exporter-metrics-otlp-http" diff --git a/experimental/packages/opentelemetry-exporter-prometheus/package.json b/experimental/packages/opentelemetry-exporter-prometheus/package.json index c8305d3c88..bb841bacd4 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/package.json +++ b/experimental/packages/opentelemetry-exporter-prometheus/package.json @@ -64,5 +64,6 @@ "@opentelemetry/sdk-metrics": "0.33.0", "@opentelemetry/resources": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-exporter-prometheus/tsconfig.json b/experimental/packages/opentelemetry-exporter-prometheus/tsconfig.json index 1283f8e6d4..9ac6b6fd90 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/tsconfig.json +++ b/experimental/packages/opentelemetry-exporter-prometheus/tsconfig.json @@ -13,13 +13,13 @@ "path": "../../../api" }, { - "path": "../../../packages/opentelemetry-core" + "path": "../../../api-metrics" }, { - "path": "../../../packages/opentelemetry-resources" + "path": "../../../packages/opentelemetry-core" }, { - "path": "../opentelemetry-api-metrics" + "path": "../../../packages/opentelemetry-resources" }, { "path": "../opentelemetry-sdk-metrics" diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/package.json b/experimental/packages/opentelemetry-instrumentation-fetch/package.json index 03b44659b2..3e7e3336fc 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/package.json +++ b/experimental/packages/opentelemetry-instrumentation-fetch/package.json @@ -92,5 +92,6 @@ "@opentelemetry/sdk-trace-web": "1.7.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-fetch" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-fetch", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/package.json b/experimental/packages/opentelemetry-instrumentation-grpc/package.json index 90e683e23b..8d9f69ef1c 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/package.json +++ b/experimental/packages/opentelemetry-instrumentation-grpc/package.json @@ -45,8 +45,8 @@ "access": "public" }, "devDependencies": { - "@grpc/grpc-js": "1.5.9", - "@grpc/proto-loader": "0.6.9", + "@grpc/grpc-js": "^1.7.1", + "@grpc/proto-loader": "^0.7.3", "@opentelemetry/api": "^1.0.0", "@opentelemetry/context-async-hooks": "1.7.0", "@opentelemetry/core": "1.7.0", @@ -75,5 +75,6 @@ "@opentelemetry/instrumentation": "0.33.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-grpc" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-grpc", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/tsconfig.json b/experimental/packages/opentelemetry-instrumentation-grpc/tsconfig.json index c21412396f..25a8b5fb03 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/tsconfig.json +++ b/experimental/packages/opentelemetry-instrumentation-grpc/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../../api" }, + { + "path": "../../../api-metrics" + }, { "path": "../../../packages/opentelemetry-context-async-hooks" }, @@ -27,9 +30,6 @@ { "path": "../../../packages/opentelemetry-semantic-conventions" }, - { - "path": "../opentelemetry-api-metrics" - }, { "path": "../opentelemetry-instrumentation" } diff --git a/experimental/packages/opentelemetry-instrumentation-http/package.json b/experimental/packages/opentelemetry-instrumentation-http/package.json index e46808bc91..ef2a792705 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/package.json +++ b/experimental/packages/opentelemetry-instrumentation-http/package.json @@ -49,7 +49,6 @@ "@opentelemetry/context-async-hooks": "1.7.0", "@opentelemetry/sdk-trace-base": "1.7.0", "@opentelemetry/sdk-trace-node": "1.7.0", - "@types/got": "9.6.12", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/request-promise-native": "1.0.18", @@ -58,7 +57,6 @@ "@types/superagent": "4.1.13", "axios": "0.24.0", "codecov": "3.8.3", - "got": "9.6.0", "mocha": "10.0.0", "nock": "13.0.11", "nyc": "15.1.0", @@ -81,5 +79,6 @@ "@opentelemetry/semantic-conventions": "1.7.0", "semver": "^7.3.5" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts index 8da3577ddb..51b15dd6f3 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts @@ -35,7 +35,8 @@ instrumentation.disable(); import * as http from 'http'; import * as request from 'request-promise-native'; import * as superagent from 'superagent'; -import * as got from 'got'; +// Temporarily removed. See https://github.com/open-telemetry/opentelemetry-js/issues/3344 +// import * as got from 'got'; import * as nock from 'nock'; import axios, { AxiosResponse } from 'axios'; @@ -80,7 +81,7 @@ describe('Packages', () => { [ { name: 'axios', httpPackage: axios }, //keep first { name: 'superagent', httpPackage: superagent }, - { name: 'got', httpPackage: { get: (url: string) => got(url) } }, + // { name: 'got', httpPackage: { get: (url: string) => got(url) } }, { name: 'request', httpPackage: { get: (url: string) => request(url) }, diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts index 5f1c11d4a7..3fd164c243 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts @@ -35,7 +35,8 @@ instrumentation.disable(); import * as http from 'http'; import * as request from 'request-promise-native'; import * as superagent from 'superagent'; -import * as got from 'got'; +// Temporarily removed. See https://github.com/open-telemetry/opentelemetry-js/issues/3344 +// import * as got from 'got'; import * as nock from 'nock'; import axios, { AxiosResponse } from 'axios'; @@ -80,7 +81,7 @@ describe('Packages', () => { [ { name: 'axios', httpPackage: axios }, //keep first { name: 'superagent', httpPackage: superagent }, - { name: 'got', httpPackage: { get: (url: string) => got(url) } }, + // { name: 'got', httpPackage: { get: (url: string) => got(url) } }, { name: 'request', httpPackage: { get: (url: string) => request(url) }, diff --git a/experimental/packages/opentelemetry-instrumentation-http/tsconfig.json b/experimental/packages/opentelemetry-instrumentation-http/tsconfig.json index 94828c26da..c6158c12c1 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/tsconfig.json +++ b/experimental/packages/opentelemetry-instrumentation-http/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../../api" }, + { + "path": "../../../api-metrics" + }, { "path": "../../../packages/opentelemetry-context-async-hooks" }, @@ -27,9 +30,6 @@ { "path": "../../../packages/opentelemetry-semantic-conventions" }, - { - "path": "../opentelemetry-api-metrics" - }, { "path": "../opentelemetry-instrumentation" }, diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json index 715097b692..40b340b5cd 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json @@ -92,5 +92,6 @@ "@opentelemetry/sdk-trace-web": "1.7.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-xml-http-request" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-xml-http-request", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-instrumentation/package.json b/experimental/packages/opentelemetry-instrumentation/package.json index 102389ed51..abc09deb23 100644 --- a/experimental/packages/opentelemetry-instrumentation/package.json +++ b/experimental/packages/opentelemetry-instrumentation/package.json @@ -108,5 +108,6 @@ }, "engines": { "node": ">=14" - } + }, + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-instrumentation/src/platform/node/ModuleNameTrie.ts b/experimental/packages/opentelemetry-instrumentation/src/platform/node/ModuleNameTrie.ts new file mode 100644 index 0000000000..3230fea99c --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/src/platform/node/ModuleNameTrie.ts @@ -0,0 +1,86 @@ +/* + * 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 type { Hooked } from './RequireInTheMiddleSingleton'; + +export const ModuleNameSeparator = '/'; + +/** + * Node in a `ModuleNameTrie` + */ +class ModuleNameTrieNode { + hooks: Array<{ hook: Hooked, insertedId: number }> = []; + children: Map = new Map(); +} + +/** + * Trie containing nodes that represent a part of a module name (i.e. the parts separated by forward slash) + */ +export class ModuleNameTrie { + private _trie: ModuleNameTrieNode = new ModuleNameTrieNode(); + private _counter: number = 0; + + /** + * Insert a module hook into the trie + * + * @param {Hooked} hook Hook + */ + insert(hook: Hooked) { + let trieNode = this._trie; + + for (const moduleNamePart of hook.moduleName.split(ModuleNameSeparator)) { + let nextNode = trieNode.children.get(moduleNamePart); + if (!nextNode) { + nextNode = new ModuleNameTrieNode(); + trieNode.children.set(moduleNamePart, nextNode); + } + trieNode = nextNode; + } + trieNode.hooks.push({ hook, insertedId: this._counter++ }); + } + + /** + * Search for matching hooks in the trie + * + * @param {string} moduleName Module name + * @param {boolean} maintainInsertionOrder Whether to return the results in insertion order + * @returns {Hooked[]} Matching hooks + */ + search(moduleName: string, { maintainInsertionOrder }: { maintainInsertionOrder?: boolean } = {}): Hooked[] { + let trieNode = this._trie; + const results: ModuleNameTrieNode['hooks'] = []; + + for (const moduleNamePart of moduleName.split(ModuleNameSeparator)) { + const nextNode = trieNode.children.get(moduleNamePart); + if (!nextNode) { + break; + } + results.push(...nextNode.hooks); + trieNode = nextNode; + } + + if (results.length === 0) { + return []; + } + if (results.length === 1) { + return [results[0].hook]; + } + if (maintainInsertionOrder) { + results.sort((a, b) => a.insertedId - b.insertedId); + } + return results.map(({ hook }) => hook); + } +} diff --git a/experimental/packages/opentelemetry-instrumentation/src/platform/node/RequireInTheMiddleSingleton.ts b/experimental/packages/opentelemetry-instrumentation/src/platform/node/RequireInTheMiddleSingleton.ts new file mode 100644 index 0000000000..812db52b68 --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/src/platform/node/RequireInTheMiddleSingleton.ts @@ -0,0 +1,111 @@ +/* + * 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 RequireInTheMiddle from 'require-in-the-middle'; +import * as path from 'path'; +import { ModuleNameTrie, ModuleNameSeparator } from './ModuleNameTrie'; + +export type Hooked = { + moduleName: string + onRequire: RequireInTheMiddle.OnRequireFn +}; + +/** + * Whether Mocha is running in this process + * Inspired by https://github.com/AndreasPizsa/detect-mocha + * + * @type {boolean} + */ +const isMocha = ['afterEach','after','beforeEach','before','describe','it'].every(fn => { + // @ts-expect-error TS7053: Element implicitly has an 'any' type + return typeof global[fn] === 'function'; +}); + +/** + * Singleton class for `require-in-the-middle` + * Allows instrumentation plugins to patch modules with only a single `require` patch + * WARNING: Because this class will create its own `require-in-the-middle` (RITM) instance, + * we should minimize the number of new instances of this class. + * Multiple instances of `@opentelemetry/instrumentation` (e.g. multiple versions) in a single process + * will result in multiple instances of RITM, which will have an impact + * on the performance of instrumentation hooks being applied. + */ +export class RequireInTheMiddleSingleton { + private _moduleNameTrie: ModuleNameTrie = new ModuleNameTrie(); + private static _instance?: RequireInTheMiddleSingleton; + + private constructor() { + this._initialize(); + } + + private _initialize() { + RequireInTheMiddle( + // Intercept all `require` calls; we will filter the matching ones below + null, + { internals: true }, + (exports, name, basedir) => { + // For internal files on Windows, `name` will use backslash as the path separator + const normalizedModuleName = normalizePathSeparators(name); + + const matches = this._moduleNameTrie.search(normalizedModuleName, { maintainInsertionOrder: true }); + + for (const { onRequire } of matches) { + exports = onRequire(exports, name, basedir); + } + + return exports; + } + ); + } + + /** + * Register a hook with `require-in-the-middle` + * + * @param {string} moduleName Module name + * @param {RequireInTheMiddle.OnRequireFn} onRequire Hook function + * @returns {Hooked} Registered hook + */ + register(moduleName: string, onRequire: RequireInTheMiddle.OnRequireFn): Hooked { + const hooked = { moduleName, onRequire }; + this._moduleNameTrie.insert(hooked); + return hooked; + } + + /** + * Get the `RequireInTheMiddleSingleton` singleton + * + * @returns {RequireInTheMiddleSingleton} Singleton of `RequireInTheMiddleSingleton` + */ + static getInstance(): RequireInTheMiddleSingleton { + // Mocha runs all test suites in the same process + // This prevents test suites from sharing a singleton + if (isMocha) return new RequireInTheMiddleSingleton(); + + return this._instance = this._instance ?? new RequireInTheMiddleSingleton(); + } +} + +/** + * Normalize the path separators to forward slash in a module name or path + * + * @param {string} moduleNameOrPath Module name or path + * @returns {string} Normalized module name or path + */ +function normalizePathSeparators(moduleNameOrPath: string): string { + return path.sep !== ModuleNameSeparator + ? moduleNameOrPath.split(path.sep).join(ModuleNameSeparator) + : moduleNameOrPath; +} diff --git a/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts b/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts index 5bc0921778..d80985431c 100644 --- a/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts +++ b/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts @@ -16,9 +16,9 @@ import * as types from '../../types'; import * as path from 'path'; -import * as RequireInTheMiddle from 'require-in-the-middle'; import { satisfies } from 'semver'; import { InstrumentationAbstract } from '../../instrumentation'; +import { RequireInTheMiddleSingleton, Hooked } from './RequireInTheMiddleSingleton'; import { InstrumentationModuleDefinition } from './types'; import { diag } from '@opentelemetry/api'; @@ -29,7 +29,8 @@ export abstract class InstrumentationBase extends InstrumentationAbstract implements types.Instrumentation { private _modules: InstrumentationModuleDefinition[]; - private _hooks: RequireInTheMiddle.Hooked[] = []; + private _hooks: Hooked[] = []; + private _requireInTheMiddleSingleton: RequireInTheMiddleSingleton = RequireInTheMiddleSingleton.getInstance(); private _enabled = false; constructor( @@ -160,9 +161,8 @@ export abstract class InstrumentationBase this._warnOnPreloadedModules(); for (const module of this._modules) { this._hooks.push( - RequireInTheMiddle( - [module.name], - { internals: true }, + this._requireInTheMiddleSingleton.register( + module.name, (exports, name, baseDir) => { return this._onRequire( (module as unknown) as InstrumentationModuleDefinition< diff --git a/experimental/packages/opentelemetry-instrumentation/test/node/ModuleNameTrie.test.ts b/experimental/packages/opentelemetry-instrumentation/test/node/ModuleNameTrie.test.ts new file mode 100644 index 0000000000..c3d72c89d7 --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/test/node/ModuleNameTrie.test.ts @@ -0,0 +1,68 @@ +/* + * 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 { Hooked } from '../../src/platform/node/RequireInTheMiddleSingleton'; +import { ModuleNameTrie } from '../../src/platform/node/ModuleNameTrie'; + +describe('ModuleNameTrie', () => { + describe('search', () => { + const trie = new ModuleNameTrie(); + const inserts = [ + { moduleName: 'a', onRequire: () => {} }, + { moduleName: 'a/b', onRequire: () => {} }, + { moduleName: 'a', onRequire: () => {} }, + { moduleName: 'a/c', onRequire: () => {} }, + { moduleName: 'd', onRequire: () => {} } + ] as Hooked[]; + inserts.forEach(trie.insert.bind(trie)); + + it('should return a list of exact matches (no results)', () => { + assert.deepEqual(trie.search('e'), []); + }); + + it('should return a list of exact matches (one result)', () => { + assert.deepEqual(trie.search('d'), [inserts[4]]); + }); + + it('should return a list of exact matches (more than one result)', () => { + assert.deepEqual(trie.search('a'), [ + inserts[0], + inserts[2] + ]); + }); + + describe('maintainInsertionOrder = false', () => { + it('should return a list of matches in prefix order', () => { + assert.deepEqual(trie.search('a/b'), [ + inserts[0], + inserts[2], + inserts[1] + ]); + }); + }); + + describe('maintainInsertionOrder = true', () => { + it('should return a list of matches in insertion order', () => { + assert.deepEqual(trie.search('a/b', { maintainInsertionOrder: true }), [ + inserts[0], + inserts[1], + inserts[2] + ]); + }); + }); + }); +}); diff --git a/experimental/packages/opentelemetry-instrumentation/test/node/RequireInTheMiddleSingleton.test.ts b/experimental/packages/opentelemetry-instrumentation/test/node/RequireInTheMiddleSingleton.test.ts new file mode 100644 index 0000000000..724dced720 --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/test/node/RequireInTheMiddleSingleton.test.ts @@ -0,0 +1,126 @@ +/* + * 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 * as path from 'path'; +import * as RequireInTheMiddle from 'require-in-the-middle'; +import { RequireInTheMiddleSingleton } from '../../src/platform/node/RequireInTheMiddleSingleton'; + +const requireInTheMiddleSingleton = RequireInTheMiddleSingleton.getInstance(); + +type AugmentedExports = { + __ritmOnRequires?: string[] +}; + +const makeOnRequiresStub = (label: string): sinon.SinonStub => sinon.stub().callsFake(((exports: AugmentedExports) => { + exports.__ritmOnRequires ??= []; + exports.__ritmOnRequires.push(label); + return exports; +}) as RequireInTheMiddle.OnRequireFn); + +describe('RequireInTheMiddleSingleton', () => { + describe('register', () => { + const onRequireFsStub = makeOnRequiresStub('fs'); + const onRequireFsPromisesStub = makeOnRequiresStub('fs-promises'); + const onRequireCodecovStub = makeOnRequiresStub('codecov'); + const onRequireCodecovLibStub = makeOnRequiresStub('codecov-lib'); + const onRequireCpxStub = makeOnRequiresStub('cpx'); + const onRequireCpxLibStub = makeOnRequiresStub('cpx-lib'); + + before(() => { + requireInTheMiddleSingleton.register('fs', onRequireFsStub); + requireInTheMiddleSingleton.register('fs/promises', onRequireFsPromisesStub); + requireInTheMiddleSingleton.register('codecov', onRequireCodecovStub); + requireInTheMiddleSingleton.register('codecov/lib/codecov.js', onRequireCodecovLibStub); + requireInTheMiddleSingleton.register('cpx', onRequireCpxStub); + requireInTheMiddleSingleton.register('cpx/lib/copy-sync.js', onRequireCpxLibStub); + }); + + beforeEach(() => { + onRequireFsStub.resetHistory(); + onRequireFsPromisesStub.resetHistory(); + onRequireCodecovStub.resetHistory(); + onRequireCodecovLibStub.resetHistory(); + onRequireCpxStub.resetHistory(); + onRequireCpxLibStub.resetHistory(); + }); + + it('should return a hooked object', () => { + const moduleName = 'm'; + const onRequire = makeOnRequiresStub('m'); + const hooked = requireInTheMiddleSingleton.register(moduleName, onRequire); + assert.deepStrictEqual(hooked, { moduleName, onRequire }); + }); + + describe('core module', () => { + describe('AND module name matches', () => { + it('should call `onRequire`', () => { + const exports = require('fs'); + assert.deepStrictEqual(exports.__ritmOnRequires, ['fs']); + sinon.assert.calledOnceWithExactly(onRequireFsStub, exports, 'fs', undefined); + sinon.assert.notCalled(onRequireFsPromisesStub); + }); + }); + describe('AND module name does not match', () => { + it('should not call `onRequire`', () => { + const exports = require('crypto'); + assert.equal(exports.__ritmOnRequires, undefined); + sinon.assert.notCalled(onRequireFsStub); + }); + }); + }); + + describe('core module with sub-path', () => { + describe('AND module name matches', () => { + it('should call `onRequire`', () => { + const exports = require('fs/promises'); + assert.deepStrictEqual(exports.__ritmOnRequires, ['fs', 'fs-promises']); + sinon.assert.calledOnceWithExactly(onRequireFsPromisesStub, exports, 'fs/promises', undefined); + sinon.assert.calledOnceWithMatch(onRequireFsStub, { __ritmOnRequires: ['fs', 'fs-promises'] }, 'fs/promises', undefined); + }); + }); + }); + + describe('non-core module', () => { + describe('AND module name matches', () => { + const baseDir = path.dirname(require.resolve('codecov')); + const modulePath = path.join('codecov', 'lib', 'codecov.js'); + it('should call `onRequire`', () => { + const exports = require('codecov'); + assert.deepStrictEqual(exports.__ritmOnRequires, ['codecov']); + sinon.assert.calledWithExactly(onRequireCodecovStub, exports, 'codecov', baseDir); + sinon.assert.calledWithMatch(onRequireCodecovStub, { __ritmOnRequires: ['codecov', 'codecov-lib'] }, modulePath, baseDir); + sinon.assert.calledWithMatch(onRequireCodecovLibStub, { __ritmOnRequires: ['codecov', 'codecov-lib'] }, modulePath, baseDir); + }).timeout(30000); + }); + }); + + describe('non-core module with sub-path', () => { + describe('AND module name matches', () => { + const baseDir = path.resolve(path.dirname(require.resolve('cpx')), '..'); + const modulePath = path.join('cpx', 'lib', 'copy-sync.js'); + it('should call `onRequire`', () => { + const exports = require('cpx/lib/copy-sync'); + assert.deepStrictEqual(exports.__ritmOnRequires, ['cpx', 'cpx-lib']); + sinon.assert.calledWithMatch(onRequireCpxStub, { __ritmOnRequires: ['cpx', 'cpx-lib'] }, modulePath, baseDir); + sinon.assert.calledWithExactly(onRequireCpxStub, exports, modulePath, baseDir); + sinon.assert.calledWithExactly(onRequireCpxLibStub, exports, modulePath, baseDir); + }); + }); + }); + }); +}); diff --git a/experimental/packages/opentelemetry-instrumentation/tsconfig.esnext.json b/experimental/packages/opentelemetry-instrumentation/tsconfig.esnext.json index 5b297a614a..dc405c942c 100644 --- a/experimental/packages/opentelemetry-instrumentation/tsconfig.esnext.json +++ b/experimental/packages/opentelemetry-instrumentation/tsconfig.esnext.json @@ -10,7 +10,7 @@ ], "references": [ { - "path": "../opentelemetry-api-metrics/tsconfig.esnext.json" + "path": "../../../api-metrics/tsconfig.esnext.json" } ] } diff --git a/experimental/packages/opentelemetry-instrumentation/tsconfig.json b/experimental/packages/opentelemetry-instrumentation/tsconfig.json index af53bd1755..ce90824272 100644 --- a/experimental/packages/opentelemetry-instrumentation/tsconfig.json +++ b/experimental/packages/opentelemetry-instrumentation/tsconfig.json @@ -13,7 +13,7 @@ "path": "../../../api" }, { - "path": "../opentelemetry-api-metrics" + "path": "../../../api-metrics" } ] } diff --git a/experimental/packages/opentelemetry-sdk-metrics/package.json b/experimental/packages/opentelemetry-sdk-metrics/package.json index 0dbb0f9cae..f53dc3cacf 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/package.json +++ b/experimental/packages/opentelemetry-sdk-metrics/package.json @@ -82,5 +82,6 @@ "@opentelemetry/resources": "1.7.0", "lodash.merge": "4.6.2" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-metrics" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-metrics", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts index e4f78d1cf0..2a686181cc 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -16,6 +16,7 @@ import * as api from '@opentelemetry/api'; import { + internal, ExportResultCode, globalErrorHandler, unrefTimer @@ -85,20 +86,12 @@ export class PeriodicExportingMetricReader extends MetricReader { api.diag.error('PeriodicExportingMetricReader: metrics collection errors', ...errors); } - return new Promise((resolve, reject) => { - this._exporter.export(resourceMetrics, result => { - if (result.code !== ExportResultCode.SUCCESS) { - reject( - result.error ?? - new Error( - `PeriodicExportingMetricReader: metrics export failed (error ${result.error})` - ) - ); - } else { - resolve(); - } - }); - }); + const result = await internal._export(this._exporter, resourceMetrics); + if (result.code !== ExportResultCode.SUCCESS) { + throw new Error( + `PeriodicExportingMetricReader: metrics export failed (error ${result.error})` + ); + } } protected override onInitialized(): void { diff --git a/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esm.json b/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esm.json index 53f9deefd0..fb267e4d26 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esm.json +++ b/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esm.json @@ -10,7 +10,7 @@ ], "references": [ { - "path": "../opentelemetry-api-metrics/tsconfig.esm.json" + "path": "../../../api-metrics/tsconfig.esnext.json" } ] } diff --git a/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esnext.json b/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esnext.json index 5b297a614a..dc405c942c 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esnext.json +++ b/experimental/packages/opentelemetry-sdk-metrics/tsconfig.esnext.json @@ -10,7 +10,7 @@ ], "references": [ { - "path": "../opentelemetry-api-metrics/tsconfig.esnext.json" + "path": "../../../api-metrics/tsconfig.esnext.json" } ] } diff --git a/experimental/packages/opentelemetry-sdk-metrics/tsconfig.json b/experimental/packages/opentelemetry-sdk-metrics/tsconfig.json index c56be210db..bcce5660d3 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/tsconfig.json +++ b/experimental/packages/opentelemetry-sdk-metrics/tsconfig.json @@ -13,13 +13,13 @@ "path": "../../../api" }, { - "path": "../../../packages/opentelemetry-core" + "path": "../../../api-metrics" }, { - "path": "../../../packages/opentelemetry-resources" + "path": "../../../packages/opentelemetry-core" }, { - "path": "../opentelemetry-api-metrics" + "path": "../../../packages/opentelemetry-resources" } ] } diff --git a/experimental/packages/opentelemetry-sdk-node/package.json b/experimental/packages/opentelemetry-sdk-node/package.json index b531360e89..8a60ba30b7 100644 --- a/experimental/packages/opentelemetry-sdk-node/package.json +++ b/experimental/packages/opentelemetry-sdk-node/package.json @@ -78,5 +78,6 @@ "ts-mocha": "10.0.0", "typescript": "4.4.4" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node", + "sideEffects": false } diff --git a/experimental/packages/opentelemetry-sdk-node/tsconfig.json b/experimental/packages/opentelemetry-sdk-node/tsconfig.json index 55448ab788..1ecf7ce067 100644 --- a/experimental/packages/opentelemetry-sdk-node/tsconfig.json +++ b/experimental/packages/opentelemetry-sdk-node/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../../api" }, + { + "path": "../../../api-metrics" + }, { "path": "../../../packages/opentelemetry-context-async-hooks" }, @@ -45,9 +48,6 @@ { "path": "../exporter-trace-otlp-proto" }, - { - "path": "../opentelemetry-api-metrics" - }, { "path": "../opentelemetry-instrumentation" }, diff --git a/experimental/packages/otlp-exporter-base/package.json b/experimental/packages/otlp-exporter-base/package.json index bca5ca1f2f..0d6eadfb0b 100644 --- a/experimental/packages/otlp-exporter-base/package.json +++ b/experimental/packages/otlp-exporter-base/package.json @@ -81,5 +81,6 @@ "peerDependencies": { "@opentelemetry/api": "^1.0.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-exporter-base" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-exporter-base", + "sideEffects": false } diff --git a/experimental/packages/otlp-grpc-exporter-base/package.json b/experimental/packages/otlp-grpc-exporter-base/package.json index 7f29e1be4f..b164cec8dd 100644 --- a/experimental/packages/otlp-grpc-exporter-base/package.json +++ b/experimental/packages/otlp-grpc-exporter-base/package.json @@ -71,10 +71,11 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@grpc/grpc-js": "^1.5.9", - "@grpc/proto-loader": "^0.6.9", + "@grpc/grpc-js": "^1.7.1", + "@grpc/proto-loader": "^0.7.3", "@opentelemetry/core": "1.7.0", "@opentelemetry/otlp-exporter-base": "0.33.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-grpc-exporter-base" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-grpc-exporter-base", + "sideEffects": false } diff --git a/experimental/packages/otlp-proto-exporter-base/package.json b/experimental/packages/otlp-proto-exporter-base/package.json index d41a554e10..aaec196080 100644 --- a/experimental/packages/otlp-proto-exporter-base/package.json +++ b/experimental/packages/otlp-proto-exporter-base/package.json @@ -63,10 +63,10 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@grpc/proto-loader": "^0.6.9", "@opentelemetry/core": "1.7.0", "@opentelemetry/otlp-exporter-base": "0.33.0", "protobufjs": "7.1.1" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-proto-exporter-base" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-proto-exporter-base", + "sideEffects": false } diff --git a/experimental/packages/otlp-transformer/package.json b/experimental/packages/otlp-transformer/package.json index 5b24ff3971..471c94cda9 100644 --- a/experimental/packages/otlp-transformer/package.json +++ b/experimental/packages/otlp-transformer/package.json @@ -82,5 +82,6 @@ "@opentelemetry/sdk-metrics": "0.33.0", "@opentelemetry/sdk-trace-base": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-transformer" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-transformer", + "sideEffects": false } diff --git a/experimental/packages/otlp-transformer/tsconfig.json b/experimental/packages/otlp-transformer/tsconfig.json index e06d7404a5..64be5006a0 100644 --- a/experimental/packages/otlp-transformer/tsconfig.json +++ b/experimental/packages/otlp-transformer/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../../api" }, + { + "path": "../../../api-metrics" + }, { "path": "../../../packages/opentelemetry-core" }, @@ -21,9 +24,6 @@ { "path": "../../../packages/opentelemetry-sdk-trace-base" }, - { - "path": "../opentelemetry-api-metrics" - }, { "path": "../opentelemetry-sdk-metrics" } diff --git a/lerna.json b/lerna.json index d97ab02a62..26c6e6b7bb 100644 --- a/lerna.json +++ b/lerna.json @@ -3,6 +3,7 @@ "npmClient": "npm", "packages": [ "api", + "api-metrics", "packages/*", "experimental/packages/*", "experimental/examples/*", diff --git a/packages/opentelemetry-context-async-hooks/package.json b/packages/opentelemetry-context-async-hooks/package.json index 7a9385f8ce..07c85471a0 100644 --- a/packages/opentelemetry-context-async-hooks/package.json +++ b/packages/opentelemetry-context-async-hooks/package.json @@ -57,5 +57,6 @@ "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.3.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-context-async-hooks" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-context-async-hooks", + "sideEffects": false } diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index 31412164ec..8334561f5b 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -93,5 +93,6 @@ "dependencies": { "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-core" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-core", + "sideEffects": false } diff --git a/packages/opentelemetry-core/src/index.ts b/packages/opentelemetry-core/src/index.ts index fd8d3c6778..6c0834fe0f 100644 --- a/packages/opentelemetry-core/src/index.ts +++ b/packages/opentelemetry-core/src/index.ts @@ -42,3 +42,7 @@ export * from './utils/url'; export * from './utils/wrap'; export * from './utils/callback'; export * from './version'; +import { _export } from './internal/exporter'; +export const internal = { + _export +}; diff --git a/packages/opentelemetry-core/src/internal/exporter.ts b/packages/opentelemetry-core/src/internal/exporter.ts new file mode 100644 index 0000000000..a489b35eac --- /dev/null +++ b/packages/opentelemetry-core/src/internal/exporter.ts @@ -0,0 +1,38 @@ +/* + * 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 { context } from '@opentelemetry/api'; +import { ExportResult } from '../ExportResult'; +import { suppressTracing } from '../trace/suppress-tracing'; + +export interface Exporter { + export(arg: T, resultCallback: (result: ExportResult) => void): void; +} + +/** +* @internal +* Shared functionality used by Exporters while exporting data, including suppresion of Traces. +*/ +export function _export(exporter: Exporter, arg: T): Promise { + return new Promise(resolve => { + // prevent downstream exporter calls from generating spans + context.with(suppressTracing(context.active()), () => { + exporter.export(arg, (result: ExportResult) => { + resolve(result); + }); + }); + }); +} diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index 8d50e2cb9b..fcb0614c61 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -106,6 +106,7 @@ export type ENVIRONMENT = { OTEL_EXPORTER_OTLP_PROTOCOL?: string, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL?: string, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL?: string, + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE?: string } & ENVIRONMENT_NUMBERS & ENVIRONMENT_LISTS; @@ -178,6 +179,7 @@ export const DEFAULT_ENVIRONMENT: Required = { OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf', OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: 'http/protobuf', OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: 'http/protobuf', + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: 'cumulative' }; /** diff --git a/packages/opentelemetry-core/test/internal/exporter.test.ts b/packages/opentelemetry-core/test/internal/exporter.test.ts new file mode 100644 index 0000000000..1ca256d659 --- /dev/null +++ b/packages/opentelemetry-core/test/internal/exporter.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { ExportResult, ExportResultCode } from '../../src'; +import * as suppress from '../../src/trace/suppress-tracing'; +import { _export } from '../../src/internal/exporter'; + +describe('exporter', () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + class TestExporter { + export(arg: any, resultCallback: (result: ExportResult) => void) { + resultCallback({ code: ExportResultCode.SUCCESS }); + } + } + + it('_export should suppress tracing', async () => { + const suppressSpy = sandbox.spy(suppress, 'suppressTracing'); + const exporter = new TestExporter(); + const result = await _export(exporter, ['Test1']); + assert.strictEqual(result.code, ExportResultCode.SUCCESS); + assert.ok(suppressSpy.calledOnce); + }); +}); diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index dc926c5695..0578ba2643 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -67,5 +67,6 @@ "@opentelemetry/semantic-conventions": "1.7.0", "jaeger-client": "^3.15.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-jaeger" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-jaeger", + "sideEffects": false } diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json index 15ce13dbc4..fbed197c0d 100644 --- a/packages/opentelemetry-exporter-zipkin/package.json +++ b/packages/opentelemetry-exporter-zipkin/package.json @@ -96,5 +96,6 @@ "@opentelemetry/sdk-trace-base": "1.7.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin", + "sideEffects": false } diff --git a/packages/opentelemetry-propagator-b3/package.json b/packages/opentelemetry-propagator-b3/package.json index 97e44060c8..a54d56e95e 100644 --- a/packages/opentelemetry-propagator-b3/package.json +++ b/packages/opentelemetry-propagator-b3/package.json @@ -69,5 +69,6 @@ "ts-mocha": "10.0.0", "typescript": "4.4.4" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-b3" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-b3", + "sideEffects": false } diff --git a/packages/opentelemetry-propagator-jaeger/package.json b/packages/opentelemetry-propagator-jaeger/package.json index c85137a3d1..98aba129a6 100644 --- a/packages/opentelemetry-propagator-jaeger/package.json +++ b/packages/opentelemetry-propagator-jaeger/package.json @@ -82,5 +82,6 @@ "dependencies": { "@opentelemetry/core": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-jaeger" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-jaeger", + "sideEffects": false } diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index e834d693a1..84cb29a7af 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -92,5 +92,6 @@ "@opentelemetry/core": "1.7.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources", + "sideEffects": false } diff --git a/packages/opentelemetry-sdk-trace-base/package.json b/packages/opentelemetry-sdk-trace-base/package.json index 5c8e879958..a062b9c4d7 100644 --- a/packages/opentelemetry-sdk-trace-base/package.json +++ b/packages/opentelemetry-sdk-trace-base/package.json @@ -95,5 +95,6 @@ "@opentelemetry/resources": "1.7.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-base" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-base", + "sideEffects": false } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index c775bdf6d4..a510ad02a6 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { context, Context, TraceFlags } from '@opentelemetry/api'; +import { Context, TraceFlags } from '@opentelemetry/api'; import { + internal, ExportResultCode, globalErrorHandler, - suppressTracing, BindOnceFuture, + ExportResult } from '@opentelemetry/core'; import { Span } from '../Span'; import { SpanProcessor } from '../SpanProcessor'; @@ -45,7 +46,7 @@ export class SimpleSpanProcessor implements SpanProcessor { } // does nothing. - onStart(_span: Span, _parentContext: Context): void {} + onStart(_span: Span, _parentContext: Context): void { } onEnd(span: ReadableSpan): void { if (this._shutdownOnce.isCalled) { @@ -56,18 +57,17 @@ export class SimpleSpanProcessor implements SpanProcessor { return; } - // prevent downstream exporter calls from generating spans - context.with(suppressTracing(context.active()), () => { - this._exporter.export([span], result => { - if (result.code !== ExportResultCode.SUCCESS) { - globalErrorHandler( - result.error ?? - new Error( - `SimpleSpanProcessor: span export failed (status ${result})` - ) - ); - } - }); + internal._export(this._exporter, [span]).then((result: ExportResult) => { + if (result.code !== ExportResultCode.SUCCESS) { + globalErrorHandler( + result.error ?? + new Error( + `SimpleSpanProcessor: span export failed (status ${result})` + ) + ); + } + }).catch(error => { + globalErrorHandler(error); }); } diff --git a/packages/opentelemetry-sdk-trace-node/package.json b/packages/opentelemetry-sdk-trace-node/package.json index 0c5b86ba7a..cbf5bd9e37 100644 --- a/packages/opentelemetry-sdk-trace-node/package.json +++ b/packages/opentelemetry-sdk-trace-node/package.json @@ -71,5 +71,6 @@ "@opentelemetry/sdk-trace-base": "1.7.0", "semver": "^7.3.5" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node", + "sideEffects": false } diff --git a/packages/opentelemetry-sdk-trace-web/package.json b/packages/opentelemetry-sdk-trace-web/package.json index 3c8d18b65b..65afa52b30 100644 --- a/packages/opentelemetry-sdk-trace-web/package.json +++ b/packages/opentelemetry-sdk-trace-web/package.json @@ -95,5 +95,6 @@ "@opentelemetry/sdk-trace-base": "1.7.0", "@opentelemetry/semantic-conventions": "1.7.0" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-web" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-web", + "sideEffects": false } diff --git a/packages/opentelemetry-semantic-conventions/package.json b/packages/opentelemetry-semantic-conventions/package.json index b9e59edd16..c7cbcc040b 100644 --- a/packages/opentelemetry-semantic-conventions/package.json +++ b/packages/opentelemetry-semantic-conventions/package.json @@ -61,5 +61,6 @@ "ts-mocha": "10.0.0", "typescript": "4.4.4" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-semantic-conventions" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-semantic-conventions", + "sideEffects": false } diff --git a/packages/opentelemetry-shim-opentracing/package.json b/packages/opentelemetry-shim-opentracing/package.json index 51ea010f0f..f73fb08774 100644 --- a/packages/opentelemetry-shim-opentracing/package.json +++ b/packages/opentelemetry-shim-opentracing/package.json @@ -63,5 +63,6 @@ "@opentelemetry/semantic-conventions": "1.7.0", "opentracing": "^0.14.4" }, - "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-shim-opentracing" + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-shim-opentracing", + "sideEffects": false } diff --git a/packages/template/package.json b/packages/template/package.json index 6b8cc9759e..77493ccde3 100644 --- a/packages/template/package.json +++ b/packages/template/package.json @@ -68,6 +68,7 @@ "LICENSE", "README.md" ], + "sideEffects": false, "Add these to files if browser is supported": [ "build/esm/**/*.js", "build/esm/**/*.js.map", diff --git a/tsconfig.json b/tsconfig.json index 10419db907..a1404aedcb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -53,6 +53,9 @@ { "path": "api" }, + { + "path": "api-metrics" + }, { "path": "packages/opentelemetry-context-async-hooks" }, @@ -110,9 +113,6 @@ { "path": "experimental/packages/exporter-trace-otlp-proto" }, - { - "path": "experimental/packages/opentelemetry-api-metrics" - }, { "path": "experimental/packages/opentelemetry-exporter-metrics-otlp-grpc" },