diff --git a/shared/OpenTelemetry/Tests/Unit/src/attribute/AttributeContainer.Tests.ts b/shared/OpenTelemetry/Tests/Unit/src/attribute/AttributeContainer.Tests.ts index 18bf699d8..9ba6b82c9 100644 --- a/shared/OpenTelemetry/Tests/Unit/src/attribute/AttributeContainer.Tests.ts +++ b/shared/OpenTelemetry/Tests/Unit/src/attribute/AttributeContainer.Tests.ts @@ -5,6 +5,7 @@ import { eAttributeFilter, IAttributeChangeInfo } from "../../../../src/attribut import { IOTelConfig } from "../../../../src/interfaces/config/IOTelConfig"; import { IOTelAttributes } from "../../../../src/interfaces/IOTelAttributes"; import { eAttributeChangeOp } from "../../../../src/enums/eAttributeChangeOp"; +import { isFunction } from "@nevware21/ts-utils"; export class AttributeContainerTests extends AITestClass { @@ -528,8 +529,8 @@ export class AttributeContainerTests extends AITestClass { Assert.ok(isAttributeContainer(snapshotContainer), "Should identify snapshot child container as valid"); // Verify that child containers have the required methods and properties - Assert.ok(typeof childContainer.child === "function", "Child container should have child method"); - Assert.ok(typeof childContainer.listen === "function", "Child container should have listen method"); + Assert.equal(isFunction(childContainer.child), true, "Child container should have child method"); + Assert.equal(isFunction(childContainer.listen), true, "Child container should have listen method"); Assert.ok("id" in childContainer, "Child container should have id property"); Assert.ok("size" in childContainer, "Child container should have size property"); } diff --git a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts index b0413eb94..b748caba5 100644 --- a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts +++ b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts @@ -5,6 +5,7 @@ import { createLoggerProviderSharedState } from "../../../../src/internal/Logger import { reconfigureLimits } from "../../../../src/sdk/config"; import { createLogRecord } from "../../../../src/sdk/OTelLogRecord"; import { IOTelLogRecordLimits } from "../../../../src/interfaces/logs/IOTelLogRecordLimits"; +import { isObject } from "@nevware21/ts-utils"; const setup = (logRecordLimits?: IOTelLogRecordLimits, data?: IOTelLogRecord) => { const instrumentationScope = { @@ -40,7 +41,7 @@ export class OTelLogRecordTests extends AITestClass { name: "LogRecord: constructor - should create an instance", test: () => { const { logRecord } = setup(); - Assert.ok(logRecord && typeof logRecord === "object", "LogRecord should be created"); + Assert.ok(logRecord && isObject(logRecord), "LogRecord should be created"); } }); diff --git a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogger.Tests.ts b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogger.Tests.ts index 876be3ec6..cb40bce72 100644 --- a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogger.Tests.ts +++ b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLogger.Tests.ts @@ -10,6 +10,7 @@ import { eW3CTraceFlags } from "@microsoft/applicationinsights-common"; import { createContextManager } from "../../../../src/api/context/contextManager"; import { setContextSpanContext } from "../../../../src/api/trace/utils"; import { createLogger } from "../../../../src/sdk/OTelLogger"; +import { isFunction } from "@nevware21/ts-utils"; type LoggerWithScope = IOTelLogger & { instrumentationScope: IOTelInstrumentationScope }; @@ -49,7 +50,7 @@ export class OTelLoggerTests extends AITestClass { const logger = createLogger(scope, sharedState) as LoggerWithScope; Assert.equal(logger.instrumentationScope.name, "test name", "Should set instrumentation scope name"); Assert.equal(logger.instrumentationScope.version, "test version", "Should set instrumentation scope version"); - Assert.equal(typeof logger.emit, "function", "Should expose emit implementation"); + Assert.equal(isFunction(logger.emit), true, "Should expose emit implementation"); } }); diff --git a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts index 0c041986e..b9dd5b816 100644 --- a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts +++ b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts @@ -12,6 +12,7 @@ import { IOTelInstrumentationScope } from "../../../../src/interfaces/trace/IOTe import { createMultiLogRecordProcessor } from "../../../../src/sdk/OTelMultiLogRecordProcessor"; import { loadDefaultConfig } from "../../../../src/sdk/config"; import { IOTelResource, OTelRawResourceAttribute } from "../../../../src/interfaces/resources/IOTelResource"; +import { isFunction } from "@nevware21/ts-utils"; type LoggerProviderInstance = ReturnType; type MultiLogRecordProcessorInstance = ReturnType; @@ -32,7 +33,7 @@ export class OTelLoggerProviderTests extends AITestClass { name: "LoggerProvider: constructor without options should construct an instance", test: () => { const provider = createLoggerProvider(); - Assert.equal(typeof provider.getLogger, "function", "Should create a LoggerProvider instance"); + Assert.equal(isFunction(provider.getLogger), true, "Should create a LoggerProvider instance"); const sharedState = provider._sharedState; Assert.ok(sharedState.loggers instanceof Map, "Should expose shared state instance"); } @@ -304,7 +305,7 @@ export class OTelLoggerProviderTests extends AITestClass { try { const logger = provider.getLogger("default", "1.0.0"); const expectedNoopLogger = createNoopLogger(); - Assert.equal(typeof logger.emit, "function", "Logger should expose noop emit function after shutdown"); + Assert.equal(isFunction(logger.emit), true, "Logger should expose noop emit function after shutdown"); Assert.equal(logger.emit.length, expectedNoopLogger.emit.length, "Noop emit signature should match expected noop logger"); let threw = false; try { diff --git a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelMultiLogRecordProcessor.Tests.ts b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelMultiLogRecordProcessor.Tests.ts index 4bc9acc1b..5836c66bf 100644 --- a/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelMultiLogRecordProcessor.Tests.ts +++ b/shared/OpenTelemetry/Tests/Unit/src/sdk/OTelMultiLogRecordProcessor.Tests.ts @@ -1,5 +1,6 @@ import { AITestClass, Assert } from "@microsoft/ai-test-framework"; import { createPromise, IPromise } from "@nevware21/ts-async"; +import { isFunction } from "@nevware21/ts-utils"; import { IOTelContext } from "../../../../src/interfaces/context/IOTelContext"; import { IOTelLogRecordProcessor } from "../../../../src/interfaces/logs/IOTelLogRecordProcessor"; @@ -61,8 +62,8 @@ export class OTelMultiLogRecordProcessorTests extends AITestClass { test: () => { const { multiProcessor } = setup(); Assert.ok(!!multiProcessor, "Should create MultiLogRecordProcessor instance"); - Assert.equal(typeof multiProcessor.forceFlush, "function", "Should expose forceFlush method"); - Assert.equal(typeof multiProcessor.shutdown, "function", "Should expose shutdown method"); + Assert.equal(isFunction(multiProcessor.forceFlush), true, "Should expose forceFlush method"); + Assert.equal(isFunction(multiProcessor.shutdown), true, "Should expose shutdown method"); } }); diff --git a/shared/OpenTelemetry/src/interfaces/metrics/meter/IMetricAttributes.ts b/shared/OpenTelemetry/src/interfaces/metrics/meter/IMetricAttributes.ts index e69de29bb..410963c78 100644 --- a/shared/OpenTelemetry/src/interfaces/metrics/meter/IMetricAttributes.ts +++ b/shared/OpenTelemetry/src/interfaces/metrics/meter/IMetricAttributes.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IOTelAttributes } from "../../IOTelAttributes"; + +export type IMetricAttributes = IOTelAttributes; \ No newline at end of file diff --git a/shared/OpenTelemetry/src/sdk/OTelLogRecord.ts b/shared/OpenTelemetry/src/sdk/OTelLogRecord.ts index 95de34353..79bf4f8c7 100644 --- a/shared/OpenTelemetry/src/sdk/OTelLogRecord.ts +++ b/shared/OpenTelemetry/src/sdk/OTelLogRecord.ts @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { isArray, isObject, isString } from "@nevware21/ts-utils"; import { OTelAnyValue } from "../OTelTypes/OTelAnyValue"; import { getContextActiveSpanContext, isSpanContextValid } from "../api/trace/utils"; +import { createAttributeContainer, isAttributeContainer } from "../attribute/attributeContainer"; import { OTelSeverityNumber } from "../enums/logs/eOTelSeverityNumber"; import { OTelAttributeValue } from "../interfaces/IOTelAttributes"; import { IOTelLogRecord, LogAttributes, LogBody } from "../interfaces/logs/IOTelLogRecord"; @@ -33,13 +35,28 @@ export function createLogRecord( context } = logRecord; - const logAttributes = attributes || {}; + const hasAttributeContainer = isAttributeContainer(attributes as any); + const logAttributes = (!hasAttributeContainer && attributes) ? attributes : {}; const now = Date.now(); const hrTime = timeInputToHrTime(timestamp || now); const hrTimeObserved = timeInputToHrTime(observedTimestamp || now); const resource = sharedState.resource; const logRecordLimits: Required = sharedState.logRecordLimits; const handlers: IOTelErrorHandlers = {}; + const attributeContainer = createAttributeContainer( + { + traceCfg: { + generalLimits: { + attributeCountLimit: logRecordLimits.attributeCountLimit, + attributeValueLengthLimit: logRecordLimits.attributeValueLengthLimit + } + }, + errorHandlers: handlers + }, + instrumentationScope.name, + hasAttributeContainer ? (attributes as any) : undefined, + logRecordLimits + ); let spanContext: IOTelSpanContext | undefined; if (context) { @@ -49,18 +66,16 @@ export function createLogRecord( } } - const recordAttributes: LogAttributes = {}; let storedSeverityText: string | undefined = severityText; let storedSeverityNumber: OTelSeverityNumber | undefined = severityNumber; let storedBody: LogBody | undefined = body; let storedEventName: string | undefined = eventName; - let totalAttributesCount = 0; let isReadonly = false; let logRecordInstance: IOTelLogRecordInstance; function getDroppedAttributesCount(): number { - return totalAttributesCount - Object.keys(recordAttributes).length; + return attributeContainer.droppedAttributes; } function truncateToLimit(value: string, limit: number): string { @@ -77,13 +92,13 @@ export function createLogRecord( return value; } - if (typeof value === "string") { + if (isString(value)) { return truncateToLimit(value, limit); } - if (Array.isArray(value)) { + if (isArray(value)) { return (value as []).map(function (val) { - return typeof val === "string" ? truncateToLimit(val, limit) : val; + return isString(val) ? truncateToLimit(val, limit) : val; }); } @@ -111,8 +126,8 @@ export function createLogRecord( if ( !isAttributeValue(value) && !( - typeof value === "object" && - !Array.isArray(value) && + isObject(value) && + !isArray(value) && Object.keys(value).length > 0 ) ) { @@ -120,27 +135,19 @@ export function createLogRecord( return logRecordInstance; } - totalAttributesCount += 1; - if ( - Object.keys(recordAttributes).length >= logRecordLimits.attributeCountLimit && - !Object.prototype.hasOwnProperty.call(recordAttributes, key) - ) { - if (getDroppedAttributesCount() === 1) { - handleWarn(handlers, "Dropping extra attributes."); - } - return logRecordInstance; - } - if (isAttributeValue(value)) { - recordAttributes[key] = truncateToSize(value); - } else { - recordAttributes[key] = value; + value = truncateToSize(value); } + attributeContainer.set(key, value as any); + return logRecordInstance; } function setAttributesInternal(attributesToSet: LogAttributes): IOTelLogRecordInstance { + if (!attributesToSet) { + return logRecordInstance; + } const entries = Object.entries(attributesToSet); for (let idx = 0; idx < entries.length; idx++) { const attribute = entries[idx]; @@ -204,7 +211,7 @@ export function createLogRecord( return instrumentationScope; }, get attributes(): LogAttributes { - return recordAttributes; + return attributeContainer.attributes as unknown as LogAttributes; }, get severityText(): string | undefined { return storedSeverityText; @@ -266,7 +273,9 @@ export function createLogRecord( _makeReadonly: makeReadonly }; - setAttributesInternal(logAttributes); + if (!hasAttributeContainer) { + setAttributesInternal(logAttributes); + } return logRecordInstance; } diff --git a/shared/OpenTelemetry/src/sdk/OTelLogger.ts b/shared/OpenTelemetry/src/sdk/OTelLogger.ts index df3ecc253..a4f373cb4 100644 --- a/shared/OpenTelemetry/src/sdk/OTelLogger.ts +++ b/shared/OpenTelemetry/src/sdk/OTelLogger.ts @@ -22,7 +22,7 @@ export function createLogger( * the LogRecords it emits MUST automatically include the Trace Context from the active Context, * if Context has not been explicitly set. */ - const logRecordData: IOTelLogRecord = { + let logRecordData: IOTelLogRecord = { context: currentContext, timestamp: logRecord.timestamp, observedTimestamp: logRecord.observedTimestamp, diff --git a/shared/OpenTelemetry/src/sdk/config.ts b/shared/OpenTelemetry/src/sdk/config.ts index 8a25ff69b..11d00b1c4 100644 --- a/shared/OpenTelemetry/src/sdk/config.ts +++ b/shared/OpenTelemetry/src/sdk/config.ts @@ -57,38 +57,31 @@ export function loadDefaultConfig() { * configures the model specific limits by using the values from the general ones. * @param logRecordLimits User provided limits configuration */ -export function reconfigureLimits( - logRecordLimits: IOTelLogRecordLimits -): Required { - const providedCount = logRecordLimits.attributeCountLimit; - const providedValueLength = logRecordLimits.attributeValueLengthLimit; +export function reconfigureLimits(logRecordLimits?: IOTelLogRecordLimits): Required { + const limits = logRecordLimits || {}; + const providedCount = limits.attributeCountLimit; + const providedValueLength = limits.attributeValueLengthLimit; const envLogCount = getNumberFromEnv("OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT"); const envGeneralCount = getNumberFromEnv("OTEL_ATTRIBUTE_COUNT_LIMIT"); - const envLogValueLength = getNumberFromEnv("OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT"); const envGeneralValueLength = getNumberFromEnv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"); - return { - /** - * Reassign log record attribute count limit to use first non null value defined by user or use default value - */ - attributeCountLimit: providedCount !== undefined - ? providedCount - : envLogCount !== undefined - ? envLogCount - : envGeneralCount !== undefined - ? envGeneralCount - : 128, - /** - * Reassign log record attribute value length limit to use first non null value defined by user or use default value - */ - attributeValueLengthLimit: providedValueLength !== undefined - ? providedValueLength - : envLogValueLength !== undefined - ? envLogValueLength - : envGeneralValueLength !== undefined - ? envGeneralValueLength - : Infinity - }; + limits.attributeCountLimit = providedCount !== undefined + ? providedCount + : envLogCount !== undefined + ? envLogCount + : envGeneralCount !== undefined + ? envGeneralCount + : 128; + + limits.attributeValueLengthLimit = providedValueLength !== undefined + ? providedValueLength + : envLogValueLength !== undefined + ? envLogValueLength + : envGeneralValueLength !== undefined + ? envGeneralValueLength + : Infinity; + + return limits as Required; }