From bec77913bc4bff2a887085ab96085157c0085546 Mon Sep 17 00:00:00 2001 From: Weyert de Boer Date: Mon, 6 Sep 2021 19:59:34 +0100 Subject: [PATCH 1/3] feat: upgrade semantic conventions to the latest 1.6.1 version (#2456) Regenerated the `@opentelemetry-js/semantic-conventions`-package to use the latest definitions from the Opentelemetry specification Co-authored-by: Weyert de Boer Co-authored-by: Valentin Marchaud --- .../resource/SemanticResourceAttributes.ts | 10 +- .../src/trace/SemanticAttributes.ts | 96 +++++++++++++++++++ scripts/semconv/generate.sh | 4 +- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts b/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts index 67f0235179..382e315a24 100644 --- a/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts +++ b/packages/opentelemetry-semantic-conventions/src/resource/SemanticResourceAttributes.ts @@ -28,14 +28,14 @@ export const SemanticResourceAttributes = { CLOUD_ACCOUNT_ID: 'cloud.account.id', /** - * The geographical region the resource is running. Refer to your provider's docs to see the available regions, for example [AWS regions](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/), [Azure regions](https://azure.microsoft.com/en-us/global-infrastructure/geographies/), or [Google Cloud regions](https://cloud.google.com/about/locations). + * The geographical region the resource is running. Refer to your provider's docs to see the available regions, for example [Alibaba Cloud regions](https://www.alibabacloud.com/help/doc-detail/40654.htm), [AWS regions](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/), [Azure regions](https://azure.microsoft.com/en-us/global-infrastructure/geographies/), or [Google Cloud regions](https://cloud.google.com/about/locations). */ CLOUD_REGION: 'cloud.region', /** * Cloud regions often have multiple, isolated locations known as zones to increase availability. Availability zone represents the zone where the resource is running. * - * Note: Availability zones are called "zones" on Google Cloud. + * Note: Availability zones are called "zones" on Alibaba Cloud and Google Cloud. */ CLOUD_AVAILABILITY_ZONE: 'cloud.availability_zone', @@ -480,6 +480,8 @@ As an alternative, consider setting `faas.id` as a span attribute instead. export enum CloudProviderValues { + /** Alibaba Cloud. */ + ALIBABA_CLOUD = 'alibaba_cloud', /** Amazon Web Services. */ AWS = 'aws', /** Microsoft Azure. */ @@ -492,6 +494,10 @@ export enum CloudProviderValues { export enum CloudPlatformValues { + /** Alibaba Cloud Elastic Compute Service. */ + ALIBABA_CLOUD_ECS = 'alibaba_cloud_ecs', + /** Alibaba Cloud Function Compute. */ + ALIBABA_CLOUD_FC = 'alibaba_cloud_fc', /** AWS Elastic Compute Cloud. */ AWS_EC2 = 'aws_ec2', /** AWS Elastic Container Service. */ diff --git a/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts b/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts index 0450508f60..eb7260c073 100644 --- a/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts +++ b/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts @@ -274,6 +274,36 @@ clear whether the exception will escape. */ NET_HOST_NAME: 'net.host.name', + /** + * The internet connection type currently being used by the host. + */ + NET_HOST_CONNECTION_TYPE: 'net.host.connection.type', + + /** + * This describes more details regarding the connection.type. It may be the type of cell technology connection, but it could be used for describing details about a wifi connection. + */ + NET_HOST_CONNECTION_SUBTYPE: 'net.host.connection.subtype', + + /** + * The name of the mobile carrier. + */ + NET_HOST_CARRIER_NAME: 'net.host.carrier.name', + + /** + * The mobile carrier country code. + */ + NET_HOST_CARRIER_MCC: 'net.host.carrier.mcc', + + /** + * The mobile carrier network code. + */ + NET_HOST_CARRIER_MNC: 'net.host.carrier.mnc', + + /** + * The ISO 3166-1 alpha-2 2-character country code associated with the mobile carrier network. + */ + NET_HOST_CARRIER_ICC: 'net.host.carrier.icc', + /** * The [`service.name`](../../resource/semantic_conventions/README.md#service) of the remote service. SHOULD be equal to the actual `service.name` resource attribute of the remote service if any. */ @@ -814,6 +844,8 @@ export enum FaasDocumentOperationValues { export enum FaasInvokedProviderValues { + /** Alibaba Cloud. */ + ALIBABA_CLOUD = 'alibaba_cloud', /** Amazon Web Services. */ AWS = 'aws', /** Microsoft Azure. */ @@ -845,6 +877,70 @@ export enum NetTransportValues { +export enum NetHostConnectionTypeValues { + /** wifi. */ + WIFI = 'wifi', + /** wired. */ + WIRED = 'wired', + /** cell. */ + CELL = 'cell', + /** unavailable. */ + UNAVAILABLE = 'unavailable', + /** unknown. */ + UNKNOWN = 'unknown', +} + + + + +export enum NetHostConnectionSubtypeValues { + /** GPRS. */ + GPRS = 'gprs', + /** EDGE. */ + EDGE = 'edge', + /** UMTS. */ + UMTS = 'umts', + /** CDMA. */ + CDMA = 'cdma', + /** EVDO Rel. 0. */ + EVDO_0 = 'evdo_0', + /** EVDO Rev. A. */ + EVDO_A = 'evdo_a', + /** CDMA2000 1XRTT. */ + CDMA2000_1XRTT = 'cdma2000_1xrtt', + /** HSDPA. */ + HSDPA = 'hsdpa', + /** HSUPA. */ + HSUPA = 'hsupa', + /** HSPA. */ + HSPA = 'hspa', + /** IDEN. */ + IDEN = 'iden', + /** EVDO Rev. B. */ + EVDO_B = 'evdo_b', + /** LTE. */ + LTE = 'lte', + /** EHRPD. */ + EHRPD = 'ehrpd', + /** HSPAP. */ + HSPAP = 'hspap', + /** GSM. */ + GSM = 'gsm', + /** TD-SCDMA. */ + TD_SCDMA = 'td_scdma', + /** IWLAN. */ + IWLAN = 'iwlan', + /** 5G NR (New Radio). */ + NR = 'nr', + /** 5G NRNSA (New Radio Non-Standalone). */ + NRNSA = 'nrnsa', + /** LTE CA. */ + LTE_CA = 'lte_ca', +} + + + + export enum HttpFlavorValues { /** HTTP 1.0. */ HTTP_1_0 = '1.0', diff --git a/scripts/semconv/generate.sh b/scripts/semconv/generate.sh index f36f62b79a..89718f52e1 100755 --- a/scripts/semconv/generate.sh +++ b/scripts/semconv/generate.sh @@ -4,8 +4,8 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ROOT_DIR="${SCRIPT_DIR}/../../" # freeze the spec version to make SpanAttributess generation reproducible -SPEC_VERSION=v1.5.0 -GENERATOR_VERSION=0.3.1 +SPEC_VERSION=v1.6.1 +GENERATOR_VERSION=0.5.0 cd ${SCRIPT_DIR} From 3acebdcc070cf736e932c9ab9b683eb047c5cabc Mon Sep 17 00:00:00 2001 From: Ran Nozik Date: Wed, 8 Sep 2021 17:47:30 +0300 Subject: [PATCH 2/3] fix: handle missing package.json file when checking for version (#2450) * feat: handle missing version case * feat: add test cases * fix: cr comment * fix: add comment to test * fix: tests description --- .../src/platform/node/instrumentation.ts | 23 ++- .../test/node/InstrumentationBase.test.ts | 166 ++++++++++++++++++ 2 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts diff --git a/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts b/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts index d03afadb93..f631f001ee 100644 --- a/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts +++ b/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts @@ -59,6 +59,18 @@ export abstract class InstrumentationBase } } + private _extractPackageVersion(baseDir: string): string | undefined { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const version = require(path.join(baseDir, 'package.json')).version; + return typeof version === 'string' ? version : undefined; + } catch (error) { + diag.warn('Failed extracting version', baseDir); + } + + return undefined; + } + private _onRequire( module: InstrumentationModuleDefinition, exports: T, @@ -73,13 +85,11 @@ export abstract class InstrumentationBase return exports; } - // eslint-disable-next-line @typescript-eslint/no-var-requires - const version = require(path.join(baseDir, 'package.json')).version; + const version = this._extractPackageVersion(baseDir); module.moduleVersion = version; if (module.name === name) { // main module if ( - typeof version === 'string' && isSupported(module.supportedVersions, version, module.includePrerelease) ) { if (typeof module.patch === 'function') { @@ -167,7 +177,12 @@ export abstract class InstrumentationBase } } -function isSupported(supportedVersions: string[], version: string, includePrerelease?: boolean): boolean { +function isSupported(supportedVersions: string[], version?: string, includePrerelease?: boolean): boolean { + if (typeof version === 'undefined') { + // If we don't have the version, accept the wildcard case only + return supportedVersions.includes('*'); + } + return supportedVersions.some(supportedVersion => { return satisfies(version, supportedVersion, { includePrerelease }); }); diff --git a/packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts b/packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts new file mode 100644 index 0000000000..e1c42681d3 --- /dev/null +++ b/packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { InstrumentationBase, InstrumentationModuleDefinition } from '../../src'; + +const MODULE_NAME = 'test-module'; +const MODULE_FILE_NAME = 'test-module-file'; +const MODULE_VERSION = '0.1.0'; +const WILDCARD_VERSION = '*'; +const MODULE_DIR = '/random/dir'; + +class TestInstrumentation extends InstrumentationBase { + constructor() { + super(MODULE_NAME, MODULE_VERSION); + } + + init() {} +} + +describe('InstrumentationBase', () => { + describe('_onRequire - module version is not available', () => { + // For all of these cases, there is no indication of the actual module version, + // so we require there to be a wildcard supported version. + + let instrumentation: TestInstrumentation; + let modulePatchSpy: sinon.SinonSpy; + + beforeEach(() => { + instrumentation = new TestInstrumentation(); + // @ts-expect-error access internal property for testing + instrumentation._enabled = true; + modulePatchSpy = sinon.spy(); + }); + + describe('when patching a module', () => { + describe('AND there is no wildcard supported version', () => { + it('should not patch module', () => { + const moduleExports = {}; + const instrumentationModule = { + supportedVersions: [`^${MODULE_VERSION}`], + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.moduleExports, undefined); + sinon.assert.notCalled(modulePatchSpy); + }); + }); + + describe('AND there is a wildcard supported version', () => { + it('should patch module', () => { + const moduleExports = {}; + const instrumentationModule = { + supportedVersions: [`^${MODULE_VERSION}`, WILDCARD_VERSION], + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.moduleExports, moduleExports); + sinon.assert.calledOnceWithExactly(modulePatchSpy, moduleExports, undefined); + }); + }); + }); + + describe('when patching module files', () => { + let filePatchSpy: sinon.SinonSpy; + + beforeEach(() => { + filePatchSpy = sinon.spy(); + }) + + describe('AND there is no wildcard supported version', () => { + it('should not patch module file', () => { + const moduleExports = {}; + const supportedVersions = [`^${MODULE_VERSION}`]; + const instrumentationModule = { + supportedVersions, + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + files: [{ + name: MODULE_FILE_NAME, + supportedVersions, + patch: filePatchSpy as unknown + }] + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_FILE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.moduleExports, undefined); + sinon.assert.notCalled(modulePatchSpy); + sinon.assert.notCalled(filePatchSpy); + }); + }); + + describe('AND there is a wildcard supported version', () => { + it('should patch module file', () => { + const moduleExports = {}; + const supportedVersions = [`^${MODULE_VERSION}`, WILDCARD_VERSION]; + const instrumentationModule = { + supportedVersions, + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + files: [{ + name: MODULE_FILE_NAME, + supportedVersions, + patch: filePatchSpy as unknown + }] + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_FILE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.files[0].moduleExports, moduleExports); + sinon.assert.notCalled(modulePatchSpy); + sinon.assert.calledOnceWithExactly(filePatchSpy, moduleExports, undefined); + }); + }); + }); + }); +}); From feea5167c15c41f0aeedc60959e36c18315c7ede Mon Sep 17 00:00:00 2001 From: Banothu Ramesh Naik Date: Thu, 9 Sep 2021 03:42:02 +0530 Subject: [PATCH 3/3] feat(opentelemetry-sdk-trace-base): implemented general limits of attributes (#2430) * feat(opentelemetry-sdk-trace-base): implemented general limits of attributes Signed-off-by: Banothu Ramesh Naik * fix(opentelemetry-sdk-trace-base): type caste issue fixed Signed-off-by: Banothu Ramesh Naik Co-authored-by: Rauno Viskus Co-authored-by: Daniel Dyla --- .../src/utils/environment.ts | 12 +- .../test/utils/environment.test.ts | 4 + .../src/Tracer.ts | 9 +- .../src/config.ts | 4 + .../opentelemetry-sdk-trace-base/src/types.ts | 11 ++ .../src/utility.ts | 32 +++- .../test/common/BasicTracerProvider.test.ts | 78 ++++++++ .../test/common/Span.test.ts | 170 ++++++++++++++++++ 8 files changed, 315 insertions(+), 5 deletions(-) diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index b6e9dc5db4..7df0be698c 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -28,6 +28,8 @@ const ENVIRONMENT_NUMBERS_KEYS = [ 'OTEL_BSP_MAX_EXPORT_BATCH_SIZE', 'OTEL_BSP_MAX_QUEUE_SIZE', 'OTEL_BSP_SCHEDULE_DELAY', + 'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT', + 'OTEL_ATTRIBUTE_COUNT_LIMIT', 'OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT', 'OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT', 'OTEL_SPAN_EVENT_COUNT_LIMIT', @@ -88,6 +90,10 @@ export type RAW_ENVIRONMENT = { [key: string]: string | number | undefined | string[]; }; +export const DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT = Infinity; + +export const DEFAULT_ATTRIBUTE_COUNT_LIMIT = 128; + /** * Default environment variables */ @@ -118,8 +124,10 @@ export const DEFAULT_ENVIRONMENT: Required = { OTEL_PROPAGATORS: ['tracecontext', 'baggage'], OTEL_RESOURCE_ATTRIBUTES: '', OTEL_SERVICE_NAME: '', - OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: Infinity, - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 128, + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_ATTRIBUTE_COUNT_LIMIT: DEFAULT_ATTRIBUTE_COUNT_LIMIT, + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT , + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: DEFAULT_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT: 128, OTEL_SPAN_LINK_COUNT_LIMIT: 128, OTEL_TRACES_EXPORTER: 'none', diff --git a/packages/opentelemetry-core/test/utils/environment.test.ts b/packages/opentelemetry-core/test/utils/environment.test.ts index 3f3a709b62..4ab3518645 100644 --- a/packages/opentelemetry-core/test/utils/environment.test.ts +++ b/packages/opentelemetry-core/test/utils/environment.test.ts @@ -84,6 +84,8 @@ describe('environment', () => { OTEL_LOG_LEVEL: 'ERROR', OTEL_NO_PATCH_MODULES: 'a,b,c', OTEL_RESOURCE_ATTRIBUTES: '', + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: 40, + OTEL_ATTRIBUTE_COUNT_LIMIT: 50, OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: 100, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 10, OTEL_SPAN_EVENT_COUNT_LIMIT: 20, @@ -94,6 +96,8 @@ describe('environment', () => { const env = getEnv(); assert.deepStrictEqual(env.OTEL_NO_PATCH_MODULES, ['a', 'b', 'c']); assert.strictEqual(env.OTEL_LOG_LEVEL, DiagLogLevel.ERROR); + assert.strictEqual(env.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, 40); + assert.strictEqual(env.OTEL_ATTRIBUTE_COUNT_LIMIT, 50); assert.strictEqual(env.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, 100); assert.strictEqual(env.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, 10); assert.strictEqual(env.OTEL_SPAN_EVENT_COUNT_LIMIT, 20); diff --git a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts index f39d55d9fd..9960243cba 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Tracer.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Tracer.ts @@ -25,7 +25,7 @@ import { import { Resource } from '@opentelemetry/resources'; import { BasicTracerProvider } from './BasicTracerProvider'; import { Span } from './Span'; -import { SpanLimits, TracerConfig } from './types'; +import { GeneralLimits, SpanLimits, TracerConfig } from './types'; import { mergeConfig } from './utility'; import { SpanProcessor } from './SpanProcessor'; @@ -34,6 +34,7 @@ import { SpanProcessor } from './SpanProcessor'; */ export class Tracer implements api.Tracer { private readonly _sampler: api.Sampler; + private readonly _generalLimits: GeneralLimits; private readonly _spanLimits: SpanLimits; private readonly _idGenerator: IdGenerator; readonly resource: Resource; @@ -49,6 +50,7 @@ export class Tracer implements api.Tracer { ) { const localConfig = mergeConfig(config); this._sampler = localConfig.sampler; + this._generalLimits = localConfig.generalLimits; this._spanLimits = localConfig.spanLimits; this._idGenerator = config.idGenerator || new RandomIdGenerator(); this.resource = _tracerProvider.resource; @@ -212,6 +214,11 @@ export class Tracer implements api.Tracer { return api.context.with(contextWithSpanSet, fn, undefined, span); } + /** Returns the active {@link GeneralLimits}. */ + getGeneralLimits(): GeneralLimits { + return this._generalLimits; + } + /** Returns the active {@link SpanLimits}. */ getSpanLimits(): SpanLimits { return this._spanLimits; diff --git a/packages/opentelemetry-sdk-trace-base/src/config.ts b/packages/opentelemetry-sdk-trace-base/src/config.ts index 7ed67c2b12..4b364b3718 100644 --- a/packages/opentelemetry-sdk-trace-base/src/config.ts +++ b/packages/opentelemetry-sdk-trace-base/src/config.ts @@ -38,6 +38,10 @@ const DEFAULT_RATIO = 1; export const DEFAULT_CONFIG = { sampler: buildSamplerFromEnv(env), forceFlushTimeoutMillis: 30000, + generalLimits: { + attributeValueLengthLimit: getEnv().OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + attributeCountLimit: getEnv().OTEL_ATTRIBUTE_COUNT_LIMIT, + }, spanLimits: { attributeValueLengthLimit: getEnv().OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, attributeCountLimit: getEnv().OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, diff --git a/packages/opentelemetry-sdk-trace-base/src/types.ts b/packages/opentelemetry-sdk-trace-base/src/types.ts index 91a330d9c9..d60df053ac 100644 --- a/packages/opentelemetry-sdk-trace-base/src/types.ts +++ b/packages/opentelemetry-sdk-trace-base/src/types.ts @@ -29,6 +29,9 @@ export interface TracerConfig { */ sampler?: Sampler; + /** General Limits */ + generalLimits?: GeneralLimits; + /** Span Limits */ spanLimits?: SpanLimits; @@ -61,6 +64,14 @@ export interface SDKRegistrationConfig { contextManager?: ContextManager | null; } +/** Global configuration limits of trace service */ +export interface GeneralLimits { + /** attributeValueLengthLimit is maximum allowed attribute value size */ + attributeValueLengthLimit?: number; + /** attributeCountLimit is number of attributes per trace */ + attributeCountLimit?: number; +} + /** Global configuration of trace service */ export interface SpanLimits { /** attributeValueLengthLimit is maximum allowed attribute value size */ diff --git a/packages/opentelemetry-sdk-trace-base/src/utility.ts b/packages/opentelemetry-sdk-trace-base/src/utility.ts index 542c26925e..44654ae87d 100644 --- a/packages/opentelemetry-sdk-trace-base/src/utility.ts +++ b/packages/opentelemetry-sdk-trace-base/src/utility.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT, DEFAULT_ATTRIBUTE_COUNT_LIMIT } from '@opentelemetry/core'; + import { Sampler } from '@opentelemetry/api'; import { buildSamplerFromEnv, DEFAULT_CONFIG } from './config'; -import { SpanLimits, TracerConfig } from './types'; +import { SpanLimits, TracerConfig, GeneralLimits } from './types'; /** * Function to merge Default configuration (as specified in './config') with * user provided configurations. */ -export function mergeConfig(userConfig: TracerConfig): TracerConfig & { sampler: Sampler; spanLimits: SpanLimits } { +export function mergeConfig(userConfig: TracerConfig): TracerConfig & { + sampler: Sampler; + spanLimits: SpanLimits; + generalLimits: GeneralLimits; +} { const perInstanceDefaults: Partial = { sampler: buildSamplerFromEnv(), }; @@ -34,11 +40,33 @@ export function mergeConfig(userConfig: TracerConfig): TracerConfig & { sampler: userConfig ); + target.generalLimits = Object.assign( + {}, + DEFAULT_CONFIG.generalLimits, + userConfig.generalLimits || {} + ); + target.spanLimits = Object.assign( {}, DEFAULT_CONFIG.spanLimits, userConfig.spanLimits || {} ); + /** + * When span attribute count limit is not defined, but general attribute count limit is defined + * Then, span attribute count limit will be same as general one + */ + if (target.spanLimits.attributeCountLimit === DEFAULT_ATTRIBUTE_COUNT_LIMIT && target.generalLimits.attributeCountLimit !== DEFAULT_ATTRIBUTE_COUNT_LIMIT) { + target.spanLimits.attributeCountLimit = target.generalLimits.attributeCountLimit; + } + + /** + * When span attribute value length limit is not defined, but general attribute value length limit is defined + * Then, span attribute value length limit will be same as general one + */ + if (target.spanLimits.attributeValueLengthLimit === DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT && target.generalLimits.attributeValueLengthLimit !== DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT) { + target.spanLimits.attributeValueLengthLimit = target.generalLimits.attributeValueLengthLimit; + } + return target; } diff --git a/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts index 581a30f855..1b5e6872e3 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts @@ -85,6 +85,52 @@ describe('BasicTracerProvider', () => { }); }); + describe('generalLimits', () => { + describe('when not defined default values', () => { + it('should have tracer with default values', () => { + const tracer = new BasicTracerProvider({}).getTracer('default'); + assert.deepStrictEqual(tracer.getGeneralLimits(), { + attributeValueLengthLimit: Infinity, + attributeCountLimit: 128, + }); + }); + }); + + describe('when "attributeCountLimit" is defined', () => { + it('should have tracer with defined value', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + attributeCountLimit: 100, + }, + }).getTracer('default'); + const generalLimits = tracer.getGeneralLimits(); + assert.strictEqual(generalLimits.attributeCountLimit, 100); + }); + }); + + describe('when "attributeValueLengthLimit" is defined', () => { + it('should have tracer with defined value', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + attributeValueLengthLimit: 10, + }, + }).getTracer('default'); + const generalLimits = tracer.getGeneralLimits(); + assert.strictEqual(generalLimits.attributeValueLengthLimit, 10); + }); + + it('should have tracer with negative "attributeValueLengthLimit" value', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + attributeValueLengthLimit: -10, + }, + }).getTracer('default'); + const generalLimits = tracer.getGeneralLimits(); + assert.strictEqual(generalLimits.attributeValueLengthLimit, -10); + }); + }); + }); + describe('spanLimits', () => { describe('when not defined default values', () => { it('should have tracer with default values', () => { @@ -155,6 +201,38 @@ describe('BasicTracerProvider', () => { assert.strictEqual(spanLimits.linkCountLimit, 10); }); }); + + describe('when only generalLimits are defined', () => { + it('should have span limits as general limits', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + attributeValueLengthLimit: 100, + attributeCountLimit: 200, + }, + }).getTracer('default'); + const spanLimits = tracer.getSpanLimits(); + assert.strictEqual(spanLimits.attributeValueLengthLimit, 100); + assert.strictEqual(spanLimits.attributeCountLimit, 200); + }); + }); + + describe('when both generalLimits and spanLimits defined', () => { + it('should have span limits as priority than general limits', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + attributeValueLengthLimit: 100, + attributeCountLimit: 200, + }, + spanLimits: { + attributeValueLengthLimit: 10, + attributeCountLimit: 20, + }, + }).getTracer('default'); + const spanLimits = tracer.getSpanLimits(); + assert.strictEqual(spanLimits.attributeValueLengthLimit, 10); + assert.strictEqual(spanLimits.attributeCountLimit, 20); + }); + }); }); }); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts index 21e5eb37ff..d9ca0b1301 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/Span.test.ts @@ -267,6 +267,99 @@ describe('Span', () => { }); }); + describe('when generalLimits options set', () => { + describe('when "attributeCountLimit" option defined', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + // Setting count limit + attributeCountLimit: 100, + }, + }).getTracer('default'); + + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT + ); + for (let i = 0; i < 150; i++) { + span.setAttribute('foo' + i, 'bar' + i); + } + span.end(); + + it('should remove / drop all remaining values after the number of values exceeds this limit', () => { + assert.strictEqual(Object.keys(span.attributes).length, 100); + assert.strictEqual(span.attributes['foo0'], 'bar0'); + assert.strictEqual(span.attributes['foo99'], 'bar99'); + assert.strictEqual(span.attributes['foo149'], undefined); + }); + }); + + describe('when "attributeValueLengthLimit" option defined', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + // Setting attribute value length limit + attributeValueLengthLimit: 5, + }, + }).getTracer('default'); + + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT + ); + + it('should truncate value which length exceeds this limit', () => { + span.setAttribute('attr-with-more-length', 'abcdefgh'); + assert.strictEqual(span.attributes['attr-with-more-length'], 'abcde'); + }); + + it('should truncate value of arrays which exceeds this limit', () => { + span.setAttribute('attr-array-of-strings', ['abcdefgh', 'abc', 'abcde', '']); + span.setAttribute('attr-array-of-bool', [true, false]); + assert.deepStrictEqual(span.attributes['attr-array-of-strings'], ['abcde', 'abc', 'abcde', '']); + assert.deepStrictEqual(span.attributes['attr-array-of-bool'], [true, false]); + }); + + it('should not truncate value which length not exceeds this limit', () => { + span.setAttribute('attr-with-less-length', 'abc'); + assert.strictEqual(span.attributes['attr-with-less-length'], 'abc'); + }); + + it('should return same value for non-string values', () => { + span.setAttribute('attr-non-string', true); + assert.strictEqual(span.attributes['attr-non-string'], true); + }); + }); + + describe('when "attributeValueLengthLimit" option is invalid', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + // Setting invalid attribute value length limit + attributeValueLengthLimit: -5, + }, + }).getTracer('default'); + + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT + ); + + it('should not truncate any value', () => { + span.setAttribute('attr-not-truncate', 'abcdefgh'); + span.setAttribute('attr-array-of-strings', ['abcdefgh', 'abc', 'abcde']); + assert.deepStrictEqual(span.attributes['attr-not-truncate'], 'abcdefgh'); + assert.deepStrictEqual(span.attributes['attr-array-of-strings'], ['abcdefgh', 'abc', 'abcde']); + }); + }); + }); + describe('when spanLimits options set', () => { describe('when "attributeCountLimit" option defined', () => { const tracer = new BasicTracerProvider({ @@ -359,6 +452,83 @@ describe('Span', () => { }); }); }); + + describe('when both generalLimits and spanLimits options set', () => { + describe('when "attributeCountLimit" option defined', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + // Setting count limit + attributeCountLimit: 10, + }, + spanLimits: { + attributeCountLimit: 5, + } + }).getTracer('default'); + + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT + ); + for (let i = 0; i < 150; i++) { + span.setAttribute('foo' + i, 'bar' + i); + } + span.end(); + + it('should remove / drop all remaining values after the number of values exceeds span limit', () => { + assert.strictEqual(Object.keys(span.attributes).length, 5); + assert.strictEqual(span.attributes['foo0'], 'bar0'); + assert.strictEqual(span.attributes['foo4'], 'bar4'); + assert.strictEqual(span.attributes['foo5'], undefined); + assert.strictEqual(span.attributes['foo10'], undefined); + }); + }); + + describe('when "attributeValueLengthLimit" option defined', () => { + const tracer = new BasicTracerProvider({ + generalLimits: { + // Setting attribute value length limit + attributeValueLengthLimit: 10, + }, + spanLimits: { + // Setting attribute value length limit + attributeValueLengthLimit: 5, + }, + }).getTracer('default'); + + const span = new Span( + tracer, + ROOT_CONTEXT, + name, + spanContext, + SpanKind.CLIENT + ); + + it('should truncate value which length exceeds span limit', () => { + span.setAttribute('attr-with-more-length', 'abcdefgh'); + assert.strictEqual(span.attributes['attr-with-more-length'], 'abcde'); + }); + + it('should truncate value of arrays which exceeds span limit', () => { + span.setAttribute('attr-array-of-strings', ['abcdefgh', 'abc', 'abcde', '']); + span.setAttribute('attr-array-of-bool', [true, false]); + assert.deepStrictEqual(span.attributes['attr-array-of-strings'], ['abcde', 'abc', 'abcde', '']); + assert.deepStrictEqual(span.attributes['attr-array-of-bool'], [true, false]); + }); + + it('should not truncate value which length not exceeds span limit', () => { + span.setAttribute('attr-with-less-length', 'abc'); + assert.strictEqual(span.attributes['attr-with-less-length'], 'abc'); + }); + + it('should return same value for non-string values', () => { + span.setAttribute('attr-non-string', true); + assert.strictEqual(span.attributes['attr-non-string'], true); + }); + }); + }); }); describe('setAttributes', () => {