From 1650ee5c949fcbb72d63239b197e2306c556987b Mon Sep 17 00:00:00 2001 From: Siim Kallas Date: Wed, 28 Aug 2024 16:40:49 +0300 Subject: [PATCH] add resourceFactory option for traces (#938) * add resource factory for traces * update resource factory docs --- docs/advanced-config.md | 2 ++ src/metrics/index.ts | 2 +- src/tracing/options.ts | 20 ++++++++++++-------- src/types.ts | 4 ++++ test/options.test.ts | 28 ++++++++++++++++++++++++---- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/docs/advanced-config.md b/docs/advanced-config.md index d3651a60..c402d8f8 100644 --- a/docs/advanced-config.md +++ b/docs/advanced-config.md @@ -80,6 +80,8 @@ The following config options can be set by passing them as tracing arguments to - `tracing.tracerConfig`: A JS object that is merged into the default tracer config replacing any existing keys. It's passed to the tracer provider during initialization. This can be used to customize the tracer provider or tracer. Must satisfy [`TracerConfig` interface](https://github.com/open-telemetry/opentelemetry-js/blob/71ba83a0dc51118e08e3148c788b81fe711003e7/packages/opentelemetry-tracing/src/types.ts#L26) +- `tracing.resourceFactory`: A function that is invoked with the default resource detected from the environment. Can be used to change the detected attributes or return a completely new resource. + - `tracing.spanExporterFactory`: A function that accepts the tracing options. Returns a new instance of SpanExporter. When set, this function is used to create a new exporter and the returned exporter will be used in the pipeline. - `tracing.spanProcessorFactory`: Returns a SpanProcessor instance or an array of SpanProcessor instances. When set, this function is be used to create one or more span processors. The returned processors are added to the global tracer provider and configured to process all spans generated by any tracer provider by the global provider. The function creates a new span exporter and uses it with each processor it creates. It may call `options.spanExporterFactory(options)` to create a new exporter as configured by the user. diff --git a/src/metrics/index.ts b/src/metrics/index.ts index 6873a7bf..5311ac28 100644 --- a/src/metrics/index.ts +++ b/src/metrics/index.ts @@ -39,9 +39,9 @@ import * as util from 'util'; import { detect as detectResource } from '../resource'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { ConsoleMetricExporter } from './ConsoleMetricExporter'; +import type { ResourceFactory } from '../types'; export type MetricReaderFactory = (options: MetricsOptions) => MetricReader[]; -export type ResourceFactory = (resource: Resource) => Resource; interface MetricsOptions { accessToken: string; diff --git a/src/tracing/options.ts b/src/tracing/options.ts index fbbdc3e2..6dafdd2c 100644 --- a/src/tracing/options.ts +++ b/src/tracing/options.ts @@ -35,7 +35,7 @@ import { getNonEmptyEnvVar, } from '../utils'; import { NodeTracerConfig } from '@opentelemetry/sdk-trace-node'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { diag, Span, TextMapPropagator } from '@opentelemetry/api'; import { CompositePropagator, @@ -44,6 +44,7 @@ import { } from '@opentelemetry/core'; import { SplunkBatchSpanProcessor } from './SplunkBatchSpanProcessor'; import { Resource } from '@opentelemetry/resources'; +import type { ResourceFactory } from '../types'; type SpanExporterFactory = (options: Options) => SpanExporter | SpanExporter[]; @@ -67,6 +68,7 @@ export interface Options { captureHttpRequestUriParams: string[] | CaptureHttpUriParameters; instrumentations: (Instrumentation | Instrumentation[])[]; propagatorFactory: PropagatorFactory; + resourceFactory: ResourceFactory; serverTimingEnabled: boolean; spanExporterFactory: SpanExporterFactory; spanProcessorFactory: SpanProcessorFactory; @@ -80,6 +82,7 @@ export const allowedTracingOptions = [ 'endpoint', 'instrumentations', 'propagatorFactory', + 'resourceFactory', 'serverTimingEnabled', 'serviceName', 'spanExporterFactory', @@ -115,12 +118,15 @@ export function _setDefaultOptions(options: Partial = {}): Options { const extraTracerConfig = options.tracerConfig || {}; - let resource = detectResource(); + const envResource = detectResource(); + + const resourceFactory = options.resourceFactory || ((r: Resource) => r); + let resource = resourceFactory(envResource); const serviceName = options.serviceName || getNonEmptyEnvVar('OTEL_SERVICE_NAME') || - resource.attributes[SemanticResourceAttributes.SERVICE_NAME]; + resource.attributes[SEMRESATTRS_SERVICE_NAME]; if (!serviceName) { diag.warn( @@ -132,8 +138,7 @@ export function _setDefaultOptions(options: Partial = {}): Options { resource = resource.merge( new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: - serviceName || defaultServiceName(), + [SEMRESATTRS_SERVICE_NAME]: serviceName || defaultServiceName(), }) ); @@ -172,9 +177,7 @@ export function _setDefaultOptions(options: Partial = {}): Options { return { realm: options.realm, endpoint: options.endpoint, - serviceName: String( - resource.attributes[SemanticResourceAttributes.SERVICE_NAME] - ), + serviceName: String(resource.attributes[SEMRESATTRS_SERVICE_NAME]), accessToken: options.accessToken, serverTimingEnabled: options.serverTimingEnabled, instrumentations: options.instrumentations, @@ -182,6 +185,7 @@ export function _setDefaultOptions(options: Partial = {}): Options { spanExporterFactory: options.spanExporterFactory, spanProcessorFactory: options.spanProcessorFactory, propagatorFactory: options.propagatorFactory, + resourceFactory, captureHttpRequestUriParams: options.captureHttpRequestUriParams, }; } diff --git a/src/types.ts b/src/types.ts index e258ad8c..5217afab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,8 +14,12 @@ * limitations under the License. */ +import type { Resource } from '@opentelemetry/resources'; + export type LogLevel = 'none' | 'verbose' | 'debug' | 'info' | 'warn' | 'error'; +export type ResourceFactory = (resource: Resource) => Resource; + export type EnvVarKey = | 'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT' | 'OTEL_BSP_SCHEDULE_DELAY' diff --git a/test/options.test.ts b/test/options.test.ts index 2377ea87..dd2f0bf5 100644 --- a/test/options.test.ts +++ b/test/options.test.ts @@ -217,6 +217,10 @@ describe('options', () => { const testInstrumentation = new TestInstrumentation('inst', '1.0', {}); const idGenerator = new TestIdGenerator(); + const resourceFactory = (resource: Resource) => { + return resource; + }; + const options = _setDefaultOptions({ realm: 'rlm', endpoint: 'custom-endpoint', @@ -224,11 +228,10 @@ describe('options', () => { accessToken: 'custom-access-token', instrumentations: [testInstrumentation], tracerConfig: { - resource: new Resource({ - attr1: 'value', - }), + resource: new Resource({ attr1: 'value1' }), idGenerator: idGenerator, }, + resourceFactory, spanExporterFactory: testSpanExporterFactory, spanProcessorFactory: testSpanProcessorFactory, propagatorFactory: testPropagatorFactory, @@ -242,8 +245,9 @@ describe('options', () => { accessToken: 'custom-access-token', serverTimingEnabled: true, instrumentations: [testInstrumentation], + resourceFactory, tracerConfig: { - resource: new Resource({ attr1: 'value' }), + resource: new Resource({ attr1: 'value1' }), idGenerator: idGenerator, }, spanExporterFactory: testSpanExporterFactory, @@ -259,6 +263,22 @@ describe('options', () => { ); }); + it('is possible to provide additional resource attributes', () => { + const options = _setDefaultOptions({ + resourceFactory: (resource) => { + return resource.merge( + new Resource({ 'splunk.distro.version': 'v9001', abc: 42 }) + ); + }, + }); + + assert.strictEqual( + options.tracerConfig.resource?.attributes['splunk.distro.version'], + 'v9001' + ); + assert.strictEqual(options.tracerConfig.resource?.attributes['abc'], 42); + }); + describe('OTEL_TRACES_EXPORTER', () => { it('accepts a single key', () => { process.env.OTEL_TRACES_EXPORTER = 'console';