diff --git a/change/@splunk-otel-7380d88c-bd30-4b35-aa18-8cc7a6b9192e.json b/change/@splunk-otel-7380d88c-bd30-4b35-aa18-8cc7a6b9192e.json new file mode 100644 index 00000000..f7cf9e40 --- /dev/null +++ b/change/@splunk-otel-7380d88c-bd30-4b35-aa18-8cc7a6b9192e.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add splunk.distro.version resource attribute", + "packageName": "@splunk/otel", + "email": "rauno56@gmail.com", + "dependentChangeType": "patch" +} diff --git a/src/detectors/DistroDetector.ts b/src/detectors/DistroDetector.ts new file mode 100644 index 00000000..69180daf --- /dev/null +++ b/src/detectors/DistroDetector.ts @@ -0,0 +1,37 @@ +/* + * Copyright Splunk Inc. + * + * 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 + * + * http://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, + ResourceAttributes, + ResourceDetectionConfig, +} from '@opentelemetry/resources'; +import { VERSION } from '../version'; + +/** + * DistroDetector will be used to detect `splunk.distro.version` and other + * distro-related resource information. + */ +class DistroDetector { + detect(_config?: ResourceDetectionConfig): Resource { + const distroAttributes: ResourceAttributes = { + 'splunk.distro.version': VERSION, + }; + return new Resource(distroAttributes); + } +} + +export const distroDetector = new DistroDetector(); diff --git a/src/resource.ts b/src/resource.ts index 6e8a4570..7d94281e 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -17,12 +17,19 @@ import { diag } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; +import { distroDetector } from './detectors/DistroDetector'; import { envDetector } from './detectors/EnvDetector'; import { hostDetector } from './detectors/HostDetector'; import { osDetector } from './detectors/OSDetector'; import { processDetector } from './detectors/ProcessDetector'; -const detectors = [envDetector, hostDetector, osDetector, processDetector]; +const detectors = [ + distroDetector, + envDetector, + hostDetector, + osDetector, + processDetector, +]; export const detect = (): Resource => { return detectors diff --git a/test/options.test.ts b/test/options.test.ts index 7bb838a8..0a1f19f1 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -64,6 +64,7 @@ const expectedAttributes = new Set([ SemanticResourceAttributes.PROCESS_RUNTIME_DESCRIPTION, SemanticResourceAttributes.PROCESS_RUNTIME_NAME, SemanticResourceAttributes.PROCESS_RUNTIME_VERSION, + 'splunk.distro.version', ]); describe('options', () => { @@ -84,51 +85,65 @@ describe('options', () => { api.diag.disable(); }); - it('has expected defaults', () => { - // Mock the default `getInstrumentations` in case some instrumentations (e.g. http) are part of dev dependencies. - const getInstrumentationsStub = sinon - .stub(instrumentations, 'getInstrumentations') - .returns([]); - const options = _setDefaultOptions(); - - // resource attributes for process, host and os are different at each run, iterate through them, make sure they exist and then delete - Object.keys(options.tracerConfig.resource.attributes) - .filter(attribute => { - return expectedAttributes.has(attribute); - }) - .forEach(processAttribute => { - assert(options.tracerConfig.resource.attributes[processAttribute]); - delete options.tracerConfig.resource.attributes[processAttribute]; - }); + describe('defaults', () => { + let getInstrumentationsStub; + beforeEach(() => { + // Mock the default `getInstrumentations` in case some instrumentations (e.g. http) are part of dev dependencies. + getInstrumentationsStub = sinon + .stub(instrumentations, 'getInstrumentations') + .returns([]); + }); - assert.deepStrictEqual(options, { - /* - let the OTel exporter package itself - resolve the default for endpoint. - */ - endpoint: undefined, - serviceName: 'unnamed-node-service', - accessToken: '', - serverTimingEnabled: true, - instrumentations: [], - tracerConfig: { - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'unnamed-node-service', - }), - }, - spanExporterFactory: otlpSpanExporterFactory, - spanProcessorFactory: defaultSpanProcessorFactory, - propagatorFactory: defaultPropagatorFactory, - captureHttpRequestUriParams: [], + afterEach(() => { + getInstrumentationsStub.restore(); }); - sinon.assert.calledWithMatch(logger.warn, MATCH_SERVICE_NAME_WARNING); - sinon.assert.calledWithMatch( - logger.warn, - MATCH_NO_INSTRUMENTATIONS_WARNING - ); + it('has expected defaults', () => { + const options = _setDefaultOptions(); + + assert( + /[0-9]+\.[0-9]+\.[0-9]+/.test( + options.tracerConfig.resource.attributes['splunk.distro.version'] + ) + ); + + // resource attributes for process, host and os are different at each run, iterate through them, make sure they exist and then delete + Object.keys(options.tracerConfig.resource.attributes) + .filter(attribute => { + return expectedAttributes.has(attribute); + }) + .forEach(processAttribute => { + assert(options.tracerConfig.resource.attributes[processAttribute]); + delete options.tracerConfig.resource.attributes[processAttribute]; + }); + + assert.deepStrictEqual(options, { + /* + let the OTel exporter package itself + resolve the default for endpoint. + */ + endpoint: undefined, + serviceName: 'unnamed-node-service', + accessToken: '', + serverTimingEnabled: true, + instrumentations: [], + tracerConfig: { + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'unnamed-node-service', + }), + }, + spanExporterFactory: otlpSpanExporterFactory, + spanProcessorFactory: defaultSpanProcessorFactory, + propagatorFactory: defaultPropagatorFactory, + captureHttpRequestUriParams: [], + }); - getInstrumentationsStub.restore(); + sinon.assert.calledWithMatch(logger.warn, MATCH_SERVICE_NAME_WARNING); + sinon.assert.calledWithMatch( + logger.warn, + MATCH_NO_INSTRUMENTATIONS_WARNING + ); + }); }); it('accepts and applies configuration', () => {