From 8999c3931a868924a03ed8c5035183f88e97f2e6 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:57:24 -0700 Subject: [PATCH 1/5] chore: add Prettier formatting with eslint-config-prettier integration - Add .prettierrc.json (printWidth: 100, double quotes, semicolons, 2-space indent) - Add .prettierignore for dist, generated, and artifact files - Add eslint-config-prettier to disable conflicting ESLint rules - Add format check to build script and PR validation workflow - Apply formatting to all source and test files --- .github/workflows/pr-validation.yml | 3 +++ .prettierignore | 7 ++++++ .prettierrc.json | 9 ++++++++ eslint.config.mjs | 2 ++ package.json | 9 ++++---- src/browserSdkLoader/browserSdkLoader.ts | 11 ++++----- .../browserSdkLoaderHelper.ts | 4 ---- src/distro/distro.ts | 2 +- src/index.ts | 5 +--- src/logs/batchLogRecordProcessor.ts | 1 - src/metrics/handler.ts | 2 +- src/metrics/quickpulse/export/exporter.ts | 2 +- src/metrics/quickpulse/liveMetrics.ts | 9 ++++---- src/metrics/quickpulse/utils.ts | 7 +----- src/shared/config.ts | 5 +--- src/shared/jsonConfig.ts | 18 ++++++++++----- src/shared/logging/diagFileConsoleLogger.ts | 23 ++++--------------- src/shared/logging/logger.ts | 5 ---- src/shared/module.ts | 2 -- .../browserSdkLoader/browserSdkLoader.test.ts | 22 +++++------------- test/internal/unit/logs/logHandler.test.ts | 2 -- test/internal/unit/main.test.ts | 6 ++--- .../internal/unit/metrics/liveMetrics.test.ts | 2 -- .../unit/metrics/liveMetricsFilter.test.ts | 1 - .../unit/metrics/metricHandler.test.ts | 18 +++++++-------- test/internal/unit/shared/jsonConfig.test.ts | 20 ++++++++++++---- .../logging/diagFileConsoleLogger.test.ts | 18 +++++---------- .../internal/unit/traces/traceHandler.test.ts | 4 +--- test/utils/basic.ts | 3 --- 29 files changed, 97 insertions(+), 125 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index d0bdef11..0444b850 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -27,6 +27,9 @@ jobs: - name: Build run: npm run build + - name: Format check + run: npm run format + - name: Lint run: npm run lint diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..faae0877 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +**/dist/ +**/node_modules/ +**/src/generated/ +**/*.d.ts +.env +tsdoc-metadata.json +lint-output.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..04dfc2dd --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "auto", + "printWidth": 100, + "semi": true, + "singleQuote": false, + "tabWidth": 2 +} diff --git a/eslint.config.mjs b/eslint.config.mjs index fdbc9915..3d5efdfd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,11 @@ import eslint from "@eslint/js"; +import prettier from "eslint-config-prettier"; import tseslint from "typescript-eslint"; export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, + prettier, { ignores: ["dist/", "node_modules/"], }, diff --git a/package.json b/package.json index fd092f57..86ba9833 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,12 @@ "types": "./dist/commonjs/index.d.ts", "type": "module", "scripts": { - "build": "npm run clean && tsc -p tsconfig.src.json && tsc -p tsconfig.src.cjs.json", + "build": "npm run clean && npm run format && npm run lint && tsc -p tsconfig.src.json && tsc -p tsconfig.src.cjs.json", "clean": "rimraf dist", "lint": "eslint src test", "lint:fix": "eslint src test --fix", - "format": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"*.json\"", - "format:fix": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.json\"", + "format": "prettier --list-different \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", + "format:fix": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", @@ -106,7 +106,8 @@ "@vitest/coverage-istanbul": "^3.0.0", "dotenv": "^16.0.0", "eslint": "^9.0.0", - "prettier": "^3.0.0", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.3.3", "rimraf": "^6.0.0", "typescript": "^5.6.0", "typescript-eslint": "^8.58.0", diff --git a/src/browserSdkLoader/browserSdkLoader.ts b/src/browserSdkLoader/browserSdkLoader.ts index bedb5c87..a7163598 100644 --- a/src/browserSdkLoader/browserSdkLoader.ts +++ b/src/browserSdkLoader/browserSdkLoader.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - - import http from "node:http"; import https from "node:https"; import { webSnippet as sdkLoader } from "@microsoft/applicationinsights-web-snippet"; @@ -102,7 +100,7 @@ export class BrowserSdkLoader { if (originalRequestListener) { requestListener = (request: IncomingMessage, response: ServerResponse) => { // Patch response write method - + const originalResponseWrite = response.write; const isGetRequest = request.method === "GET"; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type @@ -143,7 +141,7 @@ export class BrowserSdkLoader { }; // Patch response end method for cases when HTML is added there - + const originalResponseEnd = response.end; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type @@ -255,7 +253,7 @@ export class BrowserSdkLoader { // eslint-disable-next-line prefer-rest-params return originalHttpsResponseEnd.apply(res, arguments); }; - + return originalHttpsRequestListener(req, res); }; return originalHttpsServer(options, httpsRequestListener); @@ -266,7 +264,7 @@ export class BrowserSdkLoader { /** * Validate response and try to inject Browser SDK Loader */ - + public ValidateInjection(response: any, input: string | Buffer): boolean { try { if (!response || !input || response.statusCode !== 200) return false; @@ -289,7 +287,6 @@ export class BrowserSdkLoader { * Inject Browser SDK Loader */ public InjectSdkLoader( - response: any, input: string | Buffer, encodeType?: browserSdkLoaderHelper.contentEncodingMethod, diff --git a/src/browserSdkLoader/browserSdkLoaderHelper.ts b/src/browserSdkLoader/browserSdkLoaderHelper.ts index 94968d89..025d4976 100644 --- a/src/browserSdkLoader/browserSdkLoaderHelper.ts +++ b/src/browserSdkLoader/browserSdkLoaderHelper.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - - import * as zlib from "zlib"; import { promisify } from "node:util"; import type http from "node:http"; @@ -50,7 +48,6 @@ export const getBrotliCompressAsync = (zlibObject: any): Function | null => { export const getBrotliCompressSync = (zlibObject: any): Function | null => { const isMajorVer = isBrotliSupported(); if (isMajorVer && typeof zlibObject.brotliCompressSync === "function") { - return zlibObject.brotliCompressSync; } return null; @@ -69,7 +66,6 @@ export const getBrotliDecompressAsync = (zlibObject: any): Function | null => { export const getBrotliDecompressSync = (zlibObject: any): Function | null => { const isMajorVer = isBrotliSupported(); if (isMajorVer && typeof zlibObject.brotliDecompressSync === "function") { - return zlibObject.brotliDecompressSync; } return null; diff --git a/src/distro/distro.ts b/src/distro/distro.ts index eab970c8..9decfcc4 100644 --- a/src/distro/distro.ts +++ b/src/distro/distro.ts @@ -121,7 +121,7 @@ export function shutdownMicrosoftOpenTelemetry(): Promise { * Get the internal SDK instance for testing purposes * @internal */ - + export function _getSdkInstance(): NodeSDK | undefined { return sdk; } diff --git a/src/index.ts b/src/index.ts index 2bf3b44f..7beda779 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,10 +3,7 @@ import type { AzureMonitorOpenTelemetryOptions } from "./types.js"; import type { MicrosoftOpenTelemetryOptions } from "./distro/types.js"; -import { - useMicrosoftOpenTelemetry, - shutdownMicrosoftOpenTelemetry, -} from "./distro/distro.js"; +import { useMicrosoftOpenTelemetry, shutdownMicrosoftOpenTelemetry } from "./distro/distro.js"; // ── Re-exports from distro ────────────────────────────────────────────────── export type { AzureMonitorOpenTelemetryOptions }; diff --git a/src/logs/batchLogRecordProcessor.ts b/src/logs/batchLogRecordProcessor.ts index 004110fc..be080e1a 100644 --- a/src/logs/batchLogRecordProcessor.ts +++ b/src/logs/batchLogRecordProcessor.ts @@ -24,7 +24,6 @@ export class AzureBatchLogRecordProcessor extends BatchLogRecordProcessor { // Trace based sampling for logs if (this._options.enableTraceBasedSamplingForLogs) { if (logRecord.spanContext && logRecord.spanContext.spanId) { - if (logRecord.spanContext.traceFlags !== TraceFlags.SAMPLED) { // Do not export log for spans that were sampled out return; diff --git a/src/metrics/handler.ts b/src/metrics/handler.ts index 2c37648e..4a1f36d0 100644 --- a/src/metrics/handler.ts +++ b/src/metrics/handler.ts @@ -159,7 +159,7 @@ export class MetricHandler { /** * Shutdown handler */ - + public async shutdown(): Promise { this._standardMetrics?.shutdown(); this._liveMetrics?.shutdown(); diff --git a/src/metrics/quickpulse/export/exporter.ts b/src/metrics/quickpulse/export/exporter.ts index 98f30e01..2fc538ef 100644 --- a/src/metrics/quickpulse/export/exporter.ts +++ b/src/metrics/quickpulse/export/exporter.ts @@ -55,7 +55,7 @@ export class QuickpulseMetricExporter implements PushMetricExporter { * @param metrics - Resource metrics to export. * @param resultCallback - Result callback. */ - + async export( metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void, diff --git a/src/metrics/quickpulse/liveMetrics.ts b/src/metrics/quickpulse/liveMetrics.ts index 7e520e79..ea2f81c8 100644 --- a/src/metrics/quickpulse/liveMetrics.ts +++ b/src/metrics/quickpulse/liveMetrics.ts @@ -184,7 +184,7 @@ export class LiveMetrics { credentialScopes: parsedConnectionString.aadaudience || this.config.azureMonitorExporterOptions.credentialScopes, - + postCallback: this.quickPulseDone.bind(this), getDocumentsFn: this.getDocuments.bind(this), getErrorsFn: this.getErrors.bind(this), @@ -195,7 +195,7 @@ export class LiveMetrics { this.isCollectingData = false; this.pingInterval = PING_INTERVAL; // Default this.postInterval = POST_INTERVAL; - + this.handle = setTimeout(this.goQuickpulse.bind(this), this.pingInterval); this.handle.unref(); // Don't block apps from terminating this.lastCpuUsage = process.cpuUsage(); @@ -222,7 +222,7 @@ export class LiveMetrics { } catch (_error) { this.quickPulseDone(undefined); } - + this.handle = setTimeout(this.goQuickpulse.bind(this), this.pingInterval); this.handle.unref(); } @@ -231,7 +231,6 @@ export class LiveMetrics { } } - private async quickPulseDone(response: QuickpulseResponse | undefined): Promise { if (!response) { if (!this.isCollectingData) { @@ -259,7 +258,7 @@ export class LiveMetrics { if (!this.isCollectingData && this.meterProvider) { this.etag = ""; this.deactivateMetrics(); - + this.handle = setTimeout(this.goQuickpulse.bind(this), this.pingInterval); this.handle.unref(); } diff --git a/src/metrics/quickpulse/utils.ts b/src/metrics/quickpulse/utils.ts index b9edd1f3..dc204320 100644 --- a/src/metrics/quickpulse/utils.ts +++ b/src/metrics/quickpulse/utils.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - - import type { ReadableSpan } from "@opentelemetry/sdk-trace-base"; import type { SdkLogRecord } from "@opentelemetry/sdk-logs"; import type { @@ -394,15 +392,13 @@ export function getLogData(log: SdkLogRecord): ExceptionData | TraceData { const customDims = createCustomDimsFromAttributes(log.attributes); if (isExceptionTelemetry(log)) { return { - Message: String(log.attributes[SEMATTRS_EXCEPTION_MESSAGE]), - + StackTrace: String(log.attributes[SEMATTRS_EXCEPTION_STACKTRACE]), CustomDimensions: customDims, }; } else { return { - Message: String(log.body), CustomDimensions: customDims, }; @@ -483,7 +479,6 @@ function createCustomDimsFromAttributes( httpSemanticValues.includes(key as any) ) ) { - customDims.set(key, String(attributes[key])); } } diff --git a/src/shared/config.ts b/src/shared/config.ts index 9153fe6a..f40689d1 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -8,10 +8,7 @@ import { emptyResource, envDetector, } from "@opentelemetry/resources"; -import type { - BrowserSdkLoaderOptions, - InstrumentationOptions, -} from "../types.js"; +import type { BrowserSdkLoaderOptions, InstrumentationOptions } from "../types.js"; import type { MicrosoftOpenTelemetryOptions } from "../distro/types.js"; import type { Sampler } from "@opentelemetry/sdk-trace-base"; import type { AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-exporter"; diff --git a/src/shared/jsonConfig.ts b/src/shared/jsonConfig.ts index 5d80ad48..84b32b53 100644 --- a/src/shared/jsonConfig.ts +++ b/src/shared/jsonConfig.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - - import fs from "node:fs"; import path from "node:path"; import type { @@ -106,14 +104,22 @@ export class JsonConfig implements MicrosoftOpenTelemetryOptions { // Global options this.samplingRatio = jsonConfig.samplingRatio as number | undefined; this.tracesPerSecond = jsonConfig.tracesPerSecond as number | undefined; - this.instrumentationOptions = jsonConfig.instrumentationOptions as InstrumentationOptions | undefined; + this.instrumentationOptions = jsonConfig.instrumentationOptions as + | InstrumentationOptions + | undefined; // Azure Monitor-scoped options (flat JSON → nested structure) this.azureMonitor = { - azureMonitorExporterOptions: jsonConfig.azureMonitorExporterOptions as AzureMonitorExporterOptions | undefined, - browserSdkLoaderOptions: jsonConfig.browserSdkLoaderOptions as BrowserSdkLoaderOptions | undefined, + azureMonitorExporterOptions: jsonConfig.azureMonitorExporterOptions as + | AzureMonitorExporterOptions + | undefined, + browserSdkLoaderOptions: jsonConfig.browserSdkLoaderOptions as + | BrowserSdkLoaderOptions + | undefined, enableLiveMetrics: jsonConfig.enableLiveMetrics as boolean | undefined, enableStandardMetrics: jsonConfig.enableStandardMetrics as boolean | undefined, - enableTraceBasedSamplingForLogs: jsonConfig.enableTraceBasedSamplingForLogs as boolean | undefined, + enableTraceBasedSamplingForLogs: jsonConfig.enableTraceBasedSamplingForLogs as + | boolean + | undefined, }; } catch (err) { Logger.getInstance().info("Missing or invalid JSON config file: ", err); diff --git a/src/shared/logging/diagFileConsoleLogger.ts b/src/shared/logging/diagFileConsoleLogger.ts index 4cbe9b78..3b1888d8 100644 --- a/src/shared/logging/diagFileConsoleLogger.ts +++ b/src/shared/logging/diagFileConsoleLogger.ts @@ -60,7 +60,6 @@ export class DiagFileConsoleLogger implements DiagLogger { if (this._logToFile) { if (!this._fileCleanupTimer) { this._fileCleanupTimer = setInterval(() => { - this._fileCleanupTask(); }, this._cleanupTimeOut); this._fileCleanupTimer.unref(); @@ -68,7 +67,6 @@ export class DiagFileConsoleLogger implements DiagLogger { } } - public error(message?: any, ...args: any[]): void { if (this._shouldFilterResourceAttributeWarning(message, args)) { return; @@ -76,11 +74,10 @@ export class DiagFileConsoleLogger implements DiagLogger { if (this._shouldFilterAzureMonitorExporterWarning(message)) { return; } - + this.logMessage(message, args); } - public warn(message?: any, ...args: any[]): void { if (this._shouldFilterResourceAttributeWarning(message, args)) { return; @@ -88,11 +85,10 @@ export class DiagFileConsoleLogger implements DiagLogger { if (this._shouldFilterAzureMonitorExporterWarning(message)) { return; } - + this.logMessage(message, args); } - public info(message?: any, ...args: any[]): void { if (this._shouldFilterResourceAttributeWarning(message, args)) { return; @@ -100,11 +96,10 @@ export class DiagFileConsoleLogger implements DiagLogger { if (this._shouldFilterAzureMonitorExporterWarning(message)) { return; } - + this.logMessage(message, args); } - public debug(message?: any, ...args: any[]): void { if (this._shouldFilterResourceAttributeWarning(message, args)) { return; @@ -112,11 +107,10 @@ export class DiagFileConsoleLogger implements DiagLogger { if (this._shouldFilterAzureMonitorExporterWarning(message)) { return; } - + this.logMessage(message, args); } - public verbose(message?: any, ...args: any[]): void { if (this._shouldFilterResourceAttributeWarning(message, args)) { return; @@ -124,11 +118,10 @@ export class DiagFileConsoleLogger implements DiagLogger { if (this._shouldFilterAzureMonitorExporterWarning(message)) { return; } - + this.logMessage(message, args); } - public async logMessage(message?: any, ...optionalParams: any[]): Promise { try { const args = message ? [message, ...optionalParams] : optionalParams; @@ -136,11 +129,9 @@ export class DiagFileConsoleLogger implements DiagLogger { await this._storeToDisk(args); } if (this._logToConsole) { - console.log(...args); } } catch (err: any) { - console.log(this._TAG, `Failed to log to file: ${err && err.message}`); } } @@ -213,7 +204,6 @@ export class DiagFileConsoleLogger implements DiagLogger { try { await confirmDirExists(this._tempDir); } catch (err: any) { - console.log(this._TAG, `Failed to create directory for log file: ${err && err.message}`); return; } @@ -224,7 +214,6 @@ export class DiagFileConsoleLogger implements DiagLogger { try { await appendFileAsync(this._fileFullPath, data); } catch (appendError: any) { - console.log( this._TAG, `Failed to put log into file: ${appendError && appendError.message}`, @@ -247,7 +236,6 @@ export class DiagFileConsoleLogger implements DiagLogger { const backupPath = path.join(this._tempDir, `${new Date().getTime()}.${this._logFileName}`); await writeFileAsync(backupPath, buffer); } catch (err: any) { - console.log("Failed to generate backup log file", err); } finally { // Store logs @@ -277,7 +265,6 @@ export class DiagFileConsoleLogger implements DiagLogger { await unlinkAsync(pathToDelete); } } catch (err: any) { - console.log(this._TAG, `Failed to cleanup log files: ${err && err.message}`); } } diff --git a/src/shared/logging/logger.ts b/src/shared/logging/logger.ts index a3bb258e..ed103701 100644 --- a/src/shared/logging/logger.ts +++ b/src/shared/logging/logger.ts @@ -87,7 +87,6 @@ export class Logger { }; } - public error(message?: any, ...args: any[]): void { if (this.logToAzureLogger) { this.azureLogger.error(message, args); @@ -97,7 +96,6 @@ export class Logger { } } - public warn(message?: any, ...args: any[]): void { if (this.logToAzureLogger) { this.azureLogger.warning(message, args); @@ -107,7 +105,6 @@ export class Logger { } } - public info(message?: any, ...args: any[]): void { if (this.logToAzureLogger) { this.azureLogger.info(message, args); @@ -117,7 +114,6 @@ export class Logger { } } - public debug(message?: any, ...args: any[]): void { if (this.logToAzureLogger) { this.azureLogger.verbose(message, args); @@ -127,7 +123,6 @@ export class Logger { } } - public verbose(message?: any, ...args: any[]): void { if (this.logToAzureLogger) { this.azureLogger.verbose(message, args); diff --git a/src/shared/module.ts b/src/shared/module.ts index 230dc1a0..a06c12bb 100644 --- a/src/shared/module.ts +++ b/src/shared/module.ts @@ -14,7 +14,6 @@ import { fileURLToPath } from "node:url"; * @returns The directory name of the current module. */ export function dirName(): string { - // @ts-ignore ESM only output return dirname(fileURLToPath(import.meta.url)); } @@ -25,7 +24,6 @@ export function dirName(): string { * @internal */ export function getModuleParentURL(): string { - // @ts-ignore ESM only output return import.meta.url; } diff --git a/test/internal/unit/browserSdkLoader/browserSdkLoader.test.ts b/test/internal/unit/browserSdkLoader/browserSdkLoader.test.ts index 4764058a..75d06efb 100644 --- a/test/internal/unit/browserSdkLoader/browserSdkLoader.test.ts +++ b/test/internal/unit/browserSdkLoader/browserSdkLoader.test.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - - import type http from "node:http"; import { BrowserSdkLoader } from "../../../../src/browserSdkLoader/browserSdkLoader.js"; import * as BrowserSdkLoaderHelper from "../../../../src/browserSdkLoader/browserSdkLoaderHelper.js"; @@ -65,13 +63,12 @@ describe("#BrowserSdkLoader", () => { const browserSdkLoader = BrowserSdkLoader.getInstance(); const _headers: any = {}; - + const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, }; @@ -109,13 +106,11 @@ describe("#BrowserSdkLoader", () => { const _headers: any = {}; - const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, removeHeader: (header: string) => { @@ -153,13 +148,12 @@ describe("#BrowserSdkLoader", () => { useAzureMonitor(config); const browserSdkLoader = BrowserSdkLoader.getInstance(); const _headers: any = {}; - + const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, removeHeader: (header: string) => { @@ -190,13 +184,12 @@ describe("#BrowserSdkLoader", () => { const browserSdkLoader = BrowserSdkLoader.getInstance(); const _headers: any = {}; - + const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, removeHeader: (header: string) => { @@ -236,13 +229,12 @@ describe("#BrowserSdkLoader", () => { const browserSdkLoader = BrowserSdkLoader.getInstance(); const _headers: any = {}; - + const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, removeHeader: (header: string) => { @@ -282,13 +274,12 @@ describe("#BrowserSdkLoader", () => { const browserSdkLoader = BrowserSdkLoader.getInstance(); const _headers: any = {}; - + const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, removeHeader: (header: string) => { @@ -328,13 +319,12 @@ describe("#BrowserSdkLoader", () => { assert.equal(browserSdkLoader["_isIkeyValid"], true, "ikey should be set to valid"); const _headers: any = {}; - + const response: http.ServerResponse = { setHeader: (header: string, value: string) => { _headers[header] = value; }, getHeader: (header: string) => { - return _headers[header]; }, removeHeader: (header: string) => { diff --git a/test/internal/unit/logs/logHandler.test.ts b/test/internal/unit/logs/logHandler.test.ts index 49d63bbc..2ecef956 100644 --- a/test/internal/unit/logs/logHandler.test.ts +++ b/test/internal/unit/logs/logHandler.test.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - - import { trace, context, isValidTraceId, isValidSpanId } from "@opentelemetry/api"; import type { LogRecord as APILogRecord } from "@opentelemetry/api-logs"; import { SeverityNumber, logs } from "@opentelemetry/api-logs"; diff --git a/test/internal/unit/main.test.ts b/test/internal/unit/main.test.ts index a162b0d8..d2fc036d 100644 --- a/test/internal/unit/main.test.ts +++ b/test/internal/unit/main.test.ts @@ -292,7 +292,7 @@ describe("Main functions", () => { }, }; useAzureMonitor(config); - + const meterConfig = (_getSdkInstance() as any)?._meterProviderConfig; expect(meterConfig).toBeDefined(); expect(meterConfig?.views).toContain(customView); @@ -568,7 +568,6 @@ describe("Main functions", () => { }); }); - it("should update statsbeat env var based on reading instrumentations array", () => { const config: MicrosoftOpenTelemetryOptions = { instrumentationOptions: { @@ -760,7 +759,7 @@ describe("Main functions", () => { if (sharedState && sharedState.metricCollectors) { // Extract metric readers from metricCollectors - + metricReaders = sharedState.metricCollectors.map((collector: any) => collector._metricReader); foundProperty = "_sharedState.metricCollectors[].._metricReader"; } @@ -793,7 +792,6 @@ describe("Main functions", () => { // Verify the OTLP exporter has the correct URL configuration const delegate = exporter["_delegate"]; if (delegate) { - const transportParams = delegate._transport._transport._parameters; assert.strictEqual( transportParams.url, diff --git a/test/internal/unit/metrics/liveMetrics.test.ts b/test/internal/unit/metrics/liveMetrics.test.ts index c69db961..eab446ec 100644 --- a/test/internal/unit/metrics/liveMetrics.test.ts +++ b/test/internal/unit/metrics/liveMetrics.test.ts @@ -22,7 +22,6 @@ import { import type { MockInstance } from "vitest"; import { afterEach, assert, beforeEach, describe, expect, it, vi } from "vitest"; - describe("#LiveMetrics", () => { let exportStub: MockInstance<(typeof autoCollect)["quickpulseExporter"]["export"]>; let autoCollect: LiveMetrics; @@ -669,4 +668,3 @@ describe("#LiveMetrics", () => { ); }); }); - diff --git a/test/internal/unit/metrics/liveMetricsFilter.test.ts b/test/internal/unit/metrics/liveMetricsFilter.test.ts index c178ef69..3f08bb3e 100644 --- a/test/internal/unit/metrics/liveMetricsFilter.test.ts +++ b/test/internal/unit/metrics/liveMetricsFilter.test.ts @@ -6,7 +6,6 @@ import type { FilterConjunctionGroupInfo, FilterInfo, RemoteDependency, - Request, Exception, Trace, diff --git a/test/internal/unit/metrics/metricHandler.test.ts b/test/internal/unit/metrics/metricHandler.test.ts index f55d0dff..130622b6 100644 --- a/test/internal/unit/metrics/metricHandler.test.ts +++ b/test/internal/unit/metrics/metricHandler.test.ts @@ -292,7 +292,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Access the private _exportInterval property to verify it was set correctly - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -318,7 +318,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Should use default interval when env var is invalid - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -344,7 +344,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Should use default since negative values are invalid - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -370,7 +370,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Should use default since zero is invalid - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -396,7 +396,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Should use default when env var is not set - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -415,7 +415,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Should use the default _collectionInterval (60000ms) - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -441,7 +441,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // Should use environment variable over default - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -467,7 +467,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // parseInt should handle whitespace correctly - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, @@ -493,7 +493,7 @@ describe("MetricHandler", () => { const metricReader = metricHandler.getMetricReader(); // parseInt should truncate decimal values - + const exportInterval = (metricReader as any)._exportInterval; assert.strictEqual( exportInterval, diff --git a/test/internal/unit/shared/jsonConfig.test.ts b/test/internal/unit/shared/jsonConfig.test.ts index 91451699..99236617 100644 --- a/test/internal/unit/shared/jsonConfig.test.ts +++ b/test/internal/unit/shared/jsonConfig.test.ts @@ -65,8 +65,14 @@ describe("Json Config", () => { config.azureMonitor?.azureMonitorExporterOptions?.connectionString, "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", ); - assert.deepStrictEqual(config.azureMonitor?.azureMonitorExporterOptions?.disableOfflineStorage, true); - assert.deepStrictEqual(config.azureMonitor?.azureMonitorExporterOptions?.storageDirectory, "testPath"); + assert.deepStrictEqual( + config.azureMonitor?.azureMonitorExporterOptions?.disableOfflineStorage, + true, + ); + assert.deepStrictEqual( + config.azureMonitor?.azureMonitorExporterOptions?.storageDirectory, + "testPath", + ); assert.deepStrictEqual(config.samplingRatio, 0.3, "Wrong samplingRatio"); assert.deepStrictEqual(config.tracesPerSecond, 0.2, "Wrong tracesPerSecond"); assert.deepStrictEqual( @@ -136,12 +142,18 @@ describe("Json Config", () => { assert.strictEqual(config.instrumentationOptions?.postgreSql?.enabled, false); assert.strictEqual(config.instrumentationOptions?.redis?.enabled, false); assert.strictEqual(config.instrumentationOptions?.redis4?.enabled, false); - assert.strictEqual(config.azureMonitor?.azureMonitorExporterOptions?.connectionString, "testConnString"); + assert.strictEqual( + config.azureMonitor?.azureMonitorExporterOptions?.connectionString, + "testConnString", + ); assert.strictEqual( config.azureMonitor?.azureMonitorExporterOptions?.storageDirectory, "teststorageDirectory", ); - assert.strictEqual(config.azureMonitor?.azureMonitorExporterOptions?.disableOfflineStorage, true); + assert.strictEqual( + config.azureMonitor?.azureMonitorExporterOptions?.disableOfflineStorage, + true, + ); }); }); }); diff --git a/test/internal/unit/shared/logging/diagFileConsoleLogger.test.ts b/test/internal/unit/shared/logging/diagFileConsoleLogger.test.ts index 73e41cb8..3eefab3d 100644 --- a/test/internal/unit/shared/logging/diagFileConsoleLogger.test.ts +++ b/test/internal/unit/shared/logging/diagFileConsoleLogger.test.ts @@ -72,7 +72,6 @@ describe("Library/DiagFileConsoleLogger", () => { vi.spyOn(fileHelper, "confirmDirExists").mockImplementation(async () => {}); vi.spyOn(fileHelper, "accessAsync").mockImplementation(async () => {}); vi.spyOn(fileHelper, "getShallowFileSize").mockImplementation( - async () => // Fake file size check 123, @@ -81,10 +80,9 @@ describe("Library/DiagFileConsoleLogger", () => { const writeStub = vi.spyOn(fileHelper, "writeFileAsync").mockImplementation(async () => {}); const appendStub = vi.spyOn(fileHelper, "appendFileAsync").mockImplementation(async () => {}); - const readStub = vi.spyOn(fileHelper, "readFileAsync").mockImplementation( - - async () => Buffer.from("existing content"), - ); + const readStub = vi + .spyOn(fileHelper, "readFileAsync") + .mockImplementation(async () => Buffer.from("existing content")); logger["_logToFile"] = true; await logger["logMessage"]("backupTestMessage"); @@ -104,16 +102,14 @@ describe("Library/DiagFileConsoleLogger", () => { vi.spyOn(fileHelper, "confirmDirExists").mockImplementation(async () => {}); vi.spyOn(fileHelper, "accessAsync").mockImplementation(async () => {}); vi.spyOn(fileHelper, "getShallowFileSize").mockImplementation( - async () => // Fake file size check 123, ); const writeStub = vi.spyOn(fileHelper, "writeFileAsync").mockImplementation(async () => {}); - const readStub = vi.spyOn(fileHelper, "readFileAsync").mockImplementation( - - async () => Buffer.from("existing content"), - ); + const readStub = vi + .spyOn(fileHelper, "readFileAsync") + .mockImplementation(async () => Buffer.from("existing content")); logger["_maxSizeBytes"] = 122; logger["_logToFile"] = true; await logger["logMessage"]("backupTestMessage"); @@ -135,7 +131,6 @@ describe("Library/DiagFileConsoleLogger", () => { it("should remove backup files", async () => { vi.spyOn(fileHelper, "readdirAsync").mockImplementation( - async () => [ "applicationinsights.log", @@ -151,7 +146,6 @@ describe("Library/DiagFileConsoleLogger", () => { it("cleanup should keep configured number of backups", async () => { vi.spyOn(fileHelper, "readdirAsync").mockImplementation( - async () => [ "applicationinsights.log", diff --git a/test/internal/unit/traces/traceHandler.test.ts b/test/internal/unit/traces/traceHandler.test.ts index c4777139..92efdf01 100644 --- a/test/internal/unit/traces/traceHandler.test.ts +++ b/test/internal/unit/traces/traceHandler.test.ts @@ -35,7 +35,7 @@ import { RateLimitedSampler } from "@azure/monitor-opentelemetry-exporter"; describe("Library/TraceHandler", () => { const connectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333"; let http: typeof Http | null = null; - + let _config: InternalConfig; let handler: TraceHandler; let metricHandler: MetricHandler; @@ -181,7 +181,6 @@ describe("Library/TraceHandler", () => { }); }); - function createHandler(httpConfig: HttpInstrumentationConfig) { _config.instrumentationOptions.http = httpConfig; metricHandler = new MetricHandler(_config); @@ -218,7 +217,6 @@ describe("Library/TraceHandler", () => { require("http"); } - async function makeHttpRequest() { const options = { hostname: "localhost", diff --git a/test/utils/basic.ts b/test/utils/basic.ts index 291b3c1c..31b1d3ac 100644 --- a/test/utils/basic.ts +++ b/test/utils/basic.ts @@ -56,7 +56,6 @@ export class TraceBasicScenario implements Scenario { ).getDelegate() as NodeTracerProvider; } - async run(): Promise { const tracer = opentelemetry.trace.getTracer("basic"); const root = tracer.startSpan(`${this.constructor.name}.Root`, { @@ -216,7 +215,6 @@ export class MetricBasicScenario implements Scenario { }); } - async run(): Promise { const meter = opentelemetry.metrics.getMeter("basic"); const counter = meter.createCounter("testCounter"); @@ -404,7 +402,6 @@ export class LogBasicScenario implements Scenario { }); } - async run(): Promise { const logger = logs.getLogger("basic"); From 508ba9b74042ee55fde4f4aa62a20d747938de8d Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:02:45 -0700 Subject: [PATCH 2/5] fix: remove endOfLine from Prettier config and drop format from build --- .prettierrc.json | 1 - package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 04dfc2dd..b0e99e1c 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,7 +1,6 @@ { "arrowParens": "always", "bracketSpacing": true, - "endOfLine": "auto", "printWidth": 100, "semi": true, "singleQuote": false, diff --git a/package.json b/package.json index 86ba9833..5a559e27 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "types": "./dist/commonjs/index.d.ts", "type": "module", "scripts": { - "build": "npm run clean && npm run format && npm run lint && tsc -p tsconfig.src.json && tsc -p tsconfig.src.cjs.json", + "build": "npm run clean && npm run lint && tsc -p tsconfig.src.json && tsc -p tsconfig.src.cjs.json", "clean": "rimraf dist", "lint": "eslint src test", "lint:fix": "eslint src test --fix", From 0ab03d511332da79ebfb7a657784db62f18ef285 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:03:52 -0700 Subject: [PATCH 3/5] Update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d53f95f..c60faf43 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "types": "./dist/commonjs/index.d.ts", "type": "module", "scripts": { - "build": "npm run clean && npm run lint && tsc -p tsconfig.src.json && tsc -p tsconfig.src.cjs.json", + "build": "npm run clean && tsc -p tsconfig.src.json && tsc -p tsconfig.src.cjs.json", "clean": "rimraf dist", "lint": "eslint src test", "lint:fix": "eslint src test --fix", From d343b35db2724e1334ea33fec67947012ccaf544 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:09:06 -0700 Subject: [PATCH 4/5] npm install, format missing files --- .prettierrc.json | 1 + src/_a365/configuration/A365Configuration.ts | 28 ++++-- src/_a365/exporter/Agent365Exporter.ts | 17 ++-- src/_a365/exporter/utils.ts | 83 ++++++++++++----- src/genai/index.ts | 2 +- src/genai/instrumentations/langchain/index.ts | 4 +- .../langchain/langchainTraceInstrumentor.ts | 35 ++++---- .../instrumentations/langchain/tracer.ts | 39 +++++--- src/genai/instrumentations/langchain/utils.ts | 77 ++++++++++------ src/genai/semconv.ts | 46 +++++----- src/genai/utils.ts | 2 +- src/index.ts | 11 +-- test/internal/functional/langchain.test.ts | 37 +++++--- test/internal/functional/otlp.test.ts | 5 +- .../unit/a365/a365Configuration.test.ts | 5 +- .../unit/a365/agent365Exporter.test.ts | 10 +-- .../langchainTraceInstrumentor.test.ts | 12 +-- .../unit/genai/langchain/tracer.test.ts | 58 +++++++++--- .../unit/genai/langchain/utils.test.ts | 90 +++++++++++-------- 19 files changed, 346 insertions(+), 216 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index b0e99e1c..04dfc2dd 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,6 +1,7 @@ { "arrowParens": "always", "bracketSpacing": true, + "endOfLine": "auto", "printWidth": 100, "semi": true, "singleQuote": false, diff --git a/src/_a365/configuration/A365Configuration.ts b/src/_a365/configuration/A365Configuration.ts index 0d94ad0d..2bf64dc9 100644 --- a/src/_a365/configuration/A365Configuration.ts +++ b/src/_a365/configuration/A365Configuration.ts @@ -26,8 +26,18 @@ export const A365_ENV_VARS = { const DEFAULT_AUTH_SCOPE = "https://api.powerplatform.com/.default"; const VALID_CLUSTER_CATEGORIES: ReadonlySet = new Set([ - "local", "dev", "test", "preprod", "firstrelease", - "prod", "gov", "high", "dod", "mooncake", "ex", "rx", + "local", + "dev", + "test", + "preprod", + "firstrelease", + "prod", + "gov", + "high", + "dod", + "mooncake", + "ex", + "rx", ]); /** @@ -106,7 +116,10 @@ export class A365Configuration { const envScopes = getEnv(A365_ENV_VARS.AUTH_SCOPES); if (envScopes) { - authScopes = envScopes.split(",").map((s) => s.trim()).filter(Boolean); + authScopes = envScopes + .split(",") + .map((s) => s.trim()) + .filter(Boolean); } const envDomain = getEnv(A365_ENV_VARS.DOMAIN); @@ -128,10 +141,9 @@ export class A365Configuration { this.perRequestExport = perRequestExport; this.baggage = { - propagationEnabled: jsonA365?.baggage?.propagationEnabled - ?? options?.baggage?.propagationEnabled ?? true, - enrichSpans: jsonA365?.baggage?.enrichSpans - ?? options?.baggage?.enrichSpans ?? true, + propagationEnabled: + jsonA365?.baggage?.propagationEnabled ?? options?.baggage?.propagationEnabled ?? true, + enrichSpans: jsonA365?.baggage?.enrichSpans ?? options?.baggage?.enrichSpans ?? true, }; this.hosting = { @@ -156,7 +168,7 @@ export class A365Configuration { if (hasNonTrivialOptions) { Logger.getInstance().warn( "A365 configuration options are set but A365 is not enabled. " + - "Set `a365.enabled: true` or `MICROSOFT_OTEL_A365_EXPORTER_ENABLED=true` to enable.", + "Set `a365.enabled: true` or `MICROSOFT_OTEL_A365_EXPORTER_ENABLED=true` to enable.", ); } } diff --git a/src/_a365/exporter/Agent365Exporter.ts b/src/_a365/exporter/Agent365Exporter.ts index 1beb38e8..e1f4c7ca 100644 --- a/src/_a365/exporter/Agent365Exporter.ts +++ b/src/_a365/exporter/Agent365Exporter.ts @@ -133,8 +133,8 @@ export class Agent365Exporter implements SpanExporter { ? `/observabilityService/tenants/${encodeURIComponent(tenantId)}/agents/${encodeURIComponent(agentId)}/traces` : `/observability/tenants/${encodeURIComponent(tenantId)}/agents/${encodeURIComponent(agentId)}/traces`; - const baseUrl = this.options.domainOverride - ?? resolveAgent365Endpoint(this.options.clusterCategory); + const baseUrl = + this.options.domainOverride ?? resolveAgent365Endpoint(this.options.clusterCategory); const url = `${baseUrl}${endpointPath}?api-version=1`; const headers: Record = { @@ -158,10 +158,7 @@ export class Agent365Exporter implements SpanExporter { } } - private async resolveToken( - agentId: string, - tenantId: string, - ): Promise { + private async resolveToken(agentId: string, tenantId: string): Promise { if (!this.options.tokenResolver) return null; const result = this.options.tokenResolver(agentId, tenantId); return result instanceof Promise ? result : result; @@ -280,17 +277,13 @@ export class Agent365Exporter implements SpanExporter { ? ev.time[0] * 1_000_000_000 + ev.time[1] : (ev.time as number); const evAttrs = - ev.attributes && Object.keys(ev.attributes).length > 0 - ? { ...ev.attributes } - : null; + ev.attributes && Object.keys(ev.attributes).length > 0 ? { ...ev.attributes } : null; return { timeUnixNano: timeNs, name: ev.name, attributes: evAttrs }; }); const links: OTLPLink[] = (sp.links ?? []).map((ln) => { const lnAttrs = - ln.attributes && Object.keys(ln.attributes).length > 0 - ? { ...ln.attributes } - : null; + ln.attributes && Object.keys(ln.attributes).length > 0 ? { ...ln.attributes } : null; return { traceId: hexTraceId(ln.context.traceId), spanId: hexSpanId(ln.context.spanId), diff --git a/src/_a365/exporter/utils.ts b/src/_a365/exporter/utils.ts index 5fd60998..8c7acb2d 100644 --- a/src/_a365/exporter/utils.ts +++ b/src/_a365/exporter/utils.ts @@ -88,22 +88,32 @@ export function asStr(v: unknown): string | undefined { /** Get span kind name. */ export function kindName(kind: SpanKind): string { switch (kind) { - case SpanKind.INTERNAL: return "INTERNAL"; - case SpanKind.SERVER: return "SERVER"; - case SpanKind.CLIENT: return "CLIENT"; - case SpanKind.PRODUCER: return "PRODUCER"; - case SpanKind.CONSUMER: return "CONSUMER"; - default: return "UNSPECIFIED"; + case SpanKind.INTERNAL: + return "INTERNAL"; + case SpanKind.SERVER: + return "SERVER"; + case SpanKind.CLIENT: + return "CLIENT"; + case SpanKind.PRODUCER: + return "PRODUCER"; + case SpanKind.CONSUMER: + return "CONSUMER"; + default: + return "UNSPECIFIED"; } } /** Get status name. */ export function statusName(code: SpanStatusCode): string { switch (code) { - case SpanStatusCode.UNSET: return "UNSET"; - case SpanStatusCode.OK: return "OK"; - case SpanStatusCode.ERROR: return "ERROR"; - default: return "UNSET"; + case SpanStatusCode.UNSET: + return "UNSET"; + case SpanStatusCode.OK: + return "OK"; + case SpanStatusCode.ERROR: + return "ERROR"; + default: + return "UNSET"; } } @@ -169,7 +179,10 @@ function serializeOverflowSentinel(totalMessages: number): string { */ function trimString(value: string, bytesToShed: number): string { const currentBytes = Buffer.byteLength(value, "utf8"); - const targetContentBytes = Math.max(0, currentBytes - Math.max(1, bytesToShed) - TRUNCATED_SUFFIX_BYTES); + const targetContentBytes = Math.max( + 0, + currentBytes - Math.max(1, bytesToShed) - TRUNCATED_SUFFIX_BYTES, + ); if (targetContentBytes <= 0) return TRUNCATED_SUFFIX; let consumedBytes = 0; @@ -209,7 +222,10 @@ function createBlobShrinkAction( */ function collectShrinkActions( attributes: Record, - parsedMessages: Map> }> }>, + parsedMessages: Map< + string, + { version: string; messages: Array<{ parts: Array> }> } + >, ): ShrinkAction[] { const actions: ShrinkAction[] = []; @@ -244,16 +260,23 @@ function collectShrinkActions( // Tool/server JSON payload fields → sentinel const jsonField = - partType === "tool_call" ? "arguments" - : partType === "tool_call_response" ? "response" - : partType === "server_tool_call" ? "server_tool_call" - : partType === "server_tool_call_response" ? "server_tool_call_response" + partType === "tool_call" + ? "arguments" + : partType === "tool_call_response" + ? "response" + : partType === "server_tool_call" + ? "server_tool_call" + : partType === "server_tool_call_response" + ? "server_tool_call_response" : undefined; if (jsonField && part[jsonField] !== undefined && part[jsonField] !== JSON_SENTINEL) { let fieldSize: number; - try { fieldSize = Buffer.byteLength(JSON.stringify(part[jsonField]), "utf8"); } - catch { fieldSize = 0; } + try { + fieldSize = Buffer.byteLength(JSON.stringify(part[jsonField]), "utf8"); + } catch { + fieldSize = 0; + } if (fieldSize > 0) { const action: ShrinkAction = { contentBytes: fieldSize, @@ -269,7 +292,10 @@ function collectShrinkActions( } // Text/reasoning → trim - if ((partType === "text" || partType === "reasoning") && typeof part.content === "string") { + if ( + (partType === "text" || partType === "reasoning") && + typeof part.content === "string" + ) { const contentSize = Buffer.byteLength(part.content, "utf8"); if (contentSize > MIN_SHRINKABLE_STRING_BYTES) { const action: ShrinkAction = { @@ -316,7 +342,10 @@ function collectShrinkActions( function flushParsedMessages( attributes: Record, - parsedMessages: Map> }> }>, + parsedMessages: Map< + string, + { version: string; messages: Array<{ parts: Array> }> } + >, ): void { for (const [key, wrapper] of parsedMessages) { try { @@ -329,7 +358,10 @@ function flushParsedMessages( function flushParsedMessage( attributes: Record, - parsedMessages: Map> }> }>, + parsedMessages: Map< + string, + { version: string; messages: Array<{ parts: Array> }> } + >, key: string, ): void { const wrapper = parsedMessages.get(key); @@ -345,7 +377,10 @@ function flushParsedMessage( function runShrinkPhase( span: OTLPSpanLike, attributes: Record, - parsedMessages: Map> }> }>, + parsedMessages: Map< + string, + { version: string; messages: Array<{ parts: Array> }> } + >, currentSize: number, ): number { let nextSize = currentSize; @@ -429,7 +464,9 @@ export function truncateSpan(spanDict: T): T { if (parsed && Array.isArray(parsed.messages)) { messageCount = parsed.messages.length; } - } catch { /* not valid JSON */ } + } catch { + /* not valid JSON */ + } } attributes[key] = serializeOverflowSentinel(messageCount); parsedMessages.delete(key); diff --git a/src/genai/index.ts b/src/genai/index.ts index 992102d5..1324c231 100644 --- a/src/genai/index.ts +++ b/src/genai/index.ts @@ -1,4 +1,4 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -export * from './semconv.js'; +export * from "./semconv.js"; diff --git a/src/genai/instrumentations/langchain/index.ts b/src/genai/instrumentations/langchain/index.ts index df4b0de4..c4feb4a8 100644 --- a/src/genai/instrumentations/langchain/index.ts +++ b/src/genai/instrumentations/langchain/index.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -export { LangChainTraceInstrumentor } from './langchainTraceInstrumentor.js'; -export { LangChainTracer } from './tracer.js'; +export { LangChainTraceInstrumentor } from "./langchainTraceInstrumentor.js"; +export { LangChainTracer } from "./tracer.js"; diff --git a/src/genai/instrumentations/langchain/langchainTraceInstrumentor.ts b/src/genai/instrumentations/langchain/langchainTraceInstrumentor.ts index 2221adb8..62f8ade8 100644 --- a/src/genai/instrumentations/langchain/langchainTraceInstrumentor.ts +++ b/src/genai/instrumentations/langchain/langchainTraceInstrumentor.ts @@ -9,7 +9,7 @@ import { InstrumentationBase, InstrumentationConfig, InstrumentationModuleDefinition, - isWrapped + isWrapped, } from "@opentelemetry/instrumentation"; import { LangChainTracer } from "./tracer.js"; @@ -28,20 +28,19 @@ class LangChainTraceInstrumentorImpl extends InstrumentationBase=0.2.0"], files: [], patch: this.patch.bind(this), - unpatch: this.unpatch.bind(this) + unpatch: this.unpatch.bind(this), }; } private unpatch(moduleExports: Record): void { - const CallbackManager = moduleExports?.CallbackManager as typeof CallbackManagerModule.CallbackManager; + const CallbackManager = + moduleExports?.CallbackManager as typeof CallbackManagerModule.CallbackManager; if (!CallbackManager || !isWrapped(CallbackManager._configureSync)) { return; } @@ -95,9 +95,11 @@ class LangChainTraceInstrumentorImpl extends InstrumentationBase { return function ( this: CallbackManagerModuleType, - ...args: Parameters + ...args: Parameters<(typeof CallbackManager)["_configureSync"]> ) { - args[0] = addTracerToHandlers(instrumentor.otelTracer, args[0], { isContentRecordingEnabled: instrumentor.isContentRecordingEnabled }); + args[0] = addTracerToHandlers(instrumentor.otelTracer, args[0], { + isContentRecordingEnabled: instrumentor.isContentRecordingEnabled, + }); diag.debug("[LangChainTraceInstrumentor] _configureSync wrapped to add LangChainTracer"); return original.apply(this, args); }; @@ -139,8 +141,8 @@ class LangChainTraceInstrumentorImpl extends InstrumentationBase 0 ? messages[0] : messages; - const processed = preprocess?.map((msg: Record) => { - const content = extractMessageContent(msg); - if (!content) return null; + const preprocess = + getScopeType(run) === "inference" && messages.length > 0 ? messages[0] : messages; + const processed = preprocess + ?.map((msg: Record) => { + const content = extractMessageContent(msg); + if (!content) return null; - const msgType = getMessageType(msg); - if (shouldIncludeInputMessage(msgType)) { - return content; - } - return null; - }) + const msgType = getMessageType(msg); + if (shouldIncludeInputMessage(msgType)) { + return content; + } + return null; + }) .filter(Boolean); if (processed.length > 0) { @@ -118,7 +127,13 @@ function extractMessageContent(msg: Record): string | null { } // New LangChain format: {lc: 1, type: "constructor", kwargs: {content}} - if (msg.lc === 1 && msg.type === "constructor" && msg.kwargs && typeof msg.kwargs === "object" && !Array.isArray(msg.kwargs)) { + if ( + msg.lc === 1 && + msg.type === "constructor" && + msg.kwargs && + typeof msg.kwargs === "object" && + !Array.isArray(msg.kwargs) + ) { const kwargs = msg.kwargs as Record; if (isString(kwargs.content)) return kwargs.content; } @@ -243,10 +258,12 @@ export function setOutputMessagesAttribute(run: Run, span: Span) { // Model - Helper to extract model name from run export function getModel(run: Run): string | undefined { - return [run.outputs?.generations?.[0]?.[0]?.message?.kwargs?.response_metadata?.model_name, + return [ + run.outputs?.generations?.[0]?.[0]?.message?.kwargs?.response_metadata?.model_name, run.extra?.metadata?.ls_model_name, run.extra?.invocation_params?.model, - run.extra?.invocation_params?.model_name] + run.extra?.invocation_params?.model_name, + ] .map((v) => (v != null ? String(v).trim() : "")) .find((v) => v.length > 0); } @@ -260,18 +277,14 @@ export function setModelAttribute(run: Run, span: Span) { // Provider export function setProviderNameAttribute(run: Run, span: Span) { const provider = (run.extra?.metadata as Record | undefined)?.ls_provider; - if (isString(provider)) - span.setAttribute(ATTR_GEN_AI_PROVIDER_NAME, provider.toLowerCase()); + if (isString(provider)) span.setAttribute(ATTR_GEN_AI_PROVIDER_NAME, provider.toLowerCase()); } export function setSessionIdAttribute(run: Run, span: Span): void { const metadata = run.extra?.metadata as Record | undefined; if (!metadata) return; - const sessionId = - metadata.session_id ?? - metadata.conversation_id ?? - metadata.thread_id; + const sessionId = metadata.session_id ?? metadata.conversation_id ?? metadata.thread_id; if (typeof sessionId === "string" && sessionId.length > 0) { span.setAttribute(ATTR_MICROSOFT_SESSION_ID, sessionId); @@ -285,18 +298,23 @@ export function setSystemInstructionsAttribute(run: Run, span: Span) { return; } - const prompts = Array.isArray(inputs.prompts) ? inputs.prompts.map(p => String(p ?? "").trim()).filter(Boolean).join("\n") : ""; - if (prompts) - return span.setAttribute(ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, prompts); + const prompts = Array.isArray(inputs.prompts) + ? inputs.prompts + .map((p) => String(p ?? "").trim()) + .filter(Boolean) + .join("\n") + : ""; + if (prompts) return span.setAttribute(ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, prompts); const messages = Array.isArray(inputs.messages) ? inputs.messages : []; const systemText = messages .filter((m: Record) => m.lc_type === "system") - .map((m: Record) => String((m.lc_kwargs as Record | undefined)?.content ?? "").trim()) + .map((m: Record) => + String((m.lc_kwargs as Record | undefined)?.content ?? "").trim(), + ) .filter(Boolean) .join("\n"); - if (systemText) - span.setAttribute(ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, systemText); + if (systemText) span.setAttribute(ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, systemText); } // Tokens (input and output) @@ -310,7 +328,10 @@ export function setTokenAttributes(run: Run, span: Span) { run.outputs?.message?.response_metadata?.usage || run.outputs?.message?.response_metadata?.tokenUsage || run.outputs?.messages - ?.map((msg: Record) => (msg.response_metadata as Record | undefined)?.tokenUsage) + ?.map( + (msg: Record) => + (msg.response_metadata as Record | undefined)?.tokenUsage, + ) .filter(Boolean)[0]; if (!usage || typeof usage !== "object") { diff --git a/src/genai/semconv.ts b/src/genai/semconv.ts index 13776cc6..f9fb1b36 100644 --- a/src/genai/semconv.ts +++ b/src/genai/semconv.ts @@ -12,40 +12,40 @@ */ // --- Span operation names --- -export const GEN_AI_OPERATION_INVOKE_AGENT = 'invoke_agent' as const; -export const GEN_AI_OPERATION_EXECUTE_TOOL = 'execute_tool' as const; -export const GEN_AI_OPERATION_OUTPUT_MESSAGES = 'output_messages' as const; -export const GEN_AI_OPERATION_CHAT = 'chat' as const; +export const GEN_AI_OPERATION_INVOKE_AGENT = "invoke_agent" as const; +export const GEN_AI_OPERATION_EXECUTE_TOOL = "execute_tool" as const; +export const GEN_AI_OPERATION_OUTPUT_MESSAGES = "output_messages" as const; +export const GEN_AI_OPERATION_CHAT = "chat" as const; // --- Attributes (ATTR_ prefix, following OTel convention) --- // Error -export const ATTR_ERROR_TYPE = 'error.type' as const; -export const ATTR_ERROR_MESSAGE = 'error.message' as const; +export const ATTR_ERROR_TYPE = "error.type" as const; +export const ATTR_ERROR_MESSAGE = "error.message" as const; // GenAI core -export const ATTR_GEN_AI_OPERATION_NAME = 'gen_ai.operation.name' as const; -export const ATTR_GEN_AI_REQUEST_MODEL = 'gen_ai.request.model' as const; -export const ATTR_GEN_AI_RESPONSE_MODEL = 'gen_ai.response.model' as const; -export const ATTR_GEN_AI_PROVIDER_NAME = 'gen_ai.provider.name' as const; -export const ATTR_GEN_AI_SYSTEM_INSTRUCTIONS = 'gen_ai.system_instructions' as const; -export const ATTR_GEN_AI_INPUT_MESSAGES = 'gen_ai.input.messages' as const; -export const ATTR_GEN_AI_OUTPUT_MESSAGES = 'gen_ai.output.messages' as const; +export const ATTR_GEN_AI_OPERATION_NAME = "gen_ai.operation.name" as const; +export const ATTR_GEN_AI_REQUEST_MODEL = "gen_ai.request.model" as const; +export const ATTR_GEN_AI_RESPONSE_MODEL = "gen_ai.response.model" as const; +export const ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name" as const; +export const ATTR_GEN_AI_SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions" as const; +export const ATTR_GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages" as const; +export const ATTR_GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages" as const; // GenAI usage -export const ATTR_GEN_AI_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens' as const; -export const ATTR_GEN_AI_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens' as const; +export const ATTR_GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens" as const; +export const ATTR_GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens" as const; // GenAI agent -export const ATTR_GEN_AI_AGENT_ID = 'gen_ai.agent.id' as const; -export const ATTR_GEN_AI_AGENT_NAME = 'gen_ai.agent.name' as const; +export const ATTR_GEN_AI_AGENT_ID = "gen_ai.agent.id" as const; +export const ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name" as const; // GenAI tool -export const ATTR_GEN_AI_TOOL_CALL_ID = 'gen_ai.tool.call.id' as const; -export const ATTR_GEN_AI_TOOL_NAME = 'gen_ai.tool.name' as const; -export const ATTR_GEN_AI_TOOL_CALL_ARGUMENTS = 'gen_ai.tool.call.arguments' as const; -export const ATTR_GEN_AI_TOOL_CALL_RESULT = 'gen_ai.tool.call.result' as const; -export const ATTR_GEN_AI_TOOL_TYPE = 'gen_ai.tool.type' as const; +export const ATTR_GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id" as const; +export const ATTR_GEN_AI_TOOL_NAME = "gen_ai.tool.name" as const; +export const ATTR_GEN_AI_TOOL_CALL_ARGUMENTS = "gen_ai.tool.call.arguments" as const; +export const ATTR_GEN_AI_TOOL_CALL_RESULT = "gen_ai.tool.call.result" as const; +export const ATTR_GEN_AI_TOOL_TYPE = "gen_ai.tool.type" as const; // Microsoft-specific (not in OTel semconv) -export const ATTR_MICROSOFT_SESSION_ID = 'microsoft.session.id' as const; +export const ATTR_MICROSOFT_SESSION_ID = "microsoft.session.id" as const; diff --git a/src/genai/utils.ts b/src/genai/utils.ts index 3e669c67..738ce723 100644 --- a/src/genai/utils.ts +++ b/src/genai/utils.ts @@ -22,7 +22,7 @@ */ export const MAX_ATTRIBUTE_LENGTH = 8_192; -const TRUNCATION_SUFFIX = '...[truncated]'; +const TRUNCATION_SUFFIX = "...[truncated]"; /** * Truncate a string value to {@link MAX_ATTRIBUTE_LENGTH} characters. diff --git a/src/index.ts b/src/index.ts index 8c67a868..a94c1428 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,17 +21,10 @@ export type { // ── Re-exports from A365 configuration ────────────────────────────────────── export { A365Configuration } from "./_a365/index.js"; -export type { - ClusterCategory, - A365BaggageOptions, - A365HostingOptions, -} from "./_a365/index.js"; +export type { ClusterCategory, A365BaggageOptions, A365HostingOptions } from "./_a365/index.js"; // ── Re-exports from types ─────────────────────────────────────────────────── -export type { - OpenAIAgentsInstrumentationConfig, - LangChainInstrumentationConfig, -} from "./types.js"; +export type { OpenAIAgentsInstrumentationConfig, LangChainInstrumentationConfig } from "./types.js"; // ── Azure Monitor backward-compatible API ─────────────────────────────────── diff --git a/test/internal/functional/langchain.test.ts b/test/internal/functional/langchain.test.ts index 7f4c1652..d39c38e2 100644 --- a/test/internal/functional/langchain.test.ts +++ b/test/internal/functional/langchain.test.ts @@ -10,10 +10,7 @@ */ import { afterEach, assert, describe, it } from "vitest"; -import { - SpanKind, - SpanStatusCode, -} from "@opentelemetry/api"; +import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; import { BasicTracerProvider, InMemorySpanExporter, @@ -197,15 +194,23 @@ describe("LangChain Instrumentation Functional Tests", () => { // End in reverse order (children end before parent) await (langchainTracer as unknown as { _endTrace(r: Run): Promise })._endTrace(llmRun); await (langchainTracer as unknown as { _endTrace(r: Run): Promise })._endTrace(toolRun); - await (langchainTracer as unknown as { _endTrace(r: Run): Promise })._endTrace(agentRun); + await (langchainTracer as unknown as { _endTrace(r: Run): Promise })._endTrace( + agentRun, + ); const spans = exporter.getFinishedSpans(); assert.strictEqual(spans.length, 3, `expected 3 spans, got ${spans.length}`); // Find spans by operation - const agentSpan = spans.find((s) => s.attributes[ATTR_GEN_AI_OPERATION_NAME] === GEN_AI_OPERATION_INVOKE_AGENT); - const toolSpan = spans.find((s) => s.attributes[ATTR_GEN_AI_OPERATION_NAME] === GEN_AI_OPERATION_EXECUTE_TOOL); - const llmSpan = spans.find((s) => s.attributes[ATTR_GEN_AI_OPERATION_NAME] === GEN_AI_OPERATION_CHAT); + const agentSpan = spans.find( + (s) => s.attributes[ATTR_GEN_AI_OPERATION_NAME] === GEN_AI_OPERATION_INVOKE_AGENT, + ); + const toolSpan = spans.find( + (s) => s.attributes[ATTR_GEN_AI_OPERATION_NAME] === GEN_AI_OPERATION_EXECUTE_TOOL, + ); + const llmSpan = spans.find( + (s) => s.attributes[ATTR_GEN_AI_OPERATION_NAME] === GEN_AI_OPERATION_CHAT, + ); assert.ok(agentSpan, "agent span should exist"); assert.ok(toolSpan, "tool span should exist"); @@ -230,12 +235,12 @@ describe("LangChain Instrumentation Functional Tests", () => { assert.strictEqual( toolSpan!.parentSpanContext?.spanId, agentSpan!.spanContext().spanId, - "tool span should be child of agent span" + "tool span should be child of agent span", ); assert.strictEqual( llmSpan!.parentSpanContext?.spanId, agentSpan!.spanContext().spanId, - "llm span should be child of agent span" + "llm span should be child of agent span", ); }); }); @@ -263,8 +268,16 @@ describe("LangChain Instrumentation Functional Tests", () => { assert.strictEqual(spans.length, 1); const span = spans[0]; - assert.strictEqual(span.attributes[ATTR_GEN_AI_INPUT_MESSAGES], undefined, "should not record input messages"); - assert.strictEqual(span.attributes[ATTR_GEN_AI_OUTPUT_MESSAGES], undefined, "should not record output messages"); + assert.strictEqual( + span.attributes[ATTR_GEN_AI_INPUT_MESSAGES], + undefined, + "should not record input messages", + ); + assert.strictEqual( + span.attributes[ATTR_GEN_AI_OUTPUT_MESSAGES], + undefined, + "should not record output messages", + ); }); it("records message content when enabled", async () => { diff --git a/test/internal/functional/otlp.test.ts b/test/internal/functional/otlp.test.ts index ace661ea..187f717a 100644 --- a/test/internal/functional/otlp.test.ts +++ b/test/internal/functional/otlp.test.ts @@ -8,10 +8,7 @@ import { logs, SeverityNumber } from "@opentelemetry/api-logs"; import type { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; import type { MeterProvider } from "@opentelemetry/sdk-metrics"; import type { LoggerProvider } from "@opentelemetry/sdk-logs"; -import { - useMicrosoftOpenTelemetry, - shutdownMicrosoftOpenTelemetry, -} from "../../../src/index.js"; +import { useMicrosoftOpenTelemetry, shutdownMicrosoftOpenTelemetry } from "../../../src/index.js"; import type { HttpClient, PipelineRequest } from "@azure/core-rest-pipeline"; import { describe, it, beforeEach, afterEach, expect, vi } from "vitest"; diff --git a/test/internal/unit/a365/a365Configuration.test.ts b/test/internal/unit/a365/a365Configuration.test.ts index e163029e..9b46fadc 100644 --- a/test/internal/unit/a365/a365Configuration.test.ts +++ b/test/internal/unit/a365/a365Configuration.test.ts @@ -250,7 +250,10 @@ describe("A365Configuration", () => { describe("env var constants", () => { it("should have correct env var names", () => { assert.strictEqual(A365_ENV_VARS.EXPORTER_ENABLED, "MICROSOFT_OTEL_A365_EXPORTER_ENABLED"); - assert.strictEqual(A365_ENV_VARS.PER_REQUEST_EXPORT, "MICROSOFT_OTEL_A365_PER_REQUEST_EXPORT"); + assert.strictEqual( + A365_ENV_VARS.PER_REQUEST_EXPORT, + "MICROSOFT_OTEL_A365_PER_REQUEST_EXPORT", + ); assert.strictEqual(A365_ENV_VARS.AUTH_SCOPES, "MICROSOFT_OTEL_A365_AUTH_SCOPES"); assert.strictEqual(A365_ENV_VARS.DOMAIN, "MICROSOFT_OTEL_A365_DOMAIN"); assert.strictEqual(A365_ENV_VARS.CLUSTER_CATEGORY, "MICROSOFT_OTEL_A365_CLUSTER_CATEGORY"); diff --git a/test/internal/unit/a365/agent365Exporter.test.ts b/test/internal/unit/a365/agent365Exporter.test.ts index 36df8ce5..81fdf74b 100644 --- a/test/internal/unit/a365/agent365Exporter.test.ts +++ b/test/internal/unit/a365/agent365Exporter.test.ts @@ -291,10 +291,7 @@ describe("Exporter utils", () => { describe("resolveAgent365Endpoint", () => { it("should return prod endpoint for prod category", () => { - assert.strictEqual( - resolveAgent365Endpoint("prod"), - "https://agent365.svc.cloud.microsoft", - ); + assert.strictEqual(resolveAgent365Endpoint("prod"), "https://agent365.svc.cloud.microsoft"); }); }); @@ -334,7 +331,10 @@ describe("Exporter utils", () => { const span = { attributes: { key: largeValue } }; const result = truncateSpan(span); const size = Buffer.byteLength(JSON.stringify(result), "utf8"); - assert.ok(size <= MAX_SPAN_SIZE_BYTES, `Truncated span is ${size} bytes, expected <= ${MAX_SPAN_SIZE_BYTES}`); + assert.ok( + size <= MAX_SPAN_SIZE_BYTES, + `Truncated span is ${size} bytes, expected <= ${MAX_SPAN_SIZE_BYTES}`, + ); assert.ok((result.attributes!["key"] as string).endsWith("[truncated]")); }); diff --git a/test/internal/unit/genai/langchain/langchainTraceInstrumentor.test.ts b/test/internal/unit/genai/langchain/langchainTraceInstrumentor.test.ts index acce63c0..9248f714 100644 --- a/test/internal/unit/genai/langchain/langchainTraceInstrumentor.test.ts +++ b/test/internal/unit/genai/langchain/langchainTraceInstrumentor.test.ts @@ -36,7 +36,7 @@ describe("LangChainTraceInstrumentor", () => { assert.notStrictEqual( mockModule.CallbackManager._configureSync, configureSyncOriginal, - "_configureSync should be wrapped" + "_configureSync should be wrapped", ); }); @@ -49,17 +49,11 @@ describe("LangChainTraceInstrumentor", () => { describe("enable / disable", () => { it("throws when not initialized", () => { - assert.throws( - () => LangChainTraceInstrumentor.enable(), - /must be initialized first/ - ); + assert.throws(() => LangChainTraceInstrumentor.enable(), /must be initialized first/); }); it("throws disable when not initialized", () => { - assert.throws( - () => LangChainTraceInstrumentor.disable(), - /must be initialized first/ - ); + assert.throws(() => LangChainTraceInstrumentor.disable(), /must be initialized first/); }); it("enable and disable do not throw after initialization", () => { diff --git a/test/internal/unit/genai/langchain/tracer.test.ts b/test/internal/unit/genai/langchain/tracer.test.ts index 6a1b039a..c312949f 100644 --- a/test/internal/unit/genai/langchain/tracer.test.ts +++ b/test/internal/unit/genai/langchain/tracer.test.ts @@ -2,7 +2,14 @@ // Licensed under the MIT License. import { afterEach, assert, describe, it, vi } from "vitest"; -import { Span, SpanContext, SpanKind, SpanStatusCode, Tracer, TraceFlags } from "@opentelemetry/api"; +import { + Span, + SpanContext, + SpanKind, + SpanStatusCode, + Tracer, + TraceFlags, +} from "@opentelemetry/api"; import type { Run } from "@langchain/core/tracers/base"; import { LangChainTracer } from "../../../../../src/genai/instrumentations/langchain/tracer.js"; import { @@ -41,7 +48,12 @@ function makeLangGraphRun(overrides: Partial = {}): Run { }); } -function createMockSpan(): Span & { attrs: Record; ended: boolean; endTime?: unknown; statusObj?: { code: SpanStatusCode } } { +function createMockSpan(): Span & { + attrs: Record; + ended: boolean; + endTime?: unknown; + statusObj?: { code: SpanStatusCode }; +} { const spanCtx: SpanContext = { traceId: "aaaa0000bbbb0000cccc0000dddd0000", spanId: "1111000022220000", @@ -71,21 +83,37 @@ function createMockSpan(): Span & { attrs: Record; ended: boole addLink: vi.fn(), addLinks: vi.fn(), }; - return mockSpan as unknown as Span & { attrs: Record; ended: boolean; endTime?: unknown; statusObj?: { code: SpanStatusCode } }; + return mockSpan as unknown as Span & { + attrs: Record; + ended: boolean; + endTime?: unknown; + statusObj?: { code: SpanStatusCode }; + }; } -function createMockTracer(): Tracer & { lastSpan: ReturnType | undefined; spans: ReturnType[] } { +function createMockTracer(): Tracer & { + lastSpan: ReturnType | undefined; + spans: ReturnType[]; +} { const mockTracer = { lastSpan: undefined as ReturnType | undefined, spans: [] as ReturnType[], - startSpan: vi.fn(function (this: typeof mockTracer, _name: string, _options?: unknown, _ctx?: unknown) { + startSpan: vi.fn(function ( + this: typeof mockTracer, + _name: string, + _options?: unknown, + _ctx?: unknown, + ) { const span = createMockSpan(); this.lastSpan = span; this.spans.push(span); return span; }), }; - return mockTracer as unknown as Tracer & { lastSpan: ReturnType | undefined; spans: ReturnType[] }; + return mockTracer as unknown as Tracer & { + lastSpan: ReturnType | undefined; + spans: ReturnType[]; + }; } afterEach(() => { @@ -110,7 +138,7 @@ describe("LangChainTracer", () => { assert.ok(tracer.lastSpan, "should have created a span"); assert.ok( (tracer.startSpan as ReturnType).mock.calls[0][0].includes("chat"), - "span name should include operation type" + "span name should include operation type", ); }); @@ -199,8 +227,8 @@ describe("LangChainTracer", () => { assert.strictEqual(span.statusObj?.code, SpanStatusCode.ERROR); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_ERROR_MESSAGE && c[1] === "Something went wrong" - ) + (c: unknown[]) => c[0] === ATTR_ERROR_MESSAGE && c[1] === "Something went wrong", + ), ); }); @@ -213,8 +241,8 @@ describe("LangChainTracer", () => { await (lct as unknown as { _endTrace(run: Run): Promise })._endTrace(run); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_OPERATION_NAME && c[1] === "chat" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_OPERATION_NAME && c[1] === "chat", + ), ); }); @@ -230,7 +258,9 @@ describe("LangChainTracer", () => { await lct.onRunCreate(run); const span = tracer.lastSpan!; await (lct as unknown as { _endTrace(run: Run): Promise })._endTrace(run); - const attrKeys = (span.setAttribute as ReturnType).mock.calls.map((c: unknown[]) => c[0]); + const attrKeys = (span.setAttribute as ReturnType).mock.calls.map( + (c: unknown[]) => c[0], + ); assert.ok(!attrKeys.includes("gen_ai.tool.call.arguments"), "should not set tool arguments"); assert.ok(!attrKeys.includes("gen_ai.input.messages"), "should not set input messages"); }); @@ -247,7 +277,9 @@ describe("LangChainTracer", () => { await lct.onRunCreate(run); const span = tracer.lastSpan!; await (lct as unknown as { _endTrace(run: Run): Promise })._endTrace(run); - const attrKeys = (span.setAttribute as ReturnType).mock.calls.map((c: unknown[]) => c[0]); + const attrKeys = (span.setAttribute as ReturnType).mock.calls.map( + (c: unknown[]) => c[0], + ); assert.ok(attrKeys.includes("gen_ai.tool.name"), "should set tool name"); }); diff --git a/test/internal/unit/genai/langchain/utils.test.ts b/test/internal/unit/genai/langchain/utils.test.ts index 2ea98da1..33949542 100644 --- a/test/internal/unit/genai/langchain/utils.test.ts +++ b/test/internal/unit/genai/langchain/utils.test.ts @@ -138,8 +138,8 @@ describe("setOperationTypeAttribute", () => { setOperationTypeAttribute("chat", span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_OPERATION_NAME && c[1] === "chat" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_OPERATION_NAME && c[1] === "chat", + ), ); }); }); @@ -151,8 +151,8 @@ describe("setAgentAttributes", () => { setAgentAttributes(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_AGENT_NAME && c[1] === "WeatherAgent" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_AGENT_NAME && c[1] === "WeatherAgent", + ), ); }); @@ -181,7 +181,9 @@ describe("setToolAttributes", () => { }); setToolAttributes(run, span); const calls = (span.setAttribute as ReturnType).mock.calls; - assert.ok(calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_TOOL_NAME && c[1] === "search_web")); + assert.ok( + calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_TOOL_NAME && c[1] === "search_web"), + ); assert.ok(calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_TOOL_CALL_ARGUMENTS)); assert.ok(calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_TOOL_CALL_RESULT)); assert.ok(calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_TOOL_TYPE && c[1] === "extension")); @@ -197,7 +199,10 @@ describe("setToolAttributes", () => { it("does nothing when serialized is missing", () => { const span = makeSpan(); - const run = makeRun({ run_type: "tool", serialized: undefined as unknown as Record }); + const run = makeRun({ + run_type: "tool", + serialized: undefined as unknown as Record, + }); setToolAttributes(run, span); assert.strictEqual((span.setAttribute as ReturnType).mock.calls.length, 0); }); @@ -232,11 +237,7 @@ describe("setInputMessagesAttribute", () => { const run = makeRun({ run_type: "llm", inputs: { - messages: [ - [ - { lc_type: "human", lc_kwargs: { content: "What is 2+2?" } }, - ], - ], + messages: [[{ lc_type: "human", lc_kwargs: { content: "What is 2+2?" } }]], }, }); setInputMessagesAttribute(run, span); @@ -255,7 +256,10 @@ describe("setInputMessagesAttribute", () => { inputs: { messages: [ [ - { lc_kwargs: { content: "Build this" }, id: ["langchain", "schema", "messages", "HumanMessage"] }, + { + lc_kwargs: { content: "Build this" }, + id: ["langchain", "schema", "messages", "HumanMessage"], + }, ], ], }, @@ -281,9 +285,7 @@ describe("setOutputMessagesAttribute", () => { const run = makeRun({ run_type: "llm", outputs: { - messages: [ - { role: "assistant", content: "Here is the answer" }, - ], + messages: [{ role: "assistant", content: "Here is the answer" }], }, }); setOutputMessagesAttribute(run, span); @@ -347,8 +349,8 @@ describe("setModelAttribute", () => { setModelAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_REQUEST_MODEL && c[1] === "gpt-4o" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_REQUEST_MODEL && c[1] === "gpt-4o", + ), ); }); @@ -360,8 +362,8 @@ describe("setModelAttribute", () => { setModelAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_REQUEST_MODEL && c[1] === "claude-3" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_REQUEST_MODEL && c[1] === "claude-3", + ), ); }); @@ -373,8 +375,8 @@ describe("setModelAttribute", () => { setModelAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_REQUEST_MODEL && c[1] === "llama-3" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_REQUEST_MODEL && c[1] === "llama-3", + ), ); }); @@ -393,8 +395,8 @@ describe("setProviderNameAttribute", () => { setProviderNameAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_GEN_AI_PROVIDER_NAME && c[1] === "openai" - ) + (c: unknown[]) => c[0] === ATTR_GEN_AI_PROVIDER_NAME && c[1] === "openai", + ), ); }); @@ -413,8 +415,8 @@ describe("setSessionIdAttribute", () => { setSessionIdAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_MICROSOFT_SESSION_ID && c[1] === "sess-123" - ) + (c: unknown[]) => c[0] === ATTR_MICROSOFT_SESSION_ID && c[1] === "sess-123", + ), ); }); @@ -424,8 +426,8 @@ describe("setSessionIdAttribute", () => { setSessionIdAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_MICROSOFT_SESSION_ID && c[1] === "conv-456" - ) + (c: unknown[]) => c[0] === ATTR_MICROSOFT_SESSION_ID && c[1] === "conv-456", + ), ); }); @@ -435,8 +437,8 @@ describe("setSessionIdAttribute", () => { setSessionIdAttribute(run, span); assert.ok( (span.setAttribute as ReturnType).mock.calls.some( - (c: unknown[]) => c[0] === ATTR_MICROSOFT_SESSION_ID && c[1] === "thread-789" - ) + (c: unknown[]) => c[0] === ATTR_MICROSOFT_SESSION_ID && c[1] === "thread-789", + ), ); }); @@ -466,8 +468,8 @@ describe("setSystemInstructionsAttribute", () => { (span.setAttribute as ReturnType).mock.calls.some( (c: unknown[]) => c[0] === ATTR_GEN_AI_SYSTEM_INSTRUCTIONS && - (c[1] as string).includes("You are a helpful assistant.") - ) + (c[1] as string).includes("You are a helpful assistant."), + ), ); }); @@ -486,8 +488,8 @@ describe("setSystemInstructionsAttribute", () => { (span.setAttribute as ReturnType).mock.calls.some( (c: unknown[]) => c[0] === ATTR_GEN_AI_SYSTEM_INSTRUCTIONS && - (c[1] as string).includes("System prompt here") - ) + (c[1] as string).includes("System prompt here"), + ), ); }); @@ -504,15 +506,17 @@ describe("setTokenAttributes", () => { const span = makeSpan(); const run = makeRun({ outputs: { - generations: [ - [{ message: { usage_metadata: { input_tokens: 100, output_tokens: 50 } } }], - ], + generations: [[{ message: { usage_metadata: { input_tokens: 100, output_tokens: 50 } } }]], }, }); setTokenAttributes(run, span); const calls = (span.setAttribute as ReturnType).mock.calls; - assert.ok(calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_USAGE_INPUT_TOKENS && c[1] === 100)); - assert.ok(calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_USAGE_OUTPUT_TOKENS && c[1] === 50)); + assert.ok( + calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_USAGE_INPUT_TOKENS && c[1] === 100), + ); + assert.ok( + calls.some((c: unknown[]) => c[0] === ATTR_GEN_AI_USAGE_OUTPUT_TOKENS && c[1] === 50), + ); }); it("extracts from response_metadata.tokenUsage", () => { @@ -520,7 +524,15 @@ describe("setTokenAttributes", () => { const run = makeRun({ outputs: { generations: [ - [{ message: { kwargs: { response_metadata: { tokenUsage: { input_tokens: 10, output_tokens: 5 } } } } }], + [ + { + message: { + kwargs: { + response_metadata: { tokenUsage: { input_tokens: 10, output_tokens: 5 } }, + }, + }, + }, + ], ], }, }); From b328d37cfd36536b52db4128398608822faa5ebf Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:12:56 -0700 Subject: [PATCH 5/5] fix: sync package-lock.json with eslint-config-prettier dependency --- package-lock.json | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac307198..0b3a5396 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,8 @@ "@vitest/coverage-istanbul": "^3.0.0", "dotenv": "^16.0.0", "eslint": "^9.0.0", - "prettier": "^3.0.0", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.3.3", "rimraf": "^6.0.0", "typescript": "^5.6.0", "typescript-eslint": "^8.58.0", @@ -59,14 +60,6 @@ }, "engines": { "node": ">=20.0.0" - }, - "peerDependencies": { - "@langchain/core": ">=0.2.0" - }, - "peerDependenciesMeta": { - "@langchain/core": { - "optional": true - } } }, "node_modules/@azure-rest/core-client": { @@ -4571,6 +4564,22 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",