From 9615a79a9235539dcb130259016356ba581c2ea4 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 1 Dec 2022 16:21:13 +0200 Subject: [PATCH 01/67] feat(sync-resource-detectors): initial --- .../export/PeriodicExportingMetricReader.ts | 21 +++- .../opentelemetry-sdk-node/src/sdk.ts | 10 +- .../opentelemetry-sdk-node/test/sdk.test.ts | 54 ++++----- .../opentelemetry-resources/src/Resource.ts | 72 +++++++++++- .../src/platform/browser/detect-resources.ts | 51 ++++++++- .../src/platform/node/detect-resources.ts | 56 ++++++++- packages/opentelemetry-resources/src/types.ts | 7 +- .../test/Resource.test.ts | 95 ++++++++++++++++ .../test/detect-resources.test.ts | 106 ++++++++++++++++++ .../src/export/BatchSpanProcessorBase.ts | 18 ++- .../src/export/SimpleSpanProcessor.ts | 12 +- 11 files changed, 449 insertions(+), 53 deletions(-) create mode 100644 packages/opentelemetry-resources/test/detect-resources.test.ts diff --git a/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts index 2a686181cc..c4fe26a75d 100644 --- a/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/experimental/packages/opentelemetry-sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -27,6 +27,7 @@ import { callWithTimeout, TimeoutError } from '../utils'; +import { diag } from '@opentelemetry/api'; export type PeriodicExportingMetricReaderOptions = { /** @@ -86,11 +87,21 @@ export class PeriodicExportingMetricReader extends MetricReader { api.diag.error('PeriodicExportingMetricReader: metrics collection errors', ...errors); } - const result = await internal._export(this._exporter, resourceMetrics); - if (result.code !== ExportResultCode.SUCCESS) { - throw new Error( - `PeriodicExportingMetricReader: metrics export failed (error ${result.error})` - ); + const doExport = async () => { + const result = await internal._export(this._exporter, resourceMetrics); + if (result.code !== ExportResultCode.SUCCESS) { + throw new Error( + `PeriodicExportingMetricReader: metrics export failed (error ${result.error})` + ); + } + }; + + // Avoid scheduling a promise to make the behavior more predictable and easier to test + if (resourceMetrics.resource.asyncAttributesHaveResolved()) { + await doExport(); + } else { + resourceMetrics.resource.waitForAsyncAttributes() + .then(await doExport, err => diag.debug('Error while resolving async portion of resource: ', err)); } } diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 38f85b87f2..65f232d3ad 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -21,7 +21,7 @@ import { } from '@opentelemetry/instrumentation'; import { Detector, - detectResources, + detectResourcesSync, envDetector, processDetector, Resource, @@ -166,12 +166,12 @@ export class NodeSDK { } /** Detect resource attributes */ - public async detectResources(): Promise { + public detectResources(): void { const internalConfig: ResourceDetectionConfig = { detectors: this._resourceDetectors, }; - this.addResource(await detectResources(internalConfig)); + this.addResource(detectResourcesSync(internalConfig)); } /** Manually add a resource */ @@ -182,9 +182,9 @@ export class NodeSDK { /** * Once the SDK has been configured, call this method to construct SDK components and register them with the OpenTelemetry API. */ - public async start(): Promise { + public start(): void { if (this._autoDetectResources) { - await this.detectResources(); + this.detectResources(); } this._resource = this._serviceName === undefined diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 6d853e4902..d53ad3c9f0 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -89,7 +89,7 @@ describe('Node SDK', () => { autoDetectResources: false, }); - await sdk.start(); + sdk.start(); assert.strictEqual(context['_getContextManager'](), ctxManager, 'context manager should not change'); assert.strictEqual(propagation['_getGlobalPropagator'](), propagator, 'propagator should not change'); @@ -104,7 +104,7 @@ describe('Node SDK', () => { autoDetectResources: false, }); - await sdk.start(); + sdk.start(); assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider)); @@ -127,7 +127,7 @@ describe('Node SDK', () => { autoDetectResources: false, }); - await sdk.start(); + sdk.start(); assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider)); @@ -157,7 +157,7 @@ describe('Node SDK', () => { autoDetectResources: false, }); - await sdk.start(); + sdk.start(); assert.strictEqual(context['_getContextManager'](), ctxManager, 'context manager should not change'); assert.strictEqual(propagation['_getGlobalPropagator'](), propagator, 'propagator should not change'); @@ -206,7 +206,7 @@ describe('Node SDK', () => { autoDetectResources: false, }); - await sdk.start(); + sdk.start(); assert.strictEqual(context['_getContextManager'](), ctxManager, 'context manager should not change'); assert.strictEqual(propagation['_getGlobalPropagator'](), propagator, 'propagator should not change'); @@ -351,8 +351,9 @@ describe('Node SDK', () => { envDetector] }); - await sdk.detectResources(); + sdk.detectResources(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assertServiceResource(resource, { instanceId: '627cc493', @@ -399,7 +400,8 @@ describe('Node SDK', () => { DiagLogLevel.VERBOSE ); - await sdk.detectResources(); + sdk.detectResources(); + await sdk['_resource'].waitForAsyncAttributes().catch(() => {}); // Test that the Env Detector successfully found its resource and populated it with the right values. assert.ok( @@ -431,7 +433,7 @@ describe('Node SDK', () => { DiagLogLevel.DEBUG ); - await sdk.detectResources(); + sdk.detectResources(); assert.ok( callArgsContains( @@ -450,7 +452,7 @@ describe('Node SDK', () => { serviceName: 'config-set-name', }); - await sdk.start(); + sdk.start(); const resource = sdk['_resource']; assertServiceResource(resource, { @@ -462,7 +464,7 @@ describe('Node SDK', () => { process.env.OTEL_SERVICE_NAME = 'env-set-name'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const resource = sdk['_resource']; assertServiceResource(resource, { @@ -477,7 +479,7 @@ describe('Node SDK', () => { serviceName: 'config-set-name', }); - await sdk.start(); + sdk.start(); const resource = sdk['_resource']; assertServiceResource(resource, { @@ -490,7 +492,7 @@ describe('Node SDK', () => { process.env.OTEL_RESOURCE_ATTRIBUTES = 'service.name=resource-env-set-name'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const resource = sdk['_resource']; assertServiceResource(resource, { @@ -505,7 +507,7 @@ describe('Node SDK', () => { serviceName: 'config-set-name', }); - await sdk.start(); + sdk.start(); const resource = sdk['_resource']; assertServiceResource(resource, { @@ -530,7 +532,7 @@ describe('setup exporter from env', () => { }); it('use default exporter TracerProviderWithEnvExporters when user does not provide span processor or trace exporter to sdk config', async () => { const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); @@ -542,7 +544,7 @@ describe('setup exporter from env', () => { const sdk = new NodeSDK({ traceExporter }); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false); @@ -556,7 +558,7 @@ describe('setup exporter from env', () => { const sdk = new NodeSDK({ spanProcessor }); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false); @@ -570,7 +572,7 @@ describe('setup exporter from env', () => { const sdk = new NodeSDK({ traceExporter }); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false); @@ -583,7 +585,7 @@ describe('setup exporter from env', () => { env.OTEL_TRACES_EXPORTER = 'otlp'; env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); @@ -595,7 +597,7 @@ describe('setup exporter from env', () => { it('use noop span processor when user sets env exporter to none', async () => { env.OTEL_TRACES_EXPORTER = 'none'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; const activeProcessor = sdk['_tracerProvider']?.getActiveSpanProcessor(); @@ -607,7 +609,7 @@ describe('setup exporter from env', () => { it('log warning that sdk will not be initalized when exporter is set to none', async () => { env.OTEL_TRACES_EXPORTER = 'none'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); assert.strictEqual(stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" or is empty. SDK will not be initialized.'); delete env.OTEL_TRACES_EXPORTER; @@ -615,7 +617,7 @@ describe('setup exporter from env', () => { it('do not use any exporters when empty value is provided for exporter', async () => { env.OTEL_TRACES_EXPORTER = ''; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; const activeProcessor = sdk['_tracerProvider']?.getActiveSpanProcessor(); @@ -628,7 +630,7 @@ describe('setup exporter from env', () => { it('use only default exporter when none value is provided with other exporters', async () => { env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); @@ -640,7 +642,7 @@ describe('setup exporter from env', () => { it('log warning that only default exporter will be used since exporter list contains none with other exports ', async () => { env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); assert.strictEqual( stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.' @@ -650,7 +652,7 @@ describe('setup exporter from env', () => { it('should warn that provided exporter value is unrecognized and not able to be set up', async () => { env.OTEL_TRACES_EXPORTER = 'invalid'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); assert.strictEqual( stubLoggerError.args[0][0], 'Unrecognized OTEL_TRACES_EXPORTER value: invalid.' @@ -666,7 +668,7 @@ describe('setup exporter from env', () => { env.OTEL_TRACES_EXPORTER = 'zipkin, otlp, jaeger'; env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); @@ -681,7 +683,7 @@ describe('setup exporter from env', () => { it('use the console exporter', async () => { env.OTEL_TRACES_EXPORTER = 'console, otlp'; const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(listOfProcessors.length === 2); diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index ac368d812e..87215947f9 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { diag } from '@opentelemetry/api'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { SDK_INFO } from '@opentelemetry/core'; import { ResourceAttributes } from './types'; @@ -25,6 +26,9 @@ import { defaultServiceName } from './platform'; */ export class Resource { static readonly EMPTY = new Resource({}); + private _attributes: ResourceAttributes; + private _asyncAttributesPromise: Promise | undefined; + private _asyncAttributesHaveResolved: boolean; /** * Returns an empty Resource @@ -34,7 +38,7 @@ export class Resource { } /** - * Returns a Resource that indentifies the SDK in use. + * Returns a Resource that identifies the SDK in use. */ static default(): Resource { return new Resource({ @@ -54,8 +58,50 @@ export class Resource { * information about the entity as numbers, strings or booleans * TODO: Consider to add check/validation on attributes. */ - readonly attributes: ResourceAttributes - ) {} + attributes: ResourceAttributes, + asyncAttributesPromise?: Promise, + ) { + this._attributes = attributes; + this._asyncAttributesHaveResolved = asyncAttributesPromise === undefined; + this._asyncAttributesPromise = asyncAttributesPromise?.then( + asyncAttributes => { + this._attributes = Object.assign({}, this._attributes, asyncAttributes); + this._asyncAttributesHaveResolved = true; + return asyncAttributes; + } + ); + this._asyncAttributesPromise?.catch(err => { + diag.debug("The resource's async promise rejected: %s", err); + this._asyncAttributesHaveResolved = true; + return {}; + }); + } + + get attributes(): ResourceAttributes { + return this._attributes; + } + + /** + * Check if async attributes have resolved. This is useful to avoid awaiting + * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. + * + * @returns true if no async attributes promise was provided or if the promise has resolved + * and been merged together with the sync attributes. + */ + asyncAttributesHaveResolved(): boolean { + return this._asyncAttributesHaveResolved; + } + + /** + * Returns a promise that resolves when all async attributes have finished being added to + * this Resource's attributes. This is useful in exporters to block until resource detection + * has finished. + */ + async waitForAsyncAttributes(): Promise { + if (!this._asyncAttributesHaveResolved) { + await this._asyncAttributesPromise; + } + } /** * Returns a new, merged {@link Resource} by merging the current Resource @@ -66,7 +112,7 @@ export class Resource { * @returns the newly merged Resource. */ merge(other: Resource | null): Resource { - if (!other || !Object.keys(other.attributes).length) return this; + if (!other) return this; // SpanAttributes from resource overwrite attributes from other resource. const mergedAttributes = Object.assign( @@ -74,6 +120,22 @@ export class Resource { this.attributes, other.attributes ); - return new Resource(mergedAttributes); + + let mergedAsyncAttributesPromise: Promise | undefined; + if (this._asyncAttributesPromise && other._asyncAttributesPromise) { + mergedAsyncAttributesPromise = Promise.all([ + this._asyncAttributesPromise.catch(() => ({})), + other._asyncAttributesPromise.catch(() => ({})), + ]).then( + ([thisAttributes, otherAttributes]) => { + return Object.assign({}, thisAttributes, otherAttributes); + } + ); + } else { + mergedAsyncAttributesPromise = this._asyncAttributesPromise ?? other._asyncAttributesPromise; + } + + + return new Resource(mergedAttributes, mergedAsyncAttributesPromise); } } diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts index 457965fadf..b9992f9d4c 100644 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -19,9 +19,11 @@ import { ResourceDetectionConfig } from '../../config'; import { diag } from '@opentelemetry/api'; /** - * Runs all resource detectors and returns the results merged into a single - * Resource. + * Runs all resource detectors and returns the results merged into a single Resource. Promise + * does not resolve until all the underlying detectors have resolved, unlike + * detectResourcesSync. * + * @deprecated use detectResourceSync() instead. * @param config Configuration for resource detection */ export const detectResources = async ( @@ -47,3 +49,48 @@ export const detectResources = async ( Resource.empty() ); }; + +/** + * Runs all resource detectors synchronously, merging their results. Any asynchronous + * attributes will be merged together in-order after they resolve. + * + * @param config Configuration for resource detection + */ +export const detectResourcesSync = ( + config: ResourceDetectionConfig = {} +): Resource => { + const internalConfig: ResourceDetectionConfig = Object.assign(config); + + const resources: Resource[] = (internalConfig.detectors ?? []).map(d => { + try { + const resourceOrPromise = d.detect(internalConfig); + let resource: Resource; + if (resourceOrPromise instanceof Promise) { + const createPromise = async () => { + const resolved = await resourceOrPromise; + await resolved.waitForAsyncAttributes(); + return resolved.attributes; + }; + resource = new Resource({}, createPromise()); + } else { + resource = resourceOrPromise; + } + + resource.waitForAsyncAttributes().then(() => { + diag.debug(`${d.constructor.name} found resource.`, resource); + }).catch(e => { + diag.debug(`${d.constructor.name} failed: ${e.message}`); + }); + + return resource; + } catch (e) { + diag.debug(`${d.constructor.name} failed: ${e.message}`); + return Resource.empty(); + } + }); + + return resources.reduce( + (acc, resource) => acc.merge(resource), + Resource.empty() + );; +}; diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts index 35bf32b4cd..ba8768f182 100644 --- a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts @@ -20,9 +20,11 @@ import { diag } from '@opentelemetry/api'; import * as util from 'util'; /** - * Runs all resource detectors and returns the results merged into a single - * Resource. + * Runs all resource detectors and returns the results merged into a single Resource. Promise + * does not resolve until all the underlying detectors have resolved, unlike + * detectResourcesSync. * + * @deprecated use detectResourceSync() instead. * @param config Configuration for resource detection */ export const detectResources = async ( @@ -52,6 +54,56 @@ export const detectResources = async ( ); }; +/** + * Runs all resource detectors synchronously, merging their results. Any asynchronous + * attributes will be merged together in-order after they resolve. + * + * @param config Configuration for resource detection + */ +export const detectResourcesSync = ( + config: ResourceDetectionConfig = {} +): Resource => { + const internalConfig: ResourceDetectionConfig = Object.assign(config); + + const resources: Resource[] = (internalConfig.detectors ?? []).map(d => { + try { + const resourceOrPromise = d.detect(internalConfig); + let resource: Resource; + if (resourceOrPromise instanceof Promise) { + const createPromise = async () => { + const resolved = await resourceOrPromise; + await resolved.waitForAsyncAttributes(); + return resolved.attributes; + }; + resource = new Resource({}, createPromise()); + } else { + resource = resourceOrPromise; + } + + resource.waitForAsyncAttributes().then(() => { + diag.debug(`${d.constructor.name} found resource.`, resource); + }).catch(e => { + diag.debug(`${d.constructor.name} failed: ${e.message}`); + }); + + return resource; + } catch (e) { + diag.debug(`${d.constructor.name} failed: ${e.message}`); + return Resource.empty(); + } + }); + + + const mergedResources = resources.reduce( + (acc, resource) => acc.merge(resource), + Resource.empty() + ); + void mergedResources.waitForAsyncAttributes().then(() => { + // Future check if verbose logging is enabled issue #1903 + logResources(resources); + }); + return mergedResources; +}; /** * Writes debug information about the detected resources to the logger defined in the resource detection config, if one is provided. diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index 717f71381d..bbe195ceae 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -26,9 +26,10 @@ import { SpanAttributes } from '@opentelemetry/api'; export type ResourceAttributes = SpanAttributes; /** - * Interface for a Resource Detector. In order to detect resources in parallel - * a detector returns a Promise containing a Resource. + * Interface for a Resource Detector. In order to detect resources asynchronously, a detector + * can pass a Promise as the second parameter to the Resource constructor. Returning a + * Promise is deprecated in favor of this approach. */ export interface Detector { - detect(config?: ResourceDetectionConfig): Promise; + detect(config?: ResourceDetectionConfig): Promise | Resource; } diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index c3780591d6..e0ed310b9d 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -14,11 +14,13 @@ * limitations under the License. */ +import * as sinon from 'sinon'; import * as assert from 'assert'; import { SDK_INFO } from '@opentelemetry/core'; import { Resource } from '../src'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeBrowser, describeNode } from './util'; +import {diag} from '@opentelemetry/api'; describe('Resource', () => { const resource1 = new Resource({ @@ -99,6 +101,99 @@ describe('Resource', () => { it('should return the same empty resource', () => { assert.strictEqual(Resource.empty(), Resource.empty()); }); + + it('should return true for asyncAttributesHaveResolved() immediately', () => { + assert.ok(Resource.empty().asyncAttributesHaveResolved()); + }); + }); + + describe('asynchronous attributes', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return true for asyncAttributesHaveResolved() if no promise provided', () => { + assert.ok(new Resource({'foo': 'bar'}).asyncAttributesHaveResolved()); + assert.ok(Resource.empty().asyncAttributesHaveResolved()); + assert.ok(Resource.default().asyncAttributesHaveResolved()); + }); + + it('should return true for asyncAttributesHaveResolved() once promise finishes', async () => { + const clock = sinon.useFakeTimers(); + const resourceResolve = new Resource({}, new Promise(resolve => { + setTimeout(resolve, 100); + })); + const resourceReject = new Resource({}, new Promise((_, reject) => { + setTimeout(reject, 200); + })); + + for (const resource of [resourceResolve, resourceReject]) { + assert.ok(!resource.asyncAttributesHaveResolved()); + await clock.nextAsync(); + await resource.waitForAsyncAttributes(); + assert.ok(resource.asyncAttributesHaveResolved()); + } + }); + + it('should merge async attributes into sync attributes once resolved', async () => { + const resource = new Resource( + {'sync': 'fromsync', 'shared': 'fromsync'}, + Promise.resolve({'async': 'fromasync', 'shared': 'fromasync'}), + ); + + await resource.waitForAsyncAttributes(); + assert.deepStrictEqual( + resource.attributes, + { + 'sync': 'fromsync', + // async takes precedence + 'shared': 'fromasync', + 'async': 'fromasync', + }, + ); + }); + + it('should merge async attributes when both resources have promises', async () => { + const resource1 = new Resource( + {}, Promise.resolve({'promise1': 'promise1val', 'shared': 'promise1val'}), + ); + const resource2 = new Resource( + {}, Promise.resolve({'promise2': 'promise2val', 'shared': 'promise2val'}), + ); + // this one rejects + const resource3 = new Resource( + {}, Promise.reject(new Error('reject')), + ); + const resource4 = new Resource( + {}, Promise.resolve({'promise4': 'promise4val', 'shared': 'promise4val'}), + ); + + const merged = resource1.merge(resource2).merge(resource3).merge(resource4); + await merged.waitForAsyncAttributes(); + assert.deepStrictEqual( + merged.attributes, + { + 'promise1': 'promise1val', + 'promise2': 'promise2val', + 'promise4': 'promise4val', + // same behavior as for synchronous attributes + 'shared': 'promise4val', + } + ); + }); + + it('should log when promise rejects', async () => { + const debugStub = sinon.spy(diag, 'debug'); + // should be possible to catch failure with waitForAsyncAttributes() + try { + await assert.rejects( + new Resource({}, Promise.reject(new Error('rejected'))).waitForAsyncAttributes(), + ); + } catch(err) {} + // will log after yielding to event loop + await new Promise(resolve => setTimeout(resolve)); + assert.ok(debugStub.calledOnce); + }); }); describeNode('.default()', () => { diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts new file mode 100644 index 0000000000..c1cba8ef0c --- /dev/null +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { diag } from '@opentelemetry/api'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { Resource, Detector, detectResourcesSync } from '../src'; +import { describeNode } from './util'; + +describe('detectResourcesSync', () => { + afterEach(() => { + sinon.restore(); + }); + + it('handles resource detectors which return Promise', async () => { + const detector: Detector = { + async detect() { + return new Resource( + { 'sync': 'fromsync', 'async': 'fromasync' }, + ); + }, + }; + const resource = detectResourcesSync({ + detectors: [detector], + }); + + await resource.waitForAsyncAttributes(); + assert.deepStrictEqual(resource.attributes, {'sync': 'fromsync', 'async': 'fromasync'}); + }); + + it('handles resource detectors which return Resource with a promise inside', async () => { + const detector: Detector = { + detect() { + return new Resource( + { 'sync': 'fromsync' }, + Promise.resolve({ 'async': 'fromasync'}) + ); + }, + }; + const resource = detectResourcesSync({ + detectors: [detector], + }); + + // before waiting, it should already have the sync resources + assert.deepStrictEqual(resource.attributes, {'sync': 'fromsync'}); + await resource.waitForAsyncAttributes(); + assert.deepStrictEqual(resource.attributes, {'sync': 'fromsync', 'async': 'fromasync'}); + }); + + describeNode('logging', () => { + it("logs when a detector's async attributes promise rejects or resolves", async () => { + const debugStub = sinon.spy(diag, 'debug'); + + // use a class so it has a name + class DetectorRejects implements Detector { + detect() { + return new Resource( + { 'sync': 'fromsync' }, + Promise.reject(new Error('reject')), + ); + } + } + class DetectorOk implements Detector { + detect() { + return new Resource( + { 'sync': 'fromsync' }, + Promise.resolve({'async': 'fromasync'}), + ); + } + } + class DetectorAsync implements Detector { + async detect() { + return new Resource( + { 'sync': 'fromsync', 'async': 'fromasync' }, + ); + } + } + + const resource = detectResourcesSync({ + detectors: [ + new DetectorRejects(), + new DetectorOk(), + new DetectorAsync(), + ], + }); + + await resource.waitForAsyncAttributes(); + assert.ok(debugStub.calledWithMatch('DetectorRejects failed: reject')); + assert.ok(debugStub.calledWithMatch('DetectorOk found resource.')); + assert.ok(debugStub.calledWithMatch('DetectorAsync found resource.')); + }); + }); +}); diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 8978fc7d79..9aa996b563 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -156,9 +156,11 @@ export abstract class BatchSpanProcessorBase implements context.with(suppressTracing(context.active()), () => { // Reset the finished spans buffer here because the next invocations of the _flush method // could pass the same finished spans to the exporter if the buffer is cleared - // outside of the execution of this callback. - this._exporter.export( - this._finishedSpans.splice(0, this._maxExportBatchSize), + // outside the execution of this callback. + const spans = this._finishedSpans.splice(0, this._maxExportBatchSize); + + const doExport = () => this._exporter.export( + spans, result => { clearTimeout(timer); if (result.code === ExportResultCode.SUCCESS) { @@ -171,6 +173,16 @@ export abstract class BatchSpanProcessorBase implements } } ); + const pendingResources = spans.map(span => span.resource) + .filter(resource => !resource.asyncAttributesHaveResolved()); + + // Avoid scheduling a promise to make the behavior more predictable and easier to test + if (pendingResources.length === 0) { + doExport(); + } else { + Promise.all(pendingResources.map(resource => resource.waitForAsyncAttributes())) + .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err)); + } }); }); } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index a510ad02a6..49e775e42f 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Context, TraceFlags } from '@opentelemetry/api'; +import { Context, diag, TraceFlags } from '@opentelemetry/api'; import { internal, ExportResultCode, @@ -57,7 +57,7 @@ export class SimpleSpanProcessor implements SpanProcessor { return; } - internal._export(this._exporter, [span]).then((result: ExportResult) => { + const doExport = () => internal._export(this._exporter, [span]).then((result: ExportResult) => { if (result.code !== ExportResultCode.SUCCESS) { globalErrorHandler( result.error ?? @@ -69,6 +69,14 @@ export class SimpleSpanProcessor implements SpanProcessor { }).catch(error => { globalErrorHandler(error); }); + + // Avoid scheduling a promise to make the behavior more predictable and easier to test + if (span.resource.asyncAttributesHaveResolved()) { + void doExport(); + } else { + span.resource.waitForAsyncAttributes() + .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err)); + } } shutdown(): Promise { From f0c6044d7a5fb0525e3daf992d92d6903bfffaf0 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 1 Dec 2022 16:21:58 +0200 Subject: [PATCH 02/67] feat(sync-resource-detectors): initial --- .../src/platform/browser/detect-resources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts index b9992f9d4c..2d21b0f475 100644 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -92,5 +92,5 @@ export const detectResourcesSync = ( return resources.reduce( (acc, resource) => acc.merge(resource), Resource.empty() - );; + ); }; From 6b80ab029ee43a1abe6745c8292fe43910ffd31b Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 4 Dec 2022 16:52:44 +0200 Subject: [PATCH 03/67] feat(sync-resource-detectors): updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a13ad70f..dcfe97db34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :rocket: (Enhancement) * feat(api): add `getActiveBaggage` API [#3385](https://github.com/open-telemetry/opentelemetry-js/pull/3385) +* feat(resource): create sync resource with some attributes that resolve asynchronously [#3460](https://github.com/open-telemetry/opentelemetry-js/pull/3460) ### :bug: (Bug Fix) From 40957fb15c6a65f562db5fea5b9877f377806af1 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 4 Dec 2022 17:15:17 +0200 Subject: [PATCH 04/67] feat(sync-resource-detectors): fixed lint issues --- .../opentelemetry-sdk-node/test/sdk.test.ts | 12 ++- .../opentelemetry-resources/src/Resource.ts | 14 ++-- .../src/platform/browser/detect-resources.ts | 13 +-- .../src/platform/node/detect-resources.ts | 14 ++-- .../test/Resource.test.ts | 83 ++++++++++--------- .../test/detect-resources.test.ts | 32 +++---- .../src/export/BatchSpanProcessorBase.ts | 18 ++-- .../src/export/SimpleSpanProcessor.ts | 35 ++++---- .../export/PeriodicExportingMetricReader.ts | 12 +-- 9 files changed, 129 insertions(+), 104 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index f3ea466b5b..4582a708ae 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -607,7 +607,8 @@ describe('setup exporter from env', () => { it('use default exporter TracerProviderWithEnvExporters when user does not provide span processor or trace exporter to sdk config', async () => { const sdk = new NodeSDK(); sdk.start(); - const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + const listOfProcessors = + sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); assert(listOfProcessors.length === 1); @@ -619,7 +620,8 @@ describe('setup exporter from env', () => { traceExporter, }); sdk.start(); - const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + const listOfProcessors = + sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert( sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false @@ -635,7 +637,8 @@ describe('setup exporter from env', () => { spanProcessor, }); sdk.start(); - const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + const listOfProcessors = + sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert( sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false @@ -651,7 +654,8 @@ describe('setup exporter from env', () => { traceExporter, }); sdk.start(); - const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + const listOfProcessors = + sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert( sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 87215947f9..8ea8614268 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -59,7 +59,7 @@ export class Resource { * TODO: Consider to add check/validation on attributes. */ attributes: ResourceAttributes, - asyncAttributesPromise?: Promise, + asyncAttributesPromise?: Promise ) { this._attributes = attributes; this._asyncAttributesHaveResolved = asyncAttributesPromise === undefined; @@ -126,16 +126,14 @@ export class Resource { mergedAsyncAttributesPromise = Promise.all([ this._asyncAttributesPromise.catch(() => ({})), other._asyncAttributesPromise.catch(() => ({})), - ]).then( - ([thisAttributes, otherAttributes]) => { - return Object.assign({}, thisAttributes, otherAttributes); - } - ); + ]).then(([thisAttributes, otherAttributes]) => { + return Object.assign({}, thisAttributes, otherAttributes); + }); } else { - mergedAsyncAttributesPromise = this._asyncAttributesPromise ?? other._asyncAttributesPromise; + mergedAsyncAttributesPromise = + this._asyncAttributesPromise ?? other._asyncAttributesPromise; } - return new Resource(mergedAttributes, mergedAsyncAttributesPromise); } } diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts index 2d21b0f475..b3c6f39607 100644 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -76,11 +76,14 @@ export const detectResourcesSync = ( resource = resourceOrPromise; } - resource.waitForAsyncAttributes().then(() => { - diag.debug(`${d.constructor.name} found resource.`, resource); - }).catch(e => { - diag.debug(`${d.constructor.name} failed: ${e.message}`); - }); + resource + .waitForAsyncAttributes() + .then(() => { + diag.debug(`${d.constructor.name} found resource.`, resource); + }) + .catch(e => { + diag.debug(`${d.constructor.name} failed: ${e.message}`); + }); return resource; } catch (e) { diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts index ba8768f182..b9cdf07aef 100644 --- a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts @@ -80,11 +80,14 @@ export const detectResourcesSync = ( resource = resourceOrPromise; } - resource.waitForAsyncAttributes().then(() => { - diag.debug(`${d.constructor.name} found resource.`, resource); - }).catch(e => { - diag.debug(`${d.constructor.name} failed: ${e.message}`); - }); + resource + .waitForAsyncAttributes() + .then(() => { + diag.debug(`${d.constructor.name} found resource.`, resource); + }) + .catch(e => { + diag.debug(`${d.constructor.name} failed: ${e.message}`); + }); return resource; } catch (e) { @@ -93,7 +96,6 @@ export const detectResourcesSync = ( } }); - const mergedResources = resources.reduce( (acc, resource) => acc.merge(resource), Resource.empty() diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 8ab4e990dc..8742b36521 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -20,7 +20,7 @@ import { SDK_INFO } from '@opentelemetry/core'; import { Resource } from '../src'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeBrowser, describeNode } from './util'; -import {diag} from '@opentelemetry/api'; +import { diag } from '@opentelemetry/api'; describe('Resource', () => { const resource1 = new Resource({ @@ -113,19 +113,25 @@ describe('Resource', () => { }); it('should return true for asyncAttributesHaveResolved() if no promise provided', () => { - assert.ok(new Resource({'foo': 'bar'}).asyncAttributesHaveResolved()); + assert.ok(new Resource({ foo: 'bar' }).asyncAttributesHaveResolved()); assert.ok(Resource.empty().asyncAttributesHaveResolved()); assert.ok(Resource.default().asyncAttributesHaveResolved()); }); it('should return true for asyncAttributesHaveResolved() once promise finishes', async () => { const clock = sinon.useFakeTimers(); - const resourceResolve = new Resource({}, new Promise(resolve => { - setTimeout(resolve, 100); - })); - const resourceReject = new Resource({}, new Promise((_, reject) => { - setTimeout(reject, 200); - })); + const resourceResolve = new Resource( + {}, + new Promise(resolve => { + setTimeout(resolve, 100); + }) + ); + const resourceReject = new Resource( + {}, + new Promise((_, reject) => { + setTimeout(reject, 200); + }) + ); for (const resource of [resourceResolve, resourceReject]) { assert.ok(!resource.asyncAttributesHaveResolved()); @@ -137,49 +143,47 @@ describe('Resource', () => { it('should merge async attributes into sync attributes once resolved', async () => { const resource = new Resource( - {'sync': 'fromsync', 'shared': 'fromsync'}, - Promise.resolve({'async': 'fromasync', 'shared': 'fromasync'}), + { sync: 'fromsync', shared: 'fromsync' }, + Promise.resolve({ async: 'fromasync', shared: 'fromasync' }) ); await resource.waitForAsyncAttributes(); - assert.deepStrictEqual( - resource.attributes, - { - 'sync': 'fromsync', - // async takes precedence - 'shared': 'fromasync', - 'async': 'fromasync', - }, - ); + assert.deepStrictEqual(resource.attributes, { + sync: 'fromsync', + // async takes precedence + shared: 'fromasync', + async: 'fromasync', + }); }); it('should merge async attributes when both resources have promises', async () => { const resource1 = new Resource( - {}, Promise.resolve({'promise1': 'promise1val', 'shared': 'promise1val'}), + {}, + Promise.resolve({ promise1: 'promise1val', shared: 'promise1val' }) ); const resource2 = new Resource( - {}, Promise.resolve({'promise2': 'promise2val', 'shared': 'promise2val'}), + {}, + Promise.resolve({ promise2: 'promise2val', shared: 'promise2val' }) ); // this one rejects - const resource3 = new Resource( - {}, Promise.reject(new Error('reject')), - ); + const resource3 = new Resource({}, Promise.reject(new Error('reject'))); const resource4 = new Resource( - {}, Promise.resolve({'promise4': 'promise4val', 'shared': 'promise4val'}), + {}, + Promise.resolve({ promise4: 'promise4val', shared: 'promise4val' }) ); - const merged = resource1.merge(resource2).merge(resource3).merge(resource4); + const merged = resource1 + .merge(resource2) + .merge(resource3) + .merge(resource4); await merged.waitForAsyncAttributes(); - assert.deepStrictEqual( - merged.attributes, - { - 'promise1': 'promise1val', - 'promise2': 'promise2val', - 'promise4': 'promise4val', - // same behavior as for synchronous attributes - 'shared': 'promise4val', - } - ); + assert.deepStrictEqual(merged.attributes, { + promise1: 'promise1val', + promise2: 'promise2val', + promise4: 'promise4val', + // same behavior as for synchronous attributes + shared: 'promise4val', + }); }); it('should log when promise rejects', async () => { @@ -187,9 +191,12 @@ describe('Resource', () => { // should be possible to catch failure with waitForAsyncAttributes() try { await assert.rejects( - new Resource({}, Promise.reject(new Error('rejected'))).waitForAsyncAttributes(), + new Resource( + {}, + Promise.reject(new Error('rejected')) + ).waitForAsyncAttributes() ); - } catch(err) {} + } catch (err) {} // will log after yielding to event loop await new Promise(resolve => setTimeout(resolve)); assert.ok(debugStub.calledOnce); diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index c1cba8ef0c..3b35f4b50f 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -28,9 +28,7 @@ describe('detectResourcesSync', () => { it('handles resource detectors which return Promise', async () => { const detector: Detector = { async detect() { - return new Resource( - { 'sync': 'fromsync', 'async': 'fromasync' }, - ); + return new Resource({ sync: 'fromsync', async: 'fromasync' }); }, }; const resource = detectResourcesSync({ @@ -38,15 +36,18 @@ describe('detectResourcesSync', () => { }); await resource.waitForAsyncAttributes(); - assert.deepStrictEqual(resource.attributes, {'sync': 'fromsync', 'async': 'fromasync'}); + assert.deepStrictEqual(resource.attributes, { + sync: 'fromsync', + async: 'fromasync', + }); }); it('handles resource detectors which return Resource with a promise inside', async () => { const detector: Detector = { detect() { return new Resource( - { 'sync': 'fromsync' }, - Promise.resolve({ 'async': 'fromasync'}) + { sync: 'fromsync' }, + Promise.resolve({ async: 'fromasync' }) ); }, }; @@ -55,9 +56,12 @@ describe('detectResourcesSync', () => { }); // before waiting, it should already have the sync resources - assert.deepStrictEqual(resource.attributes, {'sync': 'fromsync'}); + assert.deepStrictEqual(resource.attributes, { sync: 'fromsync' }); await resource.waitForAsyncAttributes(); - assert.deepStrictEqual(resource.attributes, {'sync': 'fromsync', 'async': 'fromasync'}); + assert.deepStrictEqual(resource.attributes, { + sync: 'fromsync', + async: 'fromasync', + }); }); describeNode('logging', () => { @@ -68,24 +72,22 @@ describe('detectResourcesSync', () => { class DetectorRejects implements Detector { detect() { return new Resource( - { 'sync': 'fromsync' }, - Promise.reject(new Error('reject')), + { sync: 'fromsync' }, + Promise.reject(new Error('reject')) ); } } class DetectorOk implements Detector { detect() { return new Resource( - { 'sync': 'fromsync' }, - Promise.resolve({'async': 'fromasync'}), + { sync: 'fromsync' }, + Promise.resolve({ async: 'fromasync' }) ); } } class DetectorAsync implements Detector { async detect() { - return new Resource( - { 'sync': 'fromsync', 'async': 'fromasync' }, - ); + return new Resource({ sync: 'fromsync', async: 'fromasync' }); } } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 27efbb7d7a..1e786342aa 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -163,9 +163,8 @@ export abstract class BatchSpanProcessorBase // outside the execution of this callback. const spans = this._finishedSpans.splice(0, this._maxExportBatchSize); - const doExport = () => this._exporter.export( - spans, - result => { + const doExport = () => + this._exporter.export(spans, result => { clearTimeout(timer); if (result.code === ExportResultCode.SUCCESS) { resolve(); @@ -175,17 +174,20 @@ export abstract class BatchSpanProcessorBase new Error('BatchSpanProcessor: span export failed') ); } - } - ); - const pendingResources = spans.map(span => span.resource) + }); + const pendingResources = spans + .map(span => span.resource) .filter(resource => !resource.asyncAttributesHaveResolved()); // Avoid scheduling a promise to make the behavior more predictable and easier to test if (pendingResources.length === 0) { doExport(); } else { - Promise.all(pendingResources.map(resource => resource.waitForAsyncAttributes())) - .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err)); + Promise.all( + pendingResources.map(resource => resource.waitForAsyncAttributes()) + ).then(doExport, err => + diag.debug('Error while resolving async portion of resource: ', err) + ); } }); }); diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index f1e27df091..4e15a6e867 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -57,25 +57,32 @@ export class SimpleSpanProcessor implements SpanProcessor { return; } - const doExport = () => internal._export(this._exporter, [span]).then((result: ExportResult) => { - if (result.code !== ExportResultCode.SUCCESS) { - globalErrorHandler( - result.error ?? - new Error( - `SimpleSpanProcessor: span export failed (status ${result})` - ) - ); - } - }).catch(error => { - globalErrorHandler(error); - }); + const doExport = () => + internal + ._export(this._exporter, [span]) + .then((result: ExportResult) => { + if (result.code !== ExportResultCode.SUCCESS) { + globalErrorHandler( + result.error ?? + new Error( + `SimpleSpanProcessor: span export failed (status ${result})` + ) + ); + } + }) + .catch(error => { + globalErrorHandler(error); + }); // Avoid scheduling a promise to make the behavior more predictable and easier to test if (span.resource.asyncAttributesHaveResolved()) { void doExport(); } else { - span.resource.waitForAsyncAttributes() - .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err)); + span.resource + .waitForAsyncAttributes() + .then(doExport, err => + diag.debug('Error while resolving async portion of resource: ', err) + ); } } diff --git a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts index fb971a0b8e..7b0de60c74 100644 --- a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -23,10 +23,7 @@ import { } from '@opentelemetry/core'; import { MetricReader } from './MetricReader'; import { PushMetricExporter } from './MetricExporter'; -import { - callWithTimeout, - TimeoutError -} from '../utils'; +import { callWithTimeout, TimeoutError } from '../utils'; import { diag } from '@opentelemetry/api'; export type PeriodicExportingMetricReaderOptions = { @@ -116,8 +113,11 @@ export class PeriodicExportingMetricReader extends MetricReader { if (resourceMetrics.resource.asyncAttributesHaveResolved()) { await doExport(); } else { - resourceMetrics.resource.waitForAsyncAttributes() - .then(await doExport, err => diag.debug('Error while resolving async portion of resource: ', err)); + resourceMetrics.resource + .waitForAsyncAttributes() + .then(await doExport, err => + diag.debug('Error while resolving async portion of resource: ', err) + ); } } From 60f763d1035593501e8b42cd4c8eff966354b7e6 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Mon, 5 Dec 2022 18:28:16 +0200 Subject: [PATCH 05/67] feat(sync-resource-detectors): fixed failing tests --- experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 4582a708ae..9b4ae834db 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -393,6 +393,7 @@ describe('Node SDK', () => { }); await sdk.detectResources(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assert.strictEqual(resource.attributes['customAttr'], 'someValue'); @@ -535,6 +536,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assertServiceResource(resource, { name: 'env-set-name', @@ -564,6 +566,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assertServiceResource(resource, { name: 'resource-env-set-name', From 876a978a9e660525b1dbd6b91f794e9629384431 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Mon, 12 Dec 2022 14:51:54 +0200 Subject: [PATCH 06/67] Update packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts removed unnecessary await Co-authored-by: Chengzhong Wu --- .../sdk-metrics/src/export/PeriodicExportingMetricReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts index 7b0de60c74..815fbc6bba 100644 --- a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -115,7 +115,7 @@ export class PeriodicExportingMetricReader extends MetricReader { } else { resourceMetrics.resource .waitForAsyncAttributes() - .then(await doExport, err => + .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err) ); } From e27b6b89be9d6ff34d8bfed223a974471fcfb017 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Mon, 19 Dec 2022 17:57:09 +0200 Subject: [PATCH 07/67] feat(sync-resource-detectors): _asyncAttributesPromise merged .catch with .then to always return an object even on rejection --- .../opentelemetry-resources/src/Resource.ts | 14 ++++++------- .../src/platform/browser/detect-resources.ts | 9 +-------- .../src/platform/node/detect-resources.ts | 9 +-------- .../test/detect-resources.test.ts | 20 ++++++------------- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 8ea8614268..720cbb24a8 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -68,13 +68,13 @@ export class Resource { this._attributes = Object.assign({}, this._attributes, asyncAttributes); this._asyncAttributesHaveResolved = true; return asyncAttributes; + }, + err => { + diag.debug("The resource's async promise rejected: %s", err); + this._asyncAttributesHaveResolved = true; + return {}; } ); - this._asyncAttributesPromise?.catch(err => { - diag.debug("The resource's async promise rejected: %s", err); - this._asyncAttributesHaveResolved = true; - return {}; - }); } get attributes(): ResourceAttributes { @@ -124,8 +124,8 @@ export class Resource { let mergedAsyncAttributesPromise: Promise | undefined; if (this._asyncAttributesPromise && other._asyncAttributesPromise) { mergedAsyncAttributesPromise = Promise.all([ - this._asyncAttributesPromise.catch(() => ({})), - other._asyncAttributesPromise.catch(() => ({})), + this._asyncAttributesPromise, + other._asyncAttributesPromise, ]).then(([thisAttributes, otherAttributes]) => { return Object.assign({}, thisAttributes, otherAttributes); }); diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts index b3c6f39607..81fd20e0a7 100644 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -76,14 +76,7 @@ export const detectResourcesSync = ( resource = resourceOrPromise; } - resource - .waitForAsyncAttributes() - .then(() => { - diag.debug(`${d.constructor.name} found resource.`, resource); - }) - .catch(e => { - diag.debug(`${d.constructor.name} failed: ${e.message}`); - }); + void resource.waitForAsyncAttributes(); return resource; } catch (e) { diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts index b9cdf07aef..b93c2b368c 100644 --- a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts @@ -80,14 +80,7 @@ export const detectResourcesSync = ( resource = resourceOrPromise; } - resource - .waitForAsyncAttributes() - .then(() => { - diag.debug(`${d.constructor.name} found resource.`, resource); - }) - .catch(e => { - diag.debug(`${d.constructor.name} failed: ${e.message}`); - }); + void resource.waitForAsyncAttributes(); return resource; } catch (e) { diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index 3b35f4b50f..374a45be2d 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -65,7 +65,7 @@ describe('detectResourcesSync', () => { }); describeNode('logging', () => { - it("logs when a detector's async attributes promise rejects or resolves", async () => { + it("logs when a detector's async attributes promise rejects", async () => { const debugStub = sinon.spy(diag, 'debug'); // use a class so it has a name @@ -85,24 +85,16 @@ describe('detectResourcesSync', () => { ); } } - class DetectorAsync implements Detector { - async detect() { - return new Resource({ sync: 'fromsync', async: 'fromasync' }); - } - } const resource = detectResourcesSync({ - detectors: [ - new DetectorRejects(), - new DetectorOk(), - new DetectorAsync(), - ], + detectors: [new DetectorRejects(), new DetectorOk()], }); await resource.waitForAsyncAttributes(); - assert.ok(debugStub.calledWithMatch('DetectorRejects failed: reject')); - assert.ok(debugStub.calledWithMatch('DetectorOk found resource.')); - assert.ok(debugStub.calledWithMatch('DetectorAsync found resource.')); + + assert.ok( + debugStub.calledWithMatch("The resource's async promise rejected: %s") + ); }); }); }); From ab9e481517330cc41951c83f6fc4d1ff924b95e9 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 11:07:27 +0200 Subject: [PATCH 08/67] feat(sync-resource-detectors): fixed tests --- .../opentelemetry-sdk-node/test/sdk.test.ts | 13 ++++++++++++- .../test/detect-resources.test.ts | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 9b4ae834db..8fa650aeaf 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -445,6 +445,14 @@ describe('Node SDK', () => { return call.args.some(callarg => arg === callarg); }); }; + const callArgsIncludes = ( + mockedFunction: sinon.SinonSpy, + arg: any + ): boolean => { + return mockedFunction.getCalls().some(call => { + return call.args.some(callarg => arg.includes(callarg)); + }); + }; const callArgsMatches = ( mockedFunction: sinon.SinonSpy, regex: RegExp @@ -475,7 +483,10 @@ describe('Node SDK', () => { // Test that the Env Detector successfully found its resource and populated it with the right values. assert.ok( - callArgsContains(mockedLoggerMethod, 'EnvDetector found resource.') + !callArgsIncludes( + mockedLoggerMethod, + "The resource's async promise rejected" + ) ); // Regex formatting accounts for whitespace variations in util.inspect output over different node versions assert.ok( diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index 374a45be2d..6b55b312a7 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -93,7 +93,7 @@ describe('detectResourcesSync', () => { await resource.waitForAsyncAttributes(); assert.ok( - debugStub.calledWithMatch("The resource's async promise rejected: %s") + debugStub.calledWithMatch("The resource's async promise rejected") ); }); }); From 6c29f9742365b2bb8f2d65445da845a840540fca Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 11:45:11 +0200 Subject: [PATCH 09/67] feat(sync-resource-detectors): using isPromiseLike --- .../src/platform/browser/detect-resources.ts | 3 ++- .../src/platform/node/detect-resources.ts | 3 ++- .../src/platform/utils.ts | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 packages/opentelemetry-resources/src/platform/utils.ts diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts index 81fd20e0a7..2ca57197fe 100644 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -17,6 +17,7 @@ import { Resource } from '../../Resource'; import { ResourceDetectionConfig } from '../../config'; import { diag } from '@opentelemetry/api'; +import { isPromiseLike } from '../utils'; /** * Runs all resource detectors and returns the results merged into a single Resource. Promise @@ -65,7 +66,7 @@ export const detectResourcesSync = ( try { const resourceOrPromise = d.detect(internalConfig); let resource: Resource; - if (resourceOrPromise instanceof Promise) { + if (isPromiseLike(resourceOrPromise)) { const createPromise = async () => { const resolved = await resourceOrPromise; await resolved.waitForAsyncAttributes(); diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts index b93c2b368c..5733e5ca41 100644 --- a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts @@ -18,6 +18,7 @@ import { Resource } from '../../Resource'; import { ResourceDetectionConfig } from '../../config'; import { diag } from '@opentelemetry/api'; import * as util from 'util'; +import { isPromiseLike } from '../utils'; /** * Runs all resource detectors and returns the results merged into a single Resource. Promise @@ -69,7 +70,7 @@ export const detectResourcesSync = ( try { const resourceOrPromise = d.detect(internalConfig); let resource: Resource; - if (resourceOrPromise instanceof Promise) { + if (isPromiseLike(resourceOrPromise)) { const createPromise = async () => { const resolved = await resourceOrPromise; await resolved.waitForAsyncAttributes(); diff --git a/packages/opentelemetry-resources/src/platform/utils.ts b/packages/opentelemetry-resources/src/platform/utils.ts new file mode 100644 index 0000000000..73d81040b3 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/utils.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export const isPromiseLike = (val: any): val is PromiseLike => { + return ( + val !== null && typeof val === 'object' && typeof val.then === 'function' + ); +}; From bb89113fc03a6d813cb26a6e89b3d570c1a6662b Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 11:51:13 +0200 Subject: [PATCH 10/67] feat(sync-resource-detectors): documented waitForAsyncAttributes returns promise that is never rejected --- packages/opentelemetry-resources/src/Resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 720cbb24a8..1ff51c6e70 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -93,7 +93,7 @@ export class Resource { } /** - * Returns a promise that resolves when all async attributes have finished being added to + * Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to * this Resource's attributes. This is useful in exporters to block until resource detection * has finished. */ From cc16c50e245a0729df001bc98fd99ee91561cefa Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 12:12:33 +0200 Subject: [PATCH 11/67] feat(sync-resource-detectors): updated asyncAttributesPromise nullish check --- packages/opentelemetry-resources/src/Resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 1ff51c6e70..a7e18fcd20 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -62,7 +62,7 @@ export class Resource { asyncAttributesPromise?: Promise ) { this._attributes = attributes; - this._asyncAttributesHaveResolved = asyncAttributesPromise === undefined; + this._asyncAttributesHaveResolved = asyncAttributesPromise == null; this._asyncAttributesPromise = asyncAttributesPromise?.then( asyncAttributes => { this._attributes = Object.assign({}, this._attributes, asyncAttributes); From 0ad492ae80fd5d12e24eb9813f50752bd2dced12 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 12:19:45 +0200 Subject: [PATCH 12/67] feat(sync-resource-detectors): using globalErrorHandler instead of diag --- .../src/export/SimpleSpanProcessor.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 4e15a6e867..ed62be5b34 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Context, diag, TraceFlags } from '@opentelemetry/api'; +import { Context, TraceFlags } from '@opentelemetry/api'; import { internal, ExportResultCode, @@ -80,9 +80,7 @@ export class SimpleSpanProcessor implements SpanProcessor { } else { span.resource .waitForAsyncAttributes() - .then(doExport, err => - diag.debug('Error while resolving async portion of resource: ', err) - ); + .then(doExport, err => globalErrorHandler(err)); } } From 5dc4d3124166fd247cc857329459270181ed817b Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 12:56:24 +0200 Subject: [PATCH 13/67] feat(sync-resource-detectors): removed code duplication for detect-resources.ts --- .../{platform/node => }/detect-resources.ts | 6 +- packages/opentelemetry-resources/src/index.ts | 1 + .../src/platform/browser/detect-resources.ts | 93 ------------------- .../src/platform/browser/index.ts | 1 - .../src/platform/node/index.ts | 1 - .../src/{platform => }/utils.ts | 0 6 files changed, 4 insertions(+), 98 deletions(-) rename packages/opentelemetry-resources/src/{platform/node => }/detect-resources.ts (96%) delete mode 100644 packages/opentelemetry-resources/src/platform/browser/detect-resources.ts rename packages/opentelemetry-resources/src/{platform => }/utils.ts (100%) diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts similarity index 96% rename from packages/opentelemetry-resources/src/platform/node/detect-resources.ts rename to packages/opentelemetry-resources/src/detect-resources.ts index 5733e5ca41..6cfd24b0b9 100644 --- a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Resource } from '../../Resource'; -import { ResourceDetectionConfig } from '../../config'; +import { Resource } from './Resource'; +import { ResourceDetectionConfig } from './config'; import { diag } from '@opentelemetry/api'; import * as util from 'util'; -import { isPromiseLike } from '../utils'; +import { isPromiseLike } from './utils'; /** * Runs all resource detectors and returns the results merged into a single Resource. Promise diff --git a/packages/opentelemetry-resources/src/index.ts b/packages/opentelemetry-resources/src/index.ts index 4cff4181e3..f1c9ae98e6 100644 --- a/packages/opentelemetry-resources/src/index.ts +++ b/packages/opentelemetry-resources/src/index.ts @@ -19,3 +19,4 @@ export * from './platform'; export * from './types'; export * from './config'; export * from './detectors'; +export * from './detect-resources'; diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts deleted file mode 100644 index 2ca57197fe..0000000000 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Resource } from '../../Resource'; -import { ResourceDetectionConfig } from '../../config'; -import { diag } from '@opentelemetry/api'; -import { isPromiseLike } from '../utils'; - -/** - * Runs all resource detectors and returns the results merged into a single Resource. Promise - * does not resolve until all the underlying detectors have resolved, unlike - * detectResourcesSync. - * - * @deprecated use detectResourceSync() instead. - * @param config Configuration for resource detection - */ -export const detectResources = async ( - config: ResourceDetectionConfig = {} -): Promise => { - const internalConfig: ResourceDetectionConfig = Object.assign(config); - - const resources: Resource[] = await Promise.all( - (internalConfig.detectors || []).map(async d => { - try { - const resource = await d.detect(internalConfig); - diag.debug(`${d.constructor.name} found resource.`, resource); - return resource; - } catch (e) { - diag.debug(`${d.constructor.name} failed: ${e.message}`); - return Resource.empty(); - } - }) - ); - - return resources.reduce( - (acc, resource) => acc.merge(resource), - Resource.empty() - ); -}; - -/** - * Runs all resource detectors synchronously, merging their results. Any asynchronous - * attributes will be merged together in-order after they resolve. - * - * @param config Configuration for resource detection - */ -export const detectResourcesSync = ( - config: ResourceDetectionConfig = {} -): Resource => { - const internalConfig: ResourceDetectionConfig = Object.assign(config); - - const resources: Resource[] = (internalConfig.detectors ?? []).map(d => { - try { - const resourceOrPromise = d.detect(internalConfig); - let resource: Resource; - if (isPromiseLike(resourceOrPromise)) { - const createPromise = async () => { - const resolved = await resourceOrPromise; - await resolved.waitForAsyncAttributes(); - return resolved.attributes; - }; - resource = new Resource({}, createPromise()); - } else { - resource = resourceOrPromise; - } - - void resource.waitForAsyncAttributes(); - - return resource; - } catch (e) { - diag.debug(`${d.constructor.name} failed: ${e.message}`); - return Resource.empty(); - } - }); - - return resources.reduce( - (acc, resource) => acc.merge(resource), - Resource.empty() - ); -}; diff --git a/packages/opentelemetry-resources/src/platform/browser/index.ts b/packages/opentelemetry-resources/src/platform/browser/index.ts index 6f3af40a45..51034cc577 100644 --- a/packages/opentelemetry-resources/src/platform/browser/index.ts +++ b/packages/opentelemetry-resources/src/platform/browser/index.ts @@ -15,6 +15,5 @@ */ export * from './default-service-name'; -export * from './detect-resources'; export * from './HostDetector'; export * from './OSDetector'; diff --git a/packages/opentelemetry-resources/src/platform/node/index.ts b/packages/opentelemetry-resources/src/platform/node/index.ts index 6f3af40a45..51034cc577 100644 --- a/packages/opentelemetry-resources/src/platform/node/index.ts +++ b/packages/opentelemetry-resources/src/platform/node/index.ts @@ -15,6 +15,5 @@ */ export * from './default-service-name'; -export * from './detect-resources'; export * from './HostDetector'; export * from './OSDetector'; diff --git a/packages/opentelemetry-resources/src/platform/utils.ts b/packages/opentelemetry-resources/src/utils.ts similarity index 100% rename from packages/opentelemetry-resources/src/platform/utils.ts rename to packages/opentelemetry-resources/src/utils.ts From 3ab49fb129583fc62afdb80ecb80e3bbfe746bd4 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 13:00:34 +0200 Subject: [PATCH 14/67] feat(sync-resource-detectors): fixed test --- packages/opentelemetry-resources/test/detect-resources.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index 6b55b312a7..60e9d2c413 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -28,7 +28,7 @@ describe('detectResourcesSync', () => { it('handles resource detectors which return Promise', async () => { const detector: Detector = { async detect() { - return new Resource({ sync: 'fromsync', async: 'fromasync' }); + return new Resource({ sync: 'fromsync' }); }, }; const resource = detectResourcesSync({ @@ -38,7 +38,6 @@ describe('detectResourcesSync', () => { await resource.waitForAsyncAttributes(); assert.deepStrictEqual(resource.attributes, { sync: 'fromsync', - async: 'fromasync', }); }); From 721bf45ffa7140d051daae079fa5bbef07ab1130 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 14:31:05 +0200 Subject: [PATCH 15/67] feat(sync-resource-detectors): updated README.md to use sync start() --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c4561df9c0..8c227c6439 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,7 @@ const sdk = new opentelemetry.NodeSDK({ // initialize the SDK and register with the OpenTelemetry API // this enables the API to record telemetry -sdk.start() - .then(() => console.log('Tracing initialized')) - .catch((error) => console.log('Error initializing tracing', error)); +sdk.start(); // gracefully shut down the SDK on process exit process.on('SIGTERM', () => { From 508739ff0ee627aae1f0e6db08f5f54d27aac50f Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 19:21:57 +0200 Subject: [PATCH 16/67] feat(sync-resource-detectors): updated test with a promise that resolves after 1 second --- .../opentelemetry-resources/test/Resource.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 8742b36521..51546028fa 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -21,6 +21,7 @@ import { Resource } from '../src'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeBrowser, describeNode } from './util'; import { diag } from '@opentelemetry/api'; +import { ResourceAttributes } from '../build/esnext'; describe('Resource', () => { const resource1 = new Resource({ @@ -142,9 +143,17 @@ describe('Resource', () => { }); it('should merge async attributes into sync attributes once resolved', async () => { + //async attributes that resolve after 1 second + const asyncAttributes = new Promise(resolve => { + setTimeout( + () => resolve({ async: 'fromasync', shared: 'fromasync' }), + 1000 + ); + }); + const resource = new Resource( { sync: 'fromsync', shared: 'fromsync' }, - Promise.resolve({ async: 'fromasync', shared: 'fromasync' }) + asyncAttributes ); await resource.waitForAsyncAttributes(); From 7d14a7d4f09c0858bee8eca4f49f3b58ce608329 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 19:25:47 +0200 Subject: [PATCH 17/67] feat(sync-resource-detectors): removed unnecessary attributes getter --- packages/opentelemetry-resources/src/Resource.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index a7e18fcd20..fd08a7f432 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -26,7 +26,6 @@ import { defaultServiceName } from './platform'; */ export class Resource { static readonly EMPTY = new Resource({}); - private _attributes: ResourceAttributes; private _asyncAttributesPromise: Promise | undefined; private _asyncAttributesHaveResolved: boolean; @@ -58,14 +57,13 @@ export class Resource { * information about the entity as numbers, strings or booleans * TODO: Consider to add check/validation on attributes. */ - attributes: ResourceAttributes, + public attributes: ResourceAttributes, asyncAttributesPromise?: Promise ) { - this._attributes = attributes; this._asyncAttributesHaveResolved = asyncAttributesPromise == null; this._asyncAttributesPromise = asyncAttributesPromise?.then( asyncAttributes => { - this._attributes = Object.assign({}, this._attributes, asyncAttributes); + this.attributes = Object.assign({}, this.attributes, asyncAttributes); this._asyncAttributesHaveResolved = true; return asyncAttributes; }, @@ -77,10 +75,6 @@ export class Resource { ); } - get attributes(): ResourceAttributes { - return this._attributes; - } - /** * Check if async attributes have resolved. This is useful to avoid awaiting * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. From da7adc9e4f60919b6706e95ba5029c9e8b6970cb Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 19:33:06 +0200 Subject: [PATCH 18/67] feat(sync-resource-detectors): small refactor --- packages/opentelemetry-resources/test/Resource.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 51546028fa..2ed91a6694 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -17,11 +17,10 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import { SDK_INFO } from '@opentelemetry/core'; -import { Resource } from '../src'; +import { Resource, ResourceAttributes } from '../src'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeBrowser, describeNode } from './util'; import { diag } from '@opentelemetry/api'; -import { ResourceAttributes } from '../build/esnext'; describe('Resource', () => { const resource1 = new Resource({ From 16d223a8b22c275aac91bd31acf4ea58c4fa0252 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 19:38:33 +0200 Subject: [PATCH 19/67] feat(sync-resource-detectors): removed internalConfig --- .../opentelemetry-resources/src/detect-resources.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index 6cfd24b0b9..eab852296f 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -31,12 +31,10 @@ import { isPromiseLike } from './utils'; export const detectResources = async ( config: ResourceDetectionConfig = {} ): Promise => { - const internalConfig: ResourceDetectionConfig = Object.assign(config); - const resources: Resource[] = await Promise.all( - (internalConfig.detectors || []).map(async d => { + (config.detectors || []).map(async d => { try { - const resource = await d.detect(internalConfig); + const resource = await d.detect(config); diag.debug(`${d.constructor.name} found resource.`, resource); return resource; } catch (e) { @@ -64,11 +62,9 @@ export const detectResources = async ( export const detectResourcesSync = ( config: ResourceDetectionConfig = {} ): Resource => { - const internalConfig: ResourceDetectionConfig = Object.assign(config); - - const resources: Resource[] = (internalConfig.detectors ?? []).map(d => { + const resources: Resource[] = (config.detectors ?? []).map(d => { try { - const resourceOrPromise = d.detect(internalConfig); + const resourceOrPromise = d.detect(config); let resource: Resource; if (isPromiseLike(resourceOrPromise)) { const createPromise = async () => { From 881645e1cbb86793f00d700e5acc198059ec8309 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 20 Dec 2022 20:05:50 +0200 Subject: [PATCH 20/67] feat(sync-resource-detectors): updated merge() to check if promise has not resolved to prevent multiple merge of the same async attributes --- packages/opentelemetry-resources/src/Resource.ts | 16 ++++++++++++++-- packages/opentelemetry-resources/src/utils.ts | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index fd08a7f432..75282ec65c 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -19,6 +19,7 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { SDK_INFO } from '@opentelemetry/core'; import { ResourceAttributes } from './types'; import { defaultServiceName } from './platform'; +import { getAsyncAttributesIfNotResolved } from './utils'; /** * A Resource describes the entity for which a signals (metrics or trace) are @@ -116,7 +117,18 @@ export class Resource { ); let mergedAsyncAttributesPromise: Promise | undefined; - if (this._asyncAttributesPromise && other._asyncAttributesPromise) { + + const thisAsyncAttributesIfNotResolved = getAsyncAttributesIfNotResolved( + this._asyncAttributesHaveResolved, + this._asyncAttributesPromise + ); + + const otherAsyncAttributesIfNotResolved = getAsyncAttributesIfNotResolved( + other._asyncAttributesHaveResolved, + other._asyncAttributesPromise + ); + + if (thisAsyncAttributesIfNotResolved && otherAsyncAttributesIfNotResolved) { mergedAsyncAttributesPromise = Promise.all([ this._asyncAttributesPromise, other._asyncAttributesPromise, @@ -125,7 +137,7 @@ export class Resource { }); } else { mergedAsyncAttributesPromise = - this._asyncAttributesPromise ?? other._asyncAttributesPromise; + thisAsyncAttributesIfNotResolved ?? otherAsyncAttributesIfNotResolved; } return new Resource(mergedAttributes, mergedAsyncAttributesPromise); diff --git a/packages/opentelemetry-resources/src/utils.ts b/packages/opentelemetry-resources/src/utils.ts index 73d81040b3..ba19eba837 100644 --- a/packages/opentelemetry-resources/src/utils.ts +++ b/packages/opentelemetry-resources/src/utils.ts @@ -14,8 +14,20 @@ * limitations under the License. */ +import { Attributes } from '@opentelemetry/api'; + export const isPromiseLike = (val: any): val is PromiseLike => { return ( val !== null && typeof val === 'object' && typeof val.then === 'function' ); }; + +export const getAsyncAttributesIfNotResolved = ( + asyncAttributesHaveResolved: boolean, + asyncAttributesPromise?: Promise +): Promise | undefined => { + if (!asyncAttributesHaveResolved) { + return asyncAttributesPromise; + } + return undefined; +}; From 692ef0efa791b43e44885cd1c7fecbf554c860a0 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 22 Dec 2022 17:29:24 +0200 Subject: [PATCH 21/67] feat(sync-resource-detectors): added more tests for merge() --- .../opentelemetry-resources/src/Resource.ts | 2 +- .../test/Resource.test.ts | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 75282ec65c..9822a7a83a 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -109,7 +109,7 @@ export class Resource { merge(other: Resource | null): Resource { if (!other) return this; - // SpanAttributes from resource overwrite attributes from other resource. + // SpanAttributes from other resource overwrite attributes from this resource. const mergedAttributes = Object.assign( {}, this.attributes, diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 2ed91a6694..7c7ad7cbf9 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -194,6 +194,52 @@ describe('Resource', () => { }); }); + it('should merge async attributes correctly when resource1 fulfils after resource2', async () => { + const resource1 = new Resource( + {}, + Promise.resolve({ promise1: 'promise1val', shared: 'promise1val' }) + ); + + const resource2 = new Resource({ + promise2: 'promise2val', + shared: 'promise2val', + }); + + const merged = resource1.merge(resource2); + await merged.waitForAsyncAttributes(); + assert.deepStrictEqual(merged.attributes, { + promise1: 'promise1val', + promise2: 'promise2val', + // same behavior as for synchronous attributes + shared: 'promise2val', + }); + }); + + it('should merge async attributes correctly when resource2 fulfils after resource1', async () => { + const resource1 = new Resource( + { shared: 'promise1val' }, + Promise.resolve({ promise1: 'promise1val' }) + ); + + //async attributes that resolve after 1 second + const asyncAttributes = new Promise(resolve => { + setTimeout( + () => resolve({ promise2: 'promise2val', shared: 'promise2val' }), + 1500 + ); + }); + const resource2 = new Resource({}, asyncAttributes); + + const merged = resource1.merge(resource2); + await merged.waitForAsyncAttributes(); + assert.deepStrictEqual(merged.attributes, { + promise1: 'promise1val', + promise2: 'promise2val', + // same behavior as for synchronous attributes + shared: 'promise2val', + }); + }); + it('should log when promise rejects', async () => { const debugStub = sinon.spy(diag, 'debug'); // should be possible to catch failure with waitForAsyncAttributes() From 61964a58d654a6d1902a34e1d74910501cfac2b5 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 3 Jan 2023 11:50:08 +0200 Subject: [PATCH 22/67] feat(sync-resource-detectors): fixed merge() where attribute overriding wasnt working properly --- .../opentelemetry-resources/src/Resource.ts | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 9822a7a83a..ebf50e3561 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -19,7 +19,6 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { SDK_INFO } from '@opentelemetry/core'; import { ResourceAttributes } from './types'; import { defaultServiceName } from './platform'; -import { getAsyncAttributesIfNotResolved } from './utils'; /** * A Resource describes the entity for which a signals (metrics or trace) are @@ -27,6 +26,7 @@ import { getAsyncAttributesIfNotResolved } from './utils'; */ export class Resource { static readonly EMPTY = new Resource({}); + private _syncAttributes: ResourceAttributes; private _asyncAttributesPromise: Promise | undefined; private _asyncAttributesHaveResolved: boolean; @@ -62,6 +62,7 @@ export class Resource { asyncAttributesPromise?: Promise ) { this._asyncAttributesHaveResolved = asyncAttributesPromise == null; + this._syncAttributes = attributes; this._asyncAttributesPromise = asyncAttributesPromise?.then( asyncAttributes => { this.attributes = Object.assign({}, this.attributes, asyncAttributes); @@ -110,36 +111,27 @@ export class Resource { if (!other) return this; // SpanAttributes from other resource overwrite attributes from this resource. - const mergedAttributes = Object.assign( - {}, - this.attributes, - other.attributes - ); - - let mergedAsyncAttributesPromise: Promise | undefined; - - const thisAsyncAttributesIfNotResolved = getAsyncAttributesIfNotResolved( - this._asyncAttributesHaveResolved, - this._asyncAttributesPromise - ); + const mergedSyncAttributes = { + ...this._syncAttributes, + ...other._syncAttributes, + }; - const otherAsyncAttributesIfNotResolved = getAsyncAttributesIfNotResolved( - other._asyncAttributesHaveResolved, - other._asyncAttributesPromise - ); - - if (thisAsyncAttributesIfNotResolved && otherAsyncAttributesIfNotResolved) { - mergedAsyncAttributesPromise = Promise.all([ - this._asyncAttributesPromise, - other._asyncAttributesPromise, - ]).then(([thisAttributes, otherAttributes]) => { - return Object.assign({}, thisAttributes, otherAttributes); - }); - } else { - mergedAsyncAttributesPromise = - thisAsyncAttributesIfNotResolved ?? otherAsyncAttributesIfNotResolved; + if (!this._asyncAttributesPromise && !other._asyncAttributesPromise) { + return new Resource(mergedSyncAttributes); } - return new Resource(mergedAttributes, mergedAsyncAttributesPromise); + const mergedAttributesPromise = Promise.all([ + this._asyncAttributesPromise, + other._asyncAttributesPromise, + ]).then(([thisAsyncAttributes, otherAsyncAttributes]) => { + return { + ...this._syncAttributes, + ...thisAsyncAttributes, + ...other._syncAttributes, + ...otherAsyncAttributes, + }; + }); + + return new Resource(mergedSyncAttributes, mergedAttributesPromise); } } From c56bff469a7b5e655b6032439b5f96527094f16c Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 3 Jan 2023 12:26:32 +0200 Subject: [PATCH 23/67] feat(sync-resource-detectors): updated upgrade guidelines --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8c227c6439..931bc8f046 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,10 @@ These instrumentations are hosted at Date: Tue, 3 Jan 2023 14:10:47 +0200 Subject: [PATCH 24/67] feat(sync-resource-detectors): fixed lint --- experimental/packages/opentelemetry-sdk-node/src/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 5405008ada..bedd8307e9 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -187,7 +187,7 @@ export class NodeSDK { if (this._disabled) { return; } - + const internalConfig: ResourceDetectionConfig = { detectors: this._resourceDetectors, }; From 5baca81359429bd703019c4632ee4c2f2f6c5b14 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 4 Jan 2023 13:37:50 +0200 Subject: [PATCH 25/67] feat(sync-resource-detectors): removed unnecessary await --- experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 010fec1df4..781ac9f35b 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -391,7 +391,7 @@ describe('Node SDK', () => { envDetector, ], }); - await sdk.detectResources(); + sdk.detectResources(); const resource = sdk['_resource']; await resource.waitForAsyncAttributes(); @@ -666,7 +666,7 @@ describe('Node SDK', () => { envDetector, ], }); - await sdk.detectResources(); + sdk.detectResources(); const resource = sdk['_resource']; assert.deepStrictEqual(resource, Resource.empty()); From 270d67e2d784bef71c867cd438449700d5cfea49 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 4 Jan 2023 13:41:54 +0200 Subject: [PATCH 26/67] feat(sync-resource-detectors): updated upgrade guidelines --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 931bc8f046..07a3691c8a 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ These instrumentations are hosted at Date: Wed, 4 Jan 2023 14:46:49 +0200 Subject: [PATCH 27/67] feat(sync-resource-detectors): updated outdated test --- .../test/Resource.test.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 7c7ad7cbf9..fb31c224f9 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -242,15 +242,14 @@ describe('Resource', () => { it('should log when promise rejects', async () => { const debugStub = sinon.spy(diag, 'debug'); - // should be possible to catch failure with waitForAsyncAttributes() - try { - await assert.rejects( - new Resource( - {}, - Promise.reject(new Error('rejected')) - ).waitForAsyncAttributes() - ); - } catch (err) {} + + const resource = new Resource({}, Promise.reject(new Error('rejected'))); + await resource.waitForAsyncAttributes(); + + assert.ok( + debugStub.calledWithMatch("The resource's async promise rejected") + ); + // will log after yielding to event loop await new Promise(resolve => setTimeout(resolve)); assert.ok(debugStub.calledOnce); From 551ed5ddd9faf3bd773ab4d82d7060777263768c Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 4 Jan 2023 15:42:24 +0200 Subject: [PATCH 28/67] feat(sync-resource-detectors): use globalErrorHandler in BatchSpanProcessorBase.ts --- .../src/export/BatchSpanProcessorBase.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 1e786342aa..d4d5334992 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -185,9 +185,7 @@ export abstract class BatchSpanProcessorBase } else { Promise.all( pendingResources.map(resource => resource.waitForAsyncAttributes()) - ).then(doExport, err => - diag.debug('Error while resolving async portion of resource: ', err) - ); + ).then(doExport, err => globalErrorHandler(err)); } }); }); From c635de177ffd9d811c4a8bf7d5daabd3c76d1755 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 4 Jan 2023 15:44:26 +0200 Subject: [PATCH 29/67] feat(sync-resource-detectors): remove unused code --- packages/opentelemetry-resources/src/utils.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/opentelemetry-resources/src/utils.ts b/packages/opentelemetry-resources/src/utils.ts index ba19eba837..73d81040b3 100644 --- a/packages/opentelemetry-resources/src/utils.ts +++ b/packages/opentelemetry-resources/src/utils.ts @@ -14,20 +14,8 @@ * limitations under the License. */ -import { Attributes } from '@opentelemetry/api'; - export const isPromiseLike = (val: any): val is PromiseLike => { return ( val !== null && typeof val === 'object' && typeof val.then === 'function' ); }; - -export const getAsyncAttributesIfNotResolved = ( - asyncAttributesHaveResolved: boolean, - asyncAttributesPromise?: Promise -): Promise | undefined => { - if (!asyncAttributesHaveResolved) { - return asyncAttributesPromise; - } - return undefined; -}; From 94bfcea9e8a3b6ce1cea6053204977da135dc801 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 4 Jan 2023 17:36:50 +0200 Subject: [PATCH 30/67] feat(sync-resource-detectors): resolve _flushOneBatch() if it fails before exporting --- .../src/export/BatchSpanProcessorBase.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index d4d5334992..1c43c78ada 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -185,7 +185,10 @@ export abstract class BatchSpanProcessorBase } else { Promise.all( pendingResources.map(resource => resource.waitForAsyncAttributes()) - ).then(doExport, err => globalErrorHandler(err)); + ).then(doExport, err => { + globalErrorHandler(err); + resolve(err); + }); } }); }); From e719e90b846b615f185bd437f61558ea117072fa Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 4 Jan 2023 17:43:01 +0200 Subject: [PATCH 31/67] feat(sync-resource-detectors): reduced timeout to avoid test delays for no reason --- packages/opentelemetry-resources/test/Resource.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index fb31c224f9..2b78a23362 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -146,7 +146,7 @@ describe('Resource', () => { const asyncAttributes = new Promise(resolve => { setTimeout( () => resolve({ async: 'fromasync', shared: 'fromasync' }), - 1000 + 1 ); }); @@ -225,7 +225,7 @@ describe('Resource', () => { const asyncAttributes = new Promise(resolve => { setTimeout( () => resolve({ promise2: 'promise2val', shared: 'promise2val' }), - 1500 + 1 ); }); const resource2 = new Resource({}, asyncAttributes); From c0758b33b6e60bfb0c67f9a39b560b58728c758a Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 5 Jan 2023 17:18:06 +0200 Subject: [PATCH 32/67] feat(sync-resource-detectors): removed redundant call to waitForAsyncAttributes() --- packages/opentelemetry-resources/src/detect-resources.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index eab852296f..7ec44a4c82 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -68,9 +68,8 @@ export const detectResourcesSync = ( let resource: Resource; if (isPromiseLike(resourceOrPromise)) { const createPromise = async () => { - const resolved = await resourceOrPromise; - await resolved.waitForAsyncAttributes(); - return resolved.attributes; + const resolvedResource = await resourceOrPromise; + return resolvedResource.attributes; }; resource = new Resource({}, createPromise()); } else { From 19817f0863834379e8cd4773bcdc2db63edd34e8 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Mon, 9 Jan 2023 18:29:09 +0200 Subject: [PATCH 33/67] feat(sync-resource-detectors): await unresolved resources in forceFlush() SimpleSpanProcessor.ts --- .../test/Resource.test.ts | 4 +-- .../src/export/SimpleSpanProcessor.ts | 18 +++++++--- .../common/export/SimpleSpanProcessor.test.ts | 36 +++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 2b78a23362..12188a6112 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -142,7 +142,7 @@ describe('Resource', () => { }); it('should merge async attributes into sync attributes once resolved', async () => { - //async attributes that resolve after 1 second + //async attributes that resolve after 1 ms const asyncAttributes = new Promise(resolve => { setTimeout( () => resolve({ async: 'fromasync', shared: 'fromasync' }), @@ -221,7 +221,7 @@ describe('Resource', () => { Promise.resolve({ promise1: 'promise1val' }) ); - //async attributes that resolve after 1 second + //async attributes that resolve after 1 ms const asyncAttributes = new Promise(resolve => { setTimeout( () => resolve({ promise2: 'promise2val', shared: 'promise2val' }), diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index ed62be5b34..ab73d89910 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -35,18 +35,28 @@ import { SpanExporter } from './SpanExporter'; */ export class SimpleSpanProcessor implements SpanProcessor { private _shutdownOnce: BindOnceFuture; + private _unresolvedResources: Set>; constructor(private readonly _exporter: SpanExporter) { this._shutdownOnce = new BindOnceFuture(this._shutdown, this); + this._unresolvedResources = new Set>(); } - forceFlush(): Promise { - // do nothing as all spans are being exported without waiting + async forceFlush(): Promise { + // await unresolved resources before resolving + for (const unresolvedResource of this._unresolvedResources) { + await unresolvedResource; + } + return Promise.resolve(); } - // does nothing. - onStart(_span: Span, _parentContext: Context): void {} + onStart(_span: Span, _parentContext: Context): void { + // store the resource's unresolved promise + if (!_span.resource.asyncAttributesHaveResolved()) { + this._unresolvedResources.add(_span.resource.waitForAsyncAttributes()); + } + } onEnd(span: ReadableSpan): void { if (this._shutdownOnce.isCalled) { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts index fda60f500e..2a6191799f 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts @@ -36,6 +36,7 @@ import { } from '../../../src'; import { TestStackContextManager } from './TestStackContextManager'; import { TestTracingSpanExporter } from './TestTracingSpanExporter'; +import { Resource, ResourceAttributes } from '@opentelemetry/resources'; describe('SimpleSpanProcessor', () => { let provider: BasicTracerProvider; @@ -149,6 +150,41 @@ describe('SimpleSpanProcessor', () => { }); describe('force flush', () => { + it('should await unresolved resources', async () => { + const processor = new SimpleSpanProcessor(exporter); + const providerWithAsyncResource = new BasicTracerProvider({ + resource: new Resource( + {}, + new Promise(resolve => { + setTimeout(() => resolve({ async: 'fromasync' }), 1); + }) + ), + }); + const spanContext: SpanContext = { + traceId: 'a3cda95b652f4a1592b449d5929fda1b', + spanId: '5e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const span = new Span( + providerWithAsyncResource.getTracer('default'), + ROOT_CONTEXT, + 'span-name', + spanContext, + SpanKind.CLIENT + ); + processor.onStart(span, ROOT_CONTEXT); + assert.strictEqual(exporter.getFinishedSpans().length, 0); + + processor.onEnd(span); + assert.strictEqual(exporter.getFinishedSpans().length, 0); + + await processor.forceFlush(); + assert.strictEqual(exporter.getFinishedSpans().length, 1); + + await processor.shutdown(); + assert.strictEqual(exporter.getFinishedSpans().length, 0); + }); + describe('when flushing complete', () => { it('should call an async callback', done => { const processor = new SimpleSpanProcessor(exporter); From 1af7935bc7e181f3d78af234d8c18adcb98c7273 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 10 Jan 2023 15:18:01 +0200 Subject: [PATCH 34/67] feat(sync-resource-detectors): fixed test that should log detected resources --- .../opentelemetry-sdk-node/test/sdk.test.ts | 16 +++------------- .../src/detect-resources.ts | 6 +++++- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 781ac9f35b..0c532356b6 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -445,14 +445,6 @@ describe('Node SDK', () => { return call.args.some(callarg => arg === callarg); }); }; - const callArgsIncludes = ( - mockedFunction: sinon.SinonSpy, - arg: any - ): boolean => { - return mockedFunction.getCalls().some(call => { - return call.args.some(callarg => arg.includes(callarg)); - }); - }; const callArgsMatches = ( mockedFunction: sinon.SinonSpy, regex: RegExp @@ -470,6 +462,7 @@ describe('Node SDK', () => { // This test depends on the env detector to be functioning as intended const mockedLoggerMethod = Sinon.fake(); const mockedVerboseLoggerMethod = Sinon.fake(); + diag.setLogger( { debug: mockedLoggerMethod, @@ -479,14 +472,11 @@ describe('Node SDK', () => { ); sdk.detectResources(); - await sdk['_resource'].waitForAsyncAttributes().catch(() => {}); + await sdk['_resource'].waitForAsyncAttributes(); // Test that the Env Detector successfully found its resource and populated it with the right values. assert.ok( - !callArgsIncludes( - mockedLoggerMethod, - "The resource's async promise rejected" - ) + callArgsContains(mockedLoggerMethod, 'EnvDetector found resource.') ); // Regex formatting accounts for whitespace variations in util.inspect output over different node versions assert.ok( diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index 7ec44a4c82..cff4a230b6 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -76,7 +76,11 @@ export const detectResourcesSync = ( resource = resourceOrPromise; } - void resource.waitForAsyncAttributes(); + void resource + .waitForAsyncAttributes() + .then(() => + diag.debug(`${d.constructor.name} found resource.`, resource) + ); return resource; } catch (e) { From 76ce6f5c056f71ab85b08215fa9ce538729fda63 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 11 Jan 2023 18:55:55 +0200 Subject: [PATCH 35/67] feat(sync-resource-detectors): fixed envDetector test --- experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 0c532356b6..0082f1ce97 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -553,6 +553,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assertServiceResource(resource, { name: 'config-set-name', From cc24162036d4be6ff9e6ea7e99182014712b9955 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 11 Jan 2023 18:58:32 +0200 Subject: [PATCH 36/67] feat(sync-resource-detectors): fixed envDetector test --- experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 0082f1ce97..1aa3b2996e 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -585,6 +585,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assertServiceResource(resource, { name: 'config-set-name', From 754810d9ef960f6b25dbba44c06a4f1197550fe8 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 11 Jan 2023 19:00:33 +0200 Subject: [PATCH 37/67] feat(sync-resource-detectors): fixed test --- experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 1aa3b2996e..af87fff167 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -660,6 +660,7 @@ describe('Node SDK', () => { }); sdk.detectResources(); const resource = sdk['_resource']; + await resource.waitForAsyncAttributes(); assert.deepStrictEqual(resource, Resource.empty()); }); From 321f295d582b6c180899468f17ebd606d87eefea Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 11 Jan 2023 19:12:20 +0200 Subject: [PATCH 38/67] Update Resource's asyncAttributesHaveResolved description Co-authored-by: Amir Blum --- packages/opentelemetry-resources/src/Resource.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index ebf50e3561..7ca3fba9e2 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -81,8 +81,7 @@ export class Resource { * Check if async attributes have resolved. This is useful to avoid awaiting * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. * - * @returns true if no async attributes promise was provided or if the promise has resolved - * and been merged together with the sync attributes. + * @returns true if the resource "attributes" property is settled to its final value */ asyncAttributesHaveResolved(): boolean { return this._asyncAttributesHaveResolved; From 1c48e83ecb0efd7464941e59487224a05363749a Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 11:35:43 +0200 Subject: [PATCH 39/67] feat(sync-resource-detectors): using diag.error instead of debug --- packages/opentelemetry-resources/src/detect-resources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index cff4a230b6..ef8af4def2 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -84,7 +84,7 @@ export const detectResourcesSync = ( return resource; } catch (e) { - diag.debug(`${d.constructor.name} failed: ${e.message}`); + diag.error(`${d.constructor.name} failed: ${e.message}`); return Resource.empty(); } }); From 85d8dbd697e86a18f9cffaff5cb08301d551bb10 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 15:43:55 +0200 Subject: [PATCH 40/67] feat(sync-resource-detectors): removed asyncAttributesHaveResolved() getter --- .../opentelemetry-resources/src/Resource.ts | 27 +++++++++---------- .../test/Resource.test.ts | 18 ++++++------- .../src/export/BatchSpanProcessorBase.ts | 2 +- .../src/export/SimpleSpanProcessor.ts | 4 +-- .../export/PeriodicExportingMetricReader.ts | 2 +- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 7ca3fba9e2..65827bd72c 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -28,7 +28,14 @@ export class Resource { static readonly EMPTY = new Resource({}); private _syncAttributes: ResourceAttributes; private _asyncAttributesPromise: Promise | undefined; - private _asyncAttributesHaveResolved: boolean; + + /** + * Check if async attributes have resolved. This is useful to avoid awaiting + * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. + * + * @returns true if the resource "attributes" property is settled to its final value + */ + public asyncAttributesHaveResolved: boolean; /** * Returns an empty Resource @@ -61,39 +68,29 @@ export class Resource { public attributes: ResourceAttributes, asyncAttributesPromise?: Promise ) { - this._asyncAttributesHaveResolved = asyncAttributesPromise == null; + this.asyncAttributesHaveResolved = asyncAttributesPromise == null; this._syncAttributes = attributes; this._asyncAttributesPromise = asyncAttributesPromise?.then( asyncAttributes => { this.attributes = Object.assign({}, this.attributes, asyncAttributes); - this._asyncAttributesHaveResolved = true; + this.asyncAttributesHaveResolved = true; return asyncAttributes; }, err => { diag.debug("The resource's async promise rejected: %s", err); - this._asyncAttributesHaveResolved = true; + this.asyncAttributesHaveResolved = true; return {}; } ); } - /** - * Check if async attributes have resolved. This is useful to avoid awaiting - * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. - * - * @returns true if the resource "attributes" property is settled to its final value - */ - asyncAttributesHaveResolved(): boolean { - return this._asyncAttributesHaveResolved; - } - /** * Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to * this Resource's attributes. This is useful in exporters to block until resource detection * has finished. */ async waitForAsyncAttributes(): Promise { - if (!this._asyncAttributesHaveResolved) { + if (!this.asyncAttributesHaveResolved) { await this._asyncAttributesPromise; } } diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 12188a6112..249394cd21 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -102,8 +102,8 @@ describe('Resource', () => { assert.strictEqual(Resource.empty(), Resource.empty()); }); - it('should return true for asyncAttributesHaveResolved() immediately', () => { - assert.ok(Resource.empty().asyncAttributesHaveResolved()); + it('should return true for asyncAttributesHaveResolved immediately', () => { + assert.ok(Resource.empty().asyncAttributesHaveResolved); }); }); @@ -112,13 +112,13 @@ describe('Resource', () => { sinon.restore(); }); - it('should return true for asyncAttributesHaveResolved() if no promise provided', () => { - assert.ok(new Resource({ foo: 'bar' }).asyncAttributesHaveResolved()); - assert.ok(Resource.empty().asyncAttributesHaveResolved()); - assert.ok(Resource.default().asyncAttributesHaveResolved()); + it('should return true for asyncAttributesHaveResolved if no promise provided', () => { + assert.ok(new Resource({ foo: 'bar' }).asyncAttributesHaveResolved); + assert.ok(Resource.empty().asyncAttributesHaveResolved); + assert.ok(Resource.default().asyncAttributesHaveResolved); }); - it('should return true for asyncAttributesHaveResolved() once promise finishes', async () => { + it('should return true for asyncAttributesHaveResolved once promise finishes', async () => { const clock = sinon.useFakeTimers(); const resourceResolve = new Resource( {}, @@ -134,10 +134,10 @@ describe('Resource', () => { ); for (const resource of [resourceResolve, resourceReject]) { - assert.ok(!resource.asyncAttributesHaveResolved()); + assert.ok(!resource.asyncAttributesHaveResolved); await clock.nextAsync(); await resource.waitForAsyncAttributes(); - assert.ok(resource.asyncAttributesHaveResolved()); + assert.ok(resource.asyncAttributesHaveResolved); } }); diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 1c43c78ada..96eba1a026 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -177,7 +177,7 @@ export abstract class BatchSpanProcessorBase }); const pendingResources = spans .map(span => span.resource) - .filter(resource => !resource.asyncAttributesHaveResolved()); + .filter(resource => !resource.asyncAttributesHaveResolved); // Avoid scheduling a promise to make the behavior more predictable and easier to test if (pendingResources.length === 0) { diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index ab73d89910..00f49c94bc 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -53,7 +53,7 @@ export class SimpleSpanProcessor implements SpanProcessor { onStart(_span: Span, _parentContext: Context): void { // store the resource's unresolved promise - if (!_span.resource.asyncAttributesHaveResolved()) { + if (!_span.resource.asyncAttributesHaveResolved) { this._unresolvedResources.add(_span.resource.waitForAsyncAttributes()); } } @@ -85,7 +85,7 @@ export class SimpleSpanProcessor implements SpanProcessor { }); // Avoid scheduling a promise to make the behavior more predictable and easier to test - if (span.resource.asyncAttributesHaveResolved()) { + if (span.resource.asyncAttributesHaveResolved) { void doExport(); } else { span.resource diff --git a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts index 815fbc6bba..f95fa833d5 100644 --- a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -110,7 +110,7 @@ export class PeriodicExportingMetricReader extends MetricReader { }; // Avoid scheduling a promise to make the behavior more predictable and easier to test - if (resourceMetrics.resource.asyncAttributesHaveResolved()) { + if (resourceMetrics.resource.asyncAttributesHaveResolved) { await doExport(); } else { resourceMetrics.resource From 8121a4df38f595f27d2a8a0310ac75098672ebd5 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 15:51:45 +0200 Subject: [PATCH 41/67] feat(sync-resource-detectors): removed irrelevant call to shutdown() --- .../test/common/export/SimpleSpanProcessor.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts index 2a6191799f..0ff8c3db9c 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts @@ -180,9 +180,6 @@ describe('SimpleSpanProcessor', () => { await processor.forceFlush(); assert.strictEqual(exporter.getFinishedSpans().length, 1); - - await processor.shutdown(); - assert.strictEqual(exporter.getFinishedSpans().length, 0); }); describe('when flushing complete', () => { From 9e85a433a5d14d23de49ba86ac6b849a25f5ffa2 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 16:49:03 +0200 Subject: [PATCH 42/67] Update packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts Co-authored-by: Amir Blum --- .../src/export/BatchSpanProcessorBase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 96eba1a026..8354d11327 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -187,7 +187,7 @@ export abstract class BatchSpanProcessorBase pendingResources.map(resource => resource.waitForAsyncAttributes()) ).then(doExport, err => { globalErrorHandler(err); - resolve(err); + reject(err); }); } }); From 05a83df08987419627b15d28f908a3205debf093 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 16:55:34 +0200 Subject: [PATCH 43/67] feat(sync-resource-detectors): updated SimpleSpanProcessor.ts --- .../src/export/SimpleSpanProcessor.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 00f49c94bc..0ea1056329 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -44,17 +44,19 @@ export class SimpleSpanProcessor implements SpanProcessor { async forceFlush(): Promise { // await unresolved resources before resolving - for (const unresolvedResource of this._unresolvedResources) { - await unresolvedResource; - } - - return Promise.resolve(); + await Promise.all(Array.from(this._unresolvedResources)); } onStart(_span: Span, _parentContext: Context): void { // store the resource's unresolved promise if (!_span.resource.asyncAttributesHaveResolved) { - this._unresolvedResources.add(_span.resource.waitForAsyncAttributes()); + const resourcePromise = _span.resource.waitForAsyncAttributes(); + + this._unresolvedResources.add(resourcePromise); + + void resourcePromise.then(() => + this._unresolvedResources.delete(resourcePromise) + ); } } From ad81025d2972a12948df3f11ef4b2289d17239a6 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 17:07:08 +0200 Subject: [PATCH 44/67] feat(sync-resource-detectors): removed redundant call --- packages/opentelemetry-resources/test/Resource.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 249394cd21..5e1e7bfb57 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -249,10 +249,6 @@ describe('Resource', () => { assert.ok( debugStub.calledWithMatch("The resource's async promise rejected") ); - - // will log after yielding to event loop - await new Promise(resolve => setTimeout(resolve)); - assert.ok(debugStub.calledOnce); }); }); From ce2e87c66c7598e5558c79c2f06b6e5d0a8a43e2 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 12 Jan 2023 17:07:54 +0200 Subject: [PATCH 45/67] feat(sync-resource-detectors): updated test name --- packages/opentelemetry-resources/test/Resource.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 5e1e7bfb57..586f977348 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -118,7 +118,7 @@ describe('Resource', () => { assert.ok(Resource.default().asyncAttributesHaveResolved); }); - it('should return true for asyncAttributesHaveResolved once promise finishes', async () => { + it('should return true for asyncAttributesHaveResolved once promise settles', async () => { const clock = sinon.useFakeTimers(); const resourceResolve = new Resource( {}, From e1a3795b49d26188b113b57632734044bfd662d7 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 15 Jan 2023 14:39:47 +0200 Subject: [PATCH 46/67] feat(sync-resource-detectors): Added DetectorSync interface --- packages/opentelemetry-resources/src/types.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index bbe195ceae..900318f1d5 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -31,5 +31,13 @@ export type ResourceAttributes = SpanAttributes; * Promise is deprecated in favor of this approach. */ export interface Detector { - detect(config?: ResourceDetectionConfig): Promise | Resource; + detect(config?: ResourceDetectionConfig): Promise; +} + +/** + * Interface for a synchronous Resource Detector. In order to detect resources asynchronously, a detector + * can pass a Promise as the second parameter to the Resource constructor. + */ +export interface DetectorSync { + detect(config?: ResourceDetectionConfig): Resource; } From 62546b062e2a07f189d2ee073dbfbba2d6cc2826 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 15 Jan 2023 14:48:04 +0200 Subject: [PATCH 47/67] feat(sync-resource-detectors): revert change --- packages/opentelemetry-resources/src/types.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index 900318f1d5..bbe195ceae 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -31,13 +31,5 @@ export type ResourceAttributes = SpanAttributes; * Promise is deprecated in favor of this approach. */ export interface Detector { - detect(config?: ResourceDetectionConfig): Promise; -} - -/** - * Interface for a synchronous Resource Detector. In order to detect resources asynchronously, a detector - * can pass a Promise as the second parameter to the Resource constructor. - */ -export interface DetectorSync { - detect(config?: ResourceDetectionConfig): Resource; + detect(config?: ResourceDetectionConfig): Promise | Resource; } From 70f47a3d0a1b534c65996d124a53158d8a7e3091 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 17 Jan 2023 13:00:19 +0200 Subject: [PATCH 48/67] feat(sync-resource-detectors): Added new interface for sync detector and updated resource detectors to use it --- .../src/detectors/BrowserDetector.ts | 6 +++--- .../src/detectors/EnvDetector.ts | 6 +++--- .../src/detectors/NoopDetector.ts | 6 +++--- .../src/detectors/ProcessDetector.ts | 6 +++--- .../src/platform/node/HostDetector.ts | 6 +++--- .../src/platform/node/OSDetector.ts | 6 +++--- packages/opentelemetry-resources/src/types.ts | 10 +++++++++- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts index b9271d7e56..5b355539ff 100644 --- a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts @@ -16,14 +16,14 @@ import { diag } from '@opentelemetry/api'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { Detector, Resource, ResourceDetectionConfig } from '..'; +import { DetectorSync, Resource, ResourceDetectionConfig } from '..'; import { ResourceAttributes } from '../types'; /** * BrowserDetector will be used to detect the resources related to browser. */ -class BrowserDetector implements Detector { - async detect(config?: ResourceDetectionConfig): Promise { +class BrowserDetector implements DetectorSync { + detect(config?: ResourceDetectionConfig): Resource { const isBrowser = typeof navigator !== 'undefined'; if (!isBrowser) { return Resource.empty(); diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts index 0f5cd65209..92c16c9f38 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts @@ -18,14 +18,14 @@ import { diag } from '@opentelemetry/api'; import { getEnv } from '@opentelemetry/core'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../Resource'; -import { Detector, ResourceAttributes } from '../types'; +import { DetectorSync, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; /** * EnvDetector can be used to detect the presence of and create a Resource * from the OTEL_RESOURCE_ATTRIBUTES environment variable. */ -class EnvDetector implements Detector { +class EnvDetector implements DetectorSync { // Type, attribute keys, and attribute values should not exceed 256 characters. private readonly _MAX_LENGTH = 255; @@ -52,7 +52,7 @@ class EnvDetector implements Detector { * * @param config The resource detection config */ - async detect(_config?: ResourceDetectionConfig): Promise { + detect(_config?: ResourceDetectionConfig): Resource { const attributes: ResourceAttributes = {}; const env = getEnv(); diff --git a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts index 61cf954bf7..3185cc44c5 100644 --- a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts @@ -15,10 +15,10 @@ */ import { Resource } from '../Resource'; -import { Detector } from '../types'; +import { DetectorSync } from '../types'; -export class NoopDetector implements Detector { - async detect(): Promise { +export class NoopDetector implements DetectorSync { + detect(): Resource { return new Resource({}); } } diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts index 304a67f358..9a53d088b6 100644 --- a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts @@ -17,15 +17,15 @@ import { diag } from '@opentelemetry/api'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../Resource'; -import { Detector, ResourceAttributes } from '../types'; +import { DetectorSync, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; /** * ProcessDetector will be used to detect the resources related current process running * and being instrumented from the NodeJS Process module. */ -class ProcessDetector implements Detector { - async detect(config?: ResourceDetectionConfig): Promise { +class ProcessDetector implements DetectorSync { + detect(config?: ResourceDetectionConfig): Resource { // Skip if not in Node.js environment. if (typeof process !== 'object') { return Resource.empty(); diff --git a/packages/opentelemetry-resources/src/platform/node/HostDetector.ts b/packages/opentelemetry-resources/src/platform/node/HostDetector.ts index c27766bb92..edd44b027f 100644 --- a/packages/opentelemetry-resources/src/platform/node/HostDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/HostDetector.ts @@ -16,7 +16,7 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../../Resource'; -import { Detector, ResourceAttributes } from '../../types'; +import { DetectorSync, ResourceAttributes } from '../../types'; import { ResourceDetectionConfig } from '../../config'; import { arch, hostname } from 'os'; @@ -24,8 +24,8 @@ import { arch, hostname } from 'os'; * HostDetector detects the resources related to the host current process is * running on. Currently only non-cloud-based attributes are included. */ -class HostDetector implements Detector { - async detect(_config?: ResourceDetectionConfig): Promise { +class HostDetector implements DetectorSync { + detect(_config?: ResourceDetectionConfig): Resource { const attributes: ResourceAttributes = { [SemanticResourceAttributes.HOST_NAME]: hostname(), [SemanticResourceAttributes.HOST_ARCH]: this._normalizeArch(arch()), diff --git a/packages/opentelemetry-resources/src/platform/node/OSDetector.ts b/packages/opentelemetry-resources/src/platform/node/OSDetector.ts index befa9fb1f6..c560b9e18f 100644 --- a/packages/opentelemetry-resources/src/platform/node/OSDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/OSDetector.ts @@ -16,7 +16,7 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../../Resource'; -import { Detector, ResourceAttributes } from '../../types'; +import { DetectorSync, ResourceAttributes } from '../../types'; import { ResourceDetectionConfig } from '../../config'; import { platform, release } from 'os'; @@ -24,8 +24,8 @@ import { platform, release } from 'os'; * OSDetector detects the resources related to the operating system (OS) on * which the process represented by this resource is running. */ -class OSDetector implements Detector { - async detect(_config?: ResourceDetectionConfig): Promise { +class OSDetector implements DetectorSync { + detect(_config?: ResourceDetectionConfig): Resource { const attributes: ResourceAttributes = { [SemanticResourceAttributes.OS_TYPE]: this._normalizeType(platform()), [SemanticResourceAttributes.OS_VERSION]: release(), diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index bbe195ceae..900318f1d5 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -31,5 +31,13 @@ export type ResourceAttributes = SpanAttributes; * Promise is deprecated in favor of this approach. */ export interface Detector { - detect(config?: ResourceDetectionConfig): Promise | Resource; + detect(config?: ResourceDetectionConfig): Promise; +} + +/** + * Interface for a synchronous Resource Detector. In order to detect resources asynchronously, a detector + * can pass a Promise as the second parameter to the Resource constructor. + */ +export interface DetectorSync { + detect(config?: ResourceDetectionConfig): Resource; } From 588d93c3d1becf65f0a9a1061a604ab23ad18124 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 17 Jan 2023 13:15:43 +0200 Subject: [PATCH 49/67] feat(sync-resource-detectors): Fixed issues with Detector/DetectorSync --- .../opentelemetry-sdk-node/src/sdk.ts | 3 +- .../opentelemetry-sdk-node/src/types.ts | 4 +- .../opentelemetry-resources/src/config.ts | 4 +- .../src/detect-resources.ts | 49 ++++++++++--------- .../test/detect-resources.test.ts | 8 +-- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index bedd8307e9..590786ae48 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -21,6 +21,7 @@ import { } from '@opentelemetry/instrumentation'; import { Detector, + DetectorSync, detectResourcesSync, envDetector, processDetector, @@ -64,7 +65,7 @@ export class NodeSDK { private _instrumentations: InstrumentationOption[]; private _resource: Resource; - private _resourceDetectors: Detector[]; + private _resourceDetectors: Array; private _autoDetectResources: boolean; diff --git a/experimental/packages/opentelemetry-sdk-node/src/types.ts b/experimental/packages/opentelemetry-sdk-node/src/types.ts index 4c816516dc..49dfae8f7c 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/types.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/types.ts @@ -17,7 +17,7 @@ import type { ContextManager, SpanAttributes } from '@opentelemetry/api'; import { TextMapPropagator } from '@opentelemetry/api'; import { InstrumentationOption } from '@opentelemetry/instrumentation'; -import { Detector, Resource } from '@opentelemetry/resources'; +import { Detector, DetectorSync, Resource } from '@opentelemetry/resources'; import { MetricReader, View } from '@opentelemetry/sdk-metrics'; import { Sampler, @@ -35,7 +35,7 @@ export interface NodeSDKConfiguration { views: View[]; instrumentations: InstrumentationOption[]; resource: Resource; - resourceDetectors: Detector[]; + resourceDetectors: Array; sampler: Sampler; serviceName?: string; spanProcessor: SpanProcessor; diff --git a/packages/opentelemetry-resources/src/config.ts b/packages/opentelemetry-resources/src/config.ts index 915aad0764..239d596e6d 100644 --- a/packages/opentelemetry-resources/src/config.ts +++ b/packages/opentelemetry-resources/src/config.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import type { Detector } from './types'; +import type { Detector, DetectorSync } from './types'; /** * ResourceDetectionConfig provides an interface for configuring resource auto-detection. */ export interface ResourceDetectionConfig { - detectors?: Array; + detectors?: Array; } diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index ef8af4def2..33dd21fa6d 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -19,6 +19,7 @@ import { ResourceDetectionConfig } from './config'; import { diag } from '@opentelemetry/api'; import * as util from 'util'; import { isPromiseLike } from './utils'; +import { Detector, DetectorSync } from './types'; /** * Runs all resource detectors and returns the results merged into a single Resource. Promise @@ -62,32 +63,34 @@ export const detectResources = async ( export const detectResourcesSync = ( config: ResourceDetectionConfig = {} ): Resource => { - const resources: Resource[] = (config.detectors ?? []).map(d => { - try { - const resourceOrPromise = d.detect(config); - let resource: Resource; - if (isPromiseLike(resourceOrPromise)) { - const createPromise = async () => { - const resolvedResource = await resourceOrPromise; - return resolvedResource.attributes; - }; - resource = new Resource({}, createPromise()); - } else { - resource = resourceOrPromise; - } + const resources: Resource[] = (config.detectors ?? []).map( + (d: Detector | DetectorSync) => { + try { + const resourceOrPromise = d.detect(config); + let resource: Resource; + if (isPromiseLike(resourceOrPromise)) { + const createPromise = async () => { + const resolvedResource = await resourceOrPromise; + return resolvedResource.attributes; + }; + resource = new Resource({}, createPromise()); + } else { + resource = resourceOrPromise; + } - void resource - .waitForAsyncAttributes() - .then(() => - diag.debug(`${d.constructor.name} found resource.`, resource) - ); + void resource + .waitForAsyncAttributes() + .then(() => + diag.debug(`${d.constructor.name} found resource.`, resource) + ); - return resource; - } catch (e) { - diag.error(`${d.constructor.name} failed: ${e.message}`); - return Resource.empty(); + return resource; + } catch (e) { + diag.error(`${d.constructor.name} failed: ${e.message}`); + return Resource.empty(); + } } - }); + ); const mergedResources = resources.reduce( (acc, resource) => acc.merge(resource), diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index 60e9d2c413..ef17faa34f 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -17,7 +17,7 @@ import { diag } from '@opentelemetry/api'; import * as assert from 'assert'; import * as sinon from 'sinon'; -import { Resource, Detector, detectResourcesSync } from '../src'; +import { Resource, Detector, detectResourcesSync, DetectorSync } from '../src'; import { describeNode } from './util'; describe('detectResourcesSync', () => { @@ -42,7 +42,7 @@ describe('detectResourcesSync', () => { }); it('handles resource detectors which return Resource with a promise inside', async () => { - const detector: Detector = { + const detector: DetectorSync = { detect() { return new Resource( { sync: 'fromsync' }, @@ -68,7 +68,7 @@ describe('detectResourcesSync', () => { const debugStub = sinon.spy(diag, 'debug'); // use a class so it has a name - class DetectorRejects implements Detector { + class DetectorRejects implements DetectorSync { detect() { return new Resource( { sync: 'fromsync' }, @@ -76,7 +76,7 @@ describe('detectResourcesSync', () => { ); } } - class DetectorOk implements Detector { + class DetectorOk implements DetectorSync { detect() { return new Resource( { sync: 'fromsync' }, From 4414ba5b22a18174723503789ce5b0a59ed97de4 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 17 Jan 2023 15:29:47 +0200 Subject: [PATCH 50/67] feat(sync-resource-detectors): removed unnecessary comments --- packages/opentelemetry-resources/test/Resource.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 586f977348..6e641c4ea4 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -189,7 +189,6 @@ describe('Resource', () => { promise1: 'promise1val', promise2: 'promise2val', promise4: 'promise4val', - // same behavior as for synchronous attributes shared: 'promise4val', }); }); @@ -210,7 +209,6 @@ describe('Resource', () => { assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', promise2: 'promise2val', - // same behavior as for synchronous attributes shared: 'promise2val', }); }); @@ -235,7 +233,6 @@ describe('Resource', () => { assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', promise2: 'promise2val', - // same behavior as for synchronous attributes shared: 'promise2val', }); }); From b110dcae28403827ccd16a875ba90b43eecab96a Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Wed, 18 Jan 2023 16:44:16 +0200 Subject: [PATCH 51/67] feat(sync-resource-detectors): added test for pending resource in BatchSpanProcessorBase --- .../export/BatchSpanProcessorBase.test.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts index 9149e6cbf8..fd9574537b 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/BatchSpanProcessorBase.test.ts @@ -34,6 +34,7 @@ import { TestRecordOnlySampler } from './TestRecordOnlySampler'; import { TestTracingSpanExporter } from './TestTracingSpanExporter'; import { TestStackContextManager } from './TestStackContextManager'; import { BatchSpanProcessorBase } from '../../../src/export/BatchSpanProcessorBase'; +import { Resource, ResourceAttributes } from '@opentelemetry/resources'; function createSampledSpan(spanName: string): Span { const tracer = new BasicTracerProvider({ @@ -390,6 +391,27 @@ describe('BatchSpanProcessorBase', () => { done(); }); }); + + it('should wait for pending resource on flush', async () => { + const tracer = new BasicTracerProvider({ + resource: new Resource( + {}, + new Promise(resolve => { + setTimeout(() => resolve({ async: 'fromasync' }), 1); + }) + ), + }).getTracer('default'); + + const span = tracer.startSpan('test') as Span; + span.end(); + + processor.onStart(span, ROOT_CONTEXT); + processor.onEnd(span); + + await processor.forceFlush(); + + assert.strictEqual(exporter.getFinishedSpans().length, 1); + }); }); describe('flushing spans with exporter triggering instrumentation', () => { From 79a8321f440467d04e5da7eacb346c618bf2bd24 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 19 Jan 2023 15:06:43 +0200 Subject: [PATCH 52/67] Update packages/opentelemetry-resources/src/types.ts Co-authored-by: Amir Blum --- packages/opentelemetry-resources/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index 900318f1d5..e7580360e2 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -35,7 +35,7 @@ export interface Detector { } /** - * Interface for a synchronous Resource Detector. In order to detect resources asynchronously, a detector + * Interface for a synchronous Resource Detector. In order to detect attributes asynchronously, a detector * can pass a Promise as the second parameter to the Resource constructor. */ export interface DetectorSync { From 1800c532c1bc406301e386cd4af05dc32ffdf696 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 19 Jan 2023 15:07:51 +0200 Subject: [PATCH 53/67] Update packages/opentelemetry-resources/src/Resource.ts Co-authored-by: Amir Blum --- packages/opentelemetry-resources/src/Resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 65827bd72c..06965dcd12 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -77,7 +77,7 @@ export class Resource { return asyncAttributes; }, err => { - diag.debug("The resource's async promise rejected: %s", err); + diag.debug("a resource's async attributes promise rejected: %s", err); this.asyncAttributesHaveResolved = true; return {}; } From c92412f2a3a3db6d18c54be42b7975e4201cd2ca Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 19 Jan 2023 15:45:56 +0200 Subject: [PATCH 54/67] feat(sync-resource-detectors): fixed failing tests --- packages/opentelemetry-resources/test/Resource.test.ts | 4 +++- .../opentelemetry-resources/test/detect-resources.test.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 6e641c4ea4..5c4b7088e0 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -244,7 +244,9 @@ describe('Resource', () => { await resource.waitForAsyncAttributes(); assert.ok( - debugStub.calledWithMatch("The resource's async promise rejected") + debugStub.calledWithMatch( + "a resource's async attributes promise rejected" + ) ); }); }); diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index ef17faa34f..1a8f97f157 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -92,7 +92,9 @@ describe('detectResourcesSync', () => { await resource.waitForAsyncAttributes(); assert.ok( - debugStub.calledWithMatch("The resource's async promise rejected") + debugStub.calledWithMatch( + "a resource's async attributes promise rejected" + ) ); }); }); From 680f95569fc69802045c875ac876ba128444bd17 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 24 Jan 2023 23:28:18 +0200 Subject: [PATCH 55/67] feat(sync-resource-detectors): deprecated Detector interface --- packages/opentelemetry-resources/src/types.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index e7580360e2..f1120258fd 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -26,9 +26,7 @@ import { SpanAttributes } from '@opentelemetry/api'; export type ResourceAttributes = SpanAttributes; /** - * Interface for a Resource Detector. In order to detect resources asynchronously, a detector - * can pass a Promise as the second parameter to the Resource constructor. Returning a - * Promise is deprecated in favor of this approach. + * @deprecated please use {@link DetectorSync} */ export interface Detector { detect(config?: ResourceDetectionConfig): Promise; From b49a1d82e205b63842fe4f96e2f745ab449731ed Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 24 Jan 2023 23:29:28 +0200 Subject: [PATCH 56/67] feat(sync-resource-detectors): added compatibility tests and renamed asyncAttributesHaveResolved to asyncAttributesPending --- packages/opentelemetry-resources/package.json | 1 + .../opentelemetry-resources/src/Resource.ts | 34 +++++--- .../src/detect-resources.ts | 3 +- .../test/Resource.test.ts | 55 ++++++++++--- .../opentelemetry-sdk-trace-base/package.json | 1 + .../src/export/BatchSpanProcessorBase.ts | 2 +- .../src/export/SimpleSpanProcessor.ts | 37 ++++----- .../common/export/SimpleSpanProcessor.test.ts | 81 ++++++++++++++++++- .../common/export/TestExporterWithDelay.ts | 51 ++++++++++++ .../export/PeriodicExportingMetricReader.ts | 6 +- 10 files changed, 224 insertions(+), 47 deletions(-) create mode 100644 packages/opentelemetry-sdk-trace-base/test/common/export/TestExporterWithDelay.ts diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index c6177a592d..744849e9b8 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -62,6 +62,7 @@ }, "devDependencies": { "@opentelemetry/api": ">=1.0.0 <1.5.0", + "@opentelemetry/resources_1.9.0": "npm:@opentelemetry/resources@1.9.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 06965dcd12..ae79eeeb34 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -33,9 +33,9 @@ export class Resource { * Check if async attributes have resolved. This is useful to avoid awaiting * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. * - * @returns true if the resource "attributes" property is settled to its final value + * @returns true if the resource "attributes" property is not yet settled to its final value */ - public asyncAttributesHaveResolved: boolean; + public asyncAttributesPending: boolean; /** * Returns an empty Resource @@ -65,32 +65,42 @@ export class Resource { * information about the entity as numbers, strings or booleans * TODO: Consider to add check/validation on attributes. */ - public attributes: ResourceAttributes, + private _attributes: ResourceAttributes, asyncAttributesPromise?: Promise ) { - this.asyncAttributesHaveResolved = asyncAttributesPromise == null; - this._syncAttributes = attributes; + this.asyncAttributesPending = asyncAttributesPromise != null; + this._syncAttributes = _attributes; this._asyncAttributesPromise = asyncAttributesPromise?.then( asyncAttributes => { - this.attributes = Object.assign({}, this.attributes, asyncAttributes); - this.asyncAttributesHaveResolved = true; + this._attributes = Object.assign({}, this._attributes, asyncAttributes); + this.asyncAttributesPending = false; return asyncAttributes; }, err => { diag.debug("a resource's async attributes promise rejected: %s", err); - this.asyncAttributesHaveResolved = true; + this.asyncAttributesPending = false; return {}; } ); } + get attributes(): ResourceAttributes { + if (this.asyncAttributesPending) { + diag.error( + 'Accessing resource attributes before async attributes settled' + ); + } + + return this._attributes; + } + /** * Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to * this Resource's attributes. This is useful in exporters to block until resource detection * has finished. */ async waitForAsyncAttributes(): Promise { - if (!this.asyncAttributesHaveResolved) { + if (this.asyncAttributesPending) { await this._asyncAttributesPromise; } } @@ -109,7 +119,8 @@ export class Resource { // SpanAttributes from other resource overwrite attributes from this resource. const mergedSyncAttributes = { ...this._syncAttributes, - ...other._syncAttributes, + //Support for old resource implementation where _syncAttributes is not defined + ...(other._syncAttributes ?? other.attributes), }; if (!this._asyncAttributesPromise && !other._asyncAttributesPromise) { @@ -123,7 +134,8 @@ export class Resource { return { ...this._syncAttributes, ...thisAsyncAttributes, - ...other._syncAttributes, + //Support for old resource implementation where _syncAttributes is not defined + ...(other._syncAttributes ?? other.attributes), ...otherAsyncAttributes, }; }); diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index 33dd21fa6d..2e011daa0c 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -55,8 +55,7 @@ export const detectResources = async ( }; /** - * Runs all resource detectors synchronously, merging their results. Any asynchronous - * attributes will be merged together in-order after they resolve. + * Runs all resource detectors synchronously, merging their results. In case of attribute collision later resources will take precedence. * * @param config Configuration for resource detection */ diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 5c4b7088e0..ffbbbdb674 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -21,6 +21,7 @@ import { Resource, ResourceAttributes } from '../src'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeBrowser, describeNode } from './util'; import { diag } from '@opentelemetry/api'; +import { Resource as Resource190 } from '@opentelemetry/resources_1.9.0'; describe('Resource', () => { const resource1 = new Resource({ @@ -102,8 +103,8 @@ describe('Resource', () => { assert.strictEqual(Resource.empty(), Resource.empty()); }); - it('should return true for asyncAttributesHaveResolved immediately', () => { - assert.ok(Resource.empty().asyncAttributesHaveResolved); + it('should return false for asyncAttributesPending immediately', () => { + assert.ok(!Resource.empty().asyncAttributesPending); }); }); @@ -112,13 +113,13 @@ describe('Resource', () => { sinon.restore(); }); - it('should return true for asyncAttributesHaveResolved if no promise provided', () => { - assert.ok(new Resource({ foo: 'bar' }).asyncAttributesHaveResolved); - assert.ok(Resource.empty().asyncAttributesHaveResolved); - assert.ok(Resource.default().asyncAttributesHaveResolved); + it('should return false for asyncAttributesPending if no promise provided', () => { + assert.ok(!new Resource({ foo: 'bar' }).asyncAttributesPending); + assert.ok(!Resource.empty().asyncAttributesPending); + assert.ok(!Resource.default().asyncAttributesPending); }); - it('should return true for asyncAttributesHaveResolved once promise settles', async () => { + it('should return false for asyncAttributesPending once promise settles', async () => { const clock = sinon.useFakeTimers(); const resourceResolve = new Resource( {}, @@ -134,10 +135,10 @@ describe('Resource', () => { ); for (const resource of [resourceResolve, resourceReject]) { - assert.ok(!resource.asyncAttributesHaveResolved); + assert.ok(resource.asyncAttributesPending); await clock.nextAsync(); await resource.waitForAsyncAttributes(); - assert.ok(resource.asyncAttributesHaveResolved); + assert.ok(!resource.asyncAttributesPending); } }); @@ -294,4 +295,40 @@ describe('Resource', () => { ); }); }); + + describe('compatibility', () => { + it('should merge resource with old implementation', () => { + const resource = Resource.EMPTY; + const oldResource = new Resource190({ fromold: 'fromold' }); + + //TODO: find a solution for ts-ignore + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const mergedResource = resource.merge(oldResource); + + assert.strictEqual(mergedResource.attributes['fromold'], 'fromold'); + }); + + it('should merge resource containing async attributes with old implementation', async () => { + const resource = new Resource( + {}, + Promise.resolve({ fromnew: 'fromnew' }) + ); + + const oldResource = new Resource190({ fromold: 'fromold' }); + + //TODO: find a solution for ts-ignore + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const mergedResource = resource.merge(oldResource); + + assert.strictEqual(mergedResource.attributes['fromold'], 'fromold'); + + await mergedResource.waitForAsyncAttributes(); + + assert.strictEqual(mergedResource.attributes['fromnew'], 'fromnew'); + }); + }); }); diff --git a/packages/opentelemetry-sdk-trace-base/package.json b/packages/opentelemetry-sdk-trace-base/package.json index c7ffaa8f30..2e745d9ae1 100644 --- a/packages/opentelemetry-sdk-trace-base/package.json +++ b/packages/opentelemetry-sdk-trace-base/package.json @@ -65,6 +65,7 @@ }, "devDependencies": { "@opentelemetry/api": ">=1.0.0 <1.5.0", + "@opentelemetry/resources_1.9.0": "npm:@opentelemetry/resources@1.9.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index 8354d11327..ded84b6857 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -177,7 +177,7 @@ export abstract class BatchSpanProcessorBase }); const pendingResources = spans .map(span => span.resource) - .filter(resource => !resource.asyncAttributesHaveResolved); + .filter(resource => resource.asyncAttributesPending); // Avoid scheduling a promise to make the behavior more predictable and easier to test if (pendingResources.length === 0) { diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 0ea1056329..0c848380da 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -35,30 +35,20 @@ import { SpanExporter } from './SpanExporter'; */ export class SimpleSpanProcessor implements SpanProcessor { private _shutdownOnce: BindOnceFuture; - private _unresolvedResources: Set>; + private _unresolvedExports: Set>; constructor(private readonly _exporter: SpanExporter) { this._shutdownOnce = new BindOnceFuture(this._shutdown, this); - this._unresolvedResources = new Set>(); + this._unresolvedExports = new Set>(); } async forceFlush(): Promise { // await unresolved resources before resolving - await Promise.all(Array.from(this._unresolvedResources)); + await Promise.all(Array.from(this._unresolvedExports)); + 5; } - onStart(_span: Span, _parentContext: Context): void { - // store the resource's unresolved promise - if (!_span.resource.asyncAttributesHaveResolved) { - const resourcePromise = _span.resource.waitForAsyncAttributes(); - - this._unresolvedResources.add(resourcePromise); - - void resourcePromise.then(() => - this._unresolvedResources.delete(resourcePromise) - ); - } - } + onStart(_span: Span, _parentContext: Context): void {} onEnd(span: ReadableSpan): void { if (this._shutdownOnce.isCalled) { @@ -87,12 +77,19 @@ export class SimpleSpanProcessor implements SpanProcessor { }); // Avoid scheduling a promise to make the behavior more predictable and easier to test - if (span.resource.asyncAttributesHaveResolved) { - void doExport(); + if (span.resource.asyncAttributesPending) { + const exportPromise = span.resource.waitForAsyncAttributes().then( + () => { + this._unresolvedExports.delete(exportPromise); + return doExport(); + }, + err => globalErrorHandler(err) + ); + + // store the unresolved exports + this._unresolvedExports.add(exportPromise); } else { - span.resource - .waitForAsyncAttributes() - .then(doExport, err => globalErrorHandler(err)); + void doExport(); } } diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts index 0ff8c3db9c..88490c527b 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts @@ -37,6 +37,8 @@ import { import { TestStackContextManager } from './TestStackContextManager'; import { TestTracingSpanExporter } from './TestTracingSpanExporter'; import { Resource, ResourceAttributes } from '@opentelemetry/resources'; +import { Resource as Resource190 } from '@opentelemetry/resources_1.9.0'; +import { TestExporterWithDelay } from './TestExporterWithDelay'; describe('SimpleSpanProcessor', () => { let provider: BasicTracerProvider; @@ -179,7 +181,52 @@ describe('SimpleSpanProcessor', () => { assert.strictEqual(exporter.getFinishedSpans().length, 0); await processor.forceFlush(); - assert.strictEqual(exporter.getFinishedSpans().length, 1); + + const exportedSpans = exporter.getFinishedSpans(); + + assert.strictEqual(exportedSpans.length, 1); + assert.strictEqual( + exportedSpans[0].resource.attributes['async'], + 'fromasync' + ); + }); + + it('should await doExport() and delete from _unresolvedExports', async () => { + const testExporterWithDelay = new TestExporterWithDelay(); + const processor = new SimpleSpanProcessor(testExporterWithDelay); + + const providerWithAsyncResource = new BasicTracerProvider({ + resource: new Resource( + {}, + new Promise(resolve => { + setTimeout(() => resolve({ async: 'fromasync' }), 1); + }) + ), + }); + const spanContext: SpanContext = { + traceId: 'a3cda95b652f4a1592b449d5929fda1b', + spanId: '5e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const span = new Span( + providerWithAsyncResource.getTracer('default'), + ROOT_CONTEXT, + 'span-name', + spanContext, + SpanKind.CLIENT + ); + processor.onStart(span, ROOT_CONTEXT); + processor.onEnd(span); + + assert.strictEqual(processor['_unresolvedExports'].size, 1); + + await processor.forceFlush(); + + assert.strictEqual(processor['_unresolvedExports'].size, 0); + + const exportedSpans = testExporterWithDelay.getFinishedSpans(); + + assert.strictEqual(exportedSpans.length, 1); }); describe('when flushing complete', () => { @@ -236,4 +283,36 @@ describe('SimpleSpanProcessor', () => { assert.equal(exporterCreatedSpans.length, 0); }); }); + + describe('compatibility', () => { + it('should export when using old resource implementation', async () => { + const processor = new SimpleSpanProcessor(exporter); + const providerWithAsyncResource = new BasicTracerProvider({ + resource: new Resource190({ fromold: 'fromold' }), + }); + const spanContext: SpanContext = { + traceId: 'a3cda95b652f4a1592b449d5929fda1b', + spanId: '5e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const span = new Span( + providerWithAsyncResource.getTracer('default'), + ROOT_CONTEXT, + 'span-name', + spanContext, + SpanKind.CLIENT + ); + processor.onStart(span, ROOT_CONTEXT); + assert.strictEqual(exporter.getFinishedSpans().length, 0); + processor.onEnd(span); + + const exportedSpans = exporter.getFinishedSpans(); + + assert.strictEqual(exportedSpans.length, 1); + assert.strictEqual( + exportedSpans[0].resource.attributes['fromold'], + 'fromold' + ); + }); + }); }); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/TestExporterWithDelay.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/TestExporterWithDelay.ts new file mode 100644 index 0000000000..d10dab5271 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/TestExporterWithDelay.ts @@ -0,0 +1,51 @@ +/* + * 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 { ExportResult } from '@opentelemetry/core'; +import { InMemorySpanExporter, ReadableSpan } from '../../../src'; + +/** + * A test-only exporter that delays during export to mimic a real exporter. + */ +export class TestExporterWithDelay extends InMemorySpanExporter { + private _exporterCreatedSpans: ReadableSpan[] = []; + + constructor() { + super(); + } + + override export( + spans: ReadableSpan[], + resultCallback: (result: ExportResult) => void + ): void { + super.export(spans, () => setTimeout(resultCallback, 1)); + } + + override shutdown(): Promise { + return super.shutdown().then(() => { + this._exporterCreatedSpans = []; + }); + } + + override reset() { + super.reset(); + this._exporterCreatedSpans = []; + } + + getExporterCreatedSpans(): ReadableSpan[] { + return this._exporterCreatedSpans; + } +} diff --git a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts index 79e6d57a09..fe3de9869a 100644 --- a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -128,14 +128,14 @@ export class PeriodicExportingMetricReader extends MetricReader { }; // Avoid scheduling a promise to make the behavior more predictable and easier to test - if (resourceMetrics.resource.asyncAttributesHaveResolved) { - await doExport(); - } else { + if (resourceMetrics.resource.asyncAttributesPending) { resourceMetrics.resource .waitForAsyncAttributes() .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err) ); + } else { + await doExport(); } } From a9e661447b30651da1615ebd05d0ac4b282425a5 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 24 Jan 2023 23:49:20 +0200 Subject: [PATCH 57/67] feat(sync-resource-detectors): using JSON.stringify instead of utils.inspect --- packages/opentelemetry-resources/src/detect-resources.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index 2e011daa0c..44a32e850d 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -17,7 +17,6 @@ import { Resource } from './Resource'; import { ResourceDetectionConfig } from './config'; import { diag } from '@opentelemetry/api'; -import * as util from 'util'; import { isPromiseLike } from './utils'; import { Detector, DetectorSync } from './types'; @@ -111,12 +110,7 @@ const logResources = (resources: Array) => { resources.forEach(resource => { // Print only populated resources if (Object.keys(resource.attributes).length > 0) { - const resourceDebugString = util.inspect(resource.attributes, { - depth: 2, - breakLength: Infinity, - sorted: true, - compact: false, - }); + const resourceDebugString = JSON.stringify(resource.attributes, null, 4); diag.verbose(resourceDebugString); } }); From 0eefbdfc947c92482440b5ebab696e5aeda49163 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 24 Jan 2023 23:49:43 +0200 Subject: [PATCH 58/67] feat(sync-resource-detectors): added logging test --- .../test/Resource.test.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index ffbbbdb674..623436fe09 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -93,6 +93,24 @@ describe('Resource', () => { assert.strictEqual(resource.attributes['custom.boolean'], true); }); + it('should log when accessing attributes before async attributes promise has settled', () => { + const debugStub = sinon.spy(diag, 'error'); + const resource = new Resource( + {}, + new Promise(resolve => { + setTimeout(resolve, 1); + }) + ); + + resource.attributes; + + assert.ok( + debugStub.calledWithMatch( + 'Accessing resource attributes before async attributes settled' + ) + ); + }); + describe('.empty()', () => { it('should return an empty resource (except required service name)', () => { const resource = Resource.empty(); @@ -124,13 +142,13 @@ describe('Resource', () => { const resourceResolve = new Resource( {}, new Promise(resolve => { - setTimeout(resolve, 100); + setTimeout(resolve, 1); }) ); const resourceReject = new Resource( {}, new Promise((_, reject) => { - setTimeout(reject, 200); + setTimeout(reject, 1); }) ); From d070c0c28768818cdd99085040be95be142dd78b Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Tue, 24 Jan 2023 23:52:26 +0200 Subject: [PATCH 59/67] feat(sync-resource-detectors): updated "Upgrade guidelines" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 476c6bef9f..fa72dde947 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ These instrumentations are hosted at Date: Thu, 26 Jan 2023 18:51:56 +0200 Subject: [PATCH 60/67] feat(sync-resource-detectors): fixed regex in test --- experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index af87fff167..a70b019b9e 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -482,7 +482,7 @@ describe('Node SDK', () => { assert.ok( callArgsMatches( mockedVerboseLoggerMethod, - /{\s+'service\.instance\.id':\s+'627cc493',\s+'service\.name':\s+'my-service',\s+'service\.namespace':\s+'default',\s+'service\.version':\s+'0\.0\.1'\s+}\s*/ + /{\s+"service\.instance\.id":\s+"627cc493",\s+"service\.name":\s+"my-service",\s+"service\.namespace":\s+"default",\s+"service\.version":\s+"0.0.1"\s+}\s*/gm ) ); }); From 96230d61324fe825d24efb09ebb1eb26a2d0cc8a Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Mon, 30 Jan 2023 19:33:27 +0200 Subject: [PATCH 61/67] feat(sync-resource-detectors): removed typo --- .../src/export/SimpleSpanProcessor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 0c848380da..5b91aca952 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -45,7 +45,6 @@ export class SimpleSpanProcessor implements SpanProcessor { async forceFlush(): Promise { // await unresolved resources before resolving await Promise.all(Array.from(this._unresolvedExports)); - 5; } onStart(_span: Span, _parentContext: Context): void {} From 7611fa85a3076a0bbdea5d5e4a81406c62d678f3 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Thu, 2 Feb 2023 19:31:16 +0200 Subject: [PATCH 62/67] feat(sync-resource-detectors): created IResource interface --- .../src/BrowserDetector.ts | 3 +- .../test/BrowserDetector.test.ts | 8 +-- .../test/util.ts | 6 +-- .../src/PrometheusSerializer.ts | 4 +- .../opentelemetry-sdk-node/src/sdk.ts | 5 +- .../opentelemetry-sdk-node/test/sdk.test.ts | 16 +++--- .../test/util/resource-assertions.ts | 4 +- .../otlp-transformer/src/trace/index.ts | 4 +- .../src/transform.ts | 4 +- .../opentelemetry-resources/src/IResource.ts | 54 +++++++++++++++++++ .../opentelemetry-resources/src/Resource.ts | 20 ++++--- .../src/detect-resources.ts | 41 ++++++++------ .../src/detectors/BrowserDetector.ts | 4 +- .../src/detectors/EnvDetector.ts | 3 +- .../src/detectors/NoopDetector.ts | 3 +- .../src/detectors/ProcessDetector.ts | 3 +- packages/opentelemetry-resources/src/index.ts | 1 + packages/opentelemetry-resources/src/types.ts | 6 +-- .../test/Resource.test.ts | 14 +++-- .../test/detect-resources.test.ts | 6 +-- .../detectors/browser/BrowserDetector.test.ts | 6 +-- .../detectors/browser/EnvDetector.test.ts | 10 ++-- .../detectors/browser/HostDetector.test.ts | 4 +- .../test/detectors/browser/OSDetector.test.ts | 4 +- .../detectors/browser/ProcessDetector.test.ts | 4 +- .../detectors/node/BrowserDetector.test.ts | 4 +- .../test/detectors/node/EnvDetector.test.ts | 8 +-- .../test/detectors/node/HostDetector.test.ts | 6 +-- .../test/detectors/node/OSDetector.test.ts | 6 +-- .../detectors/node/ProcessDetector.test.ts | 6 +-- .../test/util/resource-assertions.ts | 22 ++++---- .../src/BasicTracerProvider.ts | 4 +- .../opentelemetry-sdk-trace-base/src/Span.ts | 4 +- .../src/Tracer.ts | 4 +- .../src/export/BatchSpanProcessorBase.ts | 5 +- .../src/export/ReadableSpan.ts | 4 +- .../src/export/SimpleSpanProcessor.ts | 17 +++--- .../opentelemetry-sdk-trace-base/src/types.ts | 4 +- packages/sdk-metrics/src/MeterProvider.ts | 4 +- packages/sdk-metrics/src/export/MetricData.ts | 4 +- .../export/PeriodicExportingMetricReader.ts | 3 +- .../src/state/MeterProviderSharedState.ts | 4 +- 42 files changed, 216 insertions(+), 130 deletions(-) create mode 100644 packages/opentelemetry-resources/src/IResource.ts diff --git a/experimental/packages/opentelemetry-browser-detector/src/BrowserDetector.ts b/experimental/packages/opentelemetry-browser-detector/src/BrowserDetector.ts index 318168b77c..85fdd1cc50 100644 --- a/experimental/packages/opentelemetry-browser-detector/src/BrowserDetector.ts +++ b/experimental/packages/opentelemetry-browser-detector/src/BrowserDetector.ts @@ -17,6 +17,7 @@ import { diag } from '@opentelemetry/api'; import { Detector, + IResource, Resource, ResourceDetectionConfig, } from '@opentelemetry/resources'; @@ -27,7 +28,7 @@ import { BROWSER_ATTRIBUTES, UserAgentData } from './types'; * BrowserDetector will be used to detect the resources related to browser. */ class BrowserDetector implements Detector { - async detect(config?: ResourceDetectionConfig): Promise { + async detect(config?: ResourceDetectionConfig): Promise { const isBrowser = typeof navigator !== 'undefined'; if (!isBrowser) { return Resource.empty(); diff --git a/experimental/packages/opentelemetry-browser-detector/test/BrowserDetector.test.ts b/experimental/packages/opentelemetry-browser-detector/test/BrowserDetector.test.ts index 7eb3928161..727007aa55 100644 --- a/experimental/packages/opentelemetry-browser-detector/test/BrowserDetector.test.ts +++ b/experimental/packages/opentelemetry-browser-detector/test/BrowserDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as sinon from 'sinon'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { browserDetector } from '../src/BrowserDetector'; import { describeBrowser, assertResource, assertEmptyResource } from './util'; @@ -47,7 +47,7 @@ describeBrowser('browserDetector()', () => { }, }); - const resource: Resource = await browserDetector.detect(); + const resource: IResource = await browserDetector.detect(); assertResource(resource, { platform: 'platform', brands: ['Chromium 106', 'Google Chrome 106', 'Not;A=Brand 99'], @@ -63,7 +63,7 @@ describeBrowser('browserDetector()', () => { userAgentData: undefined, }); - const resource: Resource = await browserDetector.detect(); + const resource: IResource = await browserDetector.detect(); assertResource(resource, { language: 'en-US', user_agent: 'dddd', @@ -74,7 +74,7 @@ describeBrowser('browserDetector()', () => { sinon.stub(globalThis, 'navigator').value({ userAgent: '', }); - const resource: Resource = await browserDetector.detect(); + const resource: IResource = await browserDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/experimental/packages/opentelemetry-browser-detector/test/util.ts b/experimental/packages/opentelemetry-browser-detector/test/util.ts index b74fd89ced..3318f48910 100644 --- a/experimental/packages/opentelemetry-browser-detector/test/util.ts +++ b/experimental/packages/opentelemetry-browser-detector/test/util.ts @@ -16,7 +16,7 @@ import { Suite } from 'mocha'; import * as assert from 'assert'; import { BROWSER_ATTRIBUTES } from '../src/types'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; export function describeBrowser(title: string, fn: (this: Suite) => void) { title = `Browser: ${title}`; @@ -27,7 +27,7 @@ export function describeBrowser(title: string, fn: (this: Suite) => void) { } export const assertResource = ( - resource: Resource, + resource: IResource, validations: { platform?: string; brands?: string[]; @@ -74,6 +74,6 @@ export const assertResource = ( * * @param resource the Resource to validate */ -export const assertEmptyResource = (resource: Resource) => { +export const assertEmptyResource = (resource: IResource) => { assert.strictEqual(Object.keys(resource.attributes).length, 0); }; diff --git a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts index 2d9808bf35..75b29bb61b 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts +++ b/experimental/packages/opentelemetry-exporter-prometheus/src/PrometheusSerializer.ts @@ -29,7 +29,7 @@ import { Histogram, } from '@opentelemetry/sdk-metrics'; import { hrTimeToMilliseconds } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; type PrometheusDataTypeLiteral = | 'counter' @@ -340,7 +340,7 @@ export class PrometheusSerializer { return results; } - protected _serializeResource(resource: Resource): string { + protected _serializeResource(resource: IResource): string { const name = 'target_info'; const help = `# HELP ${name} Target metadata`; const type = `# TYPE ${name} gauge`; diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index 4a42bfc89a..fa14043cd3 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -24,6 +24,7 @@ import { DetectorSync, detectResourcesSync, envDetector, + IResource, processDetector, Resource, ResourceDetectionConfig, @@ -64,7 +65,7 @@ export class NodeSDK { private _meterProviderConfig?: MeterProviderConfig; private _instrumentations: InstrumentationOption[]; - private _resource: Resource; + private _resource: IResource; private _resourceDetectors: Array; private _autoDetectResources: boolean; @@ -197,7 +198,7 @@ export class NodeSDK { } /** Manually add a resource */ - public addResource(resource: Resource): void { + public addResource(resource: IResource): void { this._resource = this._resource.merge(resource); } diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index a70b019b9e..96138b2547 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -393,7 +393,7 @@ describe('Node SDK', () => { }); sdk.detectResources(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assert.strictEqual(resource.attributes['customAttr'], 'someValue'); @@ -423,7 +423,7 @@ describe('Node SDK', () => { sdk.detectResources(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assertServiceResource(resource, { instanceId: '627cc493', @@ -472,7 +472,7 @@ describe('Node SDK', () => { ); sdk.detectResources(); - await sdk['_resource'].waitForAsyncAttributes(); + await (sdk['_resource'] as Resource).waitForAsyncAttributes(); // Test that the Env Detector successfully found its resource and populated it with the right values. assert.ok( @@ -537,7 +537,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assertServiceResource(resource, { name: 'env-set-name', @@ -553,7 +553,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assertServiceResource(resource, { name: 'config-set-name', @@ -568,7 +568,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assertServiceResource(resource, { name: 'resource-env-set-name', @@ -585,7 +585,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assertServiceResource(resource, { name: 'config-set-name', @@ -660,7 +660,7 @@ describe('Node SDK', () => { }); sdk.detectResources(); const resource = sdk['_resource']; - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assert.deepStrictEqual(resource, Resource.empty()); }); diff --git a/experimental/packages/opentelemetry-sdk-node/test/util/resource-assertions.ts b/experimental/packages/opentelemetry-sdk-node/test/util/resource-assertions.ts index cd6b272433..bcbdceadc0 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/util/resource-assertions.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/util/resource-assertions.ts @@ -16,7 +16,7 @@ import { SDK_INFO } from '@opentelemetry/core'; import * as assert from 'assert'; -import { Resource } from '@opentelemetry/resources'; +import { IResource, Resource } from '@opentelemetry/resources'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; /** @@ -229,7 +229,7 @@ export const assertTelemetrySDKResource = ( * @param validations validations for the resource attributes */ export const assertServiceResource = ( - resource: Resource, + resource: IResource, validations: { name: string; instanceId?: string; diff --git a/experimental/packages/otlp-transformer/src/trace/index.ts b/experimental/packages/otlp-transformer/src/trace/index.ts index 8d20181221..ad06612a11 100644 --- a/experimental/packages/otlp-transformer/src/trace/index.ts +++ b/experimental/packages/otlp-transformer/src/trace/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { Resource } from '@opentelemetry/resources'; +import type { IResource } from '@opentelemetry/resources'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { toAttributes } from '../common/internal'; import { sdkSpanToOtlpSpan } from './internal'; @@ -33,7 +33,7 @@ export function createExportTraceServiceRequest( } function createResourceMap(readableSpans: ReadableSpan[]) { - const resourceMap: Map> = new Map(); + const resourceMap: Map> = new Map(); for (const record of readableSpans) { let ilmMap = resourceMap.get(record.resource); diff --git a/packages/opentelemetry-exporter-zipkin/src/transform.ts b/packages/opentelemetry-exporter-zipkin/src/transform.ts index 9ffd48ecce..c3c1887616 100644 --- a/packages/opentelemetry-exporter-zipkin/src/transform.ts +++ b/packages/opentelemetry-exporter-zipkin/src/transform.ts @@ -18,7 +18,7 @@ import * as api from '@opentelemetry/api'; import { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; import { hrTimeToMicroseconds } from '@opentelemetry/core'; import * as zipkinTypes from './types'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; const ZIPKIN_SPAN_KIND_MAPPING = { [api.SpanKind.CLIENT]: zipkinTypes.SpanKind.CLIENT, @@ -72,7 +72,7 @@ export function _toZipkinTags( status: api.SpanStatus, statusCodeTagName: string, statusErrorTagName: string, - resource: Resource + resource: IResource ): zipkinTypes.Tags { const tags: { [key: string]: string } = {}; for (const key of Object.keys(attributes)) { diff --git a/packages/opentelemetry-resources/src/IResource.ts b/packages/opentelemetry-resources/src/IResource.ts new file mode 100644 index 0000000000..b53a0e0244 --- /dev/null +++ b/packages/opentelemetry-resources/src/IResource.ts @@ -0,0 +1,54 @@ +/* + * 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 { ResourceAttributes } from './types'; + +/** + * An interface that represents a resource. A Resource describes the entity for which signals (metrics or trace) are + * collected. + * + */ +export interface IResource { + /** + * Check if async attributes have resolved. This is useful to avoid awaiting + * waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary. + * + * @returns true if the resource "attributes" property is not yet settled to its final value + */ + asyncAttributesPending?: boolean; + + /** + * @returns the Resource's attributes. + */ + readonly attributes: ResourceAttributes; + + /** + * Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to + * this Resource's attributes. This is useful in exporters to block until resource detection + * has finished. + */ + waitForAsyncAttributes?(): Promise; + + /** + * Returns a new, merged {@link Resource} by merging the current Resource + * with the other Resource. In case of a collision, other Resource takes + * precedence. + * + * @param other the Resource that will be merged with this. + * @returns the newly merged Resource. + */ + merge(other: IResource | null): IResource; +} diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index ae79eeeb34..2ef82468de 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -19,12 +19,13 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { SDK_INFO } from '@opentelemetry/core'; import { ResourceAttributes } from './types'; import { defaultServiceName } from './platform'; +import { IResource } from './IResource'; /** * A Resource describes the entity for which a signals (metrics or trace) are * collected. */ -export class Resource { +export class Resource implements IResource { static readonly EMPTY = new Resource({}); private _syncAttributes: ResourceAttributes; private _asyncAttributesPromise: Promise | undefined; @@ -40,14 +41,14 @@ export class Resource { /** * Returns an empty Resource */ - static empty(): Resource { + static empty(): IResource { return Resource.EMPTY; } /** * Returns a Resource that identifies the SDK in use. */ - static default(): Resource { + static default(): IResource { return new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: defaultServiceName(), [SemanticResourceAttributes.TELEMETRY_SDK_LANGUAGE]: @@ -113,29 +114,32 @@ export class Resource { * @param other the Resource that will be merged with this. * @returns the newly merged Resource. */ - merge(other: Resource | null): Resource { + merge(other: IResource | null): IResource { if (!other) return this; // SpanAttributes from other resource overwrite attributes from this resource. const mergedSyncAttributes = { ...this._syncAttributes, //Support for old resource implementation where _syncAttributes is not defined - ...(other._syncAttributes ?? other.attributes), + ...((other as Resource)._syncAttributes ?? other.attributes), }; - if (!this._asyncAttributesPromise && !other._asyncAttributesPromise) { + if ( + !this._asyncAttributesPromise && + !(other as Resource)._asyncAttributesPromise + ) { return new Resource(mergedSyncAttributes); } const mergedAttributesPromise = Promise.all([ this._asyncAttributesPromise, - other._asyncAttributesPromise, + (other as Resource)._asyncAttributesPromise, ]).then(([thisAsyncAttributes, otherAsyncAttributes]) => { return { ...this._syncAttributes, ...thisAsyncAttributes, //Support for old resource implementation where _syncAttributes is not defined - ...(other._syncAttributes ?? other.attributes), + ...((other as Resource)._syncAttributes ?? other.attributes), ...otherAsyncAttributes, }; }); diff --git a/packages/opentelemetry-resources/src/detect-resources.ts b/packages/opentelemetry-resources/src/detect-resources.ts index 44a32e850d..be6943f81d 100644 --- a/packages/opentelemetry-resources/src/detect-resources.ts +++ b/packages/opentelemetry-resources/src/detect-resources.ts @@ -19,6 +19,7 @@ import { ResourceDetectionConfig } from './config'; import { diag } from '@opentelemetry/api'; import { isPromiseLike } from './utils'; import { Detector, DetectorSync } from './types'; +import { IResource } from './IResource'; /** * Runs all resource detectors and returns the results merged into a single Resource. Promise @@ -30,8 +31,8 @@ import { Detector, DetectorSync } from './types'; */ export const detectResources = async ( config: ResourceDetectionConfig = {} -): Promise => { - const resources: Resource[] = await Promise.all( +): Promise => { + const resources: IResource[] = await Promise.all( (config.detectors || []).map(async d => { try { const resource = await d.detect(config); @@ -60,12 +61,12 @@ export const detectResources = async ( */ export const detectResourcesSync = ( config: ResourceDetectionConfig = {} -): Resource => { - const resources: Resource[] = (config.detectors ?? []).map( +): IResource => { + const resources: IResource[] = (config.detectors ?? []).map( (d: Detector | DetectorSync) => { try { const resourceOrPromise = d.detect(config); - let resource: Resource; + let resource: IResource; if (isPromiseLike(resourceOrPromise)) { const createPromise = async () => { const resolvedResource = await resourceOrPromise; @@ -73,14 +74,18 @@ export const detectResourcesSync = ( }; resource = new Resource({}, createPromise()); } else { - resource = resourceOrPromise; + resource = resourceOrPromise as IResource; } - void resource - .waitForAsyncAttributes() - .then(() => - diag.debug(`${d.constructor.name} found resource.`, resource) - ); + if (resource.waitForAsyncAttributes) { + void resource + .waitForAsyncAttributes() + .then(() => + diag.debug(`${d.constructor.name} found resource.`, resource) + ); + } else { + diag.debug(`${d.constructor.name} found resource.`, resource); + } return resource; } catch (e) { @@ -94,10 +99,14 @@ export const detectResourcesSync = ( (acc, resource) => acc.merge(resource), Resource.empty() ); - void mergedResources.waitForAsyncAttributes().then(() => { - // Future check if verbose logging is enabled issue #1903 - logResources(resources); - }); + + if (mergedResources.waitForAsyncAttributes) { + void mergedResources.waitForAsyncAttributes().then(() => { + // Future check if verbose logging is enabled issue #1903 + logResources(resources); + }); + } + return mergedResources; }; @@ -106,7 +115,7 @@ export const detectResourcesSync = ( * * @param resources The array of {@link Resource} that should be logged. Empty entries will be ignored. */ -const logResources = (resources: Array) => { +const logResources = (resources: Array) => { resources.forEach(resource => { // Print only populated resources if (Object.keys(resource.attributes).length > 0) { diff --git a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts index 5b355539ff..b305739e19 100644 --- a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts @@ -16,14 +16,14 @@ import { diag } from '@opentelemetry/api'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { DetectorSync, Resource, ResourceDetectionConfig } from '..'; +import { DetectorSync, IResource, Resource, ResourceDetectionConfig } from '..'; import { ResourceAttributes } from '../types'; /** * BrowserDetector will be used to detect the resources related to browser. */ class BrowserDetector implements DetectorSync { - detect(config?: ResourceDetectionConfig): Resource { + detect(config?: ResourceDetectionConfig): IResource { const isBrowser = typeof navigator !== 'undefined'; if (!isBrowser) { return Resource.empty(); diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts index 92c16c9f38..131592c701 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts @@ -20,6 +20,7 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { Resource } from '../Resource'; import { DetectorSync, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; +import { IResource } from '../IResource'; /** * EnvDetector can be used to detect the presence of and create a Resource @@ -52,7 +53,7 @@ class EnvDetector implements DetectorSync { * * @param config The resource detection config */ - detect(_config?: ResourceDetectionConfig): Resource { + detect(_config?: ResourceDetectionConfig): IResource { const attributes: ResourceAttributes = {}; const env = getEnv(); diff --git a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts index 3185cc44c5..57da341fbe 100644 --- a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts @@ -16,9 +16,10 @@ import { Resource } from '../Resource'; import { DetectorSync } from '../types'; +import { IResource } from '../IResource'; export class NoopDetector implements DetectorSync { - detect(): Resource { + detect(): IResource { return new Resource({}); } } diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts index 9a53d088b6..d5ce37bb2b 100644 --- a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts @@ -19,13 +19,14 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' import { Resource } from '../Resource'; import { DetectorSync, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; +import { IResource } from '../IResource'; /** * ProcessDetector will be used to detect the resources related current process running * and being instrumented from the NodeJS Process module. */ class ProcessDetector implements DetectorSync { - detect(config?: ResourceDetectionConfig): Resource { + detect(config?: ResourceDetectionConfig): IResource { // Skip if not in Node.js environment. if (typeof process !== 'object') { return Resource.empty(); diff --git a/packages/opentelemetry-resources/src/index.ts b/packages/opentelemetry-resources/src/index.ts index f1c9ae98e6..3f66901fbb 100644 --- a/packages/opentelemetry-resources/src/index.ts +++ b/packages/opentelemetry-resources/src/index.ts @@ -15,6 +15,7 @@ */ export * from './Resource'; +export * from './IResource'; export * from './platform'; export * from './types'; export * from './config'; diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts index f1120258fd..d20c09faa2 100644 --- a/packages/opentelemetry-resources/src/types.ts +++ b/packages/opentelemetry-resources/src/types.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { Resource } from './Resource'; import { ResourceDetectionConfig } from './config'; import { SpanAttributes } from '@opentelemetry/api'; +import { IResource } from './IResource'; /** * Interface for Resource attributes. @@ -29,7 +29,7 @@ export type ResourceAttributes = SpanAttributes; * @deprecated please use {@link DetectorSync} */ export interface Detector { - detect(config?: ResourceDetectionConfig): Promise; + detect(config?: ResourceDetectionConfig): Promise; } /** @@ -37,5 +37,5 @@ export interface Detector { * can pass a Promise as the second parameter to the Resource constructor. */ export interface DetectorSync { - detect(config?: ResourceDetectionConfig): Resource; + detect(config?: ResourceDetectionConfig): IResource; } diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 623436fe09..9372c62d15 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -203,7 +203,9 @@ describe('Resource', () => { .merge(resource2) .merge(resource3) .merge(resource4); - await merged.waitForAsyncAttributes(); + + await (merged as Resource).waitForAsyncAttributes(); + assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', promise2: 'promise2val', @@ -224,7 +226,9 @@ describe('Resource', () => { }); const merged = resource1.merge(resource2); - await merged.waitForAsyncAttributes(); + + await (merged as Resource).waitForAsyncAttributes(); + assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', promise2: 'promise2val', @@ -248,7 +252,9 @@ describe('Resource', () => { const resource2 = new Resource({}, asyncAttributes); const merged = resource1.merge(resource2); - await merged.waitForAsyncAttributes(); + + await (merged as Resource).waitForAsyncAttributes(); + assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', promise2: 'promise2val', @@ -344,7 +350,7 @@ describe('Resource', () => { assert.strictEqual(mergedResource.attributes['fromold'], 'fromold'); - await mergedResource.waitForAsyncAttributes(); + await (mergedResource as Resource).waitForAsyncAttributes(); assert.strictEqual(mergedResource.attributes['fromnew'], 'fromnew'); }); diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index 1a8f97f157..12943bc18b 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -35,7 +35,7 @@ describe('detectResourcesSync', () => { detectors: [detector], }); - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assert.deepStrictEqual(resource.attributes, { sync: 'fromsync', }); @@ -56,7 +56,7 @@ describe('detectResourcesSync', () => { // before waiting, it should already have the sync resources assert.deepStrictEqual(resource.attributes, { sync: 'fromsync' }); - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assert.deepStrictEqual(resource.attributes, { sync: 'fromsync', async: 'fromasync', @@ -89,7 +89,7 @@ describe('detectResourcesSync', () => { detectors: [new DetectorRejects(), new DetectorOk()], }); - await resource.waitForAsyncAttributes(); + await (resource as Resource).waitForAsyncAttributes(); assert.ok( debugStub.calledWithMatch( diff --git a/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts b/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts index ee6a92ccce..95c1b95b44 100644 --- a/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as sinon from 'sinon'; -import { Resource } from '../../../src'; +import { IResource } from '../../../src'; import { browserDetector } from '../../../src/detectors/BrowserDetector'; import { describeBrowser } from '../../util'; import { @@ -32,7 +32,7 @@ describeBrowser('browserDetector()', () => { userAgent: 'dddd', }); - const resource: Resource = await browserDetector.detect(); + const resource: IResource = await browserDetector.detect(); assertResource(resource, { version: 'dddd', runtimeDescription: 'Web Browser', @@ -43,7 +43,7 @@ describeBrowser('browserDetector()', () => { sinon.stub(globalThis, 'navigator').value({ userAgent: '', }); - const resource: Resource = await browserDetector.detect(); + const resource: IResource = await browserDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/browser/EnvDetector.test.ts b/packages/opentelemetry-resources/test/detectors/browser/EnvDetector.test.ts index 0e8894ef93..8901595773 100644 --- a/packages/opentelemetry-resources/test/detectors/browser/EnvDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/browser/EnvDetector.test.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import { RAW_ENVIRONMENT } from '@opentelemetry/core'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { envDetector, Resource } from '../../../src'; +import { envDetector, IResource } from '../../../src'; import { assertEmptyResource, assertWebEngineResource, @@ -39,7 +39,7 @@ describeBrowser('envDetector() on web browser', () => { }); it('should return resource information from environment variable', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertWebEngineResource(resource, { [SemanticResourceAttributes.WEBENGINE_NAME]: 'chromium', [SemanticResourceAttributes.WEBENGINE_VERSION]: '99', @@ -66,7 +66,7 @@ describeBrowser('envDetector() on web browser', () => { }); it('should return empty resource', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertEmptyResource(resource); }); }); @@ -75,14 +75,14 @@ describeBrowser('envDetector() on web browser', () => { describe('with empty env', () => { it('should return empty resource', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertEmptyResource(resource); }); }); describe('with empty env', () => { it('should return empty resource', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/browser/HostDetector.test.ts b/packages/opentelemetry-resources/test/detectors/browser/HostDetector.test.ts index a1299541d9..6f1df6cd23 100644 --- a/packages/opentelemetry-resources/test/detectors/browser/HostDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/browser/HostDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as sinon from 'sinon'; -import { hostDetector, Resource } from '../../../src'; +import { hostDetector, IResource } from '../../../src'; import { assertEmptyResource } from '../../util/resource-assertions'; import { describeBrowser } from '../../util'; @@ -24,7 +24,7 @@ describeBrowser('hostDetector() on web browser', () => { }); it('should return empty resource', async () => { - const resource: Resource = await hostDetector.detect(); + const resource: IResource = await hostDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/browser/OSDetector.test.ts b/packages/opentelemetry-resources/test/detectors/browser/OSDetector.test.ts index 991aa05271..bb69f73954 100644 --- a/packages/opentelemetry-resources/test/detectors/browser/OSDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/browser/OSDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as sinon from 'sinon'; -import { osDetector, Resource } from '../../../src'; +import { osDetector, IResource } from '../../../src'; import { assertEmptyResource } from '../../util/resource-assertions'; import { describeBrowser } from '../../util'; @@ -24,7 +24,7 @@ describeBrowser('osDetector() on web browser', () => { }); it('should return empty resource', async () => { - const resource: Resource = await osDetector.detect(); + const resource: IResource = await osDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/browser/ProcessDetector.test.ts b/packages/opentelemetry-resources/test/detectors/browser/ProcessDetector.test.ts index ae4c824ce0..7c404e67ed 100644 --- a/packages/opentelemetry-resources/test/detectors/browser/ProcessDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/browser/ProcessDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as sinon from 'sinon'; -import { processDetector, Resource } from '../../../src'; +import { processDetector, IResource } from '../../../src'; import { assertEmptyResource } from '../../util/resource-assertions'; import { describeBrowser } from '../../util'; @@ -24,7 +24,7 @@ describeBrowser('processDetector() on web browser', () => { }); it('should return empty resource', async () => { - const resource: Resource = await processDetector.detect(); + const resource: IResource = await processDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/node/BrowserDetector.test.ts b/packages/opentelemetry-resources/test/detectors/node/BrowserDetector.test.ts index 73873b1e1a..e8fff65750 100644 --- a/packages/opentelemetry-resources/test/detectors/node/BrowserDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/node/BrowserDetector.test.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Resource } from '../../../src'; +import { IResource } from '../../../src'; import { browserDetector } from '../../../src/detectors/BrowserDetector'; import { describeNode } from '../../util'; import { assertEmptyResource } from '../../util/resource-assertions'; describeNode('browserDetector()', () => { it('should return empty resources if window.document is missing', async () => { - const resource: Resource = await browserDetector.detect(); + const resource: IResource = await browserDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/node/EnvDetector.test.ts b/packages/opentelemetry-resources/test/detectors/node/EnvDetector.test.ts index 1397978377..b4ccdd1ad3 100644 --- a/packages/opentelemetry-resources/test/detectors/node/EnvDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/node/EnvDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { envDetector, Resource } from '../../../src'; +import { envDetector, IResource } from '../../../src'; import { assertK8sResource, assertEmptyResource, @@ -33,7 +33,7 @@ describeNode('envDetector() on Node.js', () => { }); it('should return resource information from environment variable', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertK8sResource(resource, { podName: 'pod-xyz-123', clusterName: 'c1', @@ -57,7 +57,7 @@ describeNode('envDetector() on Node.js', () => { }); it('should return empty resource', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertEmptyResource(resource); }); }); @@ -66,7 +66,7 @@ describeNode('envDetector() on Node.js', () => { describe('with empty env', () => { it('should return empty resource', async () => { - const resource: Resource = await envDetector.detect(); + const resource: IResource = await envDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/detectors/node/HostDetector.test.ts b/packages/opentelemetry-resources/test/detectors/node/HostDetector.test.ts index 3c809d500d..287b98b10b 100644 --- a/packages/opentelemetry-resources/test/detectors/node/HostDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/node/HostDetector.test.ts @@ -18,7 +18,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeNode } from '../../util'; -import { hostDetector, Resource } from '../../../src'; +import { hostDetector, IResource } from '../../../src'; describeNode('hostDetector() on Node.js', () => { afterEach(() => { @@ -31,7 +31,7 @@ describeNode('hostDetector() on Node.js', () => { sinon.stub(os, 'arch').returns('x64'); sinon.stub(os, 'hostname').returns('opentelemetry-test'); - const resource: Resource = await hostDetector.detect(); + const resource: IResource = await hostDetector.detect(); assert.strictEqual( resource.attributes[SemanticResourceAttributes.HOST_NAME], @@ -48,7 +48,7 @@ describeNode('hostDetector() on Node.js', () => { sinon.stub(os, 'arch').returns('some-unknown-arch'); - const resource: Resource = await hostDetector.detect(); + const resource: IResource = await hostDetector.detect(); assert.strictEqual( resource.attributes[SemanticResourceAttributes.HOST_ARCH], diff --git a/packages/opentelemetry-resources/test/detectors/node/OSDetector.test.ts b/packages/opentelemetry-resources/test/detectors/node/OSDetector.test.ts index dd3ec3e24b..58b1989ec3 100644 --- a/packages/opentelemetry-resources/test/detectors/node/OSDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/node/OSDetector.test.ts @@ -18,7 +18,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { describeNode } from '../../util'; -import { osDetector, Resource } from '../../../src'; +import { osDetector, IResource } from '../../../src'; describeNode('osDetector() on Node.js', () => { afterEach(() => { @@ -31,7 +31,7 @@ describeNode('osDetector() on Node.js', () => { sinon.stub(os, 'platform').returns('win32'); sinon.stub(os, 'release').returns('2.2.1(0.289/5/3)'); - const resource: Resource = await osDetector.detect(); + const resource: IResource = await osDetector.detect(); assert.strictEqual( resource.attributes[SemanticResourceAttributes.OS_TYPE], @@ -48,7 +48,7 @@ describeNode('osDetector() on Node.js', () => { sinon.stub(os, 'platform').returns('some-unknown-platform'); - const resource: Resource = await osDetector.detect(); + const resource: IResource = await osDetector.detect(); assert.strictEqual( resource.attributes[SemanticResourceAttributes.OS_TYPE], diff --git a/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts b/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts index dc1a3473a4..6f5793b6ae 100644 --- a/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as sinon from 'sinon'; -import { processDetector, Resource } from '../../../src'; +import { processDetector, IResource } from '../../../src'; import { assertResource, assertEmptyResource, @@ -34,7 +34,7 @@ describeNode('processDetector() on Node.js', () => { .value(['/tmp/node', '/home/ot/test.js', 'arg1', 'arg2']); sinon.stub(process, 'versions').value({ node: '1.4.1' }); - const resource: Resource = await processDetector.detect(); + const resource: IResource = await processDetector.detect(); assertResource(resource, { pid: 1234, name: 'otProcess', @@ -49,7 +49,7 @@ describeNode('processDetector() on Node.js', () => { sinon.stub(process, 'pid').value(1234); sinon.stub(process, 'title').value(undefined); sinon.stub(process, 'argv').value([]); - const resource: Resource = await processDetector.detect(); + const resource: IResource = await processDetector.detect(); assertEmptyResource(resource); }); }); diff --git a/packages/opentelemetry-resources/test/util/resource-assertions.ts b/packages/opentelemetry-resources/test/util/resource-assertions.ts index 70ab2c8c33..fdf541305e 100644 --- a/packages/opentelemetry-resources/test/util/resource-assertions.ts +++ b/packages/opentelemetry-resources/test/util/resource-assertions.ts @@ -16,7 +16,7 @@ import { SDK_INFO } from '@opentelemetry/core'; import * as assert from 'assert'; -import { Resource } from '../../src/Resource'; +import { IResource } from '../../src/IResource'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; /** @@ -26,7 +26,7 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' * @param validations validations for the resource attributes */ export const assertCloudResource = ( - resource: Resource, + resource: IResource, validations: { provider?: string; accountId?: string; @@ -64,7 +64,7 @@ export const assertCloudResource = ( * @param validations validations for the resource attributes */ export const assertContainerResource = ( - resource: Resource, + resource: IResource, validations: { name?: string; id?: string; @@ -102,7 +102,7 @@ export const assertContainerResource = ( * @param validations validations for the resource attributes */ export const assertHostResource = ( - resource: Resource, + resource: IResource, validations: { hostName?: string; id?: string; @@ -153,7 +153,7 @@ export const assertHostResource = ( * @param validations validations for the resource attributes */ export const assertK8sResource = ( - resource: Resource, + resource: IResource, validations: { clusterName?: string; namespaceName?: string; @@ -191,7 +191,7 @@ export const assertK8sResource = ( * @param validations validations for the resource attributes */ export const assertTelemetrySDKResource = ( - resource: Resource, + resource: IResource, validations: { name?: string; language?: string; @@ -229,7 +229,7 @@ export const assertTelemetrySDKResource = ( * @param validations validations for the resource attributes */ export const assertServiceResource = ( - resource: Resource, + resource: IResource, validations: { name: string; instanceId: string; @@ -264,7 +264,7 @@ export const assertServiceResource = ( * @param validations validations for the resource attributes */ export const assertResource = ( - resource: Resource, + resource: IResource, validations: { pid?: number; name?: string; @@ -320,7 +320,7 @@ export const assertResource = ( }; export const assertWebEngineResource = ( - resource: Resource, + resource: IResource, validations: { name?: string; version?: string; @@ -352,11 +352,11 @@ export const assertWebEngineResource = ( * * @param resource the Resource to validate */ -export const assertEmptyResource = (resource: Resource) => { +export const assertEmptyResource = (resource: IResource) => { assert.strictEqual(Object.keys(resource.attributes).length, 0); }; -const assertHasOneLabel = (prefix: string, resource: Resource): void => { +const assertHasOneLabel = (prefix: string, resource: IResource): void => { const hasOne = Object.entries(SemanticResourceAttributes).find( ([key, value]) => { return ( diff --git a/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts b/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts index 179b6b1d3e..c06f5cdcb6 100644 --- a/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts +++ b/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts @@ -29,7 +29,7 @@ import { getEnv, merge, } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; +import { IResource, Resource } from '@opentelemetry/resources'; import { SpanProcessor, Tracer } from '.'; import { loadDefaultConfig } from './config'; import { MultiSpanProcessor } from './MultiSpanProcessor'; @@ -71,7 +71,7 @@ export class BasicTracerProvider implements TracerProvider { private readonly _tracers: Map = new Map(); activeSpanProcessor: SpanProcessor; - readonly resource: Resource; + readonly resource: IResource; constructor(config: TracerConfig = {}) { const mergedConfig = merge( diff --git a/packages/opentelemetry-sdk-trace-base/src/Span.ts b/packages/opentelemetry-sdk-trace-base/src/Span.ts index 7677e6f5ce..fb2b717d48 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Span.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Span.ts @@ -42,7 +42,7 @@ import { otperformance, sanitizeAttributes, } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; import { ExceptionEventName } from './enums'; import { ReadableSpan } from './export/ReadableSpan'; @@ -64,7 +64,7 @@ export class Span implements APISpan, ReadableSpan { readonly links: Link[] = []; readonly events: TimedEvent[] = []; readonly startTime: HrTime; - readonly resource: Resource; + readonly resource: IResource; readonly instrumentationLibrary: InstrumentationLibrary; name: string; status: SpanStatus = { diff --git a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts index 6fc0102359..fd5bbcca70 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts @@ -20,7 +20,7 @@ import { sanitizeAttributes, isTracingSuppressed, } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { BasicTracerProvider } from './BasicTracerProvider'; import { Span } from './Span'; import { GeneralLimits, SpanLimits, TracerConfig } from './types'; @@ -38,7 +38,7 @@ export class Tracer implements api.Tracer { private readonly _generalLimits: GeneralLimits; private readonly _spanLimits: SpanLimits; private readonly _idGenerator: IdGenerator; - readonly resource: Resource; + readonly resource: IResource; readonly instrumentationLibrary: InstrumentationLibrary; /** diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index ded84b6857..cb84163d53 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -28,6 +28,7 @@ import { SpanProcessor } from '../SpanProcessor'; import { BufferConfig } from '../types'; import { ReadableSpan } from './ReadableSpan'; import { SpanExporter } from './SpanExporter'; +import { Resource } from '@opentelemetry/resources'; /** * Implementation of the {@link SpanProcessor} that batches spans exported by @@ -184,7 +185,9 @@ export abstract class BatchSpanProcessorBase doExport(); } else { Promise.all( - pendingResources.map(resource => resource.waitForAsyncAttributes()) + pendingResources.map(resource => + (resource as Resource).waitForAsyncAttributes() + ) ).then(doExport, err => { globalErrorHandler(err); reject(err); diff --git a/packages/opentelemetry-sdk-trace-base/src/export/ReadableSpan.ts b/packages/opentelemetry-sdk-trace-base/src/export/ReadableSpan.ts index 8552134a56..aa19891099 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/ReadableSpan.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/ReadableSpan.ts @@ -22,7 +22,7 @@ import { Link, SpanContext, } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { TimedEvent } from '../TimedEvent'; @@ -39,6 +39,6 @@ export interface ReadableSpan { readonly events: TimedEvent[]; readonly duration: HrTime; readonly ended: boolean; - readonly resource: Resource; + readonly resource: IResource; readonly instrumentationLibrary: InstrumentationLibrary; } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 5b91aca952..673e631c8d 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -26,6 +26,7 @@ import { Span } from '../Span'; import { SpanProcessor } from '../SpanProcessor'; import { ReadableSpan } from './ReadableSpan'; import { SpanExporter } from './SpanExporter'; +import { Resource } from '@opentelemetry/resources'; /** * An implementation of the {@link SpanProcessor} that converts the {@link Span} @@ -77,13 +78,15 @@ export class SimpleSpanProcessor implements SpanProcessor { // Avoid scheduling a promise to make the behavior more predictable and easier to test if (span.resource.asyncAttributesPending) { - const exportPromise = span.resource.waitForAsyncAttributes().then( - () => { - this._unresolvedExports.delete(exportPromise); - return doExport(); - }, - err => globalErrorHandler(err) - ); + const exportPromise = (span.resource as Resource) + .waitForAsyncAttributes() + .then( + () => { + this._unresolvedExports.delete(exportPromise); + return doExport(); + }, + err => globalErrorHandler(err) + ); // store the unresolved exports this._unresolvedExports.add(exportPromise); diff --git a/packages/opentelemetry-sdk-trace-base/src/types.ts b/packages/opentelemetry-sdk-trace-base/src/types.ts index 54eaaf97d9..6854f0315a 100644 --- a/packages/opentelemetry-sdk-trace-base/src/types.ts +++ b/packages/opentelemetry-sdk-trace-base/src/types.ts @@ -15,7 +15,7 @@ */ import { ContextManager, TextMapPropagator } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { IdGenerator } from './IdGenerator'; import { Sampler } from './Sampler'; @@ -35,7 +35,7 @@ export interface TracerConfig { spanLimits?: SpanLimits; /** Resource associated with trace telemetry */ - resource?: Resource; + resource?: IResource; /** * Generator of trace and span IDs diff --git a/packages/sdk-metrics/src/MeterProvider.ts b/packages/sdk-metrics/src/MeterProvider.ts index 72f0945b9f..f10cf42b9b 100644 --- a/packages/sdk-metrics/src/MeterProvider.ts +++ b/packages/sdk-metrics/src/MeterProvider.ts @@ -21,7 +21,7 @@ import { MeterOptions, createNoopMeter, } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; +import { IResource, Resource } from '@opentelemetry/resources'; import { MetricReader } from './export/MetricReader'; import { MeterProviderSharedState } from './state/MeterProviderSharedState'; import { MetricCollector } from './state/MetricCollector'; @@ -33,7 +33,7 @@ import { View } from './view/View'; */ export interface MeterProviderOptions { /** Resource associated with metric telemetry */ - resource?: Resource; + resource?: IResource; views?: View[]; } diff --git a/packages/sdk-metrics/src/export/MetricData.ts b/packages/sdk-metrics/src/export/MetricData.ts index e7adfa0357..931be0c05b 100644 --- a/packages/sdk-metrics/src/export/MetricData.ts +++ b/packages/sdk-metrics/src/export/MetricData.ts @@ -16,7 +16,7 @@ import { HrTime, MetricAttributes } from '@opentelemetry/api'; import { InstrumentationScope } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { InstrumentDescriptor } from '../InstrumentDescriptor'; import { AggregationTemporality } from './AggregationTemporality'; import { Histogram } from '../aggregator/types'; @@ -67,7 +67,7 @@ export interface ScopeMetrics { } export interface ResourceMetrics { - resource: Resource; + resource: IResource; scopeMetrics: ScopeMetrics[]; } diff --git a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts index fe3de9869a..73e314211f 100644 --- a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -25,6 +25,7 @@ import { MetricReader } from './MetricReader'; import { PushMetricExporter } from './MetricExporter'; import { callWithTimeout, TimeoutError } from '../utils'; import { diag } from '@opentelemetry/api'; +import { Resource } from '@opentelemetry/resources'; export type PeriodicExportingMetricReaderOptions = { /** @@ -129,7 +130,7 @@ export class PeriodicExportingMetricReader extends MetricReader { // Avoid scheduling a promise to make the behavior more predictable and easier to test if (resourceMetrics.resource.asyncAttributesPending) { - resourceMetrics.resource + (resourceMetrics.resource as Resource) .waitForAsyncAttributes() .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err) diff --git a/packages/sdk-metrics/src/state/MeterProviderSharedState.ts b/packages/sdk-metrics/src/state/MeterProviderSharedState.ts index a63f53d51d..fa7903b20e 100644 --- a/packages/sdk-metrics/src/state/MeterProviderSharedState.ts +++ b/packages/sdk-metrics/src/state/MeterProviderSharedState.ts @@ -15,7 +15,7 @@ */ import { InstrumentationScope } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; +import { IResource } from '@opentelemetry/resources'; import { Aggregation, InstrumentType } from '..'; import { instrumentationScopeId } from '../utils'; import { ViewRegistry } from '../view/ViewRegistry'; @@ -32,7 +32,7 @@ export class MeterProviderSharedState { meterSharedStates: Map = new Map(); - constructor(public resource: Resource) {} + constructor(public resource: IResource) {} getMeterSharedState(instrumentationScope: InstrumentationScope) { const id = instrumentationScopeId(instrumentationScope); From 0ff2427cfd6699784ddccfbef38d0f976d67470f Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 5 Feb 2023 13:27:15 +0200 Subject: [PATCH 63/67] feat(sync-resource-detectors): reverted detectors to the old detector interface. Created new sync detectors --- .../src/detectors/BrowserDetector.ts | 14 +- .../src/detectors/BrowserDetectorSync.ts | 64 +++++++ .../src/detectors/EnvDetector.ts | 8 +- .../src/detectors/EnvDetectorSync.ts | 160 ++++++++++++++++++ .../src/detectors/NoopDetector.ts | 8 +- .../src/detectors/NoopDetectorSync.ts | 27 +++ .../src/detectors/ProcessDetector.ts | 14 +- .../src/detectors/ProcessDetectorSync.ts | 79 +++++++++ .../src/detectors/index.ts | 3 + .../src/platform/browser/HostDetectorSync.ts | 19 +++ .../src/platform/browser/OSDetectorSync.ts | 19 +++ .../src/platform/browser/index.ts | 2 + .../src/platform/node/HostDetector.ts | 27 +-- .../src/platform/node/HostDetectorSync.ts | 38 +++++ .../src/platform/node/OSDetector.ts | 25 +-- .../src/platform/node/OSDetectorSync.ts | 38 +++++ .../src/platform/node/index.ts | 2 + .../src/platform/node/utils.ts | 42 +++++ 18 files changed, 531 insertions(+), 58 deletions(-) create mode 100644 packages/opentelemetry-resources/src/detectors/BrowserDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/detectors/NoopDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/platform/browser/HostDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/platform/browser/OSDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/HostDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/OSDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/utils.ts diff --git a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts index b305739e19..c0383001d3 100644 --- a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts @@ -14,26 +14,28 @@ * limitations under the License. */ -import { diag } from '@opentelemetry/api'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { DetectorSync, IResource, Resource, ResourceDetectionConfig } from '..'; +import { Detector, IResource, Resource, ResourceDetectionConfig } from '..'; import { ResourceAttributes } from '../types'; +import { diag } from '@opentelemetry/api'; /** * BrowserDetector will be used to detect the resources related to browser. */ -class BrowserDetector implements DetectorSync { - detect(config?: ResourceDetectionConfig): IResource { +class BrowserDetector implements Detector { + detect(config?: ResourceDetectionConfig): Promise { const isBrowser = typeof navigator !== 'undefined'; if (!isBrowser) { - return Resource.empty(); + return Promise.resolve(Resource.empty()); } const browserResource: ResourceAttributes = { [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'browser', [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Web Browser', [SemanticResourceAttributes.PROCESS_RUNTIME_VERSION]: navigator.userAgent, }; - return this._getResourceAttributes(browserResource, config); + return Promise.resolve( + this._getResourceAttributes(browserResource, config) + ); } /** * Validates process resource attribute map from process variables diff --git a/packages/opentelemetry-resources/src/detectors/BrowserDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/BrowserDetectorSync.ts new file mode 100644 index 0000000000..b58fea94c8 --- /dev/null +++ b/packages/opentelemetry-resources/src/detectors/BrowserDetectorSync.ts @@ -0,0 +1,64 @@ +/* + * 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 { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { DetectorSync, IResource, Resource, ResourceDetectionConfig } from '..'; +import { ResourceAttributes } from '../types'; +import { diag } from '@opentelemetry/api'; + +/** + * BrowserDetectorSync will be used to detect the resources related to browser. + */ +class BrowserDetectorSync implements DetectorSync { + detect(config?: ResourceDetectionConfig): IResource { + const isBrowser = typeof navigator !== 'undefined'; + if (!isBrowser) { + return Resource.empty(); + } + const browserResource: ResourceAttributes = { + [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'browser', + [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Web Browser', + [SemanticResourceAttributes.PROCESS_RUNTIME_VERSION]: navigator.userAgent, + }; + return this._getResourceAttributes(browserResource, config); + } + /** + * Validates process resource attribute map from process variables + * + * @param browserResource The un-sanitized resource attributes from process as key/value pairs. + * @param config: Config + * @returns The sanitized resource attributes. + */ + private _getResourceAttributes( + browserResource: ResourceAttributes, + _config?: ResourceDetectionConfig + ) { + if ( + browserResource[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION] === '' + ) { + diag.debug( + 'BrowserDetector failed: Unable to find required browser resources. ' + ); + return Resource.empty(); + } else { + return new Resource({ + ...browserResource, + }); + } + } +} + +export const browserDetectorSync = new BrowserDetectorSync(); diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts index 131592c701..d74ceb36e1 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts @@ -18,7 +18,7 @@ import { diag } from '@opentelemetry/api'; import { getEnv } from '@opentelemetry/core'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../Resource'; -import { DetectorSync, ResourceAttributes } from '../types'; +import { Detector, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; import { IResource } from '../IResource'; @@ -26,7 +26,7 @@ import { IResource } from '../IResource'; * EnvDetector can be used to detect the presence of and create a Resource * from the OTEL_RESOURCE_ATTRIBUTES environment variable. */ -class EnvDetector implements DetectorSync { +class EnvDetector implements Detector { // Type, attribute keys, and attribute values should not exceed 256 characters. private readonly _MAX_LENGTH = 255; @@ -53,7 +53,7 @@ class EnvDetector implements DetectorSync { * * @param config The resource detection config */ - detect(_config?: ResourceDetectionConfig): IResource { + detect(_config?: ResourceDetectionConfig): Promise { const attributes: ResourceAttributes = {}; const env = getEnv(); @@ -73,7 +73,7 @@ class EnvDetector implements DetectorSync { attributes[SemanticResourceAttributes.SERVICE_NAME] = serviceName; } - return new Resource(attributes); + return Promise.resolve(new Resource(attributes)); } /** diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts new file mode 100644 index 0000000000..baf77e0d08 --- /dev/null +++ b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts @@ -0,0 +1,160 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { diag } from '@opentelemetry/api'; +import { getEnv } from '@opentelemetry/core'; +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { Resource } from '../Resource'; +import { DetectorSync, ResourceAttributes } from '../types'; +import { ResourceDetectionConfig } from '../config'; +import { IResource } from '../IResource'; + +/** + * EnvDetectorSync can be used to detect the presence of and create a Resource + * from the OTEL_RESOURCE_ATTRIBUTES environment variable. + */ +class EnvDetectorSync implements DetectorSync { + // Type, attribute keys, and attribute values should not exceed 256 characters. + private readonly _MAX_LENGTH = 255; + + // OTEL_RESOURCE_ATTRIBUTES is a comma-separated list of attributes. + private readonly _COMMA_SEPARATOR = ','; + + // OTEL_RESOURCE_ATTRIBUTES contains key value pair separated by '='. + private readonly _LABEL_KEY_VALUE_SPLITTER = '='; + + private readonly _ERROR_MESSAGE_INVALID_CHARS = + 'should be a ASCII string with a length greater than 0 and not exceed ' + + this._MAX_LENGTH + + ' characters.'; + + private readonly _ERROR_MESSAGE_INVALID_VALUE = + 'should be a ASCII string with a length not exceed ' + + this._MAX_LENGTH + + ' characters.'; + + /** + * Returns a {@link Resource} populated with attributes from the + * OTEL_RESOURCE_ATTRIBUTES environment variable. Note this is an async + * function to conform to the Detector interface. + * + * @param config The resource detection config + */ + detect(_config?: ResourceDetectionConfig): IResource { + const attributes: ResourceAttributes = {}; + const env = getEnv(); + + const rawAttributes = env.OTEL_RESOURCE_ATTRIBUTES; + const serviceName = env.OTEL_SERVICE_NAME; + + if (rawAttributes) { + try { + const parsedAttributes = this._parseResourceAttributes(rawAttributes); + Object.assign(attributes, parsedAttributes); + } catch (e) { + diag.debug(`EnvDetectorSync failed: ${e.message}`); + } + } + + if (serviceName) { + attributes[SemanticResourceAttributes.SERVICE_NAME] = serviceName; + } + + return new Resource(attributes); + } + + /** + * Creates an attribute map from the OTEL_RESOURCE_ATTRIBUTES environment + * variable. + * + * OTEL_RESOURCE_ATTRIBUTES: A comma-separated list of attributes describing + * the source in more detail, e.g. “key1=val1,key2=val2”. Domain names and + * paths are accepted as attribute keys. Values may be quoted or unquoted in + * general. If a value contains whitespaces, =, or " characters, it must + * always be quoted. + * + * @param rawEnvAttributes The resource attributes as a comma-seperated list + * of key/value pairs. + * @returns The sanitized resource attributes. + */ + private _parseResourceAttributes( + rawEnvAttributes?: string + ): ResourceAttributes { + if (!rawEnvAttributes) return {}; + + const attributes: ResourceAttributes = {}; + const rawAttributes: string[] = rawEnvAttributes.split( + this._COMMA_SEPARATOR, + -1 + ); + for (const rawAttribute of rawAttributes) { + const keyValuePair: string[] = rawAttribute.split( + this._LABEL_KEY_VALUE_SPLITTER, + -1 + ); + if (keyValuePair.length !== 2) { + continue; + } + let [key, value] = keyValuePair; + // Leading and trailing whitespaces are trimmed. + key = key.trim(); + value = value.trim().split(/^"|"$/).join(''); + if (!this._isValidAndNotEmpty(key)) { + throw new Error(`Attribute key ${this._ERROR_MESSAGE_INVALID_CHARS}`); + } + if (!this._isValid(value)) { + throw new Error(`Attribute value ${this._ERROR_MESSAGE_INVALID_VALUE}`); + } + attributes[key] = decodeURIComponent(value); + } + return attributes; + } + + /** + * Determines whether the given String is a valid printable ASCII string with + * a length not exceed _MAX_LENGTH characters. + * + * @param str The String to be validated. + * @returns Whether the String is valid. + */ + private _isValid(name: string): boolean { + return name.length <= this._MAX_LENGTH && this._isBaggageOctetString(name); + } + + // https://www.w3.org/TR/baggage/#definition + private _isBaggageOctetString(str: string): boolean { + for (let i = 0; i < str.length; i++) { + const ch = str.charCodeAt(i); + if (ch < 0x21 || ch === 0x2c || ch === 0x3b || ch === 0x5c || ch > 0x7e) { + return false; + } + } + return true; + } + + /** + * Determines whether the given String is a valid printable ASCII string with + * a length greater than 0 and not exceed _MAX_LENGTH characters. + * + * @param str The String to be validated. + * @returns Whether the String is valid and not empty. + */ + private _isValidAndNotEmpty(str: string): boolean { + return str.length > 0 && this._isValid(str); + } +} + +export const envDetectorSync = new EnvDetectorSync(); diff --git a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts index 57da341fbe..f2f5ab22d0 100644 --- a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts @@ -15,12 +15,12 @@ */ import { Resource } from '../Resource'; -import { DetectorSync } from '../types'; +import { Detector } from '../types'; import { IResource } from '../IResource'; -export class NoopDetector implements DetectorSync { - detect(): IResource { - return new Resource({}); +export class NoopDetector implements Detector { + detect(): Promise { + return Promise.resolve(new Resource({})); } } diff --git a/packages/opentelemetry-resources/src/detectors/NoopDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/NoopDetectorSync.ts new file mode 100644 index 0000000000..a52473b9c3 --- /dev/null +++ b/packages/opentelemetry-resources/src/detectors/NoopDetectorSync.ts @@ -0,0 +1,27 @@ +/* + * 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 { Resource } from '../Resource'; +import { DetectorSync } from '../types'; +import { IResource } from '../IResource'; + +export class NoopDetectorSync implements DetectorSync { + detect(): IResource { + return new Resource({}); + } +} + +export const noopDetectorSync = new NoopDetectorSync(); diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts index d5ce37bb2b..4a8bedf639 100644 --- a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts @@ -17,7 +17,7 @@ import { diag } from '@opentelemetry/api'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../Resource'; -import { DetectorSync, ResourceAttributes } from '../types'; +import { Detector, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; import { IResource } from '../IResource'; @@ -25,11 +25,11 @@ import { IResource } from '../IResource'; * ProcessDetector will be used to detect the resources related current process running * and being instrumented from the NodeJS Process module. */ -class ProcessDetector implements DetectorSync { - detect(config?: ResourceDetectionConfig): IResource { +class ProcessDetector implements Detector { + detect(config?: ResourceDetectionConfig): Promise { // Skip if not in Node.js environment. if (typeof process !== 'object') { - return Resource.empty(); + return Promise.resolve(Resource.empty()); } const processResource: ResourceAttributes = { [SemanticResourceAttributes.PROCESS_PID]: process.pid, @@ -42,10 +42,12 @@ class ProcessDetector implements DetectorSync { [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'nodejs', [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Node.js', }; - return this._getResourceAttributes(processResource, config); + return Promise.resolve( + this._getResourceAttributes(processResource, config) + ); } /** - * Validates process resource attribute map from process varaibls + * Validates process resource attribute map from process variables * * @param processResource The unsantized resource attributes from process as key/value pairs. * @param config: Config diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts new file mode 100644 index 0000000000..769eb01016 --- /dev/null +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { diag } from '@opentelemetry/api'; +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { Resource } from '../Resource'; +import { DetectorSync, ResourceAttributes } from '../types'; +import { ResourceDetectionConfig } from '../config'; +import { IResource } from '../IResource'; + +/** + * ProcessDetectorSync will be used to detect the resources related current process running + * and being instrumented from the NodeJS Process module. + */ +class ProcessDetectorSync implements DetectorSync { + detect(config?: ResourceDetectionConfig): IResource { + // Skip if not in Node.js environment. + if (typeof process !== 'object') { + return Resource.empty(); + } + const processResource: ResourceAttributes = { + [SemanticResourceAttributes.PROCESS_PID]: process.pid, + [SemanticResourceAttributes.PROCESS_EXECUTABLE_NAME]: process.title || '', + [SemanticResourceAttributes.PROCESS_COMMAND]: process.argv[1] || '', + [SemanticResourceAttributes.PROCESS_COMMAND_LINE]: + process.argv.join(' ') || '', + [SemanticResourceAttributes.PROCESS_RUNTIME_VERSION]: + process.versions.node, + [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'nodejs', + [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Node.js', + }; + return this._getResourceAttributes(processResource, config); + } + /** + * Validates process resource attribute map from process variables + * + * @param processResource The unsantized resource attributes from process as key/value pairs. + * @param config: Config + * @returns The sanitized resource attributes. + */ + private _getResourceAttributes( + processResource: ResourceAttributes, + _config?: ResourceDetectionConfig + ) { + if ( + processResource[SemanticResourceAttributes.PROCESS_EXECUTABLE_NAME] === + '' || + processResource[SemanticResourceAttributes.PROCESS_EXECUTABLE_PATH] === + '' || + processResource[SemanticResourceAttributes.PROCESS_COMMAND] === '' || + processResource[SemanticResourceAttributes.PROCESS_COMMAND_LINE] === '' || + processResource[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION] === '' + ) { + diag.debug( + 'ProcessDetectorSync failed: Unable to find required process resources. ' + ); + return Resource.empty(); + } else { + return new Resource({ + ...processResource, + }); + } + } +} + +export const processDetectorSync = new ProcessDetectorSync(); diff --git a/packages/opentelemetry-resources/src/detectors/index.ts b/packages/opentelemetry-resources/src/detectors/index.ts index 2a16563da5..5ed2b3f868 100644 --- a/packages/opentelemetry-resources/src/detectors/index.ts +++ b/packages/opentelemetry-resources/src/detectors/index.ts @@ -17,3 +17,6 @@ export * from './BrowserDetector'; export * from './EnvDetector'; export * from './ProcessDetector'; +export * from './BrowserDetectorSync'; +export * from './EnvDetectorSync'; +export * from './ProcessDetectorSync'; diff --git a/packages/opentelemetry-resources/src/platform/browser/HostDetectorSync.ts b/packages/opentelemetry-resources/src/platform/browser/HostDetectorSync.ts new file mode 100644 index 0000000000..1962f53028 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/browser/HostDetectorSync.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. + */ + +import { noopDetectorSync } from '../../detectors/NoopDetectorSync'; + +export const hostDetectorSync = noopDetectorSync; diff --git a/packages/opentelemetry-resources/src/platform/browser/OSDetectorSync.ts b/packages/opentelemetry-resources/src/platform/browser/OSDetectorSync.ts new file mode 100644 index 0000000000..416fc3b4ee --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/browser/OSDetectorSync.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. + */ + +import { noopDetectorSync } from '../../detectors/NoopDetectorSync'; + +export const osDetectorSync = noopDetectorSync; diff --git a/packages/opentelemetry-resources/src/platform/browser/index.ts b/packages/opentelemetry-resources/src/platform/browser/index.ts index 51034cc577..b18be97c9c 100644 --- a/packages/opentelemetry-resources/src/platform/browser/index.ts +++ b/packages/opentelemetry-resources/src/platform/browser/index.ts @@ -17,3 +17,5 @@ export * from './default-service-name'; export * from './HostDetector'; export * from './OSDetector'; +export * from './HostDetectorSync'; +export * from './OSDetectorSync'; diff --git a/packages/opentelemetry-resources/src/platform/node/HostDetector.ts b/packages/opentelemetry-resources/src/platform/node/HostDetector.ts index edd44b027f..17680985af 100644 --- a/packages/opentelemetry-resources/src/platform/node/HostDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/HostDetector.ts @@ -16,36 +16,23 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../../Resource'; -import { DetectorSync, ResourceAttributes } from '../../types'; +import { Detector, ResourceAttributes } from '../../types'; import { ResourceDetectionConfig } from '../../config'; import { arch, hostname } from 'os'; +import { IResource } from '../../IResource'; +import { normalizeArch } from './utils'; /** * HostDetector detects the resources related to the host current process is * running on. Currently only non-cloud-based attributes are included. */ -class HostDetector implements DetectorSync { - detect(_config?: ResourceDetectionConfig): Resource { +class HostDetector implements Detector { + detect(_config?: ResourceDetectionConfig): Promise { const attributes: ResourceAttributes = { [SemanticResourceAttributes.HOST_NAME]: hostname(), - [SemanticResourceAttributes.HOST_ARCH]: this._normalizeArch(arch()), + [SemanticResourceAttributes.HOST_ARCH]: normalizeArch(arch()), }; - return new Resource(attributes); - } - - private _normalizeArch(nodeArchString: string): string { - // Maps from https://nodejs.org/api/os.html#osarch to arch values in spec: - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/host.md - switch (nodeArchString) { - case 'arm': - return 'arm32'; - case 'ppc': - return 'ppc32'; - case 'x64': - return 'amd64'; - default: - return nodeArchString; - } + return Promise.resolve(new Resource(attributes)); } } diff --git a/packages/opentelemetry-resources/src/platform/node/HostDetectorSync.ts b/packages/opentelemetry-resources/src/platform/node/HostDetectorSync.ts new file mode 100644 index 0000000000..85bd717e54 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/HostDetectorSync.ts @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { Resource } from '../../Resource'; +import { DetectorSync, ResourceAttributes } from '../../types'; +import { ResourceDetectionConfig } from '../../config'; +import { arch, hostname } from 'os'; +import { normalizeArch } from './utils'; + +/** + * HostDetectorSync detects the resources related to the host current process is + * running on. Currently only non-cloud-based attributes are included. + */ +class HostDetectorSync implements DetectorSync { + detect(_config?: ResourceDetectionConfig): Resource { + const attributes: ResourceAttributes = { + [SemanticResourceAttributes.HOST_NAME]: hostname(), + [SemanticResourceAttributes.HOST_ARCH]: normalizeArch(arch()), + }; + return new Resource(attributes); + } +} + +export const hostDetectorSync = new HostDetectorSync(); diff --git a/packages/opentelemetry-resources/src/platform/node/OSDetector.ts b/packages/opentelemetry-resources/src/platform/node/OSDetector.ts index c560b9e18f..4721a27a35 100644 --- a/packages/opentelemetry-resources/src/platform/node/OSDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/OSDetector.ts @@ -16,34 +16,23 @@ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { Resource } from '../../Resource'; -import { DetectorSync, ResourceAttributes } from '../../types'; +import { Detector, ResourceAttributes } from '../../types'; import { ResourceDetectionConfig } from '../../config'; import { platform, release } from 'os'; +import { IResource } from '../../IResource'; +import { normalizeType } from './utils'; /** * OSDetector detects the resources related to the operating system (OS) on * which the process represented by this resource is running. */ -class OSDetector implements DetectorSync { - detect(_config?: ResourceDetectionConfig): Resource { +class OSDetector implements Detector { + detect(_config?: ResourceDetectionConfig): Promise { const attributes: ResourceAttributes = { - [SemanticResourceAttributes.OS_TYPE]: this._normalizeType(platform()), + [SemanticResourceAttributes.OS_TYPE]: normalizeType(platform()), [SemanticResourceAttributes.OS_VERSION]: release(), }; - return new Resource(attributes); - } - - private _normalizeType(nodePlatform: string): string { - // Maps from https://nodejs.org/api/os.html#osplatform to arch values in spec: - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/os.md - switch (nodePlatform) { - case 'sunos': - return 'solaris'; - case 'win32': - return 'windows'; - default: - return nodePlatform; - } + return Promise.resolve(new Resource(attributes)); } } diff --git a/packages/opentelemetry-resources/src/platform/node/OSDetectorSync.ts b/packages/opentelemetry-resources/src/platform/node/OSDetectorSync.ts new file mode 100644 index 0000000000..9cb6a0385d --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/OSDetectorSync.ts @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { Resource } from '../../Resource'; +import { DetectorSync, ResourceAttributes } from '../../types'; +import { ResourceDetectionConfig } from '../../config'; +import { platform, release } from 'os'; +import { normalizeType } from './utils'; + +/** + * OSDetectorSync detects the resources related to the operating system (OS) on + * which the process represented by this resource is running. + */ +class OSDetectorSync implements DetectorSync { + detect(_config?: ResourceDetectionConfig): Resource { + const attributes: ResourceAttributes = { + [SemanticResourceAttributes.OS_TYPE]: normalizeType(platform()), + [SemanticResourceAttributes.OS_VERSION]: release(), + }; + return new Resource(attributes); + } +} + +export const osDetectorSync = new OSDetectorSync(); diff --git a/packages/opentelemetry-resources/src/platform/node/index.ts b/packages/opentelemetry-resources/src/platform/node/index.ts index 51034cc577..b18be97c9c 100644 --- a/packages/opentelemetry-resources/src/platform/node/index.ts +++ b/packages/opentelemetry-resources/src/platform/node/index.ts @@ -17,3 +17,5 @@ export * from './default-service-name'; export * from './HostDetector'; export * from './OSDetector'; +export * from './HostDetectorSync'; +export * from './OSDetectorSync'; diff --git a/packages/opentelemetry-resources/src/platform/node/utils.ts b/packages/opentelemetry-resources/src/platform/node/utils.ts new file mode 100644 index 0000000000..52f01eaa00 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/utils.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ +export const normalizeArch = (nodeArchString: string): string => { + // Maps from https://nodejs.org/api/os.html#osarch to arch values in spec: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/host.md + switch (nodeArchString) { + case 'arm': + return 'arm32'; + case 'ppc': + return 'ppc32'; + case 'x64': + return 'amd64'; + default: + return nodeArchString; + } +}; + +export const normalizeType = (nodePlatform: string): string => { + // Maps from https://nodejs.org/api/os.html#osplatform to arch values in spec: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/os.md + switch (nodePlatform) { + case 'sunos': + return 'solaris'; + case 'win32': + return 'windows'; + default: + return nodePlatform; + } +}; From 031461e4be3e5f30fb119c747ab5aeee6eba4a42 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 5 Feb 2023 14:09:27 +0200 Subject: [PATCH 64/67] feat(sync-resource-detectors): fixed codecov issues --- .../src/detectors/BrowserDetector.ts | 47 +------ .../src/detectors/EnvDetector.ts | 129 +----------------- .../src/detectors/NoopDetector.ts | 4 +- .../src/detectors/ProcessDetector.ts | 55 +------- .../src/platform/node/HostDetector.ts | 13 +- .../src/platform/node/OSDetector.ts | 13 +- 6 files changed, 22 insertions(+), 239 deletions(-) diff --git a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts index c0383001d3..e50cd9b50b 100644 --- a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts @@ -14,52 +14,19 @@ * limitations under the License. */ -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { Detector, IResource, Resource, ResourceDetectionConfig } from '..'; -import { ResourceAttributes } from '../types'; -import { diag } from '@opentelemetry/api'; +import { + browserDetectorSync, + Detector, + IResource, + ResourceDetectionConfig, +} from '..'; /** * BrowserDetector will be used to detect the resources related to browser. */ class BrowserDetector implements Detector { detect(config?: ResourceDetectionConfig): Promise { - const isBrowser = typeof navigator !== 'undefined'; - if (!isBrowser) { - return Promise.resolve(Resource.empty()); - } - const browserResource: ResourceAttributes = { - [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'browser', - [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Web Browser', - [SemanticResourceAttributes.PROCESS_RUNTIME_VERSION]: navigator.userAgent, - }; - return Promise.resolve( - this._getResourceAttributes(browserResource, config) - ); - } - /** - * Validates process resource attribute map from process variables - * - * @param browserResource The un-sanitized resource attributes from process as key/value pairs. - * @param config: Config - * @returns The sanitized resource attributes. - */ - private _getResourceAttributes( - browserResource: ResourceAttributes, - _config?: ResourceDetectionConfig - ) { - if ( - browserResource[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION] === '' - ) { - diag.debug( - 'BrowserDetector failed: Unable to find required browser resources. ' - ); - return Resource.empty(); - } else { - return new Resource({ - ...browserResource, - }); - } + return Promise.resolve(browserDetectorSync.detect(config)); } } diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts index d74ceb36e1..3467a8123f 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetector.ts @@ -14,38 +14,16 @@ * limitations under the License. */ -import { diag } from '@opentelemetry/api'; -import { getEnv } from '@opentelemetry/core'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { Resource } from '../Resource'; -import { Detector, ResourceAttributes } from '../types'; +import { Detector } from '../types'; import { ResourceDetectionConfig } from '../config'; import { IResource } from '../IResource'; +import { envDetectorSync } from './EnvDetectorSync'; /** * EnvDetector can be used to detect the presence of and create a Resource * from the OTEL_RESOURCE_ATTRIBUTES environment variable. */ class EnvDetector implements Detector { - // Type, attribute keys, and attribute values should not exceed 256 characters. - private readonly _MAX_LENGTH = 255; - - // OTEL_RESOURCE_ATTRIBUTES is a comma-separated list of attributes. - private readonly _COMMA_SEPARATOR = ','; - - // OTEL_RESOURCE_ATTRIBUTES contains key value pair separated by '='. - private readonly _LABEL_KEY_VALUE_SPLITTER = '='; - - private readonly _ERROR_MESSAGE_INVALID_CHARS = - 'should be a ASCII string with a length greater than 0 and not exceed ' + - this._MAX_LENGTH + - ' characters.'; - - private readonly _ERROR_MESSAGE_INVALID_VALUE = - 'should be a ASCII string with a length not exceed ' + - this._MAX_LENGTH + - ' characters.'; - /** * Returns a {@link Resource} populated with attributes from the * OTEL_RESOURCE_ATTRIBUTES environment variable. Note this is an async @@ -53,107 +31,8 @@ class EnvDetector implements Detector { * * @param config The resource detection config */ - detect(_config?: ResourceDetectionConfig): Promise { - const attributes: ResourceAttributes = {}; - const env = getEnv(); - - const rawAttributes = env.OTEL_RESOURCE_ATTRIBUTES; - const serviceName = env.OTEL_SERVICE_NAME; - - if (rawAttributes) { - try { - const parsedAttributes = this._parseResourceAttributes(rawAttributes); - Object.assign(attributes, parsedAttributes); - } catch (e) { - diag.debug(`EnvDetector failed: ${e.message}`); - } - } - - if (serviceName) { - attributes[SemanticResourceAttributes.SERVICE_NAME] = serviceName; - } - - return Promise.resolve(new Resource(attributes)); - } - - /** - * Creates an attribute map from the OTEL_RESOURCE_ATTRIBUTES environment - * variable. - * - * OTEL_RESOURCE_ATTRIBUTES: A comma-separated list of attributes describing - * the source in more detail, e.g. “key1=val1,key2=val2”. Domain names and - * paths are accepted as attribute keys. Values may be quoted or unquoted in - * general. If a value contains whitespaces, =, or " characters, it must - * always be quoted. - * - * @param rawEnvAttributes The resource attributes as a comma-seperated list - * of key/value pairs. - * @returns The sanitized resource attributes. - */ - private _parseResourceAttributes( - rawEnvAttributes?: string - ): ResourceAttributes { - if (!rawEnvAttributes) return {}; - - const attributes: ResourceAttributes = {}; - const rawAttributes: string[] = rawEnvAttributes.split( - this._COMMA_SEPARATOR, - -1 - ); - for (const rawAttribute of rawAttributes) { - const keyValuePair: string[] = rawAttribute.split( - this._LABEL_KEY_VALUE_SPLITTER, - -1 - ); - if (keyValuePair.length !== 2) { - continue; - } - let [key, value] = keyValuePair; - // Leading and trailing whitespaces are trimmed. - key = key.trim(); - value = value.trim().split(/^"|"$/).join(''); - if (!this._isValidAndNotEmpty(key)) { - throw new Error(`Attribute key ${this._ERROR_MESSAGE_INVALID_CHARS}`); - } - if (!this._isValid(value)) { - throw new Error(`Attribute value ${this._ERROR_MESSAGE_INVALID_VALUE}`); - } - attributes[key] = decodeURIComponent(value); - } - return attributes; - } - - /** - * Determines whether the given String is a valid printable ASCII string with - * a length not exceed _MAX_LENGTH characters. - * - * @param str The String to be validated. - * @returns Whether the String is valid. - */ - private _isValid(name: string): boolean { - return name.length <= this._MAX_LENGTH && this._isBaggageOctetString(name); - } - - // https://www.w3.org/TR/baggage/#definition - private _isBaggageOctetString(str: string): boolean { - for (let i = 0; i < str.length; i++) { - const ch = str.charCodeAt(i); - if (ch < 0x21 || ch === 0x2c || ch === 0x3b || ch === 0x5c || ch > 0x7e) { - return false; - } - } - return true; - } - - /** - * Determines whether the given String is a valid printable ASCII string with - * a length greater than 0 and not exceed _MAX_LENGTH characters. - * - * @param str The String to be validated. - * @returns Whether the String is valid and not empty. - */ - private _isValidAndNotEmpty(str: string): boolean { - return str.length > 0 && this._isValid(str); + detect(config?: ResourceDetectionConfig): Promise { + return Promise.resolve(envDetectorSync.detect(config)); } } diff --git a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts index f2f5ab22d0..463d8c629f 100644 --- a/packages/opentelemetry-resources/src/detectors/NoopDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/NoopDetector.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { Resource } from '../Resource'; import { Detector } from '../types'; import { IResource } from '../IResource'; +import { noopDetectorSync } from './NoopDetectorSync'; export class NoopDetector implements Detector { detect(): Promise { - return Promise.resolve(new Resource({})); + return Promise.resolve(noopDetectorSync.detect()); } } diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts index 4a8bedf639..b1165f2c5b 100644 --- a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { diag } from '@opentelemetry/api'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { Resource } from '../Resource'; -import { Detector, ResourceAttributes } from '../types'; +import { Detector } from '../types'; import { ResourceDetectionConfig } from '../config'; import { IResource } from '../IResource'; +import { processDetectorSync } from './ProcessDetectorSync'; /** * ProcessDetector will be used to detect the resources related current process running @@ -27,54 +25,7 @@ import { IResource } from '../IResource'; */ class ProcessDetector implements Detector { detect(config?: ResourceDetectionConfig): Promise { - // Skip if not in Node.js environment. - if (typeof process !== 'object') { - return Promise.resolve(Resource.empty()); - } - const processResource: ResourceAttributes = { - [SemanticResourceAttributes.PROCESS_PID]: process.pid, - [SemanticResourceAttributes.PROCESS_EXECUTABLE_NAME]: process.title || '', - [SemanticResourceAttributes.PROCESS_COMMAND]: process.argv[1] || '', - [SemanticResourceAttributes.PROCESS_COMMAND_LINE]: - process.argv.join(' ') || '', - [SemanticResourceAttributes.PROCESS_RUNTIME_VERSION]: - process.versions.node, - [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'nodejs', - [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Node.js', - }; - return Promise.resolve( - this._getResourceAttributes(processResource, config) - ); - } - /** - * Validates process resource attribute map from process variables - * - * @param processResource The unsantized resource attributes from process as key/value pairs. - * @param config: Config - * @returns The sanitized resource attributes. - */ - private _getResourceAttributes( - processResource: ResourceAttributes, - _config?: ResourceDetectionConfig - ) { - if ( - processResource[SemanticResourceAttributes.PROCESS_EXECUTABLE_NAME] === - '' || - processResource[SemanticResourceAttributes.PROCESS_EXECUTABLE_PATH] === - '' || - processResource[SemanticResourceAttributes.PROCESS_COMMAND] === '' || - processResource[SemanticResourceAttributes.PROCESS_COMMAND_LINE] === '' || - processResource[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION] === '' - ) { - diag.debug( - 'ProcessDetector failed: Unable to find required process resources. ' - ); - return Resource.empty(); - } else { - return new Resource({ - ...processResource, - }); - } + return Promise.resolve(processDetectorSync.detect(config)); } } diff --git a/packages/opentelemetry-resources/src/platform/node/HostDetector.ts b/packages/opentelemetry-resources/src/platform/node/HostDetector.ts index 17680985af..daf0b8e045 100644 --- a/packages/opentelemetry-resources/src/platform/node/HostDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/HostDetector.ts @@ -14,13 +14,10 @@ * limitations under the License. */ -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { Resource } from '../../Resource'; -import { Detector, ResourceAttributes } from '../../types'; +import { Detector } from '../../types'; import { ResourceDetectionConfig } from '../../config'; -import { arch, hostname } from 'os'; import { IResource } from '../../IResource'; -import { normalizeArch } from './utils'; +import { hostDetectorSync } from './HostDetectorSync'; /** * HostDetector detects the resources related to the host current process is @@ -28,11 +25,7 @@ import { normalizeArch } from './utils'; */ class HostDetector implements Detector { detect(_config?: ResourceDetectionConfig): Promise { - const attributes: ResourceAttributes = { - [SemanticResourceAttributes.HOST_NAME]: hostname(), - [SemanticResourceAttributes.HOST_ARCH]: normalizeArch(arch()), - }; - return Promise.resolve(new Resource(attributes)); + return Promise.resolve(hostDetectorSync.detect(_config)); } } diff --git a/packages/opentelemetry-resources/src/platform/node/OSDetector.ts b/packages/opentelemetry-resources/src/platform/node/OSDetector.ts index 4721a27a35..7ee528d497 100644 --- a/packages/opentelemetry-resources/src/platform/node/OSDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/OSDetector.ts @@ -14,13 +14,10 @@ * limitations under the License. */ -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { Resource } from '../../Resource'; -import { Detector, ResourceAttributes } from '../../types'; +import { Detector } from '../../types'; import { ResourceDetectionConfig } from '../../config'; -import { platform, release } from 'os'; import { IResource } from '../../IResource'; -import { normalizeType } from './utils'; +import { osDetectorSync } from './OSDetectorSync'; /** * OSDetector detects the resources related to the operating system (OS) on @@ -28,11 +25,7 @@ import { normalizeType } from './utils'; */ class OSDetector implements Detector { detect(_config?: ResourceDetectionConfig): Promise { - const attributes: ResourceAttributes = { - [SemanticResourceAttributes.OS_TYPE]: normalizeType(platform()), - [SemanticResourceAttributes.OS_VERSION]: release(), - }; - return Promise.resolve(new Resource(attributes)); + return Promise.resolve(osDetectorSync.detect(_config)); } } From c2d6760bab1ef588855bc4d55b38685073832c66 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 5 Feb 2023 14:20:22 +0200 Subject: [PATCH 65/67] feat(sync-resource-detectors): fix typo --- .../opentelemetry-resources/src/detectors/EnvDetectorSync.ts | 2 +- .../src/detectors/ProcessDetectorSync.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts index baf77e0d08..1230657e19 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts @@ -65,7 +65,7 @@ class EnvDetectorSync implements DetectorSync { const parsedAttributes = this._parseResourceAttributes(rawAttributes); Object.assign(attributes, parsedAttributes); } catch (e) { - diag.debug(`EnvDetectorSync failed: ${e.message}`); + diag.debug(`EnvDetector failed: ${e.message}`); } } diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts index 769eb01016..d63b856e3e 100644 --- a/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetectorSync.ts @@ -65,7 +65,7 @@ class ProcessDetectorSync implements DetectorSync { processResource[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION] === '' ) { diag.debug( - 'ProcessDetectorSync failed: Unable to find required process resources. ' + 'ProcessDetector failed: Unable to find required process resources. ' ); return Resource.empty(); } else { From 0f1dff0ad27b8a541a25ba5e9a4a87603bf78c40 Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 5 Feb 2023 16:30:08 +0200 Subject: [PATCH 66/67] feat(sync-resource-detectors): using optional chaining --- .../opentelemetry-sdk-node/test/sdk.test.ts | 16 ++++++++-------- .../test/Resource.test.ts | 8 ++++---- .../test/detect-resources.test.ts | 6 +++--- .../src/export/BatchSpanProcessorBase.ts | 3 +-- .../src/export/PeriodicExportingMetricReader.ts | 5 ++--- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 96138b2547..60b493435b 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -393,7 +393,7 @@ describe('Node SDK', () => { }); sdk.detectResources(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assert.strictEqual(resource.attributes['customAttr'], 'someValue'); @@ -423,7 +423,7 @@ describe('Node SDK', () => { sdk.detectResources(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assertServiceResource(resource, { instanceId: '627cc493', @@ -472,7 +472,7 @@ describe('Node SDK', () => { ); sdk.detectResources(); - await (sdk['_resource'] as Resource).waitForAsyncAttributes(); + await sdk['_resource'].waitForAsyncAttributes?.(); // Test that the Env Detector successfully found its resource and populated it with the right values. assert.ok( @@ -537,7 +537,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assertServiceResource(resource, { name: 'env-set-name', @@ -553,7 +553,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assertServiceResource(resource, { name: 'config-set-name', @@ -568,7 +568,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assertServiceResource(resource, { name: 'resource-env-set-name', @@ -585,7 +585,7 @@ describe('Node SDK', () => { sdk.start(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assertServiceResource(resource, { name: 'config-set-name', @@ -660,7 +660,7 @@ describe('Node SDK', () => { }); sdk.detectResources(); const resource = sdk['_resource']; - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assert.deepStrictEqual(resource, Resource.empty()); }); diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 9372c62d15..df9430af75 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -204,7 +204,7 @@ describe('Resource', () => { .merge(resource3) .merge(resource4); - await (merged as Resource).waitForAsyncAttributes(); + await merged.waitForAsyncAttributes?.(); assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', @@ -227,7 +227,7 @@ describe('Resource', () => { const merged = resource1.merge(resource2); - await (merged as Resource).waitForAsyncAttributes(); + await merged.waitForAsyncAttributes?.(); assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', @@ -253,7 +253,7 @@ describe('Resource', () => { const merged = resource1.merge(resource2); - await (merged as Resource).waitForAsyncAttributes(); + await merged.waitForAsyncAttributes?.(); assert.deepStrictEqual(merged.attributes, { promise1: 'promise1val', @@ -350,7 +350,7 @@ describe('Resource', () => { assert.strictEqual(mergedResource.attributes['fromold'], 'fromold'); - await (mergedResource as Resource).waitForAsyncAttributes(); + await mergedResource.waitForAsyncAttributes?.(); assert.strictEqual(mergedResource.attributes['fromnew'], 'fromnew'); }); diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts index 12943bc18b..0db97057db 100644 --- a/packages/opentelemetry-resources/test/detect-resources.test.ts +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -35,7 +35,7 @@ describe('detectResourcesSync', () => { detectors: [detector], }); - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assert.deepStrictEqual(resource.attributes, { sync: 'fromsync', }); @@ -56,7 +56,7 @@ describe('detectResourcesSync', () => { // before waiting, it should already have the sync resources assert.deepStrictEqual(resource.attributes, { sync: 'fromsync' }); - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assert.deepStrictEqual(resource.attributes, { sync: 'fromsync', async: 'fromasync', @@ -89,7 +89,7 @@ describe('detectResourcesSync', () => { detectors: [new DetectorRejects(), new DetectorOk()], }); - await (resource as Resource).waitForAsyncAttributes(); + await resource.waitForAsyncAttributes?.(); assert.ok( debugStub.calledWithMatch( diff --git a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts index cb84163d53..2f14b77c74 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts @@ -28,7 +28,6 @@ import { SpanProcessor } from '../SpanProcessor'; import { BufferConfig } from '../types'; import { ReadableSpan } from './ReadableSpan'; import { SpanExporter } from './SpanExporter'; -import { Resource } from '@opentelemetry/resources'; /** * Implementation of the {@link SpanProcessor} that batches spans exported by @@ -186,7 +185,7 @@ export abstract class BatchSpanProcessorBase } else { Promise.all( pendingResources.map(resource => - (resource as Resource).waitForAsyncAttributes() + resource.waitForAsyncAttributes?.() ) ).then(doExport, err => { globalErrorHandler(err); diff --git a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts index 73e314211f..2371ecb67f 100644 --- a/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts +++ b/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts @@ -25,7 +25,6 @@ import { MetricReader } from './MetricReader'; import { PushMetricExporter } from './MetricExporter'; import { callWithTimeout, TimeoutError } from '../utils'; import { diag } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; export type PeriodicExportingMetricReaderOptions = { /** @@ -130,8 +129,8 @@ export class PeriodicExportingMetricReader extends MetricReader { // Avoid scheduling a promise to make the behavior more predictable and easier to test if (resourceMetrics.resource.asyncAttributesPending) { - (resourceMetrics.resource as Resource) - .waitForAsyncAttributes() + resourceMetrics.resource + .waitForAsyncAttributes?.() .then(doExport, err => diag.debug('Error while resolving async portion of resource: ', err) ); From f14271f6133a467fab0896f239b746bc1517890c Mon Sep 17 00:00:00 2001 From: Sami Musallam Date: Sun, 5 Feb 2023 16:30:27 +0200 Subject: [PATCH 67/67] feat(sync-resource-detectors): updated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa72dde947..df70504c43 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ These instrumentations are hosted at