diff --git a/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts new file mode 100644 index 0000000000..a3568c2958 --- /dev/null +++ b/packages/opentelemetry-resources/src/detectors/BrowserDetector.ts @@ -0,0 +1,65 @@ +/* + * 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 { Detector, 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 { + const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; + if (!isBrowser) { + return Resource.empty(); + } + const browserResource: ResourceAttributes = { + [SemanticResourceAttributes.PROCESS_RUNTIME_NAME]: 'browser', + [SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION]: 'Web Browser', + [SemanticResourceAttributes.PROCESS_RUNTIME_VERSION]: window.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 browserDetector = new BrowserDetector(); diff --git a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts index 12e9e296bf..3c6e03d92c 100644 --- a/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts +++ b/packages/opentelemetry-resources/src/detectors/ProcessDetector.ts @@ -36,6 +36,9 @@ class ProcessDetector implements Detector { [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); } @@ -56,7 +59,8 @@ class ProcessDetector implements Detector { processResource[SemanticResourceAttributes.PROCESS_EXECUTABLE_PATH] === '' || processResource[SemanticResourceAttributes.PROCESS_COMMAND] === '' || - processResource[SemanticResourceAttributes.PROCESS_COMMAND_LINE] === '' + processResource[SemanticResourceAttributes.PROCESS_COMMAND_LINE] === '' || + processResource[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION] === '' ) { diag.debug( 'ProcessDetector failed: Unable to find required process resources. ' diff --git a/packages/opentelemetry-resources/src/detectors/index.ts b/packages/opentelemetry-resources/src/detectors/index.ts index d88f7412d3..3a69a099bc 100644 --- a/packages/opentelemetry-resources/src/detectors/index.ts +++ b/packages/opentelemetry-resources/src/detectors/index.ts @@ -16,3 +16,4 @@ export * from './EnvDetector'; export * from './ProcessDetector'; +export * from './BrowserDetector'; diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts index 3a815e8825..457965fadf 100644 --- a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -15,12 +15,35 @@ */ import { Resource } from '../../Resource'; +import { ResourceDetectionConfig } from '../../config'; +import { diag } from '@opentelemetry/api'; /** - * Detects resources for the browser platform, which is currently only the - * telemetry SDK resource. More could be added in the future. This method - * is async to match the signature of corresponding method for node. + * Runs all resource detectors and returns the results merged into a single + * Resource. + * + * @param config Configuration for resource detection */ -export const detectResources = async (): Promise => { - return Resource.empty(); +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() + ); }; diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts index 74a4d337b2..35bf32b4cd 100644 --- a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts +++ b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts @@ -30,7 +30,7 @@ export const detectResources = async ( ): Promise => { const internalConfig: ResourceDetectionConfig = Object.assign(config); - const resources: Array = await Promise.all( + const resources: Resource[] = await Promise.all( (internalConfig.detectors || []).map(async d => { try { const resource = await d.detect(internalConfig); @@ -52,10 +52,11 @@ export const detectResources = async ( ); }; + /** * Writes debug information about the detected resources to the logger defined in the resource detection config, if one is provided. * - * @param resources The array of {@link Resource} that should be logged. Empty entried will be ignored. + * @param resources The array of {@link Resource} that should be logged. Empty entries will be ignored. */ const logResources = (resources: Array) => { resources.forEach(resource => { diff --git a/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts b/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts new file mode 100644 index 0000000000..7543d29438 --- /dev/null +++ b/packages/opentelemetry-resources/test/detectors/browser/BrowserDetector.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as sinon from 'sinon'; +import { Resource } from '../../../src'; +import { browserDetector } from '../../../src/detectors/BrowserDetector'; +import { describeBrowser } from '../../util'; +import { + assertResource, + assertEmptyResource, +} from '../../util/resource-assertions'; + + +describeBrowser('browserDetector()', () => { + + afterEach(() => { + sinon.restore(); + }); + + it('should return browser information', async () => { + sinon.stub(window, 'navigator').value({ + userAgent: 'dddd', + }); + + const resource: Resource = await browserDetector.detect(); + assertResource(resource, { + version: 'dddd', + runtimeDescription: 'Web Browser', + runtimeName: 'browser', + }); + }); + it('should return empty resources if version is missing', async () => { + sinon.stub(window, 'navigator').value({ + userAgent: '', + }); + const resource: Resource = await browserDetector.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 new file mode 100644 index 0000000000..27d7bdfeff --- /dev/null +++ b/packages/opentelemetry-resources/test/detectors/node/BrowserDetector.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Resource } 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(); + assertEmptyResource(resource); + }); +}); + diff --git a/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts b/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts index a76c728c29..2357c13479 100644 --- a/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts +++ b/packages/opentelemetry-resources/test/detectors/node/ProcessDetector.test.ts @@ -16,7 +16,7 @@ import * as sinon from 'sinon'; import { processDetector, Resource } from '../../../src'; import { - assertProcessResource, + assertResource, assertEmptyResource, } from '../../util/resource-assertions'; import { describeNode } from '../../util'; @@ -32,13 +32,19 @@ describeNode('processDetector() on Node.js', () => { sinon .stub(process, 'argv') .value(['/tmp/node', '/home/ot/test.js', 'arg1', 'arg2']); + sinon + .stub(process, 'versions') + .value({'node': '1.4.1'}); const resource: Resource = await processDetector.detect(); - assertProcessResource(resource, { + assertResource(resource, { pid: 1234, name: 'otProcess', command: '/home/ot/test.js', commandLine: '/tmp/node /home/ot/test.js arg1 arg2', + version: '1.4.1', + runtimeDescription: 'Node.js', + runtimeName: 'nodejs', }); }); it('should return empty resources if title, command and commondLine is missing', async () => { diff --git a/packages/opentelemetry-resources/test/util/resource-assertions.ts b/packages/opentelemetry-resources/test/util/resource-assertions.ts index 37fd429d47..32f37e4486 100644 --- a/packages/opentelemetry-resources/test/util/resource-assertions.ts +++ b/packages/opentelemetry-resources/test/util/resource-assertions.ts @@ -258,18 +258,21 @@ export const assertServiceResource = ( }; /** - * Test utility method to validate a process resources + * Test utility method to validate a process / browser resources * * @param resource the Resource to validate * @param validations validations for the resource attributes */ -export const assertProcessResource = ( +export const assertResource = ( resource: Resource, validations: { pid?: number; name?: string; command?: string; commandLine?: string; + version?: string; + runtimeName?: string; + runtimeDescription?: string; } ) => { assert.strictEqual( @@ -294,6 +297,24 @@ export const assertProcessResource = ( validations.commandLine ); } + if (validations.version) { + assert.strictEqual( + resource.attributes[SemanticResourceAttributes.PROCESS_RUNTIME_VERSION], + validations.version + ); + } + if (validations.runtimeName) { + assert.strictEqual( + resource.attributes[SemanticResourceAttributes.PROCESS_RUNTIME_NAME], + validations.runtimeName + ); + } + if (validations.runtimeDescription) { + assert.strictEqual( + resource.attributes[SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION], + validations.runtimeDescription + ); + } }; export const assertWebEngineResource = (resource: Resource, validations: {