From df12218cc4c97ad951c5f8c59f1a9f14987c00fe Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 13 Oct 2021 01:33:24 +0200 Subject: [PATCH 01/15] feat: new merge function (#2484) * feat: new merge function * chore: updating info about lodash.merge util --- .../src/Meter.ts | 2 + .../src/MeterProvider.ts | 3 + packages/opentelemetry-core/package.json | 1 + packages/opentelemetry-core/src/index.ts | 1 + .../src/utils/lodash.merge.ts | 174 ++++++++ .../opentelemetry-core/src/utils/merge.ts | 189 +++++++++ .../test/utils/merge.test.ts | 387 ++++++++++++++++++ .../opentelemetry-sdk-trace-base/package.json | 4 +- .../src/BasicTracerProvider.ts | 3 +- 9 files changed, 759 insertions(+), 5 deletions(-) create mode 100644 packages/opentelemetry-core/src/utils/lodash.merge.ts create mode 100644 packages/opentelemetry-core/src/utils/merge.ts create mode 100644 packages/opentelemetry-core/test/utils/merge.test.ts diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts index 2ca48dfbf6..9a77e865bb 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts @@ -33,6 +33,8 @@ import { ValueObserverMetric } from './ValueObserverMetric'; import { ValueRecorderMetric } from './ValueRecorderMetric'; // eslint-disable-next-line @typescript-eslint/no-var-requires const merge = require('lodash.merge'); +// @TODO - replace once the core is released +// import { merge } from '@opentelemetry/core'; /** * Meter is an implementation of the {@link Meter} interface. diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts index 226ff63aa6..c7edefd0d7 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts @@ -20,6 +20,9 @@ import { Meter } from '.'; import { DEFAULT_CONFIG, MeterConfig } from './types'; // eslint-disable-next-line @typescript-eslint/no-var-requires const merge = require('lodash.merge'); +// @TODO - replace once the core is released +// import { merge } from '@opentelemetry/core'; + /** * This class represents a meter provider which platform libraries can extend diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index 9eecc139df..573ea927be 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -70,6 +70,7 @@ "karma-mocha": "2.0.1", "karma-spec-reporter": "0.0.32", "karma-webpack": "4.0.2", + "lerna": "3.22.1", "mocha": "7.2.0", "nyc": "15.1.0", "rimraf": "3.0.2", diff --git a/packages/opentelemetry-core/src/index.ts b/packages/opentelemetry-core/src/index.ts index 44c95f2fe9..ea9e2d2a37 100644 --- a/packages/opentelemetry-core/src/index.ts +++ b/packages/opentelemetry-core/src/index.ts @@ -35,6 +35,7 @@ export * from './trace/sampler/TraceIdRatioBasedSampler'; export * from './trace/suppress-tracing'; export * from './trace/TraceState'; export * from './utils/environment'; +export * from './utils/merge'; export * from './utils/sampling'; export * from './utils/url'; export * from './utils/wrap'; diff --git a/packages/opentelemetry-core/src/utils/lodash.merge.ts b/packages/opentelemetry-core/src/utils/lodash.merge.ts new file mode 100644 index 0000000000..0c55c7aa47 --- /dev/null +++ b/packages/opentelemetry-core/src/utils/lodash.merge.ts @@ -0,0 +1,174 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * based on lodash in order to support esm builds without esModuleInterop. + * lodash is using MIT License. + **/ + +const objectTag = '[object Object]'; +const nullTag = '[object Null]'; +const undefinedTag = '[object Undefined]'; +const funcProto = Function.prototype; +const funcToString = funcProto.toString; +const objectCtorString = funcToString.call(Object); +const getPrototype = overArg(Object.getPrototypeOf, Object); +const objectProto = Object.prototype; +const hasOwnProperty = objectProto.hasOwnProperty; +const symToStringTag = Symbol ? Symbol.toStringTag : undefined; +const nativeObjectToString = objectProto.toString; + +/** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ +function overArg(func: Function, transform: any): any { + return function(arg: any) { + return func(transform(arg)); + }; +} + +/** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * @static + * @memberOf _ + * @since 0.8.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ +export function isPlainObject(value: any) { + if (!isObjectLike(value) || baseGetTag(value) !== objectTag) { + return false; + } + const proto = getPrototype(value); + if (proto === null) { + return true; + } + const Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; + return typeof Ctor == 'function' && Ctor instanceof Ctor && + funcToString.call(Ctor) === objectCtorString; +} + +/** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ +function isObjectLike(value: any) { + return value != null && typeof value == 'object'; +} + +/** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ +function baseGetTag(value: any) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag; + } + return (symToStringTag && symToStringTag in Object(value)) + ? getRawTag(value) + : objectToString(value); +} + +/** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ +function getRawTag(value: any) { + const isOwn = hasOwnProperty.call(value, symToStringTag as any), + tag = value[symToStringTag as any]; + let unmasked = false; + + try { + value[symToStringTag as any] = undefined; + unmasked = true; + } catch (e) { + // silence + } + + const result = nativeObjectToString.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag as any] = tag; + } else { + delete value[symToStringTag as any]; + } + } + return result; +} + +/** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ +function objectToString(value: any) { + return nativeObjectToString.call(value); +} diff --git a/packages/opentelemetry-core/src/utils/merge.ts b/packages/opentelemetry-core/src/utils/merge.ts new file mode 100644 index 0000000000..ae01a953a8 --- /dev/null +++ b/packages/opentelemetry-core/src/utils/merge.ts @@ -0,0 +1,189 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { isPlainObject } from './lodash.merge'; + +const MAX_LEVEL = 20; + +interface ObjectInto { + obj: any; + key: string; +} + +/** + * Merges objects together + * @param args - objects / values to be merged + */ +export function merge(...args: any[]): any { + let result: any = args.shift(); + const objects: WeakMap | undefined = new WeakMap(); + while (args.length > 0) { + result = mergeTwoObjects(result, args.shift(), 0, objects); + } + + return result; +} + +function takeValue(value: any): any { + if (isArray(value)) { + return value.slice(); + } + return value; +} + +/** + * Merges two objects + * @param one - first object + * @param two - second object + * @param level - current deep level + * @param objects - objects holder that has been already referenced - to prevent + * cyclic dependency + */ +function mergeTwoObjects( + one: any, + two: any, + level = 0, + objects: WeakMap, +): any { + let result: any; + if (level > MAX_LEVEL) { + return undefined; + } + level++; + if (isPrimitive(one) || isPrimitive(two) || isFunction(two)) { + result = takeValue(two); + } else if (isArray(one)) { + result = one.slice(); + if (isArray(two)) { + for (let i = 0, j = two.length; i < j; i++) { + result.push(takeValue(two[i])); + } + } else if (isObject(two)) { + const keys = Object.keys(two); + for (let i = 0, j = keys.length; i < j; i++) { + const key = keys[i]; + result[key] = takeValue(two[key]); + } + } + } else if (isObject(one)) { + if (isObject(two)) { + if (!shouldMerge(one, two)) { + return two; + } + result = Object.assign({}, one); + const keys = Object.keys(two); + + for (let i = 0, j = keys.length; i < j; i++) { + const key = keys[i]; + const twoValue = two[key]; + + if (isPrimitive(twoValue)) { + if (typeof twoValue === 'undefined') { + delete result[key]; + } else { + // result[key] = takeValue(twoValue); + result[key] = twoValue; + } + } else { + const obj1 = result[key]; + const obj2 = twoValue; + + if ( + wasObjectReferenced(one, key, objects) || + wasObjectReferenced(two, key, objects) + ) { + delete result[key]; + } else { + + if (isObject(obj1) && isObject(obj2)) { + const arr1 = objects.get(obj1) || []; + const arr2 = objects.get(obj2) || []; + arr1.push({ obj: one, key }); + arr2.push({ obj: two, key }); + objects.set(obj1, arr1); + objects.set(obj2, arr2); + } + + result[key] = mergeTwoObjects( + result[key], + twoValue, + level, + objects + ); + } + } + } + } else { + result = two; + } + } + + return result; +} + +/** + * Function to check if object has been already reference + * @param obj + * @param key + * @param objects + */ +function wasObjectReferenced( + obj: any, + key: string, + objects: WeakMap, +): boolean { + const arr = objects.get(obj[key]) || []; + for (let i = 0, j = arr.length; i < j; i++) { + const info = arr[i]; + if (info.key === key && info.obj === obj) { + return true; + } + } + return false; +} + +function isArray(value: any): boolean { + return Array.isArray(value); +} + +function isFunction(value: any): boolean { + return typeof value === 'function'; +} + +function isObject(value: any): boolean { + return !isPrimitive(value) && !isArray(value) && !isFunction(value) && typeof value === 'object'; +} + +function isPrimitive(value: any): boolean { + return typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + typeof value === 'undefined' || + value instanceof Date || + value instanceof RegExp || + value === null; +} + +function shouldMerge(one: any, two: any): boolean { + if (!isPlainObject(one) || !isPlainObject(two)) { + return false; + } + + return true; +} + diff --git a/packages/opentelemetry-core/test/utils/merge.test.ts b/packages/opentelemetry-core/test/utils/merge.test.ts new file mode 100644 index 0000000000..d008ce2b21 --- /dev/null +++ b/packages/opentelemetry-core/test/utils/merge.test.ts @@ -0,0 +1,387 @@ +/* + * 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 { merge } from '../../src/utils/merge'; + +const tests: TestResult[] = []; + +tests.push({ + inputs: ['1', '2'], + result: '2', + desc: 'two strings' +}); +tests.push({ + inputs: [1, 2], + result: 2, + desc: 'two numbers' +}); +tests.push({ + inputs: [true, false], + result: false, + desc: 'two booleans' +}); +tests.push({ + inputs: [false, true], + result: true, + desc: 'two booleans case 2' +}); +tests.push({ + inputs: [undefined, undefined], + result: undefined, + desc: 'two undefined' +}); +tests.push({ + inputs: [null, null], + result: null, + desc: 'two nulls' +}); +tests.push({ + inputs: ['1', 1], + result: 1, + desc: 'string & number' +}); +tests.push({ + inputs: ['1', false], + result: false, + desc: 'string & boolean' +}); +tests.push({ + inputs: ['1', undefined], + result: undefined, + desc: 'string & undefined' +}); +tests.push({ + inputs: ['1', null], + result: null, + desc: 'string & null' +}); +tests.push({ + inputs: [3, '1'], + result: '1', + desc: 'number & string' +}); +tests.push({ + inputs: [3, false], + result: false, + desc: 'number & boolean' +}); +tests.push({ + inputs: [3, undefined], + result: undefined, + desc: 'number & undefined' +}); +tests.push({ + inputs: [3, null], + result: null, + desc: 'number & null' +}); +tests.push({ + inputs: [false, '3'], + result: '3', + desc: 'boolean & string' +}); +tests.push({ + inputs: [false, 3], + result: 3, + desc: 'boolean & number' +}); +tests.push({ + inputs: [false, undefined], + result: undefined, + desc: 'boolean & undefined' +}); +tests.push({ + inputs: [false, null], + result: null, + desc: 'boolean & null' +}); +tests.push({ + inputs: [undefined, '1'], + result: '1', + desc: 'undefined & string' +}); +tests.push({ + inputs: [undefined, 1], + result: 1, + desc: 'undefined & number' +}); +tests.push({ + inputs: [undefined, false], + result: false, + desc: 'undefined & boolean' +}); +tests.push({ + inputs: [undefined, null], + result: null, + desc: 'undefined & null' +}); +tests.push({ + inputs: [null, '1'], + result: '1', + desc: 'null & string' +}); +tests.push({ + inputs: [null, 1], + result: 1, + desc: 'null & number' +}); +tests.push({ + inputs: [null, false], + result: false, + desc: 'null & boolean' +}); +tests.push({ + inputs: [null, undefined], + result: undefined, + desc: 'null & undefined' +}); + +const date1 = new Date(327164400000); +const date2 = new Date(358700400000); +tests.push({ + inputs: [date1, date2], + result: date2, + desc: 'two dates' +}); + +tests.push({ + inputs: [/.+/g, /.a+/g], + result: /.a+/g, + desc: 'two regexp' +}); + +tests.push({ + inputs: [1, { a: 1 }], + result: { a: 1 }, + desc: 'primitive with object' +}); + +tests.push({ + inputs: [{ a: 1 }, 1], + result: 1, + desc: 'object with primitive' +}); + +const arrResult1: any = [1, 2, 3]; +arrResult1['foo'] = 1; +tests.push({ + inputs: [[1, 2, 3], { foo: 1 }], + result: arrResult1, + desc: 'array with object' +}); + +tests.push({ + inputs: [{ foo: 1 }, [1, 2, 3]], + result: [1, 2, 3], + desc: 'object with array' +}); + +tests.push({ + inputs: [{ a: 1, c: 1 }, { a: 2, b: 3 }], + result: { a: 2, b: 3, c: 1 }, + desc: 'two objects' +}); + +tests.push({ + inputs: [{ a: 1, c: 1 }, { a: 2, b: 3, c: { foo: 1 } }], + result: { a: 2, b: 3, c: { foo: 1 } }, + desc: 'two objects 2nd with nested' +}); + +tests.push({ + inputs: [ + { a: 1, c: { bar: 1, d: { bla: 2 } } }, + { a: 2, b: 3, c: { foo: 1 } } + ], + result: { a: 2, b: 3, c: { bar: 1, d: { bla: 2 }, foo: 1 } }, + desc: 'two objects with nested objects' +}); + +tests.push({ + inputs: [[1, 2, 3], [4, 5]], + result: [1, 2, 3, 4, 5], + desc: 'two arrays with numbers' +}); + +tests.push({ + inputs: [[1, 2, 3, { foo: 1 }], [4, 5, { foo: 2 }]], + result: [1, 2, 3, { foo: 1 }, 4, 5, { foo: 2 }], + desc: 'two arrays, with number and objects' +}); + +tests.push({ + inputs: [{ a: 1, c: 1 }, { a: 2, b: 3 }, { a: 3, c: 2, d: 1 }], + result: { a: 3, b: 3, c: 2, d: 1 }, + desc: 'three objects' +}); + +tests.push({ + inputs: [ + { a: 1, c: 1, foo: { bar1: 1 } }, + { a: 2, b: 3, foo: { bar1: 2 } }, + { a: 3, c: 2, d: 1, foo: { bar2: 1 } } + ], + result: { a: 3, b: 3, c: 2, d: 1, foo: { bar1: 2, bar2: 1 } }, + desc: 'three nested objects' +}); + +tests.push({ + inputs: [ + { a: 1, c: { bar: 1, d: { bla: 2 } } }, + { a: 2, b: 3, c: { foo: 1, bar: undefined } } + ], + result: { a: 2, b: 3, c: { d: { bla: 2 }, foo: 1 } }, + desc: 'two objects with nested objects and undefined' +}); + +class A { + constructor(private _name: string = 'foo') { + } + + getName() { + return this._name; + } +} + +class B extends A { + constructor(name = 'foo', private _ver = 1) { + super(name); + } + getVer(){ + return this._ver; + } +} + +const a = new A('foo'); +const b = new B('bar'); + +tests.push({ + inputs: [ + { a: 1, c: 1, foo: a, foo2: { a: 1 } }, + { a: 2, b: 3, foo: b, foo2: { b: 1, a: a } }, + ], + result: { a: 2, b: 3, c: 1, foo: b, foo2: {a: a, b: 1} }, + desc: 'two objects with nested objects and objects created from classes' +}); + +describe('merge', () => { + tests.forEach((test, index) => { + it(`should merge ${ test.desc }`, () => { + const result = merge(...test.inputs); + + assert.deepStrictEqual( + result, + test.result, + `test ${ index + 1 } '${ test.desc }' failed` + ); + }); + }); + + it('should create a shallow copy when merging plain objects', () => { + const a = { a: 1, c: 1, foo: { bar1: 1 } }; + const b = { b: 1, c: 2, foo: { bar2: 2 }, arr: [1, 2, 3] }; + + const result = merge(a, b); + a.a = 5; + b.b = 9; + b.arr.push(5); + + assert.deepStrictEqual( + result, + { a: 1, c: 2, foo: { bar1: 1, bar2: 2 }, b: 1, arr: [1, 2, 3] } + ); + }); + + it('should ignore cyclic reference', () => { + const a: any = { a: 1, c: 1, foo: { bar1: 1 } }; + a.f = a; + const b: any = { b: 1, c: 2, foo: { bar2: 2 }, arr: [1, 2, 3] }; + b.f = b; + + const result = merge(a, b); + assert.deepStrictEqual( + result, + { + a: 1, + c: 2, + foo: { bar1: 1, bar2: 2 }, + f: { a: 1, c: 2, b: 1, arr: [1, 2, 3] }, + b: 1, + arr: [1, 2, 3] + } + ); + }); + + it('should not fail for 1 argument', () => { + const result = merge(1); + assert.deepStrictEqual(result, 1); + }); + + it('should not fail for 0 arguments', () => { + const result = merge(); + assert.deepStrictEqual(result, undefined); + }); + + it('should merge function', () => { + const a = { + a: 1, b: 2 + }; + const b = { + a: 2, + c: function() { + return 'foo'; + }, + }; + const result = merge(a, b); + assert.deepStrictEqual(result, { + a: 2, b: 2, c: b.c + }); + }); + + it('should allow maximum of 20 levels deep', () => { + const a = {}; + const b = {}; + + function add(obj: any, added: any) { + obj.foo = added; + return obj.foo; + } + + let x = a; + let y = b; + for (let i = 0, j = 25; i < j; i++) { + const foo = { c: i + 1 }; + x = add(x, foo); + y = add(y, foo); + } + + const result = merge(a, b); + let check = result.foo; + let count = 0; + while (check.foo) { + count++; + check = check.foo; + } + assert.deepStrictEqual(count, 19); + }); + +}); + +interface TestResult { + desc: string; + inputs: any[]; + result: any; +} diff --git a/packages/opentelemetry-sdk-trace-base/package.json b/packages/opentelemetry-sdk-trace-base/package.json index 2926cd3a6a..9f52b0efa0 100644 --- a/packages/opentelemetry-sdk-trace-base/package.json +++ b/packages/opentelemetry-sdk-trace-base/package.json @@ -56,7 +56,6 @@ }, "devDependencies": { "@opentelemetry/api": "^1.0.2", - "@types/lodash.merge": "4.6.6", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -84,7 +83,6 @@ "dependencies": { "@opentelemetry/core": "1.0.0", "@opentelemetry/resources": "1.0.0", - "@opentelemetry/semantic-conventions": "1.0.0", - "lodash.merge": "^4.6.2" + "@opentelemetry/semantic-conventions": "1.0.0" } } diff --git a/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts b/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts index 4c028448b4..ac6b3445ee 100644 --- a/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts +++ b/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts @@ -27,6 +27,7 @@ import { W3CBaggagePropagator, W3CTraceContextPropagator, getEnv, + merge, } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; import { SpanProcessor, Tracer } from '.'; @@ -34,8 +35,6 @@ import { DEFAULT_CONFIG } from './config'; import { MultiSpanProcessor } from './MultiSpanProcessor'; import { NoopSpanProcessor } from './export/NoopSpanProcessor'; import { SDKRegistrationConfig, TracerConfig } from './types'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const merge = require('lodash.merge'); import { SpanExporter } from './export/SpanExporter'; import { BatchSpanProcessor } from './platform'; From 96dab9fc120b0151a954f22c0226a9aff4d53f4d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 15 Oct 2021 21:28:30 +0200 Subject: [PATCH 02/15] chore(deps): update typescript-eslint monorepo to v5 (#2537) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 56a145db98..03f45c704f 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "devDependencies": { "@commitlint/cli": "13.1.0", "@commitlint/config-conventional": "13.1.0", - "@typescript-eslint/eslint-plugin": "4.29.3", - "@typescript-eslint/parser": "4.29.3", + "@typescript-eslint/eslint-plugin": "5.0.0", + "@typescript-eslint/parser": "5.0.0", "beautify-benchmark": "0.2.4", "benchmark": "2.1.4", "eslint": "7.32.0", From f9f43e565af505377ca5c83790f782fb7270e06e Mon Sep 17 00:00:00 2001 From: legendecas Date: Sat, 16 Oct 2021 03:35:18 +0800 Subject: [PATCH 03/15] feat(api-metrics): rename metric instruments to match feature-freeze API specification (#2496) * feat: rename metric instruments to match feature-freeze API specification * fixup! rename observables * fixup! * fixup! * fixup! * fixup! observable naming * fixup! remove unnecessary spaces * fixup! add upgrade guidelines --- README.md | 23 + doc/processor-api.md | 2 +- examples/metrics/metrics/observer.js | 26 +- examples/otlp-exporter-node/metrics.js | 6 +- .../src/NoopMeter.ts | 95 ++--- .../src/api/global-utils.ts | 2 +- .../opentelemetry-api-metrics/src/index.ts | 2 +- .../src/types/BatchObserverResult.ts | 3 +- .../src/types/BoundInstrument.ts | 10 +- .../src/types/Meter.ts | 46 +- .../src/types/Metric.ts | 32 +- ...{ObserverResult.ts => ObservableResult.ts} | 4 +- .../src/types/Observation.ts | 6 +- .../noop-implementations/noop-meter.test.ts | 16 +- .../test/OTLPMetricExporter.test.ts | 48 +-- .../test/helper.ts | 40 +- .../src/transformMetrics.ts | 8 +- .../browser/CollectorMetricExporter.test.ts | 50 +-- .../common/CollectorMetricExporter.test.ts | 18 +- .../test/common/transformMetrics.test.ts | 114 ++--- .../test/helper.ts | 88 ++-- .../test/node/CollectorMetricExporter.test.ts | 44 +- .../test/OTLPMetricExporter.test.ts | 38 +- .../test/helper.ts | 40 +- .../src/PrometheusSerializer.ts | 4 +- .../test/PrometheusExporter.test.ts | 66 +-- .../test/PrometheusSerializer.test.ts | 72 ++-- .../opentelemetry-sdk-metrics-base/README.md | 74 ++-- .../src/BatchObserver.ts | 16 +- .../src/BatchObserverResult.ts | 4 +- .../src/BoundInstrument.ts | 10 +- ...ueRecorderMetric.ts => HistogramMetric.ts} | 16 +- .../src/Meter.ts | 74 ++-- ...erverMetric.ts => ObservableBaseMetric.ts} | 32 +- ...erMetric.ts => ObservableCounterMetric.ts} | 22 +- ...rverMetric.ts => ObservableGaugeMetric.ts} | 14 +- ...{ObserverResult.ts => ObservableResult.ts} | 6 +- ...ic.ts => ObservableUpDownCounterMetric.ts} | 14 +- .../src/export/Processor.ts | 8 +- .../src/export/types.ts | 8 +- .../src/index.ts | 4 +- .../test/Meter.test.ts | 398 +++++++++--------- 42 files changed, 813 insertions(+), 790 deletions(-) rename experimental/packages/opentelemetry-api-metrics/src/types/{ObserverResult.ts => ObservableResult.ts} (86%) rename experimental/packages/opentelemetry-sdk-metrics-base/src/{ValueRecorderMetric.ts => HistogramMetric.ts} (79%) rename experimental/packages/opentelemetry-sdk-metrics-base/src/{BaseObserverMetric.ts => ObservableBaseMetric.ts} (68%) rename experimental/packages/opentelemetry-sdk-metrics-base/src/{SumObserverMetric.ts => ObservableCounterMetric.ts} (72%) rename experimental/packages/opentelemetry-sdk-metrics-base/src/{ValueObserverMetric.ts => ObservableGaugeMetric.ts} (78%) rename experimental/packages/opentelemetry-sdk-metrics-base/src/{ObserverResult.ts => ObservableResult.ts} (83%) rename experimental/packages/opentelemetry-sdk-metrics-base/src/{UpDownSumObserverMetric.ts => ObservableUpDownCounterMetric.ts} (76%) diff --git a/README.md b/README.md index d5e2f0d917..ae4eb6f483 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,29 @@ To request automatic tracing support for a module not on this list, please [file ## Upgrade guidelines +### 0.26.x to 0.27.x + +Metric types are renamed: + +- `@openetelemetry/api-metrics` + - `Meter` + - `createValueRecorder` => `createHistogram` + - `createValueObserver` => `createObservableGauge` + - `createSumObserver` => `createObservableCounter` + - `createUpDownSumObserver` => `createObservableUpDownCounter` + - `ValueRecorder` => `Histogram` + - `ValueObserver` => `ObservableGauge` + - `SumObserver` => `ObservableCounter` + - `UpDownSumObserver` => `ObservableUpDownCounter` + - `ObserverResult` => `ObservableResult` + - `Observation.observer` => `Observation.observable` +- `@opentelemetry/sdk-metrics-base` + - `MetricKind` + - `VALUE_RECORDER` => `HISTOGRAM` + - `SUM_OBSERVER` => `OBSERVABLE_COUNTER` + - `UP_DOWN_SUM_OBSERVER` => `OBSERVABLE_UP_DOWN_COUNTER` + - `VALUE_OBSERVER` => `OBSERVABLE_GAUGE` + ### 0.25.x to 1.x.y Collector exporter packages and types are renamed: diff --git a/doc/processor-api.md b/doc/processor-api.md index b3abbcdecc..944834b038 100644 --- a/doc/processor-api.md +++ b/doc/processor-api.md @@ -138,7 +138,7 @@ const meter = new MeterProvider({ interval: 1000, }).getMeter('example-custom-processor'); -const requestsLatency = meter.createValueRecorder('requests', { +const requestsLatency = meter.createHistogram('requests', { monotonic: true, description: 'Average latency' }); diff --git a/examples/metrics/metrics/observer.js b/examples/metrics/metrics/observer.js index def56d872b..a31345f2a5 100644 --- a/examples/metrics/metrics/observer.js +++ b/examples/metrics/metrics/observer.js @@ -21,29 +21,29 @@ const exporter = new PrometheusExporter( const meter = new MeterProvider({ exporter, interval: 2000, -}).getMeter('example-observer'); +}).getMeter('example-meter'); -meter.createValueObserver('cpu_core_usage', { - description: 'Example of a sync value observer with callback', -}, async (observerResult) => { // this callback is called once per each interval +meter.createObservableGauge('cpu_core_usage', { + description: 'Example of a sync observable gauge with callback', +}, async (observableResult) => { // this callback is called once per each interval await new Promise((resolve) => { setTimeout(()=> {resolve()}, 50); }); - observerResult.observe(getRandomValue(), { core: '1' }); - observerResult.observe(getRandomValue(), { core: '2' }); + observableResult.observe(getRandomValue(), { core: '1' }); + observableResult.observe(getRandomValue(), { core: '2' }); }); // no callback as they will be updated in batch observer -const tempMetric = meter.createValueObserver('cpu_temp_per_app', { - description: 'Example of sync value observer used with async batch observer', +const tempMetric = meter.createObservableGauge('cpu_temp_per_app', { + description: 'Example of sync observable gauge used with async batch observer', }); // no callback as they will be updated in batch observer -const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', { - description: 'Example of sync value observer used with async batch observer', +const cpuUsageMetric = meter.createObservableGauge('cpu_usage_per_app', { + description: 'Example of sync observable gauge used with async batch observer', }); -meter.createBatchObserver((observerBatchResult) => { +meter.createBatchObserver((batchObserverResult) => { Promise.all([ someAsyncMetrics(), // simulate waiting @@ -52,11 +52,11 @@ meter.createBatchObserver((observerBatchResult) => { }), ]).then(([apps, waiting]) => { apps.forEach(app => { - observerBatchResult.observe({ app: app.name, core: '1' }, [ + batchObserverResult.observe({ app: app.name, core: '1' }, [ tempMetric.observation(app.core1.temp), cpuUsageMetric.observation(app.core1.usage), ]); - observerBatchResult.observe({ app: app.name, core: '2' }, [ + batchObserverResult.observe({ app: app.name, core: '2' }, [ tempMetric.observation(app.core2.temp), cpuUsageMetric.observation(app.core2.usage), ]); diff --git a/examples/otlp-exporter-node/metrics.js b/examples/otlp-exporter-node/metrics.js index e0065aa697..3a2294f43f 100644 --- a/examples/otlp-exporter-node/metrics.js +++ b/examples/otlp-exporter-node/metrics.js @@ -31,8 +31,8 @@ const upDownCounter = meter.createUpDownCounter('test_up_down_counter', { description: 'Example of a UpDownCounter', }); -const recorder = meter.createValueRecorder('test_value_recorder', { - description: 'Example of a ValueRecorder', +const histogram = meter.createHistogram('test_histogram', { + description: 'Example of a Histogram', }); const labels = { pid: process.pid, environment: 'staging' }; @@ -40,5 +40,5 @@ const labels = { pid: process.pid, environment: 'staging' }; setInterval(() => { requestCounter.bind(labels).add(1); upDownCounter.bind(labels).add(Math.random() > 0.5 ? 1 : -1); - recorder.bind(labels).record(Math.random()); + histogram.bind(labels).record(Math.random()); }, 1000); diff --git a/experimental/packages/opentelemetry-api-metrics/src/NoopMeter.ts b/experimental/packages/opentelemetry-api-metrics/src/NoopMeter.ts index 6e0fd5bd20..775ad78f00 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/NoopMeter.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/NoopMeter.ts @@ -21,18 +21,19 @@ import { UnboundMetric, Labels, Counter, - ValueRecorder, - ValueObserver, + Histogram, + ObservableGauge, UpDownCounter, - BaseObserver, - UpDownSumObserver, + ObservableBase, + ObservableCounter, + ObservableUpDownCounter, } from './types/Metric'; import { - BoundValueRecorder, + BoundHistogram, BoundCounter, - BoundBaseObserver, + BoundObservableBase, } from './types/BoundInstrument'; -import { ObserverResult } from './types/ObserverResult'; +import { ObservableResult } from './types/ObservableResult'; import { Observation } from './types/Observation'; /** @@ -43,12 +44,12 @@ export class NoopMeter implements Meter { constructor() {} /** - * Returns constant noop value recorder. + * Returns a constant noop histogram. * @param name the name of the metric. * @param [options] the metric options. */ - createValueRecorder(_name: string, _options?: MetricOptions): ValueRecorder { - return NOOP_VALUE_RECORDER_METRIC; + createHistogram(_name: string, _options?: MetricOptions): Histogram { + return NOOP_HISTOGRAM_METRIC; } /** @@ -70,45 +71,45 @@ export class NoopMeter implements Meter { } /** - * Returns constant noop value observer. + * Returns a constant noop observable gauge. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the value observer callback + * @param [callback] the observable gauge callback */ - createValueObserver( + createObservableGauge( _name: string, _options?: MetricOptions, - _callback?: (observerResult: ObserverResult) => void - ): ValueObserver { - return NOOP_VALUE_OBSERVER_METRIC; + _callback?: (observableResult: ObservableResult) => void + ): ObservableGauge { + return NOOP_OBSERVABLE_GAUGE_METRIC; } /** - * Returns constant noop sum observer. + * Returns a constant noop observable counter. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the sum observer callback + * @param [callback] the observable counter callback */ - createSumObserver( + createObservableCounter( _name: string, _options?: MetricOptions, - _callback?: (observerResult: ObserverResult) => void - ): ValueObserver { - return NOOP_SUM_OBSERVER_METRIC; + _callback?: (observableResult: ObservableResult) => void + ): ObservableCounter { + return NOOP_OBSERVABLE_COUNTER_METRIC; } /** - * Returns constant noop up down sum observer. + * Returns a constant noop up down observable counter. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the up down sum observer callback + * @param [callback] the up down observable counter callback */ - createUpDownSumObserver( + createObservableUpDownCounter( _name: string, _options?: MetricOptions, - _callback?: (observerResult: ObserverResult) => void - ): UpDownSumObserver { - return NOOP_UP_DOWN_SUM_OBSERVER_METRIC; + _callback?: (observableResult: ObservableResult) => void + ): ObservableUpDownCounter { + return NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC; } /** @@ -165,20 +166,20 @@ export class NoopCounterMetric } } -export class NoopValueRecorderMetric - extends NoopMetric - implements ValueRecorder { +export class NoopHistogramMetric + extends NoopMetric + implements Histogram { record(value: number, labels: Labels): void { this.bind(labels).record(value); } } -export class NoopBaseObserverMetric - extends NoopMetric - implements BaseObserver { +export class NoopObservableBaseMetric + extends NoopMetric + implements ObservableBase { observation(): Observation { return { - observer: this as BaseObserver, + observable: this as ObservableBase, value: 0, }; } @@ -192,13 +193,13 @@ export class NoopBoundCounter implements BoundCounter { } } -export class NoopBoundValueRecorder implements BoundValueRecorder { +export class NoopBoundHistogram implements BoundHistogram { record(_value: number, _baggage?: unknown, _spanContext?: unknown): void { return; } } -export class NoopBoundBaseObserver implements BoundBaseObserver { +export class NoopBoundObservableBase implements BoundObservableBase { update(_value: number): void {} } @@ -206,22 +207,22 @@ export const NOOP_METER = new NoopMeter(); export const NOOP_BOUND_COUNTER = new NoopBoundCounter(); export const NOOP_COUNTER_METRIC = new NoopCounterMetric(NOOP_BOUND_COUNTER); -export const NOOP_BOUND_VALUE_RECORDER = new NoopBoundValueRecorder(); -export const NOOP_VALUE_RECORDER_METRIC = new NoopValueRecorderMetric( - NOOP_BOUND_VALUE_RECORDER +export const NOOP_BOUND_HISTOGRAM = new NoopBoundHistogram(); +export const NOOP_HISTOGRAM_METRIC = new NoopHistogramMetric( + NOOP_BOUND_HISTOGRAM ); -export const NOOP_BOUND_BASE_OBSERVER = new NoopBoundBaseObserver(); -export const NOOP_VALUE_OBSERVER_METRIC = new NoopBaseObserverMetric( - NOOP_BOUND_BASE_OBSERVER +export const NOOP_BOUND_OBSERVABLE_BASE = new NoopBoundObservableBase(); +export const NOOP_OBSERVABLE_GAUGE_METRIC = new NoopObservableBaseMetric( + NOOP_BOUND_OBSERVABLE_BASE ); -export const NOOP_UP_DOWN_SUM_OBSERVER_METRIC = new NoopBaseObserverMetric( - NOOP_BOUND_BASE_OBSERVER +export const NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC = new NoopObservableBaseMetric( + NOOP_BOUND_OBSERVABLE_BASE ); -export const NOOP_SUM_OBSERVER_METRIC = new NoopBaseObserverMetric( - NOOP_BOUND_BASE_OBSERVER +export const NOOP_OBSERVABLE_COUNTER_METRIC = new NoopObservableBaseMetric( + NOOP_BOUND_OBSERVABLE_BASE ); export const NOOP_BATCH_OBSERVER = new NoopBatchObserver(); diff --git a/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts b/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts index a23f76396d..e371d5165d 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/api/global-utils.ts @@ -52,4 +52,4 @@ export function makeGetter( * 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 = 3; +export const API_BACKWARDS_COMPATIBILITY_VERSION = 4; diff --git a/experimental/packages/opentelemetry-api-metrics/src/index.ts b/experimental/packages/opentelemetry-api-metrics/src/index.ts index de39eb0821..2a7a0dd255 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/index.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/index.ts @@ -22,7 +22,7 @@ export * from './types/Meter'; export * from './types/MeterProvider'; export * from './types/Metric'; export * from './types/Observation'; -export * from './types/ObserverResult'; +export * from './types/ObservableResult'; import { MetricsAPI } from './api/metrics'; /** Entrypoint for metrics API */ diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/BatchObserverResult.ts b/experimental/packages/opentelemetry-api-metrics/src/types/BatchObserverResult.ts index bae99eb866..971156a774 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/types/BatchObserverResult.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/types/BatchObserverResult.ts @@ -18,8 +18,7 @@ import { Labels } from './Metric'; import { Observation } from './Observation'; /** - * Interface that is being used in callback function for Observer Metric - * for batch + * Interface that is being used in callback function for BatchObserver */ export interface BatchObserverResult { /** diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/BoundInstrument.ts b/experimental/packages/opentelemetry-api-metrics/src/types/BoundInstrument.ts index 0d5554771e..378c934988 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/types/BoundInstrument.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/types/BoundInstrument.ts @@ -23,16 +23,16 @@ export interface BoundCounter { add(value: number): void; } -/** ValueRecorder to report instantaneous measurement of a value. */ -export interface BoundValueRecorder { +/** Histogram to report instantaneous measurement of a value. */ +export interface BoundHistogram { /** - * Records the given value to this value recorder. + * Records the given value to this histogram. * @param value to record. */ record(value: number): void; } -/** An Instrument for Base Observer */ -export interface BoundBaseObserver { +/** An Instrument for Base Observable */ +export interface BoundObservableBase { update(value: number): void; } diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/Meter.ts b/experimental/packages/opentelemetry-api-metrics/src/types/Meter.ts index 27428f2554..a522840ec5 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/types/Meter.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/types/Meter.ts @@ -18,29 +18,29 @@ import { BatchObserverResult } from './BatchObserverResult'; import { MetricOptions, Counter, - ValueRecorder, - ValueObserver, + Histogram, + ObservableGauge, BatchObserverOptions, UpDownCounter, - SumObserver, - UpDownSumObserver, + ObservableCounter, + ObservableUpDownCounter, } from './Metric'; -import { ObserverResult } from './ObserverResult'; +import { ObservableResult } from './ObservableResult'; /** * An interface to allow the recording metrics. * * {@link Metric}s are used for recording pre-defined aggregation (`Counter`), - * or raw values (`ValueRecorder`) in which the aggregation and labels + * or raw values (`Histogram`) in which the aggregation and labels * for the exported metric are deferred. */ export interface Meter { /** - * Creates and returns a new `ValueRecorder`. + * Creates and returns a new `Histogram`. * @param name the name of the metric. * @param [options] the metric options. */ - createValueRecorder(name: string, options?: MetricOptions): ValueRecorder; + createHistogram(name: string, options?: MetricOptions): Histogram; /** * Creates a new `Counter` metric. Generally, this kind of metric when the @@ -71,40 +71,40 @@ export interface Meter { createUpDownCounter(name: string, options?: MetricOptions): UpDownCounter; /** - * Creates a new `ValueObserver` metric. + * Creates a new `ObservableGauge` metric. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the observer callback + * @param [callback] the observable callback */ - createValueObserver( + createObservableGauge( name: string, options?: MetricOptions, - callback?: (observerResult: ObserverResult) => void - ): ValueObserver; + callback?: (observableResult: ObservableResult) => void + ): ObservableGauge; /** - * Creates a new `SumObserver` metric. + * Creates a new `ObservableCounter` metric. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the observer callback + * @param [callback] the observable callback */ - createSumObserver( + createObservableCounter( name: string, options?: MetricOptions, - callback?: (observerResult: ObserverResult) => void - ): SumObserver; + callback?: (observableResult: ObservableResult) => void + ): ObservableCounter; /** - * Creates a new `UpDownSumObserver` metric. + * Creates a new `ObservableUpDownCounter` metric. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the observer callback + * @param [callback] the observable callback */ - createUpDownSumObserver( + createObservableUpDownCounter( name: string, options?: MetricOptions, - callback?: (observerResult: ObserverResult) => void - ): UpDownSumObserver; + callback?: (observableResult: ObservableResult) => void + ): ObservableUpDownCounter; /** * Creates a new `BatchObserver`, can be used to update many metrics diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/Metric.ts b/experimental/packages/opentelemetry-api-metrics/src/types/Metric.ts index aebbc46246..55b51218f8 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/types/Metric.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/types/Metric.ts @@ -15,10 +15,13 @@ */ import { - BoundBaseObserver, + BoundObservableBase, BoundCounter, - BoundValueRecorder, + BoundHistogram, } from './BoundInstrument'; +import { + Observation, +} from './Observation'; /** * Options needed for metric creation @@ -146,31 +149,28 @@ export interface UpDownCounter extends UnboundMetric { add(value: number, labels?: Labels): void; } -export interface ValueRecorder extends UnboundMetric { +export interface Histogram extends UnboundMetric { /** - * Records the given value to this value recorder. + * Records the given value to this histogram. */ record(value: number, labels?: Labels): void; } -/** Base interface for the Observer metrics. */ -export interface BaseObserver extends UnboundMetric { +/** Base interface for the Observable metrics. */ +export interface ObservableBase extends UnboundMetric { observation: ( value: number - ) => { - value: number; - observer: BaseObserver; - }; + ) => Observation; } -/** Base interface for the ValueObserver metrics. */ -export type ValueObserver = BaseObserver; +/** Base interface for the ObservableGauge metrics. */ +export type ObservableGauge = ObservableBase; -/** Base interface for the UpDownSumObserver metrics. */ -export type UpDownSumObserver = BaseObserver; +/** Base interface for the ObservableUpDownCounter metrics. */ +export type ObservableUpDownCounter = ObservableBase; -/** Base interface for the SumObserver metrics. */ -export type SumObserver = BaseObserver; +/** Base interface for the ObservableCounter metrics. */ +export type ObservableCounter = ObservableBase; /** * key-value pairs passed by the user. diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/ObserverResult.ts b/experimental/packages/opentelemetry-api-metrics/src/types/ObservableResult.ts similarity index 86% rename from experimental/packages/opentelemetry-api-metrics/src/types/ObserverResult.ts rename to experimental/packages/opentelemetry-api-metrics/src/types/ObservableResult.ts index 7792483ad1..c909833ab9 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/types/ObserverResult.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/types/ObservableResult.ts @@ -17,8 +17,8 @@ import { Labels } from './Metric'; /** - * Interface that is being used in callback function for Observer Metric + * Interface that is being used in callback function for Observable Metric */ -export interface ObserverResult { +export interface ObservableResult { observe(value: number, labels: Labels): void; } diff --git a/experimental/packages/opentelemetry-api-metrics/src/types/Observation.ts b/experimental/packages/opentelemetry-api-metrics/src/types/Observation.ts index d36f48fb71..1e805f3689 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/types/Observation.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/types/Observation.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { BaseObserver } from './Metric'; +import { ObservableBase } from './Metric'; /** - * Interface for updating value of certain value observer + * Interface for updating value of certain observable */ export interface Observation { - observer: BaseObserver; + observable: ObservableBase; value: number; } diff --git a/experimental/packages/opentelemetry-api-metrics/test/noop-implementations/noop-meter.test.ts b/experimental/packages/opentelemetry-api-metrics/test/noop-implementations/noop-meter.test.ts index cbfe044a9f..07b2e7b402 100644 --- a/experimental/packages/opentelemetry-api-metrics/test/noop-implementations/noop-meter.test.ts +++ b/experimental/packages/opentelemetry-api-metrics/test/noop-implementations/noop-meter.test.ts @@ -18,9 +18,9 @@ import * as assert from 'assert'; import { NoopMeterProvider, NOOP_BOUND_COUNTER, - NOOP_BOUND_VALUE_RECORDER, + NOOP_BOUND_HISTOGRAM, NOOP_COUNTER_METRIC, - NOOP_VALUE_RECORDER_METRIC, + NOOP_HISTOGRAM_METRIC, } from '../../src'; describe('NoopMeter', () => { @@ -38,23 +38,23 @@ describe('NoopMeter', () => { assert.strictEqual(counter.bind(labels), NOOP_BOUND_COUNTER); counter.clear(); - const valueRecorder = meter.createValueRecorder('some-name'); - valueRecorder.bind(labels).record(1); + const histogram = meter.createHistogram('some-name'); + histogram.bind(labels).record(1); // ensure the correct noop const is returned - assert.strictEqual(valueRecorder, NOOP_VALUE_RECORDER_METRIC); - assert.strictEqual(valueRecorder.bind(labels), NOOP_BOUND_VALUE_RECORDER); + assert.strictEqual(histogram, NOOP_HISTOGRAM_METRIC); + assert.strictEqual(histogram.bind(labels), NOOP_BOUND_HISTOGRAM); const options = { component: 'tests', description: 'the testing package', }; - const valueRecorderWithOptions = meter.createValueRecorder( + const histogramWithOptions = meter.createHistogram( 'some-name', options ); - assert.strictEqual(valueRecorderWithOptions, NOOP_VALUE_RECORDER_METRIC); + assert.strictEqual(histogramWithOptions, NOOP_HISTOGRAM_METRIC); const counterWithOptions = meter.createCounter('some-name', options); assert.strictEqual(counterWithOptions, NOOP_COUNTER_METRIC); }); diff --git a/experimental/packages/opentelemetry-exporter-otlp-grpc/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-otlp-grpc/test/OTLPMetricExporter.test.ts index 86b8e1cef3..83d8723d4f 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-grpc/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-grpc/test/OTLPMetricExporter.test.ts @@ -17,8 +17,8 @@ import * as protoLoader from '@grpc/proto-loader'; import { Counter, - ValueObserver, - ValueRecorder, + ObservableGauge, + Histogram, } from '@opentelemetry/api-metrics'; import { diag } from '@opentelemetry/api'; import { otlpTypes } from '@opentelemetry/exporter-otlp-http'; @@ -31,13 +31,13 @@ import * as sinon from 'sinon'; import { OTLPMetricExporter } from '../src'; import { ensureExportedCounterIsCorrect, - ensureExportedObserverIsCorrect, - ensureExportedValueRecorderIsCorrect, + ensureExportedObservableGaugeIsCorrect, + ensureExportedHistogramIsCorrect, ensureMetadataIsCorrect, ensureResourceIsCorrect, mockCounter, - mockObserver, - mockValueRecorder, + mockObservableGauge, + mockHistogram, } from './helper'; const metricsServiceProtoPath = @@ -140,21 +140,21 @@ const testOTLPMetricExporter = (params: TestParams) => metrics = []; const counter: metrics.Metric & Counter = mockCounter(); - const observer: metrics.Metric & - ValueObserver = mockObserver(observerResult => { - observerResult.observe(3, {}); - observerResult.observe(6, {}); + const observableGauge: metrics.Metric & + ObservableGauge = mockObservableGauge(observableResult => { + observableResult.observe(3, {}); + observableResult.observe(6, {}); }); - const recorder: metrics.Metric & - ValueRecorder = mockValueRecorder(); + const histogram: metrics.Metric & + Histogram = mockHistogram(); counter.add(1); - recorder.record(7); - recorder.record(14); + histogram.record(7); + histogram.record(14); metrics.push((await counter.getMetricRecord())[0]); - metrics.push((await observer.getMetricRecord())[0]); - metrics.push((await recorder.getMetricRecord())[0]); + metrics.push((await observableGauge.getMetricRecord())[0]); + metrics.push((await histogram.getMetricRecord())[0]); }); afterEach(() => { @@ -203,21 +203,21 @@ const testOTLPMetricExporter = (params: TestParams) => resource = exportedData[0].resource; const counter = exportedData[0].instrumentationLibraryMetrics[0].metrics[0]; - const observer = + const observableGauge = exportedData[0].instrumentationLibraryMetrics[0].metrics[1]; - const recorder = + const histogram = exportedData[0].instrumentationLibraryMetrics[0].metrics[2]; ensureExportedCounterIsCorrect( counter, counter.intSum?.dataPoints[0].timeUnixNano ); - ensureExportedObserverIsCorrect( - observer, - observer.doubleGauge?.dataPoints[0].timeUnixNano + ensureExportedObservableGaugeIsCorrect( + observableGauge, + observableGauge.doubleGauge?.dataPoints[0].timeUnixNano ); - ensureExportedValueRecorderIsCorrect( - recorder, - recorder.intHistogram?.dataPoints[0].timeUnixNano, + ensureExportedHistogramIsCorrect( + histogram, + histogram.intHistogram?.dataPoints[0].timeUnixNano, [0, 100], ['0', '2', '0'] ); diff --git a/experimental/packages/opentelemetry-exporter-otlp-grpc/test/helper.ts b/experimental/packages/opentelemetry-exporter-otlp-grpc/test/helper.ts index e77f0f1742..48325a12d4 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-grpc/test/helper.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-grpc/test/helper.ts @@ -17,9 +17,9 @@ import { SpanStatusCode, TraceFlags } from '@opentelemetry/api'; import { Counter, - ObserverResult, - ValueObserver, - ValueRecorder, + ObservableResult, + ObservableGauge, + Histogram, ValueType, } from '@opentelemetry/api-metrics'; import { otlpTypes } from '@opentelemetry/exporter-otlp-http'; @@ -75,16 +75,16 @@ export function mockCounter(): metrics.Metric & Counter { return metric; } -export function mockObserver( - callback: (observerResult: ObserverResult) => void -): metrics.Metric & ValueObserver { - const name = 'double-observer'; +export function mockObservableGauge( + callback: (observableResult: ObservableResult) => void +): metrics.Metric & ObservableGauge { + const name = 'double-observable-gauge'; const metric = meter['_metrics'].get(name) || - meter.createValueObserver( + meter.createObservableGauge( name, { - description: 'sample observer description', + description: 'sample observable gauge description', valueType: ValueType.DOUBLE, }, callback @@ -94,13 +94,13 @@ export function mockObserver( return metric; } -export function mockValueRecorder(): metrics.Metric & - ValueRecorder { - const name = 'int-recorder'; +export function mockHistogram(): metrics.Metric & + Histogram { + const name = 'int-histogram'; const metric = meter['_metrics'].get(name) || - meter.createValueRecorder(name, { - description: 'sample recorder description', + meter.createHistogram(name, { + description: 'sample histogram description', valueType: ValueType.INT, boundaries: [0, 100], }); @@ -352,13 +352,13 @@ export function ensureExportedCounterIsCorrect( }); } -export function ensureExportedObserverIsCorrect( +export function ensureExportedObservableGaugeIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time?: number ) { assert.deepStrictEqual(metric, { - name: 'double-observer', - description: 'sample observer description', + name: 'double-observable-gauge', + description: 'sample observable gauge description', unit: '1', data: 'doubleGauge', doubleGauge: { @@ -375,15 +375,15 @@ export function ensureExportedObserverIsCorrect( }); } -export function ensureExportedValueRecorderIsCorrect( +export function ensureExportedHistogramIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time?: number, explicitBounds: number[] = [Infinity], bucketCounts: string[] = ['2', '0'] ) { assert.deepStrictEqual(metric, { - name: 'int-recorder', - description: 'sample recorder description', + name: 'int-histogram', + description: 'sample histogram description', unit: '1', data: 'intHistogram', intHistogram: { diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/src/transformMetrics.ts b/experimental/packages/opentelemetry-exporter-otlp-http/src/transformMetrics.ts index cc0f580883..97a0e1ec3e 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/src/transformMetrics.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-http/src/transformMetrics.ts @@ -47,7 +47,7 @@ export function toCollectorLabels( export function toAggregationTemporality( metric: MetricRecord ): opentelemetryProto.metrics.v1.AggregationTemporality { - if (metric.descriptor.metricKind === MetricKind.VALUE_OBSERVER) { + if (metric.descriptor.metricKind === MetricKind.OBSERVABLE_GAUGE) { return opentelemetryProto.metrics.v1.AggregationTemporality .AGGREGATION_TEMPORALITY_UNSPECIFIED; } @@ -115,14 +115,14 @@ export function toCollectorMetric( if ( metric.aggregator.kind === AggregatorKind.SUM || - metric.descriptor.metricKind === MetricKind.SUM_OBSERVER || - metric.descriptor.metricKind === MetricKind.UP_DOWN_SUM_OBSERVER + metric.descriptor.metricKind === MetricKind.OBSERVABLE_COUNTER || + metric.descriptor.metricKind === MetricKind.OBSERVABLE_UP_DOWN_COUNTER ) { const result = { dataPoints: [toDataPoint(metric, startTime)], isMonotonic: metric.descriptor.metricKind === MetricKind.COUNTER || - metric.descriptor.metricKind === MetricKind.SUM_OBSERVER, + metric.descriptor.metricKind === MetricKind.OBSERVABLE_COUNTER, aggregationTemporality: toAggregationTemporality(metric), }; if (metric.descriptor.valueType === ValueType.INT) { diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/test/browser/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-otlp-http/test/browser/CollectorMetricExporter.test.ts index 34463695c8..a2d289a7fe 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/test/browser/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-http/test/browser/CollectorMetricExporter.test.ts @@ -17,14 +17,14 @@ import { diag } from '@opentelemetry/api'; import { Counter, - ValueObserver, - ValueRecorder, + ObservableGauge, + Histogram, } from '@opentelemetry/api-metrics'; import { ExportResultCode, hrTimeToNanoseconds } from '@opentelemetry/core'; import { BoundCounter, - BoundObserver, - BoundValueRecorder, + BoundObservable, + BoundHistogram, Metric, MetricRecord, } from '@opentelemetry/sdk-metrics-base'; @@ -37,12 +37,12 @@ import { ensureCounterIsCorrect, ensureExportMetricsServiceRequestIsSet, ensureHeadersContain, - ensureObserverIsCorrect, - ensureValueRecorderIsCorrect, + ensureObservableGaugeIsCorrect, + ensureHistogramIsCorrect, ensureWebResourceIsCorrect, mockCounter, - mockObserver, - mockValueRecorder, + mockObservableGauge, + mockHistogram, } from '../helper'; describe('OTLPMetricExporter - web', () => { @@ -57,22 +57,22 @@ describe('OTLPMetricExporter - web', () => { stubBeacon = sinon.stub(navigator, 'sendBeacon'); metrics = []; const counter: Metric & Counter = mockCounter(); - const observer: Metric & ValueObserver = mockObserver( - observerResult => { - observerResult.observe(3, {}); - observerResult.observe(6, {}); + const observableGauge: Metric & ObservableGauge = mockObservableGauge( + observableResult => { + observableResult.observe(3, {}); + observableResult.observe(6, {}); }, - 'double-observer2' + 'double-observable-gauge2' ); - const recorder: Metric & - ValueRecorder = mockValueRecorder(); + const histogram: Metric & + Histogram = mockHistogram(); counter.add(1); - recorder.record(7); - recorder.record(14); + histogram.record(7); + histogram.record(14); metrics.push((await counter.getMetricRecord())[0]); - metrics.push((await observer.getMetricRecord())[0]); - metrics.push((await recorder.getMetricRecord())[0]); + metrics.push((await observableGauge.getMetricRecord())[0]); + metrics.push((await histogram.getMetricRecord())[0]); }); afterEach(() => { @@ -121,11 +121,11 @@ describe('OTLPMetricExporter - web', () => { "second metric doesn't exist" ); if (metric2) { - ensureObserverIsCorrect( + ensureObservableGaugeIsCorrect( metric2, hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp), 6, - 'double-observer2' + 'double-observable-gauge2' ); } @@ -134,7 +134,7 @@ describe('OTLPMetricExporter - web', () => { "third metric doesn't exist" ); if (metric3) { - ensureValueRecorderIsCorrect( + ensureHistogramIsCorrect( metric3, hrTimeToNanoseconds(metrics[2].aggregator.toPoint().timestamp), [0, 100], @@ -234,11 +234,11 @@ describe('OTLPMetricExporter - web', () => { "second metric doesn't exist" ); if (metric2) { - ensureObserverIsCorrect( + ensureObservableGaugeIsCorrect( metric2, hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp), 6, - 'double-observer2' + 'double-observable-gauge2' ); } @@ -247,7 +247,7 @@ describe('OTLPMetricExporter - web', () => { "third metric doesn't exist" ); if (metric3) { - ensureValueRecorderIsCorrect( + ensureHistogramIsCorrect( metric3, hrTimeToNanoseconds(metrics[2].aggregator.toPoint().timestamp), [0, 100], diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/test/common/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-otlp-http/test/common/CollectorMetricExporter.test.ts index 119702c683..7282f538c0 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/test/common/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-http/test/common/CollectorMetricExporter.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Counter, ValueObserver } from '@opentelemetry/api-metrics'; +import { Counter, ObservableGauge } from '@opentelemetry/api-metrics'; import { ExportResultCode } from '@opentelemetry/core'; import { BoundCounter, - BoundObserver, + BoundObservable, Metric, MetricRecord, } from '@opentelemetry/sdk-metrics-base'; @@ -27,7 +27,7 @@ import * as sinon from 'sinon'; import { OTLPExporterBase } from '../../src/OTLPExporterBase'; import * as otlpTypes from '../../src/types'; import { OTLPExporterConfigBase } from '../../src/types'; -import { mockCounter, mockObserver } from '../helper'; +import { mockCounter, mockObservableGauge } from '../helper'; type CollectorExporterConfig = OTLPExporterConfigBase; class OTLPMetricExporter extends OTLPExporterBase< @@ -70,17 +70,17 @@ describe('OTLPMetricExporter - common', () => { collectorExporter = new OTLPMetricExporter(collectorExporterConfig); metrics = []; const counter: Metric & Counter = mockCounter(); - const observer: Metric & ValueObserver = mockObserver( - observerResult => { - observerResult.observe(3, {}); - observerResult.observe(6, {}); + const observableGauge: Metric & ObservableGauge = mockObservableGauge( + observableResult => { + observableResult.observe(3, {}); + observableResult.observe(6, {}); }, - 'double-observer3' + 'double-observable-gauge3' ); counter.add(1); metrics.push((await counter.getMetricRecord())[0]); - metrics.push((await observer.getMetricRecord())[0]); + metrics.push((await observableGauge.getMetricRecord())[0]); }); it('should create an instance', () => { diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/test/common/transformMetrics.test.ts b/experimental/packages/opentelemetry-exporter-otlp-http/test/common/transformMetrics.test.ts index e94a6cf71d..71ce5ab274 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/test/common/transformMetrics.test.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-http/test/common/transformMetrics.test.ts @@ -15,16 +15,16 @@ */ import { Counter, - SumObserver, - UpDownSumObserver, - ValueObserver, - ValueRecorder, + ObservableCounter, + ObservableUpDownCounter, + ObservableGauge, + Histogram, } from '@opentelemetry/api-metrics'; import { hrTimeToNanoseconds } from '@opentelemetry/core'; import { BoundCounter, - BoundObserver, - BoundValueRecorder, + BoundObservable, + BoundHistogram, Metric, SumAggregator, } from '@opentelemetry/sdk-metrics-base'; @@ -34,18 +34,18 @@ import * as transform from '../../src/transformMetrics'; import { ensureCounterIsCorrect, ensureDoubleCounterIsCorrect, - ensureObserverIsCorrect, - ensureSumObserverIsCorrect, - ensureUpDownSumObserverIsCorrect, - ensureValueRecorderIsCorrect, + ensureObservableGaugeIsCorrect, + ensureObservableCounterIsCorrect, + ensureObservableUpDownCounterIsCorrect, + ensureHistogramIsCorrect, mockCounter, mockDoubleCounter, mockedInstrumentationLibraries, mockedResources, - mockObserver, - mockSumObserver, - mockUpDownSumObserver, - mockValueRecorder, + mockObservableGauge, + mockObservableCounter, + mockObservableUpDownCounter, + mockHistogram, multiInstrumentationLibraryMetricsGet, multiResourceMetricsGet, } from '../helper'; @@ -54,10 +54,10 @@ describe('transformMetrics', () => { describe('toCollectorMetric', async () => { let counter: Metric & Counter; let doubleCounter: Metric & Counter; - let observer: Metric & ValueObserver; - let sumObserver: Metric & SumObserver; - let upDownSumObserver: Metric & UpDownSumObserver; - let recorder: Metric & ValueRecorder; + let observableGauge: Metric & ObservableGauge; + let observableCounter: Metric & ObservableCounter; + let observableUpDownCounter: Metric & ObservableUpDownCounter; + let histogram: Metric & Histogram; beforeEach(() => { counter = mockCounter(); doubleCounter = mockDoubleCounter(); @@ -72,22 +72,22 @@ describe('transformMetrics', () => { return -1; } - observer = mockObserver(observerResult => { + observableGauge = mockObservableGauge(observableResult => { count1++; - observerResult.observe(getValue(count1), {}); + observableResult.observe(getValue(count1), {}); }); - sumObserver = mockSumObserver(observerResult => { + observableCounter = mockObservableCounter(observableResult => { count2++; - observerResult.observe(getValue(count2), {}); + observableResult.observe(getValue(count2), {}); }); - upDownSumObserver = mockUpDownSumObserver(observerResult => { + observableUpDownCounter = mockObservableUpDownCounter(observableResult => { count3++; - observerResult.observe(getValue(count3), {}); + observableResult.observe(getValue(count3), {}); }); - recorder = mockValueRecorder(); + histogram = mockHistogram(); // Counter counter.add(1); @@ -95,9 +95,9 @@ describe('transformMetrics', () => { // Double Counter doubleCounter.add(8); - // ValueRecorder - recorder.record(7); - recorder.record(14); + // Histogram + histogram.record(7); + histogram.record(14); }); it('should convert metric', async () => { @@ -113,46 +113,46 @@ describe('transformMetrics', () => { hrTimeToNanoseconds(doubleCounterMetric.aggregator.toPoint().timestamp) ); - await observer.getMetricRecord(); - await observer.getMetricRecord(); - const observerMetric = (await observer.getMetricRecord())[0]; - ensureObserverIsCorrect( - transform.toCollectorMetric(observerMetric, 1592602232694000000), - hrTimeToNanoseconds(observerMetric.aggregator.toPoint().timestamp), + await observableGauge.getMetricRecord(); + await observableGauge.getMetricRecord(); + const observableGaugeMetric = (await observableGauge.getMetricRecord())[0]; + ensureObservableGaugeIsCorrect( + transform.toCollectorMetric(observableGaugeMetric, 1592602232694000000), + hrTimeToNanoseconds(observableGaugeMetric.aggregator.toPoint().timestamp), -1 ); // collect 3 times - await sumObserver.getMetricRecord(); - await sumObserver.getMetricRecord(); - const sumObserverMetric = (await sumObserver.getMetricRecord())[0]; - ensureSumObserverIsCorrect( - transform.toCollectorMetric(sumObserverMetric, 1592602232694000000), - hrTimeToNanoseconds(sumObserverMetric.aggregator.toPoint().timestamp), + await observableCounter.getMetricRecord(); + await observableCounter.getMetricRecord(); + const observableCounterMetric = (await observableCounter.getMetricRecord())[0]; + ensureObservableCounterIsCorrect( + transform.toCollectorMetric(observableCounterMetric, 1592602232694000000), + hrTimeToNanoseconds(observableCounterMetric.aggregator.toPoint().timestamp), 3 ); // collect 3 times - await upDownSumObserver.getMetricRecord(); - await upDownSumObserver.getMetricRecord(); - const upDownSumObserverMetric = ( - await upDownSumObserver.getMetricRecord() + await observableUpDownCounter.getMetricRecord(); + await observableUpDownCounter.getMetricRecord(); + const observableUpDownCounterMetric = ( + await observableUpDownCounter.getMetricRecord() )[0]; - ensureUpDownSumObserverIsCorrect( + ensureObservableUpDownCounterIsCorrect( transform.toCollectorMetric( - upDownSumObserverMetric, + observableUpDownCounterMetric, 1592602232694000000 ), hrTimeToNanoseconds( - upDownSumObserverMetric.aggregator.toPoint().timestamp + observableUpDownCounterMetric.aggregator.toPoint().timestamp ), -1 ); - const recorderMetric = (await recorder.getMetricRecord())[0]; - ensureValueRecorderIsCorrect( - transform.toCollectorMetric(recorderMetric, 1592602232694000000), - hrTimeToNanoseconds(recorderMetric.aggregator.toPoint().timestamp), + const histogramMetric = (await histogram.getMetricRecord())[0]; + ensureHistogramIsCorrect( + transform.toCollectorMetric(histogramMetric, 1592602232694000000), + hrTimeToNanoseconds(histogramMetric.aggregator.toPoint().timestamp), [0, 100], [0, 2, 0] ); @@ -186,8 +186,8 @@ describe('transformMetrics', () => { const [resource1, resource2] = mockedResources; const [library] = mockedInstrumentationLibraries; const [metric1, metric2, metric3] = multiResourceMetricsGet( - observerResult => { - observerResult.observe(1, {}); + observableResult => { + observableResult.observe(1, {}); } ); @@ -197,8 +197,8 @@ describe('transformMetrics', () => { ]); const result = transform.groupMetricsByResourceAndLibrary( - multiResourceMetricsGet(observerResult => { - observerResult.observe(1, {}); + multiResourceMetricsGet(observableResult => { + observableResult.observe(1, {}); }) ); @@ -212,7 +212,7 @@ describe('transformMetrics', () => { metric1, metric2, metric3, - ] = multiInstrumentationLibraryMetricsGet(observerResult => {}); + ] = multiInstrumentationLibraryMetricsGet(observableResult => {}); const expected = new Map([ [ resource, @@ -224,7 +224,7 @@ describe('transformMetrics', () => { ]); const result = transform.groupMetricsByResourceAndLibrary( - multiInstrumentationLibraryMetricsGet(observerResult => {}) + multiInstrumentationLibraryMetricsGet(observableResult => {}) ); assert.deepStrictEqual(result, expected); diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/test/helper.ts b/experimental/packages/opentelemetry-exporter-otlp-http/test/helper.ts index 8783906034..5befa3515f 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/test/helper.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-http/test/helper.ts @@ -17,11 +17,11 @@ import { SpanStatusCode, TraceFlags } from '@opentelemetry/api'; import { Counter, - ObserverResult, - SumObserver, - UpDownSumObserver, - ValueObserver, - ValueRecorder, + ObservableResult, + ObservableCounter, + ObservableUpDownCounter, + ObservableGauge, + Histogram, ValueType, } from '@opentelemetry/api-metrics'; import { hexToBase64, InstrumentationLibrary, VERSION } from '@opentelemetry/core'; @@ -78,16 +78,16 @@ export function mockDoubleCounter(): metrics.Metric & return metric; } -export function mockObserver( - callback: (observerResult: ObserverResult) => unknown, - name = 'double-observer' -): metrics.Metric & ValueObserver { +export function mockObservableGauge( + callback: (observableResult: ObservableResult) => unknown, + name = 'double-observable-gauge' +): metrics.Metric & ObservableGauge { const metric = meter['_metrics'].get(name) || - meter.createValueObserver( + meter.createObservableGauge( name, { - description: 'sample observer description', + description: 'sample observable gauge description', valueType: ValueType.DOUBLE, }, callback @@ -97,16 +97,16 @@ export function mockObserver( return metric; } -export function mockSumObserver( - callback: (observerResult: ObserverResult) => unknown, - name = 'double-sum-observer' -): metrics.Metric & SumObserver { +export function mockObservableCounter( + callback: (observableResult: ObservableResult) => unknown, + name = 'double-observable-counter' +): metrics.Metric & ObservableCounter { const metric = meter['_metrics'].get(name) || - meter.createSumObserver( + meter.createObservableCounter( name, { - description: 'sample sum observer description', + description: 'sample observable counter description', valueType: ValueType.DOUBLE, }, callback @@ -116,16 +116,16 @@ export function mockSumObserver( return metric; } -export function mockUpDownSumObserver( - callback: (observerResult: ObserverResult) => unknown, - name = 'double-up-down-sum-observer' -): metrics.Metric & UpDownSumObserver { +export function mockObservableUpDownCounter( + callback: (observableResult: ObservableResult) => unknown, + name = 'double-up-down-observable-counter' +): metrics.Metric & ObservableUpDownCounter { const metric = meter['_metrics'].get(name) || - meter.createUpDownSumObserver( + meter.createObservableUpDownCounter( name, { - description: 'sample up down sum observer description', + description: 'sample observable up down counter description', valueType: ValueType.DOUBLE, }, callback @@ -135,13 +135,13 @@ export function mockUpDownSumObserver( return metric; } -export function mockValueRecorder(): metrics.Metric & - ValueRecorder { - const name = 'int-recorder'; +export function mockHistogram(): metrics.Metric & + Histogram { + const name = 'int-histogram'; const metric = meter['_metrics'].get(name) || - meter.createValueRecorder(name, { - description: 'sample recorder description', + meter.createHistogram(name, { + description: 'sample histogram description', valueType: ValueType.INT, boundaries: [0, 100], }); @@ -313,7 +313,7 @@ export const multiResourceTrace: ReadableSpan[] = [ ]; export const multiResourceMetricsGet = function ( - callback: (observerResult: ObserverResult) => unknown + callback: (observableResult: ObservableResult) => unknown ): any[] { return [ { @@ -322,7 +322,7 @@ export const multiResourceMetricsGet = function ( instrumentationLibrary: mockedInstrumentationLibraries[0], }, { - ...mockObserver(callback), + ...mockObservableGauge(callback), resource: mockedResources[1], instrumentationLibrary: mockedInstrumentationLibraries[0], }, @@ -335,7 +335,7 @@ export const multiResourceMetricsGet = function ( }; export const multiInstrumentationLibraryMetricsGet = function ( - callback: (observerResult: ObserverResult) => unknown + callback: (observableResult: ObservableResult) => unknown ): any[] { return [ { @@ -344,7 +344,7 @@ export const multiInstrumentationLibraryMetricsGet = function ( instrumentationLibrary: mockedInstrumentationLibraries[0], }, { - ...mockObserver(callback), + ...mockObservableGauge(callback), resource: mockedResources[0], instrumentationLibrary: mockedInstrumentationLibraries[1], }, @@ -601,15 +601,15 @@ export function ensureDoubleCounterIsCorrect( }); } -export function ensureObserverIsCorrect( +export function ensureObservableGaugeIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time: number, value: number, - name = 'double-observer' + name = 'double-observable-gauge' ) { assert.deepStrictEqual(metric, { name, - description: 'sample observer description', + description: 'sample observable gauge description', unit: '1', doubleGauge: { dataPoints: [ @@ -624,15 +624,15 @@ export function ensureObserverIsCorrect( }); } -export function ensureSumObserverIsCorrect( +export function ensureObservableCounterIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time: number, value: number, - name = 'double-sum-observer' + name = 'double-observable-counter' ) { assert.deepStrictEqual(metric, { name, - description: 'sample sum observer description', + description: 'sample observable counter description', unit: '1', doubleSum: { isMonotonic: true, @@ -651,15 +651,15 @@ export function ensureSumObserverIsCorrect( }); } -export function ensureUpDownSumObserverIsCorrect( +export function ensureObservableUpDownCounterIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time: number, value: number, - name = 'double-up-down-sum-observer' + name = 'double-up-down-observable-counter' ) { assert.deepStrictEqual(metric, { name, - description: 'sample up down sum observer description', + description: 'sample observable up down counter description', unit: '1', doubleSum: { isMonotonic: false, @@ -678,15 +678,15 @@ export function ensureUpDownSumObserverIsCorrect( }); } -export function ensureValueRecorderIsCorrect( +export function ensureHistogramIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time: number, explicitBounds: (number | null)[] = [Infinity], bucketCounts: number[] = [2, 0] ) { assert.deepStrictEqual(metric, { - name: 'int-recorder', - description: 'sample recorder description', + name: 'int-histogram', + description: 'sample histogram description', unit: '1', intHistogram: { dataPoints: [ diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/test/node/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-otlp-http/test/node/CollectorMetricExporter.test.ts index 4937ef3737..11308aeef2 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/test/node/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-http/test/node/CollectorMetricExporter.test.ts @@ -17,14 +17,14 @@ import { diag } from '@opentelemetry/api'; import { Counter, - ValueObserver, - ValueRecorder, + ObservableGauge, + Histogram, } from '@opentelemetry/api-metrics'; import * as core from '@opentelemetry/core'; import { BoundCounter, - BoundObserver, - BoundValueRecorder, + BoundObservable, + BoundHistogram, Metric, MetricRecord, } from '@opentelemetry/sdk-metrics-base'; @@ -39,11 +39,11 @@ import * as otlpTypes from '../../src/types'; import { ensureCounterIsCorrect, ensureExportMetricsServiceRequestIsSet, - ensureObserverIsCorrect, - ensureValueRecorderIsCorrect, + ensureObservableGaugeIsCorrect, + ensureHistogramIsCorrect, mockCounter, - mockObserver, - mockValueRecorder, + mockObservableGauge, + mockHistogram, } from '../helper'; import { MockedResponse } from './nodeHelpers'; @@ -149,21 +149,21 @@ describe('OTLPMetricExporter - node with json over http', () => { }); metrics = []; const counter: Metric & Counter = mockCounter(); - const observer: Metric & ValueObserver = mockObserver( - observerResult => { - observerResult.observe(6, {}); + const observableGauge: Metric & ObservableGauge = mockObservableGauge( + observableResult => { + observableResult.observe(6, {}); }, - 'double-observer2' + 'double-observable-gauge2' ); - const recorder: Metric & - ValueRecorder = mockValueRecorder(); + const histogram: Metric & + Histogram = mockHistogram(); counter.add(1); - recorder.record(7); - recorder.record(14); + histogram.record(7); + histogram.record(14); metrics.push((await counter.getMetricRecord())[0]); - metrics.push((await observer.getMetricRecord())[0]); - metrics.push((await recorder.getMetricRecord())[0]); + metrics.push((await observableGauge.getMetricRecord())[0]); + metrics.push((await histogram.getMetricRecord())[0]); }); it('should open the connection', done => { @@ -224,15 +224,15 @@ describe('OTLPMetricExporter - node with json over http', () => { metric1, core.hrTimeToNanoseconds(metrics[0].aggregator.toPoint().timestamp) ); - assert.ok(typeof metric2 !== 'undefined', "observer doesn't exist"); - ensureObserverIsCorrect( + assert.ok(typeof metric2 !== 'undefined', "observable gauge doesn't exist"); + ensureObservableGaugeIsCorrect( metric2, core.hrTimeToNanoseconds(metrics[1].aggregator.toPoint().timestamp), 6, - 'double-observer2' + 'double-observable-gauge2' ); assert.ok(typeof metric3 !== 'undefined', "histogram doesn't exist"); - ensureValueRecorderIsCorrect( + ensureHistogramIsCorrect( metric3, core.hrTimeToNanoseconds(metrics[2].aggregator.toPoint().timestamp), [0, 100], diff --git a/experimental/packages/opentelemetry-exporter-otlp-proto/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-otlp-proto/test/OTLPMetricExporter.test.ts index 17716f0887..b623d2d819 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-proto/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-proto/test/OTLPMetricExporter.test.ts @@ -17,8 +17,8 @@ import { diag } from '@opentelemetry/api'; import { Counter, - ValueObserver, - ValueRecorder, + ObservableGauge, + Histogram, } from '@opentelemetry/api-metrics'; import { ExportResultCode } from '@opentelemetry/core'; import { @@ -33,13 +33,13 @@ import { OTLPMetricExporter } from '../src'; import { getExportRequestProto } from '../src/util'; import { ensureExportedCounterIsCorrect, - ensureExportedObserverIsCorrect, - ensureExportedValueRecorderIsCorrect, + ensureExportedObservableGaugeIsCorrect, + ensureExportedHistogramIsCorrect, ensureExportMetricsServiceRequestIsSet, mockCounter, MockedResponse, - mockObserver, - mockValueRecorder, + mockObservableGauge, + mockHistogram, } from './helper'; const fakeRequest = { @@ -121,21 +121,21 @@ describe('OTLPMetricExporter - node with proto over http', () => { metrics = []; const counter: metrics.Metric & Counter = mockCounter(); - const observer: metrics.Metric & - ValueObserver = mockObserver(observerResult => { - observerResult.observe(3, {}); - observerResult.observe(6, {}); + const observableGauge: metrics.Metric & + ObservableGauge = mockObservableGauge(observableResult => { + observableResult.observe(3, {}); + observableResult.observe(6, {}); }); - const recorder: metrics.Metric & - ValueRecorder = mockValueRecorder(); + const histogram: metrics.Metric & + Histogram = mockHistogram(); counter.add(1); - recorder.record(7); - recorder.record(14); + histogram.record(7); + histogram.record(14); metrics.push((await counter.getMetricRecord())[0]); - metrics.push((await observer.getMetricRecord())[0]); - metrics.push((await recorder.getMetricRecord())[0]); + metrics.push((await observableGauge.getMetricRecord())[0]); + metrics.push((await histogram.getMetricRecord())[0]); }); afterEach(() => { sinon.restore(); @@ -197,8 +197,8 @@ describe('OTLPMetricExporter - node with proto over http', () => { metric1, metric1.intSum?.dataPoints[0].timeUnixNano ); - assert.ok(typeof metric2 !== 'undefined', "observer doesn't exist"); - ensureExportedObserverIsCorrect( + assert.ok(typeof metric2 !== 'undefined', "observable gauge doesn't exist"); + ensureExportedObservableGaugeIsCorrect( metric2, metric2.doubleGauge?.dataPoints[0].timeUnixNano ); @@ -206,7 +206,7 @@ describe('OTLPMetricExporter - node with proto over http', () => { typeof metric3 !== 'undefined', "value recorder doesn't exist" ); - ensureExportedValueRecorderIsCorrect( + ensureExportedHistogramIsCorrect( metric3, metric3.intHistogram?.dataPoints[0].timeUnixNano, [0, 100], diff --git a/experimental/packages/opentelemetry-exporter-otlp-proto/test/helper.ts b/experimental/packages/opentelemetry-exporter-otlp-proto/test/helper.ts index f8b90b9fef..f76b8619b0 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-proto/test/helper.ts +++ b/experimental/packages/opentelemetry-exporter-otlp-proto/test/helper.ts @@ -17,9 +17,9 @@ import { SpanStatusCode, TraceFlags } from '@opentelemetry/api'; import { Counter, - ObserverResult, - ValueObserver, - ValueRecorder, + ObservableResult, + ObservableGauge, + Histogram, ValueType, } from '@opentelemetry/api-metrics'; import { hexToBase64 } from '@opentelemetry/core'; @@ -54,16 +54,16 @@ export function mockCounter(): metrics.Metric & Counter { return metric; } -export function mockObserver( - callback: (observerResult: ObserverResult) => void -): metrics.Metric & ValueObserver { - const name = 'double-observer'; +export function mockObservableGauge( + callback: (observableResult: ObservableResult) => void +): metrics.Metric & ObservableGauge { + const name = 'double-observable-gauge'; const metric = meter['_metrics'].get(name) || - meter.createValueObserver( + meter.createObservableGauge( name, { - description: 'sample observer description', + description: 'sample observable gauge description', valueType: ValueType.DOUBLE, }, callback @@ -73,13 +73,13 @@ export function mockObserver( return metric; } -export function mockValueRecorder(): metrics.Metric & - ValueRecorder { - const name = 'int-recorder'; +export function mockHistogram(): metrics.Metric & + Histogram { + const name = 'int-histogram'; const metric = meter['_metrics'].get(name) || - meter.createValueRecorder(name, { - description: 'sample recorder description', + meter.createHistogram(name, { + description: 'sample histogram description', valueType: ValueType.INT, boundaries: [0, 100], }); @@ -316,13 +316,13 @@ export function ensureExportedCounterIsCorrect( }); } -export function ensureExportedObserverIsCorrect( +export function ensureExportedObservableGaugeIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time?: number ) { assert.deepStrictEqual(metric, { - name: 'double-observer', - description: 'sample observer description', + name: 'double-observable-gauge', + description: 'sample observable gauge description', unit: '1', doubleGauge: { dataPoints: [ @@ -336,15 +336,15 @@ export function ensureExportedObserverIsCorrect( }); } -export function ensureExportedValueRecorderIsCorrect( +export function ensureExportedHistogramIsCorrect( metric: otlpTypes.opentelemetryProto.metrics.v1.Metric, time?: number, explicitBounds: number[] = [Infinity], bucketCounts: string[] = ['2', '0'] ) { assert.deepStrictEqual(metric, { - name: 'int-recorder', - description: 'sample recorder description', + name: 'int-histogram', + description: 'sample histogram description', unit: '1', intHistogram: { dataPoints: [ diff --git a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts index e62ba0478a..35473b14a3 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts @@ -106,11 +106,11 @@ function toPrometheusType( case AggregatorKind.SUM: if ( metricKind === MetricKind.COUNTER || - metricKind === MetricKind.SUM_OBSERVER + metricKind === MetricKind.OBSERVABLE_COUNTER ) { return 'counter'; } - /** MetricKind.UP_DOWN_COUNTER and MetricKind.UP_DOWN_SUM_OBSERVER */ + /** MetricKind.UP_DOWN_COUNTER and MetricKind.OBSERVABLE_UP_DOWN_COUNTER */ return 'gauge'; case AggregatorKind.LAST_VALUE: return 'gauge'; diff --git a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts index 11d0090861..3b5f044be3 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ObserverResult } from '@opentelemetry/api-metrics'; +import { ObservableResult } from '@opentelemetry/api-metrics'; import { CounterMetric, SumAggregator, @@ -270,18 +270,18 @@ describe('PrometheusExporter', () => { }); }); - it('should export an observer aggregation', done => { + it('should export an observable gauge aggregation', done => { function getCpuUsage() { return 0.999; } - meter.createValueObserver( - 'metric_observer', + meter.createObservableGauge( + 'metric_observable_gauge', { description: 'a test description', }, - (observerResult: ObserverResult) => { - observerResult.observe(getCpuUsage(), { + (observableResult: ObservableResult) => { + observableResult.observe(getCpuUsage(), { pid: String(123), core: '1', }); @@ -298,9 +298,9 @@ describe('PrometheusExporter', () => { const lines = body.split('\n'); assert.deepStrictEqual(lines, [ - '# HELP metric_observer a test description', - '# TYPE metric_observer gauge', - `metric_observer{pid="123",core="1"} 0.999 ${mockedHrTimeMs}`, + '# HELP metric_observable_gauge a test description', + '# TYPE metric_observable_gauge gauge', + `metric_observable_gauge{pid="123",core="1"} 0.999 ${mockedHrTimeMs}`, '', ]); done(); @@ -472,18 +472,18 @@ describe('PrometheusExporter', () => { }); }); - it('should export a SumObserver as a counter', done => { + it('should export an ObservableCounter as a counter', done => { function getValue() { return 20; } - meter.createSumObserver( - 'sum_observer', + meter.createObservableCounter( + 'metric_observable_counter', { description: 'a test description', }, - (observerResult: ObserverResult) => { - observerResult.observe(getValue(), { + (observableResult: ObservableResult) => { + observableResult.observe(getValue(), { key1: 'labelValue1', }); } @@ -498,9 +498,9 @@ describe('PrometheusExporter', () => { const lines = body.split('\n'); assert.deepStrictEqual(lines, [ - '# HELP sum_observer a test description', - '# TYPE sum_observer gauge', - `sum_observer{key1="labelValue1"} 20 ${mockedHrTimeMs}`, + '# HELP metric_observable_counter a test description', + '# TYPE metric_observable_counter gauge', + `metric_observable_counter{key1="labelValue1"} 20 ${mockedHrTimeMs}`, '', ]); }); @@ -512,18 +512,18 @@ describe('PrometheusExporter', () => { }); }); - it('should export a UpDownSumObserver as a gauge', done => { + it('should export an ObservableUpDownCounter as a gauge', done => { function getValue() { return 20; } - meter.createUpDownSumObserver( - 'updown_observer', + meter.createObservableUpDownCounter( + 'metric_observable_up_down_counter', { description: 'a test description', }, - (observerResult: ObserverResult) => { - observerResult.observe(getValue(), { + (observableResult: ObservableResult) => { + observableResult.observe(getValue(), { key1: 'labelValue1', }); } @@ -538,9 +538,9 @@ describe('PrometheusExporter', () => { const lines = body.split('\n'); assert.deepStrictEqual(lines, [ - '# HELP updown_observer a test description', - '# TYPE updown_observer gauge', - `updown_observer{key1="labelValue1"} 20 ${mockedHrTimeMs}`, + '# HELP metric_observable_up_down_counter a test description', + '# TYPE metric_observable_up_down_counter gauge', + `metric_observable_up_down_counter{key1="labelValue1"} 20 ${mockedHrTimeMs}`, '', ]); }); @@ -552,12 +552,12 @@ describe('PrometheusExporter', () => { }); }); - it('should export a ValueRecorder as a summary', done => { - const valueRecorder = meter.createValueRecorder('value_recorder', { + it('should export a Histogram as a summary', done => { + const histogram = meter.createHistogram('test_histogram', { description: 'a test description', }); - valueRecorder.bind({ key1: 'labelValue1' }).record(20); + histogram.bind({ key1: 'labelValue1' }).record(20); meter.collect().then(() => { exporter.export(meter.getProcessor().checkPointSet(), () => { @@ -568,11 +568,11 @@ describe('PrometheusExporter', () => { const lines = body.split('\n'); assert.deepStrictEqual(lines, [ - '# HELP value_recorder a test description', - '# TYPE value_recorder histogram', - `value_recorder_count{key1="labelValue1"} 1 ${mockedHrTimeMs}`, - `value_recorder_sum{key1="labelValue1"} 20 ${mockedHrTimeMs}`, - `value_recorder_bucket{key1="labelValue1",le="+Inf"} 1 ${mockedHrTimeMs}`, + '# HELP test_histogram a test description', + '# TYPE test_histogram histogram', + `test_histogram_count{key1="labelValue1"} 1 ${mockedHrTimeMs}`, + `test_histogram_sum{key1="labelValue1"} 20 ${mockedHrTimeMs}`, + `test_histogram_bucket{key1="labelValue1",le="+Inf"} 1 ${mockedHrTimeMs}`, '', ]); diff --git a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts index 4934ebb788..7c3582f07b 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts @@ -19,9 +19,9 @@ import { LastValueAggregator, MeterProvider, CounterMetric, - ValueRecorderMetric, + HistogramMetric, UpDownCounterMetric, - ValueObserverMetric, + ObservableGaugeMetric, } from '@opentelemetry/sdk-metrics-base'; import { diag, DiagLogLevel } from '@opentelemetry/api'; import * as assert from 'assert'; @@ -99,15 +99,15 @@ describe('PrometheusSerializer', () => { const meter = new MeterProvider({ processor: new ExactProcessor(LastValueAggregator), }).getMeter('test'); - const observer = meter.createValueObserver( + const observableGauge = meter.createObservableGauge( 'test', {}, - observerResult => { - observerResult.observe(1, labels); + observableResult => { + observableResult.observe(1, labels); } - ) as ValueObserverMetric; + ) as ObservableGaugeMetric; await meter.collect(); - const records = await observer.getMetricRecord(); + const records = await observableGauge.getMetricRecord(); const record = records[0]; const result = serializer.serializeRecord( @@ -126,15 +126,15 @@ describe('PrometheusSerializer', () => { const meter = new MeterProvider({ processor: new ExactProcessor(LastValueAggregator), }).getMeter('test'); - const observer = meter.createValueObserver( + const observableGauge = meter.createObservableGauge( 'test', {}, - observerResult => { - observerResult.observe(1, labels); + observableResult => { + observableResult.observe(1, labels); } - ) as ValueObserverMetric; + ) as ObservableGaugeMetric; await meter.collect(); - const records = await observer.getMetricRecord(); + const records = await observableGauge.getMetricRecord(); const record = records[0]; const result = serializer.serializeRecord( @@ -153,13 +153,13 @@ describe('PrometheusSerializer', () => { const processor = new ExactProcessor(HistogramAggregator, [1, 10, 100]); const meter = new MeterProvider({ processor }).getMeter('test'); - const recorder = meter.createValueRecorder('test', { + const histogram = meter.createHistogram('test', { description: 'foobar', - }) as ValueRecorderMetric; + }) as HistogramMetric; - recorder.bind(labels).record(5); + histogram.bind(labels).record(5); - const records = await recorder.getMetricRecord(); + const records = await histogram.getMetricRecord(); const record = records[0]; const result = serializer.serializeRecord( @@ -181,13 +181,13 @@ describe('PrometheusSerializer', () => { const serializer = new PrometheusSerializer(); const meter = new MeterProvider().getMeter('test'); - const recorder = meter.createValueRecorder('test', { + const histogram = meter.createHistogram('test', { description: 'foobar', boundaries: [1, 10, 100], - }) as ValueRecorderMetric; - recorder.bind(labels).record(5); + }) as HistogramMetric; + histogram.bind(labels).record(5); - const records = await recorder.getMetricRecord(); + const records = await histogram.getMetricRecord(); const record = records[0]; const result = serializer.serializeRecord( @@ -210,12 +210,12 @@ describe('PrometheusSerializer', () => { const processor = new ExactProcessor(HistogramAggregator, [1, 10, 100]); const meter = new MeterProvider({ processor }).getMeter('test'); - const recorder = meter.createValueRecorder('test', { + const histogram = meter.createHistogram('test', { description: 'foobar', - }) as ValueRecorderMetric; - recorder.bind(labels).record(5); + }) as HistogramMetric; + histogram.bind(labels).record(5); - const records = await recorder.getMetricRecord(); + const records = await histogram.getMetricRecord(); const record = records[0]; const result = serializer.serializeRecord( @@ -304,17 +304,17 @@ describe('PrometheusSerializer', () => { processor: new ExactProcessor(LastValueAggregator), }).getMeter('test'); const processor = new PrometheusLabelsBatcher(); - const observer = meter.createValueObserver( + const observableGauge = meter.createObservableGauge( 'test', { description: 'foobar', }, - observerResult => { - observerResult.observe(1, labels); + observableResult => { + observableResult.observe(1, labels); } - ) as ValueObserverMetric; + ) as ObservableGaugeMetric; await meter.collect(); - const records = await observer.getMetricRecord(); + const records = await observableGauge.getMetricRecord(); records.forEach(it => processor.process(it)); const checkPointSet = processor.checkPointSet(); @@ -336,16 +336,16 @@ describe('PrometheusSerializer', () => { const processor = new ExactProcessor(HistogramAggregator, [1, 10, 100]); const meter = new MeterProvider({ processor }).getMeter('test'); - const recorder = meter.createValueRecorder('test', { + const histogram = meter.createHistogram('test', { description: 'foobar', - }) as ValueRecorderMetric; - recorder.bind({ val: '1' }).record(5); - recorder.bind({ val: '1' }).record(50); - recorder.bind({ val: '1' }).record(120); + }) as HistogramMetric; + histogram.bind({ val: '1' }).record(5); + histogram.bind({ val: '1' }).record(50); + histogram.bind({ val: '1' }).record(120); - recorder.bind({ val: '2' }).record(5); + histogram.bind({ val: '2' }).record(5); - const records = await recorder.getMetricRecord(); + const records = await histogram.getMetricRecord(); const labelBatcher = new PrometheusLabelsBatcher(); records.forEach(it => labelBatcher.process(it)); const checkPointSet = labelBatcher.checkPointSet(); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/README.md b/experimental/packages/opentelemetry-sdk-metrics-base/README.md index 1761164b0f..b760ecc336 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/README.md +++ b/experimental/packages/opentelemetry-sdk-metrics-base/README.md @@ -73,7 +73,7 @@ boundCounter.add(Math.random() > 0.5 ? 1 : -1); ``` -### Value Observer +### Observable Gauge Choose this kind of metric when only last value is important without worry about aggregation. The callback can be sync or async. @@ -85,11 +85,11 @@ const meter = new MeterProvider().getMeter('your-meter-name'); // async callback - for operation that needs to wait for value -meter.createValueObserver('your_metric_name', { - description: 'Example of an async observer with callback', -}, async (observerResult) => { +meter.createObservableGauge('your_metric_name', { + description: 'Example of an async observable gauge with callback', +}, async (observableResult) => { const value = await getAsyncValue(); - observerResult.observe(value, { label: '1' }); + observableResult.observe(value, { label: '1' }); }); function getAsyncValue() { @@ -101,11 +101,11 @@ function getAsyncValue() { } // sync callback in case you don't need to wait for value -meter.createValueObserver('your_metric_name', { - description: 'Example of a sync observer with callback', -}, (observerResult) => { - observerResult.observe(getRandomValue(), { label: '1' }); - observerResult.observe(getRandomValue(), { label: '2' }); +meter.createObservableGauge('your_metric_name', { + description: 'Example of a sync observable gauge with callback', +}, (observableResult) => { + observableResult.observe(getRandomValue(), { label: '1' }); + observableResult.observe(getRandomValue(), { label: '2' }); }); function getRandomValue() { @@ -113,7 +113,7 @@ function getRandomValue() { } ``` -### UpDownSumObserver +### ObservableUpDownCounter Choose this kind of metric when sum is important and you want to capture any value that starts at zero and rises or falls throughout the process lifetime. The callback can be sync or async. @@ -124,11 +124,11 @@ const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); const meter = new MeterProvider().getMeter('your-meter-name'); // async callback - for operation that needs to wait for value -meter.createUpDownSumObserver('your_metric_name', { - description: 'Example of an async observer with callback', -}, async (observerResult) => { +meter.createObservableUpDownCounter('your_metric_name', { + description: 'Example of an async observable up down counter with callback', +}, async (observableResult) => { const value = await getAsyncValue(); - observerResult.observe(value, { label: '1' }); + observableResult.observe(value, { label: '1' }); }); function getAsyncValue() { @@ -140,10 +140,10 @@ function getAsyncValue() { } // sync callback in case you don't need to wait for value -meter.createUpDownSumObserver('your_metric_name', { - description: 'Example of a sync observer with callback', -}, (observerResult) => { - observerResult.observe(getRandomValue(), { label: '1' }); +meter.createObservableUpDownCounter('your_metric_name', { + description: 'Example of a sync observable up down counter with callback', +}, (observableResult) => { + observableResult.observe(getRandomValue(), { label: '1' }); }); function getRandomValue() { @@ -152,7 +152,7 @@ function getRandomValue() { ``` -### Sum Observer +### Observable Counter Choose this kind of metric when collecting a sum that never decreases. The callback can be sync or async. @@ -163,11 +163,11 @@ const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); const meter = new MeterProvider().getMeter('your-meter-name'); // async callback in case you need to wait for values -meter.createSumObserver('example_metric', { - description: 'Example of an async sum observer with callback', -}, async (observerResult) => { +meter.createObservableCounter('example_metric', { + description: 'Example of an async observable counter with callback', +}, async (observableResult) => { const value = await getAsyncValue(); - observerResult.observe(value, { label: '1' }); + observableResult.observe(value, { label: '1' }); }); function getAsyncValue() { @@ -179,11 +179,11 @@ function getAsyncValue() { } // sync callback in case you don't need to wait for values -meter.createSumObserver('example_metric', { - description: 'Example of a sync sum observer with callback', -}, (observerResult) => { +meter.createObservableCounter('example_metric', { + description: 'Example of a sync observable counter with callback', +}, (observableResult) => { const value = getRandomValue(); - observerResult.observe(value, { label: '1' }); + observableResult.observe(value, { label: '1' }); }); function getRandomValue() { @@ -193,7 +193,7 @@ function getRandomValue() { ### Batch Observer -Choose this kind of metric when you need to update multiple observers with the results of a single async calculation. +Choose this kind of metric when you need to update multiple observables with the results of a single async calculation. ```js const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); @@ -213,17 +213,17 @@ const meter = new MeterProvider({ interval: 3000, }).getMeter('example-observer'); -const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', { +const cpuUsageMetric = meter.createObservableGauge('cpu_usage_per_app', { description: 'CPU', }); -const MemUsageMetric = meter.createValueObserver('mem_usage_per_app', { +const MemUsageMetric = meter.createObservableGauge('mem_usage_per_app', { description: 'Memory', }); -meter.createBatchObserver((observerBatchResult) => { +meter.createBatchObserver((batchObserverResult) => { getSomeAsyncMetrics().then(metrics => { - observerBatchResult.observe({ app: 'myApp' }, [ + batchObserverResult.observe({ app: 'myApp' }, [ cpuUsageMetric.observation(metrics.value1), MemUsageMetric.observation(metrics.value2) ]); @@ -245,11 +245,11 @@ function getSomeAsyncMetrics() { See [examples/prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/prometheus) for a short example. -### Value Recorder +### Histogram -`ValueRecorder` is a non-additive synchronous instrument useful for recording any non-additive number, positive or negative. -Values captured by `ValueRecorder.record(value)` are treated as individual events belonging to a distribution that is being summarized. -`ValueRecorder` should be chosen either when capturing measurements that do not contribute meaningfully to a sum, or when capturing numbers that are additive in nature, but where the distribution of individual increments is considered interesting. +`Histogram` is a non-additive synchronous instrument useful for recording any non-additive number, positive or negative. +Values captured by `Histogram.record(value)` are treated as individual events belonging to a distribution that is being summarized. +`Histogram` should be chosen either when capturing measurements that do not contribute meaningfully to a sum, or when capturing numbers that are additive in nature, but where the distribution of individual increments is considered interesting. ## Useful links diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserver.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserver.ts index 90c5384465..576102cb46 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserver.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserver.ts @@ -23,12 +23,12 @@ const MAX_TIMEOUT_UPDATE_MS = 500; /** This is a SDK implementation of Batch Observer. */ export class BatchObserver { - private _callback: (observerResult: api.BatchObserverResult) => void; + private _callback: (observableResult: api.BatchObserverResult) => void; private _maxTimeoutUpdateMS: number; constructor( options: api.BatchObserverOptions, - callback?: (observerResult: api.BatchObserverResult) => void + callback?: (observableResult: api.BatchObserverResult) => void ) { this._maxTimeoutUpdateMS = options.maxTimeoutUpdateMS ?? MAX_TIMEOUT_UPDATE_MS; @@ -38,27 +38,27 @@ export class BatchObserver { collect(): Promise { diag.debug('getMetricRecord - start'); return new Promise(resolve => { - const observerResult = new BatchObserverResult(); + const batchObserverResult = new BatchObserverResult(); // cancels after MAX_TIMEOUT_MS - no more waiting for results const timer = setTimeout(() => { - observerResult.cancelled = true; + batchObserverResult.cancelled = true; // remove callback to prevent user from updating the values later if - // for any reason the observerBatchResult will be referenced - observerResult.onObserveCalled(); + // for any reason the batchObserverResult will be referenced + batchObserverResult.onObserveCalled(); resolve(); diag.debug('getMetricRecord - timeout'); }, this._maxTimeoutUpdateMS); // sets callback for each "observe" method - observerResult.onObserveCalled(() => { + batchObserverResult.onObserveCalled(() => { clearTimeout(timer); resolve(); diag.debug('getMetricRecord - end'); }); // calls the BatchObserverResult callback - this._callback(observerResult); + this._callback(batchObserverResult); }); } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserverResult.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserverResult.ts index 4e2c0c8b28..f882774ad0 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserverResult.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/BatchObserverResult.ts @@ -44,14 +44,14 @@ export class BatchObserverResult implements api.BatchObserverResult { return; } observations.forEach(observation => { - observation.observer.bind(labels).update(observation.value); + observation.observable.bind(labels).update(observation.value); }); if (!this._immediate) { this._immediate = setImmediate(() => { if (typeof this._callback === 'function') { this._callback(); // prevent user from updating the values later if for any reason - // the observerBatchResult will be referenced and then try to use + // the batchObserverResult will be referenced and then try to use this._callback = undefined; } }); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts index 296bf72909..67a66b204c 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts @@ -117,9 +117,9 @@ export class BoundUpDownCounter /** * BoundMeasure is an implementation of the {@link BoundMeasure} interface. */ -export class BoundValueRecorder +export class BoundHistogram extends BaseBoundInstrument - implements api.BoundValueRecorder { + implements api.BoundHistogram { constructor( labels: api.Labels, disabled: boolean, @@ -135,11 +135,11 @@ export class BoundValueRecorder } /** - * BoundObserver is an implementation of the {@link BoundObserver} interface. + * BoundObservable is an implementation of the {@link BoundObservable} interface. */ -export class BoundObserver +export class BoundObservable extends BaseBoundInstrument - implements api.BoundBaseObserver { + implements api.BoundObservableBase { constructor( labels: api.Labels, disabled: boolean, diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ValueRecorderMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/HistogramMetric.ts similarity index 79% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/ValueRecorderMetric.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/src/HistogramMetric.ts index 28e9d7e1b2..20a7ade3a7 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ValueRecorderMetric.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/HistogramMetric.ts @@ -17,15 +17,15 @@ import * as api from '@opentelemetry/api-metrics'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; -import { BoundValueRecorder } from './BoundInstrument'; +import { BoundHistogram } from './BoundInstrument'; import { Processor } from './export/Processor'; import { MetricKind } from './export/types'; import { Metric } from './Metric'; -/** This is a SDK implementation of Value Recorder Metric. */ -export class ValueRecorderMetric - extends Metric - implements api.ValueRecorder { +/** This is a SDK implementation of Histogram Metric. */ +export class HistogramMetric + extends Metric + implements api.Histogram { constructor( name: string, options: api.MetricOptions, @@ -36,14 +36,14 @@ export class ValueRecorderMetric super( name, options, - MetricKind.VALUE_RECORDER, + MetricKind.HISTOGRAM, resource, instrumentationLibrary ); } - protected _makeInstrument(labels: api.Labels): BoundValueRecorder { - return new BoundValueRecorder( + protected _makeInstrument(labels: api.Labels): BoundHistogram { + return new BoundHistogram( labels, this._disabled, this._valueType, diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts index 9a77e865bb..143d50692a 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts @@ -25,12 +25,12 @@ import { PushController } from './export/Controller'; import { NoopExporter } from './export/NoopExporter'; import { Processor, UngroupedProcessor } from './export/Processor'; import { Metric } from './Metric'; -import { SumObserverMetric } from './SumObserverMetric'; +import { ObservableCounterMetric } from './ObservableCounterMetric'; import { DEFAULT_CONFIG, DEFAULT_METRIC_OPTIONS, MeterConfig } from './types'; import { UpDownCounterMetric } from './UpDownCounterMetric'; -import { UpDownSumObserverMetric } from './UpDownSumObserverMetric'; -import { ValueObserverMetric } from './ValueObserverMetric'; -import { ValueRecorderMetric } from './ValueRecorderMetric'; +import { ObservableUpDownCounterMetric } from './ObservableUpDownCounterMetric'; +import { ObservableGaugeMetric } from './ObservableGaugeMetric'; +import { HistogramMetric } from './HistogramMetric'; // eslint-disable-next-line @typescript-eslint/no-var-requires const merge = require('lodash.merge'); // @TODO - replace once the core is released @@ -68,34 +68,34 @@ export class Meter implements api.Meter { } /** - * Creates and returns a new {@link ValueRecorder}. + * Creates and returns a new {@link Histogram}. * @param name the name of the metric. * @param [options] the metric options. */ - createValueRecorder( + createHistogram( name: string, options?: api.MetricOptions - ): api.ValueRecorder { + ): api.Histogram { if (!this._isValidName(name)) { diag.warn( `Invalid metric name ${name}. Defaulting to noop metric implementation.` ); - return api.NOOP_VALUE_RECORDER_METRIC; + return api.NOOP_HISTOGRAM_METRIC; } const opt: api.MetricOptions = { ...DEFAULT_METRIC_OPTIONS, ...options, }; - const valueRecorder = new ValueRecorderMetric( + const histogram = new HistogramMetric( name, opt, this._processor, this._resource, this._instrumentationLibrary ); - this._registerMetric(name, valueRecorder); - return valueRecorder; + this._registerMetric(name, histogram); + return histogram; } /** @@ -163,27 +163,27 @@ export class Meter implements api.Meter { } /** - * Creates a new `ValueObserver` metric. + * Creates a new `ObservableGauge` metric. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the value observer callback + * @param [callback] the observable gauge callback */ - createValueObserver( + createObservableGauge( name: string, options: api.MetricOptions = {}, - callback?: (observerResult: api.ObserverResult) => unknown - ): api.ValueObserver { + callback?: (observableResult: api.ObservableResult) => unknown + ): api.ObservableGauge { if (!this._isValidName(name)) { diag.warn( `Invalid metric name ${name}. Defaulting to noop metric implementation.` ); - return api.NOOP_VALUE_OBSERVER_METRIC; + return api.NOOP_OBSERVABLE_GAUGE_METRIC; } const opt: api.MetricOptions = { ...DEFAULT_METRIC_OPTIONS, ...options, }; - const valueObserver = new ValueObserverMetric( + const observableGauge = new ObservableGaugeMetric( name, opt, this._processor, @@ -191,26 +191,26 @@ export class Meter implements api.Meter { this._instrumentationLibrary, callback ); - this._registerMetric(name, valueObserver); - return valueObserver; + this._registerMetric(name, observableGauge); + return observableGauge; } - createSumObserver( + createObservableCounter( name: string, options: api.MetricOptions = {}, - callback?: (observerResult: api.ObserverResult) => unknown - ): api.SumObserver { + callback?: (observableResult: api.ObservableResult) => unknown + ): api.ObservableCounter { if (!this._isValidName(name)) { diag.warn( `Invalid metric name ${name}. Defaulting to noop metric implementation.` ); - return api.NOOP_SUM_OBSERVER_METRIC; + return api.NOOP_OBSERVABLE_COUNTER_METRIC; } const opt: api.MetricOptions = { ...DEFAULT_METRIC_OPTIONS, ...options, }; - const sumObserver = new SumObserverMetric( + const observableCounter = new ObservableCounterMetric( name, opt, this._processor, @@ -218,32 +218,32 @@ export class Meter implements api.Meter { this._instrumentationLibrary, callback ); - this._registerMetric(name, sumObserver); - return sumObserver; + this._registerMetric(name, observableCounter); + return observableCounter; } /** - * Creates a new `UpDownSumObserver` metric. + * Creates a new `ObservableUpDownCounter` metric. * @param name the name of the metric. * @param [options] the metric options. - * @param [callback] the value observer callback + * @param [callback] the observable gauge callback */ - createUpDownSumObserver( + createObservableUpDownCounter( name: string, options: api.MetricOptions = {}, - callback?: (observerResult: api.ObserverResult) => unknown - ): api.UpDownSumObserver { + callback?: (observableResult: api.ObservableResult) => unknown + ): api.ObservableUpDownCounter { if (!this._isValidName(name)) { diag.warn( `Invalid metric name ${name}. Defaulting to noop metric implementation.` ); - return api.NOOP_UP_DOWN_SUM_OBSERVER_METRIC; + return api.NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC; } const opt: api.MetricOptions = { ...DEFAULT_METRIC_OPTIONS, ...options, }; - const upDownSumObserver = new UpDownSumObserverMetric( + const observableUpDownCounter = new ObservableUpDownCounterMetric( name, opt, this._processor, @@ -251,8 +251,8 @@ export class Meter implements api.Meter { this._instrumentationLibrary, callback ); - this._registerMetric(name, upDownSumObserver); - return upDownSumObserver; + this._registerMetric(name, observableUpDownCounter); + return observableUpDownCounter; } /** @@ -261,7 +261,7 @@ export class Meter implements api.Meter { * @param [options] the batch options. */ createBatchObserver( - callback: (observerResult: api.BatchObserverResult) => void, + callback: (observableResult: api.BatchObserverResult) => void, options: api.BatchObserverOptions = {} ): BatchObserver { const opt: api.BatchObserverOptions = { diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/BaseObserverMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableBaseMetric.ts similarity index 68% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/BaseObserverMetric.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableBaseMetric.ts index 6f48df49bf..60626680a5 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/BaseObserverMetric.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableBaseMetric.ts @@ -17,22 +17,22 @@ import * as api from '@opentelemetry/api-metrics'; import { Observation } from '@opentelemetry/api-metrics'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; -import { BoundObserver } from './BoundInstrument'; +import { BoundObservable } from './BoundInstrument'; import { Processor } from './export/Processor'; import { MetricKind, MetricRecord } from './export/types'; import { Metric } from './Metric'; -import { ObserverResult } from './ObserverResult'; +import { ObservableResult } from './ObservableResult'; const NOOP_CALLBACK = () => {}; /** * This is a SDK implementation of Base Observer Metric. - * All observers should extend this class + * All observables should extend this class */ -export abstract class BaseObserverMetric - extends Metric - implements api.BaseObserver { - protected _callback: (observerResult: api.ObserverResult) => unknown; +export abstract class ObservableBaseMetric + extends Metric + implements api.ObservableBase { + protected _callback: (observableResult: api.ObservableResult) => unknown; constructor( name: string, @@ -41,14 +41,14 @@ export abstract class BaseObserverMetric resource: Resource, metricKind: MetricKind, instrumentationLibrary: InstrumentationLibrary, - callback?: (observerResult: api.ObserverResult) => unknown + callback?: (observableResult: api.ObservableResult) => unknown ) { super(name, options, metricKind, resource, instrumentationLibrary); this._callback = callback || NOOP_CALLBACK; } - protected _makeInstrument(labels: api.Labels): BoundObserver { - return new BoundObserver( + protected _makeInstrument(labels: api.Labels): BoundObservable { + return new BoundObservable( labels, this._disabled, this._valueType, @@ -57,16 +57,16 @@ export abstract class BaseObserverMetric } override async getMetricRecord(): Promise { - const observerResult = new ObserverResult(); - await this._callback(observerResult); + const observableResult = new ObservableResult(); + await this._callback(observableResult); - this._processResults(observerResult); + this._processResults(observableResult); return super.getMetricRecord(); } - protected _processResults(observerResult: ObserverResult): void { - observerResult.values.forEach((value, labels) => { + protected _processResults(observableResult: ObservableResult): void { + observableResult.values.forEach((value, labels) => { const instrument = this.bind(labels); instrument.update(value); }); @@ -75,7 +75,7 @@ export abstract class BaseObserverMetric observation(value: number): Observation { return { value, - observer: this as BaseObserverMetric, + observable: this as ObservableBaseMetric, }; } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/SumObserverMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableCounterMetric.ts similarity index 72% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/SumObserverMetric.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableCounterMetric.ts index 8f58727ceb..5465f14eff 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/SumObserverMetric.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableCounterMetric.ts @@ -17,38 +17,38 @@ import * as api from '@opentelemetry/api-metrics'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; -import { BaseObserverMetric } from './BaseObserverMetric'; +import { ObservableBaseMetric } from './ObservableBaseMetric'; import { Processor } from './export/Processor'; import { LastValue, MetricKind } from './export/types'; -import { ObserverResult } from './ObserverResult'; +import { ObservableResult } from './ObservableResult'; -/** This is a SDK implementation of SumObserver Metric. */ -export class SumObserverMetric - extends BaseObserverMetric - implements api.SumObserver { +/** This is a SDK implementation of ObservableCounter Metric. */ +export class ObservableCounterMetric + extends ObservableBaseMetric + implements api.ObservableCounter { constructor( name: string, options: api.MetricOptions, processor: Processor, resource: Resource, instrumentationLibrary: InstrumentationLibrary, - callback?: (observerResult: api.ObserverResult) => unknown + callback?: (observableResult: api.ObservableResult) => unknown ) { super( name, options, processor, resource, - MetricKind.SUM_OBSERVER, + MetricKind.OBSERVABLE_COUNTER, instrumentationLibrary, callback ); } - protected override _processResults(observerResult: ObserverResult): void { - observerResult.values.forEach((value, labels) => { + protected override _processResults(observableResult: ObservableResult): void { + observableResult.values.forEach((value, labels) => { const instrument = this.bind(labels); - // SumObserver is monotonic which means it should only accept values + // ObservableCounter is monotonic which means it should only accept values // greater or equal then previous value const previous = instrument.getAggregator().toPoint(); let previousValue = -Infinity; diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ValueObserverMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableGaugeMetric.ts similarity index 78% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/ValueObserverMetric.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableGaugeMetric.ts index a7ac5c5ff5..2d59e78f95 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ValueObserverMetric.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableGaugeMetric.ts @@ -16,28 +16,28 @@ import * as api from '@opentelemetry/api-metrics'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; -import { BaseObserverMetric } from './BaseObserverMetric'; +import { ObservableBaseMetric } from './ObservableBaseMetric'; import { Processor } from './export/Processor'; import { MetricKind } from './export/types'; -/** This is a SDK implementation of Value Observer Metric. */ -export class ValueObserverMetric - extends BaseObserverMetric - implements api.ValueObserver { +/** This is a SDK implementation of ObservableGauge Metric. */ +export class ObservableGaugeMetric + extends ObservableBaseMetric + implements api.ObservableGauge { constructor( name: string, options: api.MetricOptions, processor: Processor, resource: Resource, instrumentationLibrary: InstrumentationLibrary, - callback?: (observerResult: api.ObserverResult) => unknown + callback?: (observableResult: api.ObservableResult) => unknown ) { super( name, options, processor, resource, - MetricKind.VALUE_OBSERVER, + MetricKind.OBSERVABLE_GAUGE, instrumentationLibrary, callback ); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObserverResult.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableResult.ts similarity index 83% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/ObserverResult.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableResult.ts index 60dcc04449..51fc07899e 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObserverResult.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableResult.ts @@ -15,14 +15,14 @@ */ import { - ObserverResult as TypeObserverResult, + ObservableResult as TypeObservableResult, Labels, } from '@opentelemetry/api-metrics'; /** - * Implementation of {@link TypeObserverResult} + * Implementation of {@link TypeObservableResult} */ -export class ObserverResult implements TypeObserverResult { +export class ObservableResult implements TypeObservableResult { values: Map = new Map(); observe(value: number, labels: Labels): void { diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/UpDownSumObserverMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableUpDownCounterMetric.ts similarity index 76% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/UpDownSumObserverMetric.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableUpDownCounterMetric.ts index 31cec8af7a..d9a767044a 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/UpDownSumObserverMetric.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableUpDownCounterMetric.ts @@ -17,28 +17,28 @@ import * as api from '@opentelemetry/api-metrics'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; -import { BaseObserverMetric } from './BaseObserverMetric'; +import { ObservableBaseMetric } from './ObservableBaseMetric'; import { Processor } from './export/Processor'; import { MetricKind } from './export/types'; -/** This is a SDK implementation of UpDownSumObserver Metric. */ -export class UpDownSumObserverMetric - extends BaseObserverMetric - implements api.UpDownSumObserver { +/** This is a SDK implementation of ObservableUpDownCounter Metric. */ +export class ObservableUpDownCounterMetric + extends ObservableBaseMetric + implements api.ObservableUpDownCounter { constructor( name: string, options: api.MetricOptions, processor: Processor, resource: Resource, instrumentationLibrary: InstrumentationLibrary, - callback?: (observerResult: api.ObserverResult) => unknown + callback?: (observableResult: api.ObservableResult) => unknown ) { super( name, options, processor, resource, - MetricKind.UP_DOWN_SUM_OBSERVER, + MetricKind.OBSERVABLE_UP_DOWN_COUNTER, instrumentationLibrary, callback ); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts index 03d2cbcf52..3cc23c70f0 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts @@ -54,12 +54,12 @@ export class UngroupedProcessor extends Processor { case MetricKind.UP_DOWN_COUNTER: return new aggregators.SumAggregator(); - case MetricKind.SUM_OBSERVER: - case MetricKind.UP_DOWN_SUM_OBSERVER: - case MetricKind.VALUE_OBSERVER: + case MetricKind.OBSERVABLE_COUNTER: + case MetricKind.OBSERVABLE_UP_DOWN_COUNTER: + case MetricKind.OBSERVABLE_GAUGE: return new aggregators.LastValueAggregator(); - case MetricKind.VALUE_RECORDER: + case MetricKind.HISTOGRAM: return new aggregators.HistogramAggregator( metricDescriptor.boundaries || [Infinity] ); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts index 889e96598d..33206ea936 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts @@ -27,10 +27,10 @@ import { Resource } from '@opentelemetry/resources'; export enum MetricKind { COUNTER, UP_DOWN_COUNTER, - VALUE_RECORDER, - SUM_OBSERVER, - UP_DOWN_SUM_OBSERVER, - VALUE_OBSERVER, + HISTOGRAM, + OBSERVABLE_COUNTER, + OBSERVABLE_UP_DOWN_COUNTER, + OBSERVABLE_GAUGE, BATCH_OBSERVER, } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts index afcdeea5b1..a3d74d5352 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts @@ -16,11 +16,11 @@ export * from './BoundInstrument'; export * from './CounterMetric'; -export * from './ValueRecorderMetric'; +export * from './HistogramMetric'; export * from './Meter'; export * from './MeterProvider'; export * from './Metric'; -export * from './ValueObserverMetric'; +export * from './ObservableGaugeMetric'; export * from './export/aggregators'; export * from './export/ConsoleMetricExporter'; export * from './export/Processor'; diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts index 0fcc05ede3..e89545f7ad 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts @@ -34,15 +34,15 @@ import { MetricRecord, Sum, UpDownCounterMetric, - ValueObserverMetric, - ValueRecorderMetric, + ObservableGaugeMetric, + HistogramMetric, } from '../src'; import { BatchObserver } from '../src/BatchObserver'; import { BatchObserverResult } from '../src/BatchObserverResult'; import { SumAggregator } from '../src/export/aggregators'; import { Processor } from '../src/export/Processor'; -import { SumObserverMetric } from '../src/SumObserverMetric'; -import { UpDownSumObserverMetric } from '../src/UpDownSumObserverMetric'; +import { ObservableCounterMetric } from '../src/ObservableCounterMetric'; +import { ObservableUpDownCounterMetric } from '../src/ObservableUpDownCounterMetric'; import { hashLabels } from '../src/Utils'; const nonNumberValues = [ @@ -550,33 +550,33 @@ describe('Meter', () => { }); }); - describe('#ValueRecorder', () => { - it('should create a valueRecorder', () => { - const valueRecorder = meter.createValueRecorder('name'); - assert.ok(valueRecorder instanceof Metric); + describe('#Histogram', () => { + it('should create a histogram', () => { + const histogram = meter.createHistogram('name'); + assert.ok(histogram instanceof Metric); }); - it('should create a valueRecorder with options', () => { - const valueRecorder = meter.createValueRecorder('name', { + it('should create a histogram with options', () => { + const histogram = meter.createHistogram('name', { description: 'desc', unit: '1', disabled: false, }); - assert.ok(valueRecorder instanceof Metric); + assert.ok(histogram instanceof Metric); }); - it('should set histogram boundaries for value recorder', async () => { - const valueRecorder = meter.createValueRecorder('name', { + it('should set histogram boundaries for histogram', async () => { + const histogram = meter.createHistogram('name', { description: 'desc', unit: '1', disabled: false, boundaries: [10, 20, 30, 100], - }) as ValueRecorderMetric; + }) as HistogramMetric; - valueRecorder.record(10); - valueRecorder.record(30); - valueRecorder.record(50); - valueRecorder.record(200); + histogram.record(10); + histogram.record(30); + histogram.record(50); + histogram.record(200); await meter.collect(); const [record] = meter.getProcessor().checkPointSet(); @@ -589,30 +589,30 @@ describe('Meter', () => { sum: 290, }); - assert.ok(valueRecorder instanceof Metric); + assert.ok(histogram instanceof Metric); }); it('should pipe through resource', async () => { - const valueRecorder = meter.createValueRecorder( + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - assert.ok(valueRecorder.resource instanceof Resource); + ) as HistogramMetric; + assert.ok(histogram.resource instanceof Resource); - valueRecorder.record(1, { foo: 'bar' }); + histogram.record(1, { foo: 'bar' }); - const [record] = await valueRecorder.getMetricRecord(); + const [record] = await histogram.getMetricRecord(); assert.ok(record.resource instanceof Resource); }); it('should pipe through instrumentation library', async () => { - const valueRecorder = meter.createValueRecorder( + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - assert.ok(valueRecorder.instrumentationLibrary); + ) as HistogramMetric; + assert.ok(histogram.instrumentationLibrary); - valueRecorder.record(1, { foo: 'bar' }); + histogram.record(1, { foo: 'bar' }); - const [record] = await valueRecorder.getMetricRecord(); + const [record] = await histogram.getMetricRecord(); const { name, version } = record.instrumentationLibrary; assert.strictEqual(name, 'test-meter'); assert.strictEqual(version, undefined); @@ -620,42 +620,42 @@ describe('Meter', () => { describe('names', () => { it('should return no op metric if name is an empty string', () => { - const valueRecorder = meter.createValueRecorder(''); - assert.ok(valueRecorder instanceof api.NoopMetric); + const histogram = meter.createHistogram(''); + assert.ok(histogram instanceof api.NoopMetric); }); it('should return no op metric if name does not start with a letter', () => { - const valueRecorder1 = meter.createValueRecorder('1name'); - const valueRecorder_ = meter.createValueRecorder('_name'); - assert.ok(valueRecorder1 instanceof api.NoopMetric); - assert.ok(valueRecorder_ instanceof api.NoopMetric); + const histogram1 = meter.createHistogram('1name'); + const histogram_ = meter.createHistogram('_name'); + assert.ok(histogram1 instanceof api.NoopMetric); + assert.ok(histogram_ instanceof api.NoopMetric); }); it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => { - const valueRecorder = meter.createValueRecorder( + const histogram = meter.createHistogram( 'name with invalid characters^&*(' ); - assert.ok(valueRecorder instanceof api.NoopMetric); + assert.ok(histogram instanceof api.NoopMetric); }); }); describe('.bind()', () => { const performanceTimeOrigin = hrTime(); - it('should create a valueRecorder instrument', () => { - const valueRecorder = meter.createValueRecorder( + it('should create a histogram instrument', () => { + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - const boundValueRecorder = valueRecorder.bind(labels); - assert.doesNotThrow(() => boundValueRecorder.record(10)); + ) as HistogramMetric; + const boundHistogram = histogram.bind(labels); + assert.doesNotThrow(() => boundHistogram.record(10)); }); it('should not set the instrument data when disabled', async () => { - const valueRecorder = meter.createValueRecorder('name', { + const histogram = meter.createHistogram('name', { disabled: true, - }) as ValueRecorderMetric; - const boundValueRecorder = valueRecorder.bind(labels); - boundValueRecorder.record(10); + }) as HistogramMetric; + const boundHistogram = histogram.bind(labels); + boundHistogram.record(10); await meter.collect(); const [record1] = meter.getProcessor().checkPointSet(); @@ -673,10 +673,10 @@ describe('Meter', () => { }); it('should accept negative (and positive) values', async () => { - const valueRecorder = meter.createValueRecorder('name'); - const boundValueRecorder = valueRecorder.bind(labels); - boundValueRecorder.record(-10); - boundValueRecorder.record(50); + const histogram = meter.createHistogram('name'); + const boundHistogram = histogram.bind(labels); + boundHistogram.record(-10); + boundHistogram.record(50); await meter.collect(); const [record1] = meter.getProcessor().checkPointSet(); @@ -698,13 +698,13 @@ describe('Meter', () => { }); it('should return same instrument on same label values', async () => { - const valueRecorder = meter.createValueRecorder( + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - const boundValueRecorder1 = valueRecorder.bind(labels); - boundValueRecorder1.record(10); - const boundValueRecorder2 = valueRecorder.bind(labels); - boundValueRecorder2.record(100); + ) as HistogramMetric; + const boundHistogram1 = histogram.bind(labels); + boundHistogram1.record(10); + const boundHistogram2 = histogram.bind(labels); + boundHistogram2.record(100); await meter.collect(); const [record1] = meter.getProcessor().checkPointSet(); assert.deepStrictEqual( @@ -718,19 +718,19 @@ describe('Meter', () => { sum: 110, } ); - assert.strictEqual(boundValueRecorder1, boundValueRecorder2); + assert.strictEqual(boundHistogram1, boundHistogram2); }); it('should ignore non-number values', async () => { - const valueRecorder = meter.createValueRecorder( + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - const boundValueRecorder = valueRecorder.bind(labels); + ) as HistogramMetric; + const boundHistogram = histogram.bind(labels); await Promise.all( nonNumberValues.map(async val => { // @ts-expect-error verify non number types - boundValueRecorder.record(val); + boundHistogram.record(val); await meter.collect(); const [record1] = meter.getProcessor().checkPointSet(); assert.deepStrictEqual( @@ -750,47 +750,47 @@ describe('Meter', () => { }); describe('.unbind()', () => { - it('should remove the valueRecorder instrument', () => { - const valueRecorder = meter.createValueRecorder( + it('should remove the histogram instrument', () => { + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - const boundValueRecorder = valueRecorder.bind(labels); - assert.strictEqual(valueRecorder['_instruments'].size, 1); - valueRecorder.unbind(labels); - assert.strictEqual(valueRecorder['_instruments'].size, 0); - const boundValueRecorder2 = valueRecorder.bind(labels); - assert.strictEqual(valueRecorder['_instruments'].size, 1); - assert.notStrictEqual(boundValueRecorder, boundValueRecorder2); + ) as HistogramMetric; + const boundHistogram = histogram.bind(labels); + assert.strictEqual(histogram['_instruments'].size, 1); + histogram.unbind(labels); + assert.strictEqual(histogram['_instruments'].size, 0); + const boundHistogram2 = histogram.bind(labels); + assert.strictEqual(histogram['_instruments'].size, 1); + assert.notStrictEqual(boundHistogram, boundHistogram2); }); it('should not fail when removing non existing instrument', () => { - const valueRecorder = meter.createValueRecorder('name'); - valueRecorder.unbind({}); + const histogram = meter.createHistogram('name'); + histogram.unbind({}); }); it('should clear all instruments', () => { - const valueRecorder = meter.createValueRecorder( + const histogram = meter.createHistogram( 'name' - ) as ValueRecorderMetric; - valueRecorder.bind(labels); - assert.strictEqual(valueRecorder['_instruments'].size, 1); - valueRecorder.clear(); - assert.strictEqual(valueRecorder['_instruments'].size, 0); + ) as HistogramMetric; + histogram.bind(labels); + assert.strictEqual(histogram['_instruments'].size, 1); + histogram.clear(); + assert.strictEqual(histogram['_instruments'].size, 0); }); }); }); - describe('#SumObserverMetric', () => { - it('should create an Sum observer', () => { - const sumObserver = meter.createSumObserver('name') as SumObserverMetric; - assert.ok(sumObserver instanceof Metric); + describe('#ObservableCounterMetric', () => { + it('should create an ObservableCounter', () => { + const observableCounter = meter.createObservableCounter('name') as ObservableCounterMetric; + assert.ok(observableCounter instanceof Metric); }); - it('should return noop observer when name is invalid', () => { + it('should return noop observable counter when name is invalid', () => { // Need to stub/spy on the underlying logger as the "diag" instance is global const spy = sinon.stub(diag, 'warn'); - const sumObserver = meter.createSumObserver('na me'); - assert.ok(sumObserver === api.NOOP_SUM_OBSERVER_METRIC); + const observableCounter = meter.createObservableCounter('na me'); + assert.ok(observableCounter === api.NOOP_OBSERVABLE_COUNTER_METRIC); const args = spy.args[0]; assert.ok( args[0], @@ -798,13 +798,13 @@ describe('Meter', () => { ); }); - it('should create observer with options', () => { - const sumObserver = meter.createSumObserver('name', { + it('should create observable counter with options', () => { + const observableCounter = meter.createObservableCounter('name', { description: 'desc', unit: '1', disabled: false, - }) as SumObserverMetric; - assert.ok(sumObserver instanceof Metric); + }) as ObservableCounterMetric; + assert.ok(observableCounter instanceof Metric); }); it('should set callback and observe value ', async () => { @@ -818,23 +818,23 @@ describe('Meter', () => { return -1; } - const sumObserver = meter.createSumObserver( + const observableCounter = meter.createObservableCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { + (observableResult: api.ObservableResult) => { // simulate async return new Promise(resolve => { setTimeout(() => { - observerResult.observe(getValue(), { pid: '123', core: '1' }); + observableResult.observe(getValue(), { pid: '123', core: '1' }); resolve(); }, 1); }); } - ) as SumObserverMetric; + ) as ObservableCounterMetric; - let metricRecords = await sumObserver.getMetricRecord(); + let metricRecords = await observableCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); let point = metricRecords[0].aggregator.toPoint(); assert.strictEqual(point.value, -1); @@ -843,29 +843,29 @@ describe('Meter', () => { '|#core:1,pid:123' ); - metricRecords = await sumObserver.getMetricRecord(); + metricRecords = await observableCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); point = metricRecords[0].aggregator.toPoint(); assert.strictEqual(point.value, 3); - metricRecords = await sumObserver.getMetricRecord(); + metricRecords = await observableCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); point = metricRecords[0].aggregator.toPoint(); assert.strictEqual(point.value, 3); }); it('should set callback and observe value when callback returns nothing', async () => { - const sumObserver = meter.createSumObserver( + const observableCounter = meter.createObservableCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { - observerResult.observe(1, { pid: '123', core: '1' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(1, { pid: '123', core: '1' }); } - ) as SumObserverMetric; + ) as ObservableCounterMetric; - const metricRecords = await sumObserver.getMetricRecord(); + const metricRecords = await observableCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); }); @@ -873,34 +873,34 @@ describe('Meter', () => { 'should set callback and observe value when callback returns anything' + ' but Promise', async () => { - const sumObserver = meter.createSumObserver( + const observableCounter = meter.createObservableCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { - observerResult.observe(1, { pid: '123', core: '1' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(1, { pid: '123', core: '1' }); return '1'; } - ) as SumObserverMetric; + ) as ObservableCounterMetric; - const metricRecords = await sumObserver.getMetricRecord(); + const metricRecords = await observableCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); } ); it('should reject getMetricRecord when callback throws an error', async () => { - const sumObserver = meter.createSumObserver( + const observableCounter = meter.createObservableCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { - observerResult.observe(1, { pid: '123', core: '1' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(1, { pid: '123', core: '1' }); throw new Error('Boom'); } - ) as SumObserverMetric; - await sumObserver + ) as ObservableCounterMetric; + await observableCounter .getMetricRecord() .then() .catch(e => { @@ -909,30 +909,30 @@ describe('Meter', () => { }); it('should pipe through resource', async () => { - const sumObserver = meter.createSumObserver('name', {}, result => { + const observableCounter = meter.createObservableCounter('name', {}, result => { result.observe(42, { foo: 'bar' }); return Promise.resolve(); - }) as SumObserverMetric; - assert.ok(sumObserver.resource instanceof Resource); + }) as ObservableCounterMetric; + assert.ok(observableCounter.resource instanceof Resource); - const [record] = await sumObserver.getMetricRecord(); + const [record] = await observableCounter.getMetricRecord(); assert.ok(record.resource instanceof Resource); }); }); - describe('#ValueObserver', () => { - it('should create a value observer', () => { - const valueObserver = meter.createValueObserver( + describe('#ObservableGauge', () => { + it('should create an observable gauge', () => { + const observableGauge = meter.createObservableGauge( 'name' - ) as ValueObserverMetric; - assert.ok(valueObserver instanceof Metric); + ) as ObservableGaugeMetric; + assert.ok(observableGauge instanceof Metric); }); - it('should return noop observer when name is invalid', () => { + it('should return noop observable gauge when name is invalid', () => { // Need to stub/spy on the underlying logger as the "diag" instance is global const spy = sinon.stub(diag, 'warn'); - const valueObserver = meter.createValueObserver('na me'); - assert.ok(valueObserver === api.NOOP_VALUE_OBSERVER_METRIC); + const observableGauge = meter.createObservableGauge('na me'); + assert.ok(observableGauge === api.NOOP_OBSERVABLE_GAUGE_METRIC); const args = spy.args[0]; assert.ok( args[0], @@ -940,40 +940,40 @@ describe('Meter', () => { ); }); - it('should create observer with options', () => { - const valueObserver = meter.createValueObserver('name', { + it('should create observable gauge with options', () => { + const observableGauge = meter.createObservableGauge('name', { description: 'desc', unit: '1', disabled: false, - }) as ValueObserverMetric; - assert.ok(valueObserver instanceof Metric); + }) as ObservableGaugeMetric; + assert.ok(observableGauge instanceof Metric); }); it('should set callback and observe value ', async () => { - const valueObserver = meter.createValueObserver( + const observableGauge = meter.createObservableGauge( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { + (observableResult: api.ObservableResult) => { // simulate async return new Promise(resolve => { setTimeout(() => { - observerResult.observe(getCpuUsage(), { pid: '123', core: '1' }); - observerResult.observe(getCpuUsage(), { pid: '123', core: '2' }); - observerResult.observe(getCpuUsage(), { pid: '123', core: '3' }); - observerResult.observe(getCpuUsage(), { pid: '123', core: '4' }); + observableResult.observe(getCpuUsage(), { pid: '123', core: '1' }); + observableResult.observe(getCpuUsage(), { pid: '123', core: '2' }); + observableResult.observe(getCpuUsage(), { pid: '123', core: '3' }); + observableResult.observe(getCpuUsage(), { pid: '123', core: '4' }); resolve(); }, 1); }); } - ) as ValueObserverMetric; + ) as ObservableGaugeMetric; function getCpuUsage() { return Math.random(); } - const metricRecords: MetricRecord[] = await valueObserver.getMetricRecord(); + const metricRecords: MetricRecord[] = await observableGauge.getMetricRecord(); assert.strictEqual(metricRecords.length, 4); const metric1 = metricRecords[0]; @@ -992,29 +992,29 @@ describe('Meter', () => { }); it('should pipe through resource', async () => { - const valueObserver = meter.createValueObserver('name', {}, result => { + const observableGauge = meter.createObservableGauge('name', {}, result => { result.observe(42, { foo: 'bar' }); - }) as ValueObserverMetric; - assert.ok(valueObserver.resource instanceof Resource); + }) as ObservableGaugeMetric; + assert.ok(observableGauge.resource instanceof Resource); - const [record] = await valueObserver.getMetricRecord(); + const [record] = await observableGauge.getMetricRecord(); assert.ok(record.resource instanceof Resource); }); }); - describe('#UpDownSumObserverMetric', () => { - it('should create an UpDownSum observer', () => { - const upDownSumObserver = meter.createUpDownSumObserver( + describe('#ObservableUpDownCounterMetric', () => { + it('should create an ObservableUpDownCounter', () => { + const observableUpDownCounter = meter.createObservableUpDownCounter( 'name' - ) as UpDownSumObserverMetric; - assert.ok(upDownSumObserver instanceof Metric); + ) as ObservableUpDownCounterMetric; + assert.ok(observableUpDownCounter instanceof Metric); }); - it('should return noop observer when name is invalid', () => { + it('should return noop observable up down counter when name is invalid', () => { // Need to stub/spy on the underlying logger as the "diag" instance is global const spy = sinon.stub(diag, 'warn'); - const upDownSumObserver = meter.createUpDownSumObserver('na me'); - assert.ok(upDownSumObserver === api.NOOP_UP_DOWN_SUM_OBSERVER_METRIC); + const observableUpDownCounter = meter.createObservableUpDownCounter('na me'); + assert.ok(observableUpDownCounter === api.NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC); const args = spy.args[0]; assert.ok( args[0], @@ -1022,13 +1022,13 @@ describe('Meter', () => { ); }); - it('should create observer with options', () => { - const upDownSumObserver = meter.createUpDownSumObserver('name', { + it('should create observable up down counter with options', () => { + const observableUpDownCounter = meter.createObservableUpDownCounter('name', { description: 'desc', unit: '1', disabled: false, - }) as UpDownSumObserverMetric; - assert.ok(upDownSumObserver instanceof Metric); + }) as ObservableUpDownCounterMetric; + assert.ok(observableUpDownCounter instanceof Metric); }); it('should set callback and observe value ', async () => { @@ -1042,23 +1042,23 @@ describe('Meter', () => { return 3; } - const upDownSumObserver = meter.createUpDownSumObserver( + const observableUpDownCounter = meter.createObservableUpDownCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { + (observableResult: api.ObservableResult) => { // simulate async return new Promise(resolve => { setTimeout(() => { - observerResult.observe(getValue(), { pid: '123', core: '1' }); + observableResult.observe(getValue(), { pid: '123', core: '1' }); resolve(); }, 1); }); } - ) as UpDownSumObserverMetric; + ) as ObservableUpDownCounterMetric; - let metricRecords = await upDownSumObserver.getMetricRecord(); + let metricRecords = await observableUpDownCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); let point = metricRecords[0].aggregator.toPoint(); assert.strictEqual(point.value, 3); @@ -1067,29 +1067,29 @@ describe('Meter', () => { '|#core:1,pid:123' ); - metricRecords = await upDownSumObserver.getMetricRecord(); + metricRecords = await observableUpDownCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); point = metricRecords[0].aggregator.toPoint(); assert.strictEqual(point.value, 2); - metricRecords = await upDownSumObserver.getMetricRecord(); + metricRecords = await observableUpDownCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); point = metricRecords[0].aggregator.toPoint(); assert.strictEqual(point.value, 3); }); it('should set callback and observe value when callback returns nothing', async () => { - const upDownSumObserver = meter.createUpDownSumObserver( + const observableUpDownCounter = meter.createObservableUpDownCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { - observerResult.observe(1, { pid: '123', core: '1' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(1, { pid: '123', core: '1' }); } - ) as UpDownSumObserverMetric; + ) as ObservableUpDownCounterMetric; - const metricRecords = await upDownSumObserver.getMetricRecord(); + const metricRecords = await observableUpDownCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); }); @@ -1097,34 +1097,34 @@ describe('Meter', () => { 'should set callback and observe value when callback returns anything' + ' but Promise', async () => { - const upDownSumObserver = meter.createUpDownSumObserver( + const observableUpDownCounter = meter.createObservableUpDownCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { - observerResult.observe(1, { pid: '123', core: '1' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(1, { pid: '123', core: '1' }); return '1'; } - ) as UpDownSumObserverMetric; + ) as ObservableUpDownCounterMetric; - const metricRecords = await upDownSumObserver.getMetricRecord(); + const metricRecords = await observableUpDownCounter.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); } ); it('should reject getMetricRecord when callback throws an error', async () => { - const upDownSumObserver = meter.createUpDownSumObserver( + const observableUpDownCounter = meter.createObservableUpDownCounter( 'name', { description: 'desc', }, - (observerResult: api.ObserverResult) => { - observerResult.observe(1, { pid: '123', core: '1' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(1, { pid: '123', core: '1' }); throw new Error('Boom'); } - ) as UpDownSumObserverMetric; - await upDownSumObserver + ) as ObservableUpDownCounterMetric; + await observableUpDownCounter .getMetricRecord() .then() .catch(e => { @@ -1133,17 +1133,17 @@ describe('Meter', () => { }); it('should pipe through resource', async () => { - const upDownSumObserver = meter.createUpDownSumObserver( + const observableUpDownCounter = meter.createObservableUpDownCounter( 'name', {}, result => { result.observe(42, { foo: 'bar' }); return Promise.resolve(); } - ) as UpDownSumObserverMetric; - assert.ok(upDownSumObserver.resource instanceof Resource); + ) as ObservableUpDownCounterMetric; + assert.ok(observableUpDownCounter.resource instanceof Resource); - const [record] = await upDownSumObserver.getMetricRecord(); + const [record] = await observableUpDownCounter.getMetricRecord(); assert.ok(record.resource instanceof Resource); }); }); @@ -1162,15 +1162,15 @@ describe('Meter', () => { }); it('should use callback to observe values ', async () => { - const tempMetric = meter.createValueObserver('cpu_temp_per_app', { + const tempMetric = meter.createObservableGauge('cpu_temp_per_app', { description: 'desc', - }) as ValueObserverMetric; + }) as ObservableGaugeMetric; - const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', { + const cpuUsageMetric = meter.createObservableGauge('cpu_usage_per_app', { description: 'desc', - }) as ValueObserverMetric; + }) as ObservableGaugeMetric; - meter.createBatchObserver(observerBatchResult => { + meter.createBatchObserver(batchObserverResult => { interface StatItem { usage: number; temp: number; @@ -1209,11 +1209,11 @@ describe('Meter', () => { ]).then((stats: unknown[]) => { const apps = (stats[0] as unknown) as Stat[]; apps.forEach(app => { - observerBatchResult.observe({ app: app.name, core: '1' }, [ + batchObserverResult.observe({ app: app.name, core: '1' }, [ tempMetric.observation(app.core1.temp), cpuUsageMetric.observation(app.core1.usage), ]); - observerBatchResult.observe({ app: app.name, core: '2' }, [ + batchObserverResult.observe({ app: app.name, core: '2' }, [ tempMetric.observation(app.core2.temp), cpuUsageMetric.observation(app.core2.usage), ]); @@ -1255,12 +1255,12 @@ describe('Meter', () => { }); it('should not observe values when timeout', done => { - const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', { + const cpuUsageMetric = meter.createObservableGauge('cpu_usage_per_app', { description: 'desc', - }) as ValueObserverMetric; + }) as ObservableGaugeMetric; meter.createBatchObserver( - observerBatchResult => { + batchObserverResult => { Promise.all([ // simulate waiting 11ms new Promise((resolve, reject) => { @@ -1268,8 +1268,8 @@ describe('Meter', () => { }), ]).then(async () => { // try to hack to be able to update - (observerBatchResult as BatchObserverResult).cancelled = false; - observerBatchResult.observe({ foo: 'bar' }, [ + (batchObserverResult as BatchObserverResult).cancelled = false; + batchObserverResult.observe({ foo: 'bar' }, [ cpuUsageMetric.observation(123), ]); @@ -1296,16 +1296,16 @@ describe('Meter', () => { }); it('should pipe through instrumentation library', async () => { - const observer = meter.createValueObserver( + const observableGauge = meter.createObservableGauge( 'name', {}, - (observerResult: api.ObserverResult) => { - observerResult.observe(42, { foo: 'bar' }); + (observableResult: api.ObservableResult) => { + observableResult.observe(42, { foo: 'bar' }); } - ) as ValueObserverMetric; - assert.ok(observer.instrumentationLibrary); + ) as ObservableGaugeMetric; + assert.ok(observableGauge.instrumentationLibrary); - const [record] = await observer.getMetricRecord(); + const [record] = await observableGauge.getMetricRecord(); const { name, version } = record.instrumentationLibrary; assert.strictEqual(name, 'test-meter'); assert.strictEqual(version, undefined); @@ -1370,8 +1370,8 @@ describe('Meter', () => { processor: new CustomProcessor(), }); assert.throws(() => { - const valueRecorder = customMeter.createValueRecorder('myValueRecorder'); - valueRecorder.bind({}).record(1); + const histogram = customMeter.createHistogram('myHistogram'); + histogram.bind({}).record(1); }, /aggregatorFor method not implemented/); }); }); @@ -1396,6 +1396,6 @@ function ensureMetric(metric: MetricRecord, name?: string, value?: LastValue) { assert.strictEqual(descriptor.name, name || 'name'); assert.strictEqual(descriptor.description, 'desc'); assert.strictEqual(descriptor.unit, '1'); - assert.strictEqual(descriptor.metricKind, MetricKind.VALUE_OBSERVER); + assert.strictEqual(descriptor.metricKind, MetricKind.OBSERVABLE_GAUGE); assert.strictEqual(descriptor.valueType, api.ValueType.DOUBLE); } From 043067fa3a376bdf5e15adf86f06bb627cceecc8 Mon Sep 17 00:00:00 2001 From: echoontheway <1152760298@qq.com> Date: Mon, 18 Oct 2021 00:09:02 +0800 Subject: [PATCH 04/15] feat(@opentelemetry/semantic-conventions): change enum to object literals (#2532) Co-authored-by: Valentin Marchaud --- .../resource/SemanticResourceAttributes.ts | 134 +++---- .../src/trace/SemanticAttributes.ts | 329 +++++++++--------- .../templates/SemanticAttributes.ts.j2 | 8 +- 3 files changed, 243 insertions(+), 228 deletions(-) diff --git a/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts b/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts index 382e315a24..9e3a9d02ae 100644 --- a/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts +++ b/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts @@ -476,141 +476,145 @@ As an alternative, consider setting `faas.id` as a span attribute instead. WEBENGINE_DESCRIPTION: 'webengine.description', } -// Enum definitions - -export enum CloudProviderValues { +export const CloudProviderValues = { /** Alibaba Cloud. */ - ALIBABA_CLOUD = 'alibaba_cloud', + ALIBABA_CLOUD: 'alibaba_cloud', /** Amazon Web Services. */ - AWS = 'aws', + AWS: 'aws', /** Microsoft Azure. */ - AZURE = 'azure', + AZURE: 'azure', /** Google Cloud Platform. */ - GCP = 'gcp', -} + GCP: 'gcp', +} as const +export type CloudProviderValues = typeof CloudProviderValues[keyof typeof CloudProviderValues] -export enum CloudPlatformValues { +export const CloudPlatformValues = { /** Alibaba Cloud Elastic Compute Service. */ - ALIBABA_CLOUD_ECS = 'alibaba_cloud_ecs', + ALIBABA_CLOUD_ECS: 'alibaba_cloud_ecs', /** Alibaba Cloud Function Compute. */ - ALIBABA_CLOUD_FC = 'alibaba_cloud_fc', + ALIBABA_CLOUD_FC: 'alibaba_cloud_fc', /** AWS Elastic Compute Cloud. */ - AWS_EC2 = 'aws_ec2', + AWS_EC2: 'aws_ec2', /** AWS Elastic Container Service. */ - AWS_ECS = 'aws_ecs', + AWS_ECS: 'aws_ecs', /** AWS Elastic Kubernetes Service. */ - AWS_EKS = 'aws_eks', + AWS_EKS: 'aws_eks', /** AWS Lambda. */ - AWS_LAMBDA = 'aws_lambda', + AWS_LAMBDA: 'aws_lambda', /** AWS Elastic Beanstalk. */ - AWS_ELASTIC_BEANSTALK = 'aws_elastic_beanstalk', + AWS_ELASTIC_BEANSTALK: 'aws_elastic_beanstalk', /** Azure Virtual Machines. */ - AZURE_VM = 'azure_vm', + AZURE_VM: 'azure_vm', /** Azure Container Instances. */ - AZURE_CONTAINER_INSTANCES = 'azure_container_instances', + AZURE_CONTAINER_INSTANCES: 'azure_container_instances', /** Azure Kubernetes Service. */ - AZURE_AKS = 'azure_aks', + AZURE_AKS: 'azure_aks', /** Azure Functions. */ - AZURE_FUNCTIONS = 'azure_functions', + AZURE_FUNCTIONS: 'azure_functions', /** Azure App Service. */ - AZURE_APP_SERVICE = 'azure_app_service', + AZURE_APP_SERVICE: 'azure_app_service', /** Google Cloud Compute Engine (GCE). */ - GCP_COMPUTE_ENGINE = 'gcp_compute_engine', + GCP_COMPUTE_ENGINE: 'gcp_compute_engine', /** Google Cloud Run. */ - GCP_CLOUD_RUN = 'gcp_cloud_run', + GCP_CLOUD_RUN: 'gcp_cloud_run', /** Google Cloud Kubernetes Engine (GKE). */ - GCP_KUBERNETES_ENGINE = 'gcp_kubernetes_engine', + GCP_KUBERNETES_ENGINE: 'gcp_kubernetes_engine', /** Google Cloud Functions (GCF). */ - GCP_CLOUD_FUNCTIONS = 'gcp_cloud_functions', + GCP_CLOUD_FUNCTIONS: 'gcp_cloud_functions', /** Google Cloud App Engine (GAE). */ - GCP_APP_ENGINE = 'gcp_app_engine', -} + GCP_APP_ENGINE: 'gcp_app_engine', +} as const +export type CloudPlatformValues = typeof CloudPlatformValues[keyof typeof CloudPlatformValues] -export enum AwsEcsLaunchtypeValues { +export const AwsEcsLaunchtypeValues = { /** ec2. */ - EC2 = 'ec2', + EC2: 'ec2', /** fargate. */ - FARGATE = 'fargate', -} + FARGATE: 'fargate', +} as const +export type AwsEcsLaunchtypeValues = typeof AwsEcsLaunchtypeValues[keyof typeof AwsEcsLaunchtypeValues] -export enum HostArchValues { +export const HostArchValues = { /** AMD64. */ - AMD64 = 'amd64', + AMD64: 'amd64', /** ARM32. */ - ARM32 = 'arm32', + ARM32: 'arm32', /** ARM64. */ - ARM64 = 'arm64', + ARM64: 'arm64', /** Itanium. */ - IA64 = 'ia64', + IA64: 'ia64', /** 32-bit PowerPC. */ - PPC32 = 'ppc32', + PPC32: 'ppc32', /** 64-bit PowerPC. */ - PPC64 = 'ppc64', + PPC64: 'ppc64', /** 32-bit x86. */ - X86 = 'x86', -} + X86: 'x86', +} as const +export type HostArchValues = typeof HostArchValues[keyof typeof HostArchValues] -export enum OsTypeValues { +export const OsTypeValues = { /** Microsoft Windows. */ - WINDOWS = 'windows', + WINDOWS: 'windows', /** Linux. */ - LINUX = 'linux', + LINUX: 'linux', /** Apple Darwin. */ - DARWIN = 'darwin', + DARWIN: 'darwin', /** FreeBSD. */ - FREEBSD = 'freebsd', + FREEBSD: 'freebsd', /** NetBSD. */ - NETBSD = 'netbsd', + NETBSD: 'netbsd', /** OpenBSD. */ - OPENBSD = 'openbsd', + OPENBSD: 'openbsd', /** DragonFly BSD. */ - DRAGONFLYBSD = 'dragonflybsd', + DRAGONFLYBSD: 'dragonflybsd', /** HP-UX (Hewlett Packard Unix). */ - HPUX = 'hpux', + HPUX: 'hpux', /** AIX (Advanced Interactive eXecutive). */ - AIX = 'aix', + AIX: 'aix', /** Oracle Solaris. */ - SOLARIS = 'solaris', + SOLARIS: 'solaris', /** IBM z/OS. */ - Z_OS = 'z_os', -} + Z_OS: 'z_os', +} as const +export type OsTypeValues = typeof OsTypeValues[keyof typeof OsTypeValues] -export enum TelemetrySdkLanguageValues { +export const TelemetrySdkLanguageValues = { /** cpp. */ - CPP = 'cpp', + CPP: 'cpp', /** dotnet. */ - DOTNET = 'dotnet', + DOTNET: 'dotnet', /** erlang. */ - ERLANG = 'erlang', + ERLANG: 'erlang', /** go. */ - GO = 'go', + GO: 'go', /** java. */ - JAVA = 'java', + JAVA: 'java', /** nodejs. */ - NODEJS = 'nodejs', + NODEJS: 'nodejs', /** php. */ - PHP = 'php', + PHP: 'php', /** python. */ - PYTHON = 'python', + PYTHON: 'python', /** ruby. */ - RUBY = 'ruby', + RUBY: 'ruby', /** webjs. */ - WEBJS = 'webjs', -} + WEBJS: 'webjs', +} as const +export type TelemetrySdkLanguageValues = typeof TelemetrySdkLanguageValues[keyof typeof TelemetrySdkLanguageValues] diff --git a/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts b/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts index 48fecf29be..052d36e4c0 100644 --- a/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts +++ b/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts @@ -723,343 +723,354 @@ the closest proxy. MESSAGE_UNCOMPRESSED_SIZE: 'message.uncompressed_size', } -// Enum definitions - -export enum DbSystemValues { +export const DbSystemValues = { /** Some other SQL database. Fallback only. See notes. */ - OTHER_SQL = 'other_sql', + OTHER_SQL: 'other_sql', /** Microsoft SQL Server. */ - MSSQL = 'mssql', + MSSQL: 'mssql', /** MySQL. */ - MYSQL = 'mysql', + MYSQL: 'mysql', /** Oracle Database. */ - ORACLE = 'oracle', + ORACLE: 'oracle', /** IBM Db2. */ - DB2 = 'db2', + DB2: 'db2', /** PostgreSQL. */ - POSTGRESQL = 'postgresql', + POSTGRESQL: 'postgresql', /** Amazon Redshift. */ - REDSHIFT = 'redshift', + REDSHIFT: 'redshift', /** Apache Hive. */ - HIVE = 'hive', + HIVE: 'hive', /** Cloudscape. */ - CLOUDSCAPE = 'cloudscape', + CLOUDSCAPE: 'cloudscape', /** HyperSQL DataBase. */ - HSQLDB = 'hsqldb', + HSQLDB: 'hsqldb', /** Progress Database. */ - PROGRESS = 'progress', + PROGRESS: 'progress', /** SAP MaxDB. */ - MAXDB = 'maxdb', + MAXDB: 'maxdb', /** SAP HANA. */ - HANADB = 'hanadb', + HANADB: 'hanadb', /** Ingres. */ - INGRES = 'ingres', + INGRES: 'ingres', /** FirstSQL. */ - FIRSTSQL = 'firstsql', + FIRSTSQL: 'firstsql', /** EnterpriseDB. */ - EDB = 'edb', + EDB: 'edb', /** InterSystems Caché. */ - CACHE = 'cache', + CACHE: 'cache', /** Adabas (Adaptable Database System). */ - ADABAS = 'adabas', + ADABAS: 'adabas', /** Firebird. */ - FIREBIRD = 'firebird', + FIREBIRD: 'firebird', /** Apache Derby. */ - DERBY = 'derby', + DERBY: 'derby', /** FileMaker. */ - FILEMAKER = 'filemaker', + FILEMAKER: 'filemaker', /** Informix. */ - INFORMIX = 'informix', + INFORMIX: 'informix', /** InstantDB. */ - INSTANTDB = 'instantdb', + INSTANTDB: 'instantdb', /** InterBase. */ - INTERBASE = 'interbase', + INTERBASE: 'interbase', /** MariaDB. */ - MARIADB = 'mariadb', + MARIADB: 'mariadb', /** Netezza. */ - NETEZZA = 'netezza', + NETEZZA: 'netezza', /** Pervasive PSQL. */ - PERVASIVE = 'pervasive', + PERVASIVE: 'pervasive', /** PointBase. */ - POINTBASE = 'pointbase', + POINTBASE: 'pointbase', /** SQLite. */ - SQLITE = 'sqlite', + SQLITE: 'sqlite', /** Sybase. */ - SYBASE = 'sybase', + SYBASE: 'sybase', /** Teradata. */ - TERADATA = 'teradata', + TERADATA: 'teradata', /** Vertica. */ - VERTICA = 'vertica', + VERTICA: 'vertica', /** H2. */ - H2 = 'h2', + H2: 'h2', /** ColdFusion IMQ. */ - COLDFUSION = 'coldfusion', + COLDFUSION: 'coldfusion', /** Apache Cassandra. */ - CASSANDRA = 'cassandra', + CASSANDRA: 'cassandra', /** Apache HBase. */ - HBASE = 'hbase', + HBASE: 'hbase', /** MongoDB. */ - MONGODB = 'mongodb', + MONGODB: 'mongodb', /** Redis. */ - REDIS = 'redis', + REDIS: 'redis', /** Couchbase. */ - COUCHBASE = 'couchbase', + COUCHBASE: 'couchbase', /** CouchDB. */ - COUCHDB = 'couchdb', + COUCHDB: 'couchdb', /** Microsoft Azure Cosmos DB. */ - COSMOSDB = 'cosmosdb', + COSMOSDB: 'cosmosdb', /** Amazon DynamoDB. */ - DYNAMODB = 'dynamodb', + DYNAMODB: 'dynamodb', /** Neo4j. */ - NEO4J = 'neo4j', + NEO4J: 'neo4j', /** Apache Geode. */ - GEODE = 'geode', + GEODE: 'geode', /** Elasticsearch. */ - ELASTICSEARCH = 'elasticsearch', + ELASTICSEARCH: 'elasticsearch', /** Memcached. */ - MEMCACHED = 'memcached', + MEMCACHED: 'memcached', /** CockroachDB. */ - COCKROACHDB = 'cockroachdb', -} + COCKROACHDB: 'cockroachdb', +} as const +export type DbSystemValues = typeof DbSystemValues[keyof typeof DbSystemValues] -export enum DbCassandraConsistencyLevelValues { +export const DbCassandraConsistencyLevelValues = { /** all. */ - ALL = 'all', + ALL: 'all', /** each_quorum. */ - EACH_QUORUM = 'each_quorum', + EACH_QUORUM: 'each_quorum', /** quorum. */ - QUORUM = 'quorum', + QUORUM: 'quorum', /** local_quorum. */ - LOCAL_QUORUM = 'local_quorum', + LOCAL_QUORUM: 'local_quorum', /** one. */ - ONE = 'one', + ONE: 'one', /** two. */ - TWO = 'two', + TWO: 'two', /** three. */ - THREE = 'three', + THREE: 'three', /** local_one. */ - LOCAL_ONE = 'local_one', + LOCAL_ONE: 'local_one', /** any. */ - ANY = 'any', + ANY: 'any', /** serial. */ - SERIAL = 'serial', + SERIAL: 'serial', /** local_serial. */ - LOCAL_SERIAL = 'local_serial', -} + LOCAL_SERIAL: 'local_serial', +} as const +export type DbCassandraConsistencyLevelValues = typeof DbCassandraConsistencyLevelValues[keyof typeof DbCassandraConsistencyLevelValues] -export enum FaasTriggerValues { +export const FaasTriggerValues = { /** A response to some data source operation such as a database or filesystem read/write. */ - DATASOURCE = 'datasource', + DATASOURCE: 'datasource', /** To provide an answer to an inbound HTTP request. */ - HTTP = 'http', + HTTP: 'http', /** A function is set to be executed when messages are sent to a messaging system. */ - PUBSUB = 'pubsub', + PUBSUB: 'pubsub', /** A function is scheduled to be executed regularly. */ - TIMER = 'timer', + TIMER: 'timer', /** If none of the others apply. */ - OTHER = 'other', -} + OTHER: 'other', +} as const +export type FaasTriggerValues = typeof FaasTriggerValues[keyof typeof FaasTriggerValues] -export enum FaasDocumentOperationValues { +export const FaasDocumentOperationValues = { /** When a new object is created. */ - INSERT = 'insert', + INSERT: 'insert', /** When an object is modified. */ - EDIT = 'edit', + EDIT: 'edit', /** When an object is deleted. */ - DELETE = 'delete', -} + DELETE: 'delete', +} as const +export type FaasDocumentOperationValues = typeof FaasDocumentOperationValues[keyof typeof FaasDocumentOperationValues] -export enum FaasInvokedProviderValues { +export const FaasInvokedProviderValues = { /** Alibaba Cloud. */ - ALIBABA_CLOUD = 'alibaba_cloud', + ALIBABA_CLOUD: 'alibaba_cloud', /** Amazon Web Services. */ - AWS = 'aws', + AWS: 'aws', /** Microsoft Azure. */ - AZURE = 'azure', + AZURE: 'azure', /** Google Cloud Platform. */ - GCP = 'gcp', -} + GCP: 'gcp', +} as const +export type FaasInvokedProviderValues = typeof FaasInvokedProviderValues[keyof typeof FaasInvokedProviderValues] -export enum NetTransportValues { +export const NetTransportValues = { /** ip_tcp. */ - IP_TCP = 'ip_tcp', + IP_TCP: 'ip_tcp', /** ip_udp. */ - IP_UDP = 'ip_udp', + IP_UDP: 'ip_udp', /** Another IP-based protocol. */ - IP = 'ip', + IP: 'ip', /** Unix Domain socket. See below. */ - UNIX = 'unix', + UNIX: 'unix', /** Named or anonymous pipe. See note below. */ - PIPE = 'pipe', + PIPE: 'pipe', /** In-process communication. */ - INPROC = 'inproc', + INPROC: 'inproc', /** Something else (non IP-based). */ - OTHER = 'other', -} + OTHER: 'other', +} as const +export type NetTransportValues = typeof NetTransportValues[keyof typeof NetTransportValues] -export enum NetHostConnectionTypeValues { +export const NetHostConnectionTypeValues = { /** wifi. */ - WIFI = 'wifi', + WIFI: 'wifi', /** wired. */ - WIRED = 'wired', + WIRED: 'wired', /** cell. */ - CELL = 'cell', + CELL: 'cell', /** unavailable. */ - UNAVAILABLE = 'unavailable', + UNAVAILABLE: 'unavailable', /** unknown. */ - UNKNOWN = 'unknown', -} + UNKNOWN: 'unknown', +} as const +export type NetHostConnectionTypeValues = typeof NetHostConnectionTypeValues[keyof typeof NetHostConnectionTypeValues] -export enum NetHostConnectionSubtypeValues { +export const NetHostConnectionSubtypeValues = { /** GPRS. */ - GPRS = 'gprs', + GPRS: 'gprs', /** EDGE. */ - EDGE = 'edge', + EDGE: 'edge', /** UMTS. */ - UMTS = 'umts', + UMTS: 'umts', /** CDMA. */ - CDMA = 'cdma', + CDMA: 'cdma', /** EVDO Rel. 0. */ - EVDO_0 = 'evdo_0', + EVDO_0: 'evdo_0', /** EVDO Rev. A. */ - EVDO_A = 'evdo_a', + EVDO_A: 'evdo_a', /** CDMA2000 1XRTT. */ - CDMA2000_1XRTT = 'cdma2000_1xrtt', + CDMA2000_1XRTT: 'cdma2000_1xrtt', /** HSDPA. */ - HSDPA = 'hsdpa', + HSDPA: 'hsdpa', /** HSUPA. */ - HSUPA = 'hsupa', + HSUPA: 'hsupa', /** HSPA. */ - HSPA = 'hspa', + HSPA: 'hspa', /** IDEN. */ - IDEN = 'iden', + IDEN: 'iden', /** EVDO Rev. B. */ - EVDO_B = 'evdo_b', + EVDO_B: 'evdo_b', /** LTE. */ - LTE = 'lte', + LTE: 'lte', /** EHRPD. */ - EHRPD = 'ehrpd', + EHRPD: 'ehrpd', /** HSPAP. */ - HSPAP = 'hspap', + HSPAP: 'hspap', /** GSM. */ - GSM = 'gsm', + GSM: 'gsm', /** TD-SCDMA. */ - TD_SCDMA = 'td_scdma', + TD_SCDMA: 'td_scdma', /** IWLAN. */ - IWLAN = 'iwlan', + IWLAN: 'iwlan', /** 5G NR (New Radio). */ - NR = 'nr', + NR: 'nr', /** 5G NRNSA (New Radio Non-Standalone). */ - NRNSA = 'nrnsa', + NRNSA: 'nrnsa', /** LTE CA. */ - LTE_CA = 'lte_ca', -} + LTE_CA: 'lte_ca', +} as const +export type NetHostConnectionSubtypeValues = typeof NetHostConnectionSubtypeValues[keyof typeof NetHostConnectionSubtypeValues] -export enum HttpFlavorValues { +export const HttpFlavorValues = { /** HTTP 1.0. */ - HTTP_1_0 = '1.0', + HTTP_1_0: '1.0', /** HTTP 1.1. */ - HTTP_1_1 = '1.1', + HTTP_1_1: '1.1', /** HTTP 2. */ - HTTP_2_0 = '2.0', + HTTP_2_0: '2.0', /** SPDY protocol. */ - SPDY = 'SPDY', + SPDY: 'SPDY', /** QUIC protocol. */ - QUIC = 'QUIC', -} + QUIC: 'QUIC', +} as const +export type HttpFlavorValues = typeof HttpFlavorValues[keyof typeof HttpFlavorValues] -export enum MessagingDestinationKindValues { +export const MessagingDestinationKindValues = { /** A message sent to a queue. */ - QUEUE = 'queue', + QUEUE: 'queue', /** A message sent to a topic. */ - TOPIC = 'topic', -} + TOPIC: 'topic', +} as const +export type MessagingDestinationKindValues = typeof MessagingDestinationKindValues[keyof typeof MessagingDestinationKindValues] -export enum MessagingOperationValues { +export const MessagingOperationValues = { /** receive. */ - RECEIVE = 'receive', + RECEIVE: 'receive', /** process. */ - PROCESS = 'process', -} + PROCESS: 'process', +} as const +export type MessagingOperationValues = typeof MessagingOperationValues[keyof typeof MessagingOperationValues] -export enum RpcGrpcStatusCodeValues { +export const RpcGrpcStatusCodeValues = { /** OK. */ - OK = 0, + OK: 0, /** CANCELLED. */ - CANCELLED = 1, + CANCELLED: 1, /** UNKNOWN. */ - UNKNOWN = 2, + UNKNOWN: 2, /** INVALID_ARGUMENT. */ - INVALID_ARGUMENT = 3, + INVALID_ARGUMENT: 3, /** DEADLINE_EXCEEDED. */ - DEADLINE_EXCEEDED = 4, + DEADLINE_EXCEEDED: 4, /** NOT_FOUND. */ - NOT_FOUND = 5, + NOT_FOUND: 5, /** ALREADY_EXISTS. */ - ALREADY_EXISTS = 6, + ALREADY_EXISTS: 6, /** PERMISSION_DENIED. */ - PERMISSION_DENIED = 7, + PERMISSION_DENIED: 7, /** RESOURCE_EXHAUSTED. */ - RESOURCE_EXHAUSTED = 8, + RESOURCE_EXHAUSTED: 8, /** FAILED_PRECONDITION. */ - FAILED_PRECONDITION = 9, + FAILED_PRECONDITION: 9, /** ABORTED. */ - ABORTED = 10, + ABORTED: 10, /** OUT_OF_RANGE. */ - OUT_OF_RANGE = 11, + OUT_OF_RANGE: 11, /** UNIMPLEMENTED. */ - UNIMPLEMENTED = 12, + UNIMPLEMENTED: 12, /** INTERNAL. */ - INTERNAL = 13, + INTERNAL: 13, /** UNAVAILABLE. */ - UNAVAILABLE = 14, + UNAVAILABLE: 14, /** DATA_LOSS. */ - DATA_LOSS = 15, + DATA_LOSS: 15, /** UNAUTHENTICATED. */ - UNAUTHENTICATED = 16, -} + UNAUTHENTICATED: 16, +} as const +export type RpcGrpcStatusCodeValues = typeof RpcGrpcStatusCodeValues[keyof typeof RpcGrpcStatusCodeValues] -export enum MessageTypeValues { +export const MessageTypeValues = { /** sent. */ - SENT = 'SENT', + SENT: 'SENT', /** received. */ - RECEIVED = 'RECEIVED', -} + RECEIVED: 'RECEIVED', +} as const +export type MessageTypeValues = typeof MessageTypeValues[keyof typeof MessageTypeValues] diff --git a/scripts/semconv/templates/SemanticAttributes.ts.j2 b/scripts/semconv/templates/SemanticAttributes.ts.j2 index 18004561d9..eb144b93b2 100644 --- a/scripts/semconv/templates/SemanticAttributes.ts.j2 +++ b/scripts/semconv/templates/SemanticAttributes.ts.j2 @@ -44,19 +44,19 @@ export const {{class}} = { {%- endfor %} } -// Enum definitions {%- for attribute in attributes if attribute.is_local and not attribute.ref %} {%- if attribute.is_enum %} {%- set class_name = attribute.fqn | to_camelcase(True) ~ "Values" %} {%- set type = attribute.attr_type.enum_type %} {% if attribute.attr_type.members is defined and attribute.attr_type.members|length > 0 %} -export enum {{class_name}} { +export const {{class_name}} = { {%- for member in attribute.attr_type.members if attribute.is_local and not attribute.ref %} /** {% filter escape %}{{member.brief | to_doc_brief}}.{% endfilter %} */ - {{ member.member_id | to_const_name }} = {{ print_value(type, member.value) }}, + {{ member.member_id | to_const_name }}: {{ print_value(type, member.value) }}, {%- endfor %} -} +} as const +export type {{class_name}} = typeof {{class_name}}[keyof typeof {{class_name}}] {% endif %} {% endif %} From f2a562430bf36997d1e16ac9d2d9cf3d22ce40f0 Mon Sep 17 00:00:00 2001 From: Severin Neumann Date: Sun, 17 Oct 2021 18:14:21 +0200 Subject: [PATCH 05/15] chore: remove getting started and link to documentation. (#2493) Co-authored-by: Valentin Marchaud --- README.md | 4 +- getting-started/.eslintrc.js | 6 - getting-started/README.md | 398 ------------------ getting-started/example/app.js | 42 -- getting-started/example/package.json | 15 - getting-started/images/prometheus-graph.png | Bin 90810 -> 0 bytes getting-started/images/prometheus.png | Bin 98150 -> 0 bytes getting-started/images/zipkin-trace.png | Bin 64614 -> 0 bytes getting-started/images/zipkin.png | Bin 36304 -> 0 bytes getting-started/monitored-example/app.js | 45 -- .../monitored-example/monitoring.js | 40 -- .../monitored-example/package.json | 17 - getting-started/traced-example/app.js | 42 -- getting-started/traced-example/package.json | 22 - getting-started/traced-example/tracing.js | 38 -- getting-started/ts-example/.eslintrc | 16 - getting-started/ts-example/README.md | 389 ----------------- getting-started/ts-example/example/app.ts | 40 -- .../ts-example/example/package.json | 20 - .../ts-example/monitored-example/app.ts | 42 -- .../monitored-example/monitoring.ts | 39 -- .../ts-example/monitored-example/package.json | 22 - .../ts-example/traced-example/app.ts | 40 -- .../ts-example/traced-example/package.json | 27 -- .../ts-example/traced-example/tracing.ts | 41 -- package.json | 2 - 26 files changed, 2 insertions(+), 1345 deletions(-) delete mode 100644 getting-started/.eslintrc.js delete mode 100644 getting-started/README.md delete mode 100644 getting-started/example/app.js delete mode 100644 getting-started/example/package.json delete mode 100644 getting-started/images/prometheus-graph.png delete mode 100644 getting-started/images/prometheus.png delete mode 100644 getting-started/images/zipkin-trace.png delete mode 100644 getting-started/images/zipkin.png delete mode 100644 getting-started/monitored-example/app.js delete mode 100644 getting-started/monitored-example/monitoring.js delete mode 100644 getting-started/monitored-example/package.json delete mode 100644 getting-started/traced-example/app.js delete mode 100644 getting-started/traced-example/package.json delete mode 100644 getting-started/traced-example/tracing.js delete mode 100644 getting-started/ts-example/.eslintrc delete mode 100644 getting-started/ts-example/README.md delete mode 100644 getting-started/ts-example/example/app.ts delete mode 100644 getting-started/ts-example/example/package.json delete mode 100644 getting-started/ts-example/monitored-example/app.ts delete mode 100644 getting-started/ts-example/monitored-example/monitoring.ts delete mode 100644 getting-started/ts-example/monitored-example/package.json delete mode 100644 getting-started/ts-example/traced-example/app.ts delete mode 100644 getting-started/ts-example/traced-example/package.json delete mode 100644 getting-started/ts-example/traced-example/tracing.ts diff --git a/README.md b/README.md index ae4eb6f483..45e83be795 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ---

- Getting Started + Getting Started   •   API Reference   •   @@ -131,7 +131,7 @@ process.on('SIGTERM', () => { node -r ./tracing.js app.js ``` -The above example will emit auto-instrumented telemetry about your Node.js application to the console. For a more in-depth example, see the [Getting Started Guide](https://github.com/open-telemetry/opentelemetry-js/blob/main/getting-started/README.md). For more information about automatic instrumentation see [@opentelemetry/sdk-trace-node][otel-node], which provides auto-instrumentation for Node.js applications. If the automatic instrumentation does not suit your needs, or you would like to create manual traces, see [@opentelemetry/sdk-trace-base][otel-tracing] +The above example will emit auto-instrumented telemetry about your Node.js application to the console. For a more in-depth example, see the [Getting Started Guide](https://opentelemetry.io/docs/js/getting_started/). For more information about automatic instrumentation see [@opentelemetry/sdk-trace-node][otel-node], which provides auto-instrumentation for Node.js applications. If the automatic instrumentation does not suit your needs, or you would like to create manual traces, see [@opentelemetry/sdk-trace-base][otel-tracing] ### Library Author diff --git a/getting-started/.eslintrc.js b/getting-started/.eslintrc.js deleted file mode 100644 index 8580fbfb04..0000000000 --- a/getting-started/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable global-require */ -/* eslint-disable strict */ - -module.exports = { - ...require('../examples/.eslintrc.json'), -}; diff --git a/getting-started/README.md b/getting-started/README.md deleted file mode 100644 index 132bd340ae..0000000000 --- a/getting-started/README.md +++ /dev/null @@ -1,398 +0,0 @@ -# Getting started with OpenTelemetry JS - -This guide walks you through the setup and configuration process for a tracing backend (in this case [Zipkin](https://zipkin.io), but [Jaeger](https://www.jaegertracing.io) is simple to use as well), a metrics backend like [Prometheus](https://prometheus.io), and auto-instrumentation of NodeJS. [You can find the guide for TypeScript here](ts-example/README.md#getting-started-with-opentelemetry-js-typescript). - -- [Getting started with OpenTelemetry JS](#getting-started-with-opentelemetry-js) - - [Trace your application with OpenTelemetry](#trace-your-application-with-opentelemetry) - - [Set up a tracing backend](#set-up-a-tracing-backend) - - [Trace your NodeJS application](#trace-your-nodejs-application) - - [Install the required OpenTelemetry libraries](#install-the-required-opentelemetry-libraries) - - [Initialize a global tracer](#initialize-a-global-tracer) - - [Initialize and register a trace exporter](#initialize-and-register-a-trace-exporter) - - [Collect metrics using OpenTelemetry](#collect-metrics-using-opentelemetry) - - [Set up a metrics backend](#set-up-a-metrics-backend) - - [Monitor your NodeJS application](#monitor-your-nodejs-application) - - [Install the required OpenTelemetry metrics libraries](#install-the-required-opentelemetry-metrics-libraries) - - [Initialize a meter and collect metrics](#initialize-a-meter-and-collect-metrics) - - [Initialize and register a metrics exporter](#initialize-and-register-a-metrics-exporter) - -## Trace your application with OpenTelemetry - -([link to TypeScript version](ts-example/README.md#trace-your-application-with-opentelemetry)) - -This guide assumes you're using Zipkin as your tracing backend, but modifying it for Jaeger should be straightforward. - -You can find an example application to use with this guide in the [example directory](example). See what it looks like with tracing enabled in the [traced-example directory](traced-example). - -### Set up a tracing backend - -([link to TypeScript version](ts-example/README.md#set-up-a-tracing-backend)) - -The first thing you need before you can start collecting traces is a tracing backend like Zipkin that you can export traces to. If you already have a supported tracing backend (Zipkin or Jaeger), you can skip this step. If not, you need to run one. - -To set up Zipkin as quickly as possible, run the latest [Docker Zipkin](https://github.com/openzipkin/docker-zipkin) container, exposing port `9411`. If you can’t run Docker containers, you need to download and run Zipkin by following the Zipkin [quickstart guide](https://zipkin.io/pages/quickstart.html). - -```sh -docker run --rm -d -p 9411:9411 --name zipkin openzipkin/zipkin -``` - -Browse to to make sure you can see the Zipkin UI. - -

- -### Trace your NodeJS application - -([link to TypeScript version](ts-example/README.md#trace-your-nodejs-application)) - -This guide uses the example application provided in the [example directory](example), but the steps to instrument your own application should be broadly the same. Here's an overview of what you'll be doing: - -1. Install the required OpenTelemetry libraries -2. Initialize a global tracer -3. Initialize and register a trace exporter - -#### Install the required OpenTelemetry libraries - -([link to TypeScript version](ts-example/README.md#install-the-required-opentelemetry-libraries)) - -To create traces on NodeJS, you need `@opentelemetry/sdk-trace-node`, `@opentelemetry/core`, and any instrumentation required by your application such as gRPC or HTTP. If you're using the example application, you need to install `@opentelemetry/instrumentation-http` and `@opentelemetry/instrumentation-express`. - -```sh -$ npm install \ - @opentelemetry/api \ - @opentelemetry/sdk-trace-node \ - @opentelemetry/instrumentation-http \ - @opentelemetry/instrumentation-express \ - @opentelemetry/instrumentation-grpc -``` - -#### Initialize a global tracer - -([link to TypeScript version](ts-example/README.md#initialize-a-global-tracer)) - -All tracing initialization should happen before your application code runs. The easiest way to do this is to initialize tracing in a separate file that is required using the `node` `-r` option before your application code runs. - -Create a file named `tracing.js` and add the following code: - -```javascript -'use strict'; - -const { diag, DiagConsoleLogger, DiagLogLevel } = require("@opentelemetry/api"); -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { registerInstrumentations } = require("@opentelemetry/instrumentation"); -const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http"); -const { GrpcInstrumentation } = require("@opentelemetry/instrumentation-grpc"); - -const provider = new NodeTracerProvider(); - -diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL); - -provider.register(); - -registerInstrumentations({ - instrumentations: [ - new HttpInstrumentation(), - new GrpcInstrumentation(), - ], -}); - -``` - -Now, if you run your application with `node -r ./tracing.js app.js`, your application will create and propagate traces over HTTP. If an already instrumented service that supports [Trace Context](https://www.w3.org/TR/trace-context/) headers calls your application using HTTP, and you call another application using HTTP, the Trace Context headers will be correctly propagated. - -However, if you want to see a completed trace, you need to register an exporter to send traces to a tracing backend. - -#### Initialize and register a trace exporter - -([link to TypeScript version](ts-example/README.md#initialize-and-register-a-trace-exporter)) - -This guide uses the Zipkin tracing backend. However, if you're using another backend like [Jaeger](https://www.jaegertracing.io), make your change there. - -To export traces, you need a few more dependencies. Install them with the following command: - -```sh -$ npm install \ - @opentelemetry/sdk-trace-base \ - @opentelemetry/exporter-zipkin - -$ # for jaeger you would run this command: -$ # npm install @opentelemetry/exporter-jaeger -``` - -After you install these dependencies, initialize and register them. Modify `tracing.js` so it matches the following code snippet. Optionally replace the service name `"getting-started"` with your own service name: - -```javascript -'use strict'; - -const { diag, DiagConsoleLogger, DiagLogLevel } = require("@opentelemetry/api"); -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { Resource } = require('@opentelemetry/resources'); -const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); -const { SimpleSpanProcessor } = require("@opentelemetry/sdk-trace-base"); -const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); -const { registerInstrumentations } = require("@opentelemetry/instrumentation"); -const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http"); -const { GrpcInstrumentation } = require("@opentelemetry/instrumentation-grpc"); - -const provider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: "getting-started", - }) -}); - -diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL); - -provider.addSpanProcessor( - new SimpleSpanProcessor( - new ZipkinExporter({ - // If you are running your tracing backend on another host, - // you can point to it using the `url` parameter of the - // exporter config. - }) - ) -); - -provider.register(); - -registerInstrumentations({ - instrumentations: [ - new HttpInstrumentation(), - new GrpcInstrumentation(), - ], -}); - -console.log("tracing initialized"); -``` - -Now if you run your application with the `tracing.js` file loaded, and you send requests to your application over HTTP (in the sample application just browse to ), you'll see traces exported to your tracing backend that look like this: - -```sh -node -r ./tracing.js app.js -``` - -

- -**Note:** Some spans appear to be duplicated, but they're not. This is because the sample application is both the client and the server for these requests. You see one span which is the client-side request timing, and one span which is the server side request timing. Anywhere they don’t overlap is network time. - -## Collect metrics using OpenTelemetry - -([link to TypeScript version](ts-example/README.md#collect-metrics-using-opentelemetry)) - -This guide assumes you're using Prometheus as your metrics backend. It's currently the only metrics backend supported by OpenTelemetry JS. - -**Note**: This section is a work in progress. - -### Set up a metrics backend - -([link to TypeScript version](ts-example/README.md#set-up-a-metrics-backend)) - -Now that you have end-to-end traces, you can collect and export some basic metrics. - -Currently, the only supported metrics backend is [Prometheus](https://prometheus.io). Go to the [Prometheus download page](https://prometheus.io/download/) and download the latest release of Prometheus for your operating system. - -Open a command line and `cd` into the directory where you downloaded the Prometheus tarball. Untar it into the newly created directory. - -```sh -$ cd Downloads - -$ # Replace the file name below with your downloaded tarball -$ tar xvfz prometheus-2.20.1.darwin-amd64.tar - -$ # Replace the dir below with your created directory -$ cd prometheus-2.20.1.darwin-amd64 - -$ ls -LICENSE console_libraries data prometheus.yml tsdb -NOTICE consoles prometheus promtool -``` - -The created directory should have a file named `prometheus.yml`. This is the file used to configure Prometheus. For now, just make sure Prometheus starts by running the `./prometheus` binary in the folder and browse to . - -```sh -$ ./prometheus -# some output elided for brevity -msg="Starting Prometheus" version="(version=2.14.0, branch=HEAD, revision=edeb7a44cbf745f1d8be4ea6f215e79e651bfe19)" -# some output elided for brevity -level=info ts=2019-11-21T20:39:40.262Z caller=web.go:496 component=web msg="Start listening for connections" address=0.0.0.0:9090 -# some output elided for brevity -level=info ts=2019-11-21T20:39:40.383Z caller=main.go:626 msg="Server is ready to receive web requests." -``` - -

- -Once you confirm that Prometheus starts, replace the contents of `prometheus.yml` with the following: - -```yaml -# my global config -global: - scrape_interval: 15s # Set the scrape interval to every 15 seconds. - -scrape_configs: - - job_name: 'opentelemetry' - # metrics_path defaults to '/metrics' - # scheme defaults to 'http'. - static_configs: - - targets: ['localhost:9464'] -``` - -### Monitor your NodeJS application - -([link to TypeScript version](ts-example/README.md#monitor-your-nodejs-application)) - -You can find an example application to use with this guide in the [example directory](example). See what it looks like with metric monitoring enabled in the [monitored-example directory](monitored-example). - -Here's an overview of what you'll be doing: - -1. Install the required OpenTelemetry metrics libraries -2. Initialize a meter and collect metrics -3. Initialize and register a metrics exporter - -#### Install the required OpenTelemetry metrics libraries - -([link to TypeScript version](ts-example/README.md#install-the-required-opentelemetry-sdk-metrics-base-libraries)) - -To create metrics on NodeJS, you need `@opentelemetry/sdk-metrics-base`. - -```sh -$ npm install \ - @opentelemetry/sdk-metrics-base -``` - -#### Initialize a meter and collect metrics - -([link to TypeScript version](ts-example/README.md#initialize-a-meter-and-collect-metrics)) - -You need a `Meter` to create and monitor metrics. A `Meter` in OpenTelemetry is the mechanism used to create and manage metrics, labels, and metric exporters. - -Create a file named `monitoring.js` and add the following code: - -```javascript -'use strict'; - -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -const meter = new MeterProvider().getMeter('your-meter-name'); -``` - -Now you can require this file from your application code and use the `Meter` to create and manage metrics. The simplest of these metrics is a counter. Create and export from your `monitoring.js` file a middleware function that express can use to count all requests by route. Modify the `monitoring.js` file so it looks like this: - -```javascript -'use strict'; - -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -const meter = new MeterProvider().getMeter('your-meter-name'); - -const requestCount = meter.createCounter("requests", { - description: "Count all incoming requests" -}); - -const boundInstruments = new Map(); - -module.exports.countAllRequests = () => { - return (req, res, next) => { - if (!boundInstruments.has(req.path)) { - const labels = { route: req.path }; - const boundCounter = requestCount.bind(labels); - boundInstruments.set(req.path, boundCounter); - } - - boundInstruments.get(req.path).add(1); - next(); - }; -}; -``` - -Now import and use this middleware in your application code: - -```javascript -const { countAllRequests } = require("./monitoring"); -const app = express(); -app.use(countAllRequests()); -``` - -Now when you make requests to your service, your meter will count all requests. - -**Note**: Creating a new `labelSet` and `binding` on every request is not ideal because creating the `labelSet` can often be an expensive operation. Therefore, the instruments are created and stored in a `Map` according to the route key. - -#### Initialize and register a metrics exporter - -([link to TypeScript version](ts-example/README.md#initialize-and-register-a-metrics-exporter)) - -Counting metrics is only useful if you can export them somewhere where you can see them. For this, w'ere going to use Prometheus. Creating and registering a metrics exporter is much like the tracing exporter above. First you need to install the Prometheus exporter by running the following command: - -```sh -npm install @opentelemetry/exporter-prometheus -``` - -Next, modify your `monitoring.js` file to look like this: - -```javascript -"use strict"; - -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); -const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); - -const prometheusPort = PrometheusExporter.DEFAULT_OPTIONS.port -const prometheusEndpoint = PrometheusExporter.DEFAULT_OPTIONS.endpoint - -const exporter = new PrometheusExporter( - { - startServer: true, - }, - () => { - console.log( - `prometheus scrape endpoint: http://localhost:${prometheusPort}${prometheusEndpoint}`, - ); - }, -); - -const meter = new MeterProvider({ - exporter, - interval: 1000, -}).getMeter('your-meter-name'); - -const requestCount = meter.createCounter("requests", { - description: "Count all incoming requests" -}); - -const boundInstruments = new Map(); - -module.exports.countAllRequests = () => { - return (req, res, next) => { - if (!boundInstruments.has(req.path)) { - const labels = { route: req.path }; - const boundCounter = requestCount.bind(labels); - boundInstruments.set(req.path, boundCounter); - } - - boundInstruments.get(req.path).add(1); - next(); - }; -}; -``` - -Ensure Prometheus is running by running the `prometheus` binary from earlier and start your application. - -```sh -$ npm start - -> @opentelemetry/getting-started@1.0.0 start /App/opentelemetry-js/getting-started/monitored-example -> node app.js - -prometheus scrape endpoint: http://localhost:9464/metrics -Listening for requests on http://localhost:8080 -``` - -Now each time you browse to you should see "Hello from the backend" in your browser and your metrics in Prometheus should update. You can verify the current metrics by browsing to , which should look like this: - -```sh -# HELP requests Count all incoming requests -# TYPE requests counter -requests{route="/"} 1 -requests{route="/middle-tier"} 2 -requests{route="/backend"} 4 -``` - -You should also be able to see gathered metrics in your Prometheus web UI. - -

diff --git a/getting-started/example/app.js b/getting-started/example/app.js deleted file mode 100644 index 287ab78cb3..0000000000 --- a/getting-started/example/app.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const PORT = process.env.PORT || '8080'; - -const express = require('express'); -const axios = require('axios'); - -const app = express(); - -app.get('/', (req, res) => { - axios - .get(`http://localhost:${PORT}/middle-tier`) - .then(() => axios.get(`http://localhost:${PORT}/middle-tier`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/middle-tier', (req, res) => { - axios - .get(`http://localhost:${PORT}/backend`) - .then(() => axios.get(`http://localhost:${PORT}/backend`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/backend', (req, res) => { - res.send('Hello from the backend'); -}); - -app.listen(parseInt(PORT, 10), () => { - console.log(`Listening for requests on http://localhost:${PORT}`); -}); diff --git a/getting-started/example/package.json b/getting-started/example/package.json deleted file mode 100644 index 4c4e289434..0000000000 --- a/getting-started/example/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@opentelemetry/getting-started-example", - "version": "0.25.0", - "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", - "main": "app.js", - "scripts": { - "start": "node app.js" - }, - "author": "OpenTelemetry Authors", - "license": "Apache-2.0", - "dependencies": { - "axios": "^0.21.0", - "express": "^4.17.1" - } -} diff --git a/getting-started/images/prometheus-graph.png b/getting-started/images/prometheus-graph.png deleted file mode 100644 index c4e174ffd3424c03ea116ec792fec82501d67a54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90810 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 uw?cljs;ygXrpu>iqIv#7!2L!dak0q69YNe6 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

)>Z^#8Acrz_P0o!dn$X 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 Cu9A@e diff --git a/getting-started/images/prometheus.png b/getting-started/images/prometheus.png deleted file mode 100644 index ef94a1faf04e74d58c3bdaed1a8e6b168f9a2528..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98150 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 diff --git a/getting-started/images/zipkin-trace.png b/getting-started/images/zipkin-trace.png deleted file mode 100644 index c00c86865df2594c35b228241db7db0a0820327c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64614 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 KpUXO@geCx9V=49k diff --git a/getting-started/images/zipkin.png b/getting-started/images/zipkin.png deleted file mode 100644 index 77e23745b49cdc418b7211a3932ea866cafa4e7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36304 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

- -### Trace Your NodeJS Application - -([link to JavaScript version](../README.md#trace-your-nodejs-application)) - -This guide uses the example application provided in the [example directory](example) but the steps to instrument your own application should be broadly the same. Here is an overview of what we will be doing. - -1. Install the required OpenTelemetry libraries -2. Initialize a global tracer -3. Initialize and register a trace exporter - -#### Install the required OpenTelemetry libraries - -([link to JavaScript version](../README.md#install-the-required-opentelemetry-libraries)) - -To create traces on NodeJS, you will need `@opentelemetry/sdk-trace-node`, `@opentelemetry/api`, and any plugins required by your application such as gRPC, or HTTP. If you are using the example application, you will need to install `@opentelemetry/instrumentation-http` and `@opentelemetry/instrumentation-express`. - -```sh -$ npm install \ - @opentelemetry/api \ - @opentelemetry/sdk-trace-node \ - @opentelemetry/instrumentation \ - @opentelemetry/instrumentation-http \ - @opentelemetry/instrumentation-express \ - @@opentelemetry/resources \ - @opentelemetry/semantic-conventions -``` - -#### Initialize a global tracer - -([link to JavaScript version](../README.md#initialize-a-global-tracer)) - -All tracing initialization should happen before your application’s code runs. The easiest way to do this is to initialize tracing in a separate file that is required using node’s `-r` option before application code runs. - -Create a file named `tracing.ts` and add the following code: - -```typescript -import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'; -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; - - -const provider: NodeTracerProvider = new NodeTracerProvider(); - -diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL); - -provider.register(); - -registerInstrumentations({ - instrumentations: [ - new ExpressInstrumentation(), - new HttpInstrumentation(), - ], -}); - -``` - -If you run your application now with `ts-node -r ./tracing.ts app.ts`, your application will create and propagate traces over HTTP. If an already instrumented service that supports [Trace Context](https://www.w3.org/TR/trace-context/) headers calls your application using HTTP, and you call another application using HTTP, the Trace Context headers will be correctly propagated. - -If you wish to see a completed trace, however, there is one more step. You must register an exporter to send traces to a tracing backend. - -#### Initialize and Register a Trace Exporter - -([link to JavaScript version](../README.md#initialize-and-register-a-trace-exporter)) - -This guide uses the Zipkin tracing backend, but if you are using another backend like [Jaeger](https://www.jaegertracing.io), this is where you would make your change. - -To export traces, we will need a few more dependencies. Install them with the following command: - -```sh -$ npm install \ - @opentelemetry/sdk-trace-base \ - @opentelemetry/exporter-zipkin - -$ # for jaeger you would run this command: -$ # npm install @opentelemetry/exporter-jaeger -``` - -After these dependencies are installed, we will need to initialize and register them. Modify `tracing.ts` so that it matches the following code snippet, replacing the service name `"getting-started"` with your own service name if you wish. - -```typescript -import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'; -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { Resource } from '@opentelemetry/resources' -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' -import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; -// For Jaeger, use the following line instead: -// import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; - -const provider: NodeTracerProvider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'getting-started', - }), -}); - -diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL); - -provider.addSpanProcessor( - new SimpleSpanProcessor( - new ZipkinExporter({ - // For Jaeger, use the following line instead: - // new JaegerExporter({ - // If you are running your tracing backend on another host, - // you can point to it using the `url` parameter of the - // exporter config. - }), - ), -); -provider.register(); - -registerInstrumentations({ - instrumentations: [ - new ExpressInstrumentation(), - new HttpInstrumentation(), - ], -}); - -console.log('tracing initialized'); -``` - -Now if you run your application with the `tracing.ts` file loaded, and you send requests to your application over HTTP (in the sample application just browse to you will see traces exported to your tracing backend that look like this: - -```sh -ts-node -r ./tracing.ts app.ts -``` - -

- -**Note:** Some spans appear to be duplicated, but they are not. This is because the sample application is both the client and the server for these requests. You see one span that is the client side request timing, and one span that is the server side request timing. Anywhere they don’t overlap is network time. - -## Collect Metrics Using OpenTelemetry - -([link to JavaScript version](../README.md#collect-metrics-using-opentelemetry)) - -This guide assumes you are going to be using Prometheus as your metrics backend. It is currently the only metrics backend supported by OpenTelemetry JS. - -**Note**: This section is a work in progress - -### Set up a Metrics Backend - -([link to JavaScript version](../README.md#set-up-a-metrics-backend)) - -Now that we have end-to-end traces, we will collect and export some basic metrics. - -Currently, the only supported metrics backend is [Prometheus](https://prometheus.io). Head to the [Prometheus download page](https://prometheus.io/download/) and download the latest release of Prometheus for your operating system. - -Open a command line and `cd` into the directory where you downloaded the Prometheus tarball. Untar it and change into the newly created directory. - -```sh -$ cd Downloads - -$ # Replace the file name below with your downloaded tarball -$ tar xvfz prometheus-2.20.1.darwin-amd64.tar - -$ # Replace the dir below with your created directory -$ cd prometheus-2.20.1.darwin-amd64 - -$ ls -LICENSE console_libraries data prometheus.yml tsdb -NOTICE consoles prometheus promtool -``` - -The created directory should have a file named `prometheus.yml`. This is the file used to configure Prometheus. For now, just make sure Prometheus starts by running the `./prometheus` binary in the folder and browse to . - -```sh -$ ./prometheus -# some output elided for brevity -msg="Starting Prometheus" version="(version=2.14.0, branch=HEAD, revision=edeb7a44cbf745f1d8be4ea6f215e79e651bfe19)" -# some output elided for brevity -level=info ts=2019-11-21T20:39:40.262Z caller=web.go:496 component=web msg="Start listening for connections" address=0.0.0.0:9090 -# some output elided for brevity -level=info ts=2019-11-21T20:39:40.383Z caller=main.go:626 msg="Server is ready to receive web requests." -``` - -

- -Once we know prometheus starts, replace the contents of `prometheus.yml` with the following: - -```yaml -# my global config -global: - scrape_interval: 15s # Set the scrape interval to every 15 seconds. - -scrape_configs: - - job_name: 'opentelemetry' - # metrics_path defaults to '/metrics' - # scheme defaults to 'http'. - static_configs: - - targets: ['localhost:9464'] -``` - -### Monitor Your NodeJS Application - -([link to JavaScript version](../README.md#monitor-your-nodejs-application)) - -An example application which can be used with this guide can be found at in the [example directory](example). You can see what it looks like with metric monitoring enabled in the [monitored-example directory](monitored-example). - -1. Install the required OpenTelemetry metrics libraries -2. Initialize a meter and collect metrics -3. Initialize and register a metrics exporter - -#### Install the required OpenTelemetry metrics libraries - -([link to JavaScript version](../README.md#install-the-required-opentelemetry-sdk-metrics-base-libraries)) - -To create metrics on NodeJS, you will need `@opentelemetry/sdk-metrics-base`. - -```sh -npm install @opentelemetry/sdk-metrics-base -``` - -#### Initialize a meter and collect metrics - -([link to JavaScript version](../README.md#initialize-a-meter-and-collect-metrics)) - -In order to create and monitor metrics, we will need a `Meter`. In OpenTelemetry, a `Meter` is the mechanism used to create and manage metrics, labels, and metric exporters. - -Create a file named `monitoring.ts` and add the following code: - -```typescript -import { MeterProvider } from '@opentelemetry/sdk-metrics-base'; - -const meter = new MeterProvider().getMeter('your-meter-name'); -``` - -Now, you can require this file from your application code and use the `Meter` to create and manage metrics. The simplest of these metrics is a counter. Let's create and export from our `monitoring.ts` file a middleware function that express can use to count all requests by route. Modify the `monitoring.ts` file so that it looks like this: - -```typescript -import { MeterProvider } from '@opentelemetry/sdk-metrics-base'; -import { Request, Response, NextFunction } from 'express'; - -const meter = new MeterProvider().getMeter('your-meter-name'); - -const requestCount = meter.createCounter('requests', { - description: 'Count all incoming requests', -}); - -const handles = new Map(); - -export const countAllRequests = () => { - return (req: Request, _res: Response, next: NextFunction) => { - if (!handles.has(req.path)) { - const labels = { route: req.path }; - const handle = requestCount.bind(labels); - handles.set(req.path, handle); - } - - handles.get(req.path).add(1); - next(); - }; -}; -``` - -Now let's import and use this middleware in our application code: - -```typescript -import { countAllRequests } from './monitoring'; -const app = express(); -app.use(countAllRequests()); -``` - -Now, when we make requests to our service our meter will count all requests. - -**Note**: Creating a new `labelSet` and `handle` on every request is not ideal as creating the `labelSet` can often be an expensive operation. This is why handles are created and stored in a `Map` according to the route key. - -#### Initialize and register a metrics exporter - -([link to JavaScript version](../README.md#initialize-and-register-a-metrics-exporter)) - -Counting metrics is only useful if we can export them somewhere that we can see them. For this, we're going to use prometheus. Creating and registering a metrics exporter is much like the tracing exporter above. First we will need to install the prometheus exporter. - -```sh -npm install @opentelemetry/exporter-prometheus -``` - -Next, modify your `monitoring.ts` file to look like this: - -```typescript -import { Request, Response, NextFunction } from 'express'; -import { MeterProvider } from '@opentelemetry/sdk-metrics-base'; -import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; - -const prometheusPort = PrometheusExporter.DEFAULT_OPTIONS.port; -const prometheusEndpoint = PrometheusExporter.DEFAULT_OPTIONS.endpoint; - -const exporter = new PrometheusExporter( - { - startServer: true, - }, - () => { - console.log( - `prometheus scrape endpoint: http://localhost:${prometheusPort}${prometheusEndpoint}`, - ); - }, -); - -const meter = new MeterProvider({ - exporter, - interval: 1000, -}).getMeter('your-meter-name'); - -const requestCount = meter.createCounter('requests', { - description: 'Count all incoming requests', -}); - -const handles = new Map(); - -export const countAllRequests = () => { - return (req: Request, _res: Response, next: NextFunction) => { - if (!handles.has(req.path)) { - const labels = { route: req.path }; - const handle = requestCount.bind(labels); - handles.set(req.path, handle); - } - - handles.get(req.path).add(1); - next(); - }; -}; -``` - -Ensure prometheus is running by running the `prometheus` binary from earlier and start your application. - -```sh -$ ts-node app.ts -prometheus scrape endpoint: http://localhost:9464/metrics -Listening for requests on http://localhost:8080 -``` - -Now, each time you browse to you should see "Hello from the backend" in your browser and your metrics in prometheus should update. You can verify the current metrics by browsing to , which should look like this: - -```sh -# HELP requests Count all incoming requests -# TYPE requests counter -requests{route="/"} 1 -requests{route="/middle-tier"} 2 -requests{route="/backend"} 4 -``` - -You should also be able to see gathered metrics in your prometheus web UI. - -

diff --git a/getting-started/ts-example/example/app.ts b/getting-started/ts-example/example/app.ts deleted file mode 100644 index a252668ebe..0000000000 --- a/getting-started/ts-example/example/app.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as express from 'express'; -import axios from 'axios'; - -const PORT: string = process.env.PORT || '8080'; - -const app = express(); - -app.get('/', (req, res) => { - axios - .get(`http://localhost:${PORT}/middle-tier`) - .then(() => axios.get(`http://localhost:${PORT}/middle-tier`)) - .then((response) => { - res.send(response.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/middle-tier', (req, res) => { - axios - .get(`http://localhost:${PORT}/backend`) - .then(() => axios.get(`http://localhost:${PORT}/backend`)) - .then((response) => { - res.send(response.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/backend', (req, res) => { - res.send('Hello from the backend'); -}); - -app.listen(parseInt(PORT, 10), () => { - console.log(`Listening for requests on http://localhost:${PORT}`); -}); diff --git a/getting-started/ts-example/example/package.json b/getting-started/ts-example/example/package.json deleted file mode 100644 index 08ab0b4634..0000000000 --- a/getting-started/ts-example/example/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@opentelemetry/getting-started-ts-example", - "version": "0.25.0", - "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", - "main": "app.ts", - "scripts": { - "start": "ts-node app.ts" - }, - "author": "OpenTelemetry Authors", - "license": "Apache-2.0", - "devDependencies": { - "@types/express": "4.17.13", - "@types/node": "14.17.11", - "ts-node": "10.2.1" - }, - "dependencies": { - "axios": "^0.21.0", - "express": "^4.17.1" - } -} diff --git a/getting-started/ts-example/monitored-example/app.ts b/getting-started/ts-example/monitored-example/app.ts deleted file mode 100644 index 9307eba1ac..0000000000 --- a/getting-started/ts-example/monitored-example/app.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as express from 'express'; -import axios from 'axios'; - -import { countAllRequests } from './monitoring'; - -const PORT: string = process.env.PORT || '8080'; -const app = express(); -app.use(countAllRequests()); - -app.get('/', (req, res) => { - axios - .get(`http://localhost:${PORT}/middle-tier`) - .then(() => axios.get(`http://localhost:${PORT}/middle-tier`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/middle-tier', (req, res) => { - axios - .get(`http://localhost:${PORT}/backend`) - .then(() => axios.get(`http://localhost:${PORT}/backend`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/backend', (req, res) => { - res.send('Hello from the backend'); -}); - -app.listen(parseInt(PORT, 10), () => { - console.log(`Listening for requests on http://localhost:${PORT}`); -}); diff --git a/getting-started/ts-example/monitored-example/monitoring.ts b/getting-started/ts-example/monitored-example/monitoring.ts deleted file mode 100644 index 5e933e4a93..0000000000 --- a/getting-started/ts-example/monitored-example/monitoring.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { MeterProvider } from '@opentelemetry/sdk-metrics-base'; -import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; - -const prometheusPort = PrometheusExporter.DEFAULT_OPTIONS.port; -const prometheusEndpoint = PrometheusExporter.DEFAULT_OPTIONS.endpoint; - -const exporter = new PrometheusExporter( - { - startServer: true, - }, - () => { - console.log( - `prometheus scrape endpoint: http://localhost:${prometheusPort}${prometheusEndpoint}`, - ); - }, -); - -const meter = new MeterProvider({ - exporter, - interval: 1000, -}).getMeter('your-meter-name'); - -const requestCount = meter.createCounter('requests', { - description: 'Count all incoming requests', -}); - -const handles = new Map(); - -export const countAllRequests = () => (req: Request, _res: Response, next: NextFunction): void => { - if (!handles.has(req.path)) { - const labels = { route: req.path }; - const handle = requestCount.bind(labels); - handles.set(req.path, handle); - } - - handles.get(req.path).add(1); - next(); -}; diff --git a/getting-started/ts-example/monitored-example/package.json b/getting-started/ts-example/monitored-example/package.json deleted file mode 100644 index 3e4138059e..0000000000 --- a/getting-started/ts-example/monitored-example/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@opentelemetry/getting-started-monitored-ts-example", - "version": "0.25.0", - "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", - "main": "app.ts", - "scripts": { - "start": "ts-node app.ts" - }, - "author": "OpenTelemetry Authors", - "license": "Apache-2.0", - "devDependencies": { - "@types/express": "4.17.13", - "@types/node": "14.17.11", - "ts-node": "10.2.1" - }, - "dependencies": { - "@opentelemetry/exporter-prometheus": "0.25.0", - "@opentelemetry/sdk-metrics-base": "0.25.0", - "axios": "^0.21.0", - "express": "^4.17.1" - } -} diff --git a/getting-started/ts-example/traced-example/app.ts b/getting-started/ts-example/traced-example/app.ts deleted file mode 100644 index 58fb8ab589..0000000000 --- a/getting-started/ts-example/traced-example/app.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as express from 'express'; -import axios from 'axios'; - -const PORT: string = process.env.PORT || '8080'; - -const app = express(); - -app.get('/', (req, res) => { - axios - .get(`http://localhost:${PORT}/middle-tier`) - .then(() => axios.get(`http://localhost:${PORT}/middle-tier`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/middle-tier', (req, res) => { - axios - .get(`http://localhost:${PORT}/backend`) - .then(() => axios.get(`http://localhost:${PORT}/backend`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/backend', (req, res) => { - res.send('Hello from the backend'); -}); - -app.listen(parseInt(PORT, 10), () => { - console.log(`Listening for requests on http://localhost:${PORT}`); -}); diff --git a/getting-started/ts-example/traced-example/package.json b/getting-started/ts-example/traced-example/package.json deleted file mode 100644 index f157176404..0000000000 --- a/getting-started/ts-example/traced-example/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@opentelemetry/getting-started-traced-ts-example", - "version": "0.25.0", - "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", - "main": "app.ts", - "scripts": { - "start": "ts-node app.ts" - }, - "author": "OpenTelemetry Authors", - "license": "Apache-2.0", - "devDependencies": { - "@types/express": "4.17.13", - "@types/node": "14.17.11", - "ts-node": "10.2.1" - }, - "dependencies": { - "@opentelemetry/core": "1.0.0", - "@opentelemetry/exporter-zipkin": "1.0.0", - "@opentelemetry/instrumentation": "0.25.0", - "@opentelemetry/instrumentation-express": "^0.24.0", - "@opentelemetry/sdk-trace-node": "1.0.0", - "@opentelemetry/instrumentation-http": "0.25.0", - "@opentelemetry/sdk-trace-base": "1.0.0", - "axios": "^0.21.0", - "express": "^4.17.1" - } -} diff --git a/getting-started/ts-example/traced-example/tracing.ts b/getting-started/ts-example/traced-example/tracing.ts deleted file mode 100644 index 8d4628a06e..0000000000 --- a/getting-started/ts-example/traced-example/tracing.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'; -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { Resource } from '@opentelemetry/resources' -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' -import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; -// For Jaeger, use the following line instead: -// import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; - -const provider: NodeTracerProvider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'getting-started', - }), -}); - -diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL); - -provider.addSpanProcessor( - new SimpleSpanProcessor( - new ZipkinExporter({ - // For Jaeger, use the following line instead: - // new JaegerExporter({ - // If you are running your tracing backend on another host, - // you can point to it using the `url` parameter of the - // exporter config. - }), - ), -); -provider.register(); - -registerInstrumentations({ - instrumentations: [ - new ExpressInstrumentation(), - new HttpInstrumentation(), - ], -}); - -console.log('tracing initialized'); \ No newline at end of file diff --git a/package.json b/package.json index 03f45c704f..702e0cde24 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,6 @@ "lint:fix:changed": "lerna run --concurrency 1 --stream lint:fix --since HEAD --exclude-dependents", "lint:examples": "eslint --no-error-on-unmatched-pattern ./examples/**/*.js", "lint:examples:fix": "eslint --no-error-on-unmatched-pattern ./examples/**/*.js --fix", - "lint:getting-started": "eslint --no-error-on-unmatched-pattern ./getting-started/**/*.{js,ts}", - "lint:getting-started:fix": "eslint --no-error-on-unmatched-pattern ./getting-started/**/*.{js,ts} --fix", "lint:markdown": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md", "lint:markdown:fix": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md --fix", "reset": "lerna clean -y && rm -rf node_modules && npm i && npm run compile && npm run lint:fix", From c160ad8b2a6f88c25c3abbe449c998987964b999 Mon Sep 17 00:00:00 2001 From: Ivan Santos Date: Sun, 17 Oct 2021 11:25:28 -0500 Subject: [PATCH 06/15] chore: expand pull request template with action items (#2509) Co-authored-by: Rauno Viskus Co-authored-by: Bartlomiej Obecny Co-authored-by: Valentin Marchaud --- .github/PULL_REQUEST_TEMPLATE.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1d576cc70d..c15dc709ef 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,8 +15,29 @@ Before creating a pull request, please make sure: ## Which problem is this PR solving? -- +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) ## Short description of the changes -- +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A + +# Checklist: + +- [ ] Followed the style guidelines of this project +- [ ] Unit tests have been added +- [ ] Documentation has been updated From 3045eba229f031e1510785dbb9713f24daf17ed1 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 20 Oct 2021 20:56:36 +0800 Subject: [PATCH 07/15] chore: slim font size for section title in PR template (#2541) --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c15dc709ef..554b4ebfee 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -30,13 +30,13 @@ Please delete options that are not relevant. - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update -# How Has This Been Tested? +## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - [ ] Test A -# Checklist: +## Checklist: - [ ] Followed the style guidelines of this project - [ ] Unit tests have been added From 9df2132398955eb25fc74ea9ae467da990d3204f Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 22 Oct 2021 02:05:10 +0800 Subject: [PATCH 08/15] fix(sdk-metrics-base): remove metric kind BATCH_OBSERVER (#2540) BATCH_OBSERVER is no longer a metric. --- .../packages/opentelemetry-sdk-metrics-base/src/export/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts index 33206ea936..61b22f4513 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts @@ -31,7 +31,6 @@ export enum MetricKind { OBSERVABLE_COUNTER, OBSERVABLE_UP_DOWN_COUNTER, OBSERVABLE_GAUGE, - BATCH_OBSERVER, } export const MetricKindValues = Object.values(MetricKind); From 6a8c2f2094028cecef0d785567599a8578df0cd4 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 22 Oct 2021 02:09:19 +0800 Subject: [PATCH 09/15] fix: prefer globalThis instead of window to support webworkers (#2465) * fix: prefer globalThis instead of window to support webworkers There is no `window` in either WebWorkers and ServiceWorkers. * Update packages/opentelemetry-core/test/platform/browser/environment.test.ts * fixup! add globalThis helpers --- .../src/platform/browser/globalThis.ts | 18 +++++++++- .../src/platform/browser/environment.ts | 4 +-- .../src/platform/browser/globalThis.ts | 35 +++++++++++++++++++ .../src/platform/browser/index.ts | 1 + .../src/platform/node/globalThis.ts | 19 ++++++++++ .../src/platform/node/index.ts | 1 + .../test/platform/browser/environment.test.ts | 25 +++++++++++++ 7 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 packages/opentelemetry-core/src/platform/browser/globalThis.ts create mode 100644 packages/opentelemetry-core/src/platform/node/globalThis.ts create mode 100644 packages/opentelemetry-core/test/platform/browser/environment.test.ts diff --git a/experimental/packages/opentelemetry-api-metrics/src/platform/browser/globalThis.ts b/experimental/packages/opentelemetry-api-metrics/src/platform/browser/globalThis.ts index 34a8254b88..693547b7c2 100644 --- a/experimental/packages/opentelemetry-api-metrics/src/platform/browser/globalThis.ts +++ b/experimental/packages/opentelemetry-api-metrics/src/platform/browser/globalThis.ts @@ -14,6 +14,22 @@ * limitations under the License. */ +// Updates to this file should also be replicated to @opentelemetry/api and +// @opentelemetry/core too. + +/** + * - globalThis (New standard) + * - self (Will return the current window instance for supported browsers) + * - window (fallback for older browser implementations) + * - global (NodeJS implementation) + * - (When all else fails) + */ + /** only globals that common to node and browsers are allowed */ // eslint-disable-next-line node/no-unsupported-features/es-builtins, no-undef -export const _globalThis = typeof globalThis === 'object' ? globalThis : window; +export const _globalThis: typeof globalThis = + typeof globalThis === 'object' ? globalThis : + typeof self === 'object' ? self : + typeof window === 'object' ? window : + typeof global === 'object' ? global : + {} as typeof globalThis; diff --git a/packages/opentelemetry-core/src/platform/browser/environment.ts b/packages/opentelemetry-core/src/platform/browser/environment.ts index ea9514e4ac..c752ae8a30 100644 --- a/packages/opentelemetry-core/src/platform/browser/environment.ts +++ b/packages/opentelemetry-core/src/platform/browser/environment.ts @@ -20,12 +20,12 @@ import { RAW_ENVIRONMENT, parseEnvironment, } from '../../utils/environment'; +import { _globalThis } from './globalThis'; /** * Gets the environment variables */ export function getEnv(): Required { - const _window = window as typeof window & RAW_ENVIRONMENT; - const globalEnv = parseEnvironment(_window); + const globalEnv = parseEnvironment(_globalThis as typeof globalThis & RAW_ENVIRONMENT); return Object.assign({}, DEFAULT_ENVIRONMENT, globalEnv); } diff --git a/packages/opentelemetry-core/src/platform/browser/globalThis.ts b/packages/opentelemetry-core/src/platform/browser/globalThis.ts new file mode 100644 index 0000000000..53dd1c3d02 --- /dev/null +++ b/packages/opentelemetry-core/src/platform/browser/globalThis.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// Updates to this file should also be replicated to @opentelemetry/api and +// @opentelemetry/api-metrics too. + +/** + * - globalThis (New standard) + * - self (Will return the current window instance for supported browsers) + * - window (fallback for older browser implementations) + * - global (NodeJS implementation) + * - (When all else fails) + */ + +/** only globals that common to node and browsers are allowed */ +// eslint-disable-next-line node/no-unsupported-features/es-builtins, no-undef +export const _globalThis: typeof globalThis = + typeof globalThis === 'object' ? globalThis : + typeof self === 'object' ? self : + typeof window === 'object' ? window : + typeof global === 'object' ? global : + {} as typeof globalThis; diff --git a/packages/opentelemetry-core/src/platform/browser/index.ts b/packages/opentelemetry-core/src/platform/browser/index.ts index c18c66118d..e2860192ef 100644 --- a/packages/opentelemetry-core/src/platform/browser/index.ts +++ b/packages/opentelemetry-core/src/platform/browser/index.ts @@ -15,6 +15,7 @@ */ export * from './environment'; +export * from './globalThis'; export * from './hex-to-base64'; export * from './RandomIdGenerator'; export * from './performance'; diff --git a/packages/opentelemetry-core/src/platform/node/globalThis.ts b/packages/opentelemetry-core/src/platform/node/globalThis.ts new file mode 100644 index 0000000000..36e97e2732 --- /dev/null +++ b/packages/opentelemetry-core/src/platform/node/globalThis.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +/** only globals that common to node and browsers are allowed */ +// eslint-disable-next-line node/no-unsupported-features/es-builtins +export const _globalThis = typeof globalThis === 'object' ? globalThis : global; diff --git a/packages/opentelemetry-core/src/platform/node/index.ts b/packages/opentelemetry-core/src/platform/node/index.ts index c18c66118d..e2860192ef 100644 --- a/packages/opentelemetry-core/src/platform/node/index.ts +++ b/packages/opentelemetry-core/src/platform/node/index.ts @@ -15,6 +15,7 @@ */ export * from './environment'; +export * from './globalThis'; export * from './hex-to-base64'; export * from './RandomIdGenerator'; export * from './performance'; diff --git a/packages/opentelemetry-core/test/platform/browser/environment.test.ts b/packages/opentelemetry-core/test/platform/browser/environment.test.ts new file mode 100644 index 0000000000..0117be82b0 --- /dev/null +++ b/packages/opentelemetry-core/test/platform/browser/environment.test.ts @@ -0,0 +1,25 @@ +/* + * 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 { getEnv } from '../../../src/platform/browser/environment'; + +describe('getEnv', () => { + it('get environments variables in a browser', () => { + const env = getEnv(); + assert.strictEqual(typeof env, 'object'); + }); +}); From b531acffbf9c4c8f5fcddb699efb736053d6485e Mon Sep 17 00:00:00 2001 From: moander Date: Fri, 22 Oct 2021 21:21:07 +0200 Subject: [PATCH 10/15] docs: expose existing comments (#2555) --- .../src/xhr.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts b/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts index 09d8a0c588..d2d126be8e 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts @@ -57,13 +57,15 @@ export type XHRCustomAttributeFunction = ( */ export interface XMLHttpRequestInstrumentationConfig extends InstrumentationConfig { - // the number of timing resources is limited, after the limit - // (chrome 250, safari 150) the information is not collected anymore - // the only way to prevent that is to regularly clean the resources - // whenever it is possible, this is needed only when PerformanceObserver - // is not available + /** + * The number of timing resources is limited, after the limit + * (chrome 250, safari 150) the information is not collected anymore. + * The only way to prevent that is to regularly clean the resources + * whenever it is possible. This is needed only when PerformanceObserver + * is not available + */ clearTimingResources?: boolean; - // urls which should include trace headers when origin doesn't match + /** URLs which should include trace headers when origin doesn't match */ propagateTraceHeaderCorsUrls?: PropagateTraceHeaderCorsUrls; /** * URLs that partially match any regex in ignoreUrls will not be traced. From c1939a79a9773f6155eb04bc4d0aa261b057a934 Mon Sep 17 00:00:00 2001 From: Jack <57678801+mothershipper@users.noreply.github.com> Date: Tue, 26 Oct 2021 12:35:57 -0700 Subject: [PATCH 11/15] fix(@opentelemetry/exporter-prometheus): unref prometheus server to prevent process running indefinitely (#2558) --- .../src/PrometheusExporter.ts | 3 ++- .../test/PrometheusExporter.test.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts index 35c1de5bd9..3028f22723 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusExporter.ts @@ -68,7 +68,8 @@ export class PrometheusExporter implements MetricExporter { typeof config.appendTimestamp === 'boolean' ? config.appendTimestamp : PrometheusExporter.DEFAULT_OPTIONS.appendTimestamp; - this._server = createServer(this._requestHandler); + // unref to prevent prometheus exporter from holding the process open on exit + this._server = createServer(this._requestHandler).unref(); this._serializer = new PrometheusSerializer( this._prefix, this._appendTimestamp diff --git a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts index 3b5f044be3..9860d82eed 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusExporter.test.ts @@ -36,6 +36,7 @@ describe('PrometheusExporter', () => { mockAggregator(HistogramAggregator); afterEach(() => { + sinon.restore(); delete process.env.OTEL_EXPORTER_PROMETHEUS_HOST; delete process.env.OTEL_EXPORTER_PROMETHEUS_PORT; }); @@ -116,6 +117,16 @@ describe('PrometheusExporter', () => { ); }); + it('should unref the server to allow graceful termination', () => { + const mockServer = sinon.createStubInstance(http.Server); + const createStub = sinon.stub(http, 'createServer'); + createStub.returns((mockServer as any) as http.Server); + const exporter = new PrometheusExporter({}, async () => { + await exporter.shutdown(); + }); + sinon.assert.calledOnce(mockServer.unref); + }); + it('should listen on environmentally set host and port', () => { process.env.OTEL_EXPORTER_PROMETHEUS_HOST = '127.0.0.1'; process.env.OTEL_EXPORTER_PROMETHEUS_PORT = '1234'; From c0ab95266a68853156de804b1f2895755df4e3f7 Mon Sep 17 00:00:00 2001 From: moander Date: Wed, 27 Oct 2021 09:44:15 +0200 Subject: [PATCH 12/15] fix(core): support regex global flag in urlMatches (#2560) Co-authored-by: Daniel Dyla --- packages/opentelemetry-core/src/utils/url.ts | 2 +- packages/opentelemetry-core/test/utils/url.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-core/src/utils/url.ts b/packages/opentelemetry-core/src/utils/url.ts index 9db9725b82..a6122ae784 100644 --- a/packages/opentelemetry-core/src/utils/url.ts +++ b/packages/opentelemetry-core/src/utils/url.ts @@ -17,7 +17,7 @@ export function urlMatches(url: string, urlToMatch: string | RegExp): boolean { if (typeof urlToMatch === 'string') { return url === urlToMatch; } else { - return urlToMatch.test(url); + return !!url.match(urlToMatch); } } /** diff --git a/packages/opentelemetry-core/test/utils/url.test.ts b/packages/opentelemetry-core/test/utils/url.test.ts index 91ae9b75c5..8855cff23c 100644 --- a/packages/opentelemetry-core/test/utils/url.test.ts +++ b/packages/opentelemetry-core/test/utils/url.test.ts @@ -71,5 +71,18 @@ describe('Core - Utils - url', () => { ); }); }); + describe('when regex has global flag', () => { + it('should return true', () => { + const ignoredUrls = [/myaddr/g]; + // Run test multiple times to ensure same result (git.io/JimS1) + for (let i = 0; i < 3; i++) { + assert.strictEqual( + isUrlIgnored(urlToTest, ignoredUrls), + true, + urlIgnored + ); + } + }); + }); }); }); From 5de52ee31953c8776ea030d16dd74bf2df868b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerhard=20St=C3=B6bich?= Date: Wed, 27 Oct 2021 09:50:51 +0200 Subject: [PATCH 13/15] chore: cleanup setting config in instrumentations (#2522) Co-authored-by: Valentin Marchaud Co-authored-by: Daniel Dyla --- .../src/fetch.ts | 4 +-- .../src/grpc-js/clientUtils.ts | 2 +- .../src/grpc-js/index.ts | 23 +++++++---------- .../src/grpc/index.ts | 19 ++++++-------- .../src/grpc/serverUtils.ts | 2 +- .../src/instrumentation.ts | 25 ++++++++----------- .../src/http.ts | 9 +++---- .../src/xhr.ts | 6 ++--- 8 files changed, 38 insertions(+), 52 deletions(-) diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts b/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts index a564b76f5a..1f762f429b 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts @@ -76,11 +76,11 @@ export class FetchInstrumentation extends InstrumentationBase< private _usedResources = new WeakSet(); private _tasksCount = 0; - constructor(config: FetchInstrumentationConfig = {}) { + constructor(config?: FetchInstrumentationConfig) { super( '@opentelemetry/instrumentation-fetch', VERSION, - Object.assign({}, config) + config ); } diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/clientUtils.ts b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/clientUtils.ts index b2810cd104..5671d3a815 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/clientUtils.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/clientUtils.ts @@ -48,7 +48,7 @@ export function getMethodsToWrap( // For a method defined in .proto as "UnaryMethod" Object.entries(methods).forEach(([name, { originalName }]) => { - if (!_methodIsIgnored(name, this._config.ignoreGrpcMethods)) { + if (!_methodIsIgnored(name, this.getConfig().ignoreGrpcMethods)) { methodList.push(name); // adds camel case method name: "unaryMethod" if ( originalName && diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/index.ts b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/index.ts index ba4f46e351..ce9777c68b 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/index.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc-js/index.ts @@ -19,10 +19,7 @@ import { InstrumentationNodeModuleDefinition, isWrapped, } from '@opentelemetry/instrumentation'; -import { - InstrumentationBase, - InstrumentationConfig, -} from '@opentelemetry/instrumentation'; +import { InstrumentationBase } from '@opentelemetry/instrumentation'; import { GrpcInstrumentationConfig } from '../types'; import { ServerCallWithMeta, @@ -56,17 +53,11 @@ import { AttributeNames } from '../enums/AttributeNames'; export class GrpcJsInstrumentation extends InstrumentationBase { constructor( - protected override _config: GrpcInstrumentationConfig & InstrumentationConfig = {}, name: string, - version: string + version: string, + config?: GrpcInstrumentationConfig, ) { - super(name, version, _config); - } - - public override setConfig( - config: GrpcInstrumentationConfig & InstrumentationConfig = {} - ) { - this._config = Object.assign({}, config); + super(name, version, config); } init() { @@ -125,6 +116,10 @@ export class GrpcJsInstrumentation extends InstrumentationBase { ]; } + override getConfig(): GrpcInstrumentationConfig { + return super.getConfig(); + } + /** * Patch for grpc.Server.prototype.register(...) function. Provides auto-instrumentation for * client_stream, server_stream, bidi, unary server handler calls. @@ -134,7 +129,7 @@ export class GrpcJsInstrumentation extends InstrumentationBase { ) => ServerRegisterFunction { const instrumentation = this; return (originalRegister: ServerRegisterFunction) => { - const config = this._config; + const config = this.getConfig(); instrumentation._diag.debug('patched gRPC server'); return function register( this: grpcJs.Server, diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/index.ts b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/index.ts index 18b649b95c..bf488028f0 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/index.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/index.ts @@ -19,7 +19,6 @@ import { InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, InstrumentationBase, - InstrumentationConfig, isWrapped, } from '@opentelemetry/instrumentation'; import { @@ -55,17 +54,11 @@ export class GrpcNativeInstrumentation extends InstrumentationBase< typeof grpcTypes > { constructor( - protected override _config: GrpcInstrumentationConfig & InstrumentationConfig = {}, name: string, - version: string + version: string, + config?: GrpcInstrumentationConfig ) { - super(name, version, _config); - } - - public override setConfig( - config: GrpcInstrumentationConfig & InstrumentationConfig = {} - ) { - this._config = Object.assign({}, config); + super(name, version, config); } init() { @@ -107,6 +100,10 @@ export class GrpcNativeInstrumentation extends InstrumentationBase< ]; } + override getConfig(): GrpcInstrumentationConfig { + return super.getConfig(); + } + private _getInternalPatchs() { const onPatch = ( moduleExports: GrpcInternalClientTypes, @@ -268,7 +265,7 @@ export class GrpcNativeInstrumentation extends InstrumentationBase< // For a method defined in .proto as "UnaryMethod" Object.entries(methods).forEach(([name, { originalName }]) => { - if (!_methodIsIgnored(name, this._config.ignoreGrpcMethods)) { + if (!_methodIsIgnored(name, this.getConfig().ignoreGrpcMethods)) { methodList.push(name); // adds camel case method name: "unaryMethod" if ( originalName && diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/serverUtils.ts b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/serverUtils.ts index 5d88eeabcc..32f26ebd38 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/serverUtils.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/src/grpc/serverUtils.ts @@ -131,6 +131,6 @@ export const shouldNotTraceServerCall = function ( const parsedName = name.split('/'); return _methodIsIgnored( parsedName[parsedName.length - 1] || name, - this._config.ignoreGrpcMethods + this.getConfig().ignoreGrpcMethods ); }; diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/src/instrumentation.ts b/experimental/packages/opentelemetry-instrumentation-grpc/src/instrumentation.ts index 0245f367d2..5ac237038a 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/src/instrumentation.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/src/instrumentation.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { GrpcInstrumentationConfig } from './types'; import { VERSION } from './version'; import { GrpcNativeInstrumentation } from './grpc'; @@ -34,26 +33,23 @@ export class GrpcInstrumentation { public readonly instrumentationVersion: string = VERSION; constructor( - protected _config: GrpcInstrumentationConfig & InstrumentationConfig = {} + config?: GrpcInstrumentationConfig ) { this._grpcJsInstrumentation = new GrpcJsInstrumentation( - _config, this.instrumentationName, - this.instrumentationVersion + this.instrumentationVersion, + config ); this._grpcNativeInstrumentation = new GrpcNativeInstrumentation( - _config, this.instrumentationName, - this.instrumentationVersion + this.instrumentationVersion, + config ); } - public setConfig( - config: GrpcInstrumentationConfig & InstrumentationConfig = {} - ) { - this._config = Object.assign({}, config); - this._grpcJsInstrumentation.setConfig(this._config); - this._grpcNativeInstrumentation.setConfig(this._config); + public setConfig(config?: GrpcInstrumentationConfig) { + this._grpcJsInstrumentation.setConfig(config); + this._grpcNativeInstrumentation.setConfig(config); } /** @@ -61,8 +57,9 @@ export class GrpcInstrumentation { * Public reference to the protected BaseInstrumentation `_config` instance to be used by this * plugin's external helper functions */ - public getConfig() { - return this._config; + public getConfig(): GrpcInstrumentationConfig { + // grpcNative and grpcJs have their own config copy which should be identical so just pick one + return this._grpcJsInstrumentation.getConfig(); } init() { diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/http.ts b/experimental/packages/opentelemetry-instrumentation-http/src/http.ts index 6c889b4066..e183d5e5e6 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/src/http.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/src/http.ts @@ -44,7 +44,6 @@ import * as utils from './utils'; import { VERSION } from './version'; import { InstrumentationBase, - InstrumentationConfig, InstrumentationNodeModuleDefinition, isWrapped, safeExecuteInTheMiddle, @@ -60,11 +59,11 @@ export class HttpInstrumentation extends InstrumentationBase { private readonly _version = process.versions.node; private _headerCapture; - constructor(config: HttpInstrumentationConfig & InstrumentationConfig = {}) { + constructor(config?: HttpInstrumentationConfig) { super( '@opentelemetry/instrumentation-http', VERSION, - Object.assign({}, config) + config ); this._headerCapture = this._createHeaderCapture(); @@ -74,8 +73,8 @@ export class HttpInstrumentation extends InstrumentationBase { return this._config; } - override setConfig(config: HttpInstrumentationConfig & InstrumentationConfig = {}): void { - this._config = Object.assign({}, config); + override setConfig(config?: HttpInstrumentationConfig): void { + super.setConfig(config); this._headerCapture = this._createHeaderCapture(); } diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts b/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts index d2d126be8e..1d0c9faac1 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts @@ -89,13 +89,11 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase(); private _usedResources = new WeakSet(); - constructor( - config: XMLHttpRequestInstrumentationConfig & InstrumentationConfig = {} - ) { + constructor(config?: XMLHttpRequestInstrumentationConfig) { super( '@opentelemetry/instrumentation-xml-http-request', VERSION, - Object.assign({}, config) + config ); } From fa0cb7270e0c39f048f8ed7cfee05f4dea1108ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerhard=20St=C3=B6bich?= Date: Wed, 27 Oct 2021 15:02:40 +0200 Subject: [PATCH 14/15] chore(deps): pin minor API version (#2531) Co-authored-by: Daniel Dyla --- .github/workflows/peer-api.yaml | 3 +++ experimental/packages/opentelemetry-api-metrics/package.json | 4 ---- .../packages/opentelemetry-exporter-otlp-grpc/package.json | 4 ++-- .../packages/opentelemetry-exporter-otlp-http/package.json | 4 ++-- .../packages/opentelemetry-exporter-otlp-proto/package.json | 4 ++-- .../packages/opentelemetry-exporter-prometheus/package.json | 4 ++-- .../packages/opentelemetry-instrumentation-fetch/package.json | 4 ++-- .../packages/opentelemetry-instrumentation-grpc/package.json | 4 ++-- .../packages/opentelemetry-instrumentation-http/package.json | 4 ++-- .../package.json | 4 ++-- .../packages/opentelemetry-instrumentation/package.json | 4 ++-- .../packages/opentelemetry-sdk-metrics-base/package.json | 4 ++-- experimental/packages/opentelemetry-sdk-node/package.json | 4 ++-- integration-tests/propagation-validation-server/package.json | 2 +- package.json | 1 + packages/opentelemetry-context-async-hooks/package.json | 4 ++-- packages/opentelemetry-context-zone-peer-dep/package.json | 4 ++-- packages/opentelemetry-core/package.json | 4 ++-- packages/opentelemetry-exporter-jaeger/package.json | 4 ++-- packages/opentelemetry-exporter-zipkin/package.json | 4 ++-- packages/opentelemetry-propagator-b3/package.json | 4 ++-- packages/opentelemetry-propagator-jaeger/package.json | 4 ++-- packages/opentelemetry-resources/package.json | 4 ++-- packages/opentelemetry-sdk-trace-base/package.json | 4 ++-- packages/opentelemetry-sdk-trace-node/package.json | 4 ++-- packages/opentelemetry-sdk-trace-web/package.json | 4 ++-- packages/opentelemetry-shim-opentracing/package.json | 4 ++-- scripts/peer-api-check.js | 3 ++- 28 files changed, 53 insertions(+), 52 deletions(-) diff --git a/.github/workflows/peer-api.yaml b/.github/workflows/peer-api.yaml index 2917235bc3..ce642134d0 100644 --- a/.github/workflows/peer-api.yaml +++ b/.github/workflows/peer-api.yaml @@ -18,6 +18,9 @@ jobs: - name: Install lerna run: npm install -g lerna + - name: Install semver + run: npm install semver + - name: Check API dependency semantics (stable) run: lerna exec --ignore propagation-validation-server "node ../../scripts/peer-api-check.js" diff --git a/experimental/packages/opentelemetry-api-metrics/package.json b/experimental/packages/opentelemetry-api-metrics/package.json index 5cb9e1503b..ecc624c4b9 100644 --- a/experimental/packages/opentelemetry-api-metrics/package.json +++ b/experimental/packages/opentelemetry-api-metrics/package.json @@ -54,11 +54,7 @@ "publishConfig": { "access": "public" }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.2" - }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/webpack-env": "1.16.2", diff --git a/experimental/packages/opentelemetry-exporter-otlp-grpc/package.json b/experimental/packages/opentelemetry-exporter-otlp-grpc/package.json index 0e169d818b..8c7729b255 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-grpc/package.json +++ b/experimental/packages/opentelemetry-exporter-otlp-grpc/package.json @@ -48,7 +48,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/api-metrics": "0.26.0", "@types/mocha": "8.2.3", "@types/node": "14.17.11", @@ -64,7 +64,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@grpc/grpc-js": "^1.3.7", diff --git a/experimental/packages/opentelemetry-exporter-otlp-http/package.json b/experimental/packages/opentelemetry-exporter-otlp-http/package.json index 91399c51a8..ab16f94c4c 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-http/package.json +++ b/experimental/packages/opentelemetry-exporter-otlp-http/package.json @@ -56,7 +56,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -83,7 +83,7 @@ "webpack-merge": "5.8.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/api-metrics": "0.26.0", diff --git a/experimental/packages/opentelemetry-exporter-otlp-proto/package.json b/experimental/packages/opentelemetry-exporter-otlp-proto/package.json index a8f7ff5afa..80eba49318 100644 --- a/experimental/packages/opentelemetry-exporter-otlp-proto/package.json +++ b/experimental/packages/opentelemetry-exporter-otlp-proto/package.json @@ -48,7 +48,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/api-metrics": "0.26.0", "@types/mocha": "8.2.3", "@types/node": "14.17.11", @@ -64,7 +64,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@grpc/proto-loader": "^0.6.4", diff --git a/experimental/packages/opentelemetry-exporter-prometheus/package.json b/experimental/packages/opentelemetry-exporter-prometheus/package.json index a8af029d50..777666c3db 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/package.json +++ b/experimental/packages/opentelemetry-exporter-prometheus/package.json @@ -41,7 +41,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -54,7 +54,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/api-metrics": "0.26.0", diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/package.json b/experimental/packages/opentelemetry-instrumentation-fetch/package.json index 40b652afb5..f7c060a8de 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/package.json +++ b/experimental/packages/opentelemetry-instrumentation-fetch/package.json @@ -49,7 +49,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/context-zone": "1.0.0", "@opentelemetry/propagator-b3": "1.0.0", "@opentelemetry/sdk-trace-base": "1.0.0", @@ -78,7 +78,7 @@ "webpack-merge": "5.8.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/package.json b/experimental/packages/opentelemetry-instrumentation-grpc/package.json index 158588b7fb..282c77b035 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/package.json +++ b/experimental/packages/opentelemetry-instrumentation-grpc/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@grpc/grpc-js": "1.3.7", "@grpc/proto-loader": "0.6.4", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/context-async-hooks": "1.0.0", "@opentelemetry/core": "1.0.0", "@opentelemetry/sdk-trace-base": "1.0.0", @@ -66,7 +66,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/api-metrics": "0.26.0", diff --git a/experimental/packages/opentelemetry-instrumentation-http/package.json b/experimental/packages/opentelemetry-instrumentation-http/package.json index 41abcbab84..3bfda8c880 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/package.json +++ b/experimental/packages/opentelemetry-instrumentation-http/package.json @@ -43,7 +43,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/context-async-hooks": "1.0.0", "@opentelemetry/sdk-trace-base": "1.0.0", "@opentelemetry/sdk-trace-node": "1.0.0", @@ -69,7 +69,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json index 66d3c85f33..2145f91b33 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json @@ -49,7 +49,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/context-zone": "1.0.0", "@opentelemetry/propagator-b3": "1.0.0", "@opentelemetry/sdk-trace-base": "1.0.0", @@ -78,7 +78,7 @@ "webpack-merge": "5.8.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/experimental/packages/opentelemetry-instrumentation/package.json b/experimental/packages/opentelemetry-instrumentation/package.json index 7204d0e391..194e7d041b 100644 --- a/experimental/packages/opentelemetry-instrumentation/package.json +++ b/experimental/packages/opentelemetry-instrumentation/package.json @@ -67,11 +67,11 @@ "shimmer": "^1.2.1" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/semver": "7.3.8", diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/package.json b/experimental/packages/opentelemetry-sdk-metrics-base/package.json index 862e167d1d..a6c490d1e6 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/package.json +++ b/experimental/packages/opentelemetry-sdk-metrics-base/package.json @@ -46,7 +46,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@types/lodash.merge": "4.6.6", "@types/mocha": "8.2.3", "@types/node": "14.17.11", @@ -60,7 +60,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/api-metrics": "0.26.0", diff --git a/experimental/packages/opentelemetry-sdk-node/package.json b/experimental/packages/opentelemetry-sdk-node/package.json index dddf6d0845..28a9e746c1 100644 --- a/experimental/packages/opentelemetry-sdk-node/package.json +++ b/experimental/packages/opentelemetry-sdk-node/package.json @@ -53,10 +53,10 @@ "@opentelemetry/sdk-trace-node": "1.0.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@opentelemetry/context-async-hooks": "1.0.0", "@types/mocha": "8.2.3", "@types/node": "14.17.11", diff --git a/integration-tests/propagation-validation-server/package.json b/integration-tests/propagation-validation-server/package.json index 7c11eb10b1..d36492b47f 100644 --- a/integration-tests/propagation-validation-server/package.json +++ b/integration-tests/propagation-validation-server/package.json @@ -11,7 +11,7 @@ "compile": "tsc --build" }, "dependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/context-async-hooks": "1.0.0", "@opentelemetry/core": "1.0.0", "@opentelemetry/sdk-trace-base": "1.0.0", diff --git a/package.json b/package.json index 702e0cde24..eba827332a 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "lerna": "3.22.1", "lerna-changelog": "1.0.1", "markdownlint-cli": "0.28.1", + "semver": "7.3.5", "typedoc": "0.21.6", "typescript": "4.3.5", "update-ts-references": "2.4.0" diff --git a/packages/opentelemetry-context-async-hooks/package.json b/packages/opentelemetry-context-async-hooks/package.json index 67b4751d95..470e1d209c 100644 --- a/packages/opentelemetry-context-async-hooks/package.json +++ b/packages/opentelemetry-context-async-hooks/package.json @@ -42,7 +42,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "codecov": "3.8.3", @@ -53,6 +53,6 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" } } diff --git a/packages/opentelemetry-context-zone-peer-dep/package.json b/packages/opentelemetry-context-zone-peer-dep/package.json index 94c21e2a08..aefebb2602 100644 --- a/packages/opentelemetry-context-zone-peer-dep/package.json +++ b/packages/opentelemetry-context-zone-peer-dep/package.json @@ -48,7 +48,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -75,7 +75,7 @@ "zone.js": "0.11.4" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": ">=1.0.0 <1.1.0", "zone.js": "^0.10.2 || ^0.11.0" }, "sideEffects": false diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index 573ea927be..fd33b2ad21 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -57,7 +57,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -81,7 +81,7 @@ "webpack": "4.46.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/semantic-conventions": "1.0.0" diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index e2988c1869..c3ec50f609 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -42,7 +42,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@opentelemetry/resources": "1.0.0", "@types/mocha": "8.2.3", "@types/node": "14.17.11", @@ -57,7 +57,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json index 653dc8a321..c14b9d9ae3 100644 --- a/packages/opentelemetry-exporter-zipkin/package.json +++ b/packages/opentelemetry-exporter-zipkin/package.json @@ -54,7 +54,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -81,7 +81,7 @@ "webpack-merge": "5.8.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/packages/opentelemetry-propagator-b3/package.json b/packages/opentelemetry-propagator-b3/package.json index 5015c0a74b..c7e1e42fe9 100644 --- a/packages/opentelemetry-propagator-b3/package.json +++ b/packages/opentelemetry-propagator-b3/package.json @@ -48,10 +48,10 @@ "@opentelemetry/core": "1.0.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "codecov": "3.8.3", diff --git a/packages/opentelemetry-propagator-jaeger/package.json b/packages/opentelemetry-propagator-jaeger/package.json index 16fe043690..e8ee5ccb1b 100644 --- a/packages/opentelemetry-propagator-jaeger/package.json +++ b/packages/opentelemetry-propagator-jaeger/package.json @@ -49,7 +49,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -72,7 +72,7 @@ "webpack": "4.46.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/core": "1.0.0" diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index 2eb290aa59..4ae97c1be4 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -50,7 +50,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -64,7 +64,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/packages/opentelemetry-sdk-trace-base/package.json b/packages/opentelemetry-sdk-trace-base/package.json index 9f52b0efa0..f08887ceee 100644 --- a/packages/opentelemetry-sdk-trace-base/package.json +++ b/packages/opentelemetry-sdk-trace-base/package.json @@ -55,7 +55,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@types/mocha": "8.2.3", "@types/node": "14.17.11", "@types/sinon": "10.0.2", @@ -78,7 +78,7 @@ "webpack": "4.46.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/packages/opentelemetry-sdk-trace-node/package.json b/packages/opentelemetry-sdk-trace-node/package.json index 69d5195d6d..336ac2c0d4 100644 --- a/packages/opentelemetry-sdk-trace-node/package.json +++ b/packages/opentelemetry-sdk-trace-node/package.json @@ -43,7 +43,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@opentelemetry/resources": "1.0.0", "@opentelemetry/semantic-conventions": "1.0.0", "@types/mocha": "8.2.3", @@ -59,7 +59,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/context-async-hooks": "1.0.0", diff --git a/packages/opentelemetry-sdk-trace-web/package.json b/packages/opentelemetry-sdk-trace-web/package.json index ca9cb92643..2cc79a2672 100644 --- a/packages/opentelemetry-sdk-trace-web/package.json +++ b/packages/opentelemetry-sdk-trace-web/package.json @@ -48,7 +48,7 @@ }, "devDependencies": { "@babel/core": "7.15.0", - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@opentelemetry/context-zone": "1.0.0", "@opentelemetry/propagator-b3": "1.0.0", "@opentelemetry/resources": "1.0.0", @@ -79,7 +79,7 @@ "webpack-merge": "5.8.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/packages/opentelemetry-shim-opentracing/package.json b/packages/opentelemetry-shim-opentracing/package.json index 496657c20a..b84d7dce3d 100644 --- a/packages/opentelemetry-shim-opentracing/package.json +++ b/packages/opentelemetry-shim-opentracing/package.json @@ -40,7 +40,7 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "~1.0.3", "@opentelemetry/propagator-b3": "1.0.0", "@opentelemetry/propagator-jaeger": "1.0.0", "@opentelemetry/sdk-trace-base": "1.0.0", @@ -54,7 +54,7 @@ "typescript": "4.3.5" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.2" + "@opentelemetry/api": ">=1.0.0 <1.1.0" }, "dependencies": { "@opentelemetry/core": "1.0.0", diff --git a/scripts/peer-api-check.js b/scripts/peer-api-check.js index 6e359e0473..3d79e60f44 100644 --- a/scripts/peer-api-check.js +++ b/scripts/peer-api-check.js @@ -17,6 +17,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); +const semver = require('semver'); const appRoot = process.cwd(); @@ -29,7 +30,7 @@ if (pjson.dependencies && pjson.dependencies["@opentelemetry/api"]) const peerVersion = pjson.peerDependencies && pjson.peerDependencies["@opentelemetry/api"] const devVersion = pjson.devDependencies && pjson.devDependencies["@opentelemetry/api"] if (peerVersion) { - if (peerVersion !== devVersion) { + if (!semver.subset(devVersion, peerVersion)) { throw new Error(`Package ${pjson.name} depends on peer API version ${peerVersion} but version ${devVersion} in development`); } console.log(`${pjson.name} OK`); From 357ec92e95e03b4d2309c65ffb17d06105985628 Mon Sep 17 00:00:00 2001 From: Valentin Marchaud Date: Wed, 27 Oct 2021 22:15:55 +0200 Subject: [PATCH 15/15] chore(doc): update matrix with contrib version for 1.0 core (#2568) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45e83be795..d6cbe69d9c 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This is the JavaScript version of [OpenTelemetry](https://opentelemetry.io/), a | API Version | Core version | Experimental Packages | Contrib Version | | ----------- |--------------| --------------------- |-------------------------| -| 1.0.x | 1.x | 0.26.x | ------ | +| 1.0.x | 1.x | 0.26.x | 0.26.x | | 1.0.x | 0.26.x | ----- | ------ | | 1.0.x | 0.25.x | ----- | ------ | | 1.0.x | 0.24.x | ----- | 0.24.x |

v>zb@rTsa~fx6>%U9I@A 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 { - axios - .get(`http://localhost:${PORT}/middle-tier`) - .then(() => axios.get(`http://localhost:${PORT}/middle-tier`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/middle-tier', (req, res) => { - axios - .get(`http://localhost:${PORT}/backend`) - .then(() => axios.get(`http://localhost:${PORT}/backend`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/backend', (req, res) => { - res.send('Hello from the backend'); -}); - -app.listen(parseInt(PORT, 10), () => { - console.log(`Listening for requests on http://localhost:${PORT}`); -}); diff --git a/getting-started/monitored-example/monitoring.js b/getting-started/monitored-example/monitoring.js deleted file mode 100644 index ca20e8d2fd..0000000000 --- a/getting-started/monitored-example/monitoring.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); -const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); - -const prometheusPort = PrometheusExporter.DEFAULT_OPTIONS.port; -const prometheusEndpoint = PrometheusExporter.DEFAULT_OPTIONS.endpoint; - -const exporter = new PrometheusExporter( - { - startServer: true, - }, - () => { - console.log( - `prometheus scrape endpoint: http://localhost:${prometheusPort}${prometheusEndpoint}`, - ); - }, -); - -const meter = new MeterProvider({ - exporter, - interval: 1000, -}).getMeter('your-meter-name'); - -const requestCount = meter.createCounter('requests', { - description: 'Count all incoming requests', -}); - -const boundInstruments = new Map(); - -module.exports.countAllRequests = () => (req, res, next) => { - if (!boundInstruments.has(req.path)) { - const labels = { route: req.path }; - const boundCounter = requestCount.bind(labels); - boundInstruments.set(req.path, boundCounter); - } - - boundInstruments.get(req.path).add(1); - next(); -}; diff --git a/getting-started/monitored-example/package.json b/getting-started/monitored-example/package.json deleted file mode 100644 index a8b6c3a66c..0000000000 --- a/getting-started/monitored-example/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@opentelemetry/getting-started-monitored-example", - "version": "0.25.0", - "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", - "main": "app.js", - "scripts": { - "start": "node app.js" - }, - "author": "OpenTelemetry Authors", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/exporter-prometheus": "0.25.0", - "@opentelemetry/sdk-metrics-base": "0.25.0", - "axios": "^0.21.0", - "express": "^4.17.1" - } -} diff --git a/getting-started/traced-example/app.js b/getting-started/traced-example/app.js deleted file mode 100644 index 287ab78cb3..0000000000 --- a/getting-started/traced-example/app.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const PORT = process.env.PORT || '8080'; - -const express = require('express'); -const axios = require('axios'); - -const app = express(); - -app.get('/', (req, res) => { - axios - .get(`http://localhost:${PORT}/middle-tier`) - .then(() => axios.get(`http://localhost:${PORT}/middle-tier`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/middle-tier', (req, res) => { - axios - .get(`http://localhost:${PORT}/backend`) - .then(() => axios.get(`http://localhost:${PORT}/backend`)) - .then((result) => { - res.send(result.data); - }) - .catch((err) => { - console.error(err); - res.status(500).send(); - }); -}); - -app.get('/backend', (req, res) => { - res.send('Hello from the backend'); -}); - -app.listen(parseInt(PORT, 10), () => { - console.log(`Listening for requests on http://localhost:${PORT}`); -}); diff --git a/getting-started/traced-example/package.json b/getting-started/traced-example/package.json deleted file mode 100644 index 8268cd9ddc..0000000000 --- a/getting-started/traced-example/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@opentelemetry/getting-started-traced-example", - "version": "0.25.0", - "description": "This repository provides everything required to follow the OpenTelemetry Getting Started Guide", - "main": "app.js", - "scripts": { - "start": "node -r ./tracing.js app.js" - }, - "author": "OpenTelemetry Authors", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.0.0", - "@opentelemetry/exporter-zipkin": "1.0.0", - "@opentelemetry/instrumentation-express": "^0.24.0", - "@opentelemetry/instrumentation-http": "0.25.0", - "@opentelemetry/instrumentation": "0.25.0", - "@opentelemetry/sdk-trace-node": "1.0.0", - "@opentelemetry/sdk-trace-base": "1.0.0", - "axios": "^0.21.0", - "express": "^4.17.1" - } -} diff --git a/getting-started/traced-example/tracing.js b/getting-started/traced-example/tracing.js deleted file mode 100644 index ae6b88b015..0000000000 --- a/getting-started/traced-example/tracing.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); -const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base'); -const { Resource } = require('@opentelemetry/resources'); -const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); -const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); -const { registerInstrumentations } = require('@opentelemetry/instrumentation'); -const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express'); -const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); - -const provider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'getting-started', - }), -}); - -provider.addSpanProcessor( - new SimpleSpanProcessor( - new ZipkinExporter({ - // If you are running your tracing backend on another host, - // you can point to it using the `url` parameter of the - // exporter config. - }), - ), -); - -provider.register(); - -// load old default plugins -registerInstrumentations({ - instrumentations: [ - new ExpressInstrumentation(), - new HttpInstrumentation(), - ], -}); - -console.log('tracing initialized'); diff --git a/getting-started/ts-example/.eslintrc b/getting-started/ts-example/.eslintrc deleted file mode 100644 index 6564779085..0000000000 --- a/getting-started/ts-example/.eslintrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "plugins": ["@typescript-eslint", "node"], - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - "parser": "@typescript-eslint/parser", - "rules": { - "@typescript-eslint/no-var-requires": 0, - "import/prefer-default-export": "off", - "import/extensions": [ - "error", - "ignorePackages", - { - "": "never" - } - ] - } -} diff --git a/getting-started/ts-example/README.md b/getting-started/ts-example/README.md deleted file mode 100644 index a1ccd6a1cb..0000000000 --- a/getting-started/ts-example/README.md +++ /dev/null @@ -1,389 +0,0 @@ -# Getting Started with OpenTelemetry JS (TypeScript) - -This TypeScript guide will walk you through the setup and configuration process for a tracing backend (in this case [Zipkin](https://zipkin.io), but [Jaeger](https://www.jaegertracing.io) would be simple to use as well), a metrics backend like [Prometheus](https://prometheus.io), and auto-instrumentation of NodeJS. [You can find the guide for JavaScript here](../README.md#getting-started-with-opentelemetry-js). - -- [Getting Started with OpenTelemetry JS (TypeScript)](#getting-started-with-opentelemetry-js-typescript) - - [Tracing Your Application with OpenTelemetry](#tracing-your-application-with-opentelemetry) - - [Setting up a Tracing Backend](#setting-up-a-tracing-backend) - - [Trace Your NodeJS Application](#trace-your-nodejs-application) - - [Install the required OpenTelemetry libraries](#install-the-required-opentelemetry-libraries) - - [Initialize a global tracer](#initialize-a-global-tracer) - - [Initialize and Register a Trace Exporter](#initialize-and-register-a-trace-exporter) - - [Collect Metrics Using OpenTelemetry](#collect-metrics-using-opentelemetry) - - [Set up a Metrics Backend](#set-up-a-metrics-backend) - - [Monitor Your NodeJS Application](#monitor-your-nodejs-application) - - [Install the required OpenTelemetry metrics libraries](#install-the-required-opentelemetry-sdk-metrics-base-libraries) - - [Initialize a meter and collect metrics](#initialize-a-meter-and-collect-metrics) - - [Initialize and register a metrics exporter](#initialize-and-register-a-metrics-exporter) - -## Tracing Your Application with OpenTelemetry - -([link to JavaScript version](../README.md#tracing-your-application-with-opentelemetry)) - -This guide assumes you are going to be using Zipkin as your tracing backend, but modifying it for Jaeger should be straightforward. - -An example application which can be used with this guide can be found in the [example directory](example). You can see what it looks like with tracing enabled in the [traced-example directory](traced-example). - -### Setting up a Tracing Backend - -([link to JavaScript version](../README.md#setting-up-a-tracing-backend)) - -The first thing we will need before we can start collecting traces is a tracing backend like Zipkin that we can export traces to. If you already have a supported tracing backend (Zipkin or Jaeger), you can skip this step. If not, you will need to run one. - -In order to set up Zipkin as quickly as possible, run the latest [Docker Zipkin](https://github.com/openzipkin/docker-zipkin) container, exposing port `9411`. If you can’t run Docker containers, you will need to download and run Zipkin by following the Zipkin [quickstart guide](https://zipkin.io/pages/quickstart.html). - -```sh -docker run --rm -d -p 9411:9411 --name zipkin openzipkin/zipkin -``` - -Browse to to ensure that you can see the Zipkin UI. - -