From 43d6defd68f4afa41b152501c3cd325b539aef8c Mon Sep 17 00:00:00 2001 From: Nev <54870357+MSNev@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:43:11 -0800 Subject: [PATCH 1/2] Phase 1: Initial Move to sync Beta and OTel-Sdk folder structure (to keep file history) --- UpdatedMergeCommonOTelProjects.md | 187 +++++- package.json | 6 +- rush.json | 2 +- .../{Common => }/AppInsightsCommon.tests.ts | 0 .../{Core => }/AppInsightsCoreSize.Tests.ts | 0 .../ApplicationInsightsCore.Tests.ts | 0 .../ConnectionStringParser.tests.ts | 0 .../src/ai/{Core => }/CookieManager.Tests.ts | 0 .../src/ai/{Core => }/EventHelper.Tests.ts | 0 .../{Core => }/EventsDiscardedReason.Tests.ts | 0 .../src/ai/{Common => }/Exception.tests.ts | 0 .../ai/{Common => }/GlobalTestHooks.Test.ts | 0 .../src/ai/{Core => }/HelperFunc.Tests.ts | 0 .../src/ai/{Core => }/LoggingEnum.Tests.ts | 0 .../ai/{Common => }/RequestHeaders.tests.ts | 0 .../ai/{Core => }/SendPostManager.Tests.ts | 0 .../ai/{Common => }/SeverityLevel.tests.ts | 0 .../Unit/src/ai/{Core => }/StatsBeat.Tests.ts | 0 .../Unit/src/ai/{Core => }/TestPlugins.ts | 0 .../src/ai/{Common => }/ThrottleMgr.tests.ts | 0 .../src/ai/{Core => }/UpdateConfig.Tests.ts | 0 .../Unit/src/ai/{Common => }/Util.tests.ts | 0 .../Unit/src/ai/{Core => }/errors.Tests.ts | 0 .../src/ai/{Core => }/traceState.Tests.ts | 0 .../src/{ai/Core => config}/Dynamic.Tests.ts | 0 .../Core => config}/DynamicConfig.Tests.ts | 0 ...onUtils.Tests.ts => handleErrors.Tests.ts} | 0 .../W3CTraceStateModes.tests.ts | 0 .../Common => trace}/W3cTraceParentTests.ts | 0 .../W3cTraceState.Tests.ts} | 0 shared/otel-core/src/utils/HelperFuncs.ts | 587 +++++++++++++++++- shared/otel-core/src/utils/HelperFuncsCore.ts | 578 ----------------- .../src/api/noop}/nonRecordingSpan.ts | 0 33 files changed, 718 insertions(+), 642 deletions(-) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/AppInsightsCommon.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/AppInsightsCoreSize.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/ApplicationInsightsCore.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/ConnectionStringParser.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/CookieManager.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/EventHelper.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/EventsDiscardedReason.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/Exception.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/GlobalTestHooks.Test.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/HelperFunc.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/LoggingEnum.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/RequestHeaders.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/SendPostManager.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/SeverityLevel.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/StatsBeat.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/TestPlugins.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/ThrottleMgr.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/UpdateConfig.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Common => }/Util.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/errors.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/ai/{Core => }/traceState.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/{ai/Core => config}/Dynamic.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/{ai/Core => config}/DynamicConfig.Tests.ts (100%) rename shared/otel-core/Tests/Unit/src/internal/{commonUtils.Tests.ts => handleErrors.Tests.ts} (100%) rename shared/otel-core/Tests/Unit/src/{ai/Common => trace}/W3CTraceStateModes.tests.ts (100%) rename shared/otel-core/Tests/Unit/src/{ai/Common => trace}/W3cTraceParentTests.ts (100%) rename shared/otel-core/Tests/Unit/src/{ai/Common/W3TraceState.Tests.ts => trace/W3cTraceState.Tests.ts} (100%) delete mode 100644 shared/otel-core/src/utils/HelperFuncsCore.ts rename shared/{otel-core/src/otel/api/trace => otel-noop/src/api/noop}/nonRecordingSpan.ts (100%) diff --git a/UpdatedMergeCommonOTelProjects.md b/UpdatedMergeCommonOTelProjects.md index 73dee5db9..9684a8472 100644 --- a/UpdatedMergeCommonOTelProjects.md +++ b/UpdatedMergeCommonOTelProjects.md @@ -63,8 +63,8 @@ After merge completion, these packages will be deleted: This document reflects the **actual implementation** of merging three core shared packages (AppInsightsCore, AppInsightsCommon, and OpenTelemetry) into a single unified `otel-core` project. It captures lessons learned, actual steps taken, and provides a template for future similar migrations or branch merges. -**Document Version:** 2.1 -**Last Updated:** January 20, 2026 +**Document Version:** 2.2 +**Last Updated:** January 23, 2026 ## Executive Summary @@ -1531,16 +1531,23 @@ rush test # MUST pass all tests ``` -**✅ Success Criteria for Phase 4:** -- [ ] All AppInsightsCommon files moved to otel-core -- [ ] All AppInsightsCommon tests moved to otel-core -- [ ] All imports updated from `@microsoft/applicationinsights-common` to `@microsoft/otel-core-js` -- [ ] All package.json dependencies updated -- [ ] rush.json no longer references AppInsightsCommon -- [ ] AppInsightsCommon directory removed -- [ ] otel-core/src/index.ts exports all AppInsightsCommon symbols -- [ ] `rush rebuild` succeeds with zero errors -- [ ] `rush test` passes all tests +**✅ Success Criteria for Phase 4:** *(Completed: January 22, 2026)* +- [x] All AppInsightsCommon files moved to otel-core +- [x] All AppInsightsCommon tests moved to otel-core +- [x] All imports updated from `@microsoft/applicationinsights-common` to `@microsoft/otel-core-js` +- [x] All package.json dependencies updated +- [x] rush.json no longer references AppInsightsCommon +- [x] AppInsightsCommon directory removed +- [x] otel-core/src/index.ts exports all AppInsightsCommon symbols +- [x] `rush rebuild` succeeds with zero errors +- [x] `rush test` passes all tests + +**Phase 4 Implementation Summary:** +- Moved all AppInsightsCommon source files to otel-core (telemetry/, utils/, interfaces/AppInsights/, enums/AppInsights/, constants/) +- Moved all AppInsightsCommon tests to Tests/Unit/src/AppInsights/Common/ +- Updated otel-core-js.ts entry point with all AppInsightsCommon exports +- Updated package.json dependencies across repository from @microsoft/applicationinsights-common to @microsoft/otel-core-js +- Removed shared/AppInsightsCommon directory --- @@ -1785,18 +1792,32 @@ grep -r "@microsoft/otel-core-js" --include="*.ts" | wc -l # Should show many matches ``` -**✅ Success Criteria for Phase 5:** -- [ ] All AppInsightsCore files moved to otel-core -- [ ] All AppInsightsCore tests moved to otel-core -- [ ] All imports updated from `@microsoft/applicationinsights-core-js` to `@microsoft/otel-core-js` -- [ ] All package.json dependencies updated -- [ ] rush.json no longer references AppInsightsCore -- [ ] AppInsightsCore directory removed -- [ ] otel-core/src/index.ts exports all AppInsightsCore symbols -- [ ] `shared/` directory contains ONLY `otel-core/` and `otel-noop-js/` -- [ ] `rush rebuild` succeeds with zero errors -- [ ] `rush test` passes all tests -- [ ] **MERGE COMPLETE**: 3 packages → 2 packages successfully merged +**✅ Success Criteria for Phase 5:** *(Completed: January 23, 2026)* +- [x] All AppInsightsCore files moved to otel-core +- [x] All AppInsightsCore tests moved to otel-core +- [x] All imports updated from `@microsoft/applicationinsights-core-js` to `@microsoft/otel-core-js` +- [x] All package.json dependencies updated +- [x] rush.json no longer references AppInsightsCore +- [x] AppInsightsCore directory removed +- [x] otel-core/src/index.ts exports all AppInsightsCore symbols +- [x] `shared/` directory contains ONLY `otel-core/` and `otel-noop/` +- [x] `rush rebuild` succeeds with zero errors (16 operations: 1 SUCCESS, 15 SUCCESS WITH WARNINGS) +- [x] `rush test` - TypeScript compiles successfully +- [x] **MERGE COMPLETE**: 3 packages → 2 packages successfully merged + +**Phase 5 Implementation Summary:** +- Moved all AppInsightsCore source files to otel-core: + - `core/AppInsights/` - Core classes (AppInsightsCore, BaseTelemetryPlugin, CookieMgr, NotificationManager, PerfManager, ProcessTelemetryContext, SenderPostManager, etc.) + - `config/AppInsights/` - Dynamic configuration (DynamicConfig, DynamicProperty, DynamicState, DynamicSupport, ConfigDefaults, ConfigDefaultHelpers) + - `diagnostics/AppInsights/` - DiagnosticLogger, ThrottleMgr +- Moved all AppInsightsCore tests to Tests/Unit/src/AppInsights/Core/ +- Updated otel-core-js.ts entry point with all AppInsightsCore exports +- Fixed rollup bundling issues: Changed 27+ internal source files from importing from barrel file (`../../otel-core-js`) to direct file imports (required by ai-rollup-importcheck plugin) +- Updated package.json dependencies across entire repository from @microsoft/applicationinsights-core-js to @microsoft/otel-core-js +- Updated rush.json - Removed AppInsightsCore project entry +- Updated gruntfile.js - Removed "core" build configuration +- Removed shared/AppInsightsCore and shared/AppInsightsCommon directories +- Full Rush build completed successfully (16 packages built) --- @@ -2037,6 +2058,114 @@ import { createNoopTracerProvider } from "@microsoft/otel-noop-js"; --- +### Phase 4 & 5: AppInsightsCommon and AppInsightsCore Merge Learnings + +#### 4.1 Barrel Import Blocking by Rollup +⚠️ **CRITICAL**: The `ai-rollup-importcheck` plugin blocks imports from barrel/index files within internal source files. This means files INSIDE otel-core cannot import from the main entry point (`../../otel-core-js`). + +```typescript +// ❌ WRONG - Blocked by rollup importcheck plugin +import { IConfiguration, IDiagnosticLogger } from "../../otel-core-js"; + +// ✅ CORRECT - Direct imports from actual source files +import { IConfiguration } from "../../interfaces/AppInsights/IConfiguration"; +import { IDiagnosticLogger } from "../../interfaces/AppInsights/IDiagnosticLogger"; +``` + +**Files that needed fixing (27+ files):** +- `core/AppInsights/AppInsightsCore.ts` +- `core/AppInsights/BaseTelemetryPlugin.ts` +- `core/AppInsights/SenderPostManager.ts` +- `config/AppInsights/DynamicConfig.ts` +- `diagnostics/AppInsights/DiagnosticLogger.ts` +- All `otel/sdk/*.ts` files +- Many others + +**How to identify**: Look for rollup error messages like: +``` +[!] (plugin ai-rollup-importcheck) Error: Invalid Import detected [import {...} from "../../otel-core-js"] +``` + +#### 4.2 Symbol-to-Source-File Mapping +When fixing barrel imports, you need to map each symbol to its actual source file. Common mappings: + +| Symbol Category | Source Location | +|----------------|-----------------| +| Interfaces (I*) | `interfaces/AppInsights/*.ts` or `interfaces/OTel/**/*.ts` | +| Enums (e*) | `enums/AppInsights/*.ts` or `enums/OTel/**/*.ts` | +| Utility functions | `utils/AppInsights/*.ts` | +| Config classes | `config/AppInsights/*.ts` | +| Constants (STR_*) | `constants/InternalConstants.ts` | +| OTel functions | `otel/api/**/*.ts` | + +#### 4.3 Version Consistency in Package Dependencies +When updating package.json files across the repository: + +```powershell +# Find all package.json files referencing old packages +Get-ChildItem -Recurse -Filter "package.json" | Select-String -Pattern "applicationinsights-core-js|applicationinsights-common" + +# Ensure version matches actual otel-core-js version (0.0.1-alpha) +# NOT a guessed version like 3.3.11 +``` + +#### 4.4 Duplicate Dependency Removal +When replacing both `applicationinsights-core-js` AND `applicationinsights-common` with `otel-core-js`, you may create duplicate entries: + +```json +// ❌ WRONG - duplicate entries after replacement +{ + "dependencies": { + "@microsoft/otel-core-js": "0.0.1-alpha", + "@microsoft/otel-core-js": "0.0.1-alpha" + } +} + +// ✅ CORRECT - single entry +{ + "dependencies": { + "@microsoft/otel-core-js": "0.0.1-alpha" + } +} +``` + +**Fix with regex replacement** that removes duplicates. + +#### 4.5 Test File Import Updates +Test files should use relative imports to the source, not package imports (since they're in the same package): + +```typescript +// In Tests/Unit/src/AppInsights/Core/SomeTest.ts +// ✅ CORRECT - relative import to source +import { AppInsightsCore } from "../../../../../src/otel-core-js"; + +// ❌ AVOID in internal tests - package import +import { AppInsightsCore } from "@microsoft/otel-core-js"; +``` + +#### 4.6 Rush Update After Major Changes +After removing packages from rush.json: + +```powershell +# Delete shrinkwrap to avoid integrity errors +Remove-Item "common/temp/npm-shrinkwrap.json" -Force -ErrorAction SilentlyContinue + +# Full clean update +rush update --recheck --purge --full +``` + +#### 4.7 Gruntfile Cleanup +When removing packages, also remove their grunt task registrations: + +```javascript +// REMOVE entries like: +grunt.registerTask("core", tsBuildActions("core")); +grunt.registerTask("coreunittest", tsTestActions("core")); +grunt.registerTask("core-mintest", tsTestActions("core", true)); +``` + +--- + ### Best Practices for Future Merges 1. **Create Verification Checklist**: Track file counts before/after @@ -2261,5 +2390,11 @@ Use this checklist for future migrations: --- -**Last Updated:** January 21, 2026 -**Status:** ✅ Phase 1, 2 & 3 Complete - Ready for Phase 4 +**Last Updated:** January 23, 2026 +**Status:** ✅ **ALL PHASES COMPLETE** - Merge Successfully Finished + +### Final State: +- `@microsoft/otel-core-js` - Unified core package containing all merged code from AppInsightsCore, AppInsightsCommon, and OpenTelemetry +- `@microsoft/otel-noop-js` - Separated noop implementations +- All 16 downstream packages build successfully +- Original packages removed: AppInsightsCore, AppInsightsCommon diff --git a/package.json b/package.json index 4c542288c..f900f4035 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ }, "homepage": "https://github.com/microsoft/ApplicationInsights-JS#readme", "devDependencies": { - "@microsoft/rush": "5.153.1", + "@microsoft/rush": "5.166.0", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", "@typescript-eslint/eslint-plugin": "^5.46.1", @@ -75,12 +75,8 @@ "grunt-contrib-connect": "^3.0.0", "grunt-contrib-copy": "^1.0.0", "grunt-contrib-uglify": "^5.2.1", - "eventemitter2": "6.4.9", "puppeteer": "^24.8.2", "request": "^2.88.2", - "rollup": "^3.20.0", - "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-sourcemaps": "^0.6.3", "typedoc": "^0.26.6", "typescript": "^4.9.3", "whatwg-fetch": "^3.6.2", diff --git a/rush.json b/rush.json index 6f4aca942..5fa4a5ae6 100644 --- a/rush.json +++ b/rush.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", "npmVersion": "9.9.3", - "rushVersion": "5.153.1", + "rushVersion": "5.166.0", "projectFolderMaxDepth": 4, "projects": [ { diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/AppInsightsCommon.tests.ts b/shared/otel-core/Tests/Unit/src/ai/AppInsightsCommon.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/AppInsightsCommon.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/AppInsightsCommon.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/AppInsightsCoreSize.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/AppInsightsCoreSize.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/AppInsightsCoreSize.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/AppInsightsCoreSize.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/ApplicationInsightsCore.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/ApplicationInsightsCore.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/ApplicationInsightsCore.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/ApplicationInsightsCore.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/ConnectionStringParser.tests.ts b/shared/otel-core/Tests/Unit/src/ai/ConnectionStringParser.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/ConnectionStringParser.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/ConnectionStringParser.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/CookieManager.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/CookieManager.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/CookieManager.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/CookieManager.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/EventHelper.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/EventHelper.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/EventHelper.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/EventHelper.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/EventsDiscardedReason.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/EventsDiscardedReason.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/EventsDiscardedReason.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/EventsDiscardedReason.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/Exception.tests.ts b/shared/otel-core/Tests/Unit/src/ai/Exception.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/Exception.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/Exception.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/GlobalTestHooks.Test.ts b/shared/otel-core/Tests/Unit/src/ai/GlobalTestHooks.Test.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/GlobalTestHooks.Test.ts rename to shared/otel-core/Tests/Unit/src/ai/GlobalTestHooks.Test.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/HelperFunc.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/HelperFunc.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/HelperFunc.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/HelperFunc.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/LoggingEnum.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/LoggingEnum.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/LoggingEnum.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/LoggingEnum.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/RequestHeaders.tests.ts b/shared/otel-core/Tests/Unit/src/ai/RequestHeaders.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/RequestHeaders.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/RequestHeaders.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/SendPostManager.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/SendPostManager.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/SendPostManager.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/SendPostManager.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/SeverityLevel.tests.ts b/shared/otel-core/Tests/Unit/src/ai/SeverityLevel.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/SeverityLevel.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/SeverityLevel.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/StatsBeat.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/StatsBeat.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/StatsBeat.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/StatsBeat.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/TestPlugins.ts b/shared/otel-core/Tests/Unit/src/ai/TestPlugins.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/TestPlugins.ts rename to shared/otel-core/Tests/Unit/src/ai/TestPlugins.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/ThrottleMgr.tests.ts b/shared/otel-core/Tests/Unit/src/ai/ThrottleMgr.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/ThrottleMgr.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/ThrottleMgr.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/UpdateConfig.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/UpdateConfig.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/UpdateConfig.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/UpdateConfig.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/Util.tests.ts b/shared/otel-core/Tests/Unit/src/ai/Util.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/Util.tests.ts rename to shared/otel-core/Tests/Unit/src/ai/Util.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/errors.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/errors.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/errors.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/errors.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/traceState.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/traceState.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/traceState.Tests.ts rename to shared/otel-core/Tests/Unit/src/ai/traceState.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/Dynamic.Tests.ts b/shared/otel-core/Tests/Unit/src/config/Dynamic.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/Dynamic.Tests.ts rename to shared/otel-core/Tests/Unit/src/config/Dynamic.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/DynamicConfig.Tests.ts b/shared/otel-core/Tests/Unit/src/config/DynamicConfig.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Core/DynamicConfig.Tests.ts rename to shared/otel-core/Tests/Unit/src/config/DynamicConfig.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/internal/commonUtils.Tests.ts b/shared/otel-core/Tests/Unit/src/internal/handleErrors.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/internal/commonUtils.Tests.ts rename to shared/otel-core/Tests/Unit/src/internal/handleErrors.Tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/W3CTraceStateModes.tests.ts b/shared/otel-core/Tests/Unit/src/trace/W3CTraceStateModes.tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/W3CTraceStateModes.tests.ts rename to shared/otel-core/Tests/Unit/src/trace/W3CTraceStateModes.tests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/W3cTraceParentTests.ts b/shared/otel-core/Tests/Unit/src/trace/W3cTraceParentTests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/W3cTraceParentTests.ts rename to shared/otel-core/Tests/Unit/src/trace/W3cTraceParentTests.ts diff --git a/shared/otel-core/Tests/Unit/src/ai/Common/W3TraceState.Tests.ts b/shared/otel-core/Tests/Unit/src/trace/W3cTraceState.Tests.ts similarity index 100% rename from shared/otel-core/Tests/Unit/src/ai/Common/W3TraceState.Tests.ts rename to shared/otel-core/Tests/Unit/src/trace/W3cTraceState.Tests.ts diff --git a/shared/otel-core/src/utils/HelperFuncs.ts b/shared/otel-core/src/utils/HelperFuncs.ts index e77cafa8c..fa4697dc7 100644 --- a/shared/otel-core/src/utils/HelperFuncs.ts +++ b/shared/otel-core/src/utils/HelperFuncs.ts @@ -1,55 +1,578 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { ObjAssign, ObjClass } from "@microsoft/applicationinsights-shims"; +import { + WellKnownSymbols, arrForEach, asString as asString21, getKnownSymbol, isArray, isBoolean, isError, isFunction, isNullOrUndefined, + isNumber, isObject, isPlainObject, isString, isUndefined, objCreate, objDeepFreeze, objDefine, objForEachKey, objHasOwn, + objSetPrototypeOf, safe, strIndexOf, strTrim +} from "@nevware21/ts-utils"; +import { STR_EMPTY } from "../constants/InternalConstants"; +import { FeatureOptInMode } from "../enums/ai/FeatureOptInEnums"; +import { TransportType } from "../enums/ai/SendRequestReason"; +import { IConfiguration } from "../interfaces/ai/IConfiguration"; +import { IXDomainRequest } from "../interfaces/ai/IXDomainRequest"; -import { arrForEach, isString, mathFloor, mathRound } from "@nevware21/ts-utils"; -import { IPlugin } from "../interfaces/ai/ITelemetryPlugin"; +// RESTRICT and AVOID circular dependencies you should not import other contained modules or export the contents of this file directly -const strEmpty = ""; +// Added to help with minification +const strGetPrototypeOf = "getPrototypeOf"; -export function stringToBoolOrDefault(str: any, defaultValue = false): boolean { - if (str === undefined || str === null) { - return defaultValue; +const rCamelCase = /-([a-z])/g; +const rNormalizeInvalid = /([^\w\d_$])/g; +const rLeadingNumeric = /^(\d+[\w\d_$])/; + +export let _getObjProto = Object[strGetPrototypeOf]; + +export function isNotUndefined(value: T): value is T { + return !isUndefined(value); +} + +export function isNotNullOrUndefined(value: T): value is T { + return !isNullOrUndefined(value); +} + +/** + * Validates that the string name conforms to the JS IdentifierName specification and if not + * normalizes the name so that it would. This method does not identify or change any keywords + * meaning that if you pass in a known keyword the same value will be returned. + * This is a simplified version + * @param name - The name to validate + */ +export function normalizeJsName(name: string): string { + let value = name; + + if (value && isString(value)) { + // CamelCase everything after the "-" and remove the dash + value = value.replace(rCamelCase, function (_all, letter) { + return letter.toUpperCase(); + }); + + value = value.replace(rNormalizeInvalid, "_"); + value = value.replace(rLeadingNumeric, function(_all, match) { + return "_" + match; + }); + } + + return value; +} + +/** + * A simple wrapper (for minification support) to check if the value contains the search string. + * @param value - The string value to check for the existence of the search value + * @param search - The value search within the value + */ +export function strContains(value: string, search: string): boolean { + if (value && search) { + return strIndexOf(value, search) !== -1; + } + + return false; +} + +/** + * Convert a date to I.S.O. format in IE8 + */ +export function toISOString(date: Date) { + return date && date.toISOString() || STR_EMPTY; +} + +export const deepFreeze: (obj: T) => T = objDeepFreeze; + +/** + * Returns the name of object if it's an Error. Otherwise, returns empty string. + */ +export function getExceptionName(object: any): string { + if (isError(object)) { + return object.name; + } + + return STR_EMPTY; +} + +/** + * Sets the provided value on the target instance using the field name when the provided chk function returns true, the chk + * function will only be called if the new value is no equal to the original value. + * @param target - The target object + * @param field - The key of the target + * @param value - The value to set + * @param valChk - [Optional] Callback to check the value that if supplied will be called check if the new value can be set + * @param srcChk - [Optional] Callback to check to original value that if supplied will be called if the new value should be set (if allowed) + * @returns The existing or new value, depending what was set + */ +export function setValue(target: T, field: K, value: T[K], valChk?: ((value: T[K]) => boolean) | null, srcChk?: ((value: T[K]) => boolean) | null) { + let theValue = value; + if (target) { + theValue = target[field]; + if (theValue !== value && (!srcChk || srcChk(theValue)) && (!valChk || valChk(value))) { + theValue = value; + target[field] = theValue; + } + } + + return theValue; +} + +/** + * Returns the current value from the target object if not null or undefined otherwise sets the new value and returns it + * @param target - The target object to return or set the default value + * @param field - The key for the field to set on the target + * @param defValue - [Optional] The value to set if not already present, when not provided a empty object will be added + */ +export function getSetValue(target: T, field: K, defValue?: T[K]): T[K] { + let theValue; + if (target) { + theValue = target[field]; + if (!theValue && isNullOrUndefined(theValue)) { + // Supports having the default as null + theValue = !isUndefined(defValue) ? defValue : {} as any; + target[field] = theValue; + } + } else { + // Expanded for performance so we only check defValue if required + theValue = !isUndefined(defValue) ? defValue : {} as any; + } + + return theValue; +} + +function _createProxyFunction(source: S | (() => S), funcName: (keyof S)) { + let srcFunc: () => S = null; + let src: S = null; + if (isFunction (source)) { + srcFunc = source; + } else { + src = source; } - return str.toString().toLowerCase() === "true"; + return function() { + // Capture the original arguments passed to the method + var originalArguments = arguments; + if (srcFunc) { + src = srcFunc(); + } + + if (src) { + return (src[funcName] as any).apply(src, originalArguments); + } + } } /** - * Convert ms to c# time span format + * Effectively assigns all enumerable properties (not just own properties) and functions (including inherited prototype) from + * the source object to the target, it attempts to use proxy getters / setters (if possible) and proxy functions to avoid potential + * implementation issues by assigning prototype functions as instance ones + * + * This method is the primary method used to "update" the snippet proxy with the ultimate implementations. + * + * Special ES3 Notes: + * Updates (setting) of direct property values on the target or indirectly on the source object WILL NOT WORK PROPERLY, updates to the + * properties of "referenced" object will work (target.context.newValue = 10 =\> will be reflected in the source.context as it's the + * same object). ES3 Failures: assigning target.myProp = 3 -\> Won't change source.myProp = 3, likewise the reverse would also fail. + * @param target - The target object to be assigned with the source properties and functions + * @param source - The source object which will be assigned / called by setting / calling the targets proxies + * @param chkSet - An optional callback to determine whether a specific property/function should be proxied */ -export function msToTimeSpan(totalms: number): string { - if (isNaN(totalms) || totalms < 0) { - totalms = 0; +export function proxyAssign(target: T, source: S, chkSet?: (name: string, isFunc?: boolean, source?: S, target?: T) => boolean) { + if (target && source && isObject(target) && isObject(source)) { + // effectively apply/proxy full source to the target instance + for (const field in source) { + if (isString(field)) { + let value = source[field] as any; + if (isFunction(value)) { + if (!chkSet || chkSet(field, true, source, target)) { + // Create a proxy function rather than just copying the (possible) prototype to the new object as an instance function + target[field as string] = _createProxyFunction(source, field); + } + } else if (!chkSet || chkSet(field, false, source, target)) { + if (objHasOwn(target, field)) { + // Remove any previous instance property + delete (target as any)[field]; + } + + objDefine(target, field, { + g: () => { + return source[field]; + }, + s: (theValue) => { + source[field] = theValue; + } + }); + } + } + } } - totalms = mathRound(totalms); + return target; +} - let ms = strEmpty + totalms % 1000; - let sec = strEmpty + mathFloor(totalms / 1000) % 60; - let min = strEmpty + mathFloor(totalms / (1000 * 60)) % 60; - let hour = strEmpty + mathFloor(totalms / (1000 * 60 * 60)) % 24; - const days = mathFloor(totalms / (1000 * 60 * 60 * 24)); +/** + * Creates a proxy function on the target which internally will call the source version with all arguments passed to the target method. + * + * @param target - The target object to be assigned with the source properties and functions + * @param name - The function name that will be added on the target + * @param source - The source object which will be assigned / called by setting / calling the targets proxies + * @param theFunc - The function name on the source that will be proxied on the target + * @param overwriteTarget - If `false` this will not replace any pre-existing name otherwise (the default) it will overwrite any existing name + */ +export function proxyFunctionAs(target: T, name: string, source: S | (() => S), theFunc: (keyof S), overwriteTarget?: boolean) { + if (target && name && source) { + if (overwriteTarget !== false || isUndefined(target[name])) { + (target as any)[name] = _createProxyFunction(source, theFunc); + } + } +} - ms = ms.length === 1 ? "00" + ms : ms.length === 2 ? "0" + ms : ms; - sec = sec.length < 2 ? "0" + sec : sec; - min = min.length < 2 ? "0" + min : min; - hour = hour.length < 2 ? "0" + hour : hour; +/** + * Creates proxy functions on the target which internally will call the source version with all arguments passed to the target method. + * + * @param target - The target object to be assigned with the source properties and functions + * @param source - The source object which will be assigned / called by setting / calling the targets proxies + * @param functionsToProxy - An array of function names that will be proxied on the target + * @param overwriteTarget - If false this will not replace any pre-existing name otherwise (the default) it will overwrite any existing name + */ +export function proxyFunctions(target: T, source: S | (() => S), functionsToProxy: (keyof S)[], overwriteTarget?: boolean) { + if (target && source && isObject(target) && isArray(functionsToProxy)) { + arrForEach(functionsToProxy, (theFuncName) => { + if (isString(theFuncName)) { + proxyFunctionAs(target, theFuncName, source, theFuncName, overwriteTarget); + } + }); + } - return (days > 0 ? days + "." : strEmpty) + hour + ":" + min + ":" + sec + "." + ms; + return target; } -export function getExtensionByName(extensions: IPlugin[], identifier: string): IPlugin | null { - let extension: IPlugin = null; - arrForEach(extensions, (value) => { - if (value.identifier === identifier) { - extension = value; - return -1; +/** + * Simpler helper to create a dynamic class that implements the interface and populates the values with the defaults. + * Only instance properties (hasOwnProperty) values are copied from the defaults to the new instance + * @param defaults - Simple helper + */ +export function createClassFromInterface(defaults?: T) { + return class { + constructor() { + if (defaults) { + objForEachKey(defaults, (field, value) => { + (this as any)[field] = value; + }); + } } - }); + } as new () => T; +} + +/** + * Set the type of the object by defining the toStringTag well known symbol, this will impact the + * Object.prototype.toString.call() results for the object. And can be used to identify the type + * in the debug output and also in the DevTools watchers window when inspecting the object etc. + * @param target - The object to set the toStringTag symbol on + * @param nameOrFunc - The name or function to use for the toStringTag + */ +export function setObjStringTag(target: any, nameOrFunc: string | (() => string)): any { + safe(objDefine, [target, getKnownSymbol(WellKnownSymbols.toStringTag), isFunction(nameOrFunc) ? { g: nameOrFunc } : { v: nameOrFunc }]); - return extension; + return target; } -export function isCrossOriginError(message: string|Event, url: string, lineNumber: number, columnNumber: number, error: Error | Event): boolean { - return !error && isString(message) && (message === "Script error." || message === "Script error"); +export function setProtoTypeName(target: any, name: string) { + if (target) { + let proto = _getObjProto(target); + let done = false; + if (proto) { + // Set the target's prototype to the new intermediate prototype + safe(() => { + // Create a new intermediate prototype that extends the current prototype + let newProto = setObjStringTag(objCreate(proto), name); + + objSetPrototypeOf(target, newProto); + done = true; + }); + } + + if (!done) { + // Either no prototype or we failed to set it so just define the toStringTag on the target + setObjStringTag(target, name); + } + } + + return target; +} + +/** + * A helper function to assist with JIT performance for objects that have properties added / removed dynamically + * this is primarily for chromium based browsers and has limited effects on Firefox and none of IE. Only call this + * function after you have finished "updating" the object, calling this within loops reduces or defeats the benefits. + * This helps when iterating using for..in, objKeys() and objForEach() + * @param theObject - The object to be optimized if possible + */ +export function optimizeObject(theObject: T): T { + // V8 Optimization to cause the JIT compiler to create a new optimized object for looking up the own properties + // primarily for object with <= 19 properties for >= 20 the effect is reduced or non-existent + if (theObject && ObjAssign) { + theObject = ObjClass(ObjAssign({}, theObject)); + } + + return theObject; +} + +/** + * Pass in the objects to merge as arguments, this will only "merge" (extend) properties that are owned by the object. + * It will NOT merge inherited or non-enumerable properties. + * @param obj1 - object to merge. Set this argument to 'true' for a deep extend. + * @param obj2 - object to merge. + * @param obj3 - object to merge. + * @param obj4 - object to merge. + * @param obj5 - object to merge. + * @returns The extended first object. + */ +export function objExtend(deepExtend?: boolean, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T2 & T3 & T4 & T5 & T6 +export function objExtend(obj1?: T1, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T1 & T2 & T3 & T4 & T5 & T6 +export function objExtend(obj1?: T1 | any, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T1 & T2 & T3 & T4 & T5 & T6 { + // Variables + let theArgs = arguments as any; + let extended: T1 & T2 & T3 & T4 & T5 & T6 = theArgs[0] || {}; + let argLen = theArgs.length; + let deep = false; + let idx = 1; + + // Check for "Deep" flag + if (argLen > 0 && isBoolean(extended)) { + deep = extended; + extended = theArgs[idx] || {}; + idx++; + } + + // Handle case when target is a string or something (possible in deep copy) + if (!isObject(extended)) { + extended = {} as T1 & T2 & T3 & T4 & T5 & T6; + } + + // Loop through each remaining object and conduct a merge + for (; idx < argLen; idx++ ) { + let arg = theArgs[idx]; + let isArgArray = isArray(arg); + let isArgObj = isObject(arg); + for (let prop in arg) { + let propOk = (isArgArray && (prop in arg)) || (isArgObj && objHasOwn(arg, prop)); + if (!propOk) { + continue; + } + + let newValue = arg[prop]; + let isNewArray: boolean; + + // If deep merge and property is an object, merge properties + if (deep && newValue && ((isNewArray = isArray(newValue)) || isPlainObject(newValue))) { + // Grab the current value of the extended object + let clone = extended[prop]; + + if (isNewArray) { + if (!isArray(clone)) { + // We can't "merge" an array with a non-array so overwrite the original + clone = []; + } + } else if (!isPlainObject(clone)) { + // We can't "merge" an object with a non-object + clone = {}; + } + + // Never move the original objects always clone them + newValue = objExtend(deep, clone, newValue); + } + + // Assign the new (or previous) value (unless undefined) + if (newValue !== undefined) { + extended[prop] = newValue; + } + } + } + + return extended; +} + +export const asString = asString21; + +/** + * Checks if the feature is enabled on not. If the feature is not defined, it will return the default state if provided or undefined. + * If the feature is defined, it will check the mode and return true if the mode is enable or false if the mode is disable. + * @param feature - The feature name to check + * @param cfg - The configuration object to check the feature state against + * @param sdkDefaultState - Optional default state to return if the feature is not defined + * @returns True if the feature is enabled, false if the feature is disabled, or undefined if the feature is not defined and no default state is provided. + */ +export function isFeatureEnabled(feature?: string, cfg?: T, sdkDefaultState?: boolean): boolean | undefined { + let ft = cfg && cfg.featureOptIn && cfg.featureOptIn[feature]; + if (feature && ft) { + let mode = ft.mode; + // NOTE: None will be considered as true + if (mode === FeatureOptInMode.enable) { + return true + } else if (mode === FeatureOptInMode.disable) { + return false; + } + } + + // Return the default state if provided or undefined + return sdkDefaultState; +} + +export function getResponseText(xhr: XMLHttpRequest | IXDomainRequest) { + try { + return xhr.responseText; + } catch (e) { + // Best effort, as XHR may throw while XDR wont so just ignore + } + + return null; +} + +export function formatErrorMessageXdr(xdr: IXDomainRequest, message?: string): string { + if (xdr) { + return "XDomainRequest,Response:" + getResponseText(xdr) || STR_EMPTY; + } + + return message; +} + +export function formatErrorMessageXhr(xhr: XMLHttpRequest, message?: string): string { + if (xhr) { + return "XMLHttpRequest,Status:" + xhr.status + ",Response:" + getResponseText(xhr) || xhr.response || STR_EMPTY; + } + + return message; +} + +export function prependTransports(theTransports: TransportType[], newTransports: TransportType | TransportType[]) { + if (newTransports) { + if (isNumber(newTransports)) { + theTransports = [newTransports as TransportType].concat(theTransports); + } else if (isArray(newTransports)) { + theTransports = newTransports.concat(theTransports); + } + } + return theTransports; +} + +const strDisabledPropertyName: string = "Microsoft_ApplicationInsights_BypassAjaxInstrumentation"; +const strWithCredentials: string = "withCredentials"; +const strTimeout: string = "timeout"; + +/** + * Create and open an XMLHttpRequest object + * @param method - The request method + * @param urlString - The url + * @param withCredentials - Option flag indicating that credentials should be sent + * @param disabled - Optional flag indicating that the XHR object should be marked as disabled and not tracked (default is false) + * @param isSync - Optional flag indicating if the instance should be a synchronous request (defaults to false) + * @param timeout - Optional value identifying the timeout value that should be assigned to the XHR request + * @returns A new opened XHR request + */ +export function openXhr(method: string, urlString: string, withCredentials?: boolean, disabled: boolean = false, isSync: boolean = false, timeout?: number) { + + function _wrapSetXhrProp(xhr: XMLHttpRequest, prop: string, value: T) { + try { + xhr[prop] = value; + } catch (e) { + // - Wrapping as depending on the environment setting the property may fail (non-terminally) + } + } + + let xhr = new XMLHttpRequest(); + + if (disabled) { + // Tag the instance so it's not tracked (trackDependency) + // If the environment has locked down the XMLHttpRequest (preventExtensions and/or freeze), this would + // cause the request to fail and we no telemetry would be sent + _wrapSetXhrProp(xhr, strDisabledPropertyName, disabled); + } + + if (withCredentials) { + // Some libraries require that the withCredentials flag is set "before" open and + // - Wrapping as IE 10 has started throwing when setting before open + _wrapSetXhrProp(xhr, strWithCredentials, withCredentials); + } + + xhr.open(method, urlString, !isSync); + + if (withCredentials) { + // withCredentials should be set AFTER open (https://xhr.spec.whatwg.org/#the-withcredentials-attribute) + // And older firefox instances from 11+ will throw for sync events (current versions don't) which happens during unload processing + _wrapSetXhrProp(xhr, strWithCredentials, withCredentials); + } + + // Only set the timeout for asynchronous requests as + // "Timeout shouldn't be used for synchronous XMLHttpRequests requests used in a document environment or it will throw an InvalidAccessError exception."" + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout + if (!isSync && timeout) { + _wrapSetXhrProp(xhr, strTimeout, timeout); + } + + return xhr; +} + +/** +* Converts the XHR getAllResponseHeaders to a map containing the header key and value. +* @internal +*/ +// tslint:disable-next-line: align +export function convertAllHeadersToMap(headersString: string): { [headerName: string]: string } { + let headers = {}; + if (isString(headersString)) { + let headersArray = strTrim(headersString).split(/[\r\n]+/); + arrForEach(headersArray, (headerEntry) => { + if (headerEntry) { + let idx = headerEntry.indexOf(": "); + if (idx !== -1) { + // The new spec has the headers returning all as lowercase -- but not all browsers do this yet + let header = strTrim(headerEntry.substring(0, idx)).toLowerCase(); + let value = strTrim(headerEntry.substring(idx + 1)); + headers[header] = value; + } else { + headers[strTrim(headerEntry)] = 1; + } + } + }); + } + + return headers; +} + +/** +* append the XHR headers. +* @internal +*/ +export function _appendHeader(theHeaders: any, xhr: XMLHttpRequest, name: string) { + if (!theHeaders[name] && xhr && xhr.getResponseHeader) { + let value = xhr.getResponseHeader(name); + if (value) { + theHeaders[name] = strTrim(value); + } + } + + return theHeaders; +} + +const STR_KILL_DURATION_HEADER = "kill-duration"; +const STR_KILL_DURATION_SECONDS_HEADER = "kill-duration-seconds"; +const STR_TIME_DELTA_HEADER = "time-delta-millis"; +/** +* get the XHR getAllResponseHeaders. +* @internal +*/ +export function _getAllResponseHeaders(xhr: XMLHttpRequest, isOneDs?: boolean) { + let theHeaders = {}; + + if (!xhr.getAllResponseHeaders) { + // Firefox 2-63 doesn't have getAllResponseHeaders function but it does have getResponseHeader + // Only call these if getAllResponseHeaders doesn't exist, otherwise we can get invalid response errors + // as collector is not currently returning the correct header to allow JS to access these headers + if (!!isOneDs) { + theHeaders = _appendHeader(theHeaders, xhr, STR_TIME_DELTA_HEADER); + theHeaders = _appendHeader(theHeaders, xhr, STR_KILL_DURATION_HEADER); + theHeaders = _appendHeader(theHeaders, xhr, STR_KILL_DURATION_SECONDS_HEADER); + } + + } else { + theHeaders = convertAllHeadersToMap(xhr.getAllResponseHeaders()); + } + + return theHeaders; } diff --git a/shared/otel-core/src/utils/HelperFuncsCore.ts b/shared/otel-core/src/utils/HelperFuncsCore.ts deleted file mode 100644 index fa4697dc7..000000000 --- a/shared/otel-core/src/utils/HelperFuncsCore.ts +++ /dev/null @@ -1,578 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { ObjAssign, ObjClass } from "@microsoft/applicationinsights-shims"; -import { - WellKnownSymbols, arrForEach, asString as asString21, getKnownSymbol, isArray, isBoolean, isError, isFunction, isNullOrUndefined, - isNumber, isObject, isPlainObject, isString, isUndefined, objCreate, objDeepFreeze, objDefine, objForEachKey, objHasOwn, - objSetPrototypeOf, safe, strIndexOf, strTrim -} from "@nevware21/ts-utils"; -import { STR_EMPTY } from "../constants/InternalConstants"; -import { FeatureOptInMode } from "../enums/ai/FeatureOptInEnums"; -import { TransportType } from "../enums/ai/SendRequestReason"; -import { IConfiguration } from "../interfaces/ai/IConfiguration"; -import { IXDomainRequest } from "../interfaces/ai/IXDomainRequest"; - -// RESTRICT and AVOID circular dependencies you should not import other contained modules or export the contents of this file directly - -// Added to help with minification -const strGetPrototypeOf = "getPrototypeOf"; - -const rCamelCase = /-([a-z])/g; -const rNormalizeInvalid = /([^\w\d_$])/g; -const rLeadingNumeric = /^(\d+[\w\d_$])/; - -export let _getObjProto = Object[strGetPrototypeOf]; - -export function isNotUndefined(value: T): value is T { - return !isUndefined(value); -} - -export function isNotNullOrUndefined(value: T): value is T { - return !isNullOrUndefined(value); -} - -/** - * Validates that the string name conforms to the JS IdentifierName specification and if not - * normalizes the name so that it would. This method does not identify or change any keywords - * meaning that if you pass in a known keyword the same value will be returned. - * This is a simplified version - * @param name - The name to validate - */ -export function normalizeJsName(name: string): string { - let value = name; - - if (value && isString(value)) { - // CamelCase everything after the "-" and remove the dash - value = value.replace(rCamelCase, function (_all, letter) { - return letter.toUpperCase(); - }); - - value = value.replace(rNormalizeInvalid, "_"); - value = value.replace(rLeadingNumeric, function(_all, match) { - return "_" + match; - }); - } - - return value; -} - -/** - * A simple wrapper (for minification support) to check if the value contains the search string. - * @param value - The string value to check for the existence of the search value - * @param search - The value search within the value - */ -export function strContains(value: string, search: string): boolean { - if (value && search) { - return strIndexOf(value, search) !== -1; - } - - return false; -} - -/** - * Convert a date to I.S.O. format in IE8 - */ -export function toISOString(date: Date) { - return date && date.toISOString() || STR_EMPTY; -} - -export const deepFreeze: (obj: T) => T = objDeepFreeze; - -/** - * Returns the name of object if it's an Error. Otherwise, returns empty string. - */ -export function getExceptionName(object: any): string { - if (isError(object)) { - return object.name; - } - - return STR_EMPTY; -} - -/** - * Sets the provided value on the target instance using the field name when the provided chk function returns true, the chk - * function will only be called if the new value is no equal to the original value. - * @param target - The target object - * @param field - The key of the target - * @param value - The value to set - * @param valChk - [Optional] Callback to check the value that if supplied will be called check if the new value can be set - * @param srcChk - [Optional] Callback to check to original value that if supplied will be called if the new value should be set (if allowed) - * @returns The existing or new value, depending what was set - */ -export function setValue(target: T, field: K, value: T[K], valChk?: ((value: T[K]) => boolean) | null, srcChk?: ((value: T[K]) => boolean) | null) { - let theValue = value; - if (target) { - theValue = target[field]; - if (theValue !== value && (!srcChk || srcChk(theValue)) && (!valChk || valChk(value))) { - theValue = value; - target[field] = theValue; - } - } - - return theValue; -} - -/** - * Returns the current value from the target object if not null or undefined otherwise sets the new value and returns it - * @param target - The target object to return or set the default value - * @param field - The key for the field to set on the target - * @param defValue - [Optional] The value to set if not already present, when not provided a empty object will be added - */ -export function getSetValue(target: T, field: K, defValue?: T[K]): T[K] { - let theValue; - if (target) { - theValue = target[field]; - if (!theValue && isNullOrUndefined(theValue)) { - // Supports having the default as null - theValue = !isUndefined(defValue) ? defValue : {} as any; - target[field] = theValue; - } - } else { - // Expanded for performance so we only check defValue if required - theValue = !isUndefined(defValue) ? defValue : {} as any; - } - - return theValue; -} - -function _createProxyFunction(source: S | (() => S), funcName: (keyof S)) { - let srcFunc: () => S = null; - let src: S = null; - if (isFunction (source)) { - srcFunc = source; - } else { - src = source; - } - - return function() { - // Capture the original arguments passed to the method - var originalArguments = arguments; - if (srcFunc) { - src = srcFunc(); - } - - if (src) { - return (src[funcName] as any).apply(src, originalArguments); - } - } -} - -/** - * Effectively assigns all enumerable properties (not just own properties) and functions (including inherited prototype) from - * the source object to the target, it attempts to use proxy getters / setters (if possible) and proxy functions to avoid potential - * implementation issues by assigning prototype functions as instance ones - * - * This method is the primary method used to "update" the snippet proxy with the ultimate implementations. - * - * Special ES3 Notes: - * Updates (setting) of direct property values on the target or indirectly on the source object WILL NOT WORK PROPERLY, updates to the - * properties of "referenced" object will work (target.context.newValue = 10 =\> will be reflected in the source.context as it's the - * same object). ES3 Failures: assigning target.myProp = 3 -\> Won't change source.myProp = 3, likewise the reverse would also fail. - * @param target - The target object to be assigned with the source properties and functions - * @param source - The source object which will be assigned / called by setting / calling the targets proxies - * @param chkSet - An optional callback to determine whether a specific property/function should be proxied - */ -export function proxyAssign(target: T, source: S, chkSet?: (name: string, isFunc?: boolean, source?: S, target?: T) => boolean) { - if (target && source && isObject(target) && isObject(source)) { - // effectively apply/proxy full source to the target instance - for (const field in source) { - if (isString(field)) { - let value = source[field] as any; - if (isFunction(value)) { - if (!chkSet || chkSet(field, true, source, target)) { - // Create a proxy function rather than just copying the (possible) prototype to the new object as an instance function - target[field as string] = _createProxyFunction(source, field); - } - } else if (!chkSet || chkSet(field, false, source, target)) { - if (objHasOwn(target, field)) { - // Remove any previous instance property - delete (target as any)[field]; - } - - objDefine(target, field, { - g: () => { - return source[field]; - }, - s: (theValue) => { - source[field] = theValue; - } - }); - } - } - } - } - - return target; -} - -/** - * Creates a proxy function on the target which internally will call the source version with all arguments passed to the target method. - * - * @param target - The target object to be assigned with the source properties and functions - * @param name - The function name that will be added on the target - * @param source - The source object which will be assigned / called by setting / calling the targets proxies - * @param theFunc - The function name on the source that will be proxied on the target - * @param overwriteTarget - If `false` this will not replace any pre-existing name otherwise (the default) it will overwrite any existing name - */ -export function proxyFunctionAs(target: T, name: string, source: S | (() => S), theFunc: (keyof S), overwriteTarget?: boolean) { - if (target && name && source) { - if (overwriteTarget !== false || isUndefined(target[name])) { - (target as any)[name] = _createProxyFunction(source, theFunc); - } - } -} - -/** - * Creates proxy functions on the target which internally will call the source version with all arguments passed to the target method. - * - * @param target - The target object to be assigned with the source properties and functions - * @param source - The source object which will be assigned / called by setting / calling the targets proxies - * @param functionsToProxy - An array of function names that will be proxied on the target - * @param overwriteTarget - If false this will not replace any pre-existing name otherwise (the default) it will overwrite any existing name - */ -export function proxyFunctions(target: T, source: S | (() => S), functionsToProxy: (keyof S)[], overwriteTarget?: boolean) { - if (target && source && isObject(target) && isArray(functionsToProxy)) { - arrForEach(functionsToProxy, (theFuncName) => { - if (isString(theFuncName)) { - proxyFunctionAs(target, theFuncName, source, theFuncName, overwriteTarget); - } - }); - } - - return target; -} - -/** - * Simpler helper to create a dynamic class that implements the interface and populates the values with the defaults. - * Only instance properties (hasOwnProperty) values are copied from the defaults to the new instance - * @param defaults - Simple helper - */ -export function createClassFromInterface(defaults?: T) { - return class { - constructor() { - if (defaults) { - objForEachKey(defaults, (field, value) => { - (this as any)[field] = value; - }); - } - } - } as new () => T; -} - -/** - * Set the type of the object by defining the toStringTag well known symbol, this will impact the - * Object.prototype.toString.call() results for the object. And can be used to identify the type - * in the debug output and also in the DevTools watchers window when inspecting the object etc. - * @param target - The object to set the toStringTag symbol on - * @param nameOrFunc - The name or function to use for the toStringTag - */ -export function setObjStringTag(target: any, nameOrFunc: string | (() => string)): any { - safe(objDefine, [target, getKnownSymbol(WellKnownSymbols.toStringTag), isFunction(nameOrFunc) ? { g: nameOrFunc } : { v: nameOrFunc }]); - - return target; -} - -export function setProtoTypeName(target: any, name: string) { - if (target) { - let proto = _getObjProto(target); - let done = false; - if (proto) { - // Set the target's prototype to the new intermediate prototype - safe(() => { - // Create a new intermediate prototype that extends the current prototype - let newProto = setObjStringTag(objCreate(proto), name); - - objSetPrototypeOf(target, newProto); - done = true; - }); - } - - if (!done) { - // Either no prototype or we failed to set it so just define the toStringTag on the target - setObjStringTag(target, name); - } - } - - return target; -} - -/** - * A helper function to assist with JIT performance for objects that have properties added / removed dynamically - * this is primarily for chromium based browsers and has limited effects on Firefox and none of IE. Only call this - * function after you have finished "updating" the object, calling this within loops reduces or defeats the benefits. - * This helps when iterating using for..in, objKeys() and objForEach() - * @param theObject - The object to be optimized if possible - */ -export function optimizeObject(theObject: T): T { - // V8 Optimization to cause the JIT compiler to create a new optimized object for looking up the own properties - // primarily for object with <= 19 properties for >= 20 the effect is reduced or non-existent - if (theObject && ObjAssign) { - theObject = ObjClass(ObjAssign({}, theObject)); - } - - return theObject; -} - -/** - * Pass in the objects to merge as arguments, this will only "merge" (extend) properties that are owned by the object. - * It will NOT merge inherited or non-enumerable properties. - * @param obj1 - object to merge. Set this argument to 'true' for a deep extend. - * @param obj2 - object to merge. - * @param obj3 - object to merge. - * @param obj4 - object to merge. - * @param obj5 - object to merge. - * @returns The extended first object. - */ -export function objExtend(deepExtend?: boolean, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T2 & T3 & T4 & T5 & T6 -export function objExtend(obj1?: T1, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T1 & T2 & T3 & T4 & T5 & T6 -export function objExtend(obj1?: T1 | any, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T1 & T2 & T3 & T4 & T5 & T6 { - // Variables - let theArgs = arguments as any; - let extended: T1 & T2 & T3 & T4 & T5 & T6 = theArgs[0] || {}; - let argLen = theArgs.length; - let deep = false; - let idx = 1; - - // Check for "Deep" flag - if (argLen > 0 && isBoolean(extended)) { - deep = extended; - extended = theArgs[idx] || {}; - idx++; - } - - // Handle case when target is a string or something (possible in deep copy) - if (!isObject(extended)) { - extended = {} as T1 & T2 & T3 & T4 & T5 & T6; - } - - // Loop through each remaining object and conduct a merge - for (; idx < argLen; idx++ ) { - let arg = theArgs[idx]; - let isArgArray = isArray(arg); - let isArgObj = isObject(arg); - for (let prop in arg) { - let propOk = (isArgArray && (prop in arg)) || (isArgObj && objHasOwn(arg, prop)); - if (!propOk) { - continue; - } - - let newValue = arg[prop]; - let isNewArray: boolean; - - // If deep merge and property is an object, merge properties - if (deep && newValue && ((isNewArray = isArray(newValue)) || isPlainObject(newValue))) { - // Grab the current value of the extended object - let clone = extended[prop]; - - if (isNewArray) { - if (!isArray(clone)) { - // We can't "merge" an array with a non-array so overwrite the original - clone = []; - } - } else if (!isPlainObject(clone)) { - // We can't "merge" an object with a non-object - clone = {}; - } - - // Never move the original objects always clone them - newValue = objExtend(deep, clone, newValue); - } - - // Assign the new (or previous) value (unless undefined) - if (newValue !== undefined) { - extended[prop] = newValue; - } - } - } - - return extended; -} - -export const asString = asString21; - -/** - * Checks if the feature is enabled on not. If the feature is not defined, it will return the default state if provided or undefined. - * If the feature is defined, it will check the mode and return true if the mode is enable or false if the mode is disable. - * @param feature - The feature name to check - * @param cfg - The configuration object to check the feature state against - * @param sdkDefaultState - Optional default state to return if the feature is not defined - * @returns True if the feature is enabled, false if the feature is disabled, or undefined if the feature is not defined and no default state is provided. - */ -export function isFeatureEnabled(feature?: string, cfg?: T, sdkDefaultState?: boolean): boolean | undefined { - let ft = cfg && cfg.featureOptIn && cfg.featureOptIn[feature]; - if (feature && ft) { - let mode = ft.mode; - // NOTE: None will be considered as true - if (mode === FeatureOptInMode.enable) { - return true - } else if (mode === FeatureOptInMode.disable) { - return false; - } - } - - // Return the default state if provided or undefined - return sdkDefaultState; -} - -export function getResponseText(xhr: XMLHttpRequest | IXDomainRequest) { - try { - return xhr.responseText; - } catch (e) { - // Best effort, as XHR may throw while XDR wont so just ignore - } - - return null; -} - -export function formatErrorMessageXdr(xdr: IXDomainRequest, message?: string): string { - if (xdr) { - return "XDomainRequest,Response:" + getResponseText(xdr) || STR_EMPTY; - } - - return message; -} - -export function formatErrorMessageXhr(xhr: XMLHttpRequest, message?: string): string { - if (xhr) { - return "XMLHttpRequest,Status:" + xhr.status + ",Response:" + getResponseText(xhr) || xhr.response || STR_EMPTY; - } - - return message; -} - -export function prependTransports(theTransports: TransportType[], newTransports: TransportType | TransportType[]) { - if (newTransports) { - if (isNumber(newTransports)) { - theTransports = [newTransports as TransportType].concat(theTransports); - } else if (isArray(newTransports)) { - theTransports = newTransports.concat(theTransports); - } - } - return theTransports; -} - -const strDisabledPropertyName: string = "Microsoft_ApplicationInsights_BypassAjaxInstrumentation"; -const strWithCredentials: string = "withCredentials"; -const strTimeout: string = "timeout"; - -/** - * Create and open an XMLHttpRequest object - * @param method - The request method - * @param urlString - The url - * @param withCredentials - Option flag indicating that credentials should be sent - * @param disabled - Optional flag indicating that the XHR object should be marked as disabled and not tracked (default is false) - * @param isSync - Optional flag indicating if the instance should be a synchronous request (defaults to false) - * @param timeout - Optional value identifying the timeout value that should be assigned to the XHR request - * @returns A new opened XHR request - */ -export function openXhr(method: string, urlString: string, withCredentials?: boolean, disabled: boolean = false, isSync: boolean = false, timeout?: number) { - - function _wrapSetXhrProp(xhr: XMLHttpRequest, prop: string, value: T) { - try { - xhr[prop] = value; - } catch (e) { - // - Wrapping as depending on the environment setting the property may fail (non-terminally) - } - } - - let xhr = new XMLHttpRequest(); - - if (disabled) { - // Tag the instance so it's not tracked (trackDependency) - // If the environment has locked down the XMLHttpRequest (preventExtensions and/or freeze), this would - // cause the request to fail and we no telemetry would be sent - _wrapSetXhrProp(xhr, strDisabledPropertyName, disabled); - } - - if (withCredentials) { - // Some libraries require that the withCredentials flag is set "before" open and - // - Wrapping as IE 10 has started throwing when setting before open - _wrapSetXhrProp(xhr, strWithCredentials, withCredentials); - } - - xhr.open(method, urlString, !isSync); - - if (withCredentials) { - // withCredentials should be set AFTER open (https://xhr.spec.whatwg.org/#the-withcredentials-attribute) - // And older firefox instances from 11+ will throw for sync events (current versions don't) which happens during unload processing - _wrapSetXhrProp(xhr, strWithCredentials, withCredentials); - } - - // Only set the timeout for asynchronous requests as - // "Timeout shouldn't be used for synchronous XMLHttpRequests requests used in a document environment or it will throw an InvalidAccessError exception."" - // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout - if (!isSync && timeout) { - _wrapSetXhrProp(xhr, strTimeout, timeout); - } - - return xhr; -} - -/** -* Converts the XHR getAllResponseHeaders to a map containing the header key and value. -* @internal -*/ -// tslint:disable-next-line: align -export function convertAllHeadersToMap(headersString: string): { [headerName: string]: string } { - let headers = {}; - if (isString(headersString)) { - let headersArray = strTrim(headersString).split(/[\r\n]+/); - arrForEach(headersArray, (headerEntry) => { - if (headerEntry) { - let idx = headerEntry.indexOf(": "); - if (idx !== -1) { - // The new spec has the headers returning all as lowercase -- but not all browsers do this yet - let header = strTrim(headerEntry.substring(0, idx)).toLowerCase(); - let value = strTrim(headerEntry.substring(idx + 1)); - headers[header] = value; - } else { - headers[strTrim(headerEntry)] = 1; - } - } - }); - } - - return headers; -} - -/** -* append the XHR headers. -* @internal -*/ -export function _appendHeader(theHeaders: any, xhr: XMLHttpRequest, name: string) { - if (!theHeaders[name] && xhr && xhr.getResponseHeader) { - let value = xhr.getResponseHeader(name); - if (value) { - theHeaders[name] = strTrim(value); - } - } - - return theHeaders; -} - -const STR_KILL_DURATION_HEADER = "kill-duration"; -const STR_KILL_DURATION_SECONDS_HEADER = "kill-duration-seconds"; -const STR_TIME_DELTA_HEADER = "time-delta-millis"; -/** -* get the XHR getAllResponseHeaders. -* @internal -*/ -export function _getAllResponseHeaders(xhr: XMLHttpRequest, isOneDs?: boolean) { - let theHeaders = {}; - - if (!xhr.getAllResponseHeaders) { - // Firefox 2-63 doesn't have getAllResponseHeaders function but it does have getResponseHeader - // Only call these if getAllResponseHeaders doesn't exist, otherwise we can get invalid response errors - // as collector is not currently returning the correct header to allow JS to access these headers - if (!!isOneDs) { - theHeaders = _appendHeader(theHeaders, xhr, STR_TIME_DELTA_HEADER); - theHeaders = _appendHeader(theHeaders, xhr, STR_KILL_DURATION_HEADER); - theHeaders = _appendHeader(theHeaders, xhr, STR_KILL_DURATION_SECONDS_HEADER); - } - - } else { - theHeaders = convertAllHeadersToMap(xhr.getAllResponseHeaders()); - } - - return theHeaders; -} diff --git a/shared/otel-core/src/otel/api/trace/nonRecordingSpan.ts b/shared/otel-noop/src/api/noop/nonRecordingSpan.ts similarity index 100% rename from shared/otel-core/src/otel/api/trace/nonRecordingSpan.ts rename to shared/otel-noop/src/api/noop/nonRecordingSpan.ts From fc2c07e64b73c1eb1f72ebcc10db1f3c5949e098 Mon Sep 17 00:00:00 2001 From: Nev <54870357+MSNev@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:34:32 -0800 Subject: [PATCH 2/2] [OTel-Sdk] Refactor OTel core tracing infrastructure with tree-shaking and span context improvements Major refactoring of the OpenTelemetry core module to synchronise the changes from [Beta] branch this includes improved tree-shaking support, simplify API interfaces, and enhance span lifecycle management. Tracing API changes: - Refactored createOTelApi to use simplified configuration through host context - Added new createTraceProvider and _createTracerProvider for proper tracer caching - Introduced withSpan, useSpan, and startActiveSpan helpers for automatic span lifecycle management - Added onException callback support to span context - Added suppressTracing configuration support for disabling trace recording - Updated createContext to require IOTelApi parameter for context binding Span improvements: - Added traceContext, attribContainer, and events properties to readable spans - Fixed span recording state to respect suppressTracing configuration - Added prototype naming via setProtoTypeName for better debugging visibility - Changed from createDeferredCachedValue to getDeferred for lazy evaluation Tree-shaking optimizations: - Added /*#__NO_SIDE_EFFECTS__*/ annotations throughout utility functions - Converted telemetry class static properties to use external constants (DataTypes.ts, EnvelopeTypes.ts) - Marked deprecated instance properties with @deprecated notices - Added caching to EnvUtils functions to avoid repeated evaluations - Removed unused classes: Data.ts, RemoteDependencyData.ts New semantic conventions: - Added SemanticConventions.ts with OTel attribute constants for Microsoft/Application Insights Consolidated utilities: - Merged HelperFuncsCore.ts into HelperFuncs.ts - Added msToTimeSpan, isTimeSpan, getExtensionByName, isCrossOriginError helpers - Moved createDistributedTraceContextFromTrace to Util.ts with deprecation notice otel-noop package updates: - Reorganized noop logger/processor implementations - Added promise-returning noop helpers - Updated test files and converted to ES5 target - Updated dependencies: @nevware21/ts-utils >=0.12.6, @nevware21/ts-async >=0.5.5 Build configuration: - Re-enabled es5Check in shims rollup config - Removed obsolete package entries from version.json --- .aiAutoMinify.json | 46 +- AISKU/Tests/Unit/src/AISKUSize.Tests.ts | 4 +- AISKU/Tests/Unit/src/CdnThrottle.tests.ts | 25 +- .../Tests/Unit/src/IAnalyticsConfig.Tests.ts | 6 +- .../Unit/src/ThrottleSentMessage.tests.ts | 19 +- AISKU/Tests/Unit/src/aiskuunittests.ts | 18 +- .../applicationinsights.e2e.fetch.tests.ts | 6 +- .../Unit/src/applicationinsights.e2e.tests.ts | 27 +- AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts | 13 +- AISKU/Tests/Unit/src/sender.e2e.tests.ts | 15 +- AISKU/Tests/Unit/src/validate.e2e.tests.ts | 13 +- AISKU/package.json | 6 +- AISKU/src/index.ts | 1 - .../Tests/Unit/src/AISKULightSize.Tests.ts | 4 +- AISKULight/Tests/Unit/src/config.tests.ts | 8 +- .../Tests/Unit/src/dynamicconfig.tests.ts | 5 +- AISKULight/build.cmd | 4 +- AISKULight/package.json | 4 +- UpdatedMergeCommonOTelProjects.md | 2400 ----------------- .../Tests/Unit/src/Sample.tests.ts | 23 +- .../Tests/Unit/src/Sender.tests.ts | 53 +- .../Tests/Unit/src/StatsBeat.tests.ts | 4 +- .../package.json | 4 +- .../src/EnvelopeCreator.ts | 84 +- .../src/Interfaces/Contracts/IRequestData.ts | 60 + .../src/Sender.ts | 58 +- .../src/Serializer.ts | 288 +- .../src/Telemetry/Common/Data.ts | 15 + .../src/Telemetry/RemoteDependencyData.ts | 91 + .../src/Telemetry/RequestData.ts | 40 + .../src/TelemetryProcessors/Sample.ts | 66 +- .../HashCodeScoreGenerator.ts | 24 +- .../SamplingScoreGenerator.ts | 27 +- channels/tee-channel-js/package.json | 4 +- common/Tests/Framework/package.json | 4 +- common/config/rush/npm-shrinkwrap.json | 302 ++- common/scripts/install-run-rush.js | 48 +- common/scripts/install-run.js | 511 ++-- examples/AISKU/package.json | 2 +- examples/cfgSync/package.json | 2 +- examples/dependency/package.json | 2 +- examples/shared-worker/package.json | 2 +- .../Unit/src/AnalyticsExtensionSize.tests.ts | 4 +- .../Tests/Unit/src/AnalyticsPlugin.tests.ts | 27 +- .../Unit/src/TelemetryItemCreator.tests.ts | 13 +- .../Tests/Unit/src/index.tests.ts | 4 +- .../package.json | 2 +- .../src/JavaScriptSDK/AnalyticsPlugin.ts | 95 +- .../Interfaces/IAnalyticsConfig.ts | 32 + .../Tests/Unit/src/cfgsynchelper.tests.ts | 19 +- .../package.json | 4 +- .../Unit/src/W3CTraceStateDependency.tests.ts | 30 +- .../Tests/Unit/src/ajax.tests.ts | 182 +- .../package.json | 4 +- .../src/ajax.ts | 16 +- .../applicationinsights-dependencies-js.ts | 2 +- .../Tests/Unit/src/OsPluginTest.ts | 4 +- .../Tests/Unit/src/index.tests.ts | 2 +- .../package.json | 4 +- .../Tests/Unit/src/MarkMeasureTests.ts | 4 +- .../Tests/Unit/src/index.tests.ts | 4 +- .../package.json | 2 +- .../Tests/Unit/src/SessionManager.Tests.ts | 2 +- .../Tests/Unit/src/index.tests.ts | 2 +- .../Tests/Unit/src/properties.tests.ts | 2 +- .../Tests/Unit/src/propertiesSize.tests.ts | 4 +- .../package.json | 2 +- gruntfile.js | 25 +- package.json | 2 +- rollup.base.config.js | 12 +- rush.json | 17 +- .../AppInsights/Common/ThrottleMgr.tests.ts | 1676 ------------ .../Unit/src/AppInsights/Common/Util.tests.ts | 572 ---- .../Unit/src/ai/AppInsightsCommon.tests.ts | 8 +- .../src/ai/ApplicationInsightsCore.Tests.ts | 31 +- .../src/ai/ConnectionStringParser.tests.ts | 4 +- .../Tests/Unit/src/ai/CookieManager.Tests.ts | 14 +- .../Unit/src/ai/Core/GlobalTestHooks.Test.ts | 13 - .../Unit/src/ai/Core/ThrottleMgr.tests.ts | 1675 ------------ .../Tests/Unit/src/ai/EventHelper.Tests.ts | 11 +- .../src/ai/EventsDiscardedReason.Tests.ts | 2 +- .../Tests/Unit/src/ai/Exception.tests.ts | 69 +- .../Tests/Unit/src/ai/GlobalTestHooks.Test.ts | 2 +- .../Tests/Unit/src/ai/HelperFunc.Tests.ts | 11 +- .../Tests/Unit/src/ai/LoggingEnum.Tests.ts | 2 +- .../Tests/Unit/src/ai/RequestHeaders.tests.ts | 2 +- .../Unit/src/ai/SendPostManager.Tests.ts | 6 +- .../Tests/Unit/src/ai/SeverityLevel.tests.ts | 2 +- .../Tests/Unit/src/ai/StatsBeat.Tests.ts | 22 +- .../Tests/Unit/src/ai/TestPlugins.ts | 22 +- .../Tests/Unit/src/ai/ThrottleMgr.tests.ts | 14 +- .../Tests/Unit/src/ai/UpdateConfig.Tests.ts | 18 +- .../otel-core/Tests/Unit/src/ai/Util.tests.ts | 20 +- .../Tests/Unit/src/ai/errors.Tests.ts | 5 +- .../Tests/Unit/src/ai/traceState.Tests.ts | 2 +- .../src/attribute/AttributeContainer.Tests.ts | 2 +- .../Tests/Unit/src/config/Dynamic.Tests.ts | 20 +- .../Unit/src/config/DynamicConfig.Tests.ts | 117 +- .../otel-core/Tests/Unit/src/index.tests.ts | 82 +- .../Unit/src/internal/handleErrors.Tests.ts | 4 +- .../OTelCoreSize.Tests.ts} | 26 +- .../Tests/Unit/src/sdk/OTelLogRecord.Tests.ts | 338 +-- .../Tests/Unit/src/sdk/OTelLogger.Tests.ts | 14 +- .../Tests/Unit/src/sdk/commonUtils.Tests.ts | 1031 +++++++ .../src/trace/W3CTraceStateModes.tests.ts | 2 +- .../Unit/src/trace/W3cTraceParentTests.ts | 6 +- .../Unit/src/trace/W3cTraceState.Tests.ts | 2 +- .../Tests/Unit/src/trace/span.Tests.ts | 1911 ++++++++++++- .../Tests/Unit/src/trace/traceState.Tests.ts | 448 +++ .../Tests/Unit/src/trace/traceUtils.Tests.ts | 1355 ++++++++++ shared/otel-core/package.json | 8 +- shared/otel-core/rollup.config.js | 41 +- shared/otel-core/src/config/DynamicSupport.ts | 3 +- .../otel-core/src/config/IDynamicWatcher.ts | 75 - shared/otel-core/src/constants/Constants.ts | 21 +- .../src/constants/CoreInternalConstants.ts | 34 - .../src/constants/InternalConstants.ts | 2 +- shared/otel-core/src/core/AppInsightsCore.ts | 314 ++- .../otel-core/src/core/BaseTelemetryPlugin.ts | 2 +- shared/otel-core/src/core/CookieMgr.ts | 10 +- shared/otel-core/src/core/InstrumentHooks.ts | 2 +- shared/otel-core/src/core/PerfManager.ts | 37 +- .../src/core/ProcessTelemetryContext.ts | 97 +- .../otel-core/src/core/SenderPostManager.ts | 6 +- shared/otel-core/src/core/StatsBeat.ts | 2 +- shared/otel-core/src/core/TelemetryHelpers.ts | 65 +- .../src/core/TelemetryInitializerPlugin.ts | 2 +- .../src/core/UnloadHandlerContainer.ts | 1 + .../src/diagnostics/DiagnosticLogger.ts | 11 +- .../otel-core/src/diagnostics/ThrottleMgr.ts | 11 +- .../otel-core/src/enums/ai/DependencyTypes.ts | 50 + shared/otel-core/src/enums/ai/LoggingEnums.ts | 6 +- shared/otel-core/src/index.ts | 568 ++-- .../otel-core/src/interfaces/IOTelHrTime.ts | 73 +- .../src/interfaces/ai/IAppInsightsCore.ts | 31 +- .../src/interfaces/ai/IConfiguration.ts | 10 +- .../otel-core/src/interfaces/ai/ICookieMgr.ts | 2 +- .../src/interfaces/ai/ICorrelationConfig.ts | 2 +- .../src/interfaces/ai/IDiagnosticLogger.ts | 5 + .../interfaces/ai/IDistributedTraceContext.ts | 191 +- .../src/interfaces/ai/IFeatureOptIn.ts | 4 +- .../src/interfaces/ai/IRequestTelemetry.ts | 44 + .../src/interfaces/ai/ITraceProvider.ts | 163 ++ .../src/interfaces/ai/PartAExtensions.ts | 2 +- .../interfaces/ai/contracts/ContextTagKeys.ts | 2 +- .../src/interfaces/ai/contracts/IEnvelope.ts | 51 - .../interfaces/ai/contracts/RequestData.ts | 50 - .../interfaces/ai/telemetry/ISerializable.ts | 4 +- .../otel-core/src/interfaces/otel/IOTelApi.ts | 45 +- .../src/interfaces/otel/IOTelApiCtx.ts | 19 +- .../otel/attribute/IAttributeContainer.ts | 4 +- .../otel/config/IOTelAttributeLimits.ts | 62 +- .../src/interfaces/otel/config/IOTelConfig.ts | 49 +- .../otel/config/IOTelErrorHandlers.ts | 185 +- .../interfaces/otel/config/IOTelSpanLimits.ts | 122 +- .../interfaces/otel/config/IOTelTraceCfg.ts | 64 +- .../interfaces/otel/context/IOTelContext.ts | 4 + .../interfaces/otel/logs/IOTelLogRecord.ts | 2 +- .../otel/logs/IOTelReadableLogRecord.ts | 6 +- .../interfaces/otel/logs/IOTelSdkLogRecord.ts | 5 +- .../src/interfaces/otel/trace/IOTelLink.ts | 3 +- .../src/interfaces/otel/trace/IOTelSpan.ts | 480 +++- .../interfaces/otel/trace/IOTelSpanContext.ts | 43 +- .../src/interfaces/otel/trace/IOTelSpanCtx.ts | 29 +- .../interfaces/otel/trace/IOTelSpanOptions.ts | 17 +- .../interfaces/otel/trace/IOTelTimedEvent.ts | 2 +- .../interfaces/otel/trace/IOTelTraceApi.ts | 35 +- .../src/interfaces/otel/trace/IOTelTracer.ts | 190 +- .../otel/trace/IOTelTracerProvider.ts | 17 + .../interfaces/otel/trace/IReadableSpan.ts | 16 +- shared/otel-core/src/internal/EventHelpers.ts | 65 +- .../src/internal/attributeHelpers.ts | 25 +- shared/otel-core/src/internal/commonUtils.ts | 338 ++- shared/otel-core/src/internal/handleErrors.ts | 107 + shared/otel-core/src/internal/noopHelpers.ts | 16 + shared/otel-core/src/internal/timeHelpers.ts | 137 +- shared/otel-core/src/otel/api/OTelApi.ts | 88 +- .../otel-core/src/otel/api/context/context.ts | 15 +- .../src/otel/api/errors/OTelError.ts | 1 + shared/otel-core/src/otel/api/trace/span.ts | 147 +- .../otel-core/src/otel/api/trace/traceApi.ts | 88 +- .../src/otel/api/trace/traceProvider.ts | 76 + .../src/otel/api/trace/traceState.ts | 3 + shared/otel-core/src/otel/api/trace/tracer.ts | 96 +- .../src/otel/api/trace/tracerProvider.ts | 40 + shared/otel-core/src/otel/api/trace/utils.ts | 397 ++- .../src/otel/attribute/SemanticConventions.ts | 106 + .../src/otel/attribute/attributeContainer.ts | 18 +- .../otel-core/src/otel/resource/resource.ts | 2 +- .../otel-core/src/otel/sdk/OTelLogRecord.ts | 10 +- .../src/otel/sdk/OTelLoggerProvider.ts | 2 +- shared/otel-core/src/otel/sdk/config.ts | 5 +- .../src/telemetry/TelemetryItemCreator.ts | 2 +- .../otel-core/src/telemetry/W3cTraceState.ts | 17 +- .../otel-core/src/telemetry/ai/Common/Data.ts | 35 - .../src/telemetry/ai/Common/DataSanitizer.ts | 73 +- .../src/telemetry/ai/Common/Envelope.ts | 2 +- .../otel-core/src/telemetry/ai/DataTypes.ts | 11 + .../src/telemetry/ai/EnvelopeTypes.ts | 16 + shared/otel-core/src/telemetry/ai/Event.ts | 13 +- .../otel-core/src/telemetry/ai/Exception.ts | 20 +- shared/otel-core/src/telemetry/ai/Metric.ts | 12 +- shared/otel-core/src/telemetry/ai/PageView.ts | 12 +- .../src/telemetry/ai/PageViewPerformance.ts | 12 +- .../src/telemetry/ai/RemoteDependencyData.ts | 122 - shared/otel-core/src/telemetry/ai/Trace.ts | 12 +- shared/otel-core/src/types/time.ts | 8 - shared/otel-core/src/utils/DataCacheHelper.ts | 2 +- shared/otel-core/src/utils/EnvUtils.ts | 99 +- shared/otel-core/src/utils/HelperFuncs.ts | 181 +- shared/otel-core/src/utils/Offline.ts | 4 +- shared/otel-core/src/utils/RandomHelper.ts | 10 +- .../otel-core/src/utils/StorageHelperFuncs.ts | 20 +- shared/otel-core/src/utils/TraceParent.ts | 192 +- shared/otel-core/src/utils/UrlHelperFuncs.ts | 15 +- shared/otel-core/src/utils/Util.ts | 24 + .../otel-noop/Tests/Unit/src/index.tests.ts | 8 +- .../Unit/src/sdk/NoopOTelLogger.Tests.ts | 143 + .../src/sdk/NoopOTelLoggerProvider.Tests.ts | 404 +++ .../Tests/Unit/src/sdk/OTelLogger.Tests.ts | 132 - .../Unit/src/sdk/OTelLoggerProvider.Tests.ts | 400 --- shared/otel-noop/Tests/tsconfig.json | 2 +- shared/otel-noop/package.json | 4 +- .../src/api/logger/noopLogRecordProcessor.ts | 16 + .../src/api/{noop => logger}/noopLogger.ts | 5 +- .../src/api/noop/nonRecordingSpan.ts | 15 +- .../otel-noop/src/api/noop/noopContextMgr.ts | 5 +- shared/otel-noop/src/api/noop/noopHelpers.ts | 26 +- .../src/api/noop/noopLogRecordProcessor.ts | 20 - .../src/api/noop/noopTracerProvider.ts | 78 +- .../src/internal/InternalConstants.ts | 11 + shared/otel-noop/src/otel-noop-js.ts | 4 +- tools/chrome-debug-extension/package.json | 4 +- .../rollup-es5/Tests/Unit/src/index.tests.ts | 2 +- tools/shims/Tests/Unit/src/shims.tests.ts | 3 +- tools/shims/Tests/UnitTests.html | 2 +- tools/shims/package.json | 4 +- tools/shims/rollup.config.js | 4 +- version.json | 8 - 239 files changed, 11621 insertions(+), 10197 deletions(-) delete mode 100644 UpdatedMergeCommonOTelProjects.md create mode 100644 channels/applicationinsights-channel-js/src/Interfaces/Contracts/IRequestData.ts create mode 100644 channels/applicationinsights-channel-js/src/Telemetry/Common/Data.ts create mode 100644 channels/applicationinsights-channel-js/src/Telemetry/RemoteDependencyData.ts create mode 100644 channels/applicationinsights-channel-js/src/Telemetry/RequestData.ts delete mode 100644 shared/otel-core/Tests/Unit/src/AppInsights/Common/ThrottleMgr.tests.ts delete mode 100644 shared/otel-core/Tests/Unit/src/AppInsights/Common/Util.tests.ts delete mode 100644 shared/otel-core/Tests/Unit/src/ai/Core/GlobalTestHooks.Test.ts delete mode 100644 shared/otel-core/Tests/Unit/src/ai/Core/ThrottleMgr.tests.ts rename shared/otel-core/Tests/Unit/src/{ai/AppInsightsCoreSize.Tests.ts => sdk/OTelCoreSize.Tests.ts} (80%) create mode 100644 shared/otel-core/Tests/Unit/src/sdk/commonUtils.Tests.ts create mode 100644 shared/otel-core/Tests/Unit/src/trace/traceState.Tests.ts create mode 100644 shared/otel-core/Tests/Unit/src/trace/traceUtils.Tests.ts delete mode 100644 shared/otel-core/src/config/IDynamicWatcher.ts delete mode 100644 shared/otel-core/src/constants/CoreInternalConstants.ts create mode 100644 shared/otel-core/src/enums/ai/DependencyTypes.ts create mode 100644 shared/otel-core/src/interfaces/ai/IRequestTelemetry.ts create mode 100644 shared/otel-core/src/interfaces/ai/ITraceProvider.ts delete mode 100644 shared/otel-core/src/interfaces/ai/contracts/IEnvelope.ts delete mode 100644 shared/otel-core/src/interfaces/ai/contracts/RequestData.ts create mode 100644 shared/otel-core/src/internal/handleErrors.ts create mode 100644 shared/otel-core/src/internal/noopHelpers.ts create mode 100644 shared/otel-core/src/otel/api/trace/traceProvider.ts create mode 100644 shared/otel-core/src/otel/api/trace/tracerProvider.ts create mode 100644 shared/otel-core/src/otel/attribute/SemanticConventions.ts delete mode 100644 shared/otel-core/src/telemetry/ai/Common/Data.ts create mode 100644 shared/otel-core/src/telemetry/ai/DataTypes.ts create mode 100644 shared/otel-core/src/telemetry/ai/EnvelopeTypes.ts delete mode 100644 shared/otel-core/src/telemetry/ai/RemoteDependencyData.ts delete mode 100644 shared/otel-core/src/types/time.ts create mode 100644 shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLogger.Tests.ts create mode 100644 shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLoggerProvider.Tests.ts delete mode 100644 shared/otel-noop/Tests/Unit/src/sdk/OTelLogger.Tests.ts delete mode 100644 shared/otel-noop/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts create mode 100644 shared/otel-noop/src/api/logger/noopLogRecordProcessor.ts rename shared/otel-noop/src/api/{noop => logger}/noopLogger.ts (57%) delete mode 100644 shared/otel-noop/src/api/noop/noopLogRecordProcessor.ts create mode 100644 shared/otel-noop/src/internal/InternalConstants.ts diff --git a/.aiAutoMinify.json b/.aiAutoMinify.json index 061ae67ad..0a1ebf362 100644 --- a/.aiAutoMinify.json +++ b/.aiAutoMinify.json @@ -1,45 +1,8 @@ { "pkgs": { - "@microsoft/applicationinsights-core-js": { - "constEnums": [ - "_eSetDynamicPropertyFlags", - "ePendingOp", - "CallbackType" - ] - }, "@microsoft/applicationinsights-perfmarkmeasure-js": { "constEnums": [] }, - "@microsoft/applicationinsights-common": { - "constEnums": [ - "eOfflineValue", - "eRequestHeaders", - "eTraceStateKeyType", - "eStorageType", - "FieldType", - "eDistributedTracingModes", - "EventPersistenceValue", - "eEventsDiscardedReason", - "eBatchDiscardedReason", - "FeatureOptInMode", - "CdnFeatureMode", - "eActiveStatus", - "eLoggingSeverity", - "_eInternalMessageId", - "SendRequestReason", - "TransportType", - "eStatsType", - "TelemetryUnloadReason", - "TelemetryUpdateReason", - "eTraceHeadersMode", - "eW3CTraceFlags", - "DataPointType", - "DependencyKind", - "DependencySourceType", - "eSeverityLevel", - "DataSanitizerValues" - ] - }, "@microsoft/applicationinsights-properties-js": { "constEnums": [] }, @@ -47,13 +10,17 @@ "constEnums": [] }, "@microsoft/applicationinsights-channel-js": { - "constEnums": [] + "constEnums": [ + "eSerializeType" + ] }, "@microsoft/applicationinsights-web-basic": { "constEnums": [] }, "@microsoft/applicationinsights-analytics-js": { - "constEnums": [] + "constEnums": [ + "eRouteTraceStrategy" + ] }, "@microsoft/applicationinsights-web": { "constEnums": [] @@ -78,6 +45,7 @@ "eRequestHeaders", "eTraceStateKeyType", "eOfflineValue", + "eDependencyTypes", "eStorageType", "FieldType", "eDistributedTracingModes", diff --git a/AISKU/Tests/Unit/src/AISKUSize.Tests.ts b/AISKU/Tests/Unit/src/AISKUSize.Tests.ts index 36f144105..d19255665 100644 --- a/AISKU/Tests/Unit/src/AISKUSize.Tests.ts +++ b/AISKU/Tests/Unit/src/AISKUSize.Tests.ts @@ -1,6 +1,6 @@ import { AITestClass, Assert } from "@microsoft/ai-test-framework"; -import { dumpObj } from '@nevware21/ts-utils'; -import { createPromise, doAwait, IPromise } from '@nevware21/ts-async'; +import { dumpObj } from "@nevware21/ts-utils"; +import { createPromise, doAwait, IPromise } from "@nevware21/ts-async"; import { strUndefined } from "@microsoft/otel-core-js"; import { utlRemoveSessionStorage } from "@microsoft/otel-core-js"; import * as pako from "pako"; diff --git a/AISKU/Tests/Unit/src/CdnThrottle.tests.ts b/AISKU/Tests/Unit/src/CdnThrottle.tests.ts index 8d9555a42..bb2bd4a52 100644 --- a/AISKU/Tests/Unit/src/CdnThrottle.tests.ts +++ b/AISKU/Tests/Unit/src/CdnThrottle.tests.ts @@ -1,14 +1,17 @@ -import { ApplicationInsights, ApplicationInsightsContainer, IApplicationInsights, IConfig, IConfiguration, LoggingSeverity, Snippet, _eInternalMessageId } from '../../../src/index' -import { AITestClass, Assert, IFetchArgs, PollingAssert} from '@microsoft/ai-test-framework'; -import { IThrottleInterval, IThrottleLimit, IThrottleMgrConfig } from '@microsoft/otel-core-js'; -import { SinonSpy } from 'sinon'; -import { AppInsightsSku } from '../../../src/AISku'; -import { createSnippetV5 } from './testSnippetV5'; -import { CdnFeatureMode, FeatureOptInMode, getGlobal, getGlobalInst, isFunction, newId } from '@microsoft/otel-core-js'; -import { createSnippetV6 } from './testSnippetV6'; -import { CfgSyncPlugin, ICfgSyncConfig, ICfgSyncMode } from '@microsoft/applicationinsights-cfgsync-js'; -import { createSyncPromise, doAwait } from '@nevware21/ts-async'; -import { ICfgSyncCdnConfig } from '@microsoft/applicationinsights-cfgsync-js/src/Interfaces/ICfgSyncCdnConfig'; +import { AppInsightsSku } from "../../../src/AISku"; +import { ApplicationInsightsContainer } from "../../../src/ApplicationInsightsContainer"; +import { IApplicationInsights } from "../../../src/IApplicationInsights"; +import { Snippet } from "../../../src/Snippet"; +import { AITestClass, Assert, IFetchArgs, PollingAssert } from "@microsoft/ai-test-framework"; +import { IConfig, IConfiguration, LoggingSeverity, _eInternalMessageId, IThrottleInterval, IThrottleLimit, IThrottleMgrConfig } from "@microsoft/otel-core-js"; +import { SinonSpy } from "sinon"; +import { createSnippetV5 } from "./testSnippetV5"; +import { CdnFeatureMode, FeatureOptInMode, getGlobal, getGlobalInst, isFunction, newId } from "@microsoft/otel-core-js"; +import { createSnippetV6 } from "./testSnippetV6"; +import { CfgSyncPlugin, ICfgSyncConfig, ICfgSyncMode } from "@microsoft/applicationinsights-cfgsync-js"; +import { createSyncPromise, doAwait } from "@nevware21/ts-async"; +import { ICfgSyncCdnConfig } from "@microsoft/applicationinsights-cfgsync-js/src/Interfaces/ICfgSyncCdnConfig"; +const ApplicationInsights = AppInsightsSku; const TestInstrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; diff --git a/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts b/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts index 0e0d67e34..e998ed926 100644 --- a/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts +++ b/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts @@ -1,8 +1,8 @@ -import { ApplicationInsights, IAnalyticsConfig, IAppInsights, IConfig, ApplicationAnalytics } from "../../../src/index"; +import { AppInsightsSku as ApplicationInsights } from "../../../src/AISku"; +import { IConfig } from "@microsoft/otel-core-js"; import { AITestClass, Assert } from "@microsoft/ai-test-framework"; -import { AnalyticsPluginIdentifier, utlRemoveSessionStorage } from "@microsoft/otel-core-js"; +import { utlRemoveSessionStorage } from "@microsoft/otel-core-js"; import { AppInsightsCore, IConfiguration, isFunction, onConfigChange } from "@microsoft/otel-core-js"; -import { Sender } from "@microsoft/applicationinsights-channel-js"; const TestInstrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; diff --git a/AISKU/Tests/Unit/src/ThrottleSentMessage.tests.ts b/AISKU/Tests/Unit/src/ThrottleSentMessage.tests.ts index aa74a08d6..ed7795361 100644 --- a/AISKU/Tests/Unit/src/ThrottleSentMessage.tests.ts +++ b/AISKU/Tests/Unit/src/ThrottleSentMessage.tests.ts @@ -1,11 +1,14 @@ -import { ApplicationInsights, ApplicationInsightsContainer, IApplicationInsights, IConfig, IConfiguration, LoggingSeverity, Snippet, _eInternalMessageId } from '../../../src/index' -import { AITestClass, Assert} from '@microsoft/ai-test-framework'; -import { IThrottleInterval, IThrottleLimit, IThrottleMgrConfig } from '@microsoft/otel-core-js'; -import { SinonSpy } from 'sinon'; -import { AppInsightsSku } from '../../../src/AISku'; -import { createSnippetV5 } from './testSnippetV5'; -import { FeatureOptInMode, newId } from '@microsoft/otel-core-js'; -import { createSnippetV6 } from './testSnippetV6'; +import { AppInsightsSku } from "../../../src/AISku"; +import { ApplicationInsightsContainer } from "../../../src/ApplicationInsightsContainer"; +import { IApplicationInsights } from "../../../src/IApplicationInsights"; +import { Snippet } from "../../../src/Snippet"; +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +import { IConfig, IConfiguration, LoggingSeverity, _eInternalMessageId, IThrottleInterval, IThrottleLimit, IThrottleMgrConfig } from "@microsoft/otel-core-js"; +import { SinonSpy } from "sinon"; +import { createSnippetV5 } from "./testSnippetV5"; +import { FeatureOptInMode, newId } from "@microsoft/otel-core-js"; +import { createSnippetV6 } from "./testSnippetV6"; +const ApplicationInsights = AppInsightsSku; const TestInstrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; diff --git a/AISKU/Tests/Unit/src/aiskuunittests.ts b/AISKU/Tests/Unit/src/aiskuunittests.ts index 8a53729b0..87bd85325 100644 --- a/AISKU/Tests/Unit/src/aiskuunittests.ts +++ b/AISKU/Tests/Unit/src/aiskuunittests.ts @@ -1,15 +1,15 @@ import { AISKUSizeCheck } from "./AISKUSize.Tests"; -import { ApplicationInsightsTests } from './applicationinsights.e2e.tests'; -import { ApplicationInsightsFetchTests } from './applicationinsights.e2e.fetch.tests'; -import { CdnPackagingChecks } from './CdnPackaging.tests'; -import { GlobalTestHooks } from './GlobalTestHooks.Test'; -import { SanitizerE2ETests } from './sanitizer.e2e.tests'; -import { ValidateE2ETests } from './validate.e2e.tests'; -import { SenderE2ETests } from './sender.e2e.tests'; -import { SnippetInitializationTests } from './SnippetInitialization.Tests'; +import { ApplicationInsightsTests } from "./applicationinsights.e2e.tests"; +import { ApplicationInsightsFetchTests } from "./applicationinsights.e2e.fetch.tests"; +import { CdnPackagingChecks } from "./CdnPackaging.tests"; +import { GlobalTestHooks } from "./GlobalTestHooks.Test"; +import { SanitizerE2ETests } from "./sanitizer.e2e.tests"; +import { ValidateE2ETests } from "./validate.e2e.tests"; +import { SenderE2ETests } from "./sender.e2e.tests"; +import { SnippetInitializationTests } from "./SnippetInitialization.Tests"; import { CdnThrottle} from "./CdnThrottle.tests"; import { ThrottleSentMessage } from "./ThrottleSentMessage.tests"; -import { IAnalyticsConfigTests } from './IAnalyticsConfig.Tests'; +import { IAnalyticsConfigTests } from "./IAnalyticsConfig.Tests"; export function runTests() { new GlobalTestHooks().registerTests(); diff --git a/AISKU/Tests/Unit/src/applicationinsights.e2e.fetch.tests.ts b/AISKU/Tests/Unit/src/applicationinsights.e2e.fetch.tests.ts index 5389f12e4..369b275eb 100644 --- a/AISKU/Tests/Unit/src/applicationinsights.e2e.fetch.tests.ts +++ b/AISKU/Tests/Unit/src/applicationinsights.e2e.fetch.tests.ts @@ -1,6 +1,6 @@ -import { DistributedTracingModes, IConfig } from '@microsoft/otel-core-js'; -import { ApplicationInsightsTests } from './applicationinsights.e2e.tests'; -import { IConfiguration } from '@microsoft/otel-core-js'; +import { DistributedTracingModes, IConfig } from "@microsoft/otel-core-js"; +import { ApplicationInsightsTests } from "./applicationinsights.e2e.tests"; +import { IConfiguration } from "@microsoft/otel-core-js"; const _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; const _connectionString = `InstrumentationKey=${_instrumentationKey}`; diff --git a/AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts b/AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts index 467fe2982..fcf55e00f 100644 --- a/AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts @@ -1,14 +1,13 @@ -import { AITestClass, Assert, PollingAssert, EventValidator, TraceValidator, ExceptionValidator, MetricValidator, PageViewValidator, PageViewPerformanceValidator, RemoteDepdencyValidator } from '@microsoft/ai-test-framework'; -import { SinonSpy } from 'sinon'; -import { ApplicationInsights } from '../../../src/index' -import { Sender } from '@microsoft/applicationinsights-channel-js'; -import { IDependencyTelemetry, ContextTagKeys, Event, Trace, Exception, Metric, PageView, PageViewPerformance, RemoteDependencyData, DistributedTracingModes, RequestHeaders, IAutoExceptionTelemetry, BreezeChannelIdentifier, IConfig, EventPersistence } from '@microsoft/otel-core-js'; -import { ITelemetryItem, getGlobal, newId, dumpObj, BaseTelemetryPlugin, IProcessTelemetryContext, __getRegisteredEvents, arrForEach, IConfiguration, ActiveStatus, FeatureOptInMode } from "@microsoft/otel-core-js"; -import { IPropTelemetryContext } from '@microsoft/applicationinsights-properties-js'; -import { createAsyncResolvedPromise } from '@nevware21/ts-async'; -import { CONFIG_ENDPOINT_URL } from '../../../src/InternalConstants'; -import { IStackFrame } from '@microsoft/otel-core-js/src/Interfaces/Contracts/IStackFrame'; -import { utcNow } from '@nevware21/ts-utils'; +import { AITestClass, Assert, PollingAssert, EventValidator, TraceValidator, ExceptionValidator, MetricValidator, PageViewValidator, PageViewPerformanceValidator, RemoteDepdencyValidator } from "@microsoft/ai-test-framework"; +import { SinonSpy } from "sinon"; +import { AppInsightsSku as ApplicationInsights } from "../../../src/AISku"; +import { Sender } from "@microsoft/applicationinsights-channel-js"; +import { IDependencyTelemetry, ContextTagKeys, Event, Trace, Exception, Metric, PageView, PageViewPerformance, DistributedTracingModes, RequestHeaders, IAutoExceptionTelemetry, BreezeChannelIdentifier, IConfig, RemoteDependencyDataType } from "@microsoft/otel-core-js"; +import { ITelemetryItem, getGlobal, newId, dumpObj, BaseTelemetryPlugin, IProcessTelemetryContext, __getRegisteredEvents, arrForEach, IConfiguration, ActiveStatus, FeatureOptInMode, IStackFrame } from "@microsoft/otel-core-js"; +import { IPropTelemetryContext } from "@microsoft/applicationinsights-properties-js"; +import { createAsyncResolvedPromise } from "@nevware21/ts-async"; +import { CONFIG_ENDPOINT_URL } from "../../../src/InternalConstants"; +import { utcNow } from "@nevware21/ts-utils"; function _checkExpectedFrame(expectedFrame: IStackFrame, actualFrame: IStackFrame, index: number) { Assert.equal(expectedFrame.assembly, actualFrame.assembly, index + ") Assembly is not as expected"); @@ -1248,7 +1247,7 @@ export class ApplicationInsightsTests extends AITestClass { " c@http://example.com/stacktrace.js:9:3\n" + " b@http://example.com/stacktrace.js:6:3\n" + " a@http://example.com/stacktrace.js:3:3\n" + - " at Object.testMethod (http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)" + " at Object.testMethod (http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)" } }; @@ -1290,7 +1289,7 @@ export class ApplicationInsightsTests extends AITestClass { { level: 25, method: "c", assembly: "c@http://example.com/stacktrace.js:9:3", fileName: "http://example.com/stacktrace.js", line: 9 }, { level: 26, method: "b", assembly: "b@http://example.com/stacktrace.js:6:3", fileName: "http://example.com/stacktrace.js", line: 6 }, { level: 27, method: "a", assembly: "a@http://example.com/stacktrace.js:3:3", fileName: "http://example.com/stacktrace.js", line: 3 }, - { level: 28, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", fileName: "http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 53058 } + { level: 28, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", fileName: "http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 53058 } ]; const payloadStr: string[] = this.getPayloadMessages(this.successSpy); @@ -2008,7 +2007,7 @@ export class ApplicationInsightsTests extends AITestClass { return PageViewValidator.PageViewValidator.Validate(payload, baseType); case PageViewPerformance.dataType: return PageViewPerformanceValidator.PageViewPerformanceValidator.Validate(payload, baseType); - case RemoteDependencyData.dataType: + case RemoteDependencyDataType: return RemoteDepdencyValidator.RemoteDepdencyValidator.Validate(payload, baseType); default: diff --git a/AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts b/AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts index 3d431be0f..be829cf5d 100644 --- a/AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts @@ -1,9 +1,10 @@ -import { ApplicationInsights, IApplicationInsights, LoggingSeverity, _eInternalMessageId } from '../../../src/index' -import { Sender } from '@microsoft/applicationinsights-channel-js'; -import { AITestClass, Assert, PollingAssert } from '@microsoft/ai-test-framework'; -import { SinonSpy } from 'sinon'; -import { newId } from '@microsoft/otel-core-js'; -import { BreezeChannelIdentifier } from '@microsoft/otel-core-js'; +import { AppInsightsSku as ApplicationInsights } from "../../../src/AISku"; +import { IApplicationInsights } from "../../../src/IApplicationInsights"; +import { Sender } from "@microsoft/applicationinsights-channel-js"; +import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; +import { SinonSpy } from "sinon"; +import { LoggingSeverity, _eInternalMessageId, newId } from "@microsoft/otel-core-js"; +import { BreezeChannelIdentifier } from "@microsoft/otel-core-js"; export class SanitizerE2ETests extends AITestClass { private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; diff --git a/AISKU/Tests/Unit/src/sender.e2e.tests.ts b/AISKU/Tests/Unit/src/sender.e2e.tests.ts index 8a3a2496e..14188ff70 100644 --- a/AISKU/Tests/Unit/src/sender.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/sender.e2e.tests.ts @@ -1,10 +1,11 @@ -import { ApplicationInsights, IApplicationInsights } from '../../../src/index' -import { Sender } from '@microsoft/applicationinsights-channel-js'; -import { BreezeChannelIdentifier, utlGetSessionStorage, utlRemoveSessionStorage } from '@microsoft/otel-core-js'; -import { ActiveStatus, dumpObj, getJSON, isArray } from '@microsoft/otel-core-js'; -import { SinonSpy } from 'sinon'; -import { Assert, AITestClass, PollingAssert} from "@microsoft/ai-test-framework" -import { createAsyncResolvedPromise } from '@nevware21/ts-async'; +import { AppInsightsSku as ApplicationInsights } from "../../../src/AISku"; +import { IApplicationInsights } from "../../../src/IApplicationInsights"; +import { Sender } from "@microsoft/applicationinsights-channel-js"; +import { BreezeChannelIdentifier, utlGetSessionStorage, utlRemoveSessionStorage } from "@microsoft/otel-core-js"; +import { ActiveStatus, dumpObj, getJSON, isArray } from "@microsoft/otel-core-js"; +import { SinonSpy } from "sinon"; +import { Assert, AITestClass, PollingAssert } from "@microsoft/ai-test-framework"; +import { createAsyncResolvedPromise } from "@nevware21/ts-async"; export class SenderE2ETests extends AITestClass { private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; diff --git a/AISKU/Tests/Unit/src/validate.e2e.tests.ts b/AISKU/Tests/Unit/src/validate.e2e.tests.ts index ef92257dc..2ee8edf4d 100644 --- a/AISKU/Tests/Unit/src/validate.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/validate.e2e.tests.ts @@ -1,9 +1,10 @@ -import { ApplicationInsights, IApplicationInsights } from '../../../src/index' -import { Sender } from '@microsoft/applicationinsights-channel-js'; -import { SinonSpy } from 'sinon'; -import { AITestClass, Assert, PollingAssert } from '@microsoft/ai-test-framework'; -import { dumpObj } from '@microsoft/otel-core-js'; -import { BreezeChannelIdentifier } from '@microsoft/otel-core-js'; +import { AppInsightsSku as ApplicationInsights } from "../../../src/AISku"; +import { IApplicationInsights } from "../../../src/IApplicationInsights"; +import { Sender } from "@microsoft/applicationinsights-channel-js"; +import { SinonSpy } from "sinon"; +import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; +import { dumpObj } from "@microsoft/otel-core-js"; +import { BreezeChannelIdentifier } from "@microsoft/otel-core-js"; export class ValidateE2ETests extends AITestClass { private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; diff --git a/AISKU/package.json b/AISKU/package.json index ff637fdde..22c0c32d1 100644 --- a/AISKU/package.json +++ b/AISKU/package.json @@ -40,7 +40,7 @@ "finalhandler": "^1.1.1", "grunt": "^1.5.3", "grunt-cli": "^1.4.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", "grunt-rollup": "^12.0.0", "@nevware21/grunt-ts-plugin": "^0.5.1", "@nevware21/grunt-eslint-ts": "^0.5.1", @@ -73,8 +73,8 @@ "@microsoft/otel-core-js": "0.0.1-alpha", "@microsoft/applicationinsights-dependencies-js": "3.3.11", "@microsoft/applicationinsights-properties-js": "3.3.11", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "license": "MIT" } diff --git a/AISKU/src/index.ts b/AISKU/src/index.ts index 2b4d1074d..02313c754 100644 --- a/AISKU/src/index.ts +++ b/AISKU/src/index.ts @@ -55,7 +55,6 @@ export { Metric, PageView, PageViewPerformance, - RemoteDependencyData, Trace, DistributedTracingModes, IRequestHeaders, diff --git a/AISKULight/Tests/Unit/src/AISKULightSize.Tests.ts b/AISKULight/Tests/Unit/src/AISKULightSize.Tests.ts index 58cec6c38..321f62cea 100644 --- a/AISKULight/Tests/Unit/src/AISKULightSize.Tests.ts +++ b/AISKULight/Tests/Unit/src/AISKULightSize.Tests.ts @@ -1,6 +1,6 @@ import { AITestClass, Assert } from "@microsoft/ai-test-framework"; -import { dumpObj } from '@nevware21/ts-utils'; -import { createPromise, doAwait, IPromise } from '@nevware21/ts-async'; +import { dumpObj } from "@nevware21/ts-utils"; +import { createPromise, doAwait, IPromise } from "@nevware21/ts-async"; import * as pako from "pako"; const PACKAGE_JSON = "../package.json"; diff --git a/AISKULight/Tests/Unit/src/config.tests.ts b/AISKULight/Tests/Unit/src/config.tests.ts index 4d6bf5c7f..22bf4821d 100644 --- a/AISKULight/Tests/Unit/src/config.tests.ts +++ b/AISKULight/Tests/Unit/src/config.tests.ts @@ -1,8 +1,8 @@ -import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; -import { ITelemetryItem, newId } from "@microsoft/otel-core-js"; -import { ApplicationInsights} from "../../../src/index"; -import { BreezeChannelIdentifier, ContextTagKeys, utlRemoveSessionStorage } from "@microsoft/otel-core-js"; +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +import { newId } from "@microsoft/otel-core-js"; +import { BreezeChannelIdentifier, utlRemoveSessionStorage } from "@microsoft/otel-core-js"; import { Sender } from "@microsoft/applicationinsights-channel-js"; +import { ApplicationInsights } from "../../../src/index"; export class ApplicationInsightsConfigTests extends AITestClass { private readonly _instrumentationKey = "b7170927-2d1c-44f1-acec-59f4e1751c11"; diff --git a/AISKULight/Tests/Unit/src/dynamicconfig.tests.ts b/AISKULight/Tests/Unit/src/dynamicconfig.tests.ts index 8f952bf32..52c94d8fe 100644 --- a/AISKULight/Tests/Unit/src/dynamicconfig.tests.ts +++ b/AISKULight/Tests/Unit/src/dynamicconfig.tests.ts @@ -1,9 +1,10 @@ import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; import { IConfig } from "@microsoft/otel-core-js"; import { IConfiguration, IPayloadData, isString, ITelemetryItem, IXHROverride, newId } from "@microsoft/otel-core-js"; -import { ApplicationInsights, ISenderConfig } from "../../../src/index"; +import { ApplicationInsights } from "../../../src/index"; +import { ISenderConfig } from "@microsoft/applicationinsights-channel-js"; import { createAsyncResolvedPromise } from "@nevware21/ts-async"; -import { SinonSpy } from 'sinon'; +import { SinonSpy } from "sinon"; export class ApplicationInsightsDynamicConfigTests extends AITestClass { private static readonly _instrumentationKey = "b7170927-2d1c-44f1-acec-59f4e1751c11"; private static readonly _connectionString = `InstrumentationKey=${ApplicationInsightsDynamicConfigTests._instrumentationKey}`; diff --git a/AISKULight/build.cmd b/AISKULight/build.cmd index 7356fc39e..c8687d2ee 100644 --- a/AISKULight/build.cmd +++ b/AISKULight/build.cmd @@ -6,6 +6,6 @@ REM npm install REM rd /s /q amd\bundle REM call grunt aiskulite && echo "copy files" xcopy "node_modules/applicationinsights-channel-js/bundle" "bundle" /S /E /I -xcopy "node_modules/applicationinsights-common/bundle" "bundle" /S /E /I -xcopy "node_modules/applicationinsights-core-js/bundle" "bundle" /S /E /I +rem xcopy "node_modules/applicationinsights-common/bundle" "bundle" /S /E /I +xcopy "node_modules/otel-core-js/bundle" "bundle" /S /E /I diff --git a/AISKULight/package.json b/AISKULight/package.json index 0ffd7d30a..cd82cd0f6 100644 --- a/AISKULight/package.json +++ b/AISKULight/package.json @@ -62,8 +62,8 @@ "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/applicationinsights-channel-js": "3.3.11", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "license": "MIT" } diff --git a/UpdatedMergeCommonOTelProjects.md b/UpdatedMergeCommonOTelProjects.md deleted file mode 100644 index 9684a8472..000000000 --- a/UpdatedMergeCommonOTelProjects.md +++ /dev/null @@ -1,2400 +0,0 @@ -# Updated Plan: Merge AppInsightsCore, AppInsightsCommon, and OpenTelemetry Projects - -## Project Goal & Context - -**Objective:** Create a unified core infrastructure for the Application Insights JavaScript SDK by consolidating three separate shared packages into two new, purpose-built packages. - -### What This Merge Accomplishes - -**Creating 2 New Packages:** -1. **`@microsoft/otel-core-js`** - The new unified common core for ALL projects in the repository -2. **`@microsoft/otel-noop-js`** - Separated no-operation (noop) implementations for lightweight scenarios - -**Consolidating 3 Existing Packages:** -1. **`@microsoft/applicationinsights-core-js`** (AppInsightsCore) → merge into `otel-core-js` -2. **`@microsoft/applicationinsights-common`** (AppInsightsCommon) → merge into `otel-core-js` -3. **`@microsoft/otel-core-js`** (OpenTelemetry) → merge into `otel-core-js` + split noop → `otel-noop-js` - -**Why This Merge:** -- **Eliminates duplication** - Three packages had overlapping functionality and shared dependencies -- **Simplifies maintenance** - Single core package easier to maintain than three separate ones -- **Clearer architecture** - Organized by functionality (config, diagnostics, telemetry) rather than historical boundaries -- **Better tree-shaking** - Consumers can import only what they need from unified package -- **Separates concerns** - Noop implementations in separate package for minimal bundle size scenarios - -**Package Relationships After Merge:** -``` -┌─────────────────────────────────────────────────────┐ -│ @microsoft/otel-core-js │ -│ (NEW - Unified core for entire repository) │ -│ - Core SDK (AppInsightsCore) │ -│ - Configuration management │ -│ - Telemetry items & contracts │ -│ - Diagnostics & logging │ -│ - OpenTelemetry API & SDK implementations │ -│ - Shared utilities & helpers │ -└─────────────────────────────────────────────────────┘ - ▲ ▲ ▲ - │ │ │ - ┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐ - │ AISKU │ │ AISKULight │ │ Extensions │ - │ (Full SDK) │ │ (Lightweight) │ │ (Plugins) │ - └─────────────────┘ └─────────────────┘ └─────────────────┘ - -┌─────────────────────────────────────────────────────┐ -│ @microsoft/otel-noop-js │ -│ (NEW - Lightweight noop implementations) │ -│ - Noop telemetry plugin │ -│ - Noop logger │ -│ - Noop span/tracer │ -│ - Used for testing & minimal scenarios │ -└─────────────────────────────────────────────────────┘ -``` - -**Source Package Retirement:** -After merge completion, these packages will be deleted: -- ❌ `@microsoft/applicationinsights-core-js` - Replaced by `@microsoft/otel-core-js` -- ❌ `@microsoft/applicationinsights-common` - Replaced by `@microsoft/otel-core-js` -- ❌ `@microsoft/otel-core-js` (existing package in `shared/OpenTelemetry`) - Folder renamed to `shared/otel-core` and merged with `@microsoft/otel-noop-js` - ---- - -## Document Purpose - -This document reflects the **actual implementation** of merging three core shared packages (AppInsightsCore, AppInsightsCommon, and OpenTelemetry) into a single unified `otel-core` project. It captures lessons learned, actual steps taken, and provides a template for future similar migrations or branch merges. - -**Document Version:** 2.2 -**Last Updated:** January 23, 2026 - -## Executive Summary - -**Merge Operation:** Consolidating 3 shared packages → 2 new purpose-built packages - -**Source Packages (To Be Merged):** -- `@microsoft/applicationinsights-core-js` (AppInsightsCore) - 32 files -- `@microsoft/applicationinsights-common` (AppInsightsCommon) - 134 files -- `@microsoft/otel-core-js` (OpenTelemetry) - 112 files -- **Total& Success Criteria - -### Primary Goals - -1. **Create Unified Core Package (`@microsoft/otel-core-js`)** - - Merge AppInsightsCore, AppInsightsCommon, OpenTelemetry into single package - - Organize by functionality, not historical boundaries - - Maintain zero compilation errors throughout the process - - Preserve all functionality from source packages - -2. **Extract Noop Implementations (`@microsoft/otel-noop-js`)** - - Separate noop code from OpenTelemetry into dedicated package - - Enable lightweight scenarios without full core package - - Maintain clean separation of concerns - -3. **Update All Consuming Packages** - - Update rush.json with new package definitions - - Update gruntfile.js build configurations - - Update package.json dependencies across all projects - - Update import statements in all consuming code - - Validate clean builds across entire repository - -4. **Maintain Code Quality** - - Zero TypeScript compilation errors at all times - - Preserve all existing functionality - - No breaking changes to public APIs - - All tests continue to pass - -### Success Criteria - -**Critical Requirements (Must Complete for Success):** -- otel-core-js compiles cleanly with zero errors -- otel-noop-js compiles cleanly with zero errors -- All consuming packages updated to reference new packages -- All consuming packages compile successfully -- Complete test suite passes -- Comprehensive documentation in place - -**Supporting Requirements (Needed for Maintainability):** -- Clear migration path documented -- Rollback plan documented -- Lessons learned captured for future merges -- AI-executable instructions for file migration - -**Optional Enhancements (Nice to Have):** -- Performance benchmarks showing no regression -- Bundle size analysis showing reduction potential -- Migration tool/script for future similar operations - -**Key Achievements (When Complete):** -- Single unified core package that ALL repository projects depend on -- Organized by functionality (config, diagnostics, telemetry, core, otel) -- Separated noop implementations for lightweight scenarios -- Zero duplication across packages - -## Actual Folder Structure Implemented - -``` -otel-core/ -├── package.json # Combined dependencies from all 3 packages -├── tsconfig.json # Unified TypeScript configuration -├── rollup.config.js # Combined build configuration -├── README.md # Comprehensive package documentation -├── MIGRATION.md # User migration guide -├── API.md # API reference documentation -├── MIGRATION_VERIFICATION.md # Migration audit trail -├── typedoc.json # API doc generation config -├── LICENSE -├── NOTICE -├── PRIVACY -│ -├── src/ -│ ├── index.ts # Main entry point (273 exports) -│ ├── InternalConstants.ts # Consolidated constants -│ │ -│ ├── interfaces/ # All interface definitions -│ │ ├── IException.ts # Shared OTel interface -│ │ ├── IExportResult.ts # Shared OTel interface -│ │ ├── time.ts # Shared time interface -│ │ │ -│ │ ├── AppInsights/ # AppInsights-specific interfaces -│ │ │ ├── IAppInsightsCore.ts -│ │ │ ├── ITelemetryPlugin.ts -│ │ │ ├── ITelemetryItem.ts -│ │ │ ├── IDiagnosticLogger.ts -│ │ │ ├── INotificationManager.ts -│ │ │ ├── telemetry/ # From AppInsightsCommon/Interfaces/ -│ │ │ │ ├── ITelemetry.ts -│ │ │ │ ├── IMetric.ts -│ │ │ │ ├── ITrace.ts -│ │ │ │ └── ... -│ │ │ ├── config/ # Configuration interfaces -│ │ │ │ ├── IDynamicConfig.ts -│ │ │ │ └── IDynamicWatcher.ts -│ │ │ ├── plugin/ # Plugin-related interfaces -│ │ │ └── core/ # Core SDK interfaces -│ │ │ ├── IUnloadHookContainer.ts -│ │ │ ├── IUnloadHandlerContainer.ts -│ │ │ └── IPluginState.ts -│ │ │ -│ │ └── OTel/ # OpenTelemetry interfaces -│ │ ├── IOTelApi.ts -│ │ ├── IOTelSdk.ts -│ │ ├── IOTelApiCtx.ts -│ │ ├── IOTelSdkCtx.ts -│ │ ├── IOTelAttributes.ts -│ │ ├── IException.ts # OTel-specific -│ │ ├── IExportResult.ts # OTel-specific -│ │ ├── attribute/ -│ │ │ └── IAttributeContainer.ts -│ │ ├── baggage/ -│ │ ├── config/ -│ │ │ └── IOTelAttributeLimits.ts -│ │ ├── context/ -│ │ │ └── IOTelContext.ts -│ │ ├── logs/ -│ │ │ ├── IOTelLogger.ts -│ │ │ └── IOTelLogRecord.ts -│ │ ├── metrics/ -│ │ ├── propagation/ -│ │ ├── resource/ -│ │ │ └── IOTelResource.ts -│ │ ├── resources/ -│ │ └── trace/ -│ │ ├── IOTelTracer.ts -│ │ └── IOTelSpan.ts -│ │ -│ ├── enums/ # All enum definitions -│ │ ├── EnumHelperFuncs.ts # Shared enum utilities -│ │ │ -│ │ ├── AppInsights/ # From AppInsightsCommon/Enums/ -│ │ │ ├── Enums.ts -│ │ │ ├── EventsDiscardedReason.ts -│ │ │ ├── FeatureOptInEnums.ts -│ │ │ ├── InitActiveStatusEnum.ts -│ │ │ ├── LoggingEnums.ts -│ │ │ ├── SendRequestReason.ts -│ │ │ ├── StatsType.ts -│ │ │ ├── TelemetryUnloadReason.ts -│ │ │ ├── TelemetryUpdateReason.ts -│ │ │ ├── TraceHeadersMode.ts -│ │ │ └── W3CTraceFlags.ts -│ │ │ -│ │ └── OTel/ # From OpenTelemetry/enums/ -│ │ ├── eAttributeChangeOp.ts -│ │ ├── logs/ -│ │ │ └── eOTelSeverityNumber.ts -│ │ └── trace/ -│ │ └── eOTelSpanKind.ts -│ │ -│ ├── types/ # Type definitions -│ │ ├── AppInsights/ # AppInsights types -│ │ └── OTel/ # OpenTelemetry types -│ │ └── OTelAnyValue.ts -│ │ -│ ├── config/ # Configuration management -│ │ ├── config.ts # Config utilities -│ │ ├── ConfigDefaults.ts # From AppInsightsCore -│ │ ├── ConfigDefaultHelpers.ts -│ │ ├── DynamicConfig.ts # Runtime config updates -│ │ ├── DynamicProperty.ts -│ │ ├── DynamicState.ts -│ │ └── DynamicSupport.ts -│ │ -│ ├── diagnostics/ # Diagnostic and logging utilities -│ │ ├── DiagnosticLogger.ts # From AppInsightsCore -│ │ └── otel/ # OTel logging implementations -│ │ ├── OTelLogger.ts -│ │ ├── OTelLoggerProvider.ts -│ │ ├── OTelLogRecord.ts -│ │ ├── OTelMultiLogRecordProcessor.ts -│ │ └── noopStubs/ # NotImplemented stubs -│ │ ├── noopLogger.ts -│ │ └── noopLogRecordProcessor.ts -│ │ -│ ├── core/ # Core SDK functionality -│ │ ├── AppInsightsCore.ts # From AppInsightsCore/JavaScriptSDK/ -│ │ ├── BaseTelemetryPlugin.ts -│ │ ├── NotificationManager.ts -│ │ ├── PerfManager.ts -│ │ ├── ProcessTelemetryContext.ts -│ │ ├── CookieMgr.ts -│ │ ├── EventHelpers.ts -│ │ ├── InstrumentHooks.ts -│ │ ├── SenderPostManager.ts -│ │ ├── StatsBeat.ts -│ │ ├── DbgExtensionUtils.ts -│ │ ├── AggregationError.ts -│ │ └── Constants.ts -│ │ -│ ├── telemetry/ # Telemetry creation and handling -│ │ ├── TelemetryItemCreator.ts # From AppInsightsCommon -│ │ ├── ConnectionStringParser.ts -│ │ ├── RequestResponseHeaders.ts -│ │ ├── W3cTraceState.ts -│ │ ├── Event.ts # From AppInsightsCommon/Telemetry/ -│ │ ├── Exception.ts -│ │ ├── Metric.ts -│ │ ├── Trace.ts -│ │ ├── PageView.ts -│ │ ├── PageViewData.ts -│ │ ├── PageViewPerformance.ts -│ │ └── RemoteDependencyData.ts -│ │ -│ ├── utils/ # Shared utility functions -│ │ ├── ThrottleMgr.ts # From AppInsightsCore/Diagnostics/ -│ │ ├── AsyncUtils.ts # From AppInsightsCore/JavaScriptSDK/ -│ │ ├── ResponseHelpers.ts -│ │ ├── DbgExtensionUtils.ts -│ │ ├── Offline.ts # From AppInsightsCommon -│ │ ├── UnloadHandlerContainer.ts -│ │ ├── notImplemented.ts # Noop stub helper -│ │ ├── CoreUtils.ts # From AppInsightsCommon/Utils/ -│ │ ├── DataCacheHelper.ts -│ │ ├── Util.ts -│ │ ├── HashCodeScoreGenerator.ts -│ │ ├── StringUtils.ts -│ │ └── Extensions.ts -│ │ -│ ├── constants/ # Constant definitions -│ │ ├── Constants.ts # From AppInsightsCommon -│ │ └── CoreConstants.ts -│ │ -│ ├── otel/ # OpenTelemetry implementations -│ │ ├── OTelApi.ts # Bridge - From AppInsightsCore -│ │ ├── OTelSdk.ts # Bridge - From AppInsightsCore -│ │ │ -│ │ ├── api/ # OpenTelemetry API implementations -│ │ │ ├── context/ -│ │ │ │ └── OTelContext.ts -│ │ │ ├── errors/ -│ │ │ │ └── OTelError.ts -│ │ │ └── trace/ -│ │ │ ├── OTelTracer.ts -│ │ │ └── OTelSpan.ts -│ │ │ -│ │ ├── sdk/ # OpenTelemetry SDK implementations -│ │ │ └── config.ts -│ │ │ -│ │ ├── attribute/ # Attribute handling -│ │ │ └── attributeContainer.ts -│ │ │ -│ │ └── resource/ # Resource handling -│ │ └── resource.ts -│ │ -│ └── internal/ # Internal utilities -│ └── otel/ # OTel internal utilities -│ ├── attributeHelpers.ts -│ ├── commonUtils.ts -│ ├── InternalConstants.ts -│ ├── LoggerProviderSharedState.ts -│ ├── timeHelpers.ts -│ └── noopStubs/ # NotImplemented stubs -│ └── noopLogRecordProcessor.ts -│ -├── Tests/ -│ ├── Unit/ -│ │ ├── src/ -│ │ │ └── index.tests.ts # Main test entry point -│ │ ├── AppInsights/ # Tests from Core & Common -│ │ │ ├── Core/ # AppInsightsCore tests (18 files) -│ │ │ │ ├── index.tests.ts -│ │ │ │ ├── ApplicationInsightsCore.Tests.ts -│ │ │ │ ├── AppInsightsCoreSize.Tests.ts -│ │ │ │ ├── CookieManager.Tests.ts -│ │ │ │ ├── DynamicConfig.Tests.ts -│ │ │ │ ├── Dynamic.Tests.ts -│ │ │ │ ├── EventHelper.Tests.ts -│ │ │ │ ├── EventsDiscardedReason.Tests.ts -│ │ │ │ ├── HelperFunc.Tests.ts -│ │ │ │ ├── LoggingEnum.Tests.ts -│ │ │ │ ├── SendPostManager.Tests.ts -│ │ │ │ ├── StatsBeat.Tests.ts -│ │ │ │ ├── ThrottleMgr.tests.ts -│ │ │ │ ├── UpdateConfig.Tests.ts -│ │ │ │ ├── errors.Tests.ts -│ │ │ │ └── traceState.Tests.ts -│ │ │ └── Common/ # AppInsightsCommon tests (12 files) -│ │ │ ├── AppInsightsCommon.tests.ts -│ │ │ ├── ConnectionStringParser.tests.ts -│ │ │ ├── Exception.tests.ts -│ │ │ ├── RequestHeaders.tests.ts -│ │ │ ├── SeverityLevel.tests.ts -│ │ │ ├── ThrottleMgr.tests.ts -│ │ │ ├── Util.tests.ts -│ │ │ ├── W3cTraceParentTests.ts -│ │ │ ├── W3CTraceStateModes.tests.ts -│ │ │ └── W3TraceState.Tests.ts -│ │ └── OTel/ # OpenTelemetry tests (9 files) -│ │ ├── index.tests.ts -│ │ ├── api/ -│ │ │ └── OTelApi.Tests.ts -│ │ ├── attribute/ -│ │ │ └── AttributeContainer.Tests.ts -│ │ ├── internal/ -│ │ │ └── commonUtils.Tests.ts -│ │ ├── sdk/ -│ │ │ ├── OTelLogger.Tests.ts -│ │ │ ├── OTelLoggerProvider.Tests.ts -│ │ │ ├── OTelLogRecord.Tests.ts -│ │ │ └── OTelMultiLogRecordProcessor.Tests.ts -│ │ └── trace/ -│ │ └── span.Tests.ts -│ ├── Perf/ -│ ├── tsconfig.json -│ ├── PerfTests.html -│ └── UnitTests.html -│ -├── build/ # Build outputs -├── dist-es5/ # ES5 distribution -├── browser/ # Browser builds -└── types/ # TypeScript definitions -``` - -## Implementation Phases - -⚠️ **CRITICAL REQUIREMENT**: At the end of EACH phase, the entire repository MUST be compilable with zero TypeScript errors AND all tests must pass. This ensures incremental validation and prevents accumulation of cascading errors. - -### 🔑 Key Principles for ALL Phases - -These principles apply to every phase and must be followed rigorously: - -1. **"Export As You Go"** - Add exports to index.ts IMMEDIATELY when moving/adding files -2. **Fix Imports Immediately** - Update all import paths as soon as files are moved -3. **Incremental Validation** - Compile and test after each significant change -4. **Update Build Infrastructure** - Update rush.json and gruntfile.js in each phase as needed -5. **Work in Small Batches** - For file moves, work in groups of 10-20 files maximum -6. **Test After Each Phase** - Entire repository must compile and all tests must pass -7. **🚫 BANNED - Wildcard & Directory index.ts** - No `export *`, no `import *`, no directory-level index.ts files. Only explicit exports in single root index.ts - ---- - -## 🚫 CRITICAL: Repository-Wide Export/Import Rules - -**These rules apply to ALL phases and the ENTIRE repository:** - -❌ **BANNED**: -- `export * from './path'` - Wildcard exports are BANNED -- `import * as X from './path'` - Wildcard imports are BANNED -- Directory-level index.ts files (e.g., `src/interfaces/index.ts`, `src/enums/index.ts`) are BANNED - -✅ **REQUIRED**: -- Only ONE index.ts per package at the root (`src/index.ts`) -- All exports MUST be explicit: `export { SpecificItem } from './path/to/file'` -- All imports MUST be specific: `import { SpecificItem } from '@microsoft/package'` or `import { Item } from './relative/path'` - -**Example of correct exports in src/index.ts**: -```typescript -// ✅ CORRECT - Explicit named exports -export { IOTelContext, IOTelApiCtx } from './interfaces/IOTelContext'; -export { OTelTracer } from './otel/OTelTracer'; -export { eLoggingSeverity, eInternalMessageId } from './enums/LoggingEnums'; - -// ❌ WRONG - These are BANNED -// export * from './interfaces'; -// export * from './otel'; -``` - ---- - -### Phase 1: Rename OpenTelemetry Folder to otel-core - -**Goal**: Rename the existing `shared/OpenTelemetry` folder to `shared/otel-core` while keeping the package name `@microsoft/otel-core-js`. Update all folder references across the entire repository. This establishes the foundation package that will receive the merged code in later phases. - -**Why This First**: Starting with a rename (rather than creating from scratch) preserves git history and ensures we have a working, compilable base package before adding complexity. - -**End State**: Package renamed, all imports updated, entire repository compiles and tests pass. - -#### 1.1: Rename Package Directory -```powershell -# From repository root -cd shared -git mv OpenTelemetry otel-core -cd .. -``` - -#### 1.2: Update package.json -**File**: `shared/otel-core/package.json` - -Keep the existing package name `@microsoft/otel-core-js` and the existing version (check actual version, likely `0.0.1-alpha`): - -⚠️ **IMPORTANT**: Check the ACTUAL version in the existing package.json. The version may be `0.0.1-alpha` rather than `4.0.0-alpha.1`. Use the actual version consistently. - -```json -{ - "name": "@microsoft/otel-core-js", - "version": "0.0.1-alpha", - "description": "Unified core infrastructure for Application Insights JavaScript SDK", - "scripts": { - "build": "grunt otelCore", - "test": "grunt otelCoreunittest", - "lint-fix": "grunt otelCore-lint-fix" - } -} -``` - -#### 1.3: Update rush.json -**File**: `rush.json` - -Update the OpenTelemetry entry to point to the new folder location: -```json -{ - "projects": [ - // UPDATE the OpenTelemetry entry with new folder path: - { - "packageName": "@microsoft/otel-core-js", - "projectFolder": "shared/otel-core", - "reviewCategory": "production" - } - ] -} -``` - -#### 1.4: Update gruntfile.js -**File**: `gruntfile.js` - -Update all OpenTelemetry references to otelCore: -```javascript -// Update task names and paths -'otelCore': { - tsconfig: './shared/otel-core/tsconfig.json', - outDir: './shared/otel-core/dist' -} - -// Update registered tasks -grunt.registerTask("otelCore", tsBuildActions("otelCore", true)); -grunt.registerTask("otelCore-min", minTasks("otelCore")); -grunt.registerTask("otelCore-restore", restoreTasks("otelCore")); -grunt.registerTask("otelCore-lint-fix", ["otelCore-restore"]); -grunt.registerTask("otelCoreunittest", tsTestActions("otelCore")); -``` - -#### 1.5: Verify Import Statements -**NOTE**: Since the package name remains `@microsoft/otel-core-js`, no import statement changes are required in Phase 1. The folder rename is transparent to consuming packages. - -**Verify** imports are still working: -```powershell -grep -r "@microsoft/otel-core-js" --include="*.ts" --exclude-dir={node_modules,dist,build,temp} . -# Should return matches - these imports are still valid -``` - -#### 1.6: Verify Package Dependencies -Since the package name `@microsoft/otel-core-js` remains unchanged, no package.json updates are required for consuming packages. - -**Packages that reference otel-core-js** (verify these still work after folder rename): -- `AISKU/package.json` -- `AISKULight/package.json` -- All `extensions/*/package.json` -- All `channels/*/package.json` - -**Existing dependency** (no change needed): -```json -{ - "dependencies": { - "@microsoft/otel-core-js": "4.0.0-alpha.1" - } -} -``` - -⚠️ **Note**: Use the actual version number (not `workspace:*`) - Rush manages symlinks via `rush update`. - -#### 1.7: Run Rush Update -```powershell -# From repository root to clean all previous dependency links and establish the correct configured ones -rush update --recheck --purge --full -``` - -This regenerates the dependency graph with the renamed package. - -#### 1.8: Build and Test - Phase 1 Validation -```powershell -# Run lint-fix to ensure consistent formatting after import changes -cd shared/otel-core -npm run lint-fix -cd ../.. - -# Build the renamed package -cd shared/otel-core -npm run build -# MUST succeed with zero errors - -# Build entire repository -cd ../.. -rush update --recheck --purge --full -rush rebuild -# MUST succeed with zero errors - -# Run all tests -rush test -# MUST pass all tests -``` - -⚠️ **Version Placeholders**: Some source files may contain `"#version#"` placeholders. These are automatically replaced during build by grunt tasks. Do NOT manually replace them in source code. - -**✅ Success Criteria for Phase 1:** *(Completed: January 20, 2026)* -- [x] Folder renamed from `OpenTelemetry` to `otel-core` -- [x] package.json keeps name `@microsoft/otel-core-js` -- [x] rush.json updated with new folder path -- [x] gruntfile.js updated with new folder path -- [x] No import changes needed (package name unchanged) -- [x] `rush update` completed successfully -- [x] `rush rebuild` succeeds with zero errors -- [x] `rush test` passes all tests -- [x] Git history preserved - -**Phase 1 Implementation Summary:** -- Renamed `shared/OpenTelemetry` to `shared/otel-core` using `git mv` -- Updated rush.json project folder path from `shared/OpenTelemetry` to `shared/otel-core` -- Updated gruntfile.js build configuration to reference new folder path -- Package name `@microsoft/otel-core-js` unchanged - no import updates required -- All consuming packages continue to work without modification - ---- - -### Phase 2: Create otel-noop-js Package and Extract Noop Functionality - -**Goal**: Create a separate `@microsoft/otel-noop-js` package and move all no-operation (noop) implementations from `otel-core` to this new package. This separation keeps the core package focused and allows minimal bundle sizes. - -**Why This Second**: With otel-core established, extract noop functionality before merging more packages. This keeps concerns separated. - -**End State**: otel-noop-js package exists with all noop implementations, otel-core has no noop code, entire repository compiles and tests pass. - -#### 2.1: Create otel-noop-js Package Structure -```powershell -# From repository root -New-Item -ItemType Directory -Force -Path "shared/otel-noop/src" -New-Item -ItemType Directory -Force -Path "shared/otel-noop/Tests" -``` - -#### 2.2: Create package.json -**File**: `shared/otel-noop/package.json` - -⚠️ **IMPORTANT**: -- otel-noop-js depends on otel-core-js, NOT the other way around. otel-core-js must NOT depend on otel-noop-js. -- Check the ACTUAL version of otel-core-js and use it consistently (likely `0.0.1-alpha`). -- Also include `@nevware21/ts-utils` and `@nevware21/ts-async` as dependencies if noop implementations need them. - -```json -{ - "name": "@microsoft/otel-noop-js", - "version": "0.0.1-alpha", - "description": "No-operation implementations for Application Insights JavaScript SDK", - "main": "dist/es5/otel-noop.js", - "module": "dist-es5/otel-noop.js", - "types": "types/otel-noop-js.d.ts", - "sideEffects": false, - "dependencies": { - "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": "0.12.4", - "@nevware21/ts-async": "0.6.1" - }, - "scripts": { - "build": "grunt otelNoop", - "test": "grunt otelNoopunittest", - "lint-fix": "grunt otelNoop-lint-fix" - } -} -``` - -⚠️ **Notes**: -- Rush manages symlinks via `rush update --recheck --purge --full`. Use actual version numbers, not `workspace:*`. -- npm script names ("build", "test") must match the grunt task names registered in gruntfile.js ("otelNoop", "otelNoopunittest"). -- The "lint-fix" script calls a grunt task that runs the restore task (similar to AISKU's ai-restore pattern). -- The types field should match the actual output filename (e.g., `otel-noop-js.d.ts` not `otel-noop.d.ts`). - -#### 2.3: Create tsconfig.json -**File**: `shared/otel-noop/tsconfig.json` - -⚠️ **IMPORTANT**: Include `moduleResolution: "node"` to properly resolve @microsoft/otel-core-js imports. - -```json -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "declarationDir": "./types", - "rootDir": "./src", - "moduleResolution": "node" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "Tests"] -} -``` - -#### 2.4: Create rollup.config.js -**File**: `shared/otel-noop/rollup.config.js` - -Copy from otel-core's rollup config and update: - -⚠️ **CRITICAL - importCheck Behavior**: The `importCheckNames` parameter specifies imports that should be **BLOCKED**, not allowed. Do NOT include dependencies like otel-core-js in this list. - -```javascript -// Key changes needed: -// 1. Update package name references -const packageName = "otel-noop"; -const namespace = "Microsoft.ApplicationInsights"; - -// 2. Update entry point -const entryPointPath = "src/otel-noop-js.ts"; - -// 3. Mark otel-core-js as external dependency -external: [ - "@microsoft/otel-core-js" -], - -// 4. CRITICAL: importCheckNames should ONLY block self-references -// The third parameter is importCheckNames - these imports are BLOCKED -createUnVersionedConfig(banner, {...}, [ "otel-noop-js" ], false) - -// ❌ WRONG - This blocks valid otel-core-js imports! -// createUnVersionedConfig(banner, {...}, [ "otel-noop-js", "otel-core-js" ], false) - -// 5. Update output file names -output: { - file: `browser/es5/ms.otel-noop.js`, - // ... other output config -} -``` - -#### 2.4b: Create api-extractor.json -**File**: `shared/otel-noop/api-extractor.json` - -⚠️ **CRITICAL**: The bundledPackages array MUST have whitespace between brackets (not `[]`). The `dtsgen.js` script uses a regex that requires at least one character. - -⚠️ **CRITICAL**: Do NOT add `@microsoft/otel-core-js` to bundledPackages - this causes type incompatibility issues. - -```json -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "mainEntryPointFilePath": "/dist/types/otel-noop-js.d.ts", - "bundledPackages": [ - ], - "compiler": { - "tsconfigFilePath": "/tsconfig.json" - }, - "apiReport": { - "enabled": false - }, - "docModel": { - "enabled": false - }, - "dtsRollup": { - "enabled": true, - "untrimmedFilePath": "/types/otel-noop-js.d.ts" - }, - "tsdocMetadata": { - "enabled": false - }, - "messages": { - "compilerMessageReporting": { - "default": { "logLevel": "warning" } - }, - "extractorMessageReporting": { - "default": { "logLevel": "warning" } - }, - "tsdocMessageReporting": { - "default": { "logLevel": "warning" } - } - } -} -``` - -**Why whitespace is required:** -```json -// ❌ WRONG - regex fails to match -"bundledPackages": [], - -// ✅ CORRECT - whitespace between brackets -"bundledPackages": [ - ], -``` - -#### 2.4a: Create Test Entry Point -**File**: `shared/otel-noop/Tests/Unit/src/index.tests.ts` - -⚠️ **IMPORTANT**: The entry point MUST be named `index.tests.ts` to match the grunt pattern. - -Create test entry point file that gruntfile.js expects: -```typescript -/** - * otel-noop-js unit tests entry point - */ - -// Import test framework -import "@microsoft/ai-test-framework"; - -// Import all test suites (update as tests are added/moved) -import "./sdk/OTelLogger.Tests"; -import "./sdk/OTelLoggerProvider.Tests"; -// Add other test imports as tests are added -``` - -**Create test directory structure**: -```powershell -New-Item -ItemType Directory -Force -Path "shared/otel-noop/Tests/Unit/src/sdk" -New-Item -ItemType File -Path "shared/otel-noop/Tests/Unit/src/index.tests.ts" -``` - -⚠️ **Const Enum Warning**: If test files use const enums like `eW3CTraceFlags`, replace them with literal values: -```typescript -// ❌ WRONG - const enum not available at runtime -import { eW3CTraceFlags } from "@microsoft/otel-core-js"; -span.traceFlags = eW3CTraceFlags.Sampled; - -// ✅ CORRECT - use literal constant -const W3C_TRACE_FLAG_SAMPLED = 1; -span.traceFlags = W3C_TRACE_FLAG_SAMPLED; -``` - -#### 2.5: Create Initial Entry Point -**File**: `shared/otel-noop/src/otel-noop-js.ts` - -⚠️ **IMPORTANT**: The entry point filename should match the package name pattern (e.g., `otel-noop-js.ts` not `index.ts`). This is consistent with other packages in this repository. - -```typescript -/** - * @microsoft/otel-noop-js - * No-operation implementations for lightweight scenarios - */ - -// Re-export interfaces from otel-core-js for convenience -export { INoopProxyConfig } from "./interfaces/noop/INoopProxyConfig"; - -// Export noop implementations -export { createNoopProxy, _noopThis, _noopVoid } from "./api/noop/noopProxy"; -export { createNoopContextMgr } from "./api/noop/noopContextMgr"; -export { createNoopLogger } from "./api/noop/noopLogger"; -export { createNoopLogRecordProcessor } from "./api/noop/noopLogRecordProcessor"; -export { createNoopTracerProvider } from "./api/noop/noopTracerProvider"; -``` - -#### 2.6: Identify Noop Files to Move -**From otel-core, identify all noop-related files**: -```powershell -cd shared/otel-core/src -Get-ChildItem -Recurse -Filter "*noop*" -Include *.ts -Get-ChildItem -Recurse -Filter "*Noop*" -Include *.ts -``` - -**Typical noop files** (verify in your codebase): -- `NoopLogger.ts` or `noopLogger.ts` -- `NoopTelemetryPlugin.ts` -- `NoopSpan.ts`, `NoopTracer.ts` (OTel noop implementations) -- Any files in `noop/` directories -- Related test files - -#### 2.7: Move Noop Files to otel-noop-js -**For each noop file**: - -⚠️ **CRITICAL**: Preserve the directory structure when moving files. Files in `otel-core/src/api/noop/` should go to `otel-noop/src/api/noop/`, etc. - -```powershell -# Create matching directory structure first -New-Item -ItemType Directory -Force -Path "shared/otel-noop/src/api/noop" -New-Item -ItemType Directory -Force -Path "shared/otel-noop/src/interfaces/noop" - -# Move files preserving structure -git mv shared/otel-core/src/api/noop/noopHelpers.ts shared/otel-noop/src/api/noop/noopHelpers.ts -git mv shared/otel-core/src/api/noop/noopProxy.ts shared/otel-noop/src/api/noop/noopProxy.ts -git mv shared/otel-core/src/api/noop/noopContextMgr.ts shared/otel-noop/src/api/noop/noopContextMgr.ts -git mv shared/otel-core/src/api/noop/noopLogger.ts shared/otel-noop/src/api/noop/noopLogger.ts -git mv shared/otel-core/src/api/noop/noopLogRecordProcessor.ts shared/otel-noop/src/api/noop/noopLogRecordProcessor.ts -git mv shared/otel-core/src/api/noop/noopTracerProvider.ts shared/otel-noop/src/api/noop/noopTracerProvider.ts -git mv shared/otel-core/src/interfaces/noop/INoopProxyConfig.ts shared/otel-noop/src/interfaces/noop/INoopProxyConfig.ts - -# Move corresponding test files -git mv shared/otel-core/Tests/Unit/src/sdk/OTelLogger.Tests.ts shared/otel-noop/Tests/Unit/src/sdk/OTelLogger.Tests.ts -git mv shared/otel-core/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts shared/otel-noop/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts -``` - -#### 2.7a: Update Test Entry Points After Moving Tests -**After moving test files**, update BOTH test entry points: - -**Update otel-core test entry point** - Remove moved test imports: -**File**: `shared/otel-core/Tests/Unit/src/index.tests.ts` -```typescript -// REMOVE these imports (tests moved to otel-noop): -// import "./sdk/OTelLogger.Tests"; -// import "./sdk/OTelLoggerProvider.Tests"; -``` - -**Update otel-noop test entry point** - Add moved test imports: -**File**: `shared/otel-noop/Tests/Unit/src/index.tests.ts` -```typescript -import "@microsoft/ai-test-framework"; -import "./sdk/OTelLogger.Tests"; -import "./sdk/OTelLoggerProvider.Tests"; -``` - -#### 2.8: Update Imports in Moved Files -**For each moved file**, update any imports from relative otel-core paths to the new structure: - -```typescript -// Before (in otel-core) -import { IDiagnosticLogger } from '../interfaces/IDiagnosticLogger'; - -// After (in otel-noop-js) -import { IDiagnosticLogger } from '@microsoft/otel-core-js'; -``` - -**For NoopLogger specifically**, ensure it implements IDiagnosticLogger: -```typescript -// In shared/otel-noop/src/NoopLogger.ts -import { IDiagnosticLogger } from '@microsoft/otel-core-js'; - -/** - * No-operation logger that implements IDiagnosticLogger interface - */ -export class NoopLogger implements IDiagnosticLogger { - // Implement all IDiagnosticLogger methods as no-ops - public throwInternal(severity: number, msgId: number, msg: string, properties?: object): void { - // No-op - } - - public warnToConsole(message: string): void { - // No-op - } - - // ... implement other interface methods -} - -/** - * Factory function to create logger with dependency injection - * @param config - Optional configuration or dependencies - */ -export function createLogger(config?: any): IDiagnosticLogger { - return new NoopLogger(); -} -``` - -#### 2.9: Add Exports to otel-noop-js/src/index.ts -**Immediately after moving files**, add exports: - -```typescript -/** - * @microsoft/otel-noop-js - * No-operation implementations for lightweight scenarios - */ - -// Export both the class and factory function for NoopLogger -export { NoopLogger, createLogger } from './NoopLogger'; -export { NoopTelemetryPlugin } from './NoopTelemetryPlugin'; -export { NoopSpan, NoopTracer } from './otel/NoopImplementations'; -// Add all noop exports -``` - -⚠️ **Design Pattern**: -- **NoopLogger class** implements `IDiagnosticLogger` interface from otel-core-js -- **createLogger() factory** provides dependency injection capability -- Consumers can use `new NoopLogger()` directly or `createLogger(config)` for DI scenarios - -#### 2.9a: Replace Noop References in otel-core with NotImplemented Exceptions -⚠️ **CRITICAL**: otel-core-js must NOT depend on otel-noop-js. Any noop functionality still referenced in otel-core files (that aren't being moved) must be replaced. - -**Find noop references in otel-core**: -```powershell -cd shared/otel-core/src -Get-ChildItem -Recurse -Filter "*.ts" | Select-String -Pattern "Noop|noop" | Where-Object { $_.Line -notmatch "^\s*//" } -``` - -**Use the existing `throwOTelNotImplementedError` helper** (already in otel-core): -```typescript -// In shared/otel-core/src/api/errors/notImplemented.ts (already exists) -import { throwOTelNotImplementedError } from "../api/errors/notImplemented"; - -// Usage in files that referenced noop implementations -// Replace noop calls with throwOTelNotImplementedError calls -throwOTelNotImplementedError("createNoopLogRecordProcessor"); -``` - -**Example replacements in otel-core files:** -```typescript -// In src/internal/LoggerProviderSharedState.ts -// Before: return createNoopLogRecordProcessor(); -// After: -import { throwOTelNotImplementedError } from "../api/errors/notImplemented"; -throwOTelNotImplementedError("LoggerProviderSharedState requires a LogRecordProcessor"); - -// In src/sdk/OTelLoggerProvider.ts -// Before: return createNoopLogger(...); -// After: -throwOTelNotImplementedError("shutdown logger requested"); - -// In src/api/trace/nonRecordingSpan.ts -// Replace _noopThis and _noopVoid with inline implementations: -function _noopThis(theThis: T) { return theThis; } -function _noopVoid() { /* noop */ } -``` - -**Best Practice**: If otel-core-js needs a default logger, create a stub that implements `IDiagnosticLogger` and throws NotImplemented. This maintains type safety and makes it clear that `@microsoft/otel-noop-js` should be used for actual noop implementations. - -#### 2.10: Update Imports Across Repository -**Find all files importing noop implementations from otel-core-js**: - -```powershell -grep -r "NoopLogger\|NoopTelemetryPlugin\|NoopSpan" --include="*.ts" --exclude-dir={node_modules,dist,build,temp} . | grep "@microsoft/otel-core-js" -``` - -**Update those imports**: -```typescript -// Before -import { NoopLogger } from '@microsoft/otel-core-js'; - -// After - choose appropriate import based on usage -import { NoopLogger } from '@microsoft/otel-noop-js'; // Direct class usage -import { createLogger } from '@microsoft/otel-noop-js'; // Factory pattern with DI - -// Usage examples: -const logger1 = new NoopLogger(); // Direct instantiation -const logger2 = createLogger(); // Factory (no config) -const logger3 = createLogger({ /* config */ }); // Factory with config injection -``` - -#### 2.11: Update rush.json -**File**: `rush.json` - -Add otel-noop-js package: -```json -{ - "projects": [ - { - "packageName": "@microsoft/otel-core-js", - "projectFolder": "shared/otel-core", - "reviewCategory": "production" - }, - { - "packageName": "@microsoft/otel-noop-js", - "projectFolder": "shared/otel-noop", - "reviewCategory": "production" - } - ] -} -``` - -#### 2.12: Update gruntfile.js -**File**: `gruntfile.js` - -**Step 1: Add otel-noop-js to modules** in the `buildConfig` call (around line 725): -```javascript -var theBuildConfig = deepMerge(buildConfig({ - // Shared - "otelCore": { - path: "./shared/otel-core", - unitTestName: "otel.unittests.js" - }, - "otelNoop": { - path: "./shared/otel-noop", - unitTestName: "otel-noop.unittests.js" - }, - - // SKUs - "aisku": { - path: "./AISKU", - // ... rest of config -``` - -**Step 2: Register grunt tasks** (around line 1165, after otelCore tasks): -```javascript -grunt.registerTask("otelNoop", tsBuildActions("otelNoop", true)); -grunt.registerTask("otelNoop-min", minTasks("otelNoop")); -grunt.registerTask("otelNoop-restore", restoreTasks("otelNoop")); -grunt.registerTask("otelNoop-lint-fix", ["otelNoop-restore"]); -grunt.registerTask("otelNoopunittest", tsTestActions("otelNoop")); -grunt.registerTask("otelNoop-mintest", tsTestActions("otelNoop", true)); -``` - -⚠️ **Important**: The module name "otelNoop" (camelCase) must match in: -1. Module definition in buildConfig -2. All grunt.registerTask calls -3. npm scripts in package.json ("grunt otelNoop") -4. The "otelNoop-lint-fix" task runs restore to apply consistent formatting - -#### 2.13: Run Rush Update -```powershell -rush update --recheck --purge --full -``` - -#### 2.14: Build and Test - Phase 2 Validation -```powershell -# Run lint-fix after file moves -cd shared/otel-core -npm run lint-fix -cd ../otel-noop -npm run lint-fix -cd ../.. - -# Build otel-noop -cd shared/otel-noop -npm run build -# MUST succeed with zero errors - -# Build otel-core (should still work, minus noop files) -cd ../otel-core -npm run build -# MUST succeed with zero errors - -# Build entire repository -cd ../.. -rush update --recheck --purge --full -rush rebuild -# MUST succeed with zero errors - -# Run all tests -rush test -# ⚠️ EXPECT POTENTIAL TEST FAILURES from NotImplemented exceptions -``` - -⚠️ **Dynamic Constants**: If any auto-generated files (like `InternalConstants.ts`) were moved, ensure the generation script is updated to output to the new location. Do NOT manually edit auto-generated files. - -**If test failures occur:** -1. **Document the failures** - Note which tests are failing and why -2. **Identify the cause** - Are tests relying on noop implementations? -3. **STOP and request manual intervention** - Do NOT proceed to Phase 3 -4. **Report findings**: - ``` - Phase 2 Test Failures: - - Test: [test name] - - File: [file path] - - Error: [NotImplemented exception message] - - Reason: Test was using noop implementation from otel-core - - Fix needed: Update test to use @microsoft/otel-noop-js or mock the functionality - ``` -5. **Wait for manual test fixes** before proceeding - -**If all tests pass**: Proceed to Phase 3 - -**✅ Success Criteria for Phase 2:** *(Completed: January 20, 2026)* -- [x] otel-noop-js package created with proper structure -- [x] otel-noop-js depends on otel-core-js (NOT the reverse) -- [x] otel-core-js does NOT depend on otel-noop-js -- [x] All noop files moved from otel-core to otel-noop-js -- [x] All noop tests moved -- [x] otel-noop-js entry point exports all noop implementations -- [x] All imports updated to use `@microsoft/otel-noop-js` -- [x] All noop references in otel-core replaced with NotImplemented exceptions -- [x] rush.json includes otel-noop-js -- [x] gruntfile.js includes otel-noop-js tasks -- [x] api-extractor.json created with correct bundledPackages format (see note below) -- [x] Consuming packages (e.g., AppInsightsCore) updated to import from otel-noop-js -- [x] `rush rebuild` succeeds with zero errors -- [x] `rush test` passes all tests -- [x] otel-core has no noop code remaining - -**Phase 2 Implementation Summary:** -- Created `shared/otel-noop` package with proper structure (src/, Tests/, package.json, tsconfig.json, rollup.config.js, api-extractor.json) -- Moved noop files: noopProxy.ts, noopContextMgr.ts, noopLogger.ts, noopLogRecordProcessor.ts, noopTracerProvider.ts, noopHelpers.ts -- Moved interface: INoopProxyConfig.ts to otel-noop/src/interfaces/noop/ -- Updated otel-core files to use inline noop functions or throwOTelNotImplementedError -- Fixed ES5 compatibility issues (strTrim, objKeys, utcNow from @nevware21/ts-utils) -- Added otel-noop-js to rush.json and gruntfile.js -- Updated AppInsightsCore to depend on and import from @microsoft/otel-noop-js - -**⚠️ api-extractor.json Note:** -Create `shared/otel-noop/api-extractor.json` with bundledPackages having whitespace: -```json -{ - "bundledPackages": [ - ], - // ... rest of config -} -``` -Do NOT bundle otel-core-js types - this causes type incompatibility issues. - ---- - -### Phase 3: Restructure otel-core File Organization - -**Goal**: Reorganize the files within `otel-core` to match the planned directory structure (config/, diagnostics/, enums/, interfaces/, etc.). This prepares otel-core to receive the merged files from AppInsightsCommon and AppInsightsCore. - -**Why This Third**: Before merging other packages, organize otel-core's existing files into the target structure. This establishes the pattern and makes subsequent merges cleaner. - -**End State**: otel-core files organized into target directory structure, all imports fixed, entire repository compiles and tests pass. - -**Target Directory Structure**: -``` -shared/otel-core/src/ - ├── config/ # Configuration classes (lowercase) - ├── diagnostics/ # Logging and diagnostics (lowercase) - ├── enums/ # All enumerations - ├── interfaces/ # TypeScript interfaces - ├── telemetry/ # Telemetry items and helpers - ├── utils/ # Utility functions - ├── constants/ # Constants - ├── internal/ # Internal utilities - └── otel/ # OpenTelemetry-specific implementations -``` - -#### 3.1: Analyze Current otel-core Structure -```powershell -cd shared/otel-core/src -Get-ChildItem -Recurse -Filter "*.ts" | Select-Object FullName -``` - -Document current structure to plan moves. - -#### 3.2: Create Target Directory Structure -```powershell -# From shared/otel-core/src -New-Item -ItemType Directory -Force -Path "config" -New-Item -ItemType Directory -Force -Path "diagnostics" -New-Item -ItemType Directory -Force -Path "enums" -New-Item -ItemType Directory -Force -Path "interfaces" -New-Item -ItemType Directory -Force -Path "telemetry" -New-Item -ItemType Directory -Force -Path "utils" -New-Item -ItemType Directory -Force -Path "constants" -New-Item -ItemType Directory -Force -Path "internal" -New-Item -ItemType Directory -Force -Path "otel" -``` - -#### 3.3: Move Files in Small Batches -**Work in batches of 10-20 files**. For each batch: - -**Batch Example - Constants**: -```powershell -# Identify constant files -cd shared/otel-core/src -Get-ChildItem -Filter "*Constant*" -Include *.ts - -# Move to constants/ directory -git mv OTelConstants.ts constants/OTelConstants.ts -git mv SomeOtherConstants.ts constants/SomeOtherConstants.ts -``` - -**Batch Example - Enums**: -```powershell -# Move enum files -git mv enums/OTel/*.ts enums/ -# Flatten or organize as needed -``` - -**Batch Example - Interfaces**: -```powershell -# Move interface files -git mv interfaces/OTel/*.ts interfaces/ -``` - -#### 3.4: Fix Imports in Moved Files (Immediately After Each Batch) -**For each moved file**, update its imports to reflect new relative paths: - -```typescript -// Before (when file was in root or different location) -import { OTelConstants } from './OTelConstants'; -import { IOTelContext } from '../interfaces/IOTelContext'; - -// After (in new location like telemetry/) -import { OTelConstants } from '../constants/OTelConstants'; -import { IOTelContext } from '../interfaces/IOTelContext'; -``` - -**Use PowerShell to help**: -```powershell -# Find files importing from old paths -cd shared/otel-core/src -Get-ChildItem -Recurse -Filter "*.ts" | Select-String -Pattern "from ['\"]\.\./" -``` - -#### 3.5: Update Exports in index.ts (Immediately After Each Batch) -**File**: `shared/otel-core/src/index.ts` - -Update export paths as files move: -```typescript -// Exports organized by category -// Constants -export { OTelConstants, CONSTANT_A, CONSTANT_B } from './constants/OTelConstants'; - -// Enums -export { eOTelEnum, eSomeOtherEnum } from './enums/OTelEnums'; - -// Interfaces -export { IOTelContext } from './interfaces/IOTelContext'; -export { IOTelSpan, ISpanOptions } from './interfaces/IOTelSpan'; - -// OTel implementations -export { OTelTracer } from './otel/OTelTracer'; -export { OTelSpan } from './otel/OTelSpan'; - -// Utils -export { helperFunction1, helperFunction2 } from './utils/OTelHelpers'; -``` - -#### 3.6: Compile and Validate After Each Batch -```powershell -# After each batch of moves -cd shared/otel-core -npm run build -# MUST succeed with zero errors - -# If errors, fix imports before proceeding to next batch -``` - -#### 3.7: Update Test Imports -**Update test files** to use the package import (not relative paths): - -```typescript -// Before (relative path) -import { OTelTracer } from '../../src/otel/OTelTracer'; - -// After (package import) -import { OTelTracer } from '@microsoft/otel-core-js'; -``` - -#### 3.8: Build and Test - Phase 3 Validation -```powershell -# Run lint-fix after file reorganization -cd shared/otel-core -npm run lint-fix - -# Build otel-core with new structure -npm run build -# MUST succeed with zero errors - -# Verify browser builds exist -ls browser/es5/ # Should contain built JavaScript files - -# Build entire repository -cd ../.. -rush update --recheck --purge --full -rush rebuild -# MUST succeed with zero errors - -# Run all tests -rush test -# MUST pass all tests -``` - -⚠️ **Path Casing**: Ensure consistent casing in imports (e.g., `config/` vs `Config/`). Use lowercase directory names as specified in target structure. - -**✅ Success Criteria for Phase 3:** *(Completed: January 21, 2026)* -- [x] All otel-core files organized into target structure -- [x] All imports fixed to use correct relative paths -- [x] index.ts exports updated and organized -- [x] All test imports use package imports (not relative) -- [x] `rush rebuild` succeeds with zero errors (20/20 packages completed) -- [x] `rush test` passes all tests (140 tests passed, 0 failed) -- [x] Directory names use lowercase (config/, diagnostics/) - -**Phase 3 Implementation Summary:** -- Created target directory structure: `config/`, `diagnostics/`, `core/`, `telemetry/`, `utils/`, `constants/`, `otel/`, `interfaces/OTel/`, `enums/OTel/`, `types/OTel/` -- Moved OTel API files to `otel/api/` (context/, errors/, trace/) -- Moved OTel SDK files to `otel/sdk/` -- Moved attribute files to `otel/attribute/` -- Moved resource files to `otel/resource/` -- Moved internal utilities to `internal/otel/` -- Organized all interfaces under `interfaces/OTel/` with subfolders (config/, context/, logs/, trace/, baggage/, metrics/, resources/, attribute/) -- Organized all enums under `enums/OTel/` with subfolders (trace/, logs/) -- Organized types under `types/OTel/` -- Updated main entry point `otel-core-js.ts` with all new export paths -- Updated all test files with corrected import paths - ---- - -### Phase 4: Merge AppInsightsCommon into otel-core - -**Goal**: Move all files and tests from `shared/AppInsightsCommon` into `otel-core` using the established directory structure. Update all imports across the repository. Remove the AppInsightsCommon package. - -**Why This Fourth**: With otel-core's structure established, merge the first major package. AppInsightsCommon before AppInsightsCore because Core often depends on Common. - -**End State**: AppInsightsCommon fully merged into otel-core, all imports updated, AppInsightsCommon package removed, entire repository compiles and tests pass. - -#### 4.1: Analyze AppInsightsCommon Structure -```powershell -cd shared/AppInsightsCommon/src -Get-ChildItem -Recurse -Filter "*.ts" | Select-Object FullName | Out-File ../../../appinsights-common-files.txt -``` - -Review the file list and plan which files go into which otel-core directories. - -#### 4.2: Create File Migration Plan -**Map AppInsightsCommon files to otel-core directories**: - -| AppInsightsCommon File | Target otel-core Location | -|------------------------|---------------------------| -| `Enums.ts` | `enums/AppInsights/Enums.ts` | -| `Util.ts` | `utils/Util.ts` | -| `Constants.ts` | `constants/AIConstants.ts` | -| `Interfaces/IConfig.ts` | `interfaces/AppInsights/IConfig.ts` | -| `Telemetry/Event.ts` | `telemetry/AppInsights/Event.ts` | -| ... | ... | - -#### 4.3: Move Files in Batches (10-20 files per batch) -**Recommended batch order**: -1. Constants (~5 files) -2. Enums (~10 files) -3. Basic interfaces (~15 files) -4. Utility functions (~10 files) -5. Telemetry interfaces (~15 files) -6. Telemetry implementations (~20 files) -7. Remaining files - -**For each batch:** - -**Example Batch 1 - Constants**: -```powershell -# Create subdirectory if needed -New-Item -ItemType Directory -Force -Path "shared/otel-core/src/constants" - -# Move constants files -git mv shared/AppInsightsCommon/src/Constants.ts shared/otel-core/src/constants/AIConstants.ts -git mv shared/AppInsightsCommon/src/InternalConstants.ts shared/otel-core/src/constants/AIInternalConstants.ts -``` - -**Fix imports in moved files immediately**: -```typescript -// In the moved file, update imports -// Before (relative to AppInsightsCommon) -import { Util } from './Util'; -import { IConfig } from './Interfaces/IConfig'; - -// After (relative to new location in otel-core) -import { Util } from '../utils/Util'; -import { IConfig } from '../interfaces/AppInsights/IConfig'; -``` - -**Add exports to otel-core/src/index.ts immediately**: -```typescript -// Constants from AppInsightsCommon -export { AIConstants, SOME_CONSTANT } from './constants/AIConstants'; -export { InternalConstant1, InternalConstant2 } from './constants/AIInternalConstants'; -``` - -**Compile and validate**: -```powershell -cd shared/otel-core -npm run build -# MUST succeed before proceeding to next batch -``` - -**If errors**: Fix them before moving to the next batch. - -#### 4.4: Move Test Files -**After moving all source files**, move the corresponding test files: - -```powershell -# Create test directory structure -New-Item -ItemType Directory -Force -Path "shared/otel-core/Tests/Unit/AppInsights" - -# Move test files -git mv shared/AppInsightsCommon/Tests/Unit/*.tests.ts shared/otel-core/Tests/Unit/AppInsights/ -``` - -**Update test imports**: -```typescript -// Before -import { Util } from '../../../src/Util'; - -// After (use package import) -import { Util } from '@microsoft/otel-core-js'; -``` - -#### 4.5: Update Imports Across Repository -**Find all files importing from AppInsightsCommon**: - -```powershell -grep -r "@microsoft/applicationinsights-common" --include="*.ts" --exclude-dir={node_modules,dist,build,temp} . -``` - -**Create PowerShell script to update imports**: -```powershell -$files = Get-ChildItem -Recurse -Include *.ts,*.tsx -Exclude node_modules,dist,build,temp | - Select-String -Pattern '@microsoft/applicationinsights-common' -List | - Select-Object -ExpandProperty Path - -foreach ($file in $files) { - $content = Get-Content $file -Raw - $content = $content -replace '@microsoft/applicationinsights-common', '@microsoft/otel-core-js' - Set-Content $file $content -NoNewline -} - -Write-Host "Updated $($files.Count) files" -``` - -**Verify**: -```powershell -grep -r "@microsoft/applicationinsights-common" --include="*.ts" --exclude-dir={node_modules,dist,build,temp} . -# Should return no matches -``` - -#### 4.6: Update Package Dependencies -**Remove AppInsightsCommon from package.json files**: - -In all consuming packages (`AISKU`, `AISKULight`, extensions, channels): -```json -{ - "dependencies": { - // REMOVE: - // "@microsoft/applicationinsights-common": "", - - // Already have from Phase 1: - "@microsoft/otel-core-js": "4.0.0-alpha.1" - } -} -``` - -#### 4.7: Update rush.json -**File**: `rush.json` - -Remove the AppInsightsCommon entry: -```json -{ - "projects": [ - // REMOVE: - // { - // "packageName": "@microsoft/applicationinsights-common", - // "projectFolder": "shared/AppInsightsCommon" - // } - ] -} -``` - -#### 4.8: Update gruntfile.js -**File**: `gruntfile.js` - -Remove AppInsightsCommon build tasks (if any standalone tasks exist). - -#### 4.9: Remove AppInsightsCommon Package -**Only after all imports are updated and builds succeed**: - -```powershell -# Comprehensive verification - check all file types -grep -r "applicationinsights-common" --include="*.ts" --include="*.json" --exclude-dir={node_modules,dist,build,temp} . -# Should be empty - -# Verify no uncommitted changes in package being removed -cd shared/AppInsightsCommon -git status -cd ../.. - -# If all clear, remove the package directory -git rm -r shared/AppInsightsCommon -``` - -#### 4.10: Run Rush Update -```powershell -rush update --recheck --purge --full -``` - -#### 4.11: Build and Test - Phase 4 Validation -```powershell -# Run lint-fix after file merge -cd shared/otel-core -npm run lint-fix - -# Build otel-core -npm run build -# MUST succeed with zero errors - -# Build entire repository -cd ../.. -rush update --recheck --purge --full -rush rebuild -# MUST succeed with zero errors - -# Run all tests (including migrated AppInsightsCommon tests) -rush test -# MUST pass all tests -``` - -**✅ Success Criteria for Phase 4:** *(Completed: January 22, 2026)* -- [x] All AppInsightsCommon files moved to otel-core -- [x] All AppInsightsCommon tests moved to otel-core -- [x] All imports updated from `@microsoft/applicationinsights-common` to `@microsoft/otel-core-js` -- [x] All package.json dependencies updated -- [x] rush.json no longer references AppInsightsCommon -- [x] AppInsightsCommon directory removed -- [x] otel-core/src/index.ts exports all AppInsightsCommon symbols -- [x] `rush rebuild` succeeds with zero errors -- [x] `rush test` passes all tests - -**Phase 4 Implementation Summary:** -- Moved all AppInsightsCommon source files to otel-core (telemetry/, utils/, interfaces/AppInsights/, enums/AppInsights/, constants/) -- Moved all AppInsightsCommon tests to Tests/Unit/src/AppInsights/Common/ -- Updated otel-core-js.ts entry point with all AppInsightsCommon exports -- Updated package.json dependencies across repository from @microsoft/applicationinsights-common to @microsoft/otel-core-js -- Removed shared/AppInsightsCommon directory - ---- - -### Phase 5: Merge AppInsightsCore into otel-core - -**Goal**: Move all files and tests from `shared/AppInsightsCore` into `otel-core` using the established directory structure. Update all imports across the repository. Remove the AppInsightsCore package. - -**Why This Last**: AppInsightsCore is merged last because it often depends on both Common and OTel functionality, which are now both in otel-core. - -**End State**: All three original packages (AppInsightsCore, AppInsightsCommon, OpenTelemetry) fully merged into otel-core, entire repository compiles and tests pass, only otel-core and otel-noop-js remain. - -#### 5.1: Analyze AppInsightsCore Structure -```powershell -cd shared/AppInsightsCore/src -Get-ChildItem -Recurse -Filter "*.ts" | Select-Object FullName | Out-File ../../../appinsights-core-files.txt -``` - -Review the file list and plan which files go into which otel-core directories. - -#### 5.2: Create File Migration Plan -**Map AppInsightsCore files to otel-core directories**: - -| AppInsightsCore File | Target otel-core Location | -|----------------------|---------------------------| -| `AppInsightsCore.ts` | `core/AppInsightsCore.ts` | -| `BaseTelemetryPlugin.ts` | `core/BaseTelemetryPlugin.ts` | -| `LoggingEnums.ts` | `enums/AppInsights/LoggingEnums.ts` | -| `DiagnosticLogger.ts` | `diagnostics/DiagnosticLogger.ts` | -| `IConfiguration.ts` | `interfaces/AppInsights/IConfiguration.ts` | -| `DynamicConfig.ts` | `config/DynamicConfig.ts` | -| ... | ... | - -#### 5.3: Move Files in Batches (10-20 files per batch) -**Recommended batch order**: -1. Enums and constants (~5 files) -2. Core interfaces (~15 files) -3. Config interfaces and classes (~10 files) -4. Diagnostic interfaces and classes (~8 files) -5. Core SDK classes (~15 files) -6. Plugin and context classes (~10 files) -7. Remaining utility files - -**For each batch:** - -**Example Batch 1 - Enums and Constants**: -```powershell -# Move enum files -git mv shared/AppInsightsCore/src/JavaScriptSDK.Enums/LoggingEnums.ts shared/otel-core/src/enums/AppInsights/LoggingEnums.ts - -# Move constant files -git mv shared/AppInsightsCore/src/JavaScriptSDK/InternalConstants.ts shared/otel-core/src/constants/CoreInternalConstants.ts -``` - -**Fix imports in moved files immediately**: -```typescript -// In the moved file, update imports -// Before (relative to AppInsightsCore) -import { IConfiguration } from '../JavaScriptSDK.Interfaces/IConfiguration'; - -// After (relative to new location in otel-core) -import { IConfiguration } from '../interfaces/AppInsights/IConfiguration'; -``` - -**Add exports to otel-core/src/index.ts immediately**: -```typescript -// Core SDK classes -export { AppInsightsCore } from './core/AppInsightsCore'; -export { BaseTelemetryPlugin } from './core/BaseTelemetryPlugin'; - -// Logging enums -export { eLoggingSeverity, eInternalMessageId } from './enums/AppInsights/LoggingEnums'; - -// Diagnostics -export { DiagnosticLogger } from './diagnostics/DiagnosticLogger'; - -// Config -export { DynamicConfig, createDynamicConfig } from './config/DynamicConfig'; -``` - -**Compile and validate**: -```powershell -cd shared/otel-core -npm run build -# MUST succeed before proceeding to next batch -``` - -#### 5.4: Move Test Files -**After moving all source files**, move the corresponding test files: - -```powershell -# Create test directory structure if needed -New-Item -ItemType Directory -Force -Path "shared/otel-core/Tests/Unit/AppInsights/Core" - -# Move test files -git mv shared/AppInsightsCore/Tests/Unit/*.tests.ts shared/otel-core/Tests/Unit/AppInsights/Core/ -``` - -**Update test imports**: -```typescript -// Before -import { AppInsightsCore } from '../../../src/JavaScriptSDK/AppInsightsCore'; - -// After (use package import) -import { AppInsightsCore } from '@microsoft/otel-core-js'; -``` - -#### 5.5: Update Imports Across Repository -**Find all files importing from AppInsightsCore**: - -```powershell -grep -r "@microsoft/applicationinsights-core-js" --include="*.ts" --exclude-dir={node_modules,dist,build,temp} . -``` - -**Create PowerShell script to update imports**: -```powershell -$files = Get-ChildItem -Recurse -Include *.ts,*.tsx -Exclude node_modules,dist,build,temp | - Select-String -Pattern '@microsoft/applicationinsights-core-js' -List | - Select-Object -ExpandProperty Path - -foreach ($file in $files) { - $content = Get-Content $file -Raw - $content = $content -replace '@microsoft/applicationinsights-core-js', '@microsoft/otel-core-js' - Set-Content $file $content -NoNewline -} - -Write-Host "Updated $($files.Count) files" -``` - -**Verify**: -```powershell -grep -r "@microsoft/applicationinsights-core-js" --include="*.ts" --exclude-dir={node_modules,dist,build,temp} . -# Should return no matches -``` - -#### 5.6: Update Package Dependencies -**Remove AppInsightsCore from package.json files**: - -In all consuming packages (`AISKU`, `AISKULight`, extensions, channels): -```json -{ - "dependencies": { - // REMOVE: - // "@microsoft/applicationinsights-core-js": "", - - // Already have from Phase 1: - "@microsoft/otel-core-js": "4.0.0-alpha.1" - } -} -``` - -#### 5.7: Update rush.json -**File**: `rush.json` - -Remove the AppInsightsCore entry: -```json -{ - "projects": [ - // REMOVE: - // { - // "packageName": "@microsoft/applicationinsights-core-js", - // "projectFolder": "shared/AppInsightsCore" - // } - ] -} -``` - -#### 5.8: Update gruntfile.js -**File**: `gruntfile.js` - -Remove AppInsightsCore build tasks. - -#### 5.9: Remove AppInsightsCore Package -**Only after all imports are updated and builds succeed**: - -```powershell -# Comprehensive verification - check all file types -grep -r "applicationinsights-core-js" --include="*.ts" --include="*.json" --exclude-dir={node_modules,dist,build,temp} . -# Should be empty - -# Verify no uncommitted changes in package being removed -cd shared/AppInsightsCore -git status -cd ../.. - -# If all clear, remove the package directory -git rm -r shared/AppInsightsCore -``` - -#### 5.10: Run Rush Update and Rebuild -```powershell -# Full update with clean state -rush update --recheck --purge --full - -# Full rebuild to ensure everything compiles correctly -rush rebuild --verbose -``` - -⚠️ **Rush Rebuild**: After major structural changes, `rush rebuild` is safer than `rush build` as it forces a clean build of all packages, catching any missed dependencies or configuration issues. - -#### 5.11: Final Build and Test - Phase 5 Validation -```powershell -# Run final lint-fix -cd shared/otel-core -npm run lint-fix - -# Build otel-core with all merged content -npm run build -# MUST succeed with zero errors - -# Verify browser builds -ls browser/es5/ # Should contain complete built files - -# Build otel-noop-js -cd ../otel-noop-js -npm run build -# MUST succeed with zero errors - -# Build entire repository (rush update + rush rebuild already done in 5.10) -cd ../.. -rush build -# MUST succeed with zero errors - -# Run all tests (including all migrated tests) -rush test -# MUST pass all tests -``` - -#### 5.12: Verify Final State -```powershell -# Verify shared/ directory structure -ls shared/ -# Should show ONLY: -# otel-core/ -# otel-noop-js/ - -# Verify no old package imports remain -grep -r "@microsoft/applicationinsights-core-js\|@microsoft/applicationinsights-common" --include="*.ts" . -# Should return no matches - -# Verify new imports are used -grep -r "@microsoft/otel-core-js" --include="*.ts" | wc -l -# Should show many matches -``` - -**✅ Success Criteria for Phase 5:** *(Completed: January 23, 2026)* -- [x] All AppInsightsCore files moved to otel-core -- [x] All AppInsightsCore tests moved to otel-core -- [x] All imports updated from `@microsoft/applicationinsights-core-js` to `@microsoft/otel-core-js` -- [x] All package.json dependencies updated -- [x] rush.json no longer references AppInsightsCore -- [x] AppInsightsCore directory removed -- [x] otel-core/src/index.ts exports all AppInsightsCore symbols -- [x] `shared/` directory contains ONLY `otel-core/` and `otel-noop/` -- [x] `rush rebuild` succeeds with zero errors (16 operations: 1 SUCCESS, 15 SUCCESS WITH WARNINGS) -- [x] `rush test` - TypeScript compiles successfully -- [x] **MERGE COMPLETE**: 3 packages → 2 packages successfully merged - -**Phase 5 Implementation Summary:** -- Moved all AppInsightsCore source files to otel-core: - - `core/AppInsights/` - Core classes (AppInsightsCore, BaseTelemetryPlugin, CookieMgr, NotificationManager, PerfManager, ProcessTelemetryContext, SenderPostManager, etc.) - - `config/AppInsights/` - Dynamic configuration (DynamicConfig, DynamicProperty, DynamicState, DynamicSupport, ConfigDefaults, ConfigDefaultHelpers) - - `diagnostics/AppInsights/` - DiagnosticLogger, ThrottleMgr -- Moved all AppInsightsCore tests to Tests/Unit/src/AppInsights/Core/ -- Updated otel-core-js.ts entry point with all AppInsightsCore exports -- Fixed rollup bundling issues: Changed 27+ internal source files from importing from barrel file (`../../otel-core-js`) to direct file imports (required by ai-rollup-importcheck plugin) -- Updated package.json dependencies across entire repository from @microsoft/applicationinsights-core-js to @microsoft/otel-core-js -- Updated rush.json - Removed AppInsightsCore project entry -- Updated gruntfile.js - Removed "core" build configuration -- Removed shared/AppInsightsCore and shared/AppInsightsCommon directories -- Full Rush build completed successfully (16 packages built) - ---- - -## Post-Merge Activities - -After completing all 5 phases, perform these final activities: - -### Documentation Updates -1. Update main README.md with new package structure -2. Create migration guide for external users -3. Update API documentation -4. Update CHANGELOG.md - -### Performance Validation -1. Compare bundle sizes (before/after) -2. Verify tree-shaking still works -3. Run performance benchmarks - -### Release Preparation -1. Tag as v4.0.0-alpha.1 -2. Generate release notes -3. Publish alpha packages for testing -4. Communicate changes to stakeholders -## Lessons Learned - -### What Went Well - -1. **Phased Approach**: Breaking work into clear phases made progress trackable -2. **Documentation First**: Creating the merge plan before starting saved time -3. **Test Organization**: Organizing tests by domain (AppInsights/OTel) maintained clarity -4. **Zero Noop Code**: Extracting noop to separate package kept main package lean -5. **Verification Document**: Creating MIGRATION_VERIFICATION.md provided audit trail -6. **Single Gruntfile**: Consolidating build configuration simplified management - -### Challenges Encountered - -1. **Import Path Updates**: Required systematic search-and-replace across all files -2. **Circular Dependencies**: Some files had implicit circular dependencies -3. **Compilation Errors**: Type mismatches required fixing (354 errors initially) -4. **Test Entry Points**: Grunt required specific test entry file structure -5. **Dynamic Constants**: Auto-generated files needed special handling -6. **Monorepo Dependencies**: All consuming packages needed dependency updates -7. **Version Synchronization**: Ensuring all package.json files reference correct version -8. **Build System Updates**: Grunt task registrations and configurations needed cleanup -9. **Import Statement Updates**: 100+ TypeScript files across repository importing from old packages - ---- - -## Implementation-Specific Learnings (Phase 1 & 2) - -### Phase 1: Folder Rename Learnings - -#### 1.1 Version Number Reality Check -⚠️ **CRITICAL**: The existing otel-core-js package version is `0.0.1-alpha`, NOT `4.0.0-alpha.1` as specified in the plan. When implementing: -- Check the ACTUAL version in the existing `package.json` before making changes -- Use the actual version number consistently across all dependencies -- Update the plan's version references to match reality - -#### 1.2 Grunt Task Naming Convention -The gruntfile.js uses a specific naming pattern. When renaming from "openTelemetry" to "otelCore": -```javascript -// Correct pattern - registered tasks must match module name -grunt.registerTask("otelCore", tsBuildActions("otelCore", true)); -grunt.registerTask("otelCore-min", minTasks("otelCore")); -grunt.registerTask("otelCore-restore", restoreTasks("otelCore")); -grunt.registerTask("otelCoreunittest", tsTestActions("otelCore")); // NOTE: No hyphen before unittest -``` - -#### 1.3 Rush Update Behavior -After rush.json changes: -```powershell -# This is the safest approach after structural changes -rush update --recheck --purge --full -``` -If you encounter npm shrinkwrap integrity errors, delete `common/temp/npm-shrinkwrap.json` and run `rush update --full` again. - ---- - -### Phase 2: Noop Extraction Learnings - -#### 2.1 Complete File Movement Required -**ALL noop files MUST be moved to otel-noop-js**, including: -- `src/api/noop/*.ts` - All noop helper files -- `src/interfaces/noop/*.ts` - All noop interfaces -- Corresponding test files - -Do NOT leave any noop implementations in otel-core. Replace with `throwOTelNotImplementedError()`. - -#### 2.2 ES5 Compatibility Issues -otel-core targets ES5 for browser compatibility. Several patterns are FORBIDDEN: - -| ❌ Forbidden Pattern | ✅ Required Alternative | Import Source | -|---------------------|------------------------|---------------| -| `str.trim()` | `strTrim(str)` | `@nevware21/ts-utils` | -| `Object.keys(obj)` | `objKeys(obj)` | `@nevware21/ts-utils` | -| `Date.now()` | `utcNow()` | `@nevware21/ts-utils` | -| `...spread` operator | Explicit copy | N/A | -| `?.` optional chaining | `&&` checks | N/A | -| `??` nullish coalescing | `\|\|` | N/A | - -**Example fixes applied in Phase 2:** -```typescript -// In src/sdk/config.ts -// Before: strName = raw.trim(); -// After: -import { strTrim } from "@nevware21/ts-utils"; -strName = strTrim(raw); - -// In src/sdk/OTelLogRecord.ts -// Before: Object.keys(this._attrs) -// After: -import { objKeys, utcNow } from "@nevware21/ts-utils"; -objKeys(this._attrs); -utcNow(); // Instead of Date.now() -``` - -#### 2.3 Rollup importCheck Plugin Behavior -⚠️ **CRITICAL - MISLEADING NAMING**: The `importCheckNames` array in rollup.config.js specifies imports that should be **BLOCKED**, not excluded from checking. - -```javascript -// In shared/otel-noop/rollup.config.js -// The exclude array BLOCKS these imports from appearing in the bundle -createUnVersionedConfig(banner, {...}, [ "otel-noop-js" ], false) - -// DO NOT include dependencies here: -// WRONG: [ "otel-noop-js", "otel-core-js" ] - This blocks importing from otel-core-js! -// CORRECT: [ "otel-noop-js" ] - Only block self-references -``` - -**How it works:** -- If a file imports from `@microsoft/otel-core-js`, that's a valid external dependency -- The importCheck plugin should NOT block it -- Only block imports from the package's own name (self-references) - -#### 2.4 api-extractor.json bundledPackages Format -The `dtsgen.js` script uses a regex that requires at least one character between the brackets: - -```json -// ❌ WRONG - regex fails to match -"bundledPackages": [], - -// ✅ CORRECT - whitespace between brackets allows regex to match -"bundledPackages": [ - ], -``` - -**Why:** The script's regex pattern `[^\]]+` requires at least one character (including whitespace) between `[` and `]`. - -#### 2.5 Type Bundling Causes Type Incompatibility -⚠️ **CRITICAL**: Do NOT bundle types from otel-core-js into otel-noop-js. - -```json -// ❌ WRONG - causes type incompatibility -"bundledPackages": [ - "@microsoft/otel-core-js" -], - -// ✅ CORRECT - import types instead of bundling -"bundledPackages": [ - ], -``` - -**Why:** If both packages bundle the same types, TypeScript sees them as different types, causing: -``` -Type 'import("otel-noop-js").IOTelLogger' is not assignable to -type 'import("otel-core-js").IOTelLogger' -``` - -**Solution:** Leave bundledPackages empty so types are imported with `import type { IOTelLogger } from '@microsoft/otel-core-js'` - -#### 2.6 Test File Organization -Test files must follow the grunt pattern. The entry point must be named `index.tests.ts`: - -``` -shared/otel-noop/Tests/ - └── Unit/ - └── src/ - ├── index.tests.ts # Main entry point (required by grunt) - └── sdk/ - ├── OTelLogger.Tests.ts - └── OTelLoggerProvider.Tests.ts -``` - -#### 2.7 Const Enum Runtime Availability -Const enums like `eW3CTraceFlags` are NOT available at runtime. In test files, use literal values: - -```typescript -// ❌ WRONG - const enum not available at runtime -import { eW3CTraceFlags } from "@microsoft/otel-core-js"; -span.traceFlags = eW3CTraceFlags.Sampled; - -// ✅ CORRECT - use literal constant -const W3C_TRACE_FLAG_SAMPLED = 1; -span.traceFlags = W3C_TRACE_FLAG_SAMPLED; -``` - -#### 2.8 NPM Shrinkwrap Integrity Issues -After adding new package dependencies, you may encounter integrity check failures: - -```powershell -# If you see: "Integrity checked failed for resolved" -# Solution: Delete temp shrinkwrap and regenerate -Remove-Item "common/temp/npm-shrinkwrap.json" -Force -rush update --full -``` - -#### 2.9 Dependency Direction -The dependency direction MUST be: -``` -otel-noop-js → depends on → otel-core-js -``` - -otel-core-js must NOT depend on otel-noop-js. If otel-core needs noop behavior, it should throw NotImplemented exceptions. - -#### 2.10 Consuming Package Updates -Any packages that import noop functionality from otel-core-js must: -1. Add `@microsoft/otel-noop-js` as a dependency -2. Update imports to use the new package - -Example for `shared/AppInsightsCore/package.json`: -```json -{ - "dependencies": { - "@microsoft/otel-core-js": "0.0.1-alpha", - "@microsoft/otel-noop-js": "0.0.1-alpha" // ADD THIS - } -} -``` - -Example import update in `src/OTelApi.ts`: -```typescript -// Before -import { createNoopTracerProvider } from "@microsoft/otel-core-js"; - -// After -import { createNoopTracerProvider } from "@microsoft/otel-noop-js"; -``` - ---- - -### Phase 4 & 5: AppInsightsCommon and AppInsightsCore Merge Learnings - -#### 4.1 Barrel Import Blocking by Rollup -⚠️ **CRITICAL**: The `ai-rollup-importcheck` plugin blocks imports from barrel/index files within internal source files. This means files INSIDE otel-core cannot import from the main entry point (`../../otel-core-js`). - -```typescript -// ❌ WRONG - Blocked by rollup importcheck plugin -import { IConfiguration, IDiagnosticLogger } from "../../otel-core-js"; - -// ✅ CORRECT - Direct imports from actual source files -import { IConfiguration } from "../../interfaces/AppInsights/IConfiguration"; -import { IDiagnosticLogger } from "../../interfaces/AppInsights/IDiagnosticLogger"; -``` - -**Files that needed fixing (27+ files):** -- `core/AppInsights/AppInsightsCore.ts` -- `core/AppInsights/BaseTelemetryPlugin.ts` -- `core/AppInsights/SenderPostManager.ts` -- `config/AppInsights/DynamicConfig.ts` -- `diagnostics/AppInsights/DiagnosticLogger.ts` -- All `otel/sdk/*.ts` files -- Many others - -**How to identify**: Look for rollup error messages like: -``` -[!] (plugin ai-rollup-importcheck) Error: Invalid Import detected [import {...} from "../../otel-core-js"] -``` - -#### 4.2 Symbol-to-Source-File Mapping -When fixing barrel imports, you need to map each symbol to its actual source file. Common mappings: - -| Symbol Category | Source Location | -|----------------|-----------------| -| Interfaces (I*) | `interfaces/AppInsights/*.ts` or `interfaces/OTel/**/*.ts` | -| Enums (e*) | `enums/AppInsights/*.ts` or `enums/OTel/**/*.ts` | -| Utility functions | `utils/AppInsights/*.ts` | -| Config classes | `config/AppInsights/*.ts` | -| Constants (STR_*) | `constants/InternalConstants.ts` | -| OTel functions | `otel/api/**/*.ts` | - -#### 4.3 Version Consistency in Package Dependencies -When updating package.json files across the repository: - -```powershell -# Find all package.json files referencing old packages -Get-ChildItem -Recurse -Filter "package.json" | Select-String -Pattern "applicationinsights-core-js|applicationinsights-common" - -# Ensure version matches actual otel-core-js version (0.0.1-alpha) -# NOT a guessed version like 3.3.11 -``` - -#### 4.4 Duplicate Dependency Removal -When replacing both `applicationinsights-core-js` AND `applicationinsights-common` with `otel-core-js`, you may create duplicate entries: - -```json -// ❌ WRONG - duplicate entries after replacement -{ - "dependencies": { - "@microsoft/otel-core-js": "0.0.1-alpha", - "@microsoft/otel-core-js": "0.0.1-alpha" - } -} - -// ✅ CORRECT - single entry -{ - "dependencies": { - "@microsoft/otel-core-js": "0.0.1-alpha" - } -} -``` - -**Fix with regex replacement** that removes duplicates. - -#### 4.5 Test File Import Updates -Test files should use relative imports to the source, not package imports (since they're in the same package): - -```typescript -// In Tests/Unit/src/AppInsights/Core/SomeTest.ts -// ✅ CORRECT - relative import to source -import { AppInsightsCore } from "../../../../../src/otel-core-js"; - -// ❌ AVOID in internal tests - package import -import { AppInsightsCore } from "@microsoft/otel-core-js"; -``` - -#### 4.6 Rush Update After Major Changes -After removing packages from rush.json: - -```powershell -# Delete shrinkwrap to avoid integrity errors -Remove-Item "common/temp/npm-shrinkwrap.json" -Force -ErrorAction SilentlyContinue - -# Full clean update -rush update --recheck --purge --full -``` - -#### 4.7 Gruntfile Cleanup -When removing packages, also remove their grunt task registrations: - -```javascript -// REMOVE entries like: -grunt.registerTask("core", tsBuildActions("core")); -grunt.registerTask("coreunittest", tsTestActions("core")); -grunt.registerTask("core-mintest", tsTestActions("core", true)); -``` - ---- - -### Best Practices for Future Merges - -1. **Create Verification Checklist**: Track file counts before/after -2. **Use Migration Scripts**: Automate repetitive tasks (import updates) -3. **Test Incrementally**: Don't wait until end to run tests -4. **Document Decisions**: Capture rationale for structural choices -5. **Keep Original Packages**: Don't delete until migration verified -6. **Update Build System Early**: Integrate with build tools first -7. **Create Clear Documentation**: README, MIGRATION, and API docs are critical -8. **Update All Dependencies**: Search entire monorepo for package references -9. **Maintain Version Consistency**: All dependency versions must match actual package versions -10. **Update Build Configuration**: Clean up task registrations and remove redundant entries -11. **Update Import Statements**: Use automated script to update all source file imports -12. **Run Dependency Manager**: Execute `rush update` or equivalent after all changes -13. **⚠️ CRITICAL: Export As You Go**: Add exports to index.ts DURING file migration, not after -14. **BANNED - Wildcard Imports/Exports**: Never use `export * from` or `import * as`. Only explicit exports allowed. No directory-level index.ts files. -15. **Compile Incrementally**: Run build after each file group migration to catch errors early -16. **Fix Path Casing Early**: Ensure consistent casing in imports (Config vs config, etc.) -17. **Run Lint-Fix After Moves**: Execute `npm run lint-fix` from each package after phase completion. The script calls a grunt task (e.g., `otelCore-lint-fix`) that runs the restore task to apply consistent formatting. -18. **Handle Dynamic Constants**: Auto-generated files (like InternalConstants.ts) should not be edited manually - update generation scripts instead -19. **Version Placeholders**: Don't manually replace `"#version#"` placeholders - grunt handles this during build -20. **Use Rush Rebuild**: After major changes, prefer `rush rebuild --verbose` over `rush build` for clean state -21. **ES5 Compatibility**: Use `@nevware21/ts-utils` helpers (strTrim, objKeys, utcNow) instead of native methods -22. **Rollup importCheck**: The importCheckNames array BLOCKS imports - only include self-references, not dependencies -23. **api-extractor bundledPackages**: Use whitespace format `[\n ]` for empty arrays (regex requirement) -24. **Type Bundling**: Do NOT bundle dependency types - causes type incompatibility across packages -25. **Test Entry Point Naming**: Entry point must be `index.tests.ts` to match grunt pattern -26. **Const Enums in Tests**: Use literal values instead of const enums (not available at runtime) -27. **NPM Shrinkwrap Issues**: Delete `common/temp/npm-shrinkwrap.json` and run `rush update --full` if integrity errors occur - -## Common Patterns for Branch Merges - -### Pattern 1: Consolidating Packages - -**When to Use:** -- Multiple packages with overlapping functionality -- Want to reduce maintenance burden -- Need clearer organizational structure - -**Steps:** -1. Analyze dependencies and file relationships -2. Design target folder structure -3. Create migration plan with file mappings -4. Implement in phases (setup → move → test → cleanup) -5. Document thoroughly - -### Pattern 2: Reorganizing by Functionality - -**When to Use:** -- Historical structure doesn't match current use -- Want better discoverability -- Need clearer separation of concerns - -**Steps:** -1. Group files by functionality (not by origin) -2. Create clear subdirectories (interfaces/, enums/, etc.) -3. Maintain domain separation (AppInsights vs OTel) -4. Use consistent naming conventions -5. Update all import paths systematically - -### Pattern 3: Extracting Optional Code - -**When to Use:** -- Have code used only in specific scenarios (testing, dev) -- Want to reduce main package size -- Need clear dependency boundaries - -**Steps:** -1. Identify optional functionality (noop, test utils, etc.) -2. Create separate package structure -3. Replace with stubs that throw clear errors -4. Configure separate package to depend on main package -5. Update documentation about optional dependencies - -## Replication Guide - -### For Merging Similar Projects - -1. **Preparation Phase** - - [ ] Audit source packages (file counts, dependencies) - - [ ] Design target structure - - [ ] Create merge plan document - - [ ] Set up version control branch - -2. **Setup Phase** - - [ ] Create target package directory - - [ ] Set up package.json with combined dependencies - - [ ] Configure build tools (TypeScript, Rollup, Grunt) - - [ ] Create npm scripts - -3. **Migration Phase** - - [ ] Move interfaces first (dependencies for other files) - - [ ] Move enums and types - - [ ] Move core functionality - - [ ] Move utilities and helpers - - [ ] Update all import paths - - [ ] Move and update tests - -4. **Integration Phase** - - [ ] Update build configuration - - [ ] Verify compilation succeeds - - [ ] Run all tests - - [ ] Fix any circular dependencies - - [ ] Generate API documentation - -5. **Documentation Phase** - - [ ] Create/update README.md - - [ ] Create MIGRATION.md guide - - [ ] Create API.md reference - - [ ] Document breaking changes - -6. **Cleanup Phase** - - [ ] Verify all files migrated - - [ ] Remove original packages - - [ ] Remove temporary scripts - - [ ] Update rush.json (remove old packages, add new ones) - - [ ] Update all package.json dependencies across monorepo - - [ ] Verify version consistency (all references match actual package version) - - [ ] Update gruntfile.js (remove old package configurations and tasks) - - [ ] **Update all import statements** (run fix-imports.ps1 script) - - [ ] Verify no old imports remain - - [ ] Run `rush update` to regenerate dependency graph - - [ ] Update CI/CD configuration - - [ ] Update consuming packages - -### For Merging from Other Branches - -1. **Assessment** - - Compare file structures between branches - - Identify new files in other branch - - Identify modified files - - Check for conflicts - -2. **Strategy Selection** - - **If other branch has old structure**: Cherry-pick changes and reorganize - - **If other branch has new structure**: Standard merge with conflict resolution - - **If mixed**: Migrate old structure files first, then merge - -3. **Execution** - ```bash - # Create merge branch - git checkout -b merge-branch-name - - # If reorganizing needed - # 1. Copy new files to correct locations - # 2. Update import paths - # 3. Test compilation - - # Standard merge - git merge other-branch - - # Resolve conflicts using new structure - # Test thoroughly - ``` - -## File Migration Checklist Template - -Use this checklist for future migrations: - -```markdown -### Package: [NAME] - -**Source:** `[path]` -**Target:** `[path]` -**Files:** [count] - -- [ ] Interfaces → `src/interfaces/[domain]/` -- [ ] Enums → `src/enums/[domain]/` -- [ ] Types → `src/types/[domain]/` -- [ ] Core functionality → `src/core/` or domain folder -- [ ] Utilities → `src/utils/` -- [ ] Constants → `src/constants/` -- [ ] Tests → `Tests/Unit/[domain]/` -- [ ] Update imports in moved files -- [ ] Verify compilation -- [ ] Verify tests pass -``` - -## Success Metrics - -**Target Metrics (Achieved):** -- ✅ Single unified package (from 3) -- ✅ 273 source files (from 278) -- ✅ Zero noop code in main package -- ✅ All tests passing -- ✅ Build succeeds -- ✅ Documentation complete -- ✅ Migration guide available - -**Quality Metrics:** -- Code organization: ✅ Logical by functionality -- Separation of concerns: ✅ Clear AppInsights vs OTel -- Tree-shaking: ✅ Side-effect-free exports -- Documentation: ✅ Comprehensive (README, MIGRATION, API) -- Test coverage: ✅ All original tests maintained - -## Version History - -- **v4.0.0-alpha.1** (January 15, 2026): Initial unified package release - - Merged AppInsightsCore, AppInsightsCommon, and OpenTelemetry - - Reorganized by functionality - - Zero noop code (moved to otel-noop-js) - - Comprehensive documentation - -## References - -### Created Documents -- [README.md](shared/otel-core/README.md) - Package documentation -- [MIGRATION.md](shared/otel-core/MIGRATION.md) - User migration guide -- [API.md](shared/otel-core/API.md) - API reference -- [MIGRATION_VERIFICATION.md](shared/otel-core/MIGRATION_VERIFICATION.md) - Audit trail -- [typedoc.json](shared/otel-core/typedoc.json) - API doc generation - -### Original Packages (Removed) -- ~~shared/AppInsightsCore~~ → otel-core -- ~~shared/AppInsightsCommon~~ → otel-core -- ~~shared/OpenTelemetry~~ → otel-core - -### New Packages -- shared/otel-core (`@microsoft/otel-core-js` - unified package) -- shared/otel-noop (`@microsoft/otel-noop-js` - noop implementations) - ---- - -**Last Updated:** January 23, 2026 -**Status:** ✅ **ALL PHASES COMPLETE** - Merge Successfully Finished - -### Final State: -- `@microsoft/otel-core-js` - Unified core package containing all merged code from AppInsightsCore, AppInsightsCommon, and OpenTelemetry -- `@microsoft/otel-noop-js` - Separated noop implementations -- All 16 downstream packages build successfully -- Original packages removed: AppInsightsCore, AppInsightsCommon diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/Sample.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/Sample.tests.ts index 0269a2c90..ee0d90140 100644 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/Sample.tests.ts +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/Sample.tests.ts @@ -1,11 +1,11 @@ import { AITestClass } from "@microsoft/ai-test-framework"; -import { Sample } from "../../../src/TelemetryProcessors/Sample"; +import { createSampler } from "../../../src/TelemetryProcessors/Sample"; import { ITelemetryItem, isBeaconsSupported, newId } from "@microsoft/otel-core-js"; -import { PageView, TelemetryItemCreator, IPageViewTelemetry } from "@microsoft/otel-core-js"; -import { HashCodeScoreGenerator } from "../../../src/TelemetryProcessors/SamplingScoreGenerators/HashCodeScoreGenerator"; +import { TelemetryItemCreator, IPageViewTelemetry, PageViewDataType, PageViewEnvelopeType, ISample } from "@microsoft/otel-core-js"; +import { getHashCodeScore } from "../../../src/TelemetryProcessors/SamplingScoreGenerators/HashCodeScoreGenerator"; export class SampleTests extends AITestClass { - private sample: Sample + private sample: ISample; private item: ITelemetryItem; public testInitialize() { @@ -26,9 +26,9 @@ export class SampleTests extends AITestClass { this.testCase({ name: 'Sampling: isSampledIn returns true for 100 sampling rate', test: () => { - this.sample = new Sample(100); + this.sample = createSampler(100); this.item = this.getTelemetryItem(); - const scoreStub = this.sandbox.stub(this.sample["samplingScoreGenerator"], "getSamplingScore"); + const scoreStub = this.sandbox.stub(this.sample["generator"], "getScore"); QUnit.assert.ok(this.sample.isSampledIn(this.item)); QUnit.assert.ok(scoreStub.notCalled); @@ -38,7 +38,7 @@ export class SampleTests extends AITestClass { this.testCase({ name: 'Sampling: hashing is based on user id even if operation id is provided', test: () => { - this.sample = new Sample(33); + this.sample = createSampler(33); const userid = "asdf"; @@ -60,7 +60,7 @@ export class SampleTests extends AITestClass { this.testCase({ name: 'Sampling: hashing is based on operation id if no user id is provided', test: () => { - this.sample = new Sample(33); + this.sample = createSampler(33); const operationId = "operation id"; const item1 = this.getTelemetryItem(); @@ -95,7 +95,7 @@ export class SampleTests extends AITestClass { name: 'Sampling: hashing is random if no user id nor operation id provided', test: () => { // setup - this.sample = new Sample(33); + this.sample = createSampler(33); const envelope1 = this.getTelemetryItem(); envelope1.tags["ai.user.id"] = null; @@ -127,11 +127,10 @@ export class SampleTests extends AITestClass { // act sampleRates.forEach((sampleRate) => { - const sut = new HashCodeScoreGenerator(); let countOfSampledItems = 0; ids.forEach((id) => { - if (sut.getHashCodeScore(id) < sampleRate) {++countOfSampledItems; } + if (getHashCodeScore(id) < sampleRate) {++countOfSampledItems; } }); // Assert @@ -147,7 +146,7 @@ export class SampleTests extends AITestClass { return TelemetryItemCreator.create({ name: 'some page', uri: 'some uri' - }, PageView.dataType, PageView.envelopeType, null); + }, PageViewDataType, PageViewEnvelopeType, null); } private getMetricItem(): ITelemetryItem { diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts index e1b8ae0d2..a566c276e 100644 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts @@ -1,14 +1,17 @@ import { AITestClass, PollingAssert } from "@microsoft/ai-test-framework"; import { Sender } from "../../../src/Sender"; -import { IOfflineListener, createOfflineListener, utlGetSessionStorageKeys, utlRemoveSessionStorage } from "@microsoft/otel-core-js"; -import { EnvelopeCreator } from '../../../src/EnvelopeCreator'; -import { Exception, CtxTagKeys, isBeaconApiSupported, DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, utlCanUseSessionStorage, utlGetSessionStorage, utlSetSessionStorage } from "@microsoft/otel-core-js"; -import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, NotificationManager, SendRequestReason, _eInternalMessageId, safeGetLogger, isString, isArray, arrForEach, isBeaconsSupported, IXHROverride, IPayloadData,TransportType, getWindow, ActiveStatus } from "@microsoft/otel-core-js"; +import { ExceptionDataType, IOfflineListener, createOfflineListener, utlGetSessionStorageKeys, utlRemoveSessionStorage } from "@microsoft/otel-core-js"; +import { EnvelopeCreator } from "../../../src/EnvelopeCreator"; +import { + Exception, CtxTagKeys, DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, utlCanUseSessionStorage, utlGetSessionStorage, utlSetSessionStorage, + ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, NotificationManager, SendRequestReason, _eInternalMessageId, safeGetLogger, + isString, isArray, arrForEach, isBeaconsSupported, IXHROverride, IPayloadData,TransportType, getWindow, ActiveStatus +} from "@microsoft/otel-core-js"; import { ArraySendBuffer, SessionStorageSendBuffer } from "../../../src/SendBuffer"; import { IInternalStorageItem, ISenderConfig } from "../../../src/Interfaces"; import { createAsyncResolvedPromise } from "@nevware21/ts-async"; import { isPromiseLike, isUndefined } from "@nevware21/ts-utils"; -import { SinonSpy } from 'sinon'; +import { SinonSpy } from "sinon"; const BUFFER_KEY = "AI_buffer_1"; @@ -1754,7 +1757,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -1798,7 +1801,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -1871,7 +1874,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -1944,7 +1947,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -2018,7 +2021,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -2091,7 +2094,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -2143,7 +2146,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -2217,7 +2220,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -2290,7 +2293,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -2356,7 +2359,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2443,7 +2446,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2533,7 +2536,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2620,7 +2623,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2719,7 +2722,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2814,7 +2817,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2920,7 +2923,7 @@ export class SenderTests extends AITestClass { let buffer = sender._buffer; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear"); @@ -2987,7 +2990,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -3038,7 +3041,7 @@ export class SenderTests extends AITestClass { baseData: {} }; - QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before"); QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before"); @@ -3755,7 +3758,7 @@ export class SenderTests extends AITestClass { name: "test", time: new Date("2018-06-12").toISOString(), iKey: "iKey", - baseType: Exception.dataType, + baseType: ExceptionDataType, baseData: bd, data: { "property3": "val3", @@ -4416,7 +4419,7 @@ export class SenderTests extends AITestClass { name: "test", time: new Date("2018-06-12").toISOString(), iKey: "iKey", - baseType: Exception.dataType, + baseType: ExceptionDataType, baseData: bd, data: { "property3": "val3", diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts index 00828e8d5..2efff023e 100644 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts @@ -3,7 +3,7 @@ // import { Sender } from "../../../src/Sender"; // import { SinonSpy, SinonStub } from "sinon"; // import { ISenderConfig } from "../../../types/applicationinsights-channel-js"; -// import { isBeaconApiSupported } from "@microsoft/otel-core-js"; +// import { isBeaconsSupported } from "@microsoft/otel-core-js"; // export class StatsbeatTests extends AITestClass { // private _core: AppInsightsCore; @@ -270,7 +270,7 @@ // sendBeaconCalled = true; // return true; // }); -// QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); +// QUnit.assert.ok(isBeaconsSupported(), "Beacon API is supported"); // this.processTelemetryAndFlush(sender, telemetryItem); // } // ].concat(PollingAssert.createPollingAssert(() => { diff --git a/channels/applicationinsights-channel-js/package.json b/channels/applicationinsights-channel-js/package.json index bbe783c9c..f1764b385 100644 --- a/channels/applicationinsights-channel-js/package.json +++ b/channels/applicationinsights-channel-js/package.json @@ -58,8 +58,8 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "license": "MIT" } diff --git a/channels/applicationinsights-channel-js/src/EnvelopeCreator.ts b/channels/applicationinsights-channel-js/src/EnvelopeCreator.ts index c7549ce0a..c749118c4 100644 --- a/channels/applicationinsights-channel-js/src/EnvelopeCreator.ts +++ b/channels/applicationinsights-channel-js/src/EnvelopeCreator.ts @@ -1,10 +1,17 @@ import { - CtxTagKeys, Data, Envelope, Event, Exception, HttpMethod, IDependencyTelemetry, IDiagnosticLogger, IEnvelope, IExceptionInternal, - IPageViewPerformanceTelemetry, IPageViewTelemetryInternal, ITelemetryItem, IWeb, Metric, PageView, PageViewPerformance, - RemoteDependencyData, SampleRate, Trace, _eInternalMessageId, _throwInternal, _warnToConsole, dataSanitizeString, eLoggingSeverity, - getJSON, hasJSON, isNullOrUndefined, isNumber, isString, isTruthy, objForEachKey, optimizeObject, setValue, toISOString + AIData, CtxTagKeys, Envelope, Event, EventDataType, EventEnvelopeType, Exception, ExceptionDataType, ExceptionEnvelopeType, HttpMethod, + IDependencyTelemetry, IDiagnosticLogger, IEnvelope, IExceptionInternal, IPageViewPerformanceTelemetry, IPageViewTelemetryInternal, + IRemoteDependencyData, IRequestTelemetry, ISerializable, ITelemetryItem, IWeb, Metric, MetricDataType, MetricEnvelopeType, PageView, + PageViewDataType, PageViewEnvelopeType, PageViewPerformance, PageViewPerformanceDataType, PageViewPerformanceEnvelopeType, + RemoteDependencyDataType, RequestDataType, SampleRate, Tags, Trace, TraceDataType, TraceEnvelopeType, _eInternalMessageId, + _throwInternal, _warnToConsole, dataSanitizeString, eLoggingSeverity, getJSON, hasJSON, isDate, isNullOrUndefined, isNumber, isString, + isTruthy, objForEachKey, optimizeObject, setValue, toISOString } from "@microsoft/otel-core-js"; +import { IRequestData } from "./Interfaces/Contracts/IRequestData"; import { STR_DURATION } from "./InternalConstants"; +import { _createData } from "./Telemetry/Common/Data"; +import { RemoteDependencyEnvelopeType, createRemoteDependencyData } from "./Telemetry/RemoteDependencyData"; +import { createRequestData } from "./Telemetry/RequestData"; // these two constants are used to filter out properties not needed when trying to extract custom properties and measurements from the incoming payload const strBaseType = "baseType"; @@ -23,7 +30,7 @@ function _extractPartAExtensions(logger: IDiagnosticLogger, item: ITelemetryItem // todo: switch to keys from common in this method let envTags = env.tags = env.tags || {}; let itmExt = item.ext = item.ext || {}; - let itmTags = item.tags = item.tags || []; + let itmTags = item.tags = item.tags || {} as Tags; let extUser = itmExt.user; if (extUser) { @@ -92,7 +99,7 @@ function _extractPartAExtensions(logger: IDiagnosticLogger, item: ITelemetryItem // ] // } - const tgs = {}; + const tgs: Tags = {}; // deals with tags.push({object}) for(let i = itmTags.length - 1; i >= 0; i--){ const tg = itmTags[i]; @@ -140,15 +147,18 @@ function _convertPropsUndefinedToCustomDefinedValue(properties: { [key: string]: } // TODO: Do we want this to take logger as arg or use this._logger as nonstatic? -function _createEnvelope(logger: IDiagnosticLogger, envelopeType: string, telemetryItem: ITelemetryItem, data: Data): IEnvelope { +function _createEnvelope(logger: IDiagnosticLogger, envelopeType: string, telemetryItem: ITelemetryItem, data: AIData): IEnvelope { const envelope = new Envelope(logger, data, envelopeType); - _setValueIf(envelope, "sampleRate", telemetryItem[SampleRate]); - if ((telemetryItem[strBaseData] || {}).startTime) { + _setValueIf(envelope, "sampleRate", (telemetryItem as any)[SampleRate]); + + let startTime = (telemetryItem[strBaseData] || {}).startTime; + if (isDate(startTime)) { // Starting from Version 3.0.3, the time property will be assigned by the startTime value, // which records the loadEvent time for the pageView event. - envelope.time = toISOString(telemetryItem[strBaseData].startTime); + envelope.time = toISOString(startTime); } + envelope.iKey = telemetryItem.iKey; const iKeyNoDashes = telemetryItem.iKey.replace(/-/g, ""); envelope.name = envelope.name.replace("{0}", iKeyNoDashes); @@ -190,21 +200,37 @@ export function DependencyEnvelopeCreator(logger: IDiagnosticLogger, telemetryIt } const method = bd[strProperties] && bd[strProperties][HttpMethod] ? bd[strProperties][HttpMethod] : "GET"; - const remoteDepData = new RemoteDependencyData(logger, bd.id, bd.target, bd.name, bd.duration, bd.success, bd.responseCode, method, bd.type, bd.correlationContext, customProperties, customMeasurements); - const data = new Data(RemoteDependencyData.dataType, remoteDepData); - return _createEnvelope(logger, RemoteDependencyData.envelopeType, telemetryItem, data); + const remoteDepData = createRemoteDependencyData(logger, bd.id, bd.target, bd.name, bd.duration, bd.success, bd.responseCode, method, bd.type, bd.correlationContext, customProperties, customMeasurements); + const data = _createData(RemoteDependencyDataType, remoteDepData); + return _createEnvelope(logger, RemoteDependencyEnvelopeType, telemetryItem, data); } +export function RequestEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { + EnvelopeCreatorInit(logger, telemetryItem); + + const customMeasurements = telemetryItem[strBaseData].measurements || {}; + const customProperties = telemetryItem[strBaseData][strProperties] || {}; + _extractPropsAndMeasurements(telemetryItem.data, customProperties, customMeasurements); + if (!isNullOrUndefined(customUndefinedValue)) { + _convertPropsUndefinedToCustomDefinedValue(customProperties, customUndefinedValue); + } + const bd = telemetryItem[strBaseData] as IRequestTelemetry; + const requestData = createRequestData(logger, bd.id, bd.name, bd.duration, bd.success, bd.responseCode, bd.source, bd.url, customProperties, customMeasurements); + const data = _createData(RequestDataType, requestData); + return _createEnvelope(logger, RequestDataType, telemetryItem, data); +} + + export function EventEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { EnvelopeCreatorInit(logger, telemetryItem); - let customProperties = {}; + let customProperties = {} as { [key: string]: any }; let customMeasurements = {}; - if (telemetryItem[strBaseType] !== Event.dataType) { - customProperties["baseTypeSource"] = telemetryItem[strBaseType]; // save the passed in base type as a property + if (telemetryItem[strBaseType] !== EventDataType) { + (customProperties as any)["baseTypeSource"] = telemetryItem[strBaseType]; // save the passed in base type as a property } - if (telemetryItem[strBaseType] === Event.dataType) { // take collection + if (telemetryItem[strBaseType] === EventDataType) { // take collection customProperties = telemetryItem[strBaseData][strProperties] || {}; customMeasurements = telemetryItem[strBaseData].measurements || {}; } else { // if its not a known type, convert to custom event @@ -220,8 +246,8 @@ export function EventEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: I } const eventName = telemetryItem[strBaseData].name; const eventData = new Event(logger, eventName, customProperties, customMeasurements); - const data = new Data(Event.dataType, eventData); - return _createEnvelope(logger, Event.envelopeType, telemetryItem, data); + const data = _createData(EventDataType, eventData); + return _createEnvelope(logger, EventEnvelopeType, telemetryItem, data); } export function ExceptionEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { @@ -236,8 +262,8 @@ export function ExceptionEnvelopeCreator(logger: IDiagnosticLogger, telemetryIte } const bd = telemetryItem[strBaseData] as IExceptionInternal; const exData = Exception.CreateFromInterface(logger, bd, customProperties, customMeasurements); - const data = new Data(Exception.dataType, exData); - return _createEnvelope(logger, Exception.envelopeType, telemetryItem, data); + const data = _createData(ExceptionDataType, exData); + return _createEnvelope(logger, ExceptionEnvelopeType, telemetryItem, data); } export function MetricEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { @@ -251,8 +277,8 @@ export function MetricEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: _convertPropsUndefinedToCustomDefinedValue(props, customUndefinedValue); } const baseMetricData = new Metric(logger, baseData.name, baseData.average, baseData.sampleCount, baseData.min, baseData.max, baseData.stdDev, props, measurements); - const data = new Data(Metric.dataType, baseMetricData); - return _createEnvelope(logger, Metric.envelopeType, telemetryItem, data); + const data = _createData(MetricDataType, baseMetricData); + return _createEnvelope(logger, MetricEnvelopeType, telemetryItem, data); } export function PageViewEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { @@ -313,8 +339,8 @@ export function PageViewEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem _convertPropsUndefinedToCustomDefinedValue(properties, customUndefinedValue); } const pageViewData = new PageView(logger, name, url, duration, properties, measurements, id); - const data = new Data(PageView.dataType, pageViewData); - return _createEnvelope(logger, PageView.envelopeType, telemetryItem, data); + const data = _createData(PageViewDataType, pageViewData); + return _createEnvelope(logger, PageViewEnvelopeType, telemetryItem, data); } export function PageViewPerformanceEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { @@ -330,8 +356,8 @@ export function PageViewPerformanceEnvelopeCreator(logger: IDiagnosticLogger, te _convertPropsUndefinedToCustomDefinedValue(properties, customUndefinedValue); } const baseData = new PageViewPerformance(logger, name, url, undefined, properties, measurements, bd); - const data = new Data(PageViewPerformance.dataType, baseData); - return _createEnvelope(logger, PageViewPerformance.envelopeType, telemetryItem, data); + const data = _createData(PageViewPerformanceDataType, baseData); + return _createEnvelope(logger, PageViewPerformanceEnvelopeType, telemetryItem, data); } export function TraceEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope { @@ -346,6 +372,6 @@ export function TraceEnvelopeCreator(logger: IDiagnosticLogger, telemetryItem: I _convertPropsUndefinedToCustomDefinedValue(props, customUndefinedValue); } const baseData = new Trace(logger, message, severityLevel, props, measurements); - const data = new Data(Trace.dataType, baseData); - return _createEnvelope(logger, Trace.envelopeType, telemetryItem, data); + const data = _createData(TraceDataType, baseData); + return _createEnvelope(logger, TraceEnvelopeType, telemetryItem, data); } diff --git a/channels/applicationinsights-channel-js/src/Interfaces/Contracts/IRequestData.ts b/channels/applicationinsights-channel-js/src/Interfaces/Contracts/IRequestData.ts new file mode 100644 index 000000000..cd0853399 --- /dev/null +++ b/channels/applicationinsights-channel-js/src/Interfaces/Contracts/IRequestData.ts @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IDomain } from "@microsoft/otel-core-js"; + +/** + * This interface indentifies the serialized request data contract that is sent to Application Insights backend + */ +export interface IRequestData extends IDomain { + + /** + * Schema version + */ + ver: number; /* 2 */ + + /** + * Identifier of a request call instance. Used for correlation between request and other telemetry items. + */ + id: string; + + /** + * Name of the request. Represents code path taken to process request. Low cardinality value to allow better grouping of requests. For HTTP requests it represents the HTTP method and URL path template like 'GET /values/\{id\}'. + */ + name?: string; + + /** + * Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. + */ + duration: string; + + /** + * Indication of successful or unsuccessful call. + */ + success: boolean; + + /** + * Result of a request execution. HTTP status code for HTTP requests. + */ + responseCode?: string; + + /** + * Source of the request. Examples are the instrumentation key of the caller or the ip address of the caller. + */ + source?: string; + + /** + * Request URL with all query string parameters. + */ + url?: string; + + /** + * Collection of custom properties. + */ + properties?: { [propertyName: string]: string }; /* \{\} */ + + /** + * Collection of custom measurements. + */ + measurements?: { [propertyName: string]: number }; /* \{\} */ +} diff --git a/channels/applicationinsights-channel-js/src/Sender.ts b/channels/applicationinsights-channel-js/src/Sender.ts index 114297252..8a4944b55 100644 --- a/channels/applicationinsights-channel-js/src/Sender.ts +++ b/channels/applicationinsights-channel-js/src/Sender.ts @@ -1,16 +1,16 @@ import dynamicProto from "@microsoft/dynamicproto-js"; import { - ActiveStatus, BaseTelemetryPlugin, BreezeChannelIdentifier, DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, Event, Exception, - IAppInsightsCore, IBackendResponse, IChannelControls, IConfig, IConfigDefaults, IConfiguration, IDiagnosticLogger, IEnvelope, - IInternalOfflineSupport, INotificationManager, IOfflineListener, IPayloadData, IPlugin, IProcessTelemetryContext, + ActiveStatus, BaseTelemetryPlugin, BreezeChannelIdentifier, DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, EventDataType, + ExceptionDataType, IAppInsightsCore, IBackendResponse, IChannelControls, IConfig, IConfigDefaults, IConfiguration, IDiagnosticLogger, + IEnvelope, IInternalOfflineSupport, INotificationManager, IOfflineListener, IPayloadData, IPlugin, IProcessTelemetryContext, IProcessTelemetryUnloadContext, ISample, IStorageBuffer, ITelemetryItem, ITelemetryPluginChain, ITelemetryUnloadState, IXDomainRequest, - IXHROverride, Metric, OnCompleteCallback, PageView, PageViewPerformance, ProcessLegacy, RemoteDependencyData, RequestHeaders, SampleRate, - SendPOSTFunction, SendRequestReason, SenderPostManager, Trace, TransportType, _ISendPostMgrConfig, _ISenderOnComplete, - _eInternalMessageId, _throwInternal, _warnToConsole, arrForEach, cfgDfBoolean, cfgDfValidate, createOfflineListener, - createProcessTelemetryContext, createUniqueNamespace, dateNow, dumpObj, eLoggingSeverity, eRequestHeaders, formatErrorMessageXdr, - formatErrorMessageXhr, getExceptionName, getIEVersion, isArray, isBeaconsSupported, isFeatureEnabled, isFetchSupported, - isInternalApplicationInsightsEndpoint, isNullOrUndefined, mergeEvtNamespace, objExtend, onConfigChange, parseResponse, prependTransports, - runTargetUnload, utlCanUseSessionStorage, utlSetStoragePrefix + IXHROverride, MetricDataType, OnCompleteCallback, PageViewDataType, PageViewPerformanceDataType, ProcessLegacy, RemoteDependencyDataType, + RequestDataType, RequestHeaders, SampleRate, SendPOSTFunction, SendRequestReason, SenderPostManager, TraceDataType, TransportType, + _ISendPostMgrConfig, _ISenderOnComplete, _eInternalMessageId, _throwInternal, _warnToConsole, arrForEach, cfgDfBoolean, cfgDfValidate, + createOfflineListener, createProcessTelemetryContext, createUniqueNamespace, dateNow, dumpObj, eLoggingSeverity, eRequestHeaders, + formatErrorMessageXdr, formatErrorMessageXhr, getExceptionName, getIEVersion, isArray, isBeaconsSupported, isFeatureEnabled, + isFetchSupported, isInternalApplicationInsightsEndpoint, isNullOrUndefined, mergeEvtNamespace, objExtend, onConfigChange, parseResponse, + prependTransports, runTargetUnload, utlCanUseSessionStorage, utlSetStoragePrefix } from "@microsoft/otel-core-js"; import { IPromise, createPromise, doAwait, doAwaitResponse } from "@nevware21/ts-async"; import { @@ -19,12 +19,12 @@ import { } from "@nevware21/ts-utils"; import { DependencyEnvelopeCreator, EnvelopeCreator, EventEnvelopeCreator, ExceptionEnvelopeCreator, MetricEnvelopeCreator, - PageViewEnvelopeCreator, PageViewPerformanceEnvelopeCreator, TraceEnvelopeCreator + PageViewEnvelopeCreator, PageViewPerformanceEnvelopeCreator, RequestEnvelopeCreator, TraceEnvelopeCreator } from "./EnvelopeCreator"; import { IInternalStorageItem, ISenderConfig } from "./Interfaces"; import { ArraySendBuffer, ISendBuffer, SessionStorageSendBuffer } from "./SendBuffer"; import { Serializer } from "./Serializer"; -import { Sample } from "./TelemetryProcessors/Sample"; +import { createSampler } from "./TelemetryProcessors/Sample"; const UNDEFINED_VALUE: undefined = undefined; const EMPTY_STR = ""; @@ -91,13 +91,14 @@ function _chkSampling(value: number) { type EnvelopeCreator = (logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any) => IEnvelope; const EnvelopeTypeCreator: { [key:string] : EnvelopeCreator } = { - [Event.dataType]: EventEnvelopeCreator, - [Trace.dataType]: TraceEnvelopeCreator, - [PageView.dataType]: PageViewEnvelopeCreator, - [PageViewPerformance.dataType]: PageViewPerformanceEnvelopeCreator, - [Exception.dataType]: ExceptionEnvelopeCreator, - [Metric.dataType]: MetricEnvelopeCreator, - [RemoteDependencyData.dataType]: DependencyEnvelopeCreator + [EventDataType]: EventEnvelopeCreator, + [TraceDataType]: TraceEnvelopeCreator, + [PageViewDataType]: PageViewEnvelopeCreator, + [PageViewPerformanceDataType]: PageViewPerformanceEnvelopeCreator, + [ExceptionDataType]: ExceptionEnvelopeCreator, + [MetricDataType]: MetricEnvelopeCreator, + [RemoteDependencyDataType]: DependencyEnvelopeCreator, + [RequestDataType]: RequestEnvelopeCreator }; export type SenderFunction = (payload: string[] | IInternalStorageItem[], isAsync: boolean) => void | IPromise; @@ -406,7 +407,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { _fetchKeepAlive = !senderConfig.onunloadDisableFetch && isFetchSupported(true); _disableBeaconSplit = !!senderConfig.disableSendBeaconSplit; - _self._sample = new Sample(senderConfig.samplingPercentage, diagLog); + _self._sample = createSampler(senderConfig.samplingPercentage, diagLog); _instrumentationKey = senderConfig.instrumentationKey; if(!isPromiseLike(_instrumentationKey) && !_validateInstrumentationKey(_instrumentationKey, config)) { @@ -903,13 +904,16 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { } // check if this item should be sampled in, else add sampleRate tag - if (!_isSampledIn(telemetryItem)) { - // Item is sampled out, do not send it - diagLogger && _throwInternal(diagLogger, eLoggingSeverity.WARNING, _eInternalMessageId.TelemetrySampledAndNotSent, - "Telemetry item was sampled out and not sent", { SampleRate: _self._sample.sampleRate }); - return false; - } else { - telemetryItem[SampleRate] = _self._sample.sampleRate; + let sampleRate = (telemetryItem as any).sampleRate; + if (isNullOrUndefined(sampleRate) || !isNumber(sampleRate) || sampleRate < 0 || sampleRate > 100) { + if (!_isSampledIn(telemetryItem)) { + // Item is sampled out, do not send it + diagLogger && _throwInternal(diagLogger, eLoggingSeverity.WARNING, _eInternalMessageId.TelemetrySampledAndNotSent, + "Telemetry item was sampled out and not sent", { SampleRate: _self._sample.sampleRate }); + return false; + } else { + telemetryItem[SampleRate] = _self._sample.sampleRate; + } } return true; } diff --git a/channels/applicationinsights-channel-js/src/Serializer.ts b/channels/applicationinsights-channel-js/src/Serializer.ts index f82e805d0..e05ef11e4 100644 --- a/channels/applicationinsights-channel-js/src/Serializer.ts +++ b/channels/applicationinsights-channel-js/src/Serializer.ts @@ -1,164 +1,170 @@ import dynamicProto from "@microsoft/dynamicproto-js" import { - IDiagnosticLogger, _eInternalMessageId, _throwInternal, eLoggingSeverity, getJSON, isArray, isFunction, isObject, objForEachKey + IDiagnosticLogger, _eInternalMessageId, _throwInternal, eLoggingSeverity, getJSON, isArray, isFunction, isNullOrUndefined, isObject, objForEachKey } from "@microsoft/otel-core-js"; import { FieldType, ISerializable } from "@microsoft/otel-core-js"; -export class Serializer { +const enum eSerializeType { + String = 1, + Number = 2 +} - constructor(logger: IDiagnosticLogger) { - dynamicProto(Serializer, this, (_self) => { - /** - * Serializes the current object to a JSON string. - */ - _self.serialize = (input: ISerializable): string => { - const output = _serializeObject(input, "root"); - try { - return getJSON().stringify(output); - } catch (e) { - // if serialization fails return an empty string - _throwInternal(logger, eLoggingSeverity.CRITICAL, _eInternalMessageId.CannotSerializeObject, (e && isFunction(e.toString)) ? e.toString() : "Error serializing object", null, true); - } +/** + * Used to "tag" objects that are currently being serialized to detect circular references + */ +const circularReferenceCheck = "__aiCircularRefCheck"; + +function _serializeObject(logger: IDiagnosticLogger, source: ISerializable, name: string): any { + let output: any = {}; + + if (!source) { + _throwInternal(logger, eLoggingSeverity.CRITICAL, _eInternalMessageId.CannotSerializeObject, "cannot serialize object because it is null or undefined", { name }, true); + return output; + } + + if ((source as any)[circularReferenceCheck]) { + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.CircularReferenceDetected, "Circular reference detected while serializing object", { name }, true); + return output; + } + + if (!source.aiDataContract) { + // special case for measurements/properties/tags + if (name === "measurements") { + output = _serializeStringMap(logger, source, eSerializeType.Number, name); + } else if (name === "properties") { + output = _serializeStringMap(logger, source, eSerializeType.String, name); + } else if (name === "tags") { + output = _serializeStringMap(logger, source, eSerializeType.String, name); + } else if (isArray(source)) { + output = _serializeArray(logger, source as any, name); + } else { + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.CannotSerializeObjectNonSerializable, "Attempting to serialize an object which does not implement ISerializable", { name }, true); + + try { + // verify that the object can be stringified + getJSON().stringify(source); + output = source; + } catch (e) { + // if serialization fails return an empty string + _throwInternal(logger, eLoggingSeverity.CRITICAL, _eInternalMessageId.CannotSerializeObject, (e && isFunction(e.toString)) ? e.toString() : "Error serializing object", null, true); } + } - function _serializeObject(source: ISerializable, name: string): any { - const circularReferenceCheck = "__aiCircularRefCheck"; - let output = {}; + return output; + } - if (!source) { - _throwInternal(logger, eLoggingSeverity.CRITICAL, _eInternalMessageId.CannotSerializeObject, "cannot serialize object because it is null or undefined", { name }, true); - return output; + (source as any)[circularReferenceCheck] = true; + objForEachKey(source.aiDataContract, (field, contract) => { + const fieldType = isFunction(contract) ? contract() : contract; + const isRequired = fieldType & FieldType.Required; + const isHidden = fieldType & FieldType.Hidden; + const isArray = fieldType & FieldType.Array; + const isPresent = (source as any)[field] !== undefined; + const isObj = isObject((source as any)[field]) && (source as any)[field] !== null; + + if (isRequired && !isPresent && !isArray) { + _throwInternal(logger, + eLoggingSeverity.CRITICAL, + _eInternalMessageId.MissingRequiredFieldSpecification, + "Missing required field specification. The field is required but not present on source", + { field, name }); + + // If not in debug mode, continue and hope the error is permissible + } else if (!isHidden) { // Don't serialize hidden fields + let value; + if (isObj) { + if (isArray) { + // special case; recurse on each object in the source array + value = _serializeArray(logger, (source as any)[field], field); + } else { + // recurse on the source object in this field + value = _serializeObject(logger, (source as any)[field], field); } + } else { + // assign the source field to the output even if undefined or required + value = (source as any)[field]; + } - if (source[circularReferenceCheck]) { - _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.CircularReferenceDetected, "Circular reference detected while serializing object", { name }, true); - return output; - } + // only emit this field if the value is defined + if (value !== undefined) { + output[field] = value; + } + } + }); - if (!source.aiDataContract) { - // special case for measurements/properties/tags - if (name === "measurements") { - output = _serializeStringMap(source, "number", name); - } else if (name === "properties") { - output = _serializeStringMap(source, "string", name); - } else if (name === "tags") { - output = _serializeStringMap(source, "string", name); - } else if (isArray(source)) { - output = _serializeArray(source as any, name); - } else { - _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.CannotSerializeObjectNonSerializable, "Attempting to serialize an object which does not implement ISerializable", { name }, true); - - try { - // verify that the object can be stringified - getJSON().stringify(source); - output = source; - } catch (e) { - // if serialization fails return an empty string - _throwInternal(logger, eLoggingSeverity.CRITICAL, _eInternalMessageId.CannotSerializeObject, (e && isFunction(e.toString)) ? e.toString() : "Error serializing object", null, true); - } - } - - return output; - } + delete (source as any)[circularReferenceCheck]; + return output; +} - source[circularReferenceCheck] = true; - objForEachKey(source.aiDataContract, (field, contract) => { - const isRequired = (isFunction(contract)) ? (contract() & FieldType.Required) : (contract & FieldType.Required); - const isHidden = (isFunction(contract)) ? (contract() & FieldType.Hidden) : (contract & FieldType.Hidden); - const isArray = contract & FieldType.Array; - - const isPresent = source[field] !== undefined; - const isObj = isObject(source[field]) && source[field] !== null; - - if (isRequired && !isPresent && !isArray) { - _throwInternal(logger, - eLoggingSeverity.CRITICAL, - _eInternalMessageId.MissingRequiredFieldSpecification, - "Missing required field specification. The field is required but not present on source", - { field, name }); - - // If not in debug mode, continue and hope the error is permissible - } else if (!isHidden) { // Don't serialize hidden fields - let value; - if (isObj) { - if (isArray) { - // special case; recurse on each object in the source array - value = _serializeArray(source[field], field); - } else { - // recurse on the source object in this field - value = _serializeObject(source[field], field); - } - } else { - // assign the source field to the output even if undefined or required - value = source[field]; - } - - // only emit this field if the value is defined - if (value !== undefined) { - output[field] = value; - } - } - }); - - delete source[circularReferenceCheck]; - return output; +function _serializeArray(logger: IDiagnosticLogger, sources: ISerializable[], name: string): any[] { + let output: any[]; + + if (!!sources) { + if (!isArray(sources)) { + _throwInternal(logger, + eLoggingSeverity.CRITICAL, + _eInternalMessageId.ItemNotInArray, + "This field was specified as an array in the contract but the item is not an array.\r\n", + { name }, true); + } else { + output = []; + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + const item = _serializeObject(logger, source, name + "[" + i + "]"); + output.push(item); } + } + } - function _serializeArray(sources: ISerializable[], name: string): any[] { - let output: any[]; - - if (!!sources) { - if (!isArray(sources)) { - _throwInternal(logger, - eLoggingSeverity.CRITICAL, - _eInternalMessageId.ItemNotInArray, - "This field was specified as an array in the contract but the item is not an array.\r\n", - { name }, true); - } else { - output = []; - for (let i = 0; i < sources.length; i++) { - const source = sources[i]; - const item = _serializeObject(source, name + "[" + i + "]"); - output.push(item); - } - } - } + return output; +} - return output; +function _serializeStringMap(logger: IDiagnosticLogger, map: any, expectedType: eSerializeType, name: string) { + let output: any; + if (map) { + output = {}; + objForEachKey(map, (field, value) => { + let serializedValue: string | number; + if (value === undefined) { + serializedValue = "undefined"; + } else if (value === null) { + serializedValue = "null"; } - function _serializeStringMap(map: any, expectedType: string, name: string) { - let output: any; - if (map) { - output = {}; - objForEachKey(map, (field, value) => { - if (expectedType === "string") { - if (value === undefined) { - output[field] = "undefined"; - } else if (value === null) { - output[field] = "null"; - } else if (!value.toString) { - output[field] = "invalid field: toString() is not defined."; - } else { - output[field] = value.toString(); - } - } else if (expectedType === "number") { - if (value === undefined) { - output[field] = "undefined"; - } else if (value === null) { - output[field] = "null"; - } else { - const num = parseFloat(value); - output[field] = num; - } - } else { - output[field] = "invalid field: " + name + " is of unknown type."; - _throwInternal(logger, eLoggingSeverity.CRITICAL, output[field], null, true); - } - }); + if (expectedType === eSerializeType.String && !serializedValue) { + if (!value.toString) { + serializedValue = "invalid field: toString() is not defined."; + } else { + serializedValue = value.toString(); } + } else if (expectedType === eSerializeType.Number && !serializedValue) { + serializedValue = parseFloat(value); + } + + if (serializedValue || !isNullOrUndefined(value)) { + output[field] = serializedValue; + } + }); + } + + return output; +} + +export class Serializer { - return output; + constructor(logger: IDiagnosticLogger) { + dynamicProto(Serializer, this, (_self) => { + /** + * Serializes the current object to a JSON string. + */ + _self.serialize = (input: ISerializable): string => { + const output = _serializeObject(logger, input, "root"); + try { + return getJSON().stringify(output); + } catch (e) { + // if serialization fails return an empty string + _throwInternal(logger, eLoggingSeverity.CRITICAL, _eInternalMessageId.CannotSerializeObject, (e && isFunction(e.toString)) ? e.toString() : "Error serializing object", null, true); + } } }); } diff --git a/channels/applicationinsights-channel-js/src/Telemetry/Common/Data.ts b/channels/applicationinsights-channel-js/src/Telemetry/Common/Data.ts new file mode 100644 index 000000000..fd153756f --- /dev/null +++ b/channels/applicationinsights-channel-js/src/Telemetry/Common/Data.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { AIData, FieldType, ISerializable } from "@microsoft/otel-core-js"; + +export function _createData(baseType: string, data: TDomain): AIData & ISerializable { + return { + baseType: baseType, + baseData: data, + aiDataContract: { + baseType: FieldType.Required, + baseData: FieldType.Required + } + }; +} diff --git a/channels/applicationinsights-channel-js/src/Telemetry/RemoteDependencyData.ts b/channels/applicationinsights-channel-js/src/Telemetry/RemoteDependencyData.ts new file mode 100644 index 000000000..e25ba7091 --- /dev/null +++ b/channels/applicationinsights-channel-js/src/Telemetry/RemoteDependencyData.ts @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + FieldType, IDiagnosticLogger, IRemoteDependencyData, ISerializable, dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString, + dataSanitizeUrl, msToTimeSpan, urlParseUrl +} from "@microsoft/otel-core-js"; + +export const RemoteDependencyEnvelopeType = "Microsoft.ApplicationInsights.{0}.RemoteDependency"; + +function AjaxHelperParseDependencyPath(logger: IDiagnosticLogger, absoluteUrl: string, method: string, commandName: string) { + let target, name = commandName, data = commandName; + + if (absoluteUrl && absoluteUrl.length > 0) { + const parsedUrl: HTMLAnchorElement = urlParseUrl(absoluteUrl); + target = parsedUrl.host; + if (!name) { + if (parsedUrl.pathname != null) { + let pathName: string = (parsedUrl.pathname.length === 0) ? "/" : parsedUrl.pathname; + if (pathName.charAt(0) !== "/") { + pathName = "/" + pathName; + } + data = parsedUrl.pathname; + name = dataSanitizeString(logger, method ? method + " " + pathName : pathName); + } else { + name = dataSanitizeString(logger, absoluteUrl); + } + } + } else { + target = commandName; + name = commandName; + } + + return { + target, + name, + data + }; +} + +export function createRemoteDependencyData( + logger: IDiagnosticLogger, id: string, absoluteUrl: string, commandName: string, value: number, success: boolean, + resultCode: number, method?: string, requestAPI: string = "Ajax", correlationContext?: string, properties?: Object, + measurements?: Object) : IRemoteDependencyData & ISerializable { + const dependencyFields = AjaxHelperParseDependencyPath(logger, absoluteUrl, method, commandName); + + let data: IRemoteDependencyData & ISerializable = { + ver: 2, + id: id, + duration: msToTimeSpan(value), + success: success, + resultCode: "" + resultCode, + type: dataSanitizeString(logger, requestAPI), + data: dataSanitizeUrl(logger, commandName) || dependencyFields.data, // get a value from hosturl if commandName not available + target: dataSanitizeString(logger, dependencyFields.target), + name: dataSanitizeString(logger, dependencyFields.name), + properties: dataSanitizeProperties(logger, properties), + measurements: dataSanitizeMeasurements(logger, measurements), + + aiDataContract: { + id: FieldType.Required, + ver: FieldType.Required, + name: FieldType.Default, + resultCode: FieldType.Default, + duration: FieldType.Default, + success: FieldType.Default, + data: FieldType.Default, + target: FieldType.Default, + type: FieldType.Default, + properties: FieldType.Default, + measurements: FieldType.Default, + + kind: FieldType.Default, + value: FieldType.Default, + count: FieldType.Default, + min: FieldType.Default, + max: FieldType.Default, + stdDev: FieldType.Default, + dependencyKind: FieldType.Default, + dependencySource: FieldType.Default, + commandName: FieldType.Default, + dependencyTypeName: FieldType.Default + } + }; + + if (correlationContext) { + data.target = "" + data.target + " | " + correlationContext; + } + + return data; +} diff --git a/channels/applicationinsights-channel-js/src/Telemetry/RequestData.ts b/channels/applicationinsights-channel-js/src/Telemetry/RequestData.ts new file mode 100644 index 000000000..e513564b8 --- /dev/null +++ b/channels/applicationinsights-channel-js/src/Telemetry/RequestData.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + FieldType, IDiagnosticLogger, ISerializable, asString, dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString, + dataSanitizeUrl, msToTimeSpan +} from "@microsoft/otel-core-js"; +import { IRequestData } from "../Interfaces/Contracts/IRequestData"; + +export const RequestEnvelopeType = "Microsoft.ApplicationInsights.{0}.Request"; + +/** + * Constructs a new instance of the RequestData object + */ +export function createRequestData(logger: IDiagnosticLogger, id: string, name: string | undefined, value: number, success: boolean, responseCode: number, source?: string, url?: string, properties?: Object, measurements?: Object): IRequestData & ISerializable { + return { + ver: 2, + id: id, + name: dataSanitizeString(logger, name), + duration: msToTimeSpan(value), + success: success, + responseCode: asString(responseCode || "0"), + source: dataSanitizeString(logger, source), + url: dataSanitizeUrl(logger, url), + properties: dataSanitizeProperties(logger, properties), + measurements: dataSanitizeMeasurements(logger, measurements), + aiDataContract: { + id: FieldType.Required, + ver: FieldType.Required, + name: FieldType.Default, + responseCode: FieldType.Required, + duration: FieldType.Required, + success: FieldType.Required, + source: FieldType.Default, + url: FieldType.Default, + properties: FieldType.Default, + measurements: FieldType.Default + } + }; +} diff --git a/channels/applicationinsights-channel-js/src/TelemetryProcessors/Sample.ts b/channels/applicationinsights-channel-js/src/TelemetryProcessors/Sample.ts index b7946b91a..65d202e3a 100644 --- a/channels/applicationinsights-channel-js/src/TelemetryProcessors/Sample.ts +++ b/channels/applicationinsights-channel-js/src/TelemetryProcessors/Sample.ts @@ -2,47 +2,45 @@ // Licensed under the MIT License. import { - IDiagnosticLogger, ISample, ITelemetryItem, Metric, _eInternalMessageId, eLoggingSeverity, safeGetLogger + IDiagnosticLogger, ISample, ITelemetryItem, MetricDataType, _eInternalMessageId, _throwInternal, eLoggingSeverity } from "@microsoft/otel-core-js"; -import { SamplingScoreGenerator } from "./SamplingScoreGenerators/SamplingScoreGenerator"; +import { IScoreGenerator, createSamplingScoreGenerator } from "./SamplingScoreGenerators/SamplingScoreGenerator"; -export class Sample implements ISample { - public sampleRate: number; +function _isSampledIn(envelope: ITelemetryItem, samplingPercentage: number, scoreGenerator: IScoreGenerator): boolean { + let isSampledIn = false; - // We're using 32 bit math, hence max value is (2^31 - 1) - public INT_MAX_VALUE: number = 2147483647; - private samplingScoreGenerator: SamplingScoreGenerator; - - constructor(sampleRate: number, logger?: IDiagnosticLogger) { - let _logger = logger || safeGetLogger(null); - - if (sampleRate > 100 || sampleRate < 0) { - _logger.throwInternal(eLoggingSeverity.WARNING, - _eInternalMessageId.SampleRateOutOfRange, - "Sampling rate is out of range (0..100). Sampling will be disabled, you may be sending too much data which may affect your AI service level.", - { samplingRate: sampleRate }, true); - sampleRate = 100; - } + if (samplingPercentage === null || samplingPercentage === undefined || samplingPercentage >= 100) { + isSampledIn = true; + } else if (envelope.baseType === MetricDataType) { + // exclude MetricData telemetry from sampling + isSampledIn = true; + } - this.sampleRate = sampleRate; - this.samplingScoreGenerator = new SamplingScoreGenerator(); + if (!isSampledIn) { + isSampledIn = scoreGenerator.getScore(envelope) < samplingPercentage; } + + return isSampledIn; +} - /** - * Determines if an envelope is sampled in (i.e. will be sent) or not (i.e. will be dropped). - */ - public isSampledIn(envelope: ITelemetryItem): boolean { - const samplingPercentage = this.sampleRate; // 0 - 100 - let isSampledIn = false; +export function createSampler(sampleRate: number, logger?: IDiagnosticLogger): ISample { + let _samplingScoreGenerator = createSamplingScoreGenerator(); + + if (sampleRate > 100 || sampleRate < 0) { + _throwInternal(logger, eLoggingSeverity.WARNING, + _eInternalMessageId.SampleRateOutOfRange, + "Sampling rate is out of range (0..100). Sampling will be disabled, you may be sending too much data which may affect your AI service level.", + { samplingRate: sampleRate }, true); + sampleRate = 100; + } - if (samplingPercentage === null || samplingPercentage === undefined || samplingPercentage >= 100) { - return true; - } else if (envelope.baseType === Metric.dataType) { - // exclude MetricData telemetry from sampling - return true; + let sampler: ISample & { generator: IScoreGenerator } = { + sampleRate: sampleRate, + generator: _samplingScoreGenerator, + isSampledIn: function (envelope: ITelemetryItem) { + return _isSampledIn(envelope, sampler.sampleRate, sampler.generator); } + }; - isSampledIn = this.samplingScoreGenerator.getSamplingScore(envelope) < samplingPercentage; - return isSampledIn; - } + return sampler; } diff --git a/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/HashCodeScoreGenerator.ts b/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/HashCodeScoreGenerator.ts index 1f6f9ee96..da0b724ad 100644 --- a/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/HashCodeScoreGenerator.ts +++ b/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/HashCodeScoreGenerator.ts @@ -1,23 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { mathAbs } from "@nevware21/ts-utils"; + // (Magic number) DJB algorithm can't work on shorter strings (results in poor distribution const MIN_INPUT_LENGTH: number = 8; -export class HashCodeScoreGenerator { - // We're using 32 bit math, hence max value is (2^31 - 1) - public static INT_MAX_VALUE: number = 2147483647; +// We're using 32 bit math, hence max value is (2^31 - 1) +const INT_MAX_VALUE: number = 2147483647; - public getHashCodeScore(key: string): number { - const score = this.getHashCode(key) / HashCodeScoreGenerator.INT_MAX_VALUE; - return score * 100; - } - - public getHashCode(input: string): number { - if (input === "") { - return 0; - } +export function getHashCodeScore(key: string): number { + let score = 0; + let input = key; + if (input) { while (input.length < MIN_INPUT_LENGTH) { input = input.concat(input); } @@ -32,6 +28,8 @@ export class HashCodeScoreGenerator { hash = hash & hash; } - return Math.abs(hash); + score = mathAbs(hash) / INT_MAX_VALUE; } + + return score * 100; } diff --git a/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/SamplingScoreGenerator.ts b/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/SamplingScoreGenerator.ts index 1f7f31308..3c2fe8891 100644 --- a/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/SamplingScoreGenerator.ts +++ b/channels/applicationinsights-channel-js/src/TelemetryProcessors/SamplingScoreGenerators/SamplingScoreGenerator.ts @@ -2,27 +2,26 @@ // Licensed under the MIT License. import { ContextTagKeys, ITelemetryItem } from "@microsoft/otel-core-js"; -import { HashCodeScoreGenerator } from "./HashCodeScoreGenerator"; +import { getHashCodeScore } from "./HashCodeScoreGenerator"; -export class SamplingScoreGenerator { - - public getSamplingScore: (item: ITelemetryItem) => number; +export interface IScoreGenerator { + getScore(item: ITelemetryItem): number; +} - constructor() { - let _self = this; - let hashCodeGenerator: HashCodeScoreGenerator = new HashCodeScoreGenerator(); - let keys: ContextTagKeys = new ContextTagKeys(); +export function createSamplingScoreGenerator(): IScoreGenerator { + let keys: ContextTagKeys = new ContextTagKeys(); - _self.getSamplingScore = (item: ITelemetryItem): number => { + return { + getScore: (item: ITelemetryItem): number => { let score: number = 0; if (item.tags && item.tags[keys.userId]) { // search in tags first, then ext - score = hashCodeGenerator.getHashCodeScore(item.tags[keys.userId]); + score = getHashCodeScore(item.tags[keys.userId]); } else if (item.ext && item.ext.user && item.ext.user.id) { - score = hashCodeGenerator.getHashCodeScore(item.ext.user.id); + score = getHashCodeScore(item.ext.user.id); } else if (item.tags && item.tags[keys.operationId]) { // search in tags first, then ext - score = hashCodeGenerator.getHashCodeScore(item.tags[keys.operationId]); + score = getHashCodeScore(item.tags[keys.operationId]); } else if (item.ext && item.ext.telemetryTrace && item.ext.telemetryTrace.traceID) { - score = hashCodeGenerator.getHashCodeScore(item.ext.telemetryTrace.traceID); + score = getHashCodeScore(item.ext.telemetryTrace.traceID); } else { // tslint:disable-next-line:insecure-random score = (Math.random() * 100); @@ -30,5 +29,5 @@ export class SamplingScoreGenerator { return score; } - } + }; } diff --git a/channels/tee-channel-js/package.json b/channels/tee-channel-js/package.json index caee9a06c..32f9c1e95 100644 --- a/channels/tee-channel-js/package.json +++ b/channels/tee-channel-js/package.json @@ -59,8 +59,8 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "license": "MIT" } diff --git a/common/Tests/Framework/package.json b/common/Tests/Framework/package.json index cb273c503..429bd18ed 100644 --- a/common/Tests/Framework/package.json +++ b/common/Tests/Framework/package.json @@ -54,7 +54,7 @@ }, "dependencies": { "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" } } diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index 1cf7ba4f8..09df7ac87 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -10,12 +10,11 @@ "dependencies": { "@microsoft/api-extractor": "^7.40.0", "@microsoft/applicationinsights-offlinechannel-js": "0.3.11", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -31,6 +30,7 @@ "@rush-temp/applicationinsights-properties-js": "file:./projects/applicationinsights-properties-js.tgz", "@rush-temp/applicationinsights-rollup-es5": "file:./projects/applicationinsights-rollup-es5.tgz", "@rush-temp/applicationinsights-rollup-plugin-uglify3-js": "file:./projects/applicationinsights-rollup-plugin-uglify3-js.tgz", + "@rush-temp/applicationinsights-shims": "file:./projects/applicationinsights-shims.tgz", "@rush-temp/applicationinsights-teechannel-js": "file:./projects/applicationinsights-teechannel-js.tgz", "@rush-temp/applicationinsights-test-module-type-check": "file:./projects/applicationinsights-test-module-type-check.tgz", "@rush-temp/applicationinsights-web": "file:./projects/applicationinsights-web.tgz", @@ -390,9 +390,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dependencies": { "@isaacs/balanced-match": "^4.0.1" }, @@ -406,20 +406,20 @@ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "node_modules/@microsoft/api-extractor": { - "version": "7.55.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.55.2.tgz", - "integrity": "sha512-1jlWO4qmgqYoVUcyh+oXYRztZde/pAi7cSVzBz/rc+S7CoVzDasy8QE13dx6sLG4VRo8SfkkLbFORR6tBw4uGQ==", + "version": "7.56.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.56.2.tgz", + "integrity": "sha512-d94f7S+H43jDxY/YIyp5UOE9N12HZmEPP5Ct96us+fw1FVKBoy4itTopdVM1VrcpduuA7fK/t31CgA2jM8AqSA==", "dependencies": { "@microsoft/api-extractor-model": "7.32.2", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.1", "@rushstack/rig-package": "0.6.0", - "@rushstack/terminal": "0.19.5", - "@rushstack/ts-command-line": "5.1.5", + "@rushstack/terminal": "0.21.0", + "@rushstack/ts-command-line": "5.2.0", "diff": "~8.0.2", "lodash": "~4.17.15", - "minimatch": "10.0.3", + "minimatch": "10.1.2", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", @@ -573,9 +573,9 @@ } }, "node_modules/@nevware21/ts-utils": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.12.5.tgz", - "integrity": "sha512-JPQZWPKQJjj7kAftdEZL0XDFfbMgXCGiUAZe0d7EhLC3QlXTlZdSckGqqRIQ2QNl0VTEZyZUvRBw6Ednw089Fw==" + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.12.6.tgz", + "integrity": "sha512-UsS1hbgr/V/x8dT7hVHvr/PwHzASi8/Itis1+L8ykLiqsUXdfzrB1maL0vMmKbDEJpmGARsoC/7RIswi+n248Q==" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -630,9 +630,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "bin": { "semver": "bin/semver.js" }, @@ -775,12 +775,12 @@ "node_modules/@rush-temp/ai-test-framework": { "version": "0.0.0", "resolved": "file:projects/ai-test-framework.tgz", - "integrity": "sha512-u6oI7nCSnLvStqAomAIZ0VLl2waEBWApexMz2a3KZaCBH4pdpLqXCxOKxaMKye5l6e7jicETYo/bXTjsyCT27Q==", + "integrity": "sha512-gV0fnBBxoCSVWqwoPnu7CfCh2oDENBSIH1P9SbTAG9d+vnQHfyRyzBzv9yEV65cTk8YLFhFYWPxYuCAjok7vXQ==", "dependencies": { "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -803,14 +803,13 @@ "node_modules/@rush-temp/applicationinsights-analytics-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-analytics-js.tgz", - "integrity": "sha512-tztLMB3TJ8V8iq27UIOd5KIkeh2j3dJ5Ezl3RYWALw/fH9VcfFFKJ2DgfFRWbDbTkqi1btuF258DBB42eb9tYw==", + "integrity": "sha512-DiYorNJcMR8s8RG4s+DX2NCF0wqnHqBihKQnubknb/FLc5T8B55/u36gVfJlB5K5oiAgP6aOa8QIy0qw0rp5mg==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -834,15 +833,14 @@ "node_modules/@rush-temp/applicationinsights-cfgsync-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-cfgsync-js.tgz", - "integrity": "sha512-A5xY7PNRW6Niq8Ca6aI9B5vAfcrzVPk7KOMJ3s4OTdZkOpCN3jD86D0gcsby8IkLINWaIXX3yOAdGxnQ9Ehxcg==", + "integrity": "sha512-Inw2c6TEKENTTis04Hyq90DmuJTjUaY+CnBbeM+FXo7ODaRXnMxYTUBlK1nZ8JxQ8ONQTWtrMCGzwX/aNcW+mg==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -866,15 +864,14 @@ "node_modules/@rush-temp/applicationinsights-channel-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-channel-js.tgz", - "integrity": "sha512-v62LjUgSH62GmcgLH2VFjYll9t+OOZc5YuWDv6aqv++NDdDGewh74FeRoXJa2FiY40qcmw2zLGIJG3bjDoPZrQ==", + "integrity": "sha512-j2k8rZQcGqxFo6IqPOZDtgzC63P/zH3AfnOt0nJ1tAlqCWk1zDWyOBF4Aq4Ig+ps5dZ5Ocvfi/p35r5HUA9vCA==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -895,15 +892,14 @@ "node_modules/@rush-temp/applicationinsights-dependencies-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-dependencies-js.tgz", - "integrity": "sha512-eOOzvHe6XFfCkgq4+xb8ho72sr3Hrqsm3k5p8G8VuN5gySPXwxdBhI1cEDDTf2nfQtQ8VNtDQu30evJegT3DSg==", + "integrity": "sha512-rb8Fwpy0sEa+JyZa9X6JxP5nBmQRh91wSJ86VbV96eY5SQkuWLHK7NGg0tEdGezkMrpa6AOFmEO1crzURjK8PQ==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -935,13 +931,12 @@ "node_modules/@rush-temp/applicationinsights-osplugin-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-osplugin-js.tgz", - "integrity": "sha512-hNV6HGNg84XJu8TLFKO9Gy7jcuhRSxN/rbqBEflu5OyXzTnGXIoIDpTxaROHO1WyBb8Cxm04izfsl8xvS/ZoqA==", + "integrity": "sha512-MfkaNUfM2TrtMN21TMsoSs6po6QeCPFaIdELW6O+p4tBAc2B8uPhRTO5HyV2SVoPG4al6XZmbHMwpVl1yNJZOw==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -959,14 +954,13 @@ "node_modules/@rush-temp/applicationinsights-perfmarkmeasure-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-perfmarkmeasure-js.tgz", - "integrity": "sha512-x9hH9120AIEoDfXGovIXBZFNRkHrp2HyRbgs0mqFGzflTOdvKNEmo2mZYPExSvB5gZnUSWqeiH0tdkv63WQ/ww==", + "integrity": "sha512-U4JcdJY5P4PbCbvE2vQlnXWKTg/Mij62+uezZO+Rmgv0ORJQa6g71hwnbKUgLBx0c310yZL47uyP6pMSPKjjSw==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -986,14 +980,13 @@ "node_modules/@rush-temp/applicationinsights-properties-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-properties-js.tgz", - "integrity": "sha512-3wRi5oCx+0jPL2/45sp7X0lS2Oa7nTzcE1M7bbYlc5GgpWDXZfDjj8yQzZWZSKggO41TAjrFQ06wE6EsgXowcg==", + "integrity": "sha512-nAKIm5irjY5mm4S3LJ2k/5gX5VoG0Q6yAQmzCMMfg7Q3C78LSBEtivw1/mo5lI53vhMkFvnJGatZ44yljnUNbA==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -1061,18 +1054,41 @@ "uglify-js": "3.16.0" } }, + "node_modules/@rush-temp/applicationinsights-shims": { + "version": "0.0.0", + "resolved": "file:projects/applicationinsights-shims.tgz", + "integrity": "sha512-da6+hree5hR3Tf6GhsS00TL7d3IxYwVN306NXXG5LVl7Pd8Rgi+hE39SmHd2OyCbWWoxGDe2t4/4pugQbXkTlQ==", + "dependencies": { + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/grunt-eslint-ts": "^0.5.1", + "@nevware21/grunt-ts-plugin": "^0.5.1", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@rollup/plugin-commonjs": "^24.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-typescript": "^12.1.2", + "@types/qunit": "^2.19.3", + "grunt": "^1.5.3", + "grunt-cli": "^1.4.3", + "grunt-rollup": "^12.0.0", + "rollup": "^3.20.0", + "rollup-plugin-cleanup": "^3.2.1", + "rollup-plugin-minify-es": "^1.1.1", + "rollup-plugin-sourcemaps": "^0.6.3", + "typescript": "^4.9.3" + } + }, "node_modules/@rush-temp/applicationinsights-teechannel-js": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-teechannel-js.tgz", - "integrity": "sha512-1v1w2XG3iQwZRBj18YCYa5PhFrzwh+mb8awEVDKWOWt1Y5sXOoTzcAYZKNCRbGkb816L6KewDe4Kda7GL007fQ==", + "integrity": "sha512-aiOfZAdexDw3V4pxKOQNrR041LuPXc72RjrSWFVcIS2eLYN+MjSz8xjfZu3kbGJncj11ATABh5BQAVVbwiZMtA==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -1102,16 +1118,15 @@ "node_modules/@rush-temp/applicationinsights-web": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-web.tgz", - "integrity": "sha512-MvydBdpWSNThOXV2PnQMb3au4zEyL8OCNnCsRhD72fo6exph0dxxPzsuLKKcDzkhCKh+dk90KiyLDFOABYk+2g==", + "integrity": "sha512-suv4QMx3b2zQpCKK2WmjmH+/5NktGRx4afF25qkeoOvjIywlp4G9eZcEzJD2t+ioS1I85ZaNooLR/bqhKEgisA==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", "@microsoft/applicationinsights-offlinechannel-js": "0.3.11", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -1138,15 +1153,14 @@ "node_modules/@rush-temp/applicationinsights-web-basic": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-web-basic.tgz", - "integrity": "sha512-qk/4osz3YE7C9m4A2c2yXSnNwCqEDO8vPHRa+XLP2z+yIXQHh7x11j/ZI+bxkdQMc+kXgSwdDIWYKsA2yVJVSQ==", + "integrity": "sha512-yJPY31+DTRRNjGE153o8U5xoqH+Cw6JUhlW6D55SgI+4VUaXQinsoPyoVMCXx/dgkl84V3WBGd8XRS9QM0QvDQ==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -1174,15 +1188,14 @@ "node_modules/@rush-temp/otel-core-js": { "version": "0.0.0", "resolved": "file:projects/otel-core-js.tgz", - "integrity": "sha512-+TLnV995AFyY+LOsI5DbW4Hp2G/0UJeHpznUqor5qE3gHxaEbI6Y1b7YXwdQr1jtlbiEpMA/ugbwrVRhUnC2fg==", + "integrity": "sha512-4nsCt/Kb9wv5xX9wgCWxqBJA1Q3yD/ZhjWP18i+G55yGNwkvon/KT/+55X2NFTndFvON5yazznnT1J+CIQmd0Q==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -1206,15 +1219,14 @@ "node_modules/@rush-temp/otel-noop-js": { "version": "0.0.0", "resolved": "file:projects/otel-noop-js.tgz", - "integrity": "sha512-U7ZakIa9P0fYJQ4AAOwKDuLP8nFIIn2uW/vTJhVeNOjNKFgurQdZZkqAQW7+GNMJDUMYM3MYk3z9TWCenDkL5g==", + "integrity": "sha512-l8/HnBK7kIhmIRPxJ69xfPe/8Pn2uTVmGHPINYeCUs0NuNxfDBdCgcsdXCwR452KWmRriW8DzrPI588yRnm4iw==", "dependencies": { "@microsoft/api-extractor": "^7.40.0", - "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", @@ -1296,9 +1308,9 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.5.tgz", - "integrity": "sha512-6k5tpdB88G0K7QrH/3yfKO84HK9ggftfUZ51p7fePyCE7+RLLHkWZbID9OFWbXuna+eeCFE7AkKnRMHMxNbz7Q==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.21.0.tgz", + "integrity": "sha512-cLaI4HwCNYmknM5ns4G+drqdEB6q3dCPV423+d3TZeBusYSSm09+nR7CnhzJMjJqeRcdMAaLnrA4M/3xDz4R3w==", "dependencies": { "@rushstack/node-core-library": "5.19.1", "@rushstack/problem-matcher": "0.1.1", @@ -1314,11 +1326,11 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.5.tgz", - "integrity": "sha512-YmrFTFUdHXblYSa+Xc9OO9FsL/XFcckZy0ycQ6q7VSBsVs5P0uD9vcges5Q9vctGlVdu27w+Ct6IuJ458V0cTQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.2.0.tgz", + "integrity": "sha512-lYxCX0nDdkDtCkVpvF0m25ymf66SaMWuppbD6b7MdkIzvGXKBXNIVZlwBH/C0YfkanrupnICWf2n4z3AKSfaHw==", "dependencies": { - "@rushstack/terminal": "0.19.5", + "@rushstack/terminal": "0.21.0", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -1456,9 +1468,9 @@ } }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", + "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", "optional": true, "dependencies": { "undici-types": "~7.16.0" @@ -1494,16 +1506,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", - "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/type-utils": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -1516,21 +1528,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.53.1", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", - "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "engines": { @@ -1546,13 +1558,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "peer": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "engines": { @@ -1567,13 +1579,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1584,9 +1596,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1600,14 +1612,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", - "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -1624,9 +1636,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1637,15 +1649,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "peer": true, "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -1688,9 +1700,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "peer": true, "bin": { "semver": "bin/semver.js" @@ -1700,15 +1712,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1723,12 +1735,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -1952,9 +1964,9 @@ } }, "node_modules/bare-fs": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", - "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", + "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", "optional": true, "dependencies": { "bare-events": "^2.5.4", @@ -3115,7 +3127,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3395,7 +3407,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4218,11 +4230,11 @@ } }, "node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "@isaacs/brace-expansion": "^5.0.1" }, "engines": { "node": "20 || >=22" @@ -5162,9 +5174,9 @@ } }, "node_modules/sinon/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.1.tgz", + "integrity": "sha512-Z3u54A8qGyqFOSr2pk0ijYs8mOE9Qz8kTvtKeBI+upoG9j04Sq+oI7W8zAJiQybDcESET8/uIdHzs0p3k4fZlw==", "engines": { "node": ">=0.3.1" } diff --git a/common/scripts/install-run-rush.js b/common/scripts/install-run-rush.js index ef1d697f9..ab7defd16 100644 --- a/common/scripts/install-run-rush.js +++ b/common/scripts/install-run-rush.js @@ -16,25 +16,25 @@ /******/ "use strict"; /******/ var __webpack_modules__ = ({ -/***/ 16928: -/*!***********************!*\ - !*** external "path" ***! - \***********************/ -/***/ ((module) => { +/***/ 176760 +/*!****************************!*\ + !*** external "node:path" ***! + \****************************/ +(module) { -module.exports = require("path"); +module.exports = require("node:path"); -/***/ }), +/***/ }, -/***/ 179896: -/*!*********************!*\ - !*** external "fs" ***! - \*********************/ -/***/ ((module) => { +/***/ 973024 +/*!**************************!*\ + !*** external "node:fs" ***! + \**************************/ +(module) { -module.exports = require("fs"); +module.exports = require("node:fs"); -/***/ }) +/***/ } /******/ }); /************************************************************************/ @@ -48,6 +48,12 @@ module.exports = require("fs"); /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } +/******/ // Check if module exists (development only) +/******/ if (__webpack_modules__[moduleId] === undefined) { +/******/ var e = new Error("Cannot find module '" + moduleId + "'"); +/******/ e.code = 'MODULE_NOT_FOUND'; +/******/ throw e; +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed @@ -111,10 +117,10 @@ var __webpack_exports__ = {}; !*** ./lib-esnext/scripts/install-run-rush.js ***! \************************************************/ __webpack_require__.r(__webpack_exports__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! path */ 16928); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! fs */ 179896); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! node:path */ 176760); +/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! node:fs */ 973024); +/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_1__); // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. /* eslint-disable no-console */ @@ -131,9 +137,9 @@ function _getRushVersion(logger) { return rushPreviewVersion; } const rushJsonFolder = findRushJsonFolder(); - const rushJsonPath = path__WEBPACK_IMPORTED_MODULE_0__.join(rushJsonFolder, RUSH_JSON_FILENAME); + const rushJsonPath = node_path__WEBPACK_IMPORTED_MODULE_0__.join(rushJsonFolder, RUSH_JSON_FILENAME); try { - const rushJsonContents = fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(rushJsonPath, 'utf-8'); + const rushJsonContents = node_fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(rushJsonPath, 'utf-8'); // Use a regular expression to parse out the rushVersion value because rush.json supports comments, // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script. const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/); @@ -159,7 +165,7 @@ function _run() { const [nodePath /* Ex: /bin/node */, scriptPath /* /repo/common/scripts/install-run-rush.js */, ...packageBinArgs /* [build, --to, myproject] */] = process.argv; // Detect if this script was directly invoked, or if the install-run-rushx script was invokved to select the // appropriate binary inside the rush package to run - const scriptName = path__WEBPACK_IMPORTED_MODULE_0__.basename(scriptPath); + const scriptName = node_path__WEBPACK_IMPORTED_MODULE_0__.basename(scriptPath); const bin = _getBin(scriptName); if (!nodePath || !scriptPath) { throw new Error('Unexpected exception: could not detect node path or script path'); diff --git a/common/scripts/install-run.js b/common/scripts/install-run.js index a35726bf1..9274e7038 100644 --- a/common/scripts/install-run.js +++ b/common/scripts/install-run.js @@ -16,51 +16,64 @@ /******/ "use strict"; /******/ var __webpack_modules__ = ({ -/***/ 16928: -/*!***********************!*\ - !*** external "path" ***! - \***********************/ -/***/ ((module) => { +/***/ 90178 +/*!****************************************************!*\ + !*** ./lib-esnext/utilities/executionUtilities.js ***! + \****************************************************/ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -module.exports = require("path"); - -/***/ }), - -/***/ 179896: -/*!*********************!*\ - !*** external "fs" ***! - \*********************/ -/***/ ((module) => { - -module.exports = require("fs"); +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ IS_WINDOWS: () => (/* binding */ IS_WINDOWS), +/* harmony export */ escapeArgumentIfNeeded: () => (/* binding */ escapeArgumentIfNeeded) +/* harmony export */ }); +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. +const IS_WINDOWS = process.platform === 'win32'; +function escapeArgumentIfNeeded(command, isWindows = IS_WINDOWS) { + if (command.includes(' ')) { + if (isWindows) { + // Windows: use double quotes and escape internal double quotes + return `"${command.replace(/"/g, '""')}"`; + } + else { + // Unix: use JSON.stringify for proper escaping + return JSON.stringify(command); + } + } + else { + return command; + } +} +//# sourceMappingURL=executionUtilities.js.map -/***/ }), +/***/ }, -/***/ 370857: -/*!*********************!*\ - !*** external "os" ***! - \*********************/ -/***/ ((module) => { +/***/ 176760 +/*!****************************!*\ + !*** external "node:path" ***! + \****************************/ +(module) { -module.exports = require("os"); +module.exports = require("node:path"); -/***/ }), +/***/ }, -/***/ 535317: -/*!********************************!*\ - !*** external "child_process" ***! - \********************************/ -/***/ ((module) => { +/***/ 731421 +/*!*************************************!*\ + !*** external "node:child_process" ***! + \*************************************/ +(module) { -module.exports = require("child_process"); +module.exports = require("node:child_process"); -/***/ }), +/***/ }, -/***/ 832286: +/***/ 832286 /*!************************************************!*\ !*** ./lib-esnext/utilities/npmrcUtilities.js ***! \************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { @@ -68,10 +81,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ syncNpmrc: () => (/* binding */ syncNpmrc), /* harmony export */ trimNpmrcFileLines: () => (/* binding */ trimNpmrcFileLines) /* harmony export */ }); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! fs */ 179896); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! path */ 16928); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! node:fs */ 973024); +/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! node:path */ 176760); +/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_1__); // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. // IMPORTANT - do not use any non-built-in libraries in this file @@ -87,7 +100,7 @@ __webpack_require__.r(__webpack_exports__); // create a global _combinedNpmrc for cache purpose const _combinedNpmrcMap = new Map(); function _trimNpmrcFile(options) { - const { sourceNpmrcPath, linesToPrepend, linesToAppend, supportEnvVarFallbackSyntax } = options; + const { sourceNpmrcPath, linesToPrepend, linesToAppend, supportEnvVarFallbackSyntax, filterNpmIncompatibleProperties, env = process.env } = options; const combinedNpmrcFromCache = _combinedNpmrcMap.get(sourceNpmrcPath); if (combinedNpmrcFromCache !== undefined) { return combinedNpmrcFromCache; @@ -96,28 +109,70 @@ function _trimNpmrcFile(options) { if (linesToPrepend) { npmrcFileLines.push(...linesToPrepend); } - if (fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sourceNpmrcPath)) { - npmrcFileLines.push(...fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(sourceNpmrcPath).toString().split('\n')); + if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sourceNpmrcPath)) { + npmrcFileLines.push(...node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(sourceNpmrcPath).toString().split('\n')); } if (linesToAppend) { npmrcFileLines.push(...linesToAppend); } npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); - const resultLines = trimNpmrcFileLines(npmrcFileLines, process.env, supportEnvVarFallbackSyntax); + const resultLines = trimNpmrcFileLines(npmrcFileLines, env, supportEnvVarFallbackSyntax, filterNpmIncompatibleProperties); const combinedNpmrc = resultLines.join('\n'); //save the cache _combinedNpmrcMap.set(sourceNpmrcPath, combinedNpmrc); return combinedNpmrc; } +/** + * List of npmrc properties that are not supported by npm but may be present in the config. + * These include pnpm-specific properties and deprecated npm properties. + */ +const NPM_INCOMPATIBLE_PROPERTIES = new Set([ + // pnpm-specific hoisting configuration + 'hoist', + 'hoist-pattern', + 'public-hoist-pattern', + 'shamefully-hoist', + // Deprecated or unknown npm properties that cause warnings + 'email', + 'publish-branch' +]); +/** + * List of registry-scoped npmrc property suffixes that are pnpm-specific. + * These are properties like "//registry.example.com/:tokenHelper" where "tokenHelper" + * is the suffix after the last colon. + */ +const NPM_INCOMPATIBLE_REGISTRY_SCOPED_PROPERTIES = new Set([ + // pnpm-specific token helper properties + 'tokenHelper', + 'urlTokenHelper' +]); +/** + * Regular expression to extract property names from .npmrc lines. + * Matches everything before '=', '[', or whitespace to capture the property name. + * Note: The 'g' flag is intentionally omitted since we only need the first match. + * Examples: + * "registry=https://..." -> matches "registry" + * "hoist-pattern[]=..." -> matches "hoist-pattern" + */ +const PROPERTY_NAME_REGEX = /^([^=\[\s]+)/; +/** + * Regular expression to extract environment variable names and optional fallback values. + * Matches patterns like: + * nameString -> group 1: nameString, group 2: undefined + * nameString-fallbackString -> group 1: nameString, group 2: fallbackString + * nameString:-fallbackString -> group 1: nameString, group 2: fallbackString + */ +const ENV_VAR_WITH_FALLBACK_REGEX = /^(?[^:-]+)(?::?-(?.+))?$/; /** * * @param npmrcFileLines The npmrc file's lines * @param env The environment variables object * @param supportEnvVarFallbackSyntax Whether to support fallback values in the form of `${VAR_NAME:-fallback}` - * @returns + * @param filterNpmIncompatibleProperties Whether to filter out properties that npm doesn't understand + * @returns An array of processed npmrc file lines with undefined environment variables and npm-incompatible properties commented out */ -function trimNpmrcFileLines(npmrcFileLines, env, supportEnvVarFallbackSyntax) { - var _a; +function trimNpmrcFileLines(npmrcFileLines, env, supportEnvVarFallbackSyntax, filterNpmIncompatibleProperties = false) { + var _a, _b, _c; const resultLines = []; // This finds environment variable tokens that look like "${VAR_NAME}" const expansionRegExp = /\$\{([^\}]+)\}/g; @@ -126,6 +181,7 @@ function trimNpmrcFileLines(npmrcFileLines, env, supportEnvVarFallbackSyntax) { // Trim out lines that reference environment variables that aren't defined for (let line of npmrcFileLines) { let lineShouldBeTrimmed = false; + let trimReason = ''; //remove spaces before or after key and value line = line .split('=') @@ -133,49 +189,89 @@ function trimNpmrcFileLines(npmrcFileLines, env, supportEnvVarFallbackSyntax) { .join('='); // Ignore comment lines if (!commentRegExp.test(line)) { - const environmentVariables = line.match(expansionRegExp); - if (environmentVariables) { - for (const token of environmentVariables) { - /** - * Remove the leading "${" and the trailing "}" from the token - * - * ${nameString} -> nameString - * ${nameString-fallbackString} -> name-fallbackString - * ${nameString:-fallbackString} -> name:-fallbackString - */ - const nameWithFallback = token.substring(2, token.length - 1); - let environmentVariableName; - let fallback; - if (supportEnvVarFallbackSyntax) { - /** - * Get the environment variable name and fallback value. - * - * name fallback - * nameString -> nameString undefined - * nameString-fallbackString -> nameString fallbackString - * nameString:-fallbackString -> nameString fallbackString - */ - const matched = nameWithFallback.match(/^([^:-]+)(?:\:?-(.+))?$/); - // matched: [originStr, variableName, fallback] - environmentVariableName = (_a = matched === null || matched === void 0 ? void 0 : matched[1]) !== null && _a !== void 0 ? _a : nameWithFallback; - fallback = matched === null || matched === void 0 ? void 0 : matched[2]; + // Check if this is a property that npm doesn't understand + if (filterNpmIncompatibleProperties) { + // Extract the property name (everything before the '=' or '[') + const match = line.match(PROPERTY_NAME_REGEX); + if (match) { + const propertyName = match[1]; + // Check if this is a registry-scoped property (starts with "//" like "//registry.npmjs.org/:_authToken") + const isRegistryScoped = propertyName.startsWith('//'); + if (isRegistryScoped) { + // For registry-scoped properties, check if the suffix (after the last colon) is npm-incompatible + // Example: "//registry.example.com/:tokenHelper" -> suffix is "tokenHelper" + const lastColonIndex = propertyName.lastIndexOf(':'); + if (lastColonIndex !== -1) { + const registryPropertySuffix = propertyName.substring(lastColonIndex + 1); + if (NPM_INCOMPATIBLE_REGISTRY_SCOPED_PROPERTIES.has(registryPropertySuffix)) { + lineShouldBeTrimmed = true; + trimReason = 'NPM_INCOMPATIBLE_PROPERTY'; + } + } } else { - environmentVariableName = nameWithFallback; + // For non-registry-scoped properties, check the full property name + if (NPM_INCOMPATIBLE_PROPERTIES.has(propertyName)) { + lineShouldBeTrimmed = true; + trimReason = 'NPM_INCOMPATIBLE_PROPERTY'; + } } - // Is the environment variable and fallback value defined. - if (!env[environmentVariableName] && !fallback) { - // No, so trim this line - lineShouldBeTrimmed = true; - break; + } + } + // Check for undefined environment variables + if (!lineShouldBeTrimmed) { + const environmentVariables = line.match(expansionRegExp); + if (environmentVariables) { + for (const token of environmentVariables) { + /** + * Remove the leading "${" and the trailing "}" from the token + * + * ${nameString} -> nameString + * ${nameString-fallbackString} -> name-fallbackString + * ${nameString:-fallbackString} -> name:-fallbackString + */ + const nameWithFallback = token.slice(2, -1); + let environmentVariableName; + let fallback; + if (supportEnvVarFallbackSyntax) { + /** + * Get the environment variable name and fallback value. + * + * name fallback + * nameString -> nameString undefined + * nameString-fallbackString -> nameString fallbackString + * nameString:-fallbackString -> nameString fallbackString + */ + const matched = nameWithFallback.match(ENV_VAR_WITH_FALLBACK_REGEX); + environmentVariableName = (_b = (_a = matched === null || matched === void 0 ? void 0 : matched.groups) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : nameWithFallback; + fallback = (_c = matched === null || matched === void 0 ? void 0 : matched.groups) === null || _c === void 0 ? void 0 : _c.fallback; + } + else { + environmentVariableName = nameWithFallback; + } + // Is the environment variable and fallback value defined. + if (!env[environmentVariableName] && !fallback) { + // No, so trim this line + lineShouldBeTrimmed = true; + trimReason = 'MISSING_ENVIRONMENT_VARIABLE'; + break; + } } } } } if (lineShouldBeTrimmed) { - // Example output: - // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" - resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + // Comment out the line with appropriate reason + if (trimReason === 'NPM_INCOMPATIBLE_PROPERTY') { + // Example output: + // "; UNSUPPORTED BY NPM: email=test@example.com" + resultLines.push('; UNSUPPORTED BY NPM: ' + line); + } + else { + // Example output: + // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" + resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + } } else { resultLines.push(line); @@ -188,7 +284,7 @@ function _copyAndTrimNpmrcFile(options) { logger.info(`Transforming ${sourceNpmrcPath}`); // Verbose logger.info(` --> "${targetNpmrcPath}"`); const combinedNpmrc = _trimNpmrcFile(options); - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(targetNpmrcPath, combinedNpmrc); + node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(targetNpmrcPath, combinedNpmrc); return combinedNpmrc; } function syncNpmrc(options) { @@ -198,13 +294,13 @@ function syncNpmrc(options) { // eslint-disable-next-line no-console error: console.error }, createIfMissing = false } = options; - const sourceNpmrcPath = path__WEBPACK_IMPORTED_MODULE_1__.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish'); - const targetNpmrcPath = path__WEBPACK_IMPORTED_MODULE_1__.join(targetNpmrcFolder, '.npmrc'); + const sourceNpmrcPath = node_path__WEBPACK_IMPORTED_MODULE_1__.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish'); + const targetNpmrcPath = node_path__WEBPACK_IMPORTED_MODULE_1__.join(targetNpmrcFolder, '.npmrc'); try { - if (fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sourceNpmrcPath) || createIfMissing) { + if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sourceNpmrcPath) || createIfMissing) { // Ensure the target folder exists - if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(targetNpmrcFolder)) { - fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(targetNpmrcFolder, { recursive: true }); + if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(targetNpmrcFolder)) { + node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(targetNpmrcFolder, { recursive: true }); } return _copyAndTrimNpmrcFile({ sourceNpmrcPath, @@ -213,10 +309,10 @@ function syncNpmrc(options) { ...options }); } - else if (fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(targetNpmrcPath)) { + else if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(targetNpmrcPath)) { // If the source .npmrc doesn't exist and there is one in the target, delete the one in the target logger.info(`Deleting ${targetNpmrcPath}`); // Verbose - fs__WEBPACK_IMPORTED_MODULE_0__.unlinkSync(targetNpmrcPath); + node_fs__WEBPACK_IMPORTED_MODULE_0__.unlinkSync(targetNpmrcPath); } } catch (e) { @@ -226,16 +322,40 @@ function syncNpmrc(options) { function isVariableSetInNpmrcFile(sourceNpmrcFolder, variableKey, supportEnvVarFallbackSyntax) { const sourceNpmrcPath = `${sourceNpmrcFolder}/.npmrc`; //if .npmrc file does not exist, return false directly - if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sourceNpmrcPath)) { + if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sourceNpmrcPath)) { return false; } - const trimmedNpmrcFile = _trimNpmrcFile({ sourceNpmrcPath, supportEnvVarFallbackSyntax }); + const trimmedNpmrcFile = _trimNpmrcFile({ + sourceNpmrcPath, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties: false + }); const variableKeyRegExp = new RegExp(`^${variableKey}=`, 'm'); return trimmedNpmrcFile.match(variableKeyRegExp) !== null; } //# sourceMappingURL=npmrcUtilities.js.map -/***/ }) +/***/ }, + +/***/ 848161 +/*!**************************!*\ + !*** external "node:os" ***! + \**************************/ +(module) { + +module.exports = require("node:os"); + +/***/ }, + +/***/ 973024 +/*!**************************!*\ + !*** external "node:fs" ***! + \**************************/ +(module) { + +module.exports = require("node:fs"); + +/***/ } /******/ }); /************************************************************************/ @@ -249,6 +369,12 @@ function isVariableSetInNpmrcFile(sourceNpmrcFolder, variableKey, supportEnvVarF /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } +/******/ // Check if module exists (development only) +/******/ if (__webpack_modules__[moduleId] === undefined) { +/******/ var e = new Error("Cannot find module '" + moduleId + "'"); +/******/ e.code = 'MODULE_NOT_FOUND'; +/******/ throw e; +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed @@ -319,15 +445,16 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ installAndRun: () => (/* binding */ installAndRun), /* harmony export */ runWithErrorAndStatusCode: () => (/* binding */ runWithErrorAndStatusCode) /* harmony export */ }); -/* harmony import */ var child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! child_process */ 535317); -/* harmony import */ var child_process__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(child_process__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! fs */ 179896); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! os */ 370857); -/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! path */ 16928); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! node:child_process */ 731421); +/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_child_process__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! node:fs */ 973024); +/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! node:os */ 848161); +/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(node_os__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! node:path */ 176760); +/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _utilities_npmrcUtilities__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utilities/npmrcUtilities */ 832286); +/* harmony import */ var _utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utilities/executionUtilities */ 90178); // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. /* eslint-disable no-console */ @@ -336,6 +463,7 @@ __webpack_require__.r(__webpack_exports__); + const RUSH_JSON_FILENAME = 'rush.json'; const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME = 'RUSH_TEMP_FOLDER'; const INSTALL_RUN_LOCKFILE_PATH_VARIABLE = 'INSTALL_RUN_LOCKFILE_PATH'; @@ -374,34 +502,34 @@ let _npmPath = undefined; function getNpmPath() { if (!_npmPath) { try { - if (_isWindows()) { + if (_utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__.IS_WINDOWS) { // We're on Windows - const whereOutput = child_process__WEBPACK_IMPORTED_MODULE_0__.execSync('where npm', { stdio: [] }).toString(); - const lines = whereOutput.split(os__WEBPACK_IMPORTED_MODULE_2__.EOL).filter((line) => !!line); + const whereOutput = node_child_process__WEBPACK_IMPORTED_MODULE_0__.execSync('where npm', { stdio: [] }).toString(); + const lines = whereOutput.split(node_os__WEBPACK_IMPORTED_MODULE_2__.EOL).filter((line) => !!line); // take the last result, we are looking for a .cmd command // see https://github.com/microsoft/rushstack/issues/759 _npmPath = lines[lines.length - 1]; } else { // We aren't on Windows - assume we're on *NIX or Darwin - _npmPath = child_process__WEBPACK_IMPORTED_MODULE_0__.execSync('command -v npm', { stdio: [] }).toString(); + _npmPath = node_child_process__WEBPACK_IMPORTED_MODULE_0__.execSync('command -v npm', { stdio: [] }).toString(); } } catch (e) { throw new Error(`Unable to determine the path to the NPM tool: ${e}`); } _npmPath = _npmPath.trim(); - if (!fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(_npmPath)) { + if (!node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(_npmPath)) { throw new Error('The NPM executable does not exist'); } } return _npmPath; } function _ensureFolder(folderPath) { - if (!fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(folderPath)) { - const parentDir = path__WEBPACK_IMPORTED_MODULE_3__.dirname(folderPath); + if (!node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(folderPath)) { + const parentDir = node_path__WEBPACK_IMPORTED_MODULE_3__.dirname(folderPath); _ensureFolder(parentDir); - fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(folderPath); + node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(folderPath); } } /** @@ -415,14 +543,14 @@ function _ensureAndJoinPath(baseFolder, ...pathSegments) { try { for (let pathSegment of pathSegments) { pathSegment = pathSegment.replace(/[\\\/]/g, '+'); - joinedPath = path__WEBPACK_IMPORTED_MODULE_3__.join(joinedPath, pathSegment); - if (!fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(joinedPath)) { - fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(joinedPath); + joinedPath = node_path__WEBPACK_IMPORTED_MODULE_3__.join(joinedPath, pathSegment); + if (!node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(joinedPath)) { + node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(joinedPath); } } } catch (e) { - throw new Error(`Error building local installation folder (${path__WEBPACK_IMPORTED_MODULE_3__.join(baseFolder, ...pathSegments)}): ${e}`); + throw new Error(`Error building local installation folder (${node_path__WEBPACK_IMPORTED_MODULE_3__.join(baseFolder, ...pathSegments)}): ${e}`); } return joinedPath; } @@ -469,14 +597,16 @@ function _resolvePackageVersion(logger, rushCommonFolder, { name, version }) { // version resolves to try { const rushTempFolder = _getRushTempFolder(rushCommonFolder); - const sourceNpmrcFolder = path__WEBPACK_IMPORTED_MODULE_3__.join(rushCommonFolder, 'config', 'rush'); + const sourceNpmrcFolder = node_path__WEBPACK_IMPORTED_MODULE_3__.join(rushCommonFolder, 'config', 'rush'); (0,_utilities_npmrcUtilities__WEBPACK_IMPORTED_MODULE_4__.syncNpmrc)({ sourceNpmrcFolder, targetNpmrcFolder: rushTempFolder, logger, - supportEnvVarFallbackSyntax: false + supportEnvVarFallbackSyntax: false, + // Always filter npm-incompatible properties in install-run scripts. + // Any warnings will be shown when running Rush commands directly. + filterNpmIncompatibleProperties: true }); - const npmPath = getNpmPath(); // This returns something that looks like: // ``` // [ @@ -494,16 +624,11 @@ function _resolvePackageVersion(logger, rushCommonFolder, { name, version }) { // ``` // // if only a single version matches. - const spawnSyncOptions = { + const npmVersionSpawnResult = _runNpmConfirmSuccess(['view', `${name}@${version}`, 'version', '--no-update-notifier', '--json'], { cwd: rushTempFolder, stdio: [], - shell: _isWindows() - }; - const platformNpmPath = _getPlatformPath(npmPath); - const npmVersionSpawnResult = child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(platformNpmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier', '--json'], spawnSyncOptions); - if (npmVersionSpawnResult.status !== 0) { - throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`); - } + env: process.env + }, 'npm view'); const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString(); const parsedVersionOutput = JSON.parse(npmViewVersionOutput); const versions = Array.isArray(parsedVersionOutput) @@ -535,15 +660,15 @@ function findRushJsonFolder() { let basePath = __dirname; let tempPath = __dirname; do { - const testRushJsonPath = path__WEBPACK_IMPORTED_MODULE_3__.join(basePath, RUSH_JSON_FILENAME); - if (fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(testRushJsonPath)) { + const testRushJsonPath = node_path__WEBPACK_IMPORTED_MODULE_3__.join(basePath, RUSH_JSON_FILENAME); + if (node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(testRushJsonPath)) { _rushJsonFolder = basePath; break; } else { basePath = tempPath; } - } while (basePath !== (tempPath = path__WEBPACK_IMPORTED_MODULE_3__.dirname(basePath))); // Exit the loop when we hit the disk root + } while (basePath !== (tempPath = node_path__WEBPACK_IMPORTED_MODULE_3__.dirname(basePath))); // Exit the loop when we hit the disk root if (!_rushJsonFolder) { throw new Error(`Unable to find ${RUSH_JSON_FILENAME}.`); } @@ -555,11 +680,11 @@ function findRushJsonFolder() { */ function _isPackageAlreadyInstalled(packageInstallFolder) { try { - const flagFilePath = path__WEBPACK_IMPORTED_MODULE_3__.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); - if (!fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(flagFilePath)) { + const flagFilePath = node_path__WEBPACK_IMPORTED_MODULE_3__.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); + if (!node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(flagFilePath)) { return false; } - const fileContents = fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(flagFilePath).toString(); + const fileContents = node_fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(flagFilePath).toString(); return fileContents.trim() === process.version; } catch (e) { @@ -571,7 +696,7 @@ function _isPackageAlreadyInstalled(packageInstallFolder) { */ function _deleteFile(file) { try { - fs__WEBPACK_IMPORTED_MODULE_1__.unlinkSync(file); + node_fs__WEBPACK_IMPORTED_MODULE_1__.unlinkSync(file); } catch (err) { if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') { @@ -587,19 +712,19 @@ function _deleteFile(file) { */ function _cleanInstallFolder(rushTempFolder, packageInstallFolder, lockFilePath) { try { - const flagFile = path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME); + const flagFile = node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME); _deleteFile(flagFile); - const packageLockFile = path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, 'package-lock.json'); + const packageLockFile = node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, 'package-lock.json'); if (lockFilePath) { - fs__WEBPACK_IMPORTED_MODULE_1__.copyFileSync(lockFilePath, packageLockFile); + node_fs__WEBPACK_IMPORTED_MODULE_1__.copyFileSync(lockFilePath, packageLockFile); } else { // Not running `npm ci`, so need to cleanup _deleteFile(packageLockFile); - const nodeModulesFolder = path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME); - if (fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(nodeModulesFolder)) { + const nodeModulesFolder = node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME); + if (node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync(nodeModulesFolder)) { const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler'); - fs__WEBPACK_IMPORTED_MODULE_1__.renameSync(nodeModulesFolder, path__WEBPACK_IMPORTED_MODULE_3__.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`)); + node_fs__WEBPACK_IMPORTED_MODULE_1__.renameSync(nodeModulesFolder, node_path__WEBPACK_IMPORTED_MODULE_3__.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`)); } } } @@ -619,8 +744,8 @@ function _createPackageJson(packageInstallFolder, name, version) { repository: "DON'T WARN", license: 'MIT' }; - const packageJsonPath = path__WEBPACK_IMPORTED_MODULE_3__.join(packageInstallFolder, PACKAGE_JSON_FILENAME); - fs__WEBPACK_IMPORTED_MODULE_1__.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2)); + const packageJsonPath = node_path__WEBPACK_IMPORTED_MODULE_3__.join(packageInstallFolder, PACKAGE_JSON_FILENAME); + node_fs__WEBPACK_IMPORTED_MODULE_1__.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2)); } catch (e) { throw new Error(`Unable to create package.json: ${e}`); @@ -629,20 +754,14 @@ function _createPackageJson(packageInstallFolder, name, version) { /** * Run "npm install" in the package install folder. */ -function _installPackage(logger, packageInstallFolder, name, version, command) { +function _installPackage(logger, packageInstallFolder, name, version, npmCommand) { try { logger.info(`Installing ${name}...`); - const npmPath = getNpmPath(); - const platformNpmPath = _getPlatformPath(npmPath); - const result = child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(platformNpmPath, [command], { + _runNpmConfirmSuccess([npmCommand], { stdio: 'inherit', cwd: packageInstallFolder, - env: process.env, - shell: _isWindows() - }); - if (result.status !== 0) { - throw new Error(`"npm ${command}" encountered an error`); - } + env: process.env + }, `npm ${npmCommand}`); logger.info(`Successfully installed ${name}@${version}`); } catch (e) { @@ -653,72 +772,113 @@ function _installPackage(logger, packageInstallFolder, name, version, command) { * Get the ".bin" path for the package. */ function _getBinPath(packageInstallFolder, binName) { - const binFolderPath = path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); - const resolvedBinName = _isWindows() ? `${binName}.cmd` : binName; - return path__WEBPACK_IMPORTED_MODULE_3__.resolve(binFolderPath, resolvedBinName); -} -/** - * Returns a cross-platform path - windows must enclose any path containing spaces within double quotes. - */ -function _getPlatformPath(platformPath) { - return _isWindows() && platformPath.includes(' ') ? `"${platformPath}"` : platformPath; + const binFolderPath = node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); + const resolvedBinName = _utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__.IS_WINDOWS ? `${binName}.cmd` : binName; + return node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(binFolderPath, resolvedBinName); } -function _isWindows() { - return os__WEBPACK_IMPORTED_MODULE_2__.platform() === 'win32'; +function _buildShellCommand(command, args) { + const escapedCommand = (0,_utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__.escapeArgumentIfNeeded)(command); + const escapedArgs = args.map((arg) => (0,_utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__.escapeArgumentIfNeeded)(arg)); + return [escapedCommand, ...escapedArgs].join(' '); } /** * Write a flag file to the package's install directory, signifying that the install was successful. */ function _writeFlagFile(packageInstallFolder) { try { - const flagFilePath = path__WEBPACK_IMPORTED_MODULE_3__.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); - fs__WEBPACK_IMPORTED_MODULE_1__.writeFileSync(flagFilePath, process.version); + const flagFilePath = node_path__WEBPACK_IMPORTED_MODULE_3__.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); + node_fs__WEBPACK_IMPORTED_MODULE_1__.writeFileSync(flagFilePath, process.version); } catch (e) { throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`); } } +/** + * Run npm under the platform's shell and throw if it didn't succeed. + */ +function _runNpmConfirmSuccess(args, options, commandNameForLogging) { + const command = getNpmPath(); + let result; + if (_utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__.IS_WINDOWS) { + result = node_child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(_buildShellCommand(command, args), { + ...options, + shell: true, + windowsVerbatimArguments: false + }); + } + else { + result = node_child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(command, args, options); + } + if (result.status !== 0) { + if (!result.status) { + // Is status null or undefined? + if (result.error) { + throw new Error(`"${commandNameForLogging}" failed: ${result.error.message.toString()}`); + } + else if (result.signal) { + throw new Error(`"${commandNameForLogging}" was terminated by signal: ${result.signal}`); + } + else { + throw new Error(`"${commandNameForLogging}" failed for an unknown reason`); + } + } + else { + throw new Error(`"${commandNameForLogging}" returned error code ${result.status}`); + } + } + return result; +} function installAndRun(logger, packageName, packageVersion, packageBinName, packageBinArgs, lockFilePath = process.env[INSTALL_RUN_LOCKFILE_PATH_VARIABLE]) { const rushJsonFolder = findRushJsonFolder(); - const rushCommonFolder = path__WEBPACK_IMPORTED_MODULE_3__.join(rushJsonFolder, 'common'); + const rushCommonFolder = node_path__WEBPACK_IMPORTED_MODULE_3__.join(rushJsonFolder, 'common'); const rushTempFolder = _getRushTempFolder(rushCommonFolder); const packageInstallFolder = _ensureAndJoinPath(rushTempFolder, 'install-run', `${packageName}@${packageVersion}`); if (!_isPackageAlreadyInstalled(packageInstallFolder)) { // The package isn't already installed _cleanInstallFolder(rushTempFolder, packageInstallFolder, lockFilePath); - const sourceNpmrcFolder = path__WEBPACK_IMPORTED_MODULE_3__.join(rushCommonFolder, 'config', 'rush'); + const sourceNpmrcFolder = node_path__WEBPACK_IMPORTED_MODULE_3__.join(rushCommonFolder, 'config', 'rush'); (0,_utilities_npmrcUtilities__WEBPACK_IMPORTED_MODULE_4__.syncNpmrc)({ sourceNpmrcFolder, targetNpmrcFolder: packageInstallFolder, logger, - supportEnvVarFallbackSyntax: false + supportEnvVarFallbackSyntax: false, + // Always filter npm-incompatible properties in install-run scripts. + // Any warnings will be shown when running Rush commands directly. + filterNpmIncompatibleProperties: true }); _createPackageJson(packageInstallFolder, packageName, packageVersion); - const command = lockFilePath ? 'ci' : 'install'; - _installPackage(logger, packageInstallFolder, packageName, packageVersion, command); + const installCommand = lockFilePath ? 'ci' : 'install'; + _installPackage(logger, packageInstallFolder, packageName, packageVersion, installCommand); _writeFlagFile(packageInstallFolder); } const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`; const statusMessageLine = new Array(statusMessage.length + 1).join('-'); logger.info('\n' + statusMessage + '\n' + statusMessageLine + '\n'); const binPath = _getBinPath(packageInstallFolder, packageBinName); - const binFolderPath = path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); + const binFolderPath = node_path__WEBPACK_IMPORTED_MODULE_3__.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); // Windows environment variables are case-insensitive. Instead of using SpawnSyncOptions.env, we need to // assign via the process.env proxy to ensure that we append to the right PATH key. const originalEnvPath = process.env.PATH || ''; let result; try { - // `npm` bin stubs on Windows are `.cmd` files - // Node.js will not directly invoke a `.cmd` file unless `shell` is set to `true` - const platformBinPath = _getPlatformPath(binPath); - process.env.PATH = [binFolderPath, originalEnvPath].join(path__WEBPACK_IMPORTED_MODULE_3__.delimiter); - result = child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(platformBinPath, packageBinArgs, { + process.env.PATH = [binFolderPath, originalEnvPath].join(node_path__WEBPACK_IMPORTED_MODULE_3__.delimiter); + const spawnOptions = { stdio: 'inherit', - windowsVerbatimArguments: false, - shell: _isWindows(), cwd: process.cwd(), env: process.env - }); + }; + if (_utilities_executionUtilities__WEBPACK_IMPORTED_MODULE_5__.IS_WINDOWS) { + result = node_child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(_buildShellCommand(binPath, packageBinArgs), { + ...spawnOptions, + windowsVerbatimArguments: false, + // `npm` bin stubs on Windows are `.cmd` files + // Node.js will not directly invoke a `.cmd` file unless `shell` is set to `true` + shell: true + }); + } + else { + result = node_child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync(binPath, packageBinArgs, spawnOptions); + } } finally { process.env.PATH = originalEnvPath; @@ -743,9 +903,10 @@ function runWithErrorAndStatusCode(logger, fn) { function _run() { const [nodePath /* Ex: /bin/node */, scriptPath /* /repo/common/scripts/install-run-rush.js */, rawPackageSpecifier /* qrcode@^1.2.0 */, packageBinName /* qrcode */, ...packageBinArgs /* [-f, myproject/lib] */] = process.argv; if (!nodePath) { - throw new Error('Unexpected exception: could not detect node path'); + throw new Error('Could not detect node path'); } - if (path__WEBPACK_IMPORTED_MODULE_3__.basename(scriptPath).toLowerCase() !== 'install-run.js') { + const scriptFileName = node_path__WEBPACK_IMPORTED_MODULE_3__.basename(scriptPath).toLowerCase(); + if (scriptFileName !== 'install-run.js' && scriptFileName !== 'install-run') { // If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control // to the script that (presumably) imported this file return; diff --git a/examples/AISKU/package.json b/examples/AISKU/package.json index daba8bc2f..5e141df6c 100644 --- a/examples/AISKU/package.json +++ b/examples/AISKU/package.json @@ -54,6 +54,6 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-web": "3.3.11", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } } diff --git a/examples/cfgSync/package.json b/examples/cfgSync/package.json index 13fb768fa..274ac4d84 100644 --- a/examples/cfgSync/package.json +++ b/examples/cfgSync/package.json @@ -67,6 +67,6 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-web": "3.3.11", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } } diff --git a/examples/dependency/package.json b/examples/dependency/package.json index 3d3167b2b..f63b76f6b 100644 --- a/examples/dependency/package.json +++ b/examples/dependency/package.json @@ -55,6 +55,6 @@ "@microsoft/applicationinsights-web": "3.3.11", "@microsoft/applicationinsights-dependencies-js": "3.3.11", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } } diff --git a/examples/shared-worker/package.json b/examples/shared-worker/package.json index 3727bb5d0..2dc7b4256 100644 --- a/examples/shared-worker/package.json +++ b/examples/shared-worker/package.json @@ -66,6 +66,6 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-web": "3.3.11", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } } diff --git a/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsExtensionSize.tests.ts b/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsExtensionSize.tests.ts index 02755db7c..4b6e3ab0b 100644 --- a/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsExtensionSize.tests.ts +++ b/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsExtensionSize.tests.ts @@ -1,6 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { dumpObj } from '@nevware21/ts-utils'; -import { createPromise, doAwait, IPromise } from '@nevware21/ts-async'; +import { dumpObj } from "@nevware21/ts-utils"; +import { createPromise, doAwait, IPromise } from "@nevware21/ts-async"; import * as pako from "pako"; const PACKAGE_JSON = "../package.json"; diff --git a/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts b/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts index 0b1bf57dd..538dfe6b3 100644 --- a/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts +++ b/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts @@ -2,10 +2,17 @@ import { Assert, AITestClass, PollingAssert, EventValidator, TraceValidator, ExceptionValidator, MetricValidator, PageViewPerformanceValidator, PageViewValidator, RemoteDepdencyValidator } from "@microsoft/ai-test-framework"; -import { SinonStub, SinonSpy } from 'sinon'; +import { SinonStub, SinonSpy } from "sinon"; import { - Exception, SeverityLevel, Event, Trace, PageViewPerformance, IConfig, IExceptionInternal, - AnalyticsPluginIdentifier, IAppInsights, Metric, PageView, RemoteDependencyData, utlCanUseLocalStorage, createDomEvent, findAllScripts + Exception, SeverityLevel, Trace, PageViewPerformance, IConfig, IExceptionInternal, + AnalyticsPluginIdentifier, IAppInsights, utlCanUseLocalStorage, createDomEvent, findAllScripts, + RemoteDependencyDataType, + PageViewPerformanceDataType, + PageViewDataType, + MetricDataType, + ExceptionDataType, + EventDataType, + TraceDataType } from "@microsoft/otel-core-js"; import { ITelemetryItem, AppInsightsCore, IPlugin, IConfiguration, IAppInsightsCore, setEnableEnvMocks, getLocation, dumpObj, __getRegisteredEvents, createCookieMgr } from "@microsoft/otel-core-js"; import { Sender } from "@microsoft/applicationinsights-channel-js" @@ -2206,19 +2213,19 @@ export class AnalyticsPluginTests extends AITestClass { const baseType = payload.data.baseType; // call the appropriate Validate depending on the baseType switch (baseType) { - case Event.dataType: + case EventDataType: return EventValidator.EventValidator.Validate(payload, baseType); - case Trace.dataType: + case TraceDataType: return TraceValidator.TraceValidator.Validate(payload, baseType); - case Exception.dataType: + case ExceptionDataType: return ExceptionValidator.ExceptionValidator.Validate(payload, baseType); - case Metric.dataType: + case MetricDataType: return MetricValidator.MetricValidator.Validate(payload, baseType); - case PageView.dataType: + case PageViewDataType: return PageViewValidator.PageViewValidator.Validate(payload, baseType); - case PageViewPerformance.dataType: + case PageViewPerformanceDataType: return PageViewPerformanceValidator.PageViewPerformanceValidator.Validate(payload, baseType); - case RemoteDependencyData.dataType: + case RemoteDependencyDataType: return RemoteDepdencyValidator.RemoteDepdencyValidator.Validate(payload, baseType); default: diff --git a/extensions/applicationinsights-analytics-js/Tests/Unit/src/TelemetryItemCreator.tests.ts b/extensions/applicationinsights-analytics-js/Tests/Unit/src/TelemetryItemCreator.tests.ts index eea4b2a23..fff1fd171 100644 --- a/extensions/applicationinsights-analytics-js/Tests/Unit/src/TelemetryItemCreator.tests.ts +++ b/extensions/applicationinsights-analytics-js/Tests/Unit/src/TelemetryItemCreator.tests.ts @@ -10,15 +10,16 @@ import { ITraceTelemetry, Metric, IMetricTelemetry, - RemoteDependencyData, IDependencyTelemetry, -} from '@microsoft/otel-core-js'; -import { AnalyticsPlugin } from '../../../src/JavaScriptSDK/AnalyticsPlugin' + RemoteDependencyEnvelopeType, + RemoteDependencyDataType, +} from "@microsoft/otel-core-js"; +import { AnalyticsPlugin } from "../../../src/JavaScriptSDK/AnalyticsPlugin" import { IAppInsightsCore, AppInsightsCore, ITelemetryItem, IConfiguration, IPlugin -} from '@microsoft/otel-core-js'; +} from "@microsoft/otel-core-js"; @@ -249,8 +250,8 @@ export class TelemetryItemCreatorTests extends AITestClass { // act const telemetryItem = TelemetryItemCreator.create( dependency, - RemoteDependencyData.dataType, - RemoteDependencyData.envelopeType, + RemoteDependencyDataType, + RemoteDependencyEnvelopeType, this._core.logger, ); diff --git a/extensions/applicationinsights-analytics-js/Tests/Unit/src/index.tests.ts b/extensions/applicationinsights-analytics-js/Tests/Unit/src/index.tests.ts index 3e4b83ca7..0e3a96a41 100644 --- a/extensions/applicationinsights-analytics-js/Tests/Unit/src/index.tests.ts +++ b/extensions/applicationinsights-analytics-js/Tests/Unit/src/index.tests.ts @@ -1,5 +1,5 @@ -import { AnalyticsPluginTests } from './AnalyticsPlugin.tests'; -import { TelemetryItemCreatorTests } from './TelemetryItemCreator.tests'; +import { AnalyticsPluginTests } from "./AnalyticsPlugin.tests"; +import { TelemetryItemCreatorTests } from "./TelemetryItemCreator.tests"; import { AnalyticsExtensionSizeCheck } from "./AnalyticsExtensionSize.tests"; import { GlobalTestHooks } from "./GlobalTestHooks.Test"; diff --git a/extensions/applicationinsights-analytics-js/package.json b/extensions/applicationinsights-analytics-js/package.json index 02e5adf76..141dca3c2 100644 --- a/extensions/applicationinsights-analytics-js/package.json +++ b/extensions/applicationinsights-analytics-js/package.json @@ -64,7 +64,7 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "license": "MIT" } diff --git a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts index 4ff81098b..155761408 100644 --- a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts +++ b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts @@ -6,20 +6,22 @@ import dynamicProto from "@microsoft/dynamicproto-js"; import { IAjaxMonitorPlugin } from "@microsoft/applicationinsights-dependencies-js"; import { - AnalyticsPluginIdentifier, BaseTelemetryPlugin, Event as EventTelemetry, Exception, IAppInsights, IAppInsightsCore, - IAutoExceptionTelemetry, IConfig, IConfigDefaults, IConfiguration, ICookieMgr, ICustomProperties, IDependencyTelemetry, IEventTelemetry, - IExceptionConfig, IExceptionInternal, IExceptionTelemetry, IInstrumentCallDetails, IMetricTelemetry, IPageViewPerformanceTelemetry, - IPageViewPerformanceTelemetryInternal, IPageViewTelemetry, IPageViewTelemetryInternal, IPlugin, IProcessTelemetryContext, - IProcessTelemetryUnloadContext, ITelemetryInitializerHandler, ITelemetryItem, ITelemetryPluginChain, ITelemetryUnloadState, - ITraceTelemetry, InstrumentEvent, Metric, PageView, PageViewPerformance, RemoteDependencyData, TelemetryInitializerFunction, Trace, - _eInternalMessageId, arrForEach, cfgDfBoolean, cfgDfMerge, cfgDfSet, cfgDfString, cfgDfValidate, createDomEvent, - createProcessTelemetryContext, createTelemetryItem, createUniqueNamespace, dataSanitizeString, dumpObj, eLoggingSeverity, eSeverityLevel, - eventOff, eventOn, fieldRedaction, findAllScripts, generateW3CId, getDocument, getExceptionName, getHistory, getLocation, getWindow, - hasHistory, hasWindow, isCrossOriginError, isFunction, isNullOrUndefined, isString, isUndefined, mergeEvtNamespace, onConfigChange, - safeGetCookieMgr, strNotSpecified, strUndefined, throwError, utlDisableStorage, utlEnableStorage, utlSetStoragePrefix + AnalyticsPluginIdentifier, BaseTelemetryPlugin, EventDataType, EventEnvelopeType, Exception, ExceptionDataType, ExceptionEnvelopeType, + IAppInsights, IAppInsightsCore, IAutoExceptionTelemetry, IConfig, IConfigDefaults, IConfiguration, ICookieMgr, ICustomProperties, + IDependencyTelemetry, IDistributedTraceContext, IEventTelemetry, IExceptionConfig, IExceptionInternal, IExceptionTelemetry, + IInstrumentCallDetails, IMetricTelemetry, IPageViewPerformanceTelemetry, IPageViewPerformanceTelemetryInternal, IPageViewTelemetry, + IPageViewTelemetryInternal, IPlugin, IProcessTelemetryContext, IProcessTelemetryUnloadContext, ITelemetryInitializerHandler, + ITelemetryItem, ITelemetryPluginChain, ITelemetryUnloadState, ITraceTelemetry, InstrumentEvent, MetricDataType, MetricEnvelopeType, + PageViewDataType, PageViewEnvelopeType, PageViewPerformanceDataType, PageViewPerformanceEnvelopeType, RemoteDependencyDataType, + TelemetryInitializerFunction, TraceDataType, TraceEnvelopeType, _eInternalMessageId, arrForEach, cfgDfBoolean, cfgDfMerge, cfgDfSet, + cfgDfString, cfgDfValidate, createDistributedTraceContext, createDomEvent, createProcessTelemetryContext, createTelemetryItem, + createUniqueNamespace, dataSanitizeString, dumpObj, eLoggingSeverity, eSeverityLevel, eventOff, eventOn, fieldRedaction, findAllScripts, + generateW3CId, getDocument, getExceptionName, getHistory, getLocation, getWindow, hasHistory, hasWindow, isCrossOriginError, isFunction, + isNullOrUndefined, isString, isUndefined, mergeEvtNamespace, onConfigChange, safeGetCookieMgr, strNotSpecified, strUndefined, throwError, + utlDisableStorage, utlEnableStorage, utlSetStoragePrefix } from "@microsoft/otel-core-js"; import { isArray, isError, objDeepFreeze, objDefine, scheduleTimeout, strIndexOf } from "@nevware21/ts-utils"; -import { IAnalyticsConfig } from "./Interfaces/IAnalyticsConfig"; +import { IAnalyticsConfig, eRouteTraceStrategy } from "./Interfaces/IAnalyticsConfig"; import { IAppInsightsInternal, IPageViewManager, createPageViewManager } from "./Telemetry/PageViewManager"; import { IPageViewPerformanceManager, createPageViewPerformanceManager } from "./Telemetry/PageViewPerformanceManager"; import { IPageVisitTimeManager, createPageVisitTimeManager } from "./Telemetry/PageVisitTimeManager"; @@ -65,7 +67,8 @@ const defaultValues: IConfigDefaults = objDeepFreeze({ enableDebug: cfgDfBoolean(), disableFlushOnBeforeUnload: cfgDfBoolean(), disableFlushOnUnload: cfgDfBoolean(false, "disableFlushOnBeforeUnload"), - expCfg: cfgDfMerge({ inclScripts: false, expLog: undefined, maxLogs: 50 }) + expCfg: cfgDfMerge({inclScripts: false, expLog: undefined, maxLogs: 50}), + routeTraceStrategy: eRouteTraceStrategy.Server }); function _chkConfigMilliseconds(value: number, defValue: number): number { @@ -121,7 +124,10 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights let _extConfig: IAnalyticsConfig; let _autoTrackPageVisitTime: boolean; let _expCfg: IExceptionConfig; - + + // New configuration variables for trace context management + let _routeTraceStrategy: eRouteTraceStrategy; + // array with max length of 2 that store current url and previous url for SPA page route change trackPageview use. let _prevUri: string; // Assigned in the constructor let _currUri: string; @@ -146,8 +152,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights try { let telemetryItem = createTelemetryItem( event, - EventTelemetry.dataType, - EventTelemetry.envelopeType, + EventDataType, + EventEnvelopeType, _self.diagLog(), customProperties ); @@ -202,8 +208,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights try { let telemetryItem = createTelemetryItem( trace, - Trace.dataType, - Trace.envelopeType, + TraceDataType, + TraceEnvelopeType, _self.diagLog(), customProperties); @@ -230,8 +236,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights try { let telemetryItem = createTelemetryItem( metric, - Metric.dataType, - Metric.envelopeType, + MetricDataType, + MetricEnvelopeType, _self.diagLog(), customProperties ); @@ -292,8 +298,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights } let telemetryItem = createTelemetryItem( pageView, - PageView.dataType, - PageView.envelopeType, + PageViewDataType, + PageViewEnvelopeType, _self.diagLog(), properties, systemProperties); @@ -311,8 +317,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights _self.sendPageViewPerformanceInternal = (pageViewPerformance: IPageViewPerformanceTelemetryInternal, properties?: { [key: string]: any }, systemProperties?: { [key: string]: any }) => { let telemetryItem = createTelemetryItem( pageViewPerformance, - PageViewPerformance.dataType, - PageViewPerformance.envelopeType, + PageViewPerformanceDataType, + PageViewPerformanceEnvelopeType, _self.diagLog(), properties, systemProperties); @@ -437,8 +443,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights } let telemetryItem: ITelemetryItem = createTelemetryItem( exceptionPartB, - Exception.dataType, - Exception.envelopeType, + ExceptionDataType, + ExceptionEnvelopeType, _self.diagLog(), customProperties, systemProperties @@ -650,6 +656,9 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights _expCfg = _extConfig.expCfg; _autoTrackPageVisitTime = _extConfig.autoTrackPageVisitTime; + // Initialize new trace context configuration options + _routeTraceStrategy = _extConfig.routeTraceStrategy || eRouteTraceStrategy.Server; + if (config.storagePrefix) { utlSetStoragePrefix(config.storagePrefix); } @@ -681,7 +690,7 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights if (!_browserLinkInitializerAdded && _isBrowserLinkTrackingEnabled) { const browserLinkPaths = ["/browserLinkSignalR/", "/__browserLink/"]; const dropBrowserLinkRequests = (envelope: ITelemetryItem) => { - if (_isBrowserLinkTrackingEnabled && envelope.baseType === RemoteDependencyData.dataType) { + if (_isBrowserLinkTrackingEnabled && envelope.baseType === RemoteDependencyDataType) { let remoteData = envelope.baseData as IDependencyTelemetry; if (remoteData) { for (let i = 0; i < browserLinkPaths.length; i++) { @@ -703,8 +712,8 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights function _sendCORSException(exception: IAutoExceptionTelemetry, properties?: ICustomProperties) { let telemetryItem: ITelemetryItem = createTelemetryItem( exception, - Exception.dataType, - Exception.envelopeType, + ExceptionDataType, + ExceptionEnvelopeType, _self.diagLog(), properties ); @@ -796,20 +805,32 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights // TODO(OTelSpan) (create new "context") / spans for the new page view // Should "end" any previous span (once we have a new one) - let newContext = _self.core.getTraceCtx(true); - // While the above will create a new context instance it doesn't generate a new traceId - // so we need to generate a new one here - newContext.setTraceId(generateW3CId()); + let newContext: IDistributedTraceContext; - // This populates the ai.operation.name which has a maximum size of 1024 so we need to sanitize it - newContext.pageName = dataSanitizeString(_self.diagLog(), newContext.pageName || "_unknown_"); + // Quick and dirty backward compatibility check -- should never be needed but here to avoid a JS exception if (_self.core && _self.core.getTraceCtx) { + let currentContext = _self.core.getTraceCtx(false); // Get current context without creating new + + if (currentContext && _routeTraceStrategy === eRouteTraceStrategy.Page) { + // Create new context with the determined parent + newContext = createDistributedTraceContext(currentContext); + } else { + // Fall back to original behavior - use server context as parent + newContext = _self.core.getTraceCtx(true); + } + + // Always generate new trace ID for route changes (this also generates new span ID) + newContext.traceId = generateW3CId(); + + // This populates the ai.operation.name which has a maximum size of 1024 so we need to sanitize it + newContext.pageName = dataSanitizeString(_self.diagLog(), newContext.pageName || "_unknown_"); + _self.core.setTraceCtx(newContext); } + // Single page view tracking call for all scenarios scheduleTimeout(((uri: string) => { - // todo: override start time so that it is not affected by autoRoutePVDelay - _self.trackPageView({ refUri: uri, properties: { duration: 0 } }); // SPA route change loading durations are undefined, so send 0 + _self.trackPageView({ refUri: uri, properties: { duration: 0 } }); }).bind(_self, _prevUri), _self.autoRoutePVDelay); } } diff --git a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts index 9c5ac7daf..6cff6a0a0 100644 --- a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts +++ b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts @@ -3,6 +3,29 @@ import { IExceptionConfig } from "@microsoft/otel-core-js"; +/** + * Enum values for configuring trace context strategy for SPA route changes. + * Controls how trace contexts are managed when navigating between pages in a Single Page Application. + * @since 3.4.0 + */ +export const enum eRouteTraceStrategy { + /** + * Server strategy: Each page view gets a new, independent trace context. + * No parent-child relationships are created between page views. + * Each page will use the original server-provided trace context (if available) as its parent, + * as defined by the {@link IConfiguration.traceHdrMode} configuration for distributed tracing headers. + * This is the traditional behavior where each page view is treated as a separate operation. + */ + Server = 0, + + /** + * Page strategy: Page views are chained together with parent-child relationships. + * Each new page view inherits the trace context from the previous page view, + * creating a connected chain of related operations for better correlation. + */ + Page = 1 +} + /** * Configuration interface specifically for AnalyticsPlugin * This interface defines only the configuration properties that the Analytics plugin uses. @@ -115,5 +138,14 @@ export interface IAnalyticsConfig { * @default { inclScripts: false, expLog: undefined, maxLogs: 50 } */ expCfg?: IExceptionConfig; + + /** + * Controls the trace context strategy for SPA route changes. + * Determines how trace contexts are managed and correlated across virtual page views + * in Single Page Applications, affecting telemetry correlation and operation tracking. + * @default eRouteTraceStrategy.Server + * @since 3.4.0 + */ + routeTraceStrategy?: eRouteTraceStrategy; } diff --git a/extensions/applicationinsights-cfgsync-js/Tests/Unit/src/cfgsynchelper.tests.ts b/extensions/applicationinsights-cfgsync-js/Tests/Unit/src/cfgsynchelper.tests.ts index 5ce572af7..864e2a2d5 100644 --- a/extensions/applicationinsights-cfgsync-js/Tests/Unit/src/cfgsynchelper.tests.ts +++ b/extensions/applicationinsights-cfgsync-js/Tests/Unit/src/cfgsynchelper.tests.ts @@ -107,9 +107,24 @@ export class CfgSyncHelperTests extends AITestClass { // } //}, traceHdrMode: 3, + traceCfg: { + generalLimits: { + attributeCountLimit: 128 + }, + // spanLimits: { + // attributeCountLimit: 128, + // linkCountLimit: 128, + // eventCountLimit: 128, + // attributePerEventCountLimit: 128, + // attributePerLinkCountLimit: 128 + // }, + serviceName: null, + suppressTracing: false + }, + errorHandlers: {}, enableDebug: false - } - + }; + let core = new AppInsightsCore(); this.onDone(() => { core.unload(false); diff --git a/extensions/applicationinsights-cfgsync-js/package.json b/extensions/applicationinsights-cfgsync-js/package.json index fe98b283f..b621c63bf 100644 --- a/extensions/applicationinsights-cfgsync-js/package.json +++ b/extensions/applicationinsights-cfgsync-js/package.json @@ -60,8 +60,8 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "license": "MIT" } diff --git a/extensions/applicationinsights-dependencies-js/Tests/Unit/src/W3CTraceStateDependency.tests.ts b/extensions/applicationinsights-dependencies-js/Tests/Unit/src/W3CTraceStateDependency.tests.ts index 29995f0a6..84c27c632 100644 --- a/extensions/applicationinsights-dependencies-js/Tests/Unit/src/W3CTraceStateDependency.tests.ts +++ b/extensions/applicationinsights-dependencies-js/Tests/Unit/src/W3CTraceStateDependency.tests.ts @@ -349,7 +349,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "https://httpbin.org/status/200" + url: "http://localhost:9002/README.md" } as any); }, 0); }); @@ -378,7 +378,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { _ensureTraceStateValue(appInsightsCore); // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "httpbin.org"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -387,7 +387,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { method: 'get', headers: headers }; - const url = 'https://httpbin.org/status/200'; + const url = 'http://localhost:9002/README.md'; // Act Assert.ok(trackSpy.notCalled, "No fetch called yet"); @@ -454,7 +454,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "https://httpbin.org/status/200" + url: "http://localhost:9002/README.md" } as any); }, 0); }); @@ -486,7 +486,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { _ensureTraceStateValue(appInsightsCore); // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "httpbin.org"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -495,7 +495,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { method: 'get', headers: headers }; - const url = 'https://httpbin.org/status/200'; + const url = 'http://localhost:9002/README.md'; // Act Assert.ok(trackSpy.notCalled, "No fetch called yet"); @@ -561,7 +561,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "https://httpbin.org/status/200" + url: "http://localhost:9002/README.md" } as any); }, 0); }); @@ -591,7 +591,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { let trackSpy = this.sandbox.spy(appInsightsCore, "track"); // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "httpbin.org"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -600,7 +600,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { method: 'get', headers: headers }; - const url = 'https://httpbin.org/status/200'; + const url = 'http://localhost:9002/README.md'; // Act Assert.ok(trackSpy.notCalled, "No fetch called yet"); @@ -666,7 +666,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "https://httpbin.org/status/200" + url: "http://localhost:9002/README.md" } as any); }, 0); }); @@ -689,7 +689,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { let trackSpy = this.sandbox.spy(appInsightsCore, "track"); // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "httpbin.org"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -698,7 +698,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { method: 'get', headers: headers }; - const url = 'https://httpbin.org/status/200'; + const url = 'http://localhost:9002/README.md'; // Act Assert.ok(trackSpy.notCalled, "No fetch called yet"); @@ -764,7 +764,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "https://httpbin.org/status/200" + url: "http://localhost:9002/README.md" } as any); }, 0); }); @@ -787,7 +787,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { let trackSpy = this.sandbox.spy(appInsightsCore, "track"); // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "httpbin.org"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -796,7 +796,7 @@ export class W3CTraceStateDependencyTests extends AITestClass { method: 'get', headers: headers }; - const url = 'https://httpbin.org/status/200'; + const url = 'http://localhost:9002/README.md'; // Act Assert.ok(trackSpy.notCalled, "No fetch called yet"); diff --git a/extensions/applicationinsights-dependencies-js/Tests/Unit/src/ajax.tests.ts b/extensions/applicationinsights-dependencies-js/Tests/Unit/src/ajax.tests.ts index 4a57cbcf1..2bf54f0a7 100644 --- a/extensions/applicationinsights-dependencies-js/Tests/Unit/src/ajax.tests.ts +++ b/extensions/applicationinsights-dependencies-js/Tests/Unit/src/ajax.tests.ts @@ -1184,14 +1184,14 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: true}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: true}).then(() => { // Assert Assert.ok(fetchSpy.notCalled, "The request was not tracked"); }, () => { @@ -1320,7 +1320,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1334,11 +1334,11 @@ export class AjaxTests extends AITestClass { .add(() => { // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: true}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: true}).then(() => { // Assert Assert.ok(fetchSpy.notCalled, "The initial request was not tracked"); - return fetch("http://localhost:9001/shared/", {method: "post" }).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post" }).then(() => { // Assert Assert.ok(fetchSpy.notCalled, "The follow up request should also not have been tracked"); }, () => { @@ -1367,13 +1367,13 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); this._ajax = new AjaxMonitor(); let appInsightsCore = new AppInsightsCore(); - const ExcludeRequestRegex = ["localhost"]; + const ExcludeRequestRegex = ["localhost:9002"]; let coreConfig = { instrumentationKey: "", disableFetchTracking: false, excludeRequestFromAutoTrackingPatterns: ExcludeRequestRegex }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); // Flush any initial requests made during initialization @@ -1385,11 +1385,11 @@ export class AjaxTests extends AITestClass { .add(() => { // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post"}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post"}).then(() => { // Assert Assert.ok(fetchSpy.notCalled, "The initial request was not tracked"); - return fetch("http://localhost:9001/shared/", {method: "post" }).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post" }).then(() => { // Assert Assert.ok(fetchSpy.notCalled, "The follow up request should also not have been tracked"); }, () => { @@ -1418,7 +1418,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1443,14 +1443,14 @@ export class AjaxTests extends AITestClass { .add(() => { // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { // assert Assert.ok(fetchSpy.calledOnce, "track is called"); let data = fetchSpy.args[0][0].baseData; Assert.equal("Fetch", data.type, "request is Fetch type"); Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called"); Assert.equal("Fetch context", data.properties.test, "Fetch request's request context is added when customer configures addRequestContext."); - Assert.equal("http://localhost:9001/shared/", data.properties.fetchRequestUrl, "Fetch request is captured."); + Assert.equal("http://localhost:9002/shared/", data.properties.fetchRequestUrl, "Fetch request is captured."); Assert.equal("basic", data.properties.fetchResponseType, "Fetch response is captured."); }, () => { Assert.ok(false, "fetch failed!"); @@ -1475,7 +1475,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1491,7 +1491,7 @@ export class AjaxTests extends AITestClass { .add(() => { // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { // Assert Assert.ok(fetchSpy.calledOnce, "createFetchRecord called once after using fetch"); let data = fetchSpy.args[0][0].baseData; @@ -1522,7 +1522,7 @@ export class AjaxTests extends AITestClass { statusText: "Blocked", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1546,7 +1546,7 @@ export class AjaxTests extends AITestClass { let dependencyFields = this._context.dependencyFields; Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { // Assert Assert.ok(fetchSpy.calledOnce, "createFetchRecord called once after using fetch"); let data = fetchSpy.args[0][0].baseData; @@ -1579,7 +1579,7 @@ export class AjaxTests extends AITestClass { statusText: "Blocked", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1603,7 +1603,7 @@ export class AjaxTests extends AITestClass { let dependencyFields = this._context.dependencyFields; Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { // Assert Assert.ok(fetchSpy.calledOnce, "createFetchRecord called once after using fetch"); let data = fetchSpy.args[0][0].baseData; @@ -1637,7 +1637,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1666,7 +1666,7 @@ export class AjaxTests extends AITestClass { .add(() => { // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { // Assert Assert.ok(fetchSpy.calledOnce, "createFetchRecord called once after using fetch"); let data = fetchSpy.args[0][0].baseData; @@ -1699,7 +1699,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1722,7 +1722,7 @@ export class AjaxTests extends AITestClass { // Act Assert.ok(fetchSpy.notCalled, "No fetch called yet"); - return fetch("http://localhost:9001/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { + return fetch("http://localhost:9002/shared/", {method: "post", [DisabledPropertyName]: false}).then(() => { // Assert Assert.ok(initializerCalled, "Initializer was not called"); Assert.ok(fetchSpy.notCalled, "track was not called"); @@ -1751,7 +1751,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1832,7 +1832,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1907,7 +1907,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -1981,7 +1981,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2065,7 +2065,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; let headerSpy = this.sandbox.spy(this._ajax, "includeCorrelationHeaders"); @@ -2114,7 +2114,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'https://httpbin.org/status/200'; + const url = 'http://localhost:9002/shared/'; let headerSpy = this.sandbox.spy(this._ajax, "includeCorrelationHeaders"); @@ -2159,7 +2159,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; let headerSpy = this.sandbox.spy(this._ajax, "includeCorrelationHeaders"); @@ -2190,7 +2190,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2212,7 +2212,7 @@ export class AjaxTests extends AITestClass { this._context["trackStub"] = trackSpy; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; return this._asyncQueue() .add(() => { @@ -2225,7 +2225,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; // Act Assert.ok(trackSpy.notCalled, "No fetch called yet"); @@ -2275,7 +2275,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2297,9 +2297,9 @@ export class AjaxTests extends AITestClass { this._context["trackStub"] = trackSpy; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { @@ -2351,7 +2351,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2374,7 +2374,7 @@ export class AjaxTests extends AITestClass { this._context["trackStub"] = trackSpy; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -2383,7 +2383,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { // Act @@ -2436,7 +2436,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2460,10 +2460,10 @@ export class AjaxTests extends AITestClass { this._context["fetchCalls"] = fetchCalls; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { @@ -2515,7 +2515,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2539,7 +2539,7 @@ export class AjaxTests extends AITestClass { this._context["fetchCalls"] = fetchCalls; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -2548,7 +2548,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { @@ -2600,7 +2600,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2624,9 +2624,9 @@ export class AjaxTests extends AITestClass { this._context["fetchCalls"] = fetchCalls; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { @@ -2677,7 +2677,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -2701,7 +2701,7 @@ export class AjaxTests extends AITestClass { this._context["trackStub"] = trackSpy; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -2711,7 +2711,7 @@ export class AjaxTests extends AITestClass { method: 'get', headers: headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { @@ -2794,7 +2794,7 @@ export class AjaxTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "https://httpbin.org/status/200", + url: "http://localhost:9002/shared/", clone: () => null, arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), blob: () => Promise.resolve(new Blob()), @@ -2824,7 +2824,7 @@ export class AjaxTests extends AITestClass { appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); // Use test hook to simulate the correct url location host to enable correlation headers - this._ajax["_currentWindowHost"] = "httpbin.org"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Set up trace context with known values let traceCtx = appInsightsCore.getTraceCtx(); @@ -2858,13 +2858,13 @@ export class AjaxTests extends AITestClass { try { // Act - make first fetch request (this should trigger header addition) - fetch("https://httpbin.org/status/200", { + fetch("http://localhost:9002/shared/", { method: "GET", headers: { "Custom-Header": "Value1" } }); // Act - make second fetch request - fetch("https://httpbin.org/api/test", { + fetch("https://localhost:9002/api/test", { method: "POST", headers: { "Custom-Header": "Value2" } }); @@ -3290,7 +3290,7 @@ export class AjaxTests extends AITestClass { }); // Use test hook to simulate the correct url location - _ajax2["_currentWindowHost"] = "localhost:9001"; + _ajax2["_currentWindowHost"] = "localhost:9002"; this._ajax = new AjaxMonitor(); @@ -3317,13 +3317,13 @@ export class AjaxTests extends AITestClass { Assert.notEqual(firstTraceId, coreTraceId, "Make sure that the traceId's are different"); // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; return createPromise((resolve) => { // Act var xhr = new XMLHttpRequest(); var spy = this.sandbox.spy(xhr, "setRequestHeader"); - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.onload = () => { try { if (xhr && xhr.readyState === 4) { @@ -3699,7 +3699,7 @@ export class AjaxPerfTrackTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Used to "wait" for App Insights to finish initializing which should complete after the XHR request this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track"); @@ -3710,7 +3710,7 @@ export class AjaxPerfTrackTests extends AITestClass { var xhr = new XMLHttpRequest(); // trigger the request that should cause a track event once the xhr request is complete - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.send(); Assert.equal(false, markSpy.called, "The code should not have called mark()"); }) @@ -3747,7 +3747,11 @@ export class AjaxPerfTrackTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; + + this.onDone(() => { + appInsightsCore.unload(false); + }); this.onDone(() => { appInsightsCore.unload(false); @@ -3762,13 +3766,13 @@ export class AjaxPerfTrackTests extends AITestClass { var xhr = new XMLHttpRequest(); // trigger the request that should cause a track event once the xhr request is complete - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.send(); Assert.equal(true, markSpy.called, "The code should have called been mark()"); this.addPerfEntry({ entryType: "resource", initiatorType: "xmlhttprequest", - name: "http://localhost:9001/shared/", + name: "http://localhost:9002/shared/", startTime: getPerformance().now(), duration: 10 }); @@ -3811,7 +3815,11 @@ export class AjaxPerfTrackTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; + + this.onDone(() => { + appInsightsCore.unload(false); + }); this.onDone(() => { appInsightsCore.unload(false); @@ -3826,13 +3834,13 @@ export class AjaxPerfTrackTests extends AITestClass { var xhr = new XMLHttpRequest(); // trigger the request that should cause a track event once the xhr request is complete - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.send(); Assert.equal(true, markSpy.called, "The code should have called been mark()"); this.addPerfEntry({ entryType: "resource", initiatorType: "xmlhttprequest", - name: "http://localhost:9001/shared/", + name: "http://localhost:9002/shared/", startTime: getPerformance().now(), duration: 10, serverTiming: [ @@ -3885,7 +3893,7 @@ export class AjaxPerfTrackTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; this.onDone(() => { appInsightsCore.unload(false); }); @@ -3930,10 +3938,10 @@ export class AjaxPerfTrackTests extends AITestClass { return this._asyncQueue() .add(() => { // trigger the request that should cause a track event once the xhr request is complete - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.send(); - xhr2.open("GET", "https://localhost:9001/anything"); + xhr2.open("GET", "https://localhost:9002/anything"); xhr2.send(); Assert.equal(true, markSpy.called, "The code should have called been mark()"); @@ -3977,7 +3985,11 @@ export class AjaxPerfTrackTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; + + this.onDone(() => { + appInsightsCore.unload(false); + }); this.onDone(() => { appInsightsCore.unload(false); @@ -3991,7 +4003,7 @@ export class AjaxPerfTrackTests extends AITestClass { // Act var xhr = new XMLHttpRequest(); // trigger the request that should cause a track event once the xhr request is complete - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.send(); Assert.equal(true, markSpy.called, "The code should have called been mark()"); }) @@ -4038,7 +4050,11 @@ export class AjaxPerfTrackTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; + + this.onDone(() => { + appInsightsCore.unload(false); + }); this.onDone(() => { appInsightsCore.unload(false); @@ -4051,7 +4067,7 @@ export class AjaxPerfTrackTests extends AITestClass { // Send fetch request that should trigger a track event when complete Assert.ok(trackSpy.notCalled, "No fetch called yet"); - fetch("http://localhost:9001/shared/", {method: "post", }).then((value) => { + fetch("http://localhost:9002/shared/", {method: "post", }).then((value) => { this._context["fetchComplete"] = true; return value; }); @@ -4089,7 +4105,7 @@ export class AjaxPerfTrackTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -4119,7 +4135,7 @@ export class AjaxPerfTrackTests extends AITestClass { .add(() => { // Send fetch request that should trigger a track event when complete Assert.ok(trackSpy.notCalled, "No fetch called yet"); - fetch("http://localhost:9001/shared/", {method: "post" }); + fetch("http://localhost:9002/shared/", {method: "post" }); Assert.equal(true, markSpy.called, "The code should have called been mark()"); }).add(PollingAssert.asyncTaskPollingAssert(() => { let trackStub = this._context["trackStub"] as SinonStub; @@ -4163,7 +4179,7 @@ export class AjaxPerfTrackTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 500); }); @@ -4193,7 +4209,7 @@ export class AjaxPerfTrackTests extends AITestClass { .add(() => { // Send fetch request that should trigger a track event when complete Assert.ok(trackSpy.notCalled, "No fetch called yet"); - fetch("http://localhost:9001/shared/", { method: "post" }); + fetch("http://localhost:9002/shared/", { method: "post" }); Assert.equal(true, markSpy.called, "The code should have called been mark()"); }) .add(PollingAssert.asyncTaskPollingAssert(() => { @@ -4238,7 +4254,7 @@ export class AjaxPerfTrackTests extends AITestClass { statusText: "Hello", trailer: null, type: "basic", - url: "http://localhost:9001/shared/" + url: "http://localhost:9002/shared/" }); }, 0); }); @@ -4263,7 +4279,7 @@ export class AjaxPerfTrackTests extends AITestClass { this._context["fetchCalls"] = fetchCalls; // Use test hook to simulate the correct url location - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Setup let headers = new Headers(); @@ -4272,7 +4288,7 @@ export class AjaxPerfTrackTests extends AITestClass { method: 'get', headers }; - const url = 'http://localhost:9001/shared/'; + const url = 'http://localhost:9002/shared/'; return this._asyncQueue() .add(() => { @@ -4371,7 +4387,7 @@ export class AjaxFrozenTests extends AITestClass { } }; appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]); - this._ajax["_currentWindowHost"] = "localhost:9001"; + this._ajax["_currentWindowHost"] = "localhost:9002"; // Used to "wait" for App Insights to finish initializing which should complete after the XHR request this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track"); @@ -4388,7 +4404,7 @@ export class AjaxFrozenTests extends AITestClass { } // trigger the request that should cause a track event once the xhr request is complete - xhr.open("GET", "http://localhost:9001/shared/"); + xhr.open("GET", "http://localhost:9002/shared/"); xhr.send(); return this._asyncQueue() @@ -4436,7 +4452,7 @@ export class AjaxFrozenTests extends AITestClass { // testThis._context["_eventsSent"] = events; // } // }); - // this._ajax["_currentWindowHost"] = "httpbin.org"; + // this._ajax["_currentWindowHost"] = "localhost:9002"; // // Used to "wait" for App Insights to finish initializing which should complete after the XHR request // this._context["trackStub"] = this.sandbox.stub(appInsightsCore, "track"); @@ -4452,7 +4468,7 @@ export class AjaxFrozenTests extends AITestClass { // } // // trigger the request that should cause a track event once the xhr request is complete - // xhr.open("GET", "https://httpbin.org/status/200"); + // xhr.open("GET", "http://localhost:9002/shared/"); // xhr.send(); // appInsightsCore.track({ // name: "Hello World!" diff --git a/extensions/applicationinsights-dependencies-js/package.json b/extensions/applicationinsights-dependencies-js/package.json index 5f9f370d3..4d967ac0b 100644 --- a/extensions/applicationinsights-dependencies-js/package.json +++ b/extensions/applicationinsights-dependencies-js/package.json @@ -60,8 +60,8 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "license": "MIT" } diff --git a/extensions/applicationinsights-dependencies-js/src/ajax.ts b/extensions/applicationinsights-dependencies-js/src/ajax.ts index 85395e181..03a765450 100644 --- a/extensions/applicationinsights-dependencies-js/src/ajax.ts +++ b/extensions/applicationinsights-dependencies-js/src/ajax.ts @@ -6,8 +6,8 @@ import { BaseTelemetryPlugin, DisabledPropertyName, IAppInsightsCore, IConfig, IConfigDefaults, IConfiguration, ICorrelationConfig, ICustomProperties, IDependencyTelemetry, IDistributedTraceContext, IInstrumentCallDetails, IInstrumentHooksCallbacks, IPlugin, IProcessTelemetryContext, IRequestContext, ITelemetryContext, ITelemetryItem, ITelemetryPluginChain, InstrumentFunc, InstrumentProto, - PropertiesPluginIdentifier, RemoteDependencyData, RequestHeaders, _eInternalMessageId, _throwInternal, arrForEach, - correlationIdCanIncludeCorrelationHeader, correlationIdGetCorrelationContext, createDistributedTraceContext, + PropertiesPluginIdentifier, RemoteDependencyDataType, RemoteDependencyEnvelopeType, RequestHeaders, _eInternalMessageId, _throwInternal, + arrForEach, correlationIdCanIncludeCorrelationHeader, correlationIdGetCorrelationContext, createDistributedTraceContext, createDistributedTraceContextFromTrace, createProcessTelemetryContext, createTelemetryItem, createTraceParent, createUniqueNamespace, dateTimeUtilsNow, dumpObj, eDistributedTracingModes, eLoggingSeverity, eRequestHeaders, eW3CTraceFlags, eventOn, fieldRedaction, formatTraceParent, generateW3CId, getExceptionName, getGlobal, getIEVersion, getLocation, getPerformance, isFunction, @@ -46,9 +46,9 @@ interface _IInternalDependencyHandler { function _supportsFetch(): (input: RequestInfo, init?: RequestInit) => Promise { let _global = getGlobal(); if (!_global || - isNullOrUndefined((_global as any).Request) || - isNullOrUndefined((_global as any).Request[strPrototype]) || - isNullOrUndefined(_global[STR_FETCH])) { + isNullOrUndefined((_global as any).Request) || + isNullOrUndefined((_global as any).Request[strPrototype]) || + isNullOrUndefined(_global[STR_FETCH])) { return null; } @@ -333,8 +333,6 @@ export interface IDependenciesPlugin extends IDependencyListenerContainer { /** * Interface for ajax data passed to includeCorrelationHeaders function. * Contains the public properties and methods needed for correlation header processing. - * - * @public */ export interface IAjaxRecordData { /** @@ -771,8 +769,8 @@ export class AjaxMonitor extends BaseTelemetryPlugin implements IAjaxMonitorPlug } const item = createTelemetryItem( dependency, - RemoteDependencyData.dataType, - RemoteDependencyData.envelopeType, + RemoteDependencyDataType, + RemoteDependencyEnvelopeType, _self[strDiagLog](), properties, systemProperties); diff --git a/extensions/applicationinsights-dependencies-js/src/applicationinsights-dependencies-js.ts b/extensions/applicationinsights-dependencies-js/src/applicationinsights-dependencies-js.ts index 7459ef7fc..35517130f 100644 --- a/extensions/applicationinsights-dependencies-js/src/applicationinsights-dependencies-js.ts +++ b/extensions/applicationinsights-dependencies-js/src/applicationinsights-dependencies-js.ts @@ -6,4 +6,4 @@ export { } from "./ajax"; export { IDependencyHandler, IDependencyListenerHandler, IDependencyListenerDetails, DependencyListenerFunction } from "./DependencyListener"; export { IDependencyInitializerHandler, IDependencyInitializerDetails, DependencyInitializerFunction } from "./DependencyInitializer"; -export { ICorrelationConfig } from "@microsoft/otel-core-js"; +export { ICorrelationConfig, eDistributedTracingModes, DistributedTracingModes } from "@microsoft/otel-core-js"; diff --git a/extensions/applicationinsights-osplugin-js/Tests/Unit/src/OsPluginTest.ts b/extensions/applicationinsights-osplugin-js/Tests/Unit/src/OsPluginTest.ts index b653f4ade..81d2cc37d 100644 --- a/extensions/applicationinsights-osplugin-js/Tests/Unit/src/OsPluginTest.ts +++ b/extensions/applicationinsights-osplugin-js/Tests/Unit/src/OsPluginTest.ts @@ -2,8 +2,8 @@ * @copyright Microsoft 2024 */ -import { Assert, AITestClass } from '@microsoft/ai-test-framework'; -import { IOSPluginConfiguration, OsPlugin } from '../../../src/applicationinsights-osplugin-js'; +import { Assert, AITestClass } from "@microsoft/ai-test-framework"; +import { IOSPluginConfiguration, OsPlugin } from "../../../src/applicationinsights-osplugin-js"; import { createAsyncPromise, ResolvePromiseHandler, RejectPromiseHandler } from "@nevware21/ts-async"; import {getWindow, AppInsightsCore, IChannelControls, ITelemetryPlugin, IConfiguration, ITelemetryItem} from "@microsoft/otel-core-js"; diff --git a/extensions/applicationinsights-osplugin-js/Tests/Unit/src/index.tests.ts b/extensions/applicationinsights-osplugin-js/Tests/Unit/src/index.tests.ts index da41a7213..31e87f345 100644 --- a/extensions/applicationinsights-osplugin-js/Tests/Unit/src/index.tests.ts +++ b/extensions/applicationinsights-osplugin-js/Tests/Unit/src/index.tests.ts @@ -1,4 +1,4 @@ -import { OsPluginTest } from './OsPluginTest'; +import { OsPluginTest } from "./OsPluginTest"; export function runTests() { new OsPluginTest().registerTests(); diff --git a/extensions/applicationinsights-osplugin-js/package.json b/extensions/applicationinsights-osplugin-js/package.json index fd2add1e5..e170b0ead 100644 --- a/extensions/applicationinsights-osplugin-js/package.json +++ b/extensions/applicationinsights-osplugin-js/package.json @@ -33,8 +33,8 @@ "@microsoft/otel-core-js": "0.0.1-alpha", "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" }, "devDependencies": { "@microsoft/ai-test-framework": "0.0.1", diff --git a/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/MarkMeasureTests.ts b/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/MarkMeasureTests.ts index da2beeb93..4ef2e7ded 100644 --- a/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/MarkMeasureTests.ts +++ b/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/MarkMeasureTests.ts @@ -3,8 +3,8 @@ */ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { ITelemetryItem, AppInsightsCore, IPlugin, IConfiguration, DiagnosticLogger, IAppInsightsCore, ITelemetryPluginChain, doPerf, EventsDiscardedReason, INotificationManager, IPerfEvent} from '@microsoft/otel-core-js'; -import { PerfMarkMeasureManager } from '../../../src/PerfMarkMeasureManager'; +import { ITelemetryItem, AppInsightsCore, IPlugin, IConfiguration, DiagnosticLogger, IAppInsightsCore, ITelemetryPluginChain, doPerf, EventsDiscardedReason, INotificationManager, IPerfEvent} from "@microsoft/otel-core-js"; +import { PerfMarkMeasureManager } from "../../../src/PerfMarkMeasureManager"; export interface PerfMeasures { name: string; diff --git a/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/index.tests.ts b/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/index.tests.ts index a5d266f63..514e657e8 100644 --- a/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/index.tests.ts +++ b/extensions/applicationinsights-perfmarkmeasure-js/Tests/Unit/src/index.tests.ts @@ -1,5 +1,5 @@ -import { MarkMeasureTests } from './MarkMeasureTests'; -import { GlobalTestHooks } from './GlobalTestHooks.Test'; +import { MarkMeasureTests } from "./MarkMeasureTests"; +import { GlobalTestHooks } from "./GlobalTestHooks.Test"; export function runTests() { new GlobalTestHooks().registerTests(); diff --git a/extensions/applicationinsights-perfmarkmeasure-js/package.json b/extensions/applicationinsights-perfmarkmeasure-js/package.json index 61aeb03b5..9ea46a5f8 100644 --- a/extensions/applicationinsights-perfmarkmeasure-js/package.json +++ b/extensions/applicationinsights-perfmarkmeasure-js/package.json @@ -57,7 +57,7 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "license": "MIT" } diff --git a/extensions/applicationinsights-properties-js/Tests/Unit/src/SessionManager.Tests.ts b/extensions/applicationinsights-properties-js/Tests/Unit/src/SessionManager.Tests.ts index ef5b2508e..3aab14d00 100644 --- a/extensions/applicationinsights-properties-js/Tests/Unit/src/SessionManager.Tests.ts +++ b/extensions/applicationinsights-properties-js/Tests/Unit/src/SessionManager.Tests.ts @@ -1,5 +1,5 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { SinonStub } from 'sinon'; +import { SinonStub } from "sinon"; import { AppInsightsCore, DiagnosticLogger, createCookieMgr, newId, dateNow, createDynamicConfig } from "@microsoft/otel-core-js"; import PropertiesPlugin from "../../../src/PropertiesPlugin"; import { _SessionManager } from "../../../src/Context/Session"; diff --git a/extensions/applicationinsights-properties-js/Tests/Unit/src/index.tests.ts b/extensions/applicationinsights-properties-js/Tests/Unit/src/index.tests.ts index be2a55a3f..3c39bdfa3 100644 --- a/extensions/applicationinsights-properties-js/Tests/Unit/src/index.tests.ts +++ b/extensions/applicationinsights-properties-js/Tests/Unit/src/index.tests.ts @@ -3,7 +3,7 @@ import { PropertiesTests } from "./properties.tests"; import { SessionManagerTests } from "./SessionManager.Tests"; import { PropertiesExtensionSizeCheck } from "./propertiesSize.tests"; import { TelemetryContextTests } from "./TelemetryContext.Tests"; -import { GlobalTestHooks } from './GlobalTestHooks.Test'; +import { GlobalTestHooks } from "./GlobalTestHooks.Test"; export function runTests() { new GlobalTestHooks().registerTests(); diff --git a/extensions/applicationinsights-properties-js/Tests/Unit/src/properties.tests.ts b/extensions/applicationinsights-properties-js/Tests/Unit/src/properties.tests.ts index b0f152e47..be4288f10 100644 --- a/extensions/applicationinsights-properties-js/Tests/Unit/src/properties.tests.ts +++ b/extensions/applicationinsights-properties-js/Tests/Unit/src/properties.tests.ts @@ -5,7 +5,7 @@ import { IPropertiesConfig } from "../../../src/Interfaces/IPropertiesConfig"; import { TelemetryContext } from "../../../src/TelemetryContext"; import { IConfig, utlCanUseLocalStorage, utlGetLocalStorage } from "@microsoft/otel-core-js"; import { TestChannelPlugin } from "./TestChannelPlugin"; -import { SinonStub } from 'sinon'; +import { SinonStub } from "sinon"; export class PropertiesTests extends AITestClass { private properties: PropertiesPlugin; diff --git a/extensions/applicationinsights-properties-js/Tests/Unit/src/propertiesSize.tests.ts b/extensions/applicationinsights-properties-js/Tests/Unit/src/propertiesSize.tests.ts index 0db75e0e9..491f1aea4 100644 --- a/extensions/applicationinsights-properties-js/Tests/Unit/src/propertiesSize.tests.ts +++ b/extensions/applicationinsights-properties-js/Tests/Unit/src/propertiesSize.tests.ts @@ -1,6 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { dumpObj } from '@nevware21/ts-utils'; -import { createPromise, doAwait, IPromise } from '@nevware21/ts-async'; +import { dumpObj } from "@nevware21/ts-utils"; +import { createPromise, doAwait, IPromise } from "@nevware21/ts-async"; import * as pako from "pako"; const PACKAGE_JSON = "../package.json"; diff --git a/extensions/applicationinsights-properties-js/package.json b/extensions/applicationinsights-properties-js/package.json index e48e77809..e9c096d62 100644 --- a/extensions/applicationinsights-properties-js/package.json +++ b/extensions/applicationinsights-properties-js/package.json @@ -61,7 +61,7 @@ "@microsoft/dynamicproto-js": "^2.0.3", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/otel-core-js": "0.0.1-alpha", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "license": "MIT" } diff --git a/gruntfile.js b/gruntfile.js index 2ac21e030..fab171934 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -6,14 +6,19 @@ module.exports = function (grunt) { function _removeEs6DynamicProto(code, id) { if (id.endsWith(".js") && id.indexOf("node_modules") === -1) { - console.log("Processing [" + id + "]"); + // console.log("Processing [" + id + "]"); const rEs6DynamicProto = /([\t ]*)(\w+)\([^\)]*\)\s*{(?:\r|\n)+([^\}]*@DynamicProtoStub[^\}]*)(?:\r|\n)+\s*}\s*(?:\r|\n)+/gi; + let processFlag = false; let modifiedCode = code; let changed = false; let match; while ((match = rEs6DynamicProto.exec(code)) !== null) { let prefix = match[1]; let funcName = match[2]; + if (!processFlag) { + console.log("Processing [" + id + "]"); + processFlag = true; + } console.log(" -- Removing [" + funcName + "]"); modifiedCode = modifiedCode.replace(match[0], prefix + "// Removed Stub for " + funcName + "\n"); changed = true; @@ -25,10 +30,10 @@ module.exports = function (grunt) { map: null }; } else { - console.log("No changes made to " + id); + // console.log("No changes made to " + id); } } else { - console.log("Skipping " + id); + // console.log("Skipping " + id); } return null; @@ -309,7 +314,7 @@ module.exports = function (grunt) { connect: { server: { options: { - port: 9001, + port: 9002, base: '.', debug: true } @@ -397,7 +402,7 @@ module.exports = function (grunt) { var addTestRollup = false; var testRoot = ""; if (modules[key].testHttp !== false) { - testRoot = "http://localhost:9001/"; + testRoot = "http://localhost:9002/"; } var testPath = modulePath + "/test"; @@ -807,6 +812,16 @@ module.exports = function (grunt) { }, unitTestName: "index.tests.js" }, + "shims": { + autoMinify: false, + path: "./tools/shims", + cfg: { + src: [ + "./tools/shims/src/*.ts" + ] + }, + unitTestName: "shimstests.js" + }, "chrome-debug-extension": { autoMinify: false, path: "./tools/chrome-debug-extension", diff --git a/package.json b/package.json index f900f4035..0425e22d5 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ }, "homepage": "https://github.com/microsoft/ApplicationInsights-JS#readme", "devDependencies": { - "@microsoft/rush": "5.166.0", + "@microsoft/rush": "5.167.0", "@nevware21/grunt-eslint-ts": "^0.5.1", "@nevware21/grunt-ts-plugin": "^0.5.1", "@typescript-eslint/eslint-plugin": "^5.46.1", diff --git a/rollup.base.config.js b/rollup.base.config.js index 35a306b0d..c7ebb7020 100644 --- a/rollup.base.config.js +++ b/rollup.base.config.js @@ -8,7 +8,6 @@ import dynamicRemove from "@microsoft/dynamicproto-js/tools/rollup"; import { es5Poly, es5Check, importCheck } from "@microsoft/applicationinsights-rollup-es5"; import { resolve } from 'path'; import { existsSync, readFileSync } from "fs"; -import { exists } from "grunt"; const rootVersion = require("./package.json").version; @@ -317,14 +316,19 @@ function getSourceMapPathTransformer(distPath, theNameSpace, isDebug) { function _removeEs6DynamicProto(code, id) { if (id.endsWith(".js") && id.indexOf("node_modules") === -1) { - console.log("Processing [" + id + "]"); + // console.log("Processing [" + id + "]"); const rEs6DynamicProto = /([\t ]*)(\w+)\([^\)]*\)\s*{(?:\r|\n)+([^\}]*@DynamicProtoStub[^\}]*)(?:\r|\n)+\s*}\s*(?:\r|\n)+/gi; + let processFlag = false; let modifiedCode = code; let changed = false; let match; while ((match = rEs6DynamicProto.exec(code)) !== null) { let prefix = match[1]; let funcName = match[2]; + if (!processFlag) { + console.log("Processing [" + id + "]"); + processFlag = true; + } console.log(" -- Removing [" + funcName + "]"); modifiedCode = modifiedCode.replace(match[0], prefix + "// Removed Stub for " + funcName + "\n"); changed = true; @@ -336,10 +340,10 @@ function _removeEs6DynamicProto(code, id) { map: null }; } else { - console.log("No changes made to " + id); + // console.log("No changes made to " + id); } } else { - console.log("Skipping " + id); + // console.log("Skipping " + id); } return null; diff --git a/rush.json b/rush.json index 5fa4a5ae6..7c98ef9bb 100644 --- a/rush.json +++ b/rush.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", "npmVersion": "9.9.3", - "rushVersion": "5.166.0", + "rushVersion": "5.167.0", "projectFolderMaxDepth": 4, "projects": [ { @@ -19,6 +19,16 @@ "projectFolder": "tools/rollup-plugin-uglify3-js", "shouldPublish": true }, + { + "packageName": "@microsoft/applicationinsights-rollup-es5", + "projectFolder": "tools/rollup-es5", + "shouldPublish": true + }, + { + "packageName": "@microsoft/applicationinsights-shims", + "projectFolder": "tools/shims", + "shouldPublish": true + }, { "packageName": "@microsoft/otel-core-js", "projectFolder": "shared/otel-core", @@ -69,11 +79,6 @@ "projectFolder": "AISKU", "shouldPublish": true }, - { - "packageName": "@microsoft/applicationinsights-rollup-es5", - "projectFolder": "tools/rollup-es5", - "shouldPublish": true - }, { "packageName": "@microsoft/applicationinsights-web-basic", "projectFolder": "AISKULight", diff --git a/shared/otel-core/Tests/Unit/src/AppInsights/Common/ThrottleMgr.tests.ts b/shared/otel-core/Tests/Unit/src/AppInsights/Common/ThrottleMgr.tests.ts deleted file mode 100644 index 61d9f68d8..000000000 --- a/shared/otel-core/Tests/Unit/src/AppInsights/Common/ThrottleMgr.tests.ts +++ /dev/null @@ -1,1676 +0,0 @@ -import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { AppInsightsCore, IAppInsightsCore, IConfiguration, IPlugin, _eInternalMessageId } from "../../../../../src/index"; -import { SinonSpy } from "sinon"; -import { ThrottleMgr } from "../../../../../src/diagnostics/ThrottleMgr"; -import { IThrottleInterval, IThrottleLimit, IThrottleMgrConfig, IThrottleResult } from "../../../../../src/interfaces/ai/IThrottleMgr"; -import { utlCanUseLocalStorage } from "../../../../../src/utils/StorageHelperFuncs"; -import { IConfig } from "../../../../../src/index"; - -const daysInMonth = [ - 31, // Jan - 28, // Feb - 31, // Mar - 30, // Apr - 31, // May - 30, // Jun - 31, // Jul - 31, // Aug - 30, // Sep - 31, // Oct - 30, // Nov - 31 // Dec -]; - -const isLeapYear = (year: number) => { - return (year % 4) === 0 && ((year % 100) !== 0 || (year % 400) === 0); -} - -const compareDates = (date1: Date, date: string | Date, expectedSame: boolean = true) => { - let isSame = false; - try { - if (date1 && date) { - let date2 = typeof date == "string"? new Date(date) : date; - isSame = date1.getUTCFullYear() === date2.getUTCFullYear() && - date1.getUTCMonth() === date2.getUTCMonth() && - date1.getUTCDate() === date2.getUTCDate(); - } - } catch (e) { - Assert.ok(false,"compare dates error" + e); - } - Assert.equal(isSame, expectedSame, "checking that the dates where as expected"); -} - -export class ThrottleMgrTest extends AITestClass { - private _core: IAppInsightsCore; - private _msgKey: number; - private _storageName: string; - private _msgId: _eInternalMessageId; - private loggingSpy: SinonSpy; - private _channel; - - public testInitialize() { - this._core = new AppInsightsCore(); - this._msgKey = _eInternalMessageId.InstrumentationKeyDeprecation; - this._storageName = "appInsightsThrottle-" + this._msgKey; - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - this._msgId = _eInternalMessageId.InstrumentationKeyDeprecation; - this._channel = new ChannelPlugin(); - - if (utlCanUseLocalStorage()) { - window.localStorage.clear(); - } - } - - public testCleanup() { - if (utlCanUseLocalStorage()) { - window.localStorage.clear(); - } - - this.loggingSpy = null; - } - - public registerTests() { - - this.testCase({ - name: "ThrottleMgrTest: Default config should be set from root", - test: () => { - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test" - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedconfig, actualConfig[_eInternalMessageId.DefaultThrottleMsgKey], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Default config should be set from root with key", - test: () => { - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: {}} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - Assert.deepEqual(expectedconfig, actualConfig[_eInternalMessageId.DefaultThrottleMsgKey], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple msg keys - Default config should be set from root", - test: () => { - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - - isTriggered = throttleMgr.isTriggered(109); - Assert.equal(isTriggered, false, "should not be triggered msg key 109"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: None set msg keys - Default config should be used", - test: () => { - let date = new Date(); - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[_eInternalMessageId.DefaultThrottleMsgKey]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedconfig, actualConfig[_eInternalMessageId.DefaultThrottleMsgKey], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true, "should be able to throttle"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Config should be updated dynamically", - useFakeTimers: true, - test: () => { - let date = new Date(); - let config = { - disabled: false, - limit: { - samplingRate: 50, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(config, actualConfig[this._msgId], "should get expected config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle"); - - config.disabled = true; - config.limit = { - samplingRate: 80, - maxSendNumber: 10 - } as IThrottleLimit, - config.interval = { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate() + 1] - } as IThrottleInterval; - this._core.config.throttleMgrCfg = this._core.config.throttleMgrCfg || {}; - this._core.config.throttleMgrCfg[this._msgId] = config; - this.clock.tick(1); - actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(config, actualConfig[this._msgId], "config should be updated dynamically"); - canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, false, "should not be able to throttle"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Config should be updated dynamically with different keys", - useFakeTimers: true, - test: () => { - let date = new Date(); - let msgId = 109; - let config = { - disabled: false, - limit: { - samplingRate: 50, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let config1 = { - disabled: false, - limit: { - samplingRate: 60, - maxSendNumber: 101 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config, [msgId]: config1} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.ok(actualConfig[_eInternalMessageId.DefaultThrottleMsgKey]); - Assert.deepEqual(config, actualConfig[this._msgId], "should get expected config"); - Assert.deepEqual(config1, actualConfig[msgId], "should get expected config1"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle"); - isTriggered = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggered, false, "should not be triggered test1"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle test1"); - - config.disabled = true; - config.limit = { - samplingRate: 80, - maxSendNumber: 10 - } as IThrottleLimit, - config.interval = { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate() + 1] - } as IThrottleInterval; - this._core.config.throttleMgrCfg = this._core.config.throttleMgrCfg || {}; - this._core.config.throttleMgrCfg[this._msgId] = config; - this.clock.tick(1); - actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(config, actualConfig[this._msgId], "config should be updated dynamically"); - Assert.deepEqual(config1, actualConfig[msgId], "config1 should not be updated dynamically"); - canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, false, "should not be able to throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle config1"); - } - }); - - - this.testCase({ - name: "ThrottleMgrTest: flush", - test: () => { - let date = new Date(); - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 10 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgKey); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgKey); - Assert.equal(canThrottle, true, "should be able to be throttle"); - - let isReady = throttleMgr.isReady(); - Assert.equal(isReady, false, "isReady state should be false"); - let result = throttleMgr.sendMessage(this._msgId, "test"); - Assert.equal(result, null, "should not be throttled"); - // note: _getDbgPlgTargets returns array - let target = throttleMgr["_getDbgPlgTargets"](); - Assert.ok(target && target.length === 1, "target should contain queue"); - let queue = target[0][this._msgKey]; - Assert.deepEqual(queue.length,1, "should have 1 item"); - Assert.equal(queue[0].msgID, this._msgId, "should be correct msgId"); - - throttleMgr.onReadyState(true); - target = throttleMgr["_getDbgPlgTargets"](); - queue = target[0][this._msgKey]; - Assert.equal(queue.length, 0, "queue should be empty"); - let storage = window.localStorage[this._storageName]; - let dateNum = date.getUTCDate(); - let prefix = dateNum < 10? "0":""; - Assert.ok(storage.indexOf(`${date.getUTCMonth() + 1}-${prefix + dateNum}`) > -1, "local storage should have correct date"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple msg keys - flush", - test: () => { - let msgId = 109; - let storageName = this._storageName = "appInsightsThrottle-" + msgId; - let date = new Date(); - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 10 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedconfig, [msgId]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - Assert.deepEqual(expectedconfig, actualConfig[msgId], "should get expected default config msgId"); - let isTriggered = throttleMgr.isTriggered(this._msgKey); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgKey); - Assert.equal(canThrottle, true, "should be able to be throttle"); - isTriggered = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggered, false, "should not be triggered test1"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.equal(canThrottle, true, "should be able to be throttle test1"); - - let isReady = throttleMgr.isReady(); - Assert.equal(isReady, false, "isReady state should be false"); - let result = throttleMgr.sendMessage(this._msgId, "test"); - Assert.equal(result, null, "should not be throttled"); - result = throttleMgr.sendMessage(msgId, "test1"); - Assert.equal(result, null, "should not be throttled test1"); - // note: _getDbgPlgTargets returns array - let target = throttleMgr["_getDbgPlgTargets"](); - Assert.ok(target && target.length === 1, "target should contain queue"); - let queue = target[0][this._msgKey]; - Assert.deepEqual(queue.length,1, "should have 1 item"); - Assert.equal(queue[0].msgID, this._msgId, "should be correct msgId"); - queue = target[0][msgId]; - Assert.deepEqual(queue.length,1, "should have 1 item test1"); - Assert.equal(queue[0].msgID, msgId, "should be correct msgId test1"); - - throttleMgr.onReadyState(true); - target = throttleMgr["_getDbgPlgTargets"](); - queue = target[0][this._msgKey]; - Assert.equal(queue.length, 0, "queue should be empty"); - let storage = window.localStorage[this._storageName]; - let dateNum = date.getUTCDate(); - let prefix = dateNum < 10? "0":""; - Assert.ok(storage.indexOf(`${date.getUTCMonth() + 1}-${prefix + dateNum}`) > -1, "local storage should have correct date"); - - target = throttleMgr["_getDbgPlgTargets"](); - queue = target[0][msgId]; - Assert.equal(queue.length, 0, "queue should be empty test1"); - storage = window.localStorage[storageName]; - dateNum = date.getUTCDate(); - prefix = dateNum < 10? "0":""; - Assert.ok(storage.indexOf(`${date.getUTCMonth() + 1}-${prefix + dateNum}`) > -1, "local storage should have correct date test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager can get expected config", - test: () => { - let config = { - disabled: false, - limit: { - samplingRate: 50, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 2, - dayInterval: 10, - daysOfMonth: undefined - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(config, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgKey); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager can get default config", - test: () => { - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: {}} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: monthInterval should be set to 3 when dayInterval and monthInterval are both undefined", - test: () => { - - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - daysOfMonth: [25, 26, 28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [25, 26, 28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: monthInterval and daysOfMonth should be changed to default when dayInterval is defined", - test: () => { - - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - dayInterval: 100 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: undefined, - dayInterval: 100, - daysOfMonth: undefined - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: message can be sent from the first day when dayInterval is set to one", - test: () => { - - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: undefined, - dayInterval: 1, - daysOfMonth: undefined - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - - let canSend = throttleMgr.canThrottle(this._msgId); - Assert.equal(canSend, true, "can send message from the day") - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager should trigger when current date is in daysOfMonth", - test: () => { - let date = new Date(); - let day = date.getUTCDate(); - let daysOfMonth; - if (day == 1) { - daysOfMonth = [31,day]; - } else { - daysOfMonth = [day-1, day]; - } - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: 28, - daysOfMonth: daysOfMonth - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(config, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true, "should throttle"); - - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager should trigger when interval config is undefined and current date 28", - test: () => { - let date = new Date(); - let day = date.getUTCDate(); - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedConfig} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig(); - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - - let shouldTrigger = false; - if (day === 28) { - shouldTrigger = true; - } - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, shouldTrigger, "should only throttle on 28th"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when disabled is true", - test: () => { - let config = { - disabled: true - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when month interval requirements are not meet", - test: () => { - let date = new Date(); - let month = date.getUTCMonth(); - let year = date.getUTCFullYear(); - if (month == 0) { - date.setUTCFullYear(year-1); - date.setUTCMonth(11); - } else { - let dayOfMonth = date.getDate(); - if (dayOfMonth > (daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 ))) { - date.setDate(daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 )); - } - date.setUTCMonth(month-1); - } - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when year and month interval requirements are not meet", - test: () => { - let date = new Date(); - let month = date.getUTCMonth(); - let year = date.getUTCFullYear(); - - if (month == 0) { - date.setUTCFullYear(year-2); - date.setUTCMonth(11); - } else { - date.setUTCFullYear(year-1); - let dayOfMonth = date.getDate(); - if (dayOfMonth > (daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 ))) { - date.setDate(daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 )); - } - date.setUTCMonth(month-1); - } - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when day interval requirements are not meet", - test: () => { - let date = new Date(); - let curDate = date.getUTCDate(); - let curMonth = date.getUTCMonth(); - if (curDate === 1) { - curMonth -= 1; - curDate = 28; - } else { - curDate -= 1; - } - date.setUTCDate(curDate); - date.setUTCMonth(curMonth); - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 31 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should trigger throttle at starting month", - test: () => { - let date = new Date(); - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should trigger throttle when month and day intervals are meet", - test: () => { - let date = new Date(); - let year = date.getUTCFullYear() - 2; - date.setUTCFullYear(year); - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 4, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when _isTrigger state is true (in valid send message date)", - test: () => { - let storageObj = { - date: new Date(), - count: 0, - preTriggerDate: new Date() - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.ok(isTriggered); - let result = throttleMgr.sendMessage(this._msgId, "test"); - let count = this.loggingSpy.callCount; - Assert.equal(count,0); - Assert.deepEqual(result, null); - let postIsTriggered = throttleMgr.isTriggered(this._msgId); - Assert.ok(postIsTriggered); - - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should update local storage (in valid send message date)", - test: () => { - let date = new Date(); - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - - let preTriggerDate = date; - preTriggerDate.setUTCFullYear(date.getUTCFullYear() - 1); - let preStorageObj = { - date: date, - count: 0, - preTriggerDate: preTriggerDate - } - - let preStorageVal = JSON.stringify(preStorageObj); - window.localStorage[this._storageName] = preStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - let isPretriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isPretriggered, false); - - throttleMgr.onReadyState(true); - let sendDate = new Date(); - let result = throttleMgr.sendMessage(this._msgId, msg); - let expectedRetryRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult; - Assert.deepEqual(result, expectedRetryRlt); - let isPostTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isPostTriggered, true); - - let afterTriggered = window.localStorage[this._storageName]; - let afterTriggeredObj = JSON.parse(afterTriggered); - compareDates(date, afterTriggeredObj.date) - Assert.equal(0, afterTriggeredObj.count); - compareDates(sendDate, afterTriggeredObj.preTriggerDate); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger sendmessage when isready state is not set and should flush message after isReady state is set", - test: () => { - - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let date = new Date(); - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle); - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false); - let initialVal = window.localStorage[this._storageName]; - let initObj = JSON.parse(initialVal); - compareDates(date, initObj.date); - Assert.equal(0, initObj.count); - Assert.equal(undefined, initObj.preTriggerDate); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount; - Assert.equal(count,0); - Assert.deepEqual(result, null); - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, false); - let postVal = window.localStorage[this._storageName]; - let postObj = JSON.parse(postVal); - compareDates(date, postObj.date); - Assert.equal(0, postObj.count); - Assert.equal(undefined, postObj.preTriggerDate); - - let isFlushed = throttleMgr.onReadyState(true); - Assert.ok(isFlushed); - let isTriggeredAfterReadySate = throttleMgr.isTriggered(this._msgId); - Assert.ok(isTriggeredAfterReadySate); - let postOnReadyVal = window.localStorage[this._storageName]; - let postOnReadyObj = JSON.parse(postOnReadyVal); - compareDates(date, postOnReadyObj.date); - Assert.equal(0, postOnReadyObj.count); - compareDates(date, postOnReadyObj.preTriggerDate); - - let onReadyResult = throttleMgr.sendMessage(this._msgId, msg); - let onReadyCount = this.loggingSpy.callCount; - let expectedRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.equal(onReadyCount,1, "test1"); - Assert.deepEqual(onReadyResult, expectedRlt); - let onReadyIsTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(onReadyIsTriggered, true); - let afterResendVal = window.localStorage[this._storageName]; - let afterResendObj = JSON.parse(afterResendVal); - compareDates(date, afterResendObj.date); - Assert.equal(1, afterResendObj.count); - compareDates(date, afterResendObj.preTriggerDate); - } - }); - - - this.testCase({ - name: "ThrottleMgrTest: throw messages with correct number", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let date = new Date(); - let testStorageObj = { - date: date, - count: 5 - } - let testStorageVal = JSON.stringify(testStorageObj); - window.localStorage[this._storageName] = testStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt); - - let val = window.localStorage[this._storageName]; - let obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count); - compareDates(date, obj.preTriggerDate); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple keys - throw messages with correct number", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - let storageName = "appInsightsThrottle-" + msgId; - let date = new Date(); - let testStorageObj = { - date: date, - count: 5 - } - let testStorageVal = JSON.stringify(testStorageObj); - window.localStorage[this._storageName] = testStorageVal; - window.localStorage[storageName] = testStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config, [msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "preTrigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "preTrigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let val = window.localStorage[this._storageName]; - let obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count"); - compareDates(date, obj.preTriggerDate); - val = window.localStorage[storageName]; - obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count test1"); - compareDates(date, obj.preTriggerDate); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: None set mutiple keys - throw messages with correct number", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - let storageName = "appInsightsThrottle-" + msgId; - let date = new Date(); - let testStorageObj = { - date: date, - count: 5 - } - let testStorageVal = JSON.stringify(testStorageObj); - window.localStorage[this._storageName] = testStorageVal; - window.localStorage[storageName] = testStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[_eInternalMessageId.DefaultThrottleMsgKey]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "preTrigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "preTrigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let val = window.localStorage[this._storageName]; - let obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count"); - compareDates(date, obj.preTriggerDate); - val = window.localStorage[storageName]; - obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count test1"); - compareDates(date, obj.preTriggerDate); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: should throw messages 1 time within a day", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1); - - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true); - let canThrottlePost = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottlePost, true); - - let retryRlt = throttleMgr.sendMessage(this._msgId, msg); - let retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,1); - let expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple msg keys - should throw messages 1 time within a day", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config, [msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "is trigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "is trigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult; - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - let canThrottlePost = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottlePost, true, "can throttle post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - canThrottlePost = throttleMgr.canThrottle(msgId); - Assert.equal(canThrottlePost, true, "can throttle post test1"); - - let retryRlt = throttleMgr.sendMessage(this._msgId, msg); - let retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count"); - let expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result"); - - retryRlt = throttleMgr.sendMessage(msgId, msg); - retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count test1"); - expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: None set mutiple msg keys - should throw messages 1 time within a day", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[_eInternalMessageId.DefaultThrottleMsgKey]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "is trigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "is trigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult; - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - let canThrottlePost = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottlePost, true, "can throttle post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - canThrottlePost = throttleMgr.canThrottle(msgId); - Assert.equal(canThrottlePost, true, "can throttle post test1"); - - let retryRlt = throttleMgr.sendMessage(this._msgId, msg); - let retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count"); - let expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result"); - - retryRlt = throttleMgr.sendMessage(msgId, msg); - retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count test1"); - expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result test1"); - } - }); - } -} - - -class ChannelPlugin implements IPlugin { - - public isFlushInvoked = false; - public isTearDownInvoked = false; - public isResumeInvoked = false; - public isPauseInvoked = false; - - public identifier = "Sender"; - - public priority: number = 1001; - - constructor() { - this.processTelemetry = this._processTelemetry.bind(this); - } - public pause(): void { - this.isPauseInvoked = true; - } - - public resume(): void { - this.isResumeInvoked = true; - } - - public teardown(): void { - this.isTearDownInvoked = true; - } - - flush(async?: boolean, callBack?: () => void): void { - this.isFlushInvoked = true; - if (callBack) { - callBack(); - } - } - - public processTelemetry(env: any) {} - - setNextPlugin(next: any) { - // no next setup - } - - public initialize = (config: IConfiguration, core: IAppInsightsCore, plugin: IPlugin[]) => { - } - - private _processTelemetry(env: any) { - - } -} diff --git a/shared/otel-core/Tests/Unit/src/AppInsights/Common/Util.tests.ts b/shared/otel-core/Tests/Unit/src/AppInsights/Common/Util.tests.ts deleted file mode 100644 index 005791ebf..000000000 --- a/shared/otel-core/Tests/Unit/src/AppInsights/Common/Util.tests.ts +++ /dev/null @@ -1,572 +0,0 @@ -import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { ICorrelationConfig } from "../../../../../src/interfaces/ai/ICorrelationConfig"; -import { strStartsWith } from "@nevware21/ts-utils"; -import { correlationIdCanIncludeCorrelationHeader } from "../../../../../src/utils/Util"; -import { createDomEvent } from "../../../../../src/utils/DomHelperFuncs"; -import { urlParseFullHost, urlParseHost, urlParseUrl } from "../../../../../src/utils/UrlHelperFuncs"; -import { getIEVersion } from "../../../../../src/utils/EnvUtils"; -import { uaDisallowsSameSiteNone } from "../../../../../src/core/CookieMgr"; - -export class UtilTests extends AITestClass { - private testRegexLists = (config: ICorrelationConfig, exp: boolean, host: string) => { - let requestUrl = host; - if (!strStartsWith(host, "http")) { - requestUrl = "https://" + host; - } - Assert.equal(exp, correlationIdCanIncludeCorrelationHeader(config, requestUrl, "not used"), host); - }; - - public testInitialize() { - } - - public testCleanup() {} - - public registerTests() { - this.testCase({ - name: 'createDomEvent: creates new event if constructor is undefined', - test: () => { - const origEvent = (window as any).Event; - (window as any).Event = {}; - const event = createDomEvent('something'); - Assert.equal('something', event.type); - (window as any).Event = origEvent; - } - }); - - this.testCase({ - name: "UrlHelper: parseUrl should contain host field even if document.createElement is not defined", - test: () => { - const origCreateElement = document.createElement; - document.createElement = null; - - let passed; - let match; - try { - const host = urlParseUrl("https://portal.azure.com/some/endpoint").host.toLowerCase(); - passed = true; - match = host === "portal.azure.com"; - } catch (e) { - passed = false; - } - - // Need to reset createElement before doing any assertions, else qunit crashes - document.createElement = origCreateElement; - Assert.ok(passed); - Assert.ok(match, "host should be portal.azure.com"); - } - }); - - this.testCase({ - name: "UrlHelper: parseHost should return correct host name", - test: () => { - Assert.equal("portal.azure.com", urlParseHost("https://portal.azure.com/some/endpoint")); - Assert.equal("bing.com", urlParseHost("http://www.bing.com")); - Assert.equal("bing.com", urlParseHost("https://www2.bing.com/")); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com/")); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW21.p.r.e.f.i.x.bing.com/")); - - Assert.equal("portal.azure.com", urlParseHost("https://portal.azure.com/some/endpoint", false)); - Assert.equal("bing.com", urlParseHost("http://www.bing.com", false)); - Assert.equal("bing.com", urlParseHost("https://www2.bing.com/", false)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com/", false)); - Assert.equal("bing.com", urlParseHost("https://www21.bing.com/", false)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW21.p.r.e.f.i.x.bing.com/", false)); - Assert.equal("bing.com", urlParseHost("https://www54321.bing.com/", false)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW54321.p.r.e.f.i.x.bing.com/", false)); - Assert.equal("www654321.bing.com", urlParseHost("https://www654321.bing.com/", false)); - Assert.equal("wwW654321.p.r.e.f.i.x.bing.com", urlParseHost("http://wwW654321.p.r.e.f.i.x.bing.com/", false)); - - Assert.equal("portal.azure.com", urlParseHost("https://portal.azure.com/some/endpoint", true)); - Assert.equal("bing.com", urlParseHost("http://www.bing.com", true)); - Assert.equal("bing.com", urlParseHost("https://www2.bing.com/", true)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com/", true)); - - // Check with port included - Assert.equal("portal.azure.com", urlParseHost("https://portal.azure.com:9999/some/endpoint")); - Assert.equal("bing.com", urlParseHost("http://www.bing.com:9999")); - Assert.equal("bing.com", urlParseHost("https://www2.bing.com:9999/")); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com:9999/")); - - Assert.equal("portal.azure.com", urlParseHost("https://portal.azure.com:9999/some/endpoint", false)); - Assert.equal("bing.com", urlParseHost("http://www.bing.com:9999", false)); - Assert.equal("bing.com", urlParseHost("https://www2.bing.com:9999/", false)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com:9999/", false)); - - Assert.equal("portal.azure.com:9999", urlParseHost("https://portal.azure.com:9999/some/endpoint", true)); - Assert.equal("bing.com:9999", urlParseHost("http://www.bing.com:9999", true)); - Assert.equal("bing.com:9999", urlParseHost("https://www2.bing.com:9999/", true)); - Assert.equal("p.r.e.f.i.x.bing.com:9999", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com:9999/", true)); - - // Check with default ports present - Assert.equal("portal.azure.com", urlParseHost("http://portal.azure.com:80/some/endpoint", true)); - Assert.equal("portal.azure.com", urlParseHost("https://portal.azure.com:443/some/endpoint", true)); - Assert.equal("portal.azure.com:80", urlParseHost("https://portal.azure.com:80/some/endpoint", true)); - Assert.equal("portal.azure.com:443", urlParseHost("http://portal.azure.com:443/some/endpoint", true)); - Assert.equal("bing.com", urlParseHost("http://www.bing.com:80", true)); - Assert.equal("bing.com", urlParseHost("https://www2.bing.com:443/", true)); - Assert.equal("bing.com:80", urlParseHost("https://www.bing.com:80", true)); - Assert.equal("bing.com:443", urlParseHost("http://www2.bing.com:443/", true)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com:80/", true)); - Assert.equal("p.r.e.f.i.x.bing.com", urlParseHost("https://wwW2.p.r.e.f.i.x.bing.com:443/", true)); - Assert.equal("p.r.e.f.i.x.bing.com:443", urlParseHost("http://wwW2.p.r.e.f.i.x.bing.com:443/", true)); - Assert.equal("p.r.e.f.i.x.bing.com:80", urlParseHost("https://wwW2.p.r.e.f.i.x.bing.com:80/", true)); - } - }); - - this.testCase({ - name: "UrlHelper: parseFullHost should return correct host name", - test: () => { - Assert.equal("portal.azure.com", urlParseFullHost("https://portal.azure.com/some/endpoint")); - Assert.equal("www.bing.com", urlParseFullHost("http://www.bing.com")); - Assert.equal("www2.bing.com", urlParseFullHost("https://www2.bing.com/")); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com/")); - - Assert.equal("portal.azure.com", urlParseFullHost("https://portal.azure.com/some/endpoint", false)); - Assert.equal("www.bing.com", urlParseFullHost("http://www.bing.com", false)); - Assert.equal("www2.bing.com", urlParseFullHost("https://www2.bing.com/", false)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com/", false)); - - Assert.equal("portal.azure.com", urlParseFullHost("https://portal.azure.com/some/endpoint", true)); - Assert.equal("www.bing.com", urlParseFullHost("http://www.bing.com", true)); - Assert.equal("www2.bing.com", urlParseFullHost("https://www2.bing.com/", true)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com/", true)); - - // Check with port included - Assert.equal("portal.azure.com", urlParseFullHost("https://portal.azure.com:9999/some/endpoint")); - Assert.equal("www.bing.com", urlParseFullHost("http://www.bing.com:9999")); - Assert.equal("www2.bing.com", urlParseFullHost("https://www2.bing.com:9999/")); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com:9999/")); - - Assert.equal("portal.azure.com", urlParseFullHost("https://portal.azure.com:9999/some/endpoint", false)); - Assert.equal("www.bing.com", urlParseFullHost("http://www.bing.com:9999", false)); - Assert.equal("www2.bing.com", urlParseFullHost("https://www2.bing.com:9999/", false)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com:9999/", false)); - - Assert.equal("portal.azure.com:9999", urlParseFullHost("https://portal.azure.com:9999/some/endpoint", true)); - Assert.equal("www.bing.com:9999", urlParseFullHost("http://www.bing.com:9999", true)); - Assert.equal("www2.bing.com:9999", urlParseFullHost("https://www2.bing.com:9999/", true)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com:9999", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com:9999/", true)); - - // Check with default ports present - Assert.equal("portal.azure.com", urlParseFullHost("http://portal.azure.com:80/some/endpoint", true)); - Assert.equal("portal.azure.com", urlParseFullHost("https://portal.azure.com:443/some/endpoint", true)); - Assert.equal("portal.azure.com:80", urlParseFullHost("https://portal.azure.com:80/some/endpoint", true)); - Assert.equal("portal.azure.com:443", urlParseFullHost("http://portal.azure.com:443/some/endpoint", true)); - Assert.equal("www.bing.com", urlParseFullHost("http://www.bing.com:80", true)); - Assert.equal("www2.bing.com", urlParseFullHost("https://www2.bing.com:443/", true)); - Assert.equal("www.bing.com:80", urlParseFullHost("https://www.bing.com:80", true)); - Assert.equal("www2.bing.com:443", urlParseFullHost("http://www2.bing.com:443/", true)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com:80/", true)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com", urlParseFullHost("https://wwW2.p.r.e.f.i.x.bing.com:443/", true)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com:443", urlParseFullHost("http://wwW2.p.r.e.f.i.x.bing.com:443/", true)); - Assert.equal("wwW2.p.r.e.f.i.x.bing.com:80", urlParseFullHost("https://wwW2.p.r.e.f.i.x.bing.com:80/", true)); - } - }); - - this.testCase({ - name: "CorrelationidHelper: canIncludeCorrelationHeader should follow included domains", - test: () => { - const config = { - enableCorsCorrelation: true, - correlationHeaderDomains: ["azure.com", "prefix.bing.com"] - } as ICorrelationConfig - this.testRegexLists(config, false, "test"); - this.testRegexLists(config, true, "portal.azure.com"); - this.testRegexLists(config, true, "azure.com"); - this.testRegexLists(config, false, "localhost"); - this.testRegexLists(config, false, "bing.com"); - this.testRegexLists(config, true, "prefix.bing.com"); - } - }); - - this.testCase({ - name: "CorrelationidHelper: canIncludeCorrelationHeader should follow excluded domains", - test: () => { - const config = { - enableCorsCorrelation: true, - correlationHeaderExcludedDomains: ["test", "*.azure.com"] - } as ICorrelationConfig - this.testRegexLists(config, false, "test"); - this.testRegexLists(config, false, "portal.azure.com"); - this.testRegexLists(config, true, "azure.com"); - this.testRegexLists(config, true, "localhost"); - this.testRegexLists(config, true, "bing.com"); - this.testRegexLists(config, true, "prefix.bing.com"); - } - }); - - this.testCase({ - name: "CorrelationidHelper: canIncludeCorrelationHeader should check excluded domains if included domains list is also provided", - test: () => { - const config = { - enableCorsCorrelation: true, - correlationHeaderExcludedDomains: ["test", "*.azure.com", "ignore.microsoft.com"], - correlationHeaderDomains: ["azure.com", "prefix.bing.com", "*.microsoft.com"] - } as ICorrelationConfig - this.testRegexLists(config, false, "test"); - this.testRegexLists(config, false, "portal.azure.com"); - this.testRegexLists(config, true, "azure.com"); - this.testRegexLists(config, false, "localhost"); - this.testRegexLists(config, false, "bing.com"); - this.testRegexLists(config, true, "prefix.bing.com"); - this.testRegexLists(config, false, "ignore.microsoft.com"); - this.testRegexLists(config, false, "should.ignore.microsoft.com"); - this.testRegexLists(config, true, "something.microsoft.com"); - this.testRegexLists(config, false, "microsoft.com"); - } - }); - - this.testCase({ - name: "CorrelationidHelper: canIncludeCorrelationHeader check when the url includes the port", - test: () => { - const config = { - enableCorsCorrelation: true, - correlationHeaderExcludedDomains: ["test", "*.azure.com", "ignore.microsoft.com"], - correlationHeaderDomains: ["azure.com", "prefix.bing.com", "*.microsoft.com"] - } as ICorrelationConfig - - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com:80", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "http://azure.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com:8000", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://monitor.azure.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://monitor.azure.com:443", "example.com")); - - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "http://prefix.bing.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:80", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:8000", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://ignore.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com:80", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com:8080", "example.com")); - - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "http://something.microsoft.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:80", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:8000", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:80", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:8000", "example.com")); - } - }); - - this.testCase({ - name: "CorrelationidHelper: canIncludeCorrelationHeader check when the url and include includes the port", - test: () => { - const config = { - enableCorsCorrelation: true, - correlationHeaderExcludedDomains: ["test", "*.azure.com", "ignore.microsoft.com"], - correlationHeaderDomains: ["azure.com", "prefix.bing.com", "*.microsoft.com:8080"] - } as ICorrelationConfig - - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com:80", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "http://azure.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://azure.com:8000", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://monitor.azure.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://monitor.azure.com:443", "example.com")); - - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "http://prefix.bing.com:443", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:80", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:8000", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://ignore.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com:80", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://ignore.microsoft.com:8080", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://something.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:80", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:8000", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:8080", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:80", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:8000", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:8080", "example.com")); - } - }); - - this.testCase({ - name: "CorrelationidHelper: canIncludeCorrelationHeader check when the url includes the port and disabled CorsCorrelation", - test: () => { - const config = { - enableCorsCorrelation: false, - correlationHeaderExcludedDomains: ["test", "*.azure.com", "ignore.microsoft.com"], - correlationHeaderDomains: ["azure.com", "prefix.bing.com", "*.microsoft.com", "example.com"] - } as ICorrelationConfig - - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://example.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://example.com:80", "example.com")); - Assert.equal(true, correlationIdCanIncludeCorrelationHeader(config, "https://example.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://example.com:8000", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://example.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://example.com:8080", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://monitor.azure.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://monitor.azure.com:443", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://prefix.bing.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://prefix.bing.com:80", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://something.microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://something.microsoft.com:80", "example.com")); - - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "http://microsoft.com:443", "example.com")); - Assert.equal(false, correlationIdCanIncludeCorrelationHeader(config, "https://microsoft.com:80", "example.com")); - } - }); - - this.testCase({ - name: "Check disableSameSiteCookie status", - test: () => { - let excludeValues = [ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.2 Safari/605.1.15", - "Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Mobile Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (iPad; CPU OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; MSAppHost/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/71.0.3578.89 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/65.0.225212226 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C101", - "Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.1.00.31860 Chrome/61.0.3163.100 Electron/2.0.10 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G950F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G930F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/71.0.3578.89 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/605.1.15 (KHTML, like Gecko)", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G935F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6821.400 QQBrowser/10.3.3040.400", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Yammer/2.1.0 Chrome/66.0.3359.181 Electron/3.0.6 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G965F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-A520F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G950U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G960U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.1.00.31860 Chrome/61.0.3163.100 Electron/2.0.10 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15", - "Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/65.0.225212226 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (iPad; CPU OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C50", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-N950U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G920F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6821.400 QQBrowser/10.3.3040.400", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 EdgiOS/42.8.6 Mobile/16C101 Safari/605.1.15", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-N950F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG SM-J530F Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36" - ]; - - let acceptValues = [ - "", - null, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0", - "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko", - "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko/20100101 Firefox/12.0", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15" - ]; - - for (let lp = 0; lp < excludeValues.length; lp++) { - Assert.equal(true, uaDisallowsSameSiteNone(excludeValues[lp]), excludeValues[lp]); - } - - for (let lp = 0; lp < acceptValues.length; lp++) { - Assert.equal(false, uaDisallowsSameSiteNone(acceptValues[lp]), acceptValues[lp]); - } - } - }); - - this.testCase({ - name: "check getIEVersion", - test: () => { - let notIEValues = [ - "", - null, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.2 Safari/605.1.15", - "Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Mobile Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (iPad; CPU OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; MSAppHost/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/71.0.3578.89 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/65.0.225212226 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C101", - "Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.1.00.31860 Chrome/61.0.3163.100 Electron/2.0.10 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G950F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G930F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/71.0.3578.89 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/605.1.15 (KHTML, like Gecko)", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G935F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6821.400 QQBrowser/10.3.3040.400", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Yammer/2.1.0 Chrome/66.0.3359.181 Electron/3.0.6 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G965F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-A520F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G950U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G960U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.1.00.31860 Chrome/61.0.3163.100 Electron/2.0.10 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15", - "Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/65.0.225212226 Mobile/15E148 Safari/605.1", - "Mozilla/5.0 (iPad; CPU OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C50", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-N950U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G920F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6821.400 QQBrowser/10.3.3040.400", - "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 EdgiOS/42.8.6 Mobile/16C101 Safari/605.1.15", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-N950F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.1.0; SAMSUNG SM-J530F Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36" - ]; - - for (let lp = 0; lp < notIEValues.length; lp++) { - let ieVersion = getIEVersion(notIEValues[lp]); - Assert.equal(null, ieVersion, "Not IE: " + notIEValues[lp]); - } - - Assert.equal(7, getIEVersion("Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 6.0)")); - Assert.equal(7, getIEVersion("Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)")); - Assert.equal(7, getIEVersion("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; Trident/4.0;)")); - Assert.equal(8, getIEVersion("Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)")); - Assert.equal(8, getIEVersion("Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)")); - Assert.equal(8, getIEVersion("Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)")); - Assert.equal(8, getIEVersion("Mozilla/4.0 (Compatible; MSIE 8.0; Windows NT 5.2; Trident/6.0)")); - Assert.equal(9, getIEVersion("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.0)")); - Assert.equal(9, getIEVersion("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)")); - Assert.equal(10, getIEVersion("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)")); - Assert.equal(10, getIEVersion("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)")); - Assert.equal(11, getIEVersion("Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko")); - Assert.equal(11, getIEVersion("Mozilla/5.0 (Windows NT 6.2; Trident/7.0; rv:11.0) like Gecko")); - Assert.equal(11, getIEVersion("Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko")); - Assert.equal(11, getIEVersion("Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko")); - Assert.equal(11, getIEVersion("Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko")); - - const origDocMode = document['documentMode']; - document['documentMode'] = 11; - - Assert.equal(11, getIEVersion("Mozilla/4.0 (Compatible; MSIE 8.0; Windows NT 5.2; Trident/6.0)")); - Assert.equal(11, getIEVersion("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/7.0; .NET4.0C; .NET4.0E)")); - - // restore documentMode - document['documentMode'] = origDocMode; - } - }); - } -} diff --git a/shared/otel-core/Tests/Unit/src/ai/AppInsightsCommon.tests.ts b/shared/otel-core/Tests/Unit/src/ai/AppInsightsCommon.tests.ts index cfc680515..a09e00a17 100644 --- a/shared/otel-core/Tests/Unit/src/ai/AppInsightsCommon.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/AppInsightsCommon.tests.ts @@ -1,9 +1,9 @@ import { strRepeat } from "@nevware21/ts-utils"; import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { LoggingSeverity, _InternalMessageId } from "../../../../../src/enums/ai/LoggingEnums"; -import { IConfiguration } from "../../../../../src/interfaces/ai/IConfiguration"; -import { IDiagnosticLogger, IInternalLogMessage } from "../../../../../src/interfaces/ai/IDiagnosticLogger"; -import { dataSanitizeInput, dataSanitizeKey, dataSanitizeMessage, DataSanitizerValues, dataSanitizeString, dataSanitizeUrl } from "../../../../../src/telemetry/ai/Common/DataSanitizer"; +import { LoggingSeverity, _InternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { IDiagnosticLogger, IInternalLogMessage } from "../../../../src/interfaces/ai/IDiagnosticLogger"; +import { dataSanitizeInput, dataSanitizeKey, dataSanitizeMessage, DataSanitizerValues, dataSanitizeString, dataSanitizeUrl } from "../../../../src/telemetry/ai/Common/DataSanitizer"; class TestDiagnosticLogger implements IDiagnosticLogger { diff --git a/shared/otel-core/Tests/Unit/src/ai/ApplicationInsightsCore.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/ApplicationInsightsCore.Tests.ts index dd31461b4..8c026c207 100644 --- a/shared/otel-core/Tests/Unit/src/ai/ApplicationInsightsCore.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/ApplicationInsightsCore.Tests.ts @@ -1,15 +1,23 @@ import { Assert, AITestClass, PollingAssert } from "@microsoft/ai-test-framework"; -import { - IConfiguration, ITelemetryPlugin, ITelemetryItem, IPlugin, IAppInsightsCore, normalizeJsName, - random32, mwcRandomSeed, newId, randomValue, mwcRandom32, isNullOrUndefined, SenderPostManager, - OnCompleteCallback, IPayloadData, _ISenderOnComplete, TransportType, _ISendPostMgrConfig, fieldRedaction, - _InternalLogMessage, DiagnosticLogger, isString -} from "../../../../../src/index" -import { AppInsightsCore } from "../../../../../src/core/AppInsightsCore"; -import { IChannelControls } from "../../../../../src/index"; -import { _eInternalMessageId, LoggingSeverity } from "../../../../../src/index"; -import { ActiveStatus } from "../../../../../src/index"; -import { createAsyncPromise, createAsyncRejectedPromise, createAsyncResolvedPromise, createPromise, createTimeoutPromise, doAwait, doAwaitResponse } from "@nevware21/ts-async"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { ITelemetryPlugin, IPlugin } from "../../../../src/interfaces/ai/ITelemetryPlugin"; +import { ITelemetryItem } from "../../../../src/interfaces/ai/ITelemetryItem"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { normalizeJsName } from "../../../../src/utils/HelperFuncs"; +import { random32, mwcRandomSeed, newId, randomValue, mwcRandom32 } from "../../../../src/utils/RandomHelper"; +import { isNullOrUndefined, isString } from "@nevware21/ts-utils"; +import { SenderPostManager } from "../../../../src/core/SenderPostManager"; +import { OnCompleteCallback, IPayloadData } from "../../../../src/interfaces/ai/IXHROverride"; +import { _ISenderOnComplete, _ISendPostMgrConfig } from "../../../../src/interfaces/ai/ISenderPostManager"; +import { TransportType } from "../../../../src/enums/ai/SendRequestReason"; +import { fieldRedaction } from "../../../../src/utils/EnvUtils"; +import { _InternalLogMessage, DiagnosticLogger } from "../../../../src/diagnostics/DiagnosticLogger"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { IChannelControls } from "../../../../src/interfaces/ai/IChannelControls"; +import { _eInternalMessageId, LoggingSeverity } from "../../../../src/enums/ai/LoggingEnums"; +import { ActiveStatus } from "../../../../src/enums/ai/InitActiveStatusEnum"; +import { createAsyncPromise, createAsyncRejectedPromise, createAsyncResolvedPromise, createTimeoutPromise, doAwaitResponse } from "@nevware21/ts-async"; +import { setBypassLazyCache } from "@nevware21/ts-utils"; const AIInternalMessagePrefix = "AITR_"; const MaxInt32 = 0xFFFFFFFF; @@ -20,6 +28,7 @@ export class ApplicationInsightsCoreTests extends AITestClass { public testInitialize() { super.testInitialize(); this.ctx = {}; + setBypassLazyCache(true); } public testCleanup() { diff --git a/shared/otel-core/Tests/Unit/src/ai/ConnectionStringParser.tests.ts b/shared/otel-core/Tests/Unit/src/ai/ConnectionStringParser.tests.ts index 1c2a3ff53..06ff93cfc 100644 --- a/shared/otel-core/Tests/Unit/src/ai/ConnectionStringParser.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/ConnectionStringParser.tests.ts @@ -1,6 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { ConnectionStringParser } from "../../../../../src/telemetry/ConnectionStringParser"; -import * as Constants from "../../../../../src/constants/Constants"; +import { ConnectionStringParser } from "../../../../src/telemetry/ConnectionStringParser"; +import * as Constants from "../../../../src/constants/Constants"; const runTest = (options: { connectionString: string, diff --git a/shared/otel-core/Tests/Unit/src/ai/CookieManager.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/CookieManager.Tests.ts index 6ec7af8c4..b4ec7730b 100644 --- a/shared/otel-core/Tests/Unit/src/ai/CookieManager.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/CookieManager.Tests.ts @@ -1,6 +1,15 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { AppInsightsCore, createCookieMgr, IAppInsightsCore, IConfiguration, ICookieMgrConfig, IPlugin, ITelemetryItem, newId, objExtend } from "../../../../../src/index" -import { _eInternalMessageId } from "../../../../../src/index"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { createCookieMgr } from "../../../../src/core/CookieMgr"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { ICookieMgrConfig } from "../../../../src/interfaces/ai/ICookieMgr"; +import { IPlugin } from "../../../../src/interfaces/ai/ITelemetryPlugin"; +import { ITelemetryItem } from "../../../../src/interfaces/ai/ITelemetryItem"; +import { newId } from "../../../../src/utils/RandomHelper"; +import { objExtend } from "../../../../src/utils/HelperFuncs"; +import { _eInternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { setBypassLazyCache } from "@nevware21/ts-utils"; export class CookieManagerTests extends AITestClass { private _cookieMgrCfg: ICookieMgrConfig; @@ -14,6 +23,7 @@ export class CookieManagerTests extends AITestClass { public testInitialize() { let _self = this; super.testInitialize(); + setBypassLazyCache(true); _self._testCookies = {}; _self._cookieMgrCfg = { getCookie: (name) => { diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/GlobalTestHooks.Test.ts b/shared/otel-core/Tests/Unit/src/ai/Core/GlobalTestHooks.Test.ts deleted file mode 100644 index 71004ee38..000000000 --- a/shared/otel-core/Tests/Unit/src/ai/Core/GlobalTestHooks.Test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Assert } from "@microsoft/ai-test-framework"; -import { _testHookMaxUnloadHooksCb } from "../../../../../src/core/UnloadHookContainer"; -import { dumpObj } from "@nevware21/ts-utils"; - -export class GlobalTestHooks { - - public registerTests() { - // Set a global maximum - _testHookMaxUnloadHooksCb(20, (state: string, hooks: Array) => { - Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks)); - }); - } -} diff --git a/shared/otel-core/Tests/Unit/src/ai/Core/ThrottleMgr.tests.ts b/shared/otel-core/Tests/Unit/src/ai/Core/ThrottleMgr.tests.ts deleted file mode 100644 index cf52e3f30..000000000 --- a/shared/otel-core/Tests/Unit/src/ai/Core/ThrottleMgr.tests.ts +++ /dev/null @@ -1,1675 +0,0 @@ -import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { AppInsightsCore, IAppInsightsCore, IConfiguration, IPlugin, _eInternalMessageId } from "../../../../../src/index"; -import { ThrottleMgr } from "../../../../../src/diagnostics/ThrottleMgr"; -import { IThrottleInterval, IThrottleLimit, IThrottleMgrConfig, IThrottleResult, IConfig, utlCanUseLocalStorage } from "../../../../../src/index"; - -const daysInMonth = [ - 31, // Jan - 28, // Feb - 31, // Mar - 30, // Apr - 31, // May - 30, // Jun - 31, // Jul - 31, // Aug - 30, // Sep - 31, // Oct - 30, // Nov - 31 // Dec -]; - -const isLeapYear = (year: number) => { - return (year % 4) === 0 && ((year % 100) !== 0 || (year % 400) === 0); -} - -const compareDates = (date1: Date, date: string | Date, expectedSame: boolean = true) => { - let isSame = false; - try { - if (date1 && date) { - let date2 = typeof date == "string"? new Date(date) : date; - isSame = date1.getUTCFullYear() === date2.getUTCFullYear() && - date1.getUTCMonth() === date2.getUTCMonth() && - date1.getUTCDate() === date2.getUTCDate(); - } - } catch (e) { - Assert.ok(false,"compare dates error" + e); - } - Assert.equal(isSame, expectedSame, "checking that the dates where as expected"); -} - -type ThrottleMgrConfigMap = { [msgKey: number]: IThrottleMgrConfig }; - -export class ThrottleMgrTest extends AITestClass { - private _core!: IAppInsightsCore; - private _msgKey!: number; - private _storageName!: string; - private _msgId!: _eInternalMessageId; - private loggingSpy: any; - private _channel!: ChannelPlugin; - - public testInitialize() { - this._core = new AppInsightsCore(); - this._msgKey = _eInternalMessageId.InstrumentationKeyDeprecation; - this._storageName = "appInsightsThrottle-" + this._msgKey; - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - this._msgId = _eInternalMessageId.InstrumentationKeyDeprecation; - this._channel = new ChannelPlugin(); - - if (utlCanUseLocalStorage()) { - window.localStorage.clear(); - } - } - - public testCleanup() { - if (utlCanUseLocalStorage()) { - window.localStorage.clear(); - } - - this.loggingSpy = null; - } - - public registerTests() { - - this.testCase({ - name: "ThrottleMgrTest: Default config should be set from root", - test: () => { - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test" - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedconfig, actualConfig[_eInternalMessageId.DefaultThrottleMsgKey], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Default config should be set from root with key", - test: () => { - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: {}} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - Assert.deepEqual(expectedconfig, actualConfig[_eInternalMessageId.DefaultThrottleMsgKey], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple msg keys - Default config should be set from root", - test: () => { - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - - isTriggered = throttleMgr.isTriggered(109); - Assert.equal(isTriggered, false, "should not be triggered msg key 109"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: None set msg keys - Default config should be used", - test: () => { - let date = new Date(); - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[_eInternalMessageId.DefaultThrottleMsgKey]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedconfig, actualConfig[_eInternalMessageId.DefaultThrottleMsgKey], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true, "should be able to throttle"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Config should be updated dynamically", - useFakeTimers: true, - test: () => { - let date = new Date(); - let config = { - disabled: false, - limit: { - samplingRate: 50, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(config, actualConfig[this._msgId], "should get expected config"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle"); - - config.disabled = true; - config.limit = { - samplingRate: 80, - maxSendNumber: 10 - } as IThrottleLimit, - config.interval = { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate() + 1] - } as IThrottleInterval; - this._core.config.throttleMgrCfg = this._core.config.throttleMgrCfg || {}; - this._core.config.throttleMgrCfg[this._msgId] = config; - this.clock.tick(1); - actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(config, actualConfig[this._msgId], "config should be updated dynamically"); - canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, false, "should not be able to throttle"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Config should be updated dynamically with different keys", - useFakeTimers: true, - test: () => { - let date = new Date(); - let msgId = 109; - let config = { - disabled: false, - limit: { - samplingRate: 50, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let config1 = { - disabled: false, - limit: { - samplingRate: 60, - maxSendNumber: 101 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config, [msgId]: config1} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.ok(actualConfig[_eInternalMessageId.DefaultThrottleMsgKey]); - Assert.deepEqual(config, actualConfig[this._msgId], "should get expected config"); - Assert.deepEqual(config1, actualConfig[msgId], "should get expected config1"); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle"); - isTriggered = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggered, false, "should not be triggered test1"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle test1"); - - config.disabled = true; - config.limit = { - samplingRate: 80, - maxSendNumber: 10 - } as IThrottleLimit, - config.interval = { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate() + 1] - } as IThrottleInterval; - this._core.config.throttleMgrCfg = this._core.config.throttleMgrCfg || {}; - this._core.config.throttleMgrCfg[this._msgId] = config; - this.clock.tick(1); - actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(config, actualConfig[this._msgId], "config should be updated dynamically"); - Assert.deepEqual(config1, actualConfig[msgId], "config1 should not be updated dynamically"); - canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.deepEqual(canThrottle, false, "should not be able to throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.deepEqual(canThrottle, true, "should be able to throttle config1"); - } - }); - - - this.testCase({ - name: "ThrottleMgrTest: flush", - test: () => { - let date = new Date(); - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 10 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - let isTriggered = throttleMgr.isTriggered(this._msgKey); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgKey); - Assert.equal(canThrottle, true, "should be able to be throttle"); - - let isReady = throttleMgr.isReady(); - Assert.equal(isReady, false, "isReady state should be false"); - let result = throttleMgr.sendMessage(this._msgId, "test"); - Assert.equal(result, null, "should not be throttled"); - // note: _getDbgPlgTargets returns array - let target = (throttleMgr as any)["_getDbgPlgTargets"](); - Assert.ok(target && target.length === 1, "target should contain queue"); - let queue = target[0][this._msgKey]; - Assert.deepEqual(queue.length,1, "should have 1 item"); - Assert.equal(queue[0].msgID, this._msgId, "should be correct msgId"); - - throttleMgr.onReadyState(true); - target = (throttleMgr as any)["_getDbgPlgTargets"](); - queue = target[0][this._msgKey]; - Assert.equal(queue.length, 0, "queue should be empty"); - let storage = window.localStorage[this._storageName]; - let dateNum = date.getUTCDate(); - let prefix = dateNum < 10? "0":""; - Assert.ok(storage.indexOf(`${date.getUTCMonth() + 1}-${prefix + dateNum}`) > -1, "local storage should have correct date"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple msg keys - flush", - test: () => { - let msgId = 109; - let storageName = this._storageName = "appInsightsThrottle-" + msgId; - let date = new Date(); - let expectedconfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber: 10 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: undefined, - daysOfMonth: [date.getUTCDate()] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedconfig, [msgId]: expectedconfig} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedconfig, actualConfig[this._msgId], "should get expected default config"); - Assert.deepEqual(expectedconfig, actualConfig[msgId], "should get expected default config msgId"); - let isTriggered = throttleMgr.isTriggered(this._msgKey); - Assert.equal(isTriggered, false, "should not be triggered"); - let canThrottle = throttleMgr.canThrottle(this._msgKey); - Assert.equal(canThrottle, true, "should be able to be throttle"); - isTriggered = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggered, false, "should not be triggered test1"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.equal(canThrottle, true, "should be able to be throttle test1"); - - let isReady = throttleMgr.isReady(); - Assert.equal(isReady, false, "isReady state should be false"); - let result = throttleMgr.sendMessage(this._msgId, "test"); - Assert.equal(result, null, "should not be throttled"); - result = throttleMgr.sendMessage(msgId, "test1"); - Assert.equal(result, null, "should not be throttled test1"); - // note: _getDbgPlgTargets returns array - let target = (throttleMgr as any)["_getDbgPlgTargets"](); - Assert.ok(target && target.length === 1, "target should contain queue"); - let queue = target[0][this._msgKey]; - Assert.deepEqual(queue.length,1, "should have 1 item"); - Assert.equal(queue[0].msgID, this._msgId, "should be correct msgId"); - queue = target[0][msgId]; - Assert.deepEqual(queue.length,1, "should have 1 item test1"); - Assert.equal(queue[0].msgID, msgId, "should be correct msgId test1"); - - throttleMgr.onReadyState(true); - target = (throttleMgr as any)["_getDbgPlgTargets"](); - queue = target[0][this._msgKey]; - Assert.equal(queue.length, 0, "queue should be empty"); - let storage = window.localStorage[this._storageName]; - let dateNum = date.getUTCDate(); - let prefix = dateNum < 10? "0":""; - Assert.ok(storage.indexOf(`${date.getUTCMonth() + 1}-${prefix + dateNum}`) > -1, "local storage should have correct date"); - - target = (throttleMgr as any)["_getDbgPlgTargets"](); - queue = target[0][msgId]; - Assert.equal(queue.length, 0, "queue should be empty test1"); - storage = window.localStorage[storageName]; - dateNum = date.getUTCDate(); - prefix = dateNum < 10? "0":""; - Assert.ok(storage.indexOf(`${date.getUTCMonth() + 1}-${prefix + dateNum}`) > -1, "local storage should have correct date test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager can get expected config", - test: () => { - let config = { - disabled: false, - limit: { - samplingRate: 50, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 2, - dayInterval: 10, - daysOfMonth: undefined - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(config, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgKey); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager can get default config", - test: () => { - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: {}} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: monthInterval should be set to 3 when dayInterval and monthInterval are both undefined", - test: () => { - - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - daysOfMonth: [25, 26, 28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [25, 26, 28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: monthInterval and daysOfMonth should be changed to default when dayInterval is defined", - test: () => { - - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - dayInterval: 100 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: undefined, - dayInterval: 100, - daysOfMonth: undefined - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: message can be sent from the first day when dayInterval is set to one", - test: () => { - - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: undefined, - dayInterval: 1, - daysOfMonth: undefined - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - - let canSend = throttleMgr.canThrottle(this._msgId); - Assert.equal(canSend, true, "can send message from the day") - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager should trigger when current date is in daysOfMonth", - test: () => { - let date = new Date(); - let day = date.getUTCDate(); - let daysOfMonth; - if (day == 1) { - daysOfMonth = [31,day]; - } else { - daysOfMonth = [day-1, day]; - } - let config = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: 28, - daysOfMonth: daysOfMonth - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(config, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true, "should throttle"); - - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Throttle Manager should trigger when interval config is undefined and current date 28", - test: () => { - let date = new Date(); - let day = date.getUTCDate(); - - let expectedConfig = { - disabled: false, - limit: { - samplingRate: 100, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: undefined, - daysOfMonth: [28] - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: expectedConfig} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let actualConfig = throttleMgr.getConfig() as ThrottleMgrConfigMap; - Assert.deepEqual(expectedConfig, actualConfig[this._msgId]); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - - let shouldTrigger = false; - if (day === 28) { - shouldTrigger = true; - } - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, shouldTrigger, "should only throttle on 28th"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when disabled is true", - test: () => { - let config = { - disabled: true - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when month interval requirements are not meet", - test: () => { - let date = new Date(); - let month = date.getUTCMonth(); - let year = date.getUTCFullYear(); - if (month == 0) { - date.setUTCFullYear(year-1); - date.setUTCMonth(11); - } else { - let dayOfMonth = date.getDate(); - if (dayOfMonth > (daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 ))) { - date.setDate(daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 )); - } - date.setUTCMonth(month-1); - } - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when year and month interval requirements are not meet", - test: () => { - let date = new Date(); - let month = date.getUTCMonth(); - let year = date.getUTCFullYear(); - - if (month == 0) { - date.setUTCFullYear(year-2); - date.setUTCMonth(11); - } else { - date.setUTCFullYear(year-1); - let dayOfMonth = date.getDate(); - if (dayOfMonth > (daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 ))) { - date.setDate(daysInMonth[month - 1] + (month === 2 ? (isLeapYear(year - 1) ? 1 : 0 ) : 0 )); - } - date.setUTCMonth(month-1); - } - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 3, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when day interval requirements are not meet", - test: () => { - let date = new Date(); - let curDate = date.getUTCDate(); - let curMonth = date.getUTCMonth(); - if (curDate === 1) { - curMonth -= 1; - curDate = 28; - } else { - curDate -= 1; - } - date.setUTCDate(curDate); - date.setUTCMonth(curMonth); - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 31 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, false); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should trigger throttle at starting month", - test: () => { - let date = new Date(); - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should trigger throttle when month and day intervals are meet", - test: () => { - let date = new Date(); - let year = date.getUTCFullYear() - 2; - date.setUTCFullYear(year); - let storageObj = { - date: date, - count: 0 - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 4, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggered, false); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger throttle when _isTrigger state is true (in valid send message date)", - test: () => { - let storageObj = { - date: new Date(), - count: 0, - preTriggerDate: new Date() - } - window.localStorage[this._storageName] = JSON.stringify(storageObj); - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 100 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - let isTriggered = throttleMgr.isTriggered(this._msgId); - Assert.ok(isTriggered); - let result = throttleMgr.sendMessage(this._msgId, "test"); - let count = this.loggingSpy.callCount; - Assert.equal(count,0); - Assert.deepEqual(result, null); - let postIsTriggered = throttleMgr.isTriggered(this._msgId); - Assert.ok(postIsTriggered); - - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should update local storage (in valid send message date)", - test: () => { - let date = new Date(); - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - - let preTriggerDate = date; - preTriggerDate.setUTCFullYear(date.getUTCFullYear() - 1); - let preStorageObj = { - date: date, - count: 0, - preTriggerDate: preTriggerDate - } - - let preStorageVal = JSON.stringify(preStorageObj); - window.localStorage[this._storageName] = preStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber: 1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottle, true); - let isPretriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isPretriggered, false); - - throttleMgr.onReadyState(true); - let sendDate = new Date(); - let result = throttleMgr.sendMessage(this._msgId, msg); - let expectedRetryRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult; - Assert.deepEqual(result, expectedRetryRlt); - let isPostTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(isPostTriggered, true); - - let afterTriggered = window.localStorage[this._storageName]; - let afterTriggeredObj = JSON.parse(afterTriggered); - compareDates(date, afterTriggeredObj.date) - Assert.equal(0, afterTriggeredObj.count); - compareDates(sendDate, afterTriggeredObj.preTriggerDate); - } - - }); - - this.testCase({ - name: "ThrottleMgrTest: should not trigger sendmessage when isready state is not set and should flush message after isReady state is set", - test: () => { - - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let date = new Date(); - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle); - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false); - let initialVal = window.localStorage[this._storageName]; - let initObj = JSON.parse(initialVal); - compareDates(date, initObj.date); - Assert.equal(0, initObj.count); - Assert.equal(undefined, initObj.preTriggerDate); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount; - Assert.equal(count,0); - Assert.deepEqual(result, null); - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, false); - let postVal = window.localStorage[this._storageName]; - let postObj = JSON.parse(postVal); - compareDates(date, postObj.date); - Assert.equal(0, postObj.count); - Assert.equal(undefined, postObj.preTriggerDate); - - let isFlushed = throttleMgr.onReadyState(true); - Assert.ok(isFlushed); - let isTriggeredAfterReadySate = throttleMgr.isTriggered(this._msgId); - Assert.ok(isTriggeredAfterReadySate); - let postOnReadyVal = window.localStorage[this._storageName]; - let postOnReadyObj = JSON.parse(postOnReadyVal); - compareDates(date, postOnReadyObj.date); - Assert.equal(0, postOnReadyObj.count); - compareDates(date, postOnReadyObj.preTriggerDate); - - let onReadyResult = throttleMgr.sendMessage(this._msgId, msg); - let onReadyCount = this.loggingSpy.callCount; - let expectedRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.equal(onReadyCount,1, "test1"); - Assert.deepEqual(onReadyResult, expectedRlt); - let onReadyIsTriggered = throttleMgr.isTriggered(this._msgId); - Assert.equal(onReadyIsTriggered, true); - let afterResendVal = window.localStorage[this._storageName]; - let afterResendObj = JSON.parse(afterResendVal); - compareDates(date, afterResendObj.date); - Assert.equal(1, afterResendObj.count); - compareDates(date, afterResendObj.preTriggerDate); - } - }); - - - this.testCase({ - name: "ThrottleMgrTest: throw messages with correct number", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let date = new Date(); - let testStorageObj = { - date: date, - count: 5 - } - let testStorageVal = JSON.stringify(testStorageObj); - window.localStorage[this._storageName] = testStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt); - - let val = window.localStorage[this._storageName]; - let obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count); - compareDates(date, obj.preTriggerDate); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple keys - throw messages with correct number", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - let storageName = "appInsightsThrottle-" + msgId; - let date = new Date(); - let testStorageObj = { - date: date, - count: 5 - } - let testStorageVal = JSON.stringify(testStorageObj); - window.localStorage[this._storageName] = testStorageVal; - window.localStorage[storageName] = testStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config, [msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "preTrigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "preTrigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let val = window.localStorage[this._storageName]; - let obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count"); - compareDates(date, obj.preTriggerDate); - val = window.localStorage[storageName]; - obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count test1"); - compareDates(date, obj.preTriggerDate); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: None set mutiple keys - throw messages with correct number", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - let storageName = "appInsightsThrottle-" + msgId; - let date = new Date(); - let testStorageObj = { - date: date, - count: 5 - } - let testStorageVal = JSON.stringify(testStorageObj); - window.localStorage[this._storageName] = testStorageVal; - window.localStorage[storageName] = testStorageVal; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[_eInternalMessageId.DefaultThrottleMsgKey]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "preTrigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "preTrigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let val = window.localStorage[this._storageName]; - let obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count"); - compareDates(date, obj.preTriggerDate); - val = window.localStorage[storageName]; - obj = JSON.parse(val); - compareDates(date, obj.date); - Assert.equal(0, obj.count, "local storage count test1"); - compareDates(date, obj.preTriggerDate); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: should throw messages 1 time within a day", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1); - - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult - Assert.deepEqual(result, expectedRlt); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true); - let canThrottlePost = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottlePost, true); - - let retryRlt = throttleMgr.sendMessage(this._msgId, msg); - let retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,1); - let expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: Mutiple msg keys - should throw messages 1 time within a day", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[this._msgId]: config, [msgId]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "is trigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "is trigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult; - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - let canThrottlePost = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottlePost, true, "can throttle post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - canThrottlePost = throttleMgr.canThrottle(msgId); - Assert.equal(canThrottlePost, true, "can throttle post test1"); - - let retryRlt = throttleMgr.sendMessage(this._msgId, msg); - let retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count"); - let expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result"); - - retryRlt = throttleMgr.sendMessage(msgId, msg); - retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count test1"); - expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result test1"); - } - }); - - this.testCase({ - name: "ThrottleMgrTest: None set mutiple msg keys - should throw messages 1 time within a day", - test: () => { - let msg = "Instrumentation key support will end soon, see aka.ms/IkeyMigrate"; - let msgId = 109; - - let config = { - disabled: false, - limit: { - samplingRate: 1000000, - maxSendNumber:1 - } as IThrottleLimit, - interval: { - monthInterval: 1, - dayInterval: 1 - } as IThrottleInterval - } as IThrottleMgrConfig; - - let coreCfg = { - instrumentationKey: "test", - throttleMgrCfg: {[_eInternalMessageId.DefaultThrottleMsgKey]: config} - }; - this._core.initialize(coreCfg, [this._channel]); - - let throttleMgr = new ThrottleMgr(this._core); - this.loggingSpy = this.sandbox.stub(this._core.logger, "throwInternal"); - - - let canThrottle = throttleMgr.canThrottle(this._msgId); - Assert.ok(canThrottle, "can throttle"); - canThrottle = throttleMgr.canThrottle(msgId); - Assert.ok(canThrottle, "can throttle test1"); - - let isTriggeredPre = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPre, false, "is trigger"); - isTriggeredPre = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPre, false, "is trigger test1"); - - throttleMgr.onReadyState(true); - - let result = throttleMgr.sendMessage(this._msgId, msg); - let count = this.loggingSpy.callCount - Assert.equal(count,1, "sendMsg count"); - let expectedRlt = { - isThrottled: true, - throttleNum: 1 - } as IThrottleResult; - Assert.deepEqual(result, expectedRlt, "expected result"); - result = throttleMgr.sendMessage(msgId, msg); - count = this.loggingSpy.callCount - Assert.equal(count,2, "sendMsg count test1"); - Assert.deepEqual(result, expectedRlt, "expected result test1"); - - let isTriggeredPost = throttleMgr.isTriggered(this._msgId); - Assert.equal(isTriggeredPost, true, "trigger post"); - let canThrottlePost = throttleMgr.canThrottle(this._msgId); - Assert.equal(canThrottlePost, true, "can throttle post"); - isTriggeredPost = throttleMgr.isTriggered(msgId); - Assert.equal(isTriggeredPost, true, "trigger post test1"); - canThrottlePost = throttleMgr.canThrottle(msgId); - Assert.equal(canThrottlePost, true, "can throttle post test1"); - - let retryRlt = throttleMgr.sendMessage(this._msgId, msg); - let retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count"); - let expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result"); - - retryRlt = throttleMgr.sendMessage(msgId, msg); - retryCount = this.loggingSpy.callCount - Assert.equal(retryCount,2, "retrt count test1"); - expectedRetryRlt = { - isThrottled: false, - throttleNum: 0 - } as IThrottleResult - Assert.deepEqual(retryRlt, expectedRetryRlt, "retry result test1"); - } - }); - } -} - - -class ChannelPlugin implements IPlugin { - - public isFlushInvoked = false; - public isTearDownInvoked = false; - public isResumeInvoked = false; - public isPauseInvoked = false; - - public identifier = "Sender"; - - public priority: number = 1001; - - constructor() { - this.processTelemetry = this._processTelemetry.bind(this); - } - public pause(): void { - this.isPauseInvoked = true; - } - - public resume(): void { - this.isResumeInvoked = true; - } - - public teardown(): void { - this.isTearDownInvoked = true; - } - - flush(async?: boolean, callBack?: () => void): void { - this.isFlushInvoked = true; - if (callBack) { - callBack(); - } - } - - public processTelemetry(env: any) {} - - setNextPlugin(next: any) { - // no next setup - } - - public initialize = (config: IConfiguration, core: IAppInsightsCore, plugin: IPlugin[]) => { - } - - private _processTelemetry(env: any) { - - } -} diff --git a/shared/otel-core/Tests/Unit/src/ai/EventHelper.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/EventHelper.Tests.ts index 12f4b0fb5..367d2f68b 100644 --- a/shared/otel-core/Tests/Unit/src/ai/EventHelper.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/EventHelper.Tests.ts @@ -1,5 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { addEventHandler, createUniqueNamespace, removeEventHandler, _eInternalMessageId, mergeEvtNamespace, __getRegisteredEvents } from "../../../../../src/index"; +import { __getRegisteredEvents, addEventHandler, mergeEvtNamespace, removeEventHandler } from "../../../../src/internal/EventHelpers"; +import { createUniqueNamespace } from "../../../../src/utils/DataCacheHelper"; export class EventHelperTests extends AITestClass { @@ -280,15 +281,15 @@ export class EventHelperTests extends AITestClass { function _checkRegisteredAddEventHandler(name: string, expected: number) { let registered = __getRegisteredEvents(window, name); - Assert.equal(expected, registered.length, "Check that window event was registered"); + Assert.equal(expected, registered.length, "Check that window event was registered for " + name); - if (window["body"]) { + if (window && window["body"]) { registered = __getRegisteredEvents(window["body"], name); - Assert.equal(expected, registered.length, "Check that window.body event was registered"); + Assert.equal(expected, registered.length, "Check that window.body event was registered for " + name); } registered = __getRegisteredEvents(document, name); - Assert.equal(expected, registered.length, "Check that document event was registered"); + Assert.equal(expected, registered.length, "Check that document event was registered for " + name); } } } diff --git a/shared/otel-core/Tests/Unit/src/ai/EventsDiscardedReason.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/EventsDiscardedReason.Tests.ts index 06464c087..b48486d99 100644 --- a/shared/otel-core/Tests/Unit/src/ai/EventsDiscardedReason.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/EventsDiscardedReason.Tests.ts @@ -1,5 +1,5 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { eEventsDiscardedReason, EventsDiscardedReason } from "../../../../../src/index"; +import { eEventsDiscardedReason, EventsDiscardedReason } from "../../../../src/enums/ai/EventsDiscardedReason"; export class EventsDiscardedReasonTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/ai/Exception.tests.ts b/shared/otel-core/Tests/Unit/src/ai/Exception.tests.ts index 7548b6d3b..d468270cb 100644 --- a/shared/otel-core/Tests/Unit/src/ai/Exception.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/Exception.tests.ts @@ -1,10 +1,11 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { LoggingSeverity, _InternalMessageId } from "../../../../../src/enums/ai/LoggingEnums"; -import { DataSanitizerValues, Exception } from "../../../../../src/index" -import { IDiagnosticLogger, IInternalLogMessage } from "../../../../../src/interfaces/ai/IDiagnosticLogger"; -import { IExceptionInternal, IExceptionDetailsInternal, IExceptionStackFrameInternal } from "../../../../../src/interfaces/ai/IExceptionTelemetry"; -import { _createExceptionDetails, _createExDetailsFromInterface, _extractStackFrame, _parsedFrameToInterface, _IParsedStackFrame } from "../../../../../src/telemetry/ai/Exception"; -import { IStackFrame } from "../../../../../src/interfaces/ai/contracts/IStackFrame"; +import { LoggingSeverity, _InternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { DataSanitizerValues } from "../../../../src/telemetry/ai/Common/DataSanitizer"; +import { Exception } from "../../../../src/telemetry/ai/Exception"; +import { IDiagnosticLogger, IInternalLogMessage } from "../../../../src/interfaces/ai/IDiagnosticLogger"; +import { IExceptionInternal, IExceptionDetailsInternal, IExceptionStackFrameInternal } from "../../../../src/interfaces/ai/IExceptionTelemetry"; +import { _createExceptionDetails, _createExDetailsFromInterface, _extractStackFrame, _parsedFrameToInterface, _IParsedStackFrame } from "../../../../src/telemetry/ai/Exception"; +import { IStackFrame } from "../../../../src/interfaces/ai/contracts/IStackFrame"; class TestDiagnosticLogger implements IDiagnosticLogger { public queue: IInternalLogMessage[]; @@ -257,7 +258,7 @@ export class ExceptionTests extends AITestClass { " c@http://example.com/stacktrace.js:9:3\n" + " b@http://example.com/stacktrace.js:6:3\n" + " a@http://example.com/stacktrace.js:3:3\n" + - " at Object.testMethod (http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)" + " at Object.testMethod (http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)" } }; @@ -290,7 +291,7 @@ export class ExceptionTests extends AITestClass { { level: 25, method: "c", assembly: "c@http://example.com/stacktrace.js:9:3", fileName: "http://example.com/stacktrace.js", line: 9 }, { level: 26, method: "b", assembly: "b@http://example.com/stacktrace.js:6:3", fileName: "http://example.com/stacktrace.js", line: 6 }, { level: 27, method: "a", assembly: "a@http://example.com/stacktrace.js:3:3", fileName: "http://example.com/stacktrace.js", line: 3 }, - { level: 28, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", fileName: "http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 53058 } + { level: 28, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", fileName: "http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 53058 } ]; let exception = Exception.CreateAutoException("message", @@ -430,8 +431,8 @@ export class ExceptionTests extends AITestClass { frame: { level: 0, method: "a", assembly: "a@http://example.com/stacktrace.js:3:3", fileName: "http://example.com/stacktrace.js", line: 3 } }, { - src: " at Object.testMethod (http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", - frame: { level: 0, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", fileName: "http://localhost:9001/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 53058 } + src: " at Object.testMethod (http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", + frame: { level: 0, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:53058:48)", fileName: "http://localhost:9002/shared/AppInsightsCommon/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 53058 } } ]; @@ -449,28 +450,28 @@ export class ExceptionTests extends AITestClass { reason:{ message: "TypeError: Cannot read property 'b' of undefined", stack: "TypeError: Cannot read property 'b' of undefined\n" + - " at ApplicationInsightsTests. (http://localhost:9001/AISKU/Tests/Unit/dist/aiskuunittests.tests.js:4578:40)\n" + - " at trigger_1 (http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52923:59)\n" + - " at Object.testMethod (http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52964:21)\n" + - " at runTest (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2725:35)\n" + - " at Test.run (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2708:9)\n" + - " at http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2972:16\n" + - " at processTaskQueue (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2293:26)\n" + - " at http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2297:13" + " at ApplicationInsightsTests. (http://localhost:9002/AISKU/Tests/Unit/dist/aiskuunittests.tests.js:4578:40)\n" + + " at trigger_1 (http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52923:59)\n" + + " at Object.testMethod (http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52964:21)\n" + + " at runTest (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2725:35)\n" + + " at Test.run (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2708:9)\n" + + " at http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2972:16\n" + + " at processTaskQueue (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2293:26)\n" + + " at http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2297:13" } }; let errDetail = { src: errObj.reason.stack, obj:[ "TypeError: Cannot read property 'b' of undefined", - "at ApplicationInsightsTests. (http://localhost:9001/AISKU/Tests/Unit/dist/aiskuunittests.tests.js:4578:40)", - "at trigger_1 (http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52923:59)", - "at Object.testMethod (http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52964:21)", - "at runTest (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2725:35)", - "at Test.run (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2708:9)", - "at http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2972:16", - "at processTaskQueue (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2293:26)", - "at http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2297:13" + "at ApplicationInsightsTests. (http://localhost:9002/AISKU/Tests/Unit/dist/aiskuunittests.tests.js:4578:40)", + "at trigger_1 (http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52923:59)", + "at Object.testMethod (http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52964:21)", + "at runTest (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2725:35)", + "at Test.run (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2708:9)", + "at http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2972:16", + "at processTaskQueue (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2293:26)", + "at http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2297:13" ] }; let exception = Exception.CreateAutoException("message", @@ -483,14 +484,14 @@ export class ExceptionTests extends AITestClass { Assert.ok(exception.stackDetails); const expectedParsedStack: IStackFrame[] = [ - { level: 0, method: "ApplicationInsightsTests.", assembly: "at ApplicationInsightsTests. (http://localhost:9001/AISKU/Tests/Unit/dist/aiskuunittests.tests.js:4578:40)", fileName: "http://localhost:9001/AISKU/Tests/Unit/dist/aiskuunittests.tests.js", line: 4578 }, - { level: 1, method: "trigger_1", assembly: "at trigger_1 (http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52923:59)", fileName: "http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 52923 }, - { level: 2, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52964:21)", fileName: "http://localhost:9001/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 52964 }, - { level: 3, method: "runTest", assembly: "at runTest (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2725:35)", fileName: "http://localhost:9001/common/Tests/External/qunit-2.9.3.js", line: 2725 }, - { level: 4, method: "Test.run", assembly: "at Test.run (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2708:9)", fileName: "http://localhost:9001/common/Tests/External/qunit-2.9.3.js", line: 2708 }, - { level: 5, method: "", assembly: "at http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2972:16", fileName: "http://localhost:9001/common/Tests/External/qunit-2.9.3.js", line: 2972 }, - { level: 6, method: "processTaskQueue", assembly: "at processTaskQueue (http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2293:26)", fileName: "http://localhost:9001/common/Tests/External/qunit-2.9.3.js", line: 2293 }, - { level: 7, method: "", assembly: "at http://localhost:9001/common/Tests/External/qunit-2.9.3.js:2297:13", fileName: "http://localhost:9001/common/Tests/External/qunit-2.9.3.js", line: 2297 } + { level: 0, method: "ApplicationInsightsTests.", assembly: "at ApplicationInsightsTests. (http://localhost:9002/AISKU/Tests/Unit/dist/aiskuunittests.tests.js:4578:40)", fileName: "http://localhost:9002/AISKU/Tests/Unit/dist/aiskuunittests.tests.js", line: 4578 }, + { level: 1, method: "trigger_1", assembly: "at trigger_1 (http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52923:59)", fileName: "http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 52923 }, + { level: 2, method: "Object.testMethod", assembly: "at Object.testMethod (http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js:52964:21)", fileName: "http://localhost:9002/AISKU/node_modules/@microsoft/ai-test-framework/dist/es5/ai-test-framework.js", line: 52964 }, + { level: 3, method: "runTest", assembly: "at runTest (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2725:35)", fileName: "http://localhost:9002/common/Tests/External/qunit-2.9.3.js", line: 2725 }, + { level: 4, method: "Test.run", assembly: "at Test.run (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2708:9)", fileName: "http://localhost:9002/common/Tests/External/qunit-2.9.3.js", line: 2708 }, + { level: 5, method: "", assembly: "at http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2972:16", fileName: "http://localhost:9002/common/Tests/External/qunit-2.9.3.js", line: 2972 }, + { level: 6, method: "processTaskQueue", assembly: "at processTaskQueue (http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2293:26)", fileName: "http://localhost:9002/common/Tests/External/qunit-2.9.3.js", line: 2293 }, + { level: 7, method: "", assembly: "at http://localhost:9002/common/Tests/External/qunit-2.9.3.js:2297:13", fileName: "http://localhost:9002/common/Tests/External/qunit-2.9.3.js", line: 2297 } ]; const exceptionDetails = _createExceptionDetails(this.logger, exception); diff --git a/shared/otel-core/Tests/Unit/src/ai/GlobalTestHooks.Test.ts b/shared/otel-core/Tests/Unit/src/ai/GlobalTestHooks.Test.ts index 71004ee38..1b1f56955 100644 --- a/shared/otel-core/Tests/Unit/src/ai/GlobalTestHooks.Test.ts +++ b/shared/otel-core/Tests/Unit/src/ai/GlobalTestHooks.Test.ts @@ -1,5 +1,5 @@ import { Assert } from "@microsoft/ai-test-framework"; -import { _testHookMaxUnloadHooksCb } from "../../../../../src/core/UnloadHookContainer"; +import { _testHookMaxUnloadHooksCb } from "../../../../src/core/UnloadHookContainer"; import { dumpObj } from "@nevware21/ts-utils"; export class GlobalTestHooks { diff --git a/shared/otel-core/Tests/Unit/src/ai/HelperFunc.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/HelperFunc.Tests.ts index 1bd7f584a..6f7ea917b 100644 --- a/shared/otel-core/Tests/Unit/src/ai/HelperFunc.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/HelperFunc.Tests.ts @@ -1,9 +1,10 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { _eInternalMessageId } from "../../../../../src/index"; -import { normalizeJsName, objExtend, _getObjProto, isFeatureEnabled } from "../../../../../src/index"; -import { AppInsightsCore } from "../../../../../src/core/AppInsightsCore"; -import { isArray, isObject, objKeys, strEndsWith, strStartsWith, isPlainObject, utcNow } from "@nevware21/ts-utils"; -import { FeatureOptInMode, IConfiguration, IFeatureOptInDetails, dumpObj } from "../../../../../src/index"; +import { isArray, isObject, objKeys, strEndsWith, strStartsWith, isPlainObject, utcNow, dumpObj } from "@nevware21/ts-utils"; +import { _getObjProto, isFeatureEnabled, normalizeJsName, objExtend } from "../../../../src/utils/HelperFuncs"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { FeatureOptInMode } from "../../../../src/enums/ai/FeatureOptInEnums"; +import { IFeatureOptInDetails } from "../../../../src/interfaces/ai/IFeatureOptIn"; diff --git a/shared/otel-core/Tests/Unit/src/ai/LoggingEnum.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/LoggingEnum.Tests.ts index e97a1e53e..65a5be6e2 100644 --- a/shared/otel-core/Tests/Unit/src/ai/LoggingEnum.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/LoggingEnum.Tests.ts @@ -1,6 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; import { isString, objForEachKey } from "@nevware21/ts-utils"; -import { LoggingSeverity, eLoggingSeverity, _eInternalMessageId } from "../../../../../src/index"; +import { LoggingSeverity, eLoggingSeverity, _eInternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; type NoRepeats = { [M in keyof T]: { [N in keyof T]: N extends M ? never : T[M] extends T[N] ? unknown : never diff --git a/shared/otel-core/Tests/Unit/src/ai/RequestHeaders.tests.ts b/shared/otel-core/Tests/Unit/src/ai/RequestHeaders.tests.ts index 7301afbf9..7f339442a 100644 --- a/shared/otel-core/Tests/Unit/src/ai/RequestHeaders.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/RequestHeaders.tests.ts @@ -1,5 +1,5 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { eRequestHeaders, RequestHeaders } from "../../../../../src/telemetry/RequestResponseHeaders"; +import { eRequestHeaders, RequestHeaders } from "../../../../src/telemetry/RequestResponseHeaders"; export class RequestHeadersTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/ai/SendPostManager.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/SendPostManager.Tests.ts index 31dddd238..92e757a53 100644 --- a/shared/otel-core/Tests/Unit/src/ai/SendPostManager.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/SendPostManager.Tests.ts @@ -1,7 +1,7 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { _eInternalMessageId } from "../../../../../src/index"; -import { SenderPostManager } from "../../../../../src/core/SenderPostManager"; -import { IPayloadData } from "../../../../../src/index"; +import { _eInternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { SenderPostManager } from "../../../../src/core/SenderPostManager"; +import { IPayloadData } from "../../../../src/interfaces/ai/IXHROverride"; import { getInst, isFunction, mathRandom } from "@nevware21/ts-utils"; import { createPromise, doAwaitResponse } from "@nevware21/ts-async"; diff --git a/shared/otel-core/Tests/Unit/src/ai/SeverityLevel.tests.ts b/shared/otel-core/Tests/Unit/src/ai/SeverityLevel.tests.ts index 20e3dd423..5c7b38a4b 100644 --- a/shared/otel-core/Tests/Unit/src/ai/SeverityLevel.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/SeverityLevel.tests.ts @@ -1,5 +1,5 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { eSeverityLevel, SeverityLevel } from "../../../../../src/index"; +import { eSeverityLevel, SeverityLevel } from "../../../../src/interfaces/ai/contracts/SeverityLevel"; export class SeverityLevelTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/ai/StatsBeat.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/StatsBeat.Tests.ts index 269213fb8..3337248d9 100644 --- a/shared/otel-core/Tests/Unit/src/ai/StatsBeat.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/StatsBeat.Tests.ts @@ -1,16 +1,16 @@ // import * as sinon from "sinon"; // import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -// import { IPayloadData } from "../../../../../src/JavaScriptSDK.Interfaces/IXHROverride"; -// import { IStatsMgr } from "../../../../../src/JavaScriptSDK.Interfaces/IStatsMgr"; -// import { AppInsightsCore } from "../../../../../src/core/AppInsightsCore"; -// import { IConfiguration } from "../../../../../src/JavaScriptSDK.Interfaces/IConfiguration"; -// import { createStatsMgr } from "../../../../../src/core/StatsBeat"; -// import { IStatsBeatState } from "../../../../../src/JavaScriptSDK.Interfaces/IStatsBeat"; -// import { eStatsType } from "../../../../../src/JavaScriptSDK.Enums/StatsType"; -// import { ITelemetryItem } from "../../../../../src/JavaScriptSDK.Interfaces/ITelemetryItem"; -// import { IPlugin } from "../../../../../src/JavaScriptSDK.Interfaces/ITelemetryPlugin"; -// import { IAppInsightsCore } from "../../../../../src/JavaScriptSDK.Interfaces/IAppInsightsCore"; -// import { FeatureOptInMode } from "../../../../../src/JavaScriptSDK.Enums/FeatureOptInEnums"; +// import { IPayloadData } from "../../../../src/JavaScriptSDK.Interfaces/IXHROverride"; +// import { IStatsMgr } from "../../../../src/JavaScriptSDK.Interfaces/IStatsMgr"; +// import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +// import { IConfiguration } from "../../../../src/JavaScriptSDK.Interfaces/IConfiguration"; +// import { createStatsMgr } from "../../../../src/core/StatsBeat"; +// import { IStatsBeatState } from "../../../../src/JavaScriptSDK.Interfaces/IStatsBeat"; +// import { eStatsType } from "../../../../src/JavaScriptSDK.Enums/StatsType"; +// import { ITelemetryItem } from "../../../../src/JavaScriptSDK.Interfaces/ITelemetryItem"; +// import { IPlugin } from "../../../../src/JavaScriptSDK.Interfaces/ITelemetryPlugin"; +// import { IAppInsightsCore } from "../../../../src/JavaScriptSDK.Interfaces/IAppInsightsCore"; +// import { FeatureOptInMode } from "../../../../src/JavaScriptSDK.Enums/FeatureOptInEnums"; // const STATS_COLLECTION_SHORT_INTERVAL: number = 900; // 15 minutes diff --git a/shared/otel-core/Tests/Unit/src/ai/TestPlugins.ts b/shared/otel-core/Tests/Unit/src/ai/TestPlugins.ts index 91b29c963..36d2af9d5 100644 --- a/shared/otel-core/Tests/Unit/src/ai/TestPlugins.ts +++ b/shared/otel-core/Tests/Unit/src/ai/TestPlugins.ts @@ -1,15 +1,15 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { _eInternalMessageId } from "../../../../../src/index"; -import { ITelemetryItem } from "../../../../../src/index"; -import { IProcessTelemetryContext, IProcessTelemetryUpdateContext } from "../../../../../src/index"; -import { TelemetryUpdateReason } from "../../../../../src/index"; -import { IConfiguration } from "../../../../../src/index"; -import { IPlugin, ITelemetryPlugin } from "../../../../../src/index"; -import { IAppInsightsCore } from "../../../../../src/index"; -import { ITelemetryPluginChain } from "../../../../../src/index"; -import { ITelemetryUpdateState } from "../../../../../src/index"; -import { IChannelControls } from "../../../../../src/index"; -import { BaseTelemetryPlugin } from "../../../../../src/index"; +import { IPlugin, ITelemetryPlugin } from "../../../../src/interfaces/ai/ITelemetryPlugin"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { BaseTelemetryPlugin } from "../../../../src/core/BaseTelemetryPlugin"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { ITelemetryPluginChain } from "../../../../src/interfaces/ai/ITelemetryPluginChain"; +import { ITelemetryItem } from "../../../../src/interfaces/ai/ITelemetryItem"; +import { IProcessTelemetryContext, IProcessTelemetryUpdateContext } from "../../../../src/interfaces/ai/IProcessTelemetryContext"; +import { ITelemetryUpdateState } from "../../../../src/interfaces/ai/ITelemetryUpdateState"; +import { TelemetryUpdateReason } from "../../../../src/enums/ai/TelemetryUpdateReason"; +import { IChannelControls } from "../../../../src/interfaces/ai/IChannelControls"; + export class TestPlugin implements IPlugin { diff --git a/shared/otel-core/Tests/Unit/src/ai/ThrottleMgr.tests.ts b/shared/otel-core/Tests/Unit/src/ai/ThrottleMgr.tests.ts index 61d9f68d8..022e549d5 100644 --- a/shared/otel-core/Tests/Unit/src/ai/ThrottleMgr.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/ThrottleMgr.tests.ts @@ -1,10 +1,14 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { AppInsightsCore, IAppInsightsCore, IConfiguration, IPlugin, _eInternalMessageId } from "../../../../../src/index"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { IPlugin } from "../../../../src/interfaces/ai/ITelemetryPlugin"; +import { _eInternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; import { SinonSpy } from "sinon"; -import { ThrottleMgr } from "../../../../../src/diagnostics/ThrottleMgr"; -import { IThrottleInterval, IThrottleLimit, IThrottleMgrConfig, IThrottleResult } from "../../../../../src/interfaces/ai/IThrottleMgr"; -import { utlCanUseLocalStorage } from "../../../../../src/utils/StorageHelperFuncs"; -import { IConfig } from "../../../../../src/index"; +import { ThrottleMgr } from "../../../../src/diagnostics/ThrottleMgr"; +import { IThrottleInterval, IThrottleLimit, IThrottleMgrConfig, IThrottleResult } from "../../../../src/interfaces/ai/IThrottleMgr"; +import { utlCanUseLocalStorage } from "../../../../src/utils/StorageHelperFuncs"; +import { IConfig } from "../../../../src/interfaces/ai/IConfig"; const daysInMonth = [ 31, // Jan diff --git a/shared/otel-core/Tests/Unit/src/ai/UpdateConfig.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/UpdateConfig.Tests.ts index 6da3f516b..95999a5b7 100644 --- a/shared/otel-core/Tests/Unit/src/ai/UpdateConfig.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/UpdateConfig.Tests.ts @@ -1,8 +1,8 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; import { dumpObj } from "@nevware21/ts-utils"; -import { AppInsightsCore } from "../../../../../src/core/AppInsightsCore"; -import { _eInternalMessageId } from "../../../../../src/index"; -import { IConfiguration } from "../../../../../src/index"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { _eInternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; import { OldTrackPlugin, TestChannelPlugin, TestPlugin, TestSamplingPlugin, TrackPlugin } from "./TestPlugins"; const AIInternalMessagePrefix = "AITR_"; @@ -25,7 +25,7 @@ export class UpdateConfigTests extends AITestClass { test: () => { const iKey1 = "09465199-12AA-4124-817F-544738CC7C41"; const iKey2 = "00000000-1111-7777-8888-999999999999"; - const testEndpoint1 = "https://localhost:9001/TestEndpoint"; + const testEndpoint1 = "https://localhost:9002/TestEndpoint"; const channelPlugin = new TestChannelPlugin(); const trackPlugin = new TrackPlugin(); @@ -56,7 +56,7 @@ export class UpdateConfigTests extends AITestClass { Assert.equal(undefined, testSamplingPlugin._updatedConfig, "Config has not been updated"); appInsightsCore.updateCfg({ - endpointUrl: "https://localhost:9001/TestEndpoint", + endpointUrl: "https://localhost:9002/TestEndpoint", disableCookiesUsage: true, extensions: [] // Try and replace the extensions }, true); @@ -116,7 +116,7 @@ export class UpdateConfigTests extends AITestClass { test: () => { const iKey1 = "09465199-12AA-4124-817F-544738CC7C41"; const iKey2 = "00000000-1111-7777-8888-999999999999"; - const testEndpoint1 = "https://localhost:9001/TestEndpoint"; + const testEndpoint1 = "https://localhost:9002/TestEndpoint"; const channelPlugin = new TestChannelPlugin(); const trackPlugin = new OldTrackPlugin(); @@ -147,7 +147,7 @@ export class UpdateConfigTests extends AITestClass { Assert.equal(undefined, testSamplingPlugin._updatedConfig, "Config has not been updated"); appInsightsCore.updateCfg({ - endpointUrl: "https://localhost:9001/TestEndpoint", + endpointUrl: "https://localhost:9002/TestEndpoint", disableCookiesUsage: true }, true); @@ -206,7 +206,7 @@ export class UpdateConfigTests extends AITestClass { test: () => { const iKey1 = "09465199-12AA-4124-817F-544738CC7C41"; const iKey2 = "00000000-1111-7777-8888-999999999999"; - const testEndpoint1 = "https://localhost:9001/TestEndpoint"; + const testEndpoint1 = "https://localhost:9002/TestEndpoint"; const channelPlugin = new TestChannelPlugin(); const trackPlugin = new OldTrackPlugin(); @@ -246,7 +246,7 @@ export class UpdateConfigTests extends AITestClass { Assert.equal(undefined, testSamplingPlugin._updatedConfig, "Config has not been updated"); appInsightsCore.updateCfg({ - endpointUrl: "https://localhost:9001/TestEndpoint", + endpointUrl: "https://localhost:9002/TestEndpoint", cookieCfg: { enabled: true } diff --git a/shared/otel-core/Tests/Unit/src/ai/Util.tests.ts b/shared/otel-core/Tests/Unit/src/ai/Util.tests.ts index 005791ebf..e84ee7156 100644 --- a/shared/otel-core/Tests/Unit/src/ai/Util.tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/Util.tests.ts @@ -1,11 +1,11 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { ICorrelationConfig } from "../../../../../src/interfaces/ai/ICorrelationConfig"; -import { strStartsWith } from "@nevware21/ts-utils"; -import { correlationIdCanIncludeCorrelationHeader } from "../../../../../src/utils/Util"; -import { createDomEvent } from "../../../../../src/utils/DomHelperFuncs"; -import { urlParseFullHost, urlParseHost, urlParseUrl } from "../../../../../src/utils/UrlHelperFuncs"; -import { getIEVersion } from "../../../../../src/utils/EnvUtils"; -import { uaDisallowsSameSiteNone } from "../../../../../src/core/CookieMgr"; +import { ICorrelationConfig } from "../../../../src/interfaces/ai/ICorrelationConfig"; +import { setBypassLazyCache, strStartsWith } from "@nevware21/ts-utils"; +import { correlationIdCanIncludeCorrelationHeader } from "../../../../src/utils/Util"; +import { createDomEvent } from "../../../../src/utils/DomHelperFuncs"; +import { urlParseFullHost, urlParseHost, urlParseUrl } from "../../../../src/utils/UrlHelperFuncs"; +import { getIEVersion } from "../../../../src/utils/EnvUtils"; +import { uaDisallowsSameSiteNone } from "../../../../src/core/CookieMgr"; export class UtilTests extends AITestClass { private testRegexLists = (config: ICorrelationConfig, exp: boolean, host: string) => { @@ -17,9 +17,13 @@ export class UtilTests extends AITestClass { }; public testInitialize() { + super.testInitialize(); + setBypassLazyCache(true); } - public testCleanup() {} + public testCleanup() { + super.testCleanup(); + } public registerTests() { this.testCase({ diff --git a/shared/otel-core/Tests/Unit/src/ai/errors.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/errors.Tests.ts index aaf80c226..0fbdf0243 100644 --- a/shared/otel-core/Tests/Unit/src/ai/errors.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/errors.Tests.ts @@ -1,5 +1,8 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { getOpenTelemetryError, OTelInvalidAttributeError, OTelSpanError, throwOTelError, throwOTelInvalidAttributeError, throwOTelNotImplementedError, throwOTelSpanError } from "../../../../../src/index"; +import { getOpenTelemetryError, throwOTelError } from "../../../../src/otel/api/errors/OTelError"; +import { OTelInvalidAttributeError, throwOTelInvalidAttributeError } from "../../../../src/otel/api/errors/OTelInvalidAttributeError"; +import { throwOTelNotImplementedError } from "../../../../src/otel/api/errors/OTelNotImplementedError"; +import { OTelSpanError, throwOTelSpanError } from "../../../../src/otel/api/errors/OTelSpanError"; import { isFunction, isString, dumpObj } from "@nevware21/ts-utils"; diff --git a/shared/otel-core/Tests/Unit/src/ai/traceState.Tests.ts b/shared/otel-core/Tests/Unit/src/ai/traceState.Tests.ts index 78362e50c..fd833c7d1 100644 --- a/shared/otel-core/Tests/Unit/src/ai/traceState.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/ai/traceState.Tests.ts @@ -1,5 +1,5 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { createOTelTraceState } from "../../../../../src/index"; +import { createOTelTraceState } from "../../../../src/otel/api/trace/traceState"; import { strRepeat } from "@nevware21/ts-utils"; export class OTelTraceApiTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/attribute/AttributeContainer.Tests.ts b/shared/otel-core/Tests/Unit/src/attribute/AttributeContainer.Tests.ts index 497a572ed..539f9de8a 100644 --- a/shared/otel-core/Tests/Unit/src/attribute/AttributeContainer.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/attribute/AttributeContainer.Tests.ts @@ -2,9 +2,9 @@ import { AITestClass, Assert } from "@microsoft/ai-test-framework"; import { objKeys } from "@nevware21/ts-utils"; import { addAttributes, createAttributeContainer, createAttributeSnapshot, isAttributeContainer } from "../../../../src/otel/attribute/attributeContainer"; import { eAttributeFilter, IAttributeChangeInfo } from "../../../../src/interfaces/otel/attribute/IAttributeContainer"; +import { eAttributeChangeOp } from "../../../../src/enums/otel/eAttributeChangeOp"; import { IOTelConfig } from "../../../../src/interfaces/otel/config/IOTelConfig"; import { IOTelAttributes } from "../../../../src/interfaces/otel/IOTelAttributes"; -import { eAttributeChangeOp } from "../../../../src/enums/otel/eAttributeChangeOp"; export class AttributeContainerTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/config/Dynamic.Tests.ts b/shared/otel-core/Tests/Unit/src/config/Dynamic.Tests.ts index 2f444fc85..8ec5605b2 100644 --- a/shared/otel-core/Tests/Unit/src/config/Dynamic.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/config/Dynamic.Tests.ts @@ -1,15 +1,15 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; import { dumpObj } from "@nevware21/ts-utils"; -import { AppInsightsCore } from "../../../../../src/core/AppInsightsCore"; -import { _eInternalMessageId } from "../../../../../src/index"; -import { IConfiguration } from "../../../../../src/index"; -import { IPlugin, ITelemetryPlugin } from "../../../../../src/index"; -import { ITelemetryItem } from "../../../../../src/index"; -import { IAppInsightsCore } from "../../../../../src/index"; -import { ITelemetryPluginChain } from "../../../../../src/index"; -import { IProcessTelemetryContext } from "../../../../../src/index"; -import { OldTrackPlugin, TestChannelPlugin, TestPlugin } from "./TestPlugins"; -import { BaseTelemetryPlugin } from "../../../../../src/index"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { _eInternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { IPlugin, ITelemetryPlugin } from "../../../../src/interfaces/ai/ITelemetryPlugin"; +import { ITelemetryItem } from "../../../../src/interfaces/ai/ITelemetryItem"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { ITelemetryPluginChain } from "../../../../src/interfaces/ai/ITelemetryPluginChain"; +import { IProcessTelemetryContext } from "../../../../src/interfaces/ai/IProcessTelemetryContext"; +import { OldTrackPlugin, TestChannelPlugin, TestPlugin } from "../ai/TestPlugins"; +import { BaseTelemetryPlugin } from "../../../../src/core/BaseTelemetryPlugin"; export class DynamicTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/config/DynamicConfig.Tests.ts b/shared/otel-core/Tests/Unit/src/config/DynamicConfig.Tests.ts index dc3308b60..afaba8ff2 100644 --- a/shared/otel-core/Tests/Unit/src/config/DynamicConfig.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/config/DynamicConfig.Tests.ts @@ -1,20 +1,23 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { eLoggingSeverity, _eInternalMessageId, LoggingSeverity, _InternalMessageId } from "../../../../../src/index"; -import { IConfigDefaults } from "../../../../../src/index"; -import { IConfiguration } from "../../../../../src/index"; -import { blockDynamicConversion, forceDynamicConversion, getDynamicConfigHandler } from "../../../../../src/config/DynamicSupport"; -import { createDynamicConfig, onConfigChange } from "../../../../../src/config/DynamicConfig"; +import { eLoggingSeverity, _eInternalMessageId, LoggingSeverity, _InternalMessageId } from "../../../../src/enums/ai/LoggingEnums"; +import { IConfigDefaults } from "../../../../src/interfaces/config/IConfigDefaults"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { blockDynamicConversion, forceDynamicConversion, getDynamicConfigHandler } from "../../../../src/config/DynamicSupport"; +import { createDynamicConfig, onConfigChange } from "../../../../src/config/DynamicConfig"; import { arrForEach, dumpObj, isArray, isFunction, objForEachKey, objKeys, isPlainObject, objHasOwn, objDeepFreeze, objDefineProps, strContains } from "@nevware21/ts-utils"; -import { IAppInsightsCore } from "../../../../../src/index"; -import { INotificationManager } from "../../../../../src/index"; -import { IPerfManager } from "../../../../../src/index"; -import { AppInsightsCore, DiagnosticLogger, IDiagnosticLogger, IProcessTelemetryContext } from "../../../../../src/index"; -import { ITelemetryItem } from "../../../../../src/index"; -import { ITelemetryPluginChain } from "../../../../../src/index"; -import { ITelemetryPlugin } from "../../../../../src/index"; -import { IChannelControls } from "../../../../../src/index"; -import { TestPlugin, TestSamplingPlugin, TrackPlugin } from "./TestPlugins"; -import { STR_CHANNELS, STR_CREATE_PERF_MGR, STR_EXTENSIONS, STR_EXTENSION_CONFIG, UNDEFINED_VALUE } from "../../../../../src/constants/InternalConstants"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { INotificationManager } from "../../../../src/interfaces/ai/INotificationManager"; +import { IPerfManager } from "../../../../src/interfaces/ai/IPerfManager"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { DiagnosticLogger } from "../../../../src/diagnostics/DiagnosticLogger"; +import { IDiagnosticLogger } from "../../../../src/interfaces/ai/IDiagnosticLogger"; +import { IProcessTelemetryContext } from "../../../../src/interfaces/ai/IProcessTelemetryContext"; +import { ITelemetryItem } from "../../../../src/interfaces/ai/ITelemetryItem"; +import { ITelemetryPluginChain } from "../../../../src/interfaces/ai/ITelemetryPluginChain"; +import { ITelemetryPlugin } from "../../../../src/interfaces/ai/ITelemetryPlugin"; +import { IChannelControls } from "../../../../src/interfaces/ai/IChannelControls"; +import { TestPlugin, TestSamplingPlugin, TrackPlugin } from "../ai/TestPlugins"; +import { STR_CHANNELS, STR_CREATE_PERF_MGR, STR_EXTENSIONS, STR_EXTENSION_CONFIG, UNDEFINED_VALUE } from "../../../../src/constants/InternalConstants"; const coreDefaultConfig: IConfigDefaults = objDeepFreeze({ cookieCfg: {}, @@ -98,7 +101,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; let dynamicHandler = createDynamicConfig(theConfig, theDefaults, this._testLogger, false); @@ -123,15 +126,15 @@ export class DynamicConfigTests extends AITestClass { }); Assert.equal("testiKey", dynamicConfig.instrumentationKey, "Expect the iKey to be set"); - Assert.equal("https://localhost:9001", dynamicConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", dynamicConfig.endpointUrl, "Expect the endpoint to be set"); dynamicConfig.instrumentationKey = "newIkey"; Assert.equal("newIkey", dynamicConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://localhost:9001", dynamicConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", dynamicConfig.endpointUrl, "Expect the endpoint to be set"); - dynamicConfig.endpointUrl = "https://newendpoint.localhost:9001"; + dynamicConfig.endpointUrl = "https://newendpoint.localhost:9002"; Assert.equal("newIkey", dynamicConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", dynamicConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", dynamicConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -146,7 +149,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", cookieCfg: { enabled: undefined, domain: "test", @@ -187,15 +190,15 @@ export class DynamicConfigTests extends AITestClass { }); Assert.equal("testiKey", theConfig.instrumentationKey, "Expect the iKey to be set"); - Assert.equal("https://localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be set"); theConfig.instrumentationKey = "newIkey"; Assert.equal("newIkey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be set"); - theConfig.endpointUrl = "https://newendpoint.localhost:9001"; + theConfig.endpointUrl = "https://newendpoint.localhost:9002"; Assert.equal("newIkey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -208,7 +211,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; // Creates a dynamic config as a side-effect @@ -230,15 +233,15 @@ export class DynamicConfigTests extends AITestClass { Assert.equal(dynamicHandler?.cfg, theConfig, "The handler should point to the config"); Assert.equal("testiKey", theConfig.instrumentationKey, "Expect the iKey to be set"); - Assert.equal("https://localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be set"); theConfig.instrumentationKey = "newIkey"; Assert.equal("newIkey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be set"); - theConfig.endpointUrl = "https://newendpoint.localhost:9001"; + theConfig.endpointUrl = "https://newendpoint.localhost:9002"; Assert.equal("newIkey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -250,7 +253,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; let dynamicHandler = createDynamicConfig(theConfig, theDefaults, this._testLogger); @@ -260,7 +263,7 @@ export class DynamicConfigTests extends AITestClass { Assert.equal(dynamicHandler?.cfg, theConfig, "The handler should point to the config"); let expectediKey = "testiKey"; - let expectedEndpointUrl = "https://localhost:9001"; + let expectedEndpointUrl = "https://localhost:9002"; let onChangeCalled = 0; let onChange = onConfigChange(dynamicConfig, (details) => { onChangeCalled ++; @@ -278,14 +281,14 @@ export class DynamicConfigTests extends AITestClass { dynamicHandler?.notify(); Assert.equal(2, onChangeCalled, "Expected the onChanged was called again"); - expectedEndpointUrl = "https://newendpoint.localhost:9001"; + expectedEndpointUrl = "https://newendpoint.localhost:9002"; theConfig.endpointUrl = expectedEndpointUrl; Assert.equal(2, onChangeCalled, "Expected the onChanged was called"); dynamicHandler?.notify(); Assert.equal(3, onChangeCalled, "Expected the onChanged was called again"); Assert.equal("newIKey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -297,7 +300,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; let dynamicHandler = createDynamicConfig(theConfig, theDefaults, this._testLogger); @@ -307,7 +310,7 @@ export class DynamicConfigTests extends AITestClass { Assert.equal(dynamicHandler?.cfg, theConfig, "The handler should point to the config"); let expectediKey = "testiKey"; - let expectedEndpointUrl = "https://localhost:9001"; + let expectedEndpointUrl = "https://localhost:9002"; let onChangeCalled = 0; let onChange = onConfigChange(dynamicConfig, (details) => { onChangeCalled ++; @@ -321,7 +324,7 @@ export class DynamicConfigTests extends AITestClass { expectediKey = "newIKey"; theConfig.instrumentationKey = expectediKey; - expectedEndpointUrl = "https://newendpoint.localhost:9001"; + expectedEndpointUrl = "https://newendpoint.localhost:9002"; theConfig.endpointUrl = expectedEndpointUrl; Assert.equal(1, onChangeCalled, "Expected the onChanged was only called once"); @@ -331,7 +334,7 @@ export class DynamicConfigTests extends AITestClass { dynamicHandler?.notify(); Assert.equal(2, onChangeCalled, "Expected the onChanged was not called again"); Assert.equal("newIKey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -343,7 +346,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; let dynamicConfig = createDynamicConfig(theConfig, theDefaults, this._testLogger).cfg; @@ -356,7 +359,7 @@ export class DynamicConfigTests extends AITestClass { let onChange = onConfigChange(theConfig, () => { onChangeCalled ++; Assert.equal("testiKey", theConfig.instrumentationKey, "Expect the iKey to be set"); - Assert.equal("https://localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be set"); }); Assert.equal(1, onChangeCalled, "Expected the onChanged was called"); @@ -364,11 +367,11 @@ export class DynamicConfigTests extends AITestClass { theConfig.instrumentationKey = "newIkey"; Assert.equal("newIkey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be set"); + Assert.equal("https://localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be set"); - theConfig.endpointUrl = "https://newendpoint.localhost:9001"; + theConfig.endpointUrl = "https://newendpoint.localhost:9002"; Assert.equal("newIkey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -381,7 +384,7 @@ export class DynamicConfigTests extends AITestClass { }; const theDefaults: IConfigDefaults = { - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; let dynamicHandler = createDynamicConfig(theConfig, theDefaults, this._testLogger); @@ -391,7 +394,7 @@ export class DynamicConfigTests extends AITestClass { Assert.equal(dynamicHandler?.cfg, theConfig, "The handler should point to the config"); let expectediKey = "testiKey"; - let expectedEndpointUrl = "https://localhost:9001"; + let expectedEndpointUrl = "https://localhost:9002"; let onChangeCalled = 0; let onChange = onConfigChange(dynamicConfig, (details) => { onChangeCalled ++; @@ -413,7 +416,7 @@ export class DynamicConfigTests extends AITestClass { dynamicHandler?.notify(); Assert.equal(2, onChangeCalled, "Expected the onChanged was called again"); - expectedEndpointUrl = "https://newendpoint.localhost:9001"; + expectedEndpointUrl = "https://newendpoint.localhost:9002"; theConfig.endpointUrl = expectedEndpointUrl; Assert.equal(2, onChangeCalled, "Expected the onChanged was called"); @@ -423,7 +426,7 @@ export class DynamicConfigTests extends AITestClass { dynamicHandler?.notify(); Assert.equal(3, onChangeCalled, "Expected the onChanged was called again"); Assert.equal("newIKey", theConfig.instrumentationKey, "Expect the iKey to be changed"); - Assert.equal("https://newendpoint.localhost:9001", theConfig.endpointUrl, "Expect the endpoint to be changed"); + Assert.equal("https://newendpoint.localhost:9002", theConfig.endpointUrl, "Expect the endpoint to be changed"); } }); @@ -433,7 +436,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: IConfiguration = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; const channelPlugin = new TestChannelPlugin(); @@ -476,7 +479,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: IConfiguration = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; const channelPlugin = new TestChannelPlugin(); @@ -505,7 +508,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: IConfiguration = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001" + endpointUrl: "https://localhost:9002" }; const channelPlugin = new TestChannelPlugin(); @@ -548,7 +551,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: IConfiguration = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", enableDebug: false, loggingLevelConsole: 1 }; @@ -611,7 +614,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: any = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", enableDebug: false, loggingLevelConsole: 1, extensionConfig: { @@ -750,7 +753,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: any = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", enableDebug: false, loggingLevelConsole: 1, extensionConfig: { @@ -804,7 +807,7 @@ export class DynamicConfigTests extends AITestClass { let theConfig: any = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", enableDebug: false, loggingLevelConsole: 1, extensionConfig: { @@ -904,7 +907,7 @@ export class DynamicConfigTests extends AITestClass { let theConfig: any = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", enableDebug: false, loggingLevelConsole: 1, userCfg: undefined @@ -997,7 +1000,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { let theConfig: any = { instrumentationKey: "testiKey", - endpointUrl: "https://localhost:9001", + endpointUrl: "https://localhost:9002", enableDebug: false, loggingLevelConsole: 1, userCfg: undefined, @@ -1095,7 +1098,7 @@ export class DynamicConfigTests extends AITestClass { test: () => { const iKey1 = "09465199-12AA-4124-817F-544738CC7C41"; const iKey2 = "00000000-1111-7777-8888-999999999999"; - const testEndpoint1 = "https://localhost:9001/TestEndpoint"; + const testEndpoint1 = "https://localhost:9002/TestEndpoint"; const channelPlugin = new TestChannelPlugin(); const trackPlugin = new TrackPlugin(); diff --git a/shared/otel-core/Tests/Unit/src/index.tests.ts b/shared/otel-core/Tests/Unit/src/index.tests.ts index dbb996bfd..c81be1895 100644 --- a/shared/otel-core/Tests/Unit/src/index.tests.ts +++ b/shared/otel-core/Tests/Unit/src/index.tests.ts @@ -1,36 +1,84 @@ import { OTelApiTests } from "./api/OTelApi.Tests"; import { AttributeContainerTests } from "./attribute/AttributeContainer.Tests"; -import { CommonUtilsTests } from "./internal/commonUtils.Tests"; +import { HandleErrorsTests } from "./internal/handleErrors.Tests"; import { SpanTests } from "./trace/span.Tests"; -import { OTelLogRecordTests } from "./sdk/OTelLogRecord.Tests"; +// import { OTelLogRecordTests } from "./sdk/OTelLogRecord.Tests"; import { OTelMultiLogRecordProcessorTests } from "./sdk/OTelMultiLogRecordProcessor.Tests"; +import { CommonUtilsTests } from "./sdk/commonUtils.Tests"; +import { OpenTelemetryErrorsTests } from "./ai/errors.Tests"; +import { OTelTraceApiTests } from "./trace/traceState.Tests"; // AppInsightsCommon tests -import { ApplicationInsightsTests } from "./ai/Common/AppInsightsCommon.tests"; -import { ExceptionTests } from "./ai/Common/Exception.tests"; -import { UtilTests } from "./ai/Common/Util.tests"; -import { ConnectionStringParserTests } from "./ai/Common/ConnectionStringParser.tests"; -import { SeverityLevelTests } from "./ai/Common/SeverityLevel.tests"; -import { RequestHeadersTests } from "./ai/Common/RequestHeaders.tests"; -import { W3CTraceStateModesTests } from "./ai/Common/W3CTraceStateModes.tests"; -import { W3cTraceParentTests } from "./ai/Common/W3cTraceParentTests"; +import { ApplicationInsightsTests } from "./ai/AppInsightsCommon.tests"; +import { ExceptionTests } from "./ai/Exception.tests"; +import { UtilTests } from "./ai/Util.tests"; +import { ConnectionStringParserTests } from "./ai/ConnectionStringParser.tests"; +import { SeverityLevelTests } from "./ai/SeverityLevel.tests"; +import { RequestHeadersTests } from "./ai/RequestHeaders.tests"; +import { W3CTraceStateModesTests } from "./trace/W3CTraceStateModes.tests"; +import { ThrottleMgrTest } from "./ai/ThrottleMgr.tests"; +import { W3cTraceParentTests } from "./trace/W3cTraceParentTests"; +import { W3cTraceStateTests } from "./trace/W3cTraceState.Tests"; + +import { ApplicationInsightsCoreTests } from "./ai/ApplicationInsightsCore.Tests"; +import { CookieManagerTests } from "./ai/CookieManager.Tests"; +import { GlobalTestHooks } from "./ai/GlobalTestHooks.Test"; +import { HelperFuncTests } from "./ai/HelperFunc.Tests"; +import { OTelCoreSizeCheck } from "./sdk/OTelCoreSize.Tests"; +import { EventHelperTests } from "./ai/EventHelper.Tests"; +import { LoggingEnumTests } from "./ai/LoggingEnum.Tests"; +import { DynamicTests } from "./config/Dynamic.Tests"; +import { UpdateConfigTests } from "./ai/UpdateConfig.Tests"; +import { EventsDiscardedReasonTests } from "./ai/EventsDiscardedReason.Tests"; +import { DynamicConfigTests } from "./config/DynamicConfig.Tests"; +import { SendPostManagerTests } from "./ai/SendPostManager.Tests"; +// import { StatsBeatTests } from "./StatsBeat.Tests"; +import { TraceUtilsTests } from "./trace/traceUtils.Tests"; + export function runTests() { // OTel tests new OTelApiTests().registerTests(); new AttributeContainerTests().registerTests(); - new CommonUtilsTests().registerTests(); + new HandleErrorsTests().registerTests(); new SpanTests().registerTests(); - new OTelLogRecordTests().registerTests(); + // new OTelLogRecordTests().registerTests(); new OTelMultiLogRecordProcessorTests().registerTests(); + new CommonUtilsTests().registerTests(); + new OpenTelemetryErrorsTests().registerTests(); + new OTelTraceApiTests().registerTests(); - // AppInsightsCommon tests + new GlobalTestHooks().registerTests(); + new DynamicTests().registerTests(); + new DynamicConfigTests().registerTests(); + new ApplicationInsightsCoreTests().registerTests(); + new CookieManagerTests(false).registerTests(); + new CookieManagerTests(true).registerTests(); + new HelperFuncTests().registerTests(); + new OTelCoreSizeCheck().registerTests(); + new EventHelperTests().registerTests(); + new LoggingEnumTests().registerTests(); + new UpdateConfigTests().registerTests(); + new EventsDiscardedReasonTests().registerTests(); + new W3cTraceParentTests().registerTests(); + // new OTelTraceApiTests().registerTests(); + // new CommonUtilsTests().registerTests(); + // new OpenTelemetryErrorsTests().registerTests(); + // new SpanTests().registerTests(); + // new AttributeContainerTests().registerTests(); + new W3cTraceStateTests().registerTests(); + new TraceUtilsTests().registerTests(); + // new StatsBeatTests(false).registerTests(); + // new StatsBeatTests(true).registerTests(); + new SendPostManagerTests().registerTests(); + + // Application Insights Common tests (merged from AppInsightsCommon) new ApplicationInsightsTests().registerTests(); - new ExceptionTests().registerTests(); - new UtilTests().registerTests(); new ConnectionStringParserTests().registerTests(); - new SeverityLevelTests().registerTests(); + new ExceptionTests().registerTests(); new RequestHeadersTests().registerTests(); + new SeverityLevelTests().registerTests(); + new ThrottleMgrTest().registerTests(); + new UtilTests().registerTests(); new W3CTraceStateModesTests().registerTests(); - new W3cTraceParentTests().registerTests(); } diff --git a/shared/otel-core/Tests/Unit/src/internal/handleErrors.Tests.ts b/shared/otel-core/Tests/Unit/src/internal/handleErrors.Tests.ts index a6448b0d5..da907c735 100644 --- a/shared/otel-core/Tests/Unit/src/internal/handleErrors.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/internal/handleErrors.Tests.ts @@ -1,10 +1,10 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; import { getGlobal } from "@nevware21/ts-utils"; -import { handleAttribError, handleDebug, handleError, handleNotImplemented, handleSpanError, handleWarn } from "../../../../src/internal/commonUtils"; +import { handleAttribError, handleDebug, handleError, handleNotImplemented, handleSpanError, handleWarn } from "../../../../src/internal/handleErrors"; import { IOTelErrorHandlers } from "../../../../src/interfaces/otel/config/IOTelErrorHandlers"; -export class CommonUtilsTests extends AITestClass { +export class HandleErrorsTests extends AITestClass { public testInitialize() { super.testInitialize(); diff --git a/shared/otel-core/Tests/Unit/src/ai/AppInsightsCoreSize.Tests.ts b/shared/otel-core/Tests/Unit/src/sdk/OTelCoreSize.Tests.ts similarity index 80% rename from shared/otel-core/Tests/Unit/src/ai/AppInsightsCoreSize.Tests.ts rename to shared/otel-core/Tests/Unit/src/sdk/OTelCoreSize.Tests.ts index 1957809bd..b4a88c557 100644 --- a/shared/otel-core/Tests/Unit/src/ai/AppInsightsCoreSize.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/sdk/OTelCoreSize.Tests.ts @@ -1,6 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { dumpObj } from '@nevware21/ts-utils'; -import { createPromise, doAwait, IPromise } from '@nevware21/ts-async'; +import { dumpObj } from "@nevware21/ts-utils"; +import { createPromise, doAwait, IPromise } from "@nevware21/ts-async"; import * as pako from "pako"; const PACKAGE_JSON = "../package.json"; @@ -50,13 +50,13 @@ function _checkSize(checkType: string, maxSize: number, size: number, isNightly: Assert.ok(size <= maxSize, `exceed ${maxSize} KB, current ${checkType} size is: ${size} KB`); } -export class AppInsightsCoreSizeCheck extends AITestClass { - private readonly MAX_RAW_SIZE = 90; - private readonly MAX_BUNDLE_SIZE = 90; - private readonly MAX_RAW_DEFLATE_SIZE = 35; - private readonly MAX_BUNDLE_DEFLATE_SIZE = 35; - private readonly rawFilePath = "../dist/es5/applicationinsights-core-js.min.js"; - private readonly prodFilePath = "../browser/es5/applicationinsights-core-js.min.js"; +export class OTelCoreSizeCheck extends AITestClass { + private readonly MAX_RAW_SIZE = 150; + private readonly MAX_BUNDLE_SIZE = 150; + private readonly MAX_RAW_DEFLATE_SIZE = 60; + private readonly MAX_BUNDLE_DEFLATE_SIZE = 60; + private readonly rawFilePath = "../dist/es5/index.min.js"; + private readonly prodFilePath = "../browser/es5/otel-core-js.min.js"; public testInitialize() { } @@ -88,7 +88,7 @@ export class AppInsightsCoreSizeCheck extends AITestClass { let postfix = isProd ? "" : "-raw"; let fileName = _filePath.split("..")[1]; this.testCase({ - name: `Test applicationinsights-core${postfix} deflate size`, + name: `Test otel-core${postfix} deflate size`, test: () => { Assert.ok(true, `test file: ${fileName}`); return _loadPackageJson((isNightly, packageJson) => { @@ -96,7 +96,7 @@ export class AppInsightsCoreSizeCheck extends AITestClass { let request = new Request(_filePath, {method:"GET"}); return fetch(request).then((response) => { if (!response.ok) { - Assert.ok(false, `applicationinsights-core${postfix} deflate size error: ${response.statusText}`); + Assert.ok(false, `otel-core${postfix} deflate size error: ${response.statusText}`); return; } else { return response.text().then(text => { @@ -107,11 +107,11 @@ export class AppInsightsCoreSizeCheck extends AITestClass { _checkSize("deflate", _maxDeflateSize, deflateSize, isNightly); Assert.ok(deflateSize <= _maxDeflateSize ,`max ${_maxDeflateSize} KB, current deflate size is: ${deflateSize} KB`); }).catch((error) => { - Assert.ok(false, `applicationinsights-core${postfix} response error: ${error}`); + Assert.ok(false, `otel-core${postfix} response error: ${error}`); }); } }).catch((error: Error) => { - Assert.ok(false, `applicationinsights-core${postfix} deflate size error: ${error}`); + Assert.ok(false, `otel-core${postfix} deflate size error: ${error}`); }); }); } diff --git a/shared/otel-core/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts b/shared/otel-core/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts index 1e5236ecb..438e9276b 100644 --- a/shared/otel-core/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/sdk/OTelLogRecord.Tests.ts @@ -1,184 +1,184 @@ -import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +// import { AITestClass, Assert } from "@microsoft/ai-test-framework"; -import { IOTelLogRecord } from "../../../../src/interfaces/otel/logs/IOTelLogRecord"; -import { createLoggerProviderSharedState } from "../../../../src/internal/LoggerProviderSharedState"; -import { reconfigureLimits } from "../../../../src/otel/sdk/config"; -import { createLogRecord } from "../../../../src/otel/sdk/OTelLogRecord"; -import { IOTelLogRecordLimits } from "../../../../src/interfaces/otel/logs/IOTelLogRecordLimits"; +// import { IOTelLogRecord } from "../../../../src/interfaces/otel/logs/IOTelLogRecord"; +// import { createLoggerProviderSharedState } from "../../../../src/internal/LoggerProviderSharedState"; +// import { reconfigureLimits } from "../../../../src/otel/sdk/config"; +// import { createLogRecord } from "../../../../src/otel/sdk/OTelLogRecord"; +// import { IOTelLogRecordLimits } from "../../../../src/interfaces/otel/logs/IOTelLogRecordLimits"; -const setup = (logRecordLimits?: IOTelLogRecordLimits, data?: IOTelLogRecord) => { - const instrumentationScope = { - name: "test name", - version: "test version", - schemaUrl: "test schema url" - }; - const sharedState = createLoggerProviderSharedState( - undefined as any, - Infinity, - reconfigureLimits(logRecordLimits ?? {}), - [] - ); - const logRecord = createLogRecord( - sharedState, - instrumentationScope, - data ?? {} - ); - return { logRecord, instrumentationScope }; -}; +// const setup = (logRecordLimits?: IOTelLogRecordLimits, data?: IOTelLogRecord) => { +// const instrumentationScope = { +// name: "test name", +// version: "test version", +// schemaUrl: "test schema url" +// }; +// const sharedState = createLoggerProviderSharedState( +// undefined as any, +// Infinity, +// reconfigureLimits(logRecordLimits ?? {}), +// [] +// ); +// const logRecord = createLogRecord( +// sharedState, +// instrumentationScope, +// data ?? {} +// ); +// return { logRecord, instrumentationScope }; +// }; -export class OTelLogRecordTests extends AITestClass { - public testInitialize() { - super.testInitialize(); - } +// export class OTelLogRecordTests extends AITestClass { +// public testInitialize() { +// super.testInitialize(); +// } - public testCleanup() { - super.testCleanup(); - } +// public testCleanup() { +// super.testCleanup(); +// } - public registerTests() { - this.testCase({ - name: "LogRecord: constructor - should create an instance", - test: () => { - const { logRecord } = setup(); - Assert.ok(logRecord && typeof logRecord === "object", "LogRecord should be created"); - } - }); +// public registerTests() { +// this.testCase({ +// name: "LogRecord: constructor - should create an instance", +// test: () => { +// const { logRecord } = setup(); +// Assert.ok(logRecord && typeof logRecord === "object", "LogRecord should be created"); +// } +// }); - this.testCase({ - name: "LogRecord: constructor - should have a default timestamp", - test: () => { - const { logRecord } = setup(); - Assert.ok(logRecord.hrTime !== undefined, "hrTime should be defined"); - Assert.ok(logRecord.hrTime[0] > 0 || logRecord.hrTime[1] > 0, "hrTime should be set"); - } - }); +// this.testCase({ +// name: "LogRecord: constructor - should have a default timestamp", +// test: () => { +// const { logRecord } = setup(); +// Assert.ok(logRecord.hrTime !== undefined, "hrTime should be defined"); +// Assert.ok(logRecord.hrTime[0] > 0 || logRecord.hrTime[1] > 0, "hrTime should be set"); +// } +// }); - this.testCase({ - name: "LogRecord: setAttribute - should set an attribute", - test: () => { - const { logRecord } = setup(); - const testAttrs = { test: "value" }; - for (const [k, v] of Object.entries(testAttrs)) { - logRecord.setAttribute(k, v); - } - Assert.equal(logRecord.attributes["test"], "value", "Attribute should be set"); - } - }); +// this.testCase({ +// name: "LogRecord: setAttribute - should set an attribute", +// test: () => { +// const { logRecord } = setup(); +// const testAttrs = { test: "value" }; +// for (const [k, v] of Object.entries(testAttrs)) { +// logRecord.setAttribute(k, v); +// } +// Assert.equal(logRecord.attributes["test"], "value", "Attribute should be set"); +// } +// }); - this.testCase({ - name: "LogRecord: setAttribute - should be able to overwrite attributes", - test: () => { - const { logRecord } = setup(); - logRecord.setAttribute("overwrite", "initial value"); - logRecord.setAttribute("overwrite", "overwritten value"); - Assert.equal(logRecord.attributes["overwrite"], "overwritten value", "Attribute should be overwritten"); - } - }); +// this.testCase({ +// name: "LogRecord: setAttribute - should be able to overwrite attributes", +// test: () => { +// const { logRecord } = setup(); +// logRecord.setAttribute("overwrite", "initial value"); +// logRecord.setAttribute("overwrite", "overwritten value"); +// Assert.equal(logRecord.attributes["overwrite"], "overwritten value", "Attribute should be overwritten"); +// } +// }); - this.testCase({ - name: "LogRecord: setAttribute with attributeCountLimit - should remove / drop all remaining values after the number of values exceeds this limit", - test: () => { - const { logRecord } = setup({ attributeCountLimit: 100 }); - for (let i = 0; i < 150; i++) { - let attributeValue; - switch (i % 3) { - case 0: { - attributeValue = `bar${i}`; - break; - } - case 1: { - attributeValue = [`bar${i}`]; - break; - } - case 2: { - attributeValue = { - bar: `bar${i}` - }; - break; - } - default: { - attributeValue = `bar${i}`; - } - } - logRecord.setAttribute(`foo${i}`, attributeValue); - } - const { attributes, droppedAttributesCount } = logRecord; - Assert.equal(Object.keys(attributes).length, 100, "Should have 100 attributes"); - Assert.equal(attributes.foo0, "bar0", "foo0 should be bar0"); - Assert.deepEqual(attributes.foo98, { bar: "bar98" }, "foo98 should match"); - Assert.equal(attributes.foo147, "bar147", "foo147 should be bar147"); - Assert.equal(attributes.foo148, undefined, "foo148 should be undefined"); - Assert.deepEqual(attributes.foo149, { bar: "bar149" }, "foo149 should match"); - } - }); +// this.testCase({ +// name: "LogRecord: setAttribute with attributeCountLimit - should remove / drop all remaining values after the number of values exceeds this limit", +// test: () => { +// const { logRecord } = setup({ attributeCountLimit: 100 }); +// for (let i = 0; i < 150; i++) { +// let attributeValue; +// switch (i % 3) { +// case 0: { +// attributeValue = `bar${i}`; +// break; +// } +// case 1: { +// attributeValue = [`bar${i}`]; +// break; +// } +// case 2: { +// attributeValue = { +// bar: `bar${i}` +// }; +// break; +// } +// default: { +// attributeValue = `bar${i}`; +// } +// } +// logRecord.setAttribute(`foo${i}`, attributeValue); +// } +// const { attributes, droppedAttributesCount } = logRecord; +// Assert.equal(Object.keys(attributes).length, 100, "Should have 100 attributes"); +// Assert.equal(attributes.foo0, "bar0", "foo0 should be bar0"); +// Assert.deepEqual(attributes.foo98, { bar: "bar98" }, "foo98 should match"); +// Assert.equal(attributes.foo147, "bar147", "foo147 should be bar147"); +// Assert.equal(attributes.foo148, undefined, "foo148 should be undefined"); +// Assert.deepEqual(attributes.foo149, { bar: "bar149" }, "foo149 should match"); +// } +// }); - this.testCase({ - name: "LogRecord: setAttributes - should be able to set multiple attributes", - test: () => { - const { logRecord } = setup(); - const attrs = { attr1: "value1", attr2: "value2" }; - logRecord.setAttributes(attrs as any); - Assert.equal(logRecord.attributes["attr1"], "value1", "attr1 should be set"); - Assert.equal(logRecord.attributes["attr2"], "value2", "attr2 should be set"); - } - }); +// this.testCase({ +// name: "LogRecord: setAttributes - should be able to set multiple attributes", +// test: () => { +// const { logRecord } = setup(); +// const attrs = { attr1: "value1", attr2: "value2" }; +// logRecord.setAttributes(attrs as any); +// Assert.equal(logRecord.attributes["attr1"], "value1", "attr1 should be set"); +// Assert.equal(logRecord.attributes["attr2"], "value2", "attr2 should be set"); +// } +// }); - this.testCase({ - name: "LogRecord: setAttribute with attributeValueLengthLimit - should truncate value which length exceeds this limit", - test: () => { - const { logRecord } = setup({ attributeValueLengthLimit: 5 }); - logRecord.setAttribute("attr-with-more-length", "abcdefgh"); - Assert.equal(logRecord.attributes["attr-with-more-length"], "abcde", "Value should be truncated"); - } - }); +// this.testCase({ +// name: "LogRecord: setAttribute with attributeValueLengthLimit - should truncate value which length exceeds this limit", +// test: () => { +// const { logRecord } = setup({ attributeValueLengthLimit: 5 }); +// logRecord.setAttribute("attr-with-more-length", "abcdefgh"); +// Assert.equal(logRecord.attributes["attr-with-more-length"], "abcde", "Value should be truncated"); +// } +// }); - this.testCase({ - name: "LogRecord: should not truncate value which length not exceeds this limit", - test: () => { - const { logRecord } = setup({ attributeValueLengthLimit: 5 }); - logRecord.setAttribute("attr-with-less-length", "abc"); - Assert.equal(logRecord.attributes["attr-with-less-length"], "abc", "Value should not be truncated"); - } - }); +// this.testCase({ +// name: "LogRecord: should not truncate value which length not exceeds this limit", +// test: () => { +// const { logRecord } = setup({ attributeValueLengthLimit: 5 }); +// logRecord.setAttribute("attr-with-less-length", "abc"); +// Assert.equal(logRecord.attributes["attr-with-less-length"], "abc", "Value should not be truncated"); +// } +// }); - this.testCase({ - name: "LogRecord: should rewrite body directly through the property method", - test: () => { - const logRecordData: IOTelLogRecord = { - body: "this is a body" - }; - const { logRecord } = setup(undefined, logRecordData); - const newBody = "this is a new body"; - logRecord.setBody(newBody); - Assert.equal(logRecord.body, newBody, "Body should be updated"); - } - }); +// this.testCase({ +// name: "LogRecord: should rewrite body directly through the property method", +// test: () => { +// const logRecordData: IOTelLogRecord = { +// body: "this is a body" +// }; +// const { logRecord } = setup(undefined, logRecordData); +// const newBody = "this is a new body"; +// logRecord.setBody(newBody); +// Assert.equal(logRecord.body, newBody, "Body should be updated"); +// } +// }); - this.testCase({ - name: "LogRecord: should rewrite using the set method", - test: () => { - const logRecordData: IOTelLogRecord = { - body: "this is a body" - }; - const { logRecord } = setup(undefined, logRecordData); - const newBody = "this is a new body"; - logRecord.setBody(newBody); - Assert.equal(logRecord.body, newBody, "Body should be updated"); - } - }); +// this.testCase({ +// name: "LogRecord: should rewrite using the set method", +// test: () => { +// const logRecordData: IOTelLogRecord = { +// body: "this is a body" +// }; +// const { logRecord } = setup(undefined, logRecordData); +// const newBody = "this is a new body"; +// logRecord.setBody(newBody); +// Assert.equal(logRecord.body, newBody, "Body should be updated"); +// } +// }); - this.testCase({ - name: "LogRecord: should not rewrite directly through the property method after makeReadonly", - test: () => { - const logRecordData: IOTelLogRecord = { - body: "this is a body" - }; - const { logRecord } = setup(undefined, logRecordData); - logRecord._makeReadonly(); - const newBody = "this is a new body"; - logRecord.setBody(newBody); - Assert.equal(logRecord.body, logRecordData.body, "Body should not be changed after makeReadonly"); - } - }); - } -} +// this.testCase({ +// name: "LogRecord: should not rewrite directly through the property method after makeReadonly", +// test: () => { +// const logRecordData: IOTelLogRecord = { +// body: "this is a body" +// }; +// const { logRecord } = setup(undefined, logRecordData); +// logRecord._makeReadonly(); +// const newBody = "this is a new body"; +// logRecord.setBody(newBody); +// Assert.equal(logRecord.body, logRecordData.body, "Body should not be changed after makeReadonly"); +// } +// }); +// } +// } diff --git a/shared/otel-core/Tests/Unit/src/sdk/OTelLogger.Tests.ts b/shared/otel-core/Tests/Unit/src/sdk/OTelLogger.Tests.ts index abae31f6f..bc3e64be3 100644 --- a/shared/otel-core/Tests/Unit/src/sdk/OTelLogger.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/sdk/OTelLogger.Tests.ts @@ -10,6 +10,7 @@ import { createContextManager } from "../../../../src/otel/api/context/contextMa import { setContextSpanContext } from "../../../../src/otel/api/trace/utils"; import { createLogger } from "../../../../src/otel/sdk/OTelLogger"; import { createResolvedPromise } from "@nevware21/ts-async"; +import { IOTelApi, IOTelConfig } from "../../../../src"; // W3C trace flags constant for sampled traces const eW3CTraceFlags_Sampled = 1; @@ -17,8 +18,18 @@ const eW3CTraceFlags_Sampled = 1; type LoggerWithScope = IOTelLogger & { instrumentationScope: IOTelInstrumentationScope }; export class OTelLoggerTests extends AITestClass { + private _mockApi!: IOTelApi; + public testInitialize() { super.testInitialize(); + + + // Create mock API + this._mockApi = { + cfg: { + errorHandlers: {} + } as IOTelConfig + } as IOTelApi; } public testCleanup() { @@ -113,7 +124,8 @@ export class OTelLoggerTests extends AITestClass { spanId: "6e0c63257de34c92", traceFlags: eW3CTraceFlags_Sampled }; - const ROOT_CONTEXT = createContext(); + + const ROOT_CONTEXT = createContext(this._mockApi); const activeContext = setContextSpanContext(ROOT_CONTEXT, spanContext); const logRecordData: IOTelLogRecord = { context: activeContext diff --git a/shared/otel-core/Tests/Unit/src/sdk/commonUtils.Tests.ts b/shared/otel-core/Tests/Unit/src/sdk/commonUtils.Tests.ts new file mode 100644 index 000000000..92cf3bbad --- /dev/null +++ b/shared/otel-core/Tests/Unit/src/sdk/commonUtils.Tests.ts @@ -0,0 +1,1031 @@ +import { Assert, AITestClass } from "@microsoft/ai-test-framework"; +import { getGlobal } from "@nevware21/ts-utils"; +import { + handleAttribError, handleSpanError, handleDebug, handleWarn, handleError, handleNotImplemented +} from "../../../../src/internal/handleErrors"; +import { getUrl, getHttpUrl } from "../../../../src/internal/commonUtils"; +import { createAttributeContainer } from "../../../../src/otel/attribute/attributeContainer"; +import { IOTelErrorHandlers } from "../../../../src/interfaces/otel/config/IOTelErrorHandlers"; +import { IOTelConfig } from "../../../../src/interfaces/otel/config/IOTelConfig"; + +export class CommonUtilsTests extends AITestClass { + + public testInitialize() { + super.testInitialize(); + // Reset console mocks before each test + this._resetConsoleMocks(); + } + + public testCleanup() { + super.testCleanup(); + // Reset console mocks after each test + this._resetConsoleMocks(); + } + + public registerTests() { + this.testCase({ + name: "handleAttribError: should call custom attribError handler when provided", + test: () => { + // Arrange + let calledMessage = ""; + let calledKey = ""; + let calledValue: any = null; + const handlers: IOTelErrorHandlers = { + attribError: (message: string, key: string, value: any) => { + calledMessage = message; + calledKey = key; + calledValue = value; + } + }; + const testMessage = "Test error message"; + const testKey = "testKey"; + const testValue = { test: "value" }; + + // Act + handleAttribError(handlers, testMessage, testKey, testValue); + + // Assert + Assert.ok(calledMessage === testMessage, "Message should match"); + Assert.ok(calledKey === testKey, "Key should match"); + Assert.ok(calledValue === testValue, "Value should match"); + } + }); + + this.testCase({ + name: "handleAttribError: should call handleWarn when no custom attribError handler provided", + test: () => { + // Arrange + let warnCalled = false; + let warnMessage = ""; + const handlers: IOTelErrorHandlers = { + warn: (message: string) => { + warnCalled = true; + warnMessage = message; + } + }; + const testMessage = "Test error"; + const testKey = "testKey"; + const testValue = "testValue"; + + // Act + handleAttribError(handlers, testMessage, testKey, testValue); + + // Assert + Assert.ok(warnCalled, "Warn should be called"); + Assert.ok(warnMessage.includes(testMessage), "Warn message should contain original message"); + Assert.ok(warnMessage.includes(testKey), "Warn message should contain key"); + Assert.ok(warnMessage.includes(testValue), "Warn message should contain value"); + } + }); + + this.testCase({ + name: "handleSpanError: should call custom spanError handler when provided", + test: () => { + // Arrange + let calledMessage = ""; + let calledSpanName = ""; + const handlers: IOTelErrorHandlers = { + spanError: (message: string, spanName: string) => { + calledMessage = message; + calledSpanName = spanName; + } + }; + const testMessage = "Span error occurred"; + const testSpanName = "testSpan"; + + // Act + handleSpanError(handlers, testMessage, testSpanName); + + // Assert + Assert.ok(calledMessage === testMessage, "Message should match"); + Assert.ok(calledSpanName === testSpanName, "Span name should match"); + } + }); + + this.testCase({ + name: "handleSpanError: should call handleWarn when no custom spanError handler provided", + test: () => { + // Arrange + let warnCalled = false; + let warnMessage = ""; + const handlers: IOTelErrorHandlers = { + warn: (message: string) => { + warnCalled = true; + warnMessage = message; + } + }; + const testMessage = "Span error"; + const testSpanName = "testSpan"; + + // Act + handleSpanError(handlers, testMessage, testSpanName); + + // Assert + Assert.ok(warnCalled, "Warn should be called"); + Assert.ok(warnMessage.includes(testMessage), "Warn message should contain original message"); + Assert.ok(warnMessage.includes(testSpanName), "Warn message should contain span name"); + } + }); + + this.testCase({ + name: "handleDebug: should call custom debug handler when provided", + test: () => { + // Arrange + let debugCalled = false; + let debugMessage = ""; + const handlers: IOTelErrorHandlers = { + debug: (message: string) => { + debugCalled = true; + debugMessage = message; + } + }; + const testMessage = "Debug message"; + + // Act + handleDebug(handlers, testMessage); + + // Assert + Assert.ok(debugCalled, "Debug should be called"); + Assert.ok(debugMessage === testMessage, "Debug message should match"); + } + }); + + this.testCase({ + name: "handleDebug: should use console.log when no custom debug handler provided", + test: () => { + // Arrange + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Debug via console"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console.log + const originalConsole = console; + const globalObj = (typeof window !== "undefined") ? window : (global || {}); + (globalObj as any).console = { + log: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleDebug(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.log should be called"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "handleWarn: should call custom warn handler when provided", + test: () => { + // Arrange + let warnCalled = false; + let warnMessage = ""; + const handlers: IOTelErrorHandlers = { + warn: (message: string) => { + warnCalled = true; + warnMessage = message; + } + }; + const testMessage = "Warning message"; + + // Act + handleWarn(handlers, testMessage); + + // Assert + Assert.ok(warnCalled, "Warn should be called"); + Assert.ok(warnMessage === testMessage, "Warn message should match"); + } + }); + + this.testCase({ + name: "handleWarn: should use console.warn when no custom warn handler provided", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Warning via console"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console.warn + const originalConsole = console; + (globalObj as any).console = { + warn: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleWarn(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.warn should be called"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "handleWarn: should fallback to console.log when console.warn not available", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Warning fallback to log"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console without warn + const originalConsole = console; + (globalObj as any).console = { + log: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleWarn(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.log should be called as fallback"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "handleError: should call custom error handler when provided", + test: () => { + // Arrange + let errorCalled = false; + let errorMessage = ""; + const handlers: IOTelErrorHandlers = { + error: (message: string) => { + errorCalled = true; + errorMessage = message; + } + }; + const testMessage = "Error message"; + + // Act + handleError(handlers, testMessage); + + // Assert + Assert.ok(errorCalled, "Error should be called"); + Assert.ok(errorMessage === testMessage, "Error message should match"); + } + }); + + this.testCase({ + name: "handleError: should fallback to warn handler when no custom error handler provided", + test: () => { + // Arrange + let warnCalled = false; + let warnMessage = ""; + const handlers: IOTelErrorHandlers = { + warn: (message: string) => { + warnCalled = true; + warnMessage = message; + } + }; + const testMessage = "Error fallback to warn"; + + // Act + handleError(handlers, testMessage); + + // Assert + Assert.ok(warnCalled, "Warn should be called as fallback"); + Assert.ok(warnMessage === testMessage, "Warn message should match"); + } + }); + + this.testCase({ + name: "handleError: should use console.error when no custom handlers provided", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Error via console"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console.error + const originalConsole = console; + (globalObj as any).console = { + error: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleError(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.error should be called"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "handleError: should fallback through console methods when preferred not available", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Error fallback chain"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console with only log available + const originalConsole = console; + (globalObj as any).console = { + log: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleError(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.log should be called as final fallback"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "handleNotImplemented: should call custom notImplemented handler when provided", + test: () => { + // Arrange + let notImplementedCalled = false; + let notImplementedMessage = ""; + const handlers: IOTelErrorHandlers = { + notImplemented: (message: string) => { + notImplementedCalled = true; + notImplementedMessage = message; + } + }; + const testMessage = "Not implemented feature"; + + // Act + handleNotImplemented(handlers, testMessage); + + // Assert + Assert.ok(notImplementedCalled, "NotImplemented should be called"); + Assert.ok(notImplementedMessage === testMessage, "NotImplemented message should match"); + } + }); + + this.testCase({ + name: "handleNotImplemented: should use console.error when no custom handler provided", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Not implemented via console"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console.error + const originalConsole = console; + (globalObj as any).console = { + error: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleNotImplemented(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.error should be called"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "handleNotImplemented: should fallback to console.log when console.error not available", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Not implemented fallback"; + let consoleCalled = false; + let consoleMessage = ""; + + // Mock console with only log available + const originalConsole = console; + (globalObj as any).console = { + log: (message: string) => { + consoleCalled = true; + consoleMessage = message; + } + }; + + try { + // Act + handleNotImplemented(handlers, testMessage); + + // Assert + Assert.ok(consoleCalled, "Console.log should be called as fallback"); + Assert.ok(consoleMessage === testMessage, "Console message should match"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.testCase({ + name: "Error handlers should handle undefined console gracefully", + test: () => { + // Arrange + const globalObj = getGlobal(); + const handlers: IOTelErrorHandlers = {}; + const testMessage = "Test with no console"; + const originalConsole = console; + + try { + // Remove console + (globalObj as any).console = undefined; + + // Act & Assert - should not throw + handleDebug(handlers, testMessage); + handleWarn(handlers, testMessage); + handleError(handlers, testMessage); + handleNotImplemented(handlers, testMessage); + + // If we get here, no exceptions were thrown + Assert.ok(true, "All handlers should complete without throwing when console is undefined"); + } finally { + // Restore console + (globalObj as any).console = originalConsole; + } + } + }); + + this.addGetUrlTests(); + this.addGetHttpUrlTests(); + } + + private addGetHttpUrlTests(): void { + this.testCase({ + name: "getHttpUrl: should return undefined when container is null", + test: () => { + // Act + const result = getHttpUrl(null as any); + + // Assert + Assert.equal(result, undefined, "Should return undefined for null container"); + } + }); + + this.testCase({ + name: "getHttpUrl: should return undefined when container is undefined", + test: () => { + // Act + const result = getHttpUrl(undefined as any); + + // Assert + Assert.equal(result, undefined, "Should return undefined for undefined container"); + } + }); + + this.testCase({ + name: "getHttpUrl: should return value from url.full (stable semantic convention)", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.full", "https://example.com/api/users?id=123"); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, "https://example.com/api/users?id=123", "Should return value from url.full"); + } + }); + + this.testCase({ + name: "getHttpUrl: should return value from http.url (legacy semantic convention)", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.url", "https://legacy.example.com/endpoint"); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, "https://legacy.example.com/endpoint", "Should return value from http.url"); + } + }); + + this.testCase({ + name: "getHttpUrl: should prefer url.full over http.url when both present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.full", "https://stable.example.com/path"); + container.set("http.url", "https://legacy.example.com/path"); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, "https://stable.example.com/path", "Should prefer url.full over http.url"); + } + }); + + this.testCase({ + name: "getHttpUrl: should return undefined when neither attribute is present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.scheme", "https"); + container.set("server.address", "example.com"); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, undefined, "Should return undefined when neither url.full nor http.url is present"); + } + }); + + this.testCase({ + name: "getHttpUrl: should handle empty string values", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.full", ""); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, undefined, "Should return empty string when url.full is empty"); + } + }); + + this.testCase({ + name: "getHttpUrl: should handle numeric values", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.full", 12345); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, 12345, "Should return numeric value as-is"); + } + }); + + this.testCase({ + name: "getHttpUrl: should handle boolean values", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.full", true); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, true, "Should return boolean value as-is"); + } + }); + + this.testCase({ + name: "getHttpUrl: should handle URLs with special characters", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + const urlWithSpecialChars = "https://example.com/api/search?q=hello%20world&filter=%7B%22type%22%3A%22test%22%7D"; + container.set("url.full", urlWithSpecialChars); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, urlWithSpecialChars, "Should handle URLs with encoded special characters"); + } + }); + + this.testCase({ + name: "getHttpUrl: should handle relative URLs", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.url", "/api/users"); + + // Act + const result = getHttpUrl(container); + + // Assert + Assert.equal(result, "/api/users", "Should handle relative URLs"); + } + }); + } + + private addGetUrlTests(): void { + this.testCase({ + name: "getUrl: should return empty string when container is null", + test: () => { + // Act + const result = getUrl(null as any); + + // Assert + Assert.equal(result, "", "Should return empty string for null container"); + } + }); + + this.testCase({ + name: "getUrl: should return empty string when container is undefined", + test: () => { + // Act + const result = getUrl(undefined as any); + + // Assert + Assert.equal(result, "", "Should return empty string for undefined container"); + } + }); + + this.testCase({ + name: "getUrl: should return empty string when no httpMethod is present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("url.full", "https://example.com/path"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "", "Should return empty string when httpMethod is missing"); + } + }); + + this.testCase({ + name: "getUrl: should return url from url.full (stable semantic convention)", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.full", "https://example.com/api/users"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://example.com/api/users", "Should return url from url.full"); + } + }); + + this.testCase({ + name: "getUrl: should return url from http.url (legacy semantic convention)", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.method", "POST"); + container.set("http.url", "https://api.example.com/data"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://api.example.com/data", "Should return url from http.url"); + } + }); + + this.testCase({ + name: "getUrl: should prefer url.full over http.url when both present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.full", "https://stable.example.com/path"); + container.set("http.url", "https://legacy.example.com/path"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://stable.example.com/path", "Should prefer url.full over http.url"); + } + }); + + this.testCase({ + name: "getUrl: should construct url from httpScheme, httpHost, and httpTarget", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "https"); + container.set("server.address", "example.com"); + container.set("url.path", "/api/users"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://example.com/api/users", "Should construct url from scheme, host, and path"); + } + }); + + this.testCase({ + name: "getUrl: should construct url from legacy http attributes", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.method", "POST"); + container.set("http.scheme", "http"); + container.set("http.host", "localhost:8080"); + container.set("http.target", "/api/data?id=123"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "http://localhost:8080/api/data?id=123", "Should construct url from legacy http attributes"); + } + }); + + this.testCase({ + name: "getUrl: should use url.query when url.path is not present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "https"); + container.set("server.address", "example.com"); + container.set("url.query", "?q=search"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://example.com?q=search", "Should use url.query when url.path not present"); + } + }); + + this.testCase({ + name: "getUrl: should construct url with netPeerName and netPeerPort when httpHost not present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.method", "GET"); + container.set("http.scheme", "http"); + container.set("net.peer.name", "api.service.local"); + container.set("net.peer.port", 8080); + container.set("http.target", "/health"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "http://api.service.local:8080/health", "Should construct url with netPeerName and port"); + } + }); + + this.testCase({ + name: "getUrl: should construct url with client.address (stable) for netPeerName", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "https"); + container.set("client.address", "service.example.com"); + container.set("client.port", 443); + container.set("url.path", "/api"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://service.example.com:443/api", "Should use client.address for peer name"); + } + }); + + this.testCase({ + name: "getUrl: should construct url with netPeerIp when netPeerName not present", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.method", "GET"); + container.set("http.scheme", "http"); + container.set("net.peer.ip", "192.168.1.100"); + container.set("net.peer.port", 3000); + container.set("http.target", "/endpoint"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "http://192.168.1.100:3000/endpoint", "Should construct url with IP address and port"); + } + }); + + this.testCase({ + name: "getUrl: should use network.peer.address (stable) for peer IP", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "POST"); + container.set("url.scheme", "https"); + container.set("network.peer.address", "10.0.0.5"); + container.set("server.port", 8443); + container.set("url.path", "/data"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://10.0.0.5:8443/data", "Should use network.peer.address for IP"); + } + }); + + this.testCase({ + name: "getUrl: should return empty string when scheme and target present but no host/peer info", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "https"); + container.set("url.path", "/api/users"); + // No host, no peer name, no peer IP + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "", "Should return empty string when no host information available"); + } + }); + + this.testCase({ + name: "getUrl: should return empty string when scheme present but target missing", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "https"); + container.set("server.address", "example.com"); + // No target/path/query + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "", "Should return empty string when target is missing"); + } + }); + + this.testCase({ + name: "getUrl: should handle IPv6 addresses", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.method", "GET"); + container.set("http.scheme", "http"); + container.set("net.peer.ip", "::1"); + container.set("net.peer.port", 8080); + container.set("http.target", "/api"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "http://::1:8080/api", "Should handle IPv6 addresses"); + } + }); + + this.testCase({ + name: "getUrl: should prefer server.port over other port attributes", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "https"); + container.set("client.address", "example.com"); + container.set("server.port", 9000); + container.set("client.port", 8000); + container.set("net.peer.port", 7000); + container.set("url.path", "/api"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://example.com:8000/api", "Should prefer server.port"); + } + }); + + this.testCase({ + name: "getUrl: should handle Unix socket paths in network.peer.address", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.request.method", "GET"); + container.set("url.scheme", "http"); + container.set("network.peer.address", "/tmp/my.sock"); + container.set("server.port", 80); + container.set("url.path", "/status"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "http:///tmp/my.sock:80/status", "Should handle Unix socket paths"); + } + }); + + this.testCase({ + name: "getUrl: should construct url with path containing query parameters", + test: () => { + // Arrange + const otelCfg: IOTelConfig = {}; + const container = createAttributeContainer(otelCfg, "test"); + container.set("http.method", "GET"); + container.set("http.scheme", "https"); + container.set("http.host", "api.example.com"); + container.set("http.target", "/search?q=test&page=1"); + + // Act + const result = getUrl(container); + + // Assert + Assert.equal(result, "https://api.example.com/search?q=test&page=1", "Should handle target with query parameters"); + } + }); + } + + private _resetConsoleMocks() { + // Helper to reset any console mocks - implementation depends on your mocking strategy + // This is a placeholder that might need adjustment based on your test framework + } +} diff --git a/shared/otel-core/Tests/Unit/src/trace/W3CTraceStateModes.tests.ts b/shared/otel-core/Tests/Unit/src/trace/W3CTraceStateModes.tests.ts index 3dc51c316..de4b0d8f8 100644 --- a/shared/otel-core/Tests/Unit/src/trace/W3CTraceStateModes.tests.ts +++ b/shared/otel-core/Tests/Unit/src/trace/W3CTraceStateModes.tests.ts @@ -1,5 +1,5 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { eDistributedTracingModes } from "../../../../../src/enums/ai/Enums"; +import { eDistributedTracingModes } from "../../../../src/enums/ai/Enums"; /** * Helper function to check if a mode should include tracestate header diff --git a/shared/otel-core/Tests/Unit/src/trace/W3cTraceParentTests.ts b/shared/otel-core/Tests/Unit/src/trace/W3cTraceParentTests.ts index 6cf182b90..2cdf6caf7 100644 --- a/shared/otel-core/Tests/Unit/src/trace/W3cTraceParentTests.ts +++ b/shared/otel-core/Tests/Unit/src/trace/W3cTraceParentTests.ts @@ -1,8 +1,8 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; import { utcNow } from "@nevware21/ts-utils"; -import { formatTraceParent, isSampledFlag, isValidSpanId, isValidTraceId, isValidTraceParent, parseTraceParent } from "../../../../../src/utils/TraceParent"; -import { ITraceParent } from "../../../../../src/interfaces/ai/ITraceParent"; -import { newGuid, generateW3CId } from "../../../../../src/utils/CoreUtils"; +import { formatTraceParent, isSampledFlag, isValidSpanId, isValidTraceId, isValidTraceParent, parseTraceParent } from "../../../../src/utils/TraceParent"; +import { ITraceParent } from "../../../../src/interfaces/ai/ITraceParent"; +import { newGuid, generateW3CId } from "../../../../src/utils/CoreUtils"; export class W3cTraceParentTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/trace/W3cTraceState.Tests.ts b/shared/otel-core/Tests/Unit/src/trace/W3cTraceState.Tests.ts index 885c8ab7d..711ef1880 100644 --- a/shared/otel-core/Tests/Unit/src/trace/W3cTraceState.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/trace/W3cTraceState.Tests.ts @@ -1,6 +1,6 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; import { asString, strRepeat } from "@nevware21/ts-utils"; -import { createW3cTraceState, isW3cTraceState, snapshotW3cTraceState } from "../../../../../src/telemetry/W3cTraceState"; +import { createW3cTraceState, isW3cTraceState, snapshotW3cTraceState } from "../../../../src/telemetry/W3cTraceState"; export class W3cTraceStateTests extends AITestClass { diff --git a/shared/otel-core/Tests/Unit/src/trace/span.Tests.ts b/shared/otel-core/Tests/Unit/src/trace/span.Tests.ts index 4d6d442ab..2f7f49b31 100644 --- a/shared/otel-core/Tests/Unit/src/trace/span.Tests.ts +++ b/shared/otel-core/Tests/Unit/src/trace/span.Tests.ts @@ -1,8 +1,7 @@ import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -import { dumpObj, perfNow, isFunction, isString, isNumber, isBoolean, isObject, isArray } from "@nevware21/ts-utils"; +import { perfNow, isFunction, isString, isNumber, isBoolean, isObject, isArray, ICachedValue, getDeferred, strSubstr, objDefine, isNullOrUndefined, mathMin } from "@nevware21/ts-utils"; import { IOTelApi } from "../../../../src/interfaces/otel/IOTelApi"; import { IOTelSpanCtx } from "../../../../src/interfaces/otel/trace/IOTelSpanCtx"; -import { IOTelSpanContext } from "../../../../src/interfaces/otel/trace/IOTelSpanContext"; import { IReadableSpan } from "../../../../src/interfaces/otel/trace/IReadableSpan"; import { IOTelConfig } from "../../../../src/interfaces/otel/config/IOTelConfig"; import { eOTelSpanKind } from "../../../../src/enums/otel/OTelSpanKind"; @@ -10,25 +9,38 @@ import { eOTelSpanStatusCode } from "../../../../src/enums/otel/OTelSpanStatus"; import { IOTelErrorHandlers } from "../../../../src/interfaces/otel/config/IOTelErrorHandlers"; import { IOTelAttributes } from "../../../../src/interfaces/otel/IOTelAttributes"; import { createSpan } from "../../../../src/otel/api/trace/span"; +import { IDistributedTraceContext } from "../../../../src/interfaces/ai/IDistributedTraceContext"; +import { createDistributedTraceContext } from "../../../../src/core/TelemetryHelpers"; +import { ITraceCfg } from "../../../../src/interfaces/otel/config/IOTelTraceCfg"; +import { isTracingSuppressed, suppressTracing, unsuppressTracing, useSpan, withSpan } from "../../../../src/otel/api/trace/utils"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { ISpanScope, ITraceHost, ITraceProvider } from "../../../../src/interfaces/ai/ITraceProvider"; +import { IOTelSpanOptions } from "../../../../src/interfaces/otel/trace/IOTelSpanOptions"; +import { generateW3CId } from "../../../../src/utils/CoreUtils"; +import { IConfiguration } from "../../../../src/interfaces/ai/IConfiguration"; +import { createOTelApi } from "../../../../src/otel/api/OTelApi"; +import { IOTelSpan } from "../../../../src"; export class SpanTests extends AITestClass { private _mockApi: IOTelApi; - private _mockSpanContext: IOTelSpanContext; + private _mockSpanContext: IDistributedTraceContext; private _onEndCalls: IReadableSpan[]; + // TODO: Change this to an OTelSdk instance when it's updated to effectively be a "core" instance that can be used in tests + private _core!: AppInsightsCore; public testInitialize() { super.testInitialize(); this._onEndCalls = []; // Create mock span context - this._mockSpanContext = { + this._mockSpanContext = createDistributedTraceContext({ traceId: "12345678901234567890123456789012", spanId: "1234567890123456", traceFlags: 1, isRemote: false - }; + }); // Create mock API this._mockApi = { @@ -41,6 +53,105 @@ export class SpanTests extends AITestClass { public testCleanup() { super.testCleanup(); this._onEndCalls = []; + + // Clean up AppInsightsCore instance if initialized + if (this._core && this._core.isInitialized()) { + this._core.unload(false); + } + this._core = undefined as any; + } + + /** + * Helper function to create a simple trace provider with onEnd callback + */ + private _createTestTraceProvider(host: ITraceHost, onEnd?: (span: IReadableSpan) => void): ICachedValue { + const actualOnEnd = onEnd || ((span) => this._onEndCalls.push(span)); + + return getDeferred(() => { + const provider: ITraceProvider = { + api: this._mockApi, + createSpan: (name: string, options?: IOTelSpanOptions, parent?: IDistributedTraceContext): IReadableSpan => { + // Create a new distributed trace context for this span + let newCtx: IDistributedTraceContext; + let parentCtx: IDistributedTraceContext | undefined; + + if (options && options.root) { + newCtx = createDistributedTraceContext(); + } else { + newCtx = createDistributedTraceContext(parent || host.getTraceCtx()); + if (newCtx.parentCtx) { + parentCtx = newCtx.parentCtx; + } + } + + // Always generate a new spanId + newCtx.spanId = strSubstr(generateW3CId(), 0, 16); + + // Get configuration from the core if available + let isRecording = options?.recording !== false; + if (this._core && this._core.config && this._core.config.traceCfg && this._core.config.traceCfg.suppressTracing) { + isRecording = false; + } + + // Create the span context + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: newCtx, + attributes: options?.attributes, + startTime: options?.startTime, + isRecording: isRecording, + onEnd: actualOnEnd + }; + + if (parentCtx) { + objDefine(spanCtx, "parentSpanContext", { + v: parentCtx, + w: false + }); + } + + return createSpan(spanCtx, name, options?.kind || eOTelSpanKind.INTERNAL); + }, + getProviderId: (): string => "test-provider", + isAvailable: (): boolean => true + }; + + return provider; + }); + } + + /** + * Helper function to set up AppInsightsCore with trace provider + */ + private _setupCore(config?: Partial): AppInsightsCore { + this._core = new AppInsightsCore(); + + // Create a simple test channel + const testChannel = { + identifier: "TestChannel", + priority: 1001, + initialize: () => {}, + processTelemetry: () => {}, + teardown: () => {}, + isInitialized: () => true + }; + + const coreConfig: IConfiguration = { + instrumentationKey: "test-ikey-12345", + traceCfg: { + serviceName: "test-service" + }, + ...config + }; + + // Initialize the core with the test channel + this._core.initialize(coreConfig, [testChannel]); + + // Set up the trace provider + const traceProvider = this._createTestTraceProvider(this._core); + this._core.setTraceProvider(traceProvider); + + return this._core; } public registerTests() { @@ -410,5 +521,1795 @@ export class SpanTests extends AITestClass { Assert.equal(span.addLinks([]), span, "addLinks should return span instance"); } }); + + this.testCase({ + name: "suppressTracing: span with suppressTracing config should not be recording", + test: () => { + // Arrange - create mock API with suppressTracing enabled + const mockApiWithSuppression: IOTelApi = { + cfg: { + traceCfg: { + suppressTracing: true + } as ITraceCfg, + errorHandlers: {} + } as IOTelConfig + } as IOTelApi; + + const spanCtx: IOTelSpanCtx = { + api: mockApiWithSuppression, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + + // Act + const span = createSpan(spanCtx, "suppressed-span", eOTelSpanKind.CLIENT); + + // Assert + Assert.ok(!span.isRecording(), "Span should not be recording when suppressTracing is enabled"); + Assert.ok(span, "Span should still be created"); + Assert.equal(span.name, "suppressed-span", "Span name should be set correctly"); + } + }); + + this.testCase({ + name: "suppressTracing: suppressTracing() helper should set config and return context", + test: () => { + // Arrange - create a context with traceCfg (following IConfiguration pattern) + const testContext = { + traceCfg: { + suppressTracing: false + } as ITraceCfg + }; + + // Act + const returnedContext = suppressTracing(testContext); + + // Assert + Assert.equal(returnedContext, testContext, "suppressTracing should return the same context"); + Assert.ok(testContext.traceCfg.suppressTracing, "suppressTracing config should be set to true"); + Assert.ok(isTracingSuppressed(testContext), "isTracingSuppressed should return true"); + } + }); + + this.testCase({ + name: "suppressTracing: unsuppressTracing() helper should clear config and return context", + test: () => { + // Arrange - create a context with suppressTracing enabled (following IConfiguration pattern) + const testContext = { + traceCfg: { + suppressTracing: true + } as ITraceCfg + }; + + // Act + const returnedContext = unsuppressTracing(testContext); + + // Assert + Assert.equal(returnedContext, testContext, "unsuppressTracing should return the same context"); + Assert.ok(!testContext.traceCfg.suppressTracing, "suppressTracing config should be set to false"); + Assert.ok(!isTracingSuppressed(testContext), "isTracingSuppressed should return false"); + } + }); + + this.testCase({ + name: "createSpan: should create span with options", + test: () => { + // Arrange + const spanName = "test-span-with-options"; + const spanKind = eOTelSpanKind.SERVER; + const attributes: IOTelAttributes = { + "service.name": "test-service", + "http.method": "GET", + "http.status_code": 200 + }; + + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + + // Act + const span = createSpan(spanCtx, spanName, spanKind); + + // Set attributes after creation + span.setAttributes(attributes); + + // Assert + Assert.ok(span, "Span should be created"); + Assert.equal(span.name, spanName, "Span name should match"); + Assert.equal(span.kind, spanKind, "Span kind should match"); + Assert.ok(span.isRecording(), "Span should be recording"); + Assert.ok(!span.ended, "Span should not be ended initially"); + } + }); + + this.testCase({ + name: "createSpan: should end span and call onEnd callback", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + + // Act + span.end(); + + // Assert + Assert.ok(span.ended, "Span should be marked as ended"); + Assert.equal(this._onEndCalls.length, 1, "onEnd callback should be called once"); + Assert.equal(this._onEndCalls[0], span, "onEnd should be called with the span"); + } + }); + + this.testCase({ + name: "createSpan: should set and get attributes", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + + // Act + span.setAttribute("http.method", "POST"); + span.setAttribute("http.status_code", 201); + span.setAttributes({ + "service.name": "test-service", + "user.authenticated": true + }); + + // Assert - Note: The exact attribute retrieval method depends on implementation + // This test verifies that setAttribute and setAttributes don't throw errors + Assert.ok(span, "Span should still be valid after setting attributes"); + Assert.ok(span.isRecording(), "Span should still be recording"); + } + }); + + this.testCase({ + name: "createSpan: should set span status", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + + // Act + span.setStatus({ + code: eOTelSpanStatusCode.ERROR, + message: "Something went wrong" + }); + + // Assert + Assert.ok(span, "Span should still be valid after setting status"); + Assert.ok(span.isRecording(), "Span should still be recording"); + } + }); + + this.testCase({ + name: "createSpan: should handle events (not yet implemented)", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + + // Act & Assert - Currently events are not supported, so this test just verifies span remains valid + // span.addEvent("request.started"); // Not implemented yet + // span.addEvent("response.received", { ... }); // Not implemented yet + + Assert.ok(span, "Span should still be valid"); + Assert.ok(span.isRecording(), "Span should still be recording"); + } + }); + + this.testCase({ + name: "createSpan: should record exception", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + const testError = new Error("Test error"); + + // Act + span.recordException(testError); + span.recordException({ + name: "CustomError", + message: "Custom error message", + stack: "Error stack trace" + }); + + // Assert + Assert.ok(span, "Span should still be valid after recording exceptions"); + Assert.ok(span.isRecording(), "Span should still be recording"); + } + }); + + this.testCase({ + name: "createSpan: should update span name", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "original-name", eOTelSpanKind.CLIENT); + + // Act + span.updateName("updated-name"); + + // Assert + Assert.equal(span.name, "updated-name", "Span name should be updated"); + Assert.ok(span.isRecording(), "Span should still be recording"); + } + }); + + this.testCase({ + name: "createSpan: should handle multiple end calls gracefully", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + + // Act + span.end(); + span.end(); // Second call should be ignored + span.end(); // Third call should be ignored + + // Assert + Assert.ok(span.ended, "Span should be marked as ended"); + Assert.equal(this._onEndCalls.length, 1, "onEnd callback should be called only once"); + } + }); + + this.testCase({ + name: "createSpan: should not record operations after span is ended", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + span.end(); + + // Act & Assert - These operations should not throw but should be ignored + span.setAttribute("test.attr", "test-value"); + // span.addEvent("test.event"); // Not implemented yet + span.setStatus({ code: eOTelSpanStatusCode.ERROR }); + span.updateName("new-name"); + span.recordException(new Error("Test error")); + + // The span name should not change after ending + Assert.equal(span.name, "test-span", "Span name should not change after ending"); + Assert.ok(span.ended, "Span should remain ended"); + Assert.ok(!span.isRecording(), "Span should not be recording after ending"); + } + }); + + this.testCase({ + name: "createSpan: should handle invalid attribute values", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + + // Act & Assert - These should not throw errors + span.setAttribute("", "empty-key"); + span.setAttribute("null-value", null as any); + span.setAttribute("undefined-value", undefined as any); + span.setAttribute("object-value", { nested: "object" } as any); + span.setAttribute("array-value", [1, 2, 3] as any); + + Assert.ok(span, "Span should remain valid after setting invalid attributes"); + Assert.ok(span.isRecording(), "Span should still be recording"); + } + }); + + this.testCase({ + name: "createSpan: should work with different span kinds", + test: () => { + // Test each span kind + const spanKinds = [ + eOTelSpanKind.INTERNAL, + eOTelSpanKind.SERVER, + eOTelSpanKind.CLIENT, + eOTelSpanKind.PRODUCER, + eOTelSpanKind.CONSUMER + ]; + + spanKinds.forEach((kind, index) => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + + // Act + const span = createSpan(spanCtx, `test-span-${index}`, kind); + + // Assert + Assert.ok(span, `Span should be created for kind ${kind}`); + Assert.equal(span.kind, kind, `Span kind should match ${kind}`); + Assert.ok(span.isRecording(), `Span should be recording for kind ${kind}`); + }); + } + }); + + this.testCase({ + name: "createSpan: should handle span context correctly", + test: () => { + // Arrange + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + + // Act + const span = createSpan(spanCtx, "test-span", eOTelSpanKind.CLIENT); + const spanContext = span.spanContext(); + + // Assert + Assert.ok(spanContext, "Span context should be available"); + Assert.equal(spanContext.traceId, this._mockSpanContext.traceId, "Trace ID should match"); + Assert.equal(spanContext.spanId, this._mockSpanContext.spanId, "Span ID should match"); + Assert.equal(spanContext.traceFlags, this._mockSpanContext.traceFlags, "Trace flags should match"); + } + }); + + // === AppInsightsCore Integration Tests === + + this.testCase({ + name: "AppInsightsCore Integration: should create span using core.startSpan with trace provider", + test: () => { + // Arrange + const core = this._setupCore(); + + // Act + const span = core.startSpan("integration-test-span", { + kind: eOTelSpanKind.SERVER, + attributes: { + "service.name": "test-service", + "operation.type": "web-request" + } + }); + + // Assert + Assert.ok(span, "Span should be created via core.startSpan"); + const readableSpan = span as IReadableSpan; + Assert.equal(readableSpan.name, "integration-test-span", "Span name should match"); + Assert.equal(readableSpan.kind, eOTelSpanKind.SERVER, "Span kind should match"); + Assert.ok(span?.isRecording(), "Span should be recording"); + Assert.ok(!readableSpan.ended, "Span should not be ended initially"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should call onEnd callback when span ends", + test: () => { + // Arrange + const core = this._setupCore(); + const span = core.startSpan("callback-test-span"); + Assert.equal(this._onEndCalls.length, 0, "No onEnd calls initially"); + + // Act + Assert.ok(span, "Span should be created"); + span?.end(); + + // Assert + const readableSpan = span as IReadableSpan; + Assert.ok(readableSpan.ended, "Span should be ended"); + Assert.equal(this._onEndCalls.length, 1, "onEnd callback should be called once"); + Assert.equal(this._onEndCalls[0].name, "callback-test-span", "onEnd should receive the correct span"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should inherit trace context from core", + test: () => { + // Arrange + const core = this._setupCore(); + + // Set a specific trace context on the core + const parentTraceContext = createDistributedTraceContext({ + traceId: "parent-trace-12345678901234567890123456789012", + spanId: "parent-span-1234567890123456", + traceFlags: 1 + }); + core.setTraceCtx(parentTraceContext); + + // Act + const span = core.startSpan("child-span"); + + // Assert + Assert.ok(span, "Child span should be created"); + const spanContext = span?.spanContext(); + Assert.equal(spanContext?.traceId, parentTraceContext.traceId, "Child span should inherit parent trace ID"); + Assert.notEqual(spanContext?.spanId, parentTraceContext.spanId, "Child span should have different span ID"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should handle configuration with suppressTracing", + test: () => { + // Arrange + const core = this._setupCore({ + traceCfg: { + suppressTracing: true, + serviceName: "suppressed-service" + } + }); + + // Act + const span = core.startSpan("suppressed-span"); + + // Assert + Assert.ok(span, "Span should still be created when suppressTracing is enabled"); + const readableSpan = span as IReadableSpan; + Assert.ok(!span?.isRecording(), "Span should not be recording when suppressTracing is enabled"); + Assert.equal(readableSpan.name, "suppressed-span", "Span name should be set correctly"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should support active span management", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + + // Act + const span = core.startSpan("active-span-test"); + Assert.ok(span, "Span should be created"); + + // Debug: Check if trace provider exists + const traceProvider = core.getTraceProvider(); + Assert.ok(traceProvider, "Trace provider should exist"); + Assert.ok(traceProvider?.isAvailable(), "Trace provider should be available"); + + // Debug: Check trace provider before setActiveSpan + const providerActiveSpanBefore = core.getActiveSpan(); + Assert.equal(providerActiveSpanBefore, initialActiveSpan, "Trace provider should return the initially active span before setActiveSpan"); + + // Manually set as active span (this would normally be done by startActiveSpan) + const scope = core.setActiveSpan(span); + + // Assert scope object + Assert.ok(scope, "Scope should be returned"); + Assert.equal(scope.span, span, "Scope.span should equal the passed span"); + + // Debug: Check trace provider directly after setActiveSpan + const providerActiveSpanAfter = core.getActiveSpan(); + Assert.ok(providerActiveSpanAfter, "Trace provider should have active span after setActiveSpan"); + Assert.equal(providerActiveSpanAfter, span, "Trace provider active span should be the same instance"); + + // Assert + const activeSpan = core.getActiveSpan(); + Assert.ok(activeSpan, "Active span should be available"); + if (activeSpan) { + const readableActiveSpan = activeSpan as IReadableSpan; + Assert.equal(readableActiveSpan.name, "active-span-test", "Active span should be the correct span"); + Assert.equal(activeSpan, span, "Active span should be the same instance"); + } + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should create child spans with proper parent-child relationship", + test: () => { + // Arrange + const core = this._setupCore(); + + // Act + const parentSpan = core.startSpan("parent-operation", { + kind: eOTelSpanKind.SERVER, + attributes: { "operation.name": "process-request" } + }); + Assert.ok(parentSpan, "Parent span should be created"); + + // Set parent as active + const scope = core.setActiveSpan(parentSpan!); + const activeSpan = core.getActiveSpan(); + + // Assert scope and activeSpan + Assert.ok(scope, "Scope should be returned"); + Assert.equal(scope.span, parentSpan, "Scope.span should equal the parent span"); + Assert.equal(activeSpan, parentSpan, "GetGetGetGetGetGetGetGetGetActiveSpan() should return the parent span"); + + const childSpan = core.startSpan("child-operation", { + kind: eOTelSpanKind.CLIENT, + attributes: { "operation.name": "database-query" } + }); + Assert.ok(childSpan, "Child span should be created"); + + // Assert + const parentContext = parentSpan!.spanContext(); + const childContext = childSpan!.spanContext(); + + Assert.equal(childContext.traceId, parentContext.traceId, "Child should have same trace ID as parent"); + Assert.notEqual(childContext.spanId, parentContext.spanId, "Child should have different span ID from parent"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should handle span attributes and status correctly", + test: () => { + // Arrange + const core = this._setupCore(); + const span = core.startSpan("attribute-test-span", { + attributes: { + "initial.attribute": "initial-value" + } + }); + + // Act + Assert.ok(span, "Span should be created"); + span!.setAttribute("http.method", "POST"); + span!.setAttribute("http.status_code", 201); + span!.setAttributes({ + "service.version": "1.2.3", + "user.authenticated": true + }); + + span!.setStatus({ + code: eOTelSpanStatusCode.OK, + message: "Operation completed successfully" + }); + + // Assert + const readableSpan = span! as IReadableSpan; + Assert.ok(span!.isRecording(), "Span should still be recording"); + Assert.ok(!readableSpan.ended, "Span should not be ended"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should handle span lifecycle properly", + test: () => { + // Arrange + const core = this._setupCore(); + const span = core.startSpan("lifecycle-test-span"); + Assert.equal(this._onEndCalls.length, 0, "No onEnd calls initially"); + + // Act - Perform operations during span lifetime + Assert.ok(span, "Span should be created"); + span!.setAttribute("test.phase", "active"); + span!.recordException(new Error("Test exception for logging")); + span!.updateName("lifecycle-test-span-updated"); + + // End the span + span!.end(); + + // Assert + const readableSpan = span! as IReadableSpan; + Assert.ok(readableSpan.ended, "Span should be ended"); + Assert.equal(readableSpan.name, "lifecycle-test-span-updated", "Span name should be updated"); + Assert.ok(!span!.isRecording(), "Span should not be recording after ending"); + Assert.equal(this._onEndCalls.length, 1, "onEnd callback should be called once"); + + // Verify operations after end are ignored + span!.setAttribute("test.phase", "completed"); + span!.updateName("should-not-change"); + Assert.equal(readableSpan.name, "lifecycle-test-span-updated", "Span name should not change after ending"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should handle multiple spans and proper cleanup", + test: () => { + // Arrange + const core = this._setupCore(); + const spans: IReadableSpan[] = []; + + // Act - Create multiple spans + for (let i = 0; i < 5; i++) { + const span = core.startSpan(`batch-span-${i}`, { + kind: eOTelSpanKind.INTERNAL, + attributes: { + "span.index": i, + "batch.id": "test-batch-123" + } + }); + Assert.ok(span, `Span ${i} should be created`); + spans.push(span!); + } + + // End all spans + spans.forEach(span => span.end()); + + // Assert + Assert.equal(spans.length, 5, "Should have created 5 spans"); + Assert.equal(this._onEndCalls.length, 5, "Should have 5 onEnd callback calls"); + + spans.forEach((span, index) => { + const readableSpan = span as IReadableSpan; + Assert.ok(readableSpan.ended, `Span ${index} should be ended`); + Assert.equal(readableSpan.name, `batch-span-${index}`, `Span ${index} should have correct name`); + }); + + // Verify all spans in onEnd calls + this._onEndCalls.forEach((readableSpan, index) => { + Assert.equal(readableSpan.name, `batch-span-${index}`, `onEnd span ${index} should have correct name`); + }); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should handle trace provider configuration changes", + test: () => { + // Arrange + const core = this._setupCore(); + const originalProvider = core.getTraceProvider(); + Assert.ok(originalProvider, "Original trace provider should be available"); + + // Act - Create new trace provider with different onEnd behavior + let alternativeOnEndCalls: IReadableSpan[] = []; + const newProvider = this._createTestTraceProvider(core, (span) => { + alternativeOnEndCalls.push(span); + }); + + core.setTraceProvider(newProvider); + const updatedProvider = core.getTraceProvider(); + + // Create spans with new provider + const span1 = core.startSpan("provider-change-test-1"); + Assert.ok(span1, "Span should be created with new provider"); + span1!.end(); + + // Assert + Assert.notEqual(updatedProvider, originalProvider, "Trace provider should be updated"); + Assert.equal(this._onEndCalls.length, 0, "Original onEnd callback should not be called"); + Assert.equal(alternativeOnEndCalls.length, 1, "New onEnd callback should be called"); + Assert.equal(alternativeOnEndCalls[0].name, "provider-change-test-1", "New callback should receive correct span"); + } + }); + + this.testCase({ + name: "AppInsightsCore Integration: should handle configuration inheritance from core config", + test: () => { + // Arrange + const core = this._setupCore({ + traceCfg: { + serviceName: "integration-test-service", + generalLimits: { + attributeCountLimit: 64, + attributeValueLengthLimit: 256 + // }, + // spanLimits: { + // attributeCountLimit: 32, + // eventCountLimit: 16, + // linkCountLimit: 8 + } + } + }); + + // Act + const span = core.startSpan("config-inheritance-test", { + attributes: { + "service.name": "integration-test-service", // Should inherit from traceCfg + "test.configured": true + } + }); + + // Assert + Assert.ok(span, "Span should be created with inherited configuration"); + const readableSpan = span! as IReadableSpan; + Assert.ok(span!.isRecording(), "Span should be recording"); + + // The span should exist and be functional - detailed config validation + // would require access to internal span configuration which is not exposed + Assert.equal(readableSpan.name, "config-inheritance-test", "Span name should be correct"); + } + }); + + // === Tracer startActiveSpan Tests === + + this.testCase({ + name: "Tracer.startActiveSpan: should set span as active during callback execution", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + const initialActiveSpan = core.getActiveSpan(); + + let activeSpanInsideCallback: IReadableSpan | null | undefined = null; + let callbackExecuted = false; + + // Act + const result = tracer.startActiveSpan("test-operation", (span) => { + callbackExecuted = true; + + // Check if the span is set as active in the host instance + activeSpanInsideCallback = core.getActiveSpan(); + + span.setAttribute("test.key", "test-value"); + return "callback-result"; + }); + + // Assert + Assert.ok(callbackExecuted, "Callback should have been executed"); + Assert.equal(result, "callback-result", "Should return callback result"); + Assert.ok(activeSpanInsideCallback, "Active span should be set during callback"); + Assert.equal(activeSpanInsideCallback?.name, "test-operation", "Active span should be the created span"); + + // Verify active span is restored after callback + const activeSpanAfterCallback = core.getActiveSpan(); + Assert.equal(activeSpanAfterCallback, initialActiveSpan, "Active span should be restored to initial state"); + } + }); + + this.testCase({ + name: "Tracer.startActiveSpan: should automatically end span after callback completes", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + + let spanInsideCallback: IReadableSpan | null = null; + const initialOnEndCount = this._onEndCalls.length; + + // Act + tracer.startActiveSpan("auto-end-test", (span) => { + spanInsideCallback = span as IReadableSpan; + Assert.ok(!spanInsideCallback.ended, "Span should not be ended during callback"); + span.setAttribute("operation.type", "test"); + }); + + // Assert + Assert.ok(spanInsideCallback, "Span should have been passed to callback"); + Assert.ok(spanInsideCallback.ended, "Span should be ended after callback completes"); + Assert.equal(this._onEndCalls.length, initialOnEndCount + 1, "onEnd should have been called"); + Assert.equal(this._onEndCalls[this._onEndCalls.length - 1].name, "auto-end-test", "onEnd should receive the correct span"); + } + }); + + this.testCase({ + name: "Tracer.startActiveSpan: should handle nested startActiveSpan calls", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + + const executionTrace: string[] = []; + + // Act + const result = tracer.startActiveSpan("outer-operation", (outerSpan) => { + const outerActiveSpan = core.getActiveSpan(); + executionTrace.push(`outer-start: ${outerActiveSpan?.name}`); + + outerSpan.setAttribute("level", "outer"); + + // Nested startActiveSpan + const innerResult = tracer.startActiveSpan("inner-operation", (innerSpan) => { + const innerActiveSpan = core.getActiveSpan(); + executionTrace.push(`inner: ${innerActiveSpan?.name}`); + + innerSpan.setAttribute("level", "inner"); + + // Verify the inner span is now active + Assert.equal(innerActiveSpan?.name, "inner-operation", "Inner span should be active during inner callback"); + + return "inner-result"; + }); + + // After inner callback, outer should be active again + const outerActiveSpanRestored = core.getActiveSpan(); + executionTrace.push(`outer-end: ${outerActiveSpanRestored?.name}`); + + Assert.equal(outerActiveSpanRestored?.name, "outer-operation", "Outer span should be restored as active after inner callback"); + + return `outer(${innerResult})`; + }); + + // Assert + Assert.equal(result, "outer(inner-result)", "Nested startActiveSpan should work correctly"); + Assert.equal(executionTrace.length, 3, "Should have captured 3 execution points"); + Assert.equal(executionTrace[0], "outer-start: outer-operation", "Outer callback should see outer span active"); + Assert.equal(executionTrace[1], "inner: inner-operation", "Inner callback should see inner span active"); + Assert.equal(executionTrace[2], "outer-end: outer-operation", "Outer callback should see outer span restored after inner"); + } + }); + + this.testCase({ + name: "Tracer.startActiveSpan: should handle async callback with active span management", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + const initialActiveSpan = core.getActiveSpan(); + + let activeSpanDuringAsync: IReadableSpan | null | undefined = null; + + // Act + return tracer.startActiveSpan("async-operation", async (span) => { + // Check active span at start + activeSpanDuringAsync = core.getActiveSpan(); + Assert.equal(activeSpanDuringAsync?.name, "async-operation", "Span should be active at start of async callback"); + + span.setAttribute("async.phase", "start"); + + // Simulate async operation + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check active span after async operation + const activeSpanAfterAwait = core.getActiveSpan(); + Assert.equal(activeSpanAfterAwait?.name, "async-operation", "Span should still be active after await"); + + span.setAttribute("async.phase", "end"); + + return "async-result"; + }).then(result => { + // Assert + Assert.equal(result, "async-result", "Should return async callback result"); + Assert.ok(activeSpanDuringAsync, "Active span should have been set during callback"); + + // Verify active span is restored after async callback completes + const activeSpanAfterCallback = core.getActiveSpan(); + Assert.equal(activeSpanAfterCallback, initialActiveSpan, "Active span should be restored after async callback"); + }); + } + }); + + this.testCase({ + name: "Tracer.startActiveSpan: should handle exceptions and still restore active span", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + const initialActiveSpan = core.getActiveSpan(); + + let spanInsideCallback: IReadableSpan | null = null; + let exceptionThrown = false; + + // Act + try { + tracer.startActiveSpan("error-operation", (span) => { + spanInsideCallback = span as IReadableSpan; + const activeSpan = core.getActiveSpan(); + Assert.equal(activeSpan?.name, "error-operation", "Span should be active before exception"); + + throw new Error("Test exception"); + }); + } catch (error: any) { + exceptionThrown = true; + Assert.equal(error.message, "Test exception", "Should propagate exception"); + } + + // Assert + Assert.ok(exceptionThrown, "Exception should have been thrown"); + Assert.ok(spanInsideCallback, "Span should have been created"); + Assert.ok(spanInsideCallback!.ended, "Span should be ended even after exception"); + + // Verify active span is restored even after exception + const activeSpanAfterException = core.getActiveSpan(); + Assert.equal(activeSpanAfterException, initialActiveSpan, "Active span should be restored even after exception"); + } + }); + + this.testCase({ + name: "Tracer.startActiveSpan: should work with options parameter", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + + let activeSpanInsideCallback: IReadableSpan | null | undefined = null; + + // Act + const result = tracer.startActiveSpan("options-test", + { + kind: eOTelSpanKind.SERVER, + attributes: { + "http.method": "POST", + "http.route": "/api/test" + } + }, + (span) => { + activeSpanInsideCallback = core.getActiveSpan(); + + Assert.equal((span as IReadableSpan).kind, eOTelSpanKind.SERVER, "Span kind should match options"); + (span as IReadableSpan).setAttribute("response.status", 200); + + return "options-result"; + } + ); + + // Assert + Assert.equal(result, "options-result", "Should return callback result"); + Assert.ok(activeSpanInsideCallback, "Active span should be set during callback"); + Assert.equal(activeSpanInsideCallback?.name, "options-test", "Active span should be the created span"); + } + }); + + this.testCase({ + name: "Tracer.startActiveSpan: child spans should inherit parent from active span", + test: () => { + // Arrange + const core = this._setupCore(); + const otelApi = createOTelApi({ host: core }); + const tracer = otelApi.trace.getTracer("test-tracer"); + + let parentSpanId: string | undefined; + let childSpanParentId: string | undefined; + + // Act + tracer.startActiveSpan("parent-operation", (parentSpan) => { + parentSpanId = parentSpan.spanContext().spanId; + + // Create a child span using startSpan (not startActiveSpan) + // It should automatically use the active span (parent-operation) as parent + const childSpan = core.startSpan("child-operation"); + + Assert.ok(childSpan, "Child span should be created"); + const childContext = childSpan!.spanContext(); + childSpanParentId = childSpan!.parentSpanId; + + // Verify parent-child relationship + Assert.equal(childContext.traceId, parentSpan.spanContext().traceId, "Child should have same trace ID as parent"); + + childSpan!.end(); + }); + + // Assert + Assert.ok(parentSpanId, "Parent span ID should be captured"); + Assert.equal(childSpanParentId, parentSpanId, "Child span parent ID should match parent span ID"); + } + }); + + // === withSpan Helper Tests === + + this.testCase({ + name: "withSpan: should execute function with span as active span", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const testSpan = core.startSpan("withSpan-test-active"); + Assert.ok(testSpan, "Test span should be created"); + + let capturedActiveSpan: IReadableSpan | null = null; + const testFunction = () => { + capturedActiveSpan = core.getActiveSpan(); + return "test-result"; + }; + + // Act + const result = withSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, "test-result", "withSpan should return function result"); + Assert.ok(capturedActiveSpan, "Function should have access to active span"); + Assert.equal(capturedActiveSpan, testSpan, "Active span should be the provided span"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Active span should be restored after execution"); + } + }); + + this.testCase({ + name: "withSpan: should restore previous active span after execution", + test: () => { + // Arrange + const core = this._setupCore(); + const previousSpan = core.startSpan("previous-span"); + const testSpan = core.startSpan("withSpan-test-restore"); + Assert.ok(previousSpan && testSpan, "Both spans should be created"); + + // Set previous span as active + core.setActiveSpan(previousSpan!); + Assert.equal(core.getActiveSpan(), previousSpan, "Previous span should be active initially"); + + let capturedActiveSpan: IReadableSpan | null = null; + const testFunction = () => { + capturedActiveSpan = core.getActiveSpan(); + return 42; + }; + + // Act + const result = withSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, 42, "withSpan should return function result"); + Assert.equal(capturedActiveSpan, testSpan, "Function should have access to test span"); + Assert.equal(core.getActiveSpan(), previousSpan, "Previous active span should be restored"); + } + }); + + this.testCase({ + name: "withSpan: should handle function with arguments", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("withSpan-test-args"); + Assert.ok(testSpan, "Test span should be created"); + + let capturedArgs: any[] = []; + const testFunction = (...args: any[]) => { + capturedArgs = args; + return args.reduce((sum, val) => sum + val, 0); + }; + + // Act + const result = withSpan(core, testSpan!, testFunction, undefined, 10, 20, 30); + + // Assert + Assert.equal(result, 60, "withSpan should return correct sum"); + Assert.equal(capturedArgs.length, 3, "Function should receive all arguments"); + Assert.equal(capturedArgs[0], 10, "First argument should be correct"); + Assert.equal(capturedArgs[1], 20, "Second argument should be correct"); + Assert.equal(capturedArgs[2], 30, "Third argument should be correct"); + } + }); + + this.testCase({ + name: "withSpan: should handle function with thisArg context", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("withSpan-test-this"); + Assert.ok(testSpan, "Test span should be created"); + + const contextObject = { + value: 100, + getValue: function(multiplier: number) { + return this.value * multiplier; + } + }; + + // Act + const result = withSpan(core, testSpan!, contextObject.getValue, contextObject, 2); + + // Assert + Assert.equal(result, 200, "withSpan should execute with correct this context"); + } + }); + + this.testCase({ + name: "withSpan: should handle exceptions and still restore active span", + test: () => { + // Arrange + const core = this._setupCore(); + const previousSpan = core.startSpan("previous-span-exception"); + const testSpan = core.startSpan("withSpan-test-exception"); + Assert.ok(previousSpan && testSpan, "Both spans should be created"); + + core.setActiveSpan(previousSpan!); + + const testFunction = () => { + throw new Error("Test exception"); + }; + + // Act & Assert + let thrownError: Error | null = null; + try { + withSpan(core, testSpan!, testFunction); + } catch (error) { + thrownError = error as Error; + } + + Assert.ok(thrownError, "Exception should be thrown"); + Assert.equal(thrownError!.message, "Test exception", "Exception message should be preserved"); + Assert.equal(core.getActiveSpan(), previousSpan, "Previous active span should be restored even after exception"); + } + }); + + this.testCase({ + name: "withSpan: should work with functions returning different types", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("withSpan-test-types"); + Assert.ok(testSpan, "Test span should be created"); + + // Test string return + const stringResult = withSpan(core, testSpan!, () => "hello world"); + Assert.equal(stringResult, "hello world", "String return should work"); + + // Test number return + const numberResult = withSpan(core, testSpan!, () => 123.45); + Assert.equal(numberResult, 123.45, "Number return should work"); + + // Test boolean return + const booleanResult = withSpan(core, testSpan!, () => true); + Assert.equal(booleanResult, true, "Boolean return should work"); + + // Test object return + const objectResult = withSpan(core, testSpan!, () => ({ key: "value" })); + Assert.ok(objectResult && objectResult.key === "value", "Object return should work"); + + // Test undefined return + const undefinedResult = withSpan(core, testSpan!, () => undefined); + Assert.equal(undefinedResult, undefined, "Undefined return should work"); + + // Test null return + const nullResult = withSpan(core, testSpan!, () => null); + Assert.equal(nullResult, null, "Null return should work"); + } + }); + + this.testCase({ + name: "withSpan: should work with async-like function patterns", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const testSpan = core.startSpan("withSpan-test-async-pattern"); + Assert.ok(testSpan, "Test span should be created"); + + let spanDuringExecution: IReadableSpan | null = null; + + // Simulate async-like pattern with callback + const asyncFunction = (callback: (result: string) => void) => { + spanDuringExecution = core.getActiveSpan(); + // Simulate some async work completing synchronously for this test + callback("async-result"); + return "function-result"; + }; + + let callbackResult = ""; + const callback = (result: string) => { + callbackResult = result; + }; + + // Act + const result = withSpan(core, testSpan!, asyncFunction, undefined, callback); + + // Assert + Assert.equal(result, "function-result", "withSpan should return main function result"); + Assert.equal(callbackResult, "async-result", "Callback should be executed"); + Assert.equal(spanDuringExecution, testSpan, "Active span should be available during execution"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Active span should be restored after completion"); + } + }); + + this.testCase({ + name: "withSpan: should work when no previous active span exists", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + const testSpan = core.startSpan("withSpan-test-no-previous"); + Assert.ok(testSpan, "Test span should be created"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Just starting a span should not change active span"); + + let capturedActiveSpan: IReadableSpan | null = null; + const testFunction = () => { + capturedActiveSpan = core.getActiveSpan(); + return "success"; + }; + + // Act + const result = withSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, "success", "Function should execute successfully"); + Assert.equal(capturedActiveSpan, testSpan, "Test span should be active during execution"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "No active span should be restored"); + } + }); + + this.testCase({ + name: "withSpan: should work with nested withSpan calls", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const outerSpan = core.startSpan("outer-span"); + const innerSpan = core.startSpan("inner-span"); + Assert.ok(outerSpan && innerSpan, "Both spans should be created"); + + const executionTrace: string[] = []; + + const innerFunction = () => { + const activeSpan = core.getActiveSpan(); + executionTrace.push(`inner: ${activeSpan ? (activeSpan as IReadableSpan).name : 'null'}`); + return "inner-result"; + }; + + const outerFunction = () => { + const activeSpanBefore = core.getActiveSpan(); + executionTrace.push(`outer-start: ${activeSpanBefore ? (activeSpanBefore as IReadableSpan).name : 'null'}`); + + const innerResult = withSpan(core, innerSpan!, innerFunction); + + const activeSpanAfter = core.getActiveSpan(); + executionTrace.push(`outer-end: ${activeSpanAfter ? (activeSpanAfter as IReadableSpan).name : 'null'}`); + + return `outer(${innerResult})`; + }; + + // Act + const result = withSpan(core, outerSpan!, outerFunction); + + // Assert + Assert.equal(result, "outer(inner-result)", "Nested withSpan should work correctly"); + Assert.equal(executionTrace.length, 3, "Should have captured 3 execution points"); + Assert.equal(executionTrace[0], "outer-start: outer-span", "Outer function should see outer span"); + Assert.equal(executionTrace[1], "inner: inner-span", "Inner function should see inner span"); + Assert.equal(executionTrace[2], "outer-end: outer-span", "Outer function should see outer span restored"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "No active span should remain after nested execution"); + } + }); + + this.testCase({ + name: "withSpan: should handle span operations within withSpan context", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const testSpan = core.startSpan("withSpan-test-operations"); + Assert.ok(testSpan, "Test span should be created"); + + const testFunction = () => { + const activeSpan = core.getActiveSpan(); + Assert.ok(activeSpan, "Should have active span in function"); + + // Perform span operations + activeSpan!.setAttribute("operation.name", "test-operation"); + activeSpan!.setAttribute("operation.step", 1); + + // Create child span + const childSpan = core.startSpan("child-operation"); + Assert.ok(childSpan, "Child span should be created"); + + childSpan!.setAttribute("child.attribute", "child-value"); + childSpan!.end(); + + return "operations-completed"; + }; + + // Act + const result = withSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, "operations-completed", "Function should complete successfully"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Active span should be restored"); + + // Verify span operations were applied (span should still be valid) + const readableSpan = testSpan! as IReadableSpan; + Assert.ok(!readableSpan.ended, "Test span should not be ended"); + Assert.ok(testSpan!.isRecording(), "Test span should still be recording"); + } + }); + + this.testCase({ + name: "withSpan: should work with core that has no trace provider", + test: () => { + // Arrange + const core = new AppInsightsCore(); + + // Create a simple test channel + const testChannel = { + identifier: "TestChannel", + priority: 1001, + initialize: () => {}, + processTelemetry: () => {}, + teardown: () => {}, + isInitialized: () => true + }; + + core.initialize({ instrumentationKey: "test-key" }, [testChannel]); // Initialize with channel but no trace provider + + // Create a mock span (this would need to come from somewhere else since no provider) + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const mockSpan = createSpan(spanCtx, "mock-span", eOTelSpanKind.CLIENT); + + let functionExecuted = false; + const testFunction = () => { + functionExecuted = true; + return "no-provider-result"; + }; + + // Act + const result = withSpan(core, mockSpan, testFunction); + + // Assert + Assert.equal(result, "no-provider-result", "Function should execute even without trace provider"); + Assert.ok(functionExecuted, "Function should have been executed"); + + // Cleanup + core.unload(false); + } + }); + + this.testCase({ + name: "withSpan: performance test - should not add significant overhead", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("withSpan-performance-test"); + Assert.ok(testSpan, "Test span should be created"); + + const iterations = 10000; + let computeResult = 0; + + const computeFunction = (base: number, multiplier: number) => { + // Simple computation to measure overhead + return base * multiplier + Math.sqrt(base); + }; + + // Measure time without withSpan + const startWithout = perfNow(); + for (let i = 0; i < iterations; i++) { + computeResult += computeFunction(i, 2); + } + const timeWithout = perfNow() - startWithout; + + // Reset result + computeResult = 0; + + // Measure time with withSpan + const startWith = perfNow(); + for (let i = 0; i < iterations; i++) { + computeResult += withSpan(core, testSpan!, computeFunction, undefined, i, 2); + } + const timeWith = perfNow() - startWith; + + // Assert reasonable performance characteristics + // withSpan should not add more than 10x overhead (very generous threshold) + const overhead = timeWith / (timeWithout || 1); + Assert.ok(overhead < 15, `withSpan overhead should be reasonable: ${overhead.toFixed(2)}x`); + + // Results should be the same + Assert.ok(computeResult > 0, "Computations should have produced results"); + } + }); + // === useSpan Helper Tests === + + this.testCase({ + name: "useSpan: should execute function with span as active span", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const testSpan = core.startSpan("useSpan-test-active"); + Assert.ok(testSpan, "Test span should be created"); + + let capturedActiveSpan: IReadableSpan | null = null; + const testFunction = () => { + capturedActiveSpan = core.getActiveSpan(); + return "test-result"; + }; + + // Act + const result = useSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, "test-result", "useSpan should return function result"); + Assert.ok(capturedActiveSpan, "Function should have access to active span"); + Assert.equal(capturedActiveSpan, testSpan, "Active span should be the provided span"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Active span should be restored after execution"); + } + }); + + this.testCase({ + name: "useSpan: should restore previous active span after execution", + test: () => { + // Arrange + const core = this._setupCore(); + const previousSpan = core.startSpan("previous-span"); + const testSpan = core.startSpan("useSpan-test-restore"); + Assert.ok(previousSpan && testSpan, "Both spans should be created"); + + // Set previous span as active + core.setActiveSpan(previousSpan!); + Assert.equal(core.getActiveSpan(), previousSpan, "Previous span should be active initially"); + + let capturedActiveSpan: IReadableSpan | null = null; + const testFunction = () => { + capturedActiveSpan = core.getActiveSpan(); + return 42; + }; + + // Act + const result = useSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, 42, "useSpan should return function result"); + Assert.equal(capturedActiveSpan, testSpan, "Function should have access to test span"); + Assert.equal(core.getActiveSpan(), previousSpan, "Previous active span should be restored"); + } + }); + + this.testCase({ + name: "useSpan: should handle function with arguments", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("useSpan-test-args"); + Assert.ok(testSpan, "Test span should be created"); + + let capturedArgs: any[] = []; + const testFunction = (scope: ISpanScope, ...args: any[]) => { + capturedArgs = args; + return args.reduce((sum, val) => sum + val, 0); + }; + + // Act + const result = useSpan(core, testSpan!, testFunction, undefined, 10, 20, 30); + + // Assert + Assert.equal(result, 60, "useSpan should return correct sum"); + Assert.equal(capturedArgs.length, 3, "Function should receive all arguments"); + Assert.equal(capturedArgs[0], 10, "First argument should be correct"); + Assert.equal(capturedArgs[1], 20, "Second argument should be correct"); + Assert.equal(capturedArgs[2], 30, "Third argument should be correct"); + } + }); + + this.testCase({ + name: "useSpan: should handle function with thisArg context", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("useSpan-test-this"); + Assert.ok(testSpan, "Test span should be created"); + + const contextObject = { + value: 100, + getValue: function(scope: ISpanScope, multiplier: number) { + return this.value * multiplier; + } + }; + + // Act + const result = useSpan(core, testSpan!, contextObject.getValue, contextObject, 2); + + // Assert + Assert.equal(result, 200, "useSpan should execute with correct this context"); + } + }); + + this.testCase({ + name: "useSpan: should handle exceptions and still restore active span", + test: () => { + // Arrange + const core = this._setupCore(); + const previousSpan = core.startSpan("previous-span-exception"); + const testSpan = core.startSpan("useSpan-test-exception"); + Assert.ok(previousSpan && testSpan, "Both spans should be created"); + + core.setActiveSpan(previousSpan!); + + const testFunction = () => { + throw new Error("Test exception"); + }; + + // Act & Assert + let thrownError: Error | null = null; + try { + useSpan(core, testSpan!, testFunction); + } catch (error) { + thrownError = error as Error; + } + + Assert.ok(thrownError, "Exception should be thrown"); + Assert.equal(thrownError!.message, "Test exception", "Exception message should be preserved"); + Assert.equal(core.getActiveSpan(), previousSpan, "Previous active span should be restored even after exception"); + } + }); + + this.testCase({ + name: "useSpan: should work with functions returning different types", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("useSpan-test-types"); + Assert.ok(testSpan, "Test span should be created"); + + // Test string return + const stringResult = useSpan(core, testSpan!, () => "hello world"); + Assert.equal(stringResult, "hello world", "String return should work"); + + // Test number return + const numberResult = useSpan(core, testSpan!, () => 123.45); + Assert.equal(numberResult, 123.45, "Number return should work"); + + // Test boolean return + const booleanResult = useSpan(core, testSpan!, () => true); + Assert.equal(booleanResult, true, "Boolean return should work"); + + // Test object return + const objectResult = useSpan(core, testSpan!, () => ({ key: "value" })); + Assert.ok(objectResult && objectResult.key === "value", "Object return should work"); + + // Test undefined return + const undefinedResult = useSpan(core, testSpan!, () => undefined); + Assert.equal(undefinedResult, undefined, "Undefined return should work"); + + // Test null return + const nullResult = useSpan(core, testSpan!, () => null); + Assert.equal(nullResult, null, "Null return should work"); + } + }); + + this.testCase({ + name: "useSpan: should work with async-like function patterns", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const testSpan = core.startSpan("useSpan-test-async-pattern"); + Assert.ok(testSpan, "Test span should be created"); + + let spanDuringExecution: IReadableSpan | null = null; + + // Simulate async-like pattern with callback + const asyncFunction = (scope: ISpanScope, callback: (result: string) => void) => { + spanDuringExecution = scope.span; + // Simulate some async work completing synchronously for this test + callback("async-result"); + return "function-result"; + }; + + let callbackResult = ""; + const callback = (result: string) => { + callbackResult = result; + }; + + // Act + const result = useSpan(core, testSpan!, asyncFunction, undefined, callback); + + // Assert + Assert.equal(result, "function-result", "useSpan should return main function result"); + Assert.equal(callbackResult, "async-result", "Callback should be executed"); + Assert.equal(spanDuringExecution, testSpan, "Active span should be available during execution"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Active span should be restored after completion"); + } + }); + + this.testCase({ + name: "useSpan: should work when no previous active span exists", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + const testSpan = core.startSpan("useSpan-test-no-previous"); + Assert.ok(testSpan, "Test span should be created"); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "With a traceprovider, activeSpan should not be null"); + + let capturedActiveSpan: IReadableSpan | null = null; + const testFunction = () => { + capturedActiveSpan = core.getActiveSpan(); + return "success"; + }; + + // Act + const result = useSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, "success", "Function should execute successfully"); + Assert.equal(capturedActiveSpan, testSpan, "Test span should be active during execution"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "No active span should be restored (was null)"); + } + }); + + this.testCase({ + name: "useSpan: should work with nested useSpan calls", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const outerSpan = core.startSpan("outer-span"); + const innerSpan = core.startSpan("inner-span"); + Assert.ok(outerSpan && innerSpan, "Both spans should be created"); + + const executionTrace: string[] = []; + + const innerFunction = (scope: ISpanScope) => { + const activeSpan = scope.span; + executionTrace.push(`inner: ${activeSpan ? (activeSpan as IReadableSpan).name : 'null'}`); + return "inner-result"; + }; + + const outerFunction = (scope: ISpanScope) => { + const activeSpanBefore = scope.span; + executionTrace.push(`outer-start: ${activeSpanBefore ? (activeSpanBefore as IReadableSpan).name : 'null'}`); + + const innerResult = useSpan(core, innerSpan!, innerFunction); + + const activeSpanAfter = core.getActiveSpan(); + executionTrace.push(`outer-end: ${activeSpanAfter ? (activeSpanAfter as IReadableSpan).name : 'null'}`); + + return `outer(${innerResult})`; + }; + + // Act + const result = useSpan(core, outerSpan!, outerFunction); + + // Assert + Assert.equal(result, "outer(inner-result)", "Nested useSpan should work correctly"); + Assert.equal(executionTrace.length, 3, "Should have captured 3 execution points"); + Assert.equal(executionTrace[0], "outer-start: outer-span", "Outer function should see outer span"); + Assert.equal(executionTrace[1], "inner: inner-span", "Inner function should see inner span"); + Assert.equal(executionTrace[2], "outer-end: outer-span", "Outer function should see outer span restored"); + Assert.equal(core.getActiveSpan?.(), initialActiveSpan, "The initial active span should be restored after nested execution"); + } + }); + + this.testCase({ + name: "useSpan: should handle span operations within useSpan context", + test: () => { + // Arrange + const core = this._setupCore(); + let initialActiveSpan = core.getActiveSpan(); + Assert.ok(!isNullOrUndefined(initialActiveSpan), "Initially, activeSpan should not be null with a trace provider"); + const testSpan = core.startSpan("useSpan-test-operations"); + Assert.ok(testSpan, "Test span should be created"); + + const testFunction = (scope: ISpanScope) => { + const activeSpan = scope.span; + Assert.ok(activeSpan, "Should have active span in function"); + + // Perform span operations + activeSpan.setAttribute("operation.name", "test-operation"); + activeSpan.setAttribute("operation.step", 1); + + // Create child span + const childSpan = core.startSpan("child-operation"); + Assert.ok(childSpan, "Child span should be created"); + + childSpan?.setAttribute("child.attribute", "child-value"); + childSpan?.end(); + + return "operations-completed"; + }; + + // Act + const result = useSpan(core, testSpan!, testFunction); + + // Assert + Assert.equal(result, "operations-completed", "Function should complete successfully"); + Assert.equal(core.getActiveSpan(), initialActiveSpan, "Active span should be restored"); + + // Verify span operations were applied (span should still be valid) + const readableSpan = testSpan! as IReadableSpan; + Assert.ok(!readableSpan.ended, "Test span should not be ended"); + Assert.ok(testSpan!.isRecording(), "Test span should still be recording"); + } + }); + + this.testCase({ + name: "useSpan: should work with core that has no trace provider", + test: () => { + // Arrange + const core = new AppInsightsCore(); + + // Create a simple test channel + const testChannel = { + identifier: "TestChannel", + priority: 1001, + initialize: () => {}, + processTelemetry: () => {}, + teardown: () => {}, + isInitialized: () => true + }; + + core.initialize({ instrumentationKey: "test-key" }, [testChannel]); // Initialize with channel but no trace provider + + // Create a mock span (this would need to come from somewhere else since no provider) + const spanCtx: IOTelSpanCtx = { + api: this._mockApi, + spanContext: this._mockSpanContext, + onEnd: (span) => this._onEndCalls.push(span) + }; + const mockSpan = createSpan(spanCtx, "mock-span", eOTelSpanKind.CLIENT); + + let functionExecuted = false; + const testFunction = () => { + functionExecuted = true; + return "no-provider-result"; + }; + + // Act + const result = useSpan(core, mockSpan, testFunction); + + // Assert + Assert.equal(result, "no-provider-result", "Function should execute even without trace provider"); + Assert.ok(functionExecuted, "Function should have been executed"); + + // Cleanup + core.unload(false); + } + }); + + this.testCase({ + name: "useSpan: performance test - should not add significant overhead", + test: () => { + // Arrange + const core = this._setupCore(); + const testSpan = core.startSpan("useSpan-performance-test"); + Assert.ok(testSpan, "Test span should be created"); + + const iterations = 10000; + let computeResult = 0; + + const computeFunction = (_scope: ISpanScope, base: number, multiplier: number) => { + // Simple computation to measure overhead + return base * multiplier + Math.sqrt(base); + }; + + let maxOverhead: number = 100; + + // Perform multiple runs to get a stable measurement + for (let lp = 0; lp < 10; lp++) { + // Measure time without useSpan + const startWithout = perfNow(); + for (let i = 0; i < iterations; i++) { + computeResult += computeFunction(null as any as ISpanScope, i, 2); + } + const timeWithout = perfNow() - startWithout; + + // Reset result + computeResult = 0; + + // Measure time with useSpan + const startWith = perfNow(); + for (let i = 0; i < iterations; i++) { + computeResult += useSpan(core, testSpan!, computeFunction, undefined, i, 2); + } + + // Results should be the same + Assert.ok(computeResult > 0, "Computations should have produced results"); + + const timeWith = perfNow() - startWith; + + const overhead = timeWith / (timeWithout || 1); + + if (lp === 0) { + maxOverhead = overhead; + } + maxOverhead = mathMin(maxOverhead, overhead); + } + + // Assert reasonable performance characteristics + // useSpan should not add more than 10x overhead (very generous threshold) + Assert.ok(maxOverhead < 10, `useSpan overhead should be reasonable: ${maxOverhead.toFixed(2)}x`); + + } + }); } } diff --git a/shared/otel-core/Tests/Unit/src/trace/traceState.Tests.ts b/shared/otel-core/Tests/Unit/src/trace/traceState.Tests.ts new file mode 100644 index 000000000..86c6ad9a6 --- /dev/null +++ b/shared/otel-core/Tests/Unit/src/trace/traceState.Tests.ts @@ -0,0 +1,448 @@ +import { Assert, AITestClass } from "@microsoft/ai-test-framework"; +import { createOTelTraceState } from "../../../../src/otel/api/trace/traceState"; +import { strRepeat } from "@nevware21/ts-utils"; + +export class OTelTraceApiTests extends AITestClass { + + public testInitialize() { + super.testInitialize(); + } + + public testCleanup() { + super.testCleanup(); + } + + public registerTests() { + + this.testCase({ + name: "TraceState: serialize", + test: () => { + const traceState = createOTelTraceState("a=1,b=2,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle empty string", + test: () => { + const traceState = createOTelTraceState(""); + Assert.equal(traceState.serialize(), ""); + } + }); + + this.testCase({ + name: "TraceState: handle null", + test: () => { + const traceState = createOTelTraceState(null as any); + Assert.equal(traceState.serialize(), ""); + } + }); + + this.testCase({ + name: "TraceState: handle undefined", + test: () => { + const traceState = createOTelTraceState(undefined); + Assert.equal(traceState.serialize(), ""); + } + }); + + this.testCase({ + name: "TraceState: handle invalid input", + test: () => { + const traceState = createOTelTraceState({} as any); + Assert.equal(traceState.serialize(), ""); + } + }); + + this.testCase({ + name: "TraceState: new / updated keys are added to the front", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", "4"); + Assert.equal(traceState.serialize(), "d=4,a=1,b=2,c=3"); + + traceState = traceState.set("a", "5"); + Assert.equal(traceState.serialize(), "a=5,d=4,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: must create new instances for each state", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + let traceState2 = createOTelTraceState(traceState.serialize()); + traceState2 = traceState2.set("d", "4"); + + Assert.notEqual(traceState, traceState2); + Assert.notDeepEqual(traceState, traceState2); + + Assert.equal(traceState.serialize(), "a=1,b=2,c=3", "Actual: " + traceState.serialize() + " expected a=1,b=2,c=3"); + Assert.equal(traceState2.serialize(), "d=4,a=1,b=2,c=3", "Actual: " + traceState2.serialize() + " expected d=4,a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: unset", + test: () => { + let traceState = createOTelTraceState("a=4,b=5,c=6"); + traceState = traceState.unset("b"); + Assert.equal(traceState.serialize(), "a=4,c=6"); + + traceState = traceState.unset("a"); + Assert.equal(traceState.serialize(), "c=6"); + } + }); + + this.testCase({ + name: "TraceState: get", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + Assert.equal(traceState.get("a"), "1"); + Assert.equal(traceState.get("b"), "2"); + Assert.equal(traceState.get("c"), "3"); + Assert.equal(traceState.get("d"), undefined); + } + }); + + this.testCase({ + name: "TraceState: serialize with spaces", + test: () => { + const traceState = createOTelTraceState("a=1, b=2, c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with tabs", + test: () => { + const traceState = createOTelTraceState("a=1\t,b=2\t,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with newlines", + test: () => { + const traceState = createOTelTraceState("a=1\n,b=2\n,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with multiple commas", + test: () => { + const traceState = createOTelTraceState("a=1,,b=2,,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with multiple equals", + test: () => { + const traceState = createOTelTraceState("a==1,b==2,c==3"); + Assert.equal(traceState.serialize(), ""); + } + }); + + this.testCase({ + name: "TraceState: serialize with multiple spaces", + test: () => { + const traceState = createOTelTraceState("a=1 , b=2 , c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with multiple tabs", + test: () => { + const traceState = createOTelTraceState("a=1\t\t,b=2\t\t,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with multiple newlines", + test: () => { + const traceState = createOTelTraceState("a=1\n\n,b=2\n\n,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: serialize with multiple commas and spaces", + test: () => { + const traceState = createOTelTraceState("a=1, ,b=2, ,c=3"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle unsetting non-existent keys", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.unset("d"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting empty key", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("", "4"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting empty value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", ""); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting empty string value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", " "); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting empty key and value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("", ""); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting null key", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set(null as any, "4"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting null value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", null as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting null string value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", "null"); + Assert.equal(traceState.serialize(), "d=null,a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting null key and value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set(null as any, null as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting undefined key", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set(undefined as any, "4"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + this.testCase({ + name: "TraceState: handle setting undefined value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", undefined as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting undefined key and value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set(undefined as any, undefined as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting invalid key", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set({} as any, "4"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting invalid value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", {} as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting invalid key and value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set({} as any, {} as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting invalid key and valid value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set({} as any, "4"); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle setting valid key and invalid value", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", {} as any); + Assert.equal(traceState.serialize(), "a=1,b=2,c=3"); + } + }); + + this.testCase({ + name: "TraceState: handle dropping states when the max number of members limit is reached", + test: () => { + let traceState = createOTelTraceState("a=1,b=2,c=3"); + traceState = traceState.set("d", "4"); + traceState = traceState.set("e", "5"); + traceState = traceState.set("f", "6"); + traceState = traceState.set("g", "7"); + traceState = traceState.set("h", "8"); + traceState = traceState.set("i", "9"); + traceState = traceState.set("j", "10"); + traceState = traceState.set("k", "11"); + traceState = traceState.set("l", "12"); + traceState = traceState.set("m", "13"); + traceState = traceState.set("n", "14"); + traceState = traceState.set("o", "15"); + traceState = traceState.set("p", "16"); + traceState = traceState.set("q", "17"); + traceState = traceState.set("r", "18"); + traceState = traceState.set("s", "19"); + traceState = traceState.set("t", "20"); + traceState = traceState.set("u", "21"); + traceState = traceState.set("v", "22"); + traceState = traceState.set("w", "23"); + traceState = traceState.set("x", "24"); + traceState = traceState.set("y", "25"); + traceState = traceState.set("z", "26"); + traceState = traceState.set("aa", "27"); + traceState = traceState.set("ab", "28"); + traceState = traceState.set("ac", "29"); + traceState = traceState.set("ad", "30"); + traceState = traceState.set("ae", "31"); + traceState = traceState.set("af", "32"); + Assert.equal(traceState.serialize(), "af=32,ae=31,ad=30,ac=29,ab=28,aa=27,z=26,y=25,x=24,w=23,v=22,u=21,t=20,s=19,r=18,q=17,p=16,o=15,n=14,m=13,l=12,k=11,j=10,i=9,h=8,g=7,f=6,e=5,d=4,a=1,b=2,c=3"); + traceState = traceState.set("ag", "33"); + Assert.equal(traceState.serialize(), "ag=33,af=32,ae=31,ad=30,ac=29,ab=28,aa=27,z=26,y=25,x=24,w=23,v=22,u=21,t=20,s=19,r=18,q=17,p=16,o=15,n=14,m=13,l=12,k=11,j=10,i=9,h=8,g=7,f=6,e=5,d=4,a=1,b=2"); + } + }); + + this.testCase({ + name: "TraceState: drop states when the items are too long", + test: () => { + const traceState = createOTelTraceState("a=" + strRepeat("b", 512)); + Assert.equal(traceState.get("a"), undefined); + Assert.equal(traceState.serialize(), ""); + } + }); + + this.testCase({ + name: "TraceState: drop items that are invalid", + test: () => { + const traceState = createOTelTraceState("a=1,b,c=3"); + Assert.equal(traceState.get("a"), "1"); + Assert.equal(traceState.get("b"), undefined); + Assert.equal(traceState.get("c"), "3"); + Assert.equal(traceState.serialize(), "a=1,c=3"); + } + }); + + this.testCase({ + name: "TraceState: drop items that are invalid with spaces", + test: () => { + const traceState = createOTelTraceState("a=1, b, c=3"); + Assert.equal(traceState.get("a"), "1"); + Assert.equal(traceState.get("b"), undefined); + Assert.equal(traceState.get("c"), "3"); + Assert.equal(traceState.serialize(), "a=1,c=3"); + } + }); + + this.testCase({ + name: "TraceState: drop items that are invalid with tabs", + test: () => { + const traceState = createOTelTraceState("a=1\t,b\t,c=3"); + Assert.equal(traceState.get("a"), "1"); + Assert.equal(traceState.get("b"), undefined); + Assert.equal(traceState.get("c"), "3"); + Assert.equal(traceState.serialize(), "a=1,c=3"); + } + }); + + this.testCase({ + name: "TraceState: drop items that have a single value with an '=' sign", + test: () => { + const traceState = createOTelTraceState("a=1,b=2=,c=3,d="); + Assert.equal(traceState.get("a"), "1"); + Assert.equal(traceState.get("b"), undefined); + Assert.equal(traceState.get("c"), "3"); + Assert.equal(traceState.get("d"), undefined); + Assert.equal(traceState.serialize(), "a=1,c=3"); + } + }); + + this.testCase({ + name: "TraceState: must handle valid state key ranges", + test: () => { + const traceState = createOTelTraceState("a-b=1,c/d=2,e_f=3,g*h=4"); + Assert.equal(traceState.get("a-b"), "1"); + Assert.equal(traceState.get("c/d"), "2"); + Assert.equal(traceState.get("e_f"), "3"); + Assert.equal(traceState.get("g*h"), "4"); + Assert.equal(traceState.serialize(), "a-b=1,c/d=2,e_f=3,g*h=4"); + } + }); + + this.testCase({ + name: "TraceState: handle values with embedded spaces", + test: () => { + const traceState = createOTelTraceState("a=1 b,c=2 d,e=3 f"); + Assert.equal(traceState.get("a"), "1 b"); + Assert.equal(traceState.get("c"), "2 d"); + Assert.equal(traceState.get("e"), "3 f"); + Assert.equal(traceState.serialize(), "a=1 b,c=2 d,e=3 f"); + } + }); + + } +} \ No newline at end of file diff --git a/shared/otel-core/Tests/Unit/src/trace/traceUtils.Tests.ts b/shared/otel-core/Tests/Unit/src/trace/traceUtils.Tests.ts new file mode 100644 index 000000000..cb5222017 --- /dev/null +++ b/shared/otel-core/Tests/Unit/src/trace/traceUtils.Tests.ts @@ -0,0 +1,1355 @@ +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +import { isSpanContextValid, wrapSpanContext, createNonRecordingSpan, isReadableSpan, useSpan, withSpan } from "../../../../src/otel/api/trace/utils"; +import { createTraceProvider } from "../../../../src/otel/api/trace/traceProvider"; +import { IDistributedTraceContext } from "../../../../src/interfaces/ai/IDistributedTraceContext"; +import { createPromise, createRejectedPromise, doAwait } from "@nevware21/ts-async"; +import { createCachedValue, isNullOrUndefined } from "@nevware21/ts-utils"; +import { IOTelApi } from "../../../../src/interfaces/otel/IOTelApi"; +import { createOTelApi } from "../../../../src/otel/api/OTelApi"; +import { eOTelSpanKind } from "../../../../src/enums/otel/OTelSpanKind"; +import { IOTelSpanCtx } from "../../../../src/interfaces/otel/trace/IOTelSpanCtx"; +import { createSpan } from "../../../../src/otel/api/trace/span"; +import { createW3cTraceState } from "../../../../src/telemetry/W3cTraceState"; +import { createDistributedTraceContext } from "../../../../src/core/TelemetryHelpers"; +import { IAppInsightsCore } from "../../../../src/interfaces/ai/IAppInsightsCore"; +import { AppInsightsCore } from "../../../../src/core/AppInsightsCore"; +import { IChannelControls } from "../../../../src/interfaces/ai/IChannelControls"; + +function _createDistributedContext(traceId: string, spanId: string, traceFlags: number, traceState?: string): IDistributedTraceContext { + const theContext: IDistributedTraceContext = { + traceId: traceId, + spanId: spanId, + traceFlags: traceFlags, + getName: function (): string { + throw new Error("Function not implemented."); + }, + setName: function (pageName: string): void { + throw new Error("Function not implemented."); + }, + getTraceId: function (): string { + throw new Error("Function not implemented."); + }, + setTraceId: function (newValue: string): void { + throw new Error("Function not implemented."); + }, + getSpanId: function (): string { + throw new Error("Function not implemented."); + }, + setSpanId: function (newValue: string): void { + throw new Error("Function not implemented."); + }, + getTraceFlags: function (): number | undefined { + throw new Error("Function not implemented."); + }, + setTraceFlags: function (newValue?: number): void { + throw new Error("Function not implemented."); + }, + pageName: "", + isRemote: false, + traceState: traceState ? createW3cTraceState(traceState) : createW3cTraceState() + }; + + return theContext; +} + +export class TraceUtilsTests extends AITestClass { + private _core: IAppInsightsCore = null as any; + private _otelApi!: IOTelApi; + private _validSpanContext!: IDistributedTraceContext; + + public testInitialize() { + // Create a minimal mock channel to satisfy core initialization requirements + const mockChannel: IChannelControls = { + pause: () => {}, + resume: () => {}, + flush: () => {}, + teardown: () => {}, + processTelemetry: () => {}, + initialize: () => {}, + identifier: "mockChannel", + priority: 1001 + } as any; + + this._core = new AppInsightsCore(); + this._core.initialize({ + instrumentationKey: "00000000-0000-0000-0000-000000000000", + disableInstrumentationKeyValidation: true, + traceCfg: {}, + errorHandlers: { + attribError: (message: string, key: string, value: any) => { + console.error(message); + }, + spanError: (message: string, spanName: string) => { + console.error(message); + }, + debug: (message: string) => { + console.error(message); + }, + warn: (message: string) => { + console.error(message); + }, + error: (message: string) => { + console.error(message); + }, + notImplemented: (message: string) => { + console.error(message); + } + } + }, [mockChannel]); + + this._otelApi = createOTelApi({ host: this._core }); + + // Set up a simple trace provider for the core + this._setupTraceProvider(); + + // Valid span context with proper IDs + this._validSpanContext = _createDistributedContext("12345678901234567890123456789012", "1234567890123456", 1); + } + + private _setupTraceProvider(): void { + // Using an cached value to wrap the provider, so it is created immediately + const provider = createCachedValue(createTraceProvider( + this._core, + "test-provider", + this._otelApi + )); + + this._core.setTraceProvider(provider); + } + + public testCleanup() { + if (this._core) { + this._core.unload(false); + this._core = null as any; + } + this._otelApi = null as any; + this._validSpanContext = null as any; + } + + public registerTests() { + this.addIsSpanContextValidTests(); + this.addWrapSpanContextTests(); + this.addCreateNonRecordingSpanTests(); + this.addIsReadableSpanTests(); + this.addWithSpanTests(); + this.addUseSpanTests(); + } + + private addIsSpanContextValidTests(): void { + this.testCase({ + name: "isSpanContextValid: should return true for valid span context", + test: () => { + // Act + const result = isSpanContextValid(this._validSpanContext); + + // Assert + Assert.ok(result, "Should return true for valid span context"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for invalid trace ID", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("invalid-trace-id", "1234567890123456", 1); + + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for invalid trace ID"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for invalid span ID", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("12345678901234567890123456789012", "bad", 1); + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for invalid span ID"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for null context", + test: () => { + // Act + const result = isSpanContextValid(null as any); + + // Assert + Assert.ok(!result, "Should return false for null context"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for undefined context", + test: () => { + // Act + const result = isSpanContextValid(undefined as any); + + // Assert + Assert.ok(!result, "Should return false for undefined context"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for empty trace ID", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("", "1234567890123456", 1); + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for empty trace ID"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for empty span ID", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("12345678901234567890123456789012", "", 1); + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for empty span ID"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for all-zero trace ID", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("00000000000000000000000000000000", "1234567890123456", 1); + + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for all-zero trace ID"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for all-zero span ID", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("12345678901234567890123456789012", "0000000000000000", 1); + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for all-zero span ID"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for trace ID with wrong length", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("123456789012", "1234567890123456", 1); + + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for trace ID with wrong length"); + } + }); + + this.testCase({ + name: "isSpanContextValid: should return false for span ID with wrong length", + test: () => { + // Arrange + const invalidContext: IDistributedTraceContext = _createDistributedContext("12345678901234567890123456789012", "1234", 1); + // Act + const result = isSpanContextValid(invalidContext); + + // Assert + Assert.ok(!result, "Should return false for span ID with wrong length"); + } + }); + } + + private addWrapSpanContextTests(): void { + this.testCase({ + name: "wrapSpanContext: should create non-recording span from valid context", + test: () => { + // Act + const wrappedSpan = wrapSpanContext(this._otelApi, this._validSpanContext); + + // Assert + Assert.ok(wrappedSpan, "Should create a span"); + Assert.ok(!wrappedSpan.isRecording(), "Wrapped span should not be recording"); + Assert.equal(wrappedSpan.spanContext().traceId, this._validSpanContext.traceId, "Trace ID should match"); + Assert.equal(wrappedSpan.spanContext().spanId, this._validSpanContext.spanId, "Span ID should match"); + } + }); + + this.testCase({ + name: "wrapSpanContext: should include span ID in wrapped span name", + test: () => { + // Act + const wrappedSpan = wrapSpanContext(this._otelApi, this._validSpanContext); + + // Assert + Assert.ok(wrappedSpan.name.includes(this._validSpanContext.spanId), + "Span name should include original span ID"); + Assert.ok(wrappedSpan.name.includes("wrapped"), + "Span name should indicate it's a wrapped span"); + } + }); + + this.testCase({ + name: "wrapSpanContext: should preserve trace flags", + test: () => { + // Arrange + const contextWithFlags: IDistributedTraceContext = _createDistributedContext("12345678901234567890123456789012", "1234567890123456", 1); + + // Act + const wrappedSpan = wrapSpanContext(this._otelApi, contextWithFlags); + + // Assert + Assert.equal(wrappedSpan.spanContext().traceFlags, contextWithFlags.traceFlags, + "Trace flags should be preserved"); + } + }); + + this.testCase({ + name: "wrapSpanContext: should handle context with tracestate", + test: () => { + // Arrange + let contextWithState: IDistributedTraceContext = createDistributedTraceContext(); + contextWithState.traceId = "12345678901234567890123456789012"; + contextWithState.spanId = "1234567890123456"; + contextWithState.traceFlags = 1; + contextWithState.traceState.set("vendor", "value"); + + // Act + const wrappedSpan = wrapSpanContext(this._otelApi, contextWithState); + + // Assert + Assert.ok(wrappedSpan, "Should create span with tracestate"); + Assert.equal(wrappedSpan.spanContext().traceState, contextWithState.traceState, + "Trace state should be preserved if present"); + } + }); + + this.testCase({ + name: "wrapSpanContext: wrapped span should not be ended", + test: () => { + // Act + const wrappedSpan = wrapSpanContext(this._otelApi, this._validSpanContext); + + // Assert + Assert.ok(!wrappedSpan.ended, "Wrapped span should not be ended initially"); + } + }); + + this.testCase({ + name: "wrapSpanContext: wrapped span should be internal kind", + test: () => { + // Act + const wrappedSpan = wrapSpanContext(this._otelApi, this._validSpanContext); + + // Assert + Assert.equal(wrappedSpan.kind, eOTelSpanKind.INTERNAL, + "Wrapped span should have INTERNAL kind"); + } + }); + } + + private addCreateNonRecordingSpanTests(): void { + this.testCase({ + name: "createNonRecordingSpan: should create non-recording span", + test: () => { + // Arrange + const spanName = "test-non-recording"; + + // Act + const span = createNonRecordingSpan(this._otelApi, spanName, this._validSpanContext); + + // Assert + Assert.ok(span, "Should create a span"); + Assert.ok(!span.isRecording(), "Span should not be recording"); + Assert.equal(span.name, spanName, "Span name should match"); + } + }); + + this.testCase({ + name: "createNonRecordingSpan: should use provided span context", + test: () => { + // Arrange + const spanName = "context-test"; + + // Act + const span = createNonRecordingSpan(this._otelApi, spanName, this._validSpanContext); + + // Assert + Assert.equal(span.spanContext().traceId, this._validSpanContext.traceId, + "Trace ID should match provided context"); + Assert.equal(span.spanContext().spanId, this._validSpanContext.spanId, + "Span ID should match provided context"); + Assert.equal(span.spanContext().traceFlags, this._validSpanContext.traceFlags, + "Trace flags should match provided context"); + } + }); + + this.testCase({ + name: "createNonRecordingSpan: should create span with INTERNAL kind", + test: () => { + // Act + const span = createNonRecordingSpan(this._otelApi, "test", this._validSpanContext); + + // Assert + Assert.equal(span.kind, eOTelSpanKind.INTERNAL, + "Non-recording span should have INTERNAL kind"); + } + }); + + this.testCase({ + name: "createRecordingSpan: recording span with onEnd should still trigger callback", + test: () => { + // Arrange + let onEndCalled = false; + + // Create a non-recording span directly with createSpan to test onEnd callback behavior + const spanCtx: IOTelSpanCtx = { + api: this._otelApi, + spanContext: this._validSpanContext, + onEnd: () => { + onEndCalled = true; + } + }; + + // Act - Create span directly with our custom context that includes onEnd callback + const span = createSpan(spanCtx, "test-recording-with-callback", eOTelSpanKind.INTERNAL); + Assert.ok(span.isRecording(), "Span should be recording"); + span.end(); + + // Assert + // onEnd callbacks are called regardless of recording state when the callback is provided + Assert.ok(onEndCalled, "onEnd callback should be called even for non-recording spans when callback is registered"); + } + }); + + this.testCase({ + name: "createNonRecordingSpan: non-recording span with onEnd should still trigger callback", + test: () => { + // Arrange + let onEndCalled = false; + + // Create a non-recording span directly with createSpan to test onEnd callback behavior + const spanCtx: IOTelSpanCtx = { + api: this._otelApi, + spanContext: this._validSpanContext, + isRecording: false, // Non-recording span + onEnd: () => { + onEndCalled = true; + } + }; + + // Act - Create span directly with our custom context that includes onEnd callback + const span = createSpan(spanCtx, "test-non-recording-with-callback", eOTelSpanKind.INTERNAL); + Assert.ok(!span.isRecording(), "Span should not be recording"); + span.end(); + + // Assert + // onEnd callbacks are called regardless of recording state when the callback is provided + // Allows for post-end processing even for non-recording spans, including tracking the number of non-recording spans ended + Assert.ok(onEndCalled, "onEnd callback should be called even for non-recording spans when callback is registered"); + } + }); + + this.testCase({ + name: "createNonRecordingSpan: utility function creates span without onEnd callback", + test: () => { + // Arrange & Act + // createNonRecordingSpan is a utility that doesn't accept an onEnd callback parameter + const span = createNonRecordingSpan(this._otelApi, "test-non-recording", this._validSpanContext); + + // Assert + Assert.ok(!span.isRecording(), "Span should not be recording"); + // This just verifies the utility function works as expected - no onEnd callback to test + + span.end(); + + // Validate that calling after end does not cause issues + Assert.ok(!span.isRecording(), "Span should not be recording"); + } + }); + + this.testCase({ + name: "createNonRecordingSpan: should accept custom span names", + test: () => { + // Arrange + const customNames = [ + "operation-1", + "http-request", + "database-query", + "cache-lookup", + "" + ]; + + // Act & Assert + customNames.forEach(name => { + const span = createNonRecordingSpan(this._otelApi, name, this._validSpanContext); + Assert.equal(span.name, name, `Span name should be '${name}'`); + Assert.ok(!span.isRecording(), "All non-recording spans should not be recording"); + }); + } + }); + + this.testCase({ + name: "createNonRecordingSpan: should preserve tracestate from context", + test: () => { + // Arrange + const contextWithState: IDistributedTraceContext = _createDistributedContext("12345678901234567890123456789012", "1234567890123456", 1, "vendor1=value1,vendor2=value2"); + + // Act + const span = createNonRecordingSpan(this._otelApi, "test", contextWithState); + + // Assert + Assert.equal(span.spanContext().traceState, contextWithState.traceState, + "Trace state should be preserved"); + } + }); + } + + private addIsReadableSpanTests(): void { + this.testCase({ + name: "isReadableSpan: should return true for valid span", + test: () => { + // Arrange - create a real span + const span = createNonRecordingSpan(this._otelApi, "test", this._validSpanContext); + + // Act + const result = isReadableSpan(span); + + // Assert + Assert.ok(result, "Should return true for a valid readable span"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for null", + test: () => { + // Act + const result = isReadableSpan(null); + + // Assert + Assert.ok(!result, "Should return false for null"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for undefined", + test: () => { + // Act + const result = isReadableSpan(undefined); + + // Assert + Assert.ok(!result, "Should return false for undefined"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for empty object", + test: () => { + // Act + const result = isReadableSpan({}); + + // Assert + Assert.ok(!result, "Should return false for empty object"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for object with only some properties", + test: () => { + // Arrange + const partialSpan = { + name: "test", + kind: eOTelSpanKind.CLIENT + }; + + // Act + const result = isReadableSpan(partialSpan); + + // Assert + Assert.ok(!result, "Should return false for object missing required properties"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for object missing spanContext method", + test: () => { + // Arrange + const invalidSpan = { + name: "test", + kind: eOTelSpanKind.CLIENT, + duration: [0, 0], + ended: false, + startTime: [0, 0], + endTime: [0, 0], + attributes: {}, + links: [], + events: [], + status: { code: 0 }, + resource: {}, + instrumentationScope: {}, + droppedAttributesCount: 0, + isRecording: () => false, + setStatus: () => {}, + updateName: () => {}, + setAttribute: () => {}, + setAttributes: () => {}, + end: () => {}, + recordException: () => {} + // Missing spanContext method + }; + + // Act + const result = isReadableSpan(invalidSpan); + + // Assert + Assert.ok(!result, "Should return false when spanContext method is missing"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for object with non-function methods", + test: () => { + // Arrange + const invalidSpan = { + name: "test", + kind: eOTelSpanKind.CLIENT, + spanContext: "not a function", + duration: [0, 0], + ended: false, + startTime: [0, 0], + endTime: [0, 0], + attributes: {}, + links: [], + events: [], + status: { code: 0 }, + resource: {}, + instrumentationScope: {}, + droppedAttributesCount: 0, + isRecording: () => false, + setStatus: () => {}, + updateName: () => {}, + setAttribute: () => {}, + setAttributes: () => {}, + end: () => {}, + recordException: () => {} + }; + + // Act + const result = isReadableSpan(invalidSpan); + + // Assert + Assert.ok(!result, "Should return false when required methods are not functions"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for primitive values", + test: () => { + // Act & Assert + Assert.ok(!isReadableSpan("string"), "Should return false for string"); + Assert.ok(!isReadableSpan(123), "Should return false for number"); + Assert.ok(!isReadableSpan(true), "Should return false for boolean"); + } + }); + + this.testCase({ + name: "isReadableSpan: should return false for array", + test: () => { + // Act + const result = isReadableSpan([]); + + // Assert + Assert.ok(!result, "Should return false for array"); + } + }); + + this.testCase({ + name: "isReadableSpan: should validate all required properties exist", + test: () => { + // Arrange - create a valid span to ensure our check is comprehensive + const span = createNonRecordingSpan(this._otelApi, "validation-test", this._validSpanContext); + + // Act - verify the span has all required properties + const hasName = "name" in span; + const hasKind = "kind" in span; + const hasSpanContext = typeof span.spanContext === "function"; + const hasDuration = "duration" in span; + const hasEnded = "ended" in span; + const hasStartTime = "startTime" in span; + const hasEndTime = "endTime" in span; + const hasAttributes = "attributes" in span; + const hasLinks = "links" in span; + const hasEvents = "events" in span; + const hasStatus = "status" in span; + // const hasResource = "resource" in span; + // const hasInstrumentationScope = "instrumentationScope" in span; + const hasDroppedAttributesCount = "droppedAttributesCount" in span; + const hasIsRecording = typeof span.isRecording === "function"; + const hasSetStatus = typeof span.setStatus === "function"; + const hasUpdateName = typeof span.updateName === "function"; + const hasSetAttribute = typeof span.setAttribute === "function"; + const hasSetAttributes = typeof span.setAttributes === "function"; + const hasEnd = typeof span.end === "function"; + const hasRecordException = typeof span.recordException === "function"; + + // Assert + Assert.ok(hasName, "Should have name property"); + Assert.ok(hasKind, "Should have kind property"); + Assert.ok(hasSpanContext, "Should have spanContext method"); + Assert.ok(hasDuration, "Should have duration property"); + Assert.ok(hasEnded, "Should have ended property"); + Assert.ok(hasStartTime, "Should have startTime property"); + Assert.ok(hasEndTime, "Should have endTime property"); + Assert.ok(hasAttributes, "Should have attributes property"); + Assert.ok(hasLinks, "Should have links property"); + Assert.ok(hasEvents, "Should have events property"); + Assert.ok(hasStatus, "Should have status property"); + // Assert.ok(hasResource, "Should have resource property"); + // Assert.ok(hasInstrumentationScope, "Should have instrumentationScope property"); + Assert.ok(hasDroppedAttributesCount, "Should have droppedAttributesCount property"); + Assert.ok(hasIsRecording, "Should have isRecording method"); + Assert.ok(hasSetStatus, "Should have setStatus method"); + Assert.ok(hasUpdateName, "Should have updateName method"); + Assert.ok(hasSetAttribute, "Should have setAttribute method"); + Assert.ok(hasSetAttributes, "Should have setAttributes method"); + Assert.ok(hasEnd, "Should have end method"); + Assert.ok(hasRecordException, "Should have recordException method"); + + // Final validation + Assert.ok(isReadableSpan(span), "isReadableSpan should return true for complete span"); + } + }); + + this.testCase({ + name: "isReadableSpan: should work with recording spans", + test: () => { + // Arrange - this would be a recording span in real usage + const recordingSpan = createNonRecordingSpan(this._otelApi, "recording-test", this._validSpanContext); + + // Act + const result = isReadableSpan(recordingSpan); + + // Assert + Assert.ok(result, "Should return true for both recording and non-recording spans"); + } + }); + } + + private addWithSpanTests(): void { + this.testCase({ + name: "withSpan: should execute synchronous callback and return value", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-sync", this._validSpanContext); + const expectedValue = "sync-result"; + + // Act + const result = withSpan(this._core, span, function() { + return expectedValue; + }); + + // Assert + Assert.equal(result, expectedValue, "Should return the callback result"); + } + }); + + this.testCase({ + name: "withSpan: should set and restore active span for synchronous callback", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(this._otelApi, "test-active-span", this._validSpanContext); + let activeSpanDuringCallback: any = null; + let coreActiveSpanDuringCallback: any = null; + const originalActiveSpan = this._core.getActiveSpan(); + + Assert.ok(!isNullOrUndefined(originalActiveSpan), "Original active span should not be null or undefined"); + + // Act + withSpan(this._core, span, function() { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + coreActiveSpanDuringCallback = _self._core.getActiveSpan(); + + Assert.equal(coreActiveSpanDuringCallback, activeSpanDuringCallback, "Core active span and OTEL active span should match during callback"); + }); + + const activeSpanAfterCallback = this._otelApi.trace.getActiveSpan(); + const coreActiveSpanAfterCallback = this._core.getActiveSpan(); + + Assert.equal(coreActiveSpanAfterCallback, activeSpanAfterCallback, "Core active span and OTEL active span should match after callback"); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + Assert.equal(activeSpanAfterCallback, originalActiveSpan, "Active span should be restored after callback"); + } + }); + + this.testCase({ + name: "withSpan: should handle promise that resolves", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-promise-resolve", _self._validSpanContext); + const expectedValue = "resolved-value"; + let activeSpanDuringCallback: any = null; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = withSpan(_self._core, span, function() { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + return createPromise((resolve) => { + setTimeout(() => resolve(expectedValue), 10); + }); + }, _self); + + const activeSpanAfterCallback = this._otelApi.trace.getActiveSpan(); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + + // Assert active span immediately after callback + Assert.equal(activeSpanAfterCallback, span, "Active span should still be set after callback until promise resolves"); + + return doAwait(promise, (result) => { + Assert.equal(result, expectedValue, "Should resolve with the expected value"); + const activeSpanAfterResolve = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterResolve, originalActiveSpan, "Active span should be restored after promise resolves"); + }); + } + }); + + this.testCase({ + name: "withSpan: should handle promise that rejects", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-promise-reject", _self._validSpanContext); + const expectedError = new Error("test-error"); + let activeSpanDuringCallback: any = null; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = withSpan(_self._core, span, function() { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + return createRejectedPromise(expectedError); + }, _self); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + + return doAwait(promise, + () => { + Assert.ok(false, "Promise should have rejected"); + }, + (error) => { + Assert.equal(error, expectedError, "Should reject with the expected error"); + const activeSpanAfterReject = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterReject, originalActiveSpan, "Active span should be restored after promise rejects"); + } + ); + } + }); + + this.testCase({ + name: "withSpan: should handle async/await pattern with resolved promise", + useFakeTimers: true, + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-async-await", _self._validSpanContext); + const expectedValue = 42; + let activeSpanDuringCallback: any = null; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = withSpan(_self._core, span, function() { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + return createPromise((resolve) => { + setTimeout(() => resolve(expectedValue), 100); + }); + }, _self); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + + // Advance timers to trigger promise resolution + this.clock.tick(100); + + return doAwait(promise, (result) => { + Assert.equal(result, expectedValue, "Should resolve with the expected value"); + const activeSpanAfterResolve = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterResolve, originalActiveSpan, "Active span should be restored after async operation"); + }); + } + }); + + this.testCase({ + name: "withSpan: should handle async/await pattern with rejected promise", + useFakeTimers: true, + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-async-reject", this._validSpanContext); + const expectedError = new Error("async-error"); + const originalActiveSpan = this._otelApi.trace.getActiveSpan(); + + // Act + const promise = withSpan(this._core, span, function() { + return createPromise((resolve, reject) => { + setTimeout(() => reject(expectedError), 50); + }); + }); + + // Advance timers + this.clock.tick(50); + + return doAwait(promise, + () => { + Assert.ok(false, "Promise should have rejected"); + }, + (error) => { + Assert.equal(error, expectedError, "Should reject with the expected error"); + const activeSpanAfterReject = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterReject, originalActiveSpan, "Active span should be restored after rejection"); + } + ); + } + }); + + this.testCase({ + name: "withSpan: should pass arguments to callback", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-args", this._validSpanContext); + const arg1 = "hello"; + const arg2 = 123; + let receivedArgs: any[] = []; + + // Act + withSpan(this._core, span, function(...args: any[]) { + receivedArgs = args; + }, undefined, arg1, arg2); + + // Assert + Assert.equal(receivedArgs.length, 2, "Should receive both arguments"); + Assert.equal(receivedArgs[0], arg1, "First argument should match"); + Assert.equal(receivedArgs[1], arg2, "Second argument should match"); + } + }); + + this.testCase({ + name: "withSpan: should use provided thisArg", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-this", this._validSpanContext); + const thisArg = { testProperty: "test-value" }; + let capturedThis: any = null; + + // Act + withSpan(this._core, span, function() { + capturedThis = this; + }, thisArg); + + // Assert + Assert.equal(capturedThis, thisArg, "Should use provided thisArg"); + Assert.equal(capturedThis.testProperty, "test-value", "thisArg properties should be accessible"); + } + }); + + this.testCase({ + name: "withSpan: should handle synchronous exception", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-exception", this._validSpanContext); + const expectedError = new Error("sync-exception"); + const originalActiveSpan = this._otelApi.trace.getActiveSpan(); + + // Act & Assert + try { + withSpan(this._core, span, function() { + throw expectedError; + }); + Assert.ok(false, "Should have thrown an exception"); + } catch (error) { + Assert.equal(error, expectedError, "Should throw the expected error"); + const activeSpanAfterException = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterException, originalActiveSpan, "Active span should be restored after exception"); + } + } + }); + } + + private addUseSpanTests(): void { + this.testCase({ + name: "useSpan: should execute synchronous callback and return value", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-sync", this._validSpanContext); + const expectedValue = "sync-result"; + + // Act + const result = useSpan(this._core, span, function(scope) { + return expectedValue; + }); + + // Assert + Assert.equal(result, expectedValue, "Should return the callback result"); + } + }); + + this.testCase({ + name: "useSpan: should provide scope as first parameter", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-scope", this._validSpanContext); + let capturedScope: any = null; + + // Act + useSpan(this._core, span, function(scope) { + capturedScope = scope; + }); + + // Assert + Assert.ok(capturedScope, "Should provide scope"); + Assert.ok(typeof capturedScope.restore === "function", "Scope should have restore method"); + } + }); + + this.testCase({ + name: "useSpan: should set and restore active span for synchronous callback", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-active-span", this._validSpanContext); + let activeSpanDuringCallback: any = null; + const _self = this; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + useSpan(this._core, span, function(scope) { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + }, this); + + const activeSpanAfterCallback = this._otelApi.trace.getActiveSpan(); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + Assert.equal(activeSpanAfterCallback, originalActiveSpan, "Active span should be restored after callback"); + } + }); + + this.testCase({ + name: "useSpan: should handle promise that resolves", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-promise-resolve", this._validSpanContext); + const expectedValue = "resolved-value"; + let activeSpanDuringCallback: any = null; + let scopeFromCallback: any = null; + const _self = this; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = useSpan(this._core, span, function(scope) { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + scopeFromCallback = scope; + return createPromise((resolve) => { + setTimeout(() => resolve(expectedValue), 10); + }); + }, this); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + Assert.ok(scopeFromCallback, "Scope should be provided to callback"); + + return doAwait(promise, (result) => { + Assert.equal(result, expectedValue, "Should resolve with the expected value"); + const activeSpanAfterResolve = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterResolve, originalActiveSpan, "Active span should be restored after promise resolves"); + }); + } + }); + + this.testCase({ + name: "useSpan: should handle promise that rejects", + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-promise-reject", this._validSpanContext); + const expectedError = new Error("test-error"); + let activeSpanDuringCallback: any = null; + const _self = this; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = useSpan(this._core, span, function(scope) { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + return createRejectedPromise(expectedError); + }, this); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + + return doAwait(promise, + () => { + Assert.ok(false, "Promise should have rejected"); + }, + (error) => { + Assert.equal(error, expectedError, "Should reject with the expected error"); + const activeSpanAfterReject = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterReject, originalActiveSpan, "Active span should be restored after promise rejects"); + } + ); + } + }); + + this.testCase({ + name: "useSpan: should handle async/await pattern with resolved promise", + useFakeTimers: true, + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-async-await", this._validSpanContext); + const expectedValue = 42; + let activeSpanDuringCallback: any = null; + const _self = this; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = useSpan(this._core, span, function(scope) { + activeSpanDuringCallback = _self._otelApi.trace.getActiveSpan(); + return createPromise((resolve) => { + setTimeout(() => resolve(expectedValue), 100); + }); + }, this); + + // Assert + Assert.equal(activeSpanDuringCallback, span, "Active span should be set during callback"); + + // Advance timers to trigger promise resolution + this.clock.tick(100); + + return doAwait(promise, (result) => { + Assert.equal(result, expectedValue, "Should resolve with the expected value"); + const activeSpanAfterResolve = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterResolve, originalActiveSpan, "Active span should be restored after async operation"); + }); + } + }); + + this.testCase({ + name: "useSpan: should handle async/await pattern with rejected promise", + useFakeTimers: true, + test: () => { + // Arrange + const span = createNonRecordingSpan(this._otelApi, "test-async-reject", this._validSpanContext); + const expectedError = new Error("async-error"); + const _self = this; + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = useSpan(this._core, span, function(scope) { + return createPromise((resolve, reject) => { + setTimeout(() => reject(expectedError), 50); + }); + }); + + // Advance timers + this.clock.tick(50); + + return doAwait(promise, + () => { + Assert.ok(false, "Promise should have rejected"); + }, + (error) => { + Assert.equal(error, expectedError, "Should reject with the expected error"); + const activeSpanAfterReject = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterReject, originalActiveSpan, "Active span should be restored after rejection"); + } + ); + } + }); + + this.testCase({ + name: "useSpan: should pass additional arguments to callback", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-args", _self._validSpanContext); + const arg1 = "hello"; + const arg2 = 123; + let receivedScope: any = null; + let receivedArgs: any[] = []; + + // Act + useSpan(this._core, span, function(scope, ...args: any[]) { + receivedScope = scope; + receivedArgs = args; + }, undefined, arg1, arg2); + + // Assert + Assert.ok(receivedScope, "Should receive scope as first argument"); + Assert.equal(receivedArgs.length, 2, "Should receive additional arguments"); + Assert.equal(receivedArgs[0], arg1, "First additional argument should match"); + Assert.equal(receivedArgs[1], arg2, "Second additional argument should match"); + } + }); + + this.testCase({ + name: "useSpan: should use provided thisArg", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-this", _self._validSpanContext); + const thisArg = { testProperty: "test-value" }; + let capturedThis: any = null; + + // Act + useSpan(this._core, span, function(scope) { + capturedThis = this; + }, thisArg); + + // Assert + Assert.equal(capturedThis, thisArg, "Should use provided thisArg"); + Assert.equal(capturedThis.testProperty, "test-value", "thisArg properties should be accessible"); + } + }); + + this.testCase({ + name: "useSpan: should use scope as thisArg when not provided", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-default-this", _self._validSpanContext); + let capturedThis: any = null; + let capturedScope: any = null; + + // Act + useSpan(_self._core, span, function(scope) { + capturedThis = this; + capturedScope = scope; + }); + + // Assert + Assert.equal(capturedThis, capturedScope, "Should use scope as thisArg when not provided"); + } + }); + + this.testCase({ + name: "useSpan: should handle synchronous exception", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-exception", _self._validSpanContext); + const expectedError = new Error("sync-exception"); + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act & Assert + try { + useSpan(_self._core, span, function(scope) { + throw expectedError; + }); + Assert.ok(false, "Should have thrown an exception"); + } catch (error) { + Assert.equal(error, expectedError, "Should throw the expected error"); + const activeSpanAfterException = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterException, originalActiveSpan, "Active span should be restored after exception"); + } + } + }); + + this.testCase({ + name: "useSpan: scope.restore should be callable manually", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-manual-restore", _self._validSpanContext); + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + let scopeRestored = false; + + // Act + useSpan(_self._core, span, function(scope) { + Assert.equal(_self._otelApi.trace.getActiveSpan(), span, "Active span should be set"); + // Manually restore (though the framework will restore again) + scope.restore(); + scopeRestored = true; + }, _self); + // Assert + Assert.ok(scopeRestored, "Scope restore should have been called"); + const activeSpanAfterCallback = this._otelApi.trace.getActiveSpan(); + Assert.equal(activeSpanAfterCallback, originalActiveSpan, "Active span should be restored"); + } + }); + + this.testCase({ + name: "useSpan: should handle promise rejection with ts-async doAwait", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-doawait-reject", _self._validSpanContext); + const expectedError = new Error("doAwait-error"); + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = useSpan(_self._core, span, function(scope) { + return createPromise((resolve, reject) => { + setTimeout(() => reject(expectedError), 5); + }); + }); + + // Use doAwait pattern to handle rejection + return doAwait(promise, + (value) => { + Assert.ok(false, "Should not resolve"); + }, + (reason) => { + Assert.equal(reason, expectedError, "Should reject with expected error"); + Assert.equal(this._otelApi.trace.getActiveSpan(), originalActiveSpan, "Active span should be restored after rejection"); + } + ); + } + }); + + this.testCase({ + name: "useSpan: should handle complex promise chain", + test: () => { + // Arrange + const _self = this; + const span = createNonRecordingSpan(_self._otelApi, "test-chain", _self._validSpanContext); + const originalActiveSpan = _self._otelApi.trace.getActiveSpan(); + + // Act + const promise = useSpan(_self._core, span, function(scope) { + return createPromise((resolve) => { + setTimeout(() => resolve(1), 5); + }); + }); + + // Chain multiple operations + return doAwait(promise, (value) => { + Assert.equal(value, 1, "First promise should resolve with 1"); + + return doAwait(createPromise((resolve) => { + setTimeout(() => resolve(value + 1), 5); + }), (value2) => { + Assert.equal(value2, 2, "Second promise should resolve with 2"); + Assert.equal(this._otelApi.trace.getActiveSpan(), originalActiveSpan, "Active span should be restored"); + }); + }); + } + }); + } +} diff --git a/shared/otel-core/package.json b/shared/otel-core/package.json index b5a4cce3d..8877e2dcb 100644 --- a/shared/otel-core/package.json +++ b/shared/otel-core/package.json @@ -13,8 +13,8 @@ "browser performance monitoring", "web analytics" ], - "main": "dist/es5/otel-core-js.js", - "module": "dist-es5/otel-core-js.js", + "main": "dist/es5/index.js", + "module": "dist-es5/index.js", "types": "types/otel-core-js.d.ts", "scripts": { "clean": "rimraf dist dist-es5 dist-es2020 build browser types temp rush-logs", @@ -73,7 +73,7 @@ "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" } } diff --git a/shared/otel-core/rollup.config.js b/shared/otel-core/rollup.config.js index ba1c7d8f2..2cca1cceb 100644 --- a/shared/otel-core/rollup.config.js +++ b/shared/otel-core/rollup.config.js @@ -3,33 +3,34 @@ import { updateDistEsmFiles } from "../../tools/updateDistEsm/updateDistEsm"; const version = require("./package.json").version; const entryPointName = "index"; +const browserOutputName = "otel-core-js"; // MUST NOT CHANGE - CDN compatibility const banner = [ - "/*!", - ` * Application Insights JavaScript SDK - OpenTelemetry Core, ${version}`, - " * Copyright (c) Microsoft and contributors. All rights reserved.", - " */" + "/*!", + ` * Application Insights JavaScript SDK - OpenTelemetry Core, ${version}`, + " * Copyright (c) Microsoft and contributors. All rights reserved.", + " */" ].join("\n"); const replaceValues = { - "// Copyright (c) Microsoft Corporation. All rights reserved.": "", - "// Licensed under the MIT License.": "" + "// Copyright (c) Microsoft Corporation. All rights reserved.": "", + "// Licensed under the MIT License.": "" }; updateDistEsmFiles(replaceValues, banner, true, true, "dist-es5"); -export default createUnVersionedConfig(banner, - { - namespace: "Microsoft.ApplicationInsights", - version: version, - node: { - entryPoint: entryPointName, - outputName: entryPointName +export default createUnVersionedConfig(banner, + { + namespace: "Microsoft.ApplicationInsights", + version: version, + node: { + entryPoint: entryPointName, + outputName: entryPointName + }, + browser: { + entryPoint: entryPointName, + outputName: browserOutputName // Browser keeps original name for CDN + } }, - browser: { - entryPoint: entryPointName, - outputName: entryPointName - }, - }, - [ "otel-core-js" ], - false + ["index", "otel-core-js"], + false ); \ No newline at end of file diff --git a/shared/otel-core/src/config/DynamicSupport.ts b/shared/otel-core/src/config/DynamicSupport.ts index 42c9eb975..5a7cad09d 100644 --- a/shared/otel-core/src/config/DynamicSupport.ts +++ b/shared/otel-core/src/config/DynamicSupport.ts @@ -55,7 +55,8 @@ export function _cfgDeepCopy(source: T): T { /** * @internal * Get the dynamic config handler if the value is already dynamic - * @returns + * @param value - The value to check for dynamic config handler + * @returns The dynamic config handler if present, null otherwise */ export function getDynamicConfigHandler(value: V | IDynamicConfigHandler): IDynamicConfigHandler | null { if (value) { diff --git a/shared/otel-core/src/config/IDynamicWatcher.ts b/shared/otel-core/src/config/IDynamicWatcher.ts deleted file mode 100644 index 1dfee7470..000000000 --- a/shared/otel-core/src/config/IDynamicWatcher.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IConfiguration } from "../interfaces/ai/IConfiguration"; -import { IUnloadHook } from "../interfaces/ai/IUnloadHook"; -import { IConfigDefaults } from "../interfaces/config/IConfigDefaults"; -import { IDynamicPropertyHandler } from "../interfaces/config/IDynamicPropertyHandler"; - -export interface IWatchDetails { - /** - * The current config object - */ - cfg: T; - - /** - * Set the value against the provided config/name with the value, the property - * will be converted to be dynamic (if not already) as long as the provided config - * is already a tracked dynamic object. - * @throws TypeError if the provided config is not a monitored dynamic config - */ - set: (theConfig: C, name: string, value: V) => V; - - /** - * Set default values for the config if not present. - * @param theConfig - The configuration object to set default on (if missing) - * @param defaultValues - The default values to apply to the config - */ - setDf: (theConfig: C, defaultValues: IConfigDefaults) => C; - - /** - * Set this named property of the target as referenced, which will cause any object or array instance - * to be updated in-place rather than being entirely replaced. All other values will continue to be replaced. - * @returns The referenced properties current value - */ - ref: (target: C, name: string) => V; - - /** - * Set this named property of the target as read-only, which will block this single named property from - * ever being changed for the target instance. - * This does NOT freeze or seal the instance, it just stops the direct re-assignment of the named property, - * if the value is a non-primitive (ie. an object or array) it's properties will still be mutable. - * @returns The referenced properties current value - */ - rdOnly: (target: C, name: string) => V; -} - -export type WatcherFunction = (details: IWatchDetails) => void; - -/** - * @internal - */ -export interface _WatcherChangeDetails { - d: _IDynamicDetail; -} - -/** - * @internal - */ -export interface _IDynamicDetail extends IDynamicPropertyHandler { - - /** - * Add the watcher for monitoring changes - */ - trk: (handler: IWatcherHandler) => void; - - /** - * Clear all of the watchers from monitoring changes - */ - clr: (handler: IWatcherHandler) => void; -} - -export interface IWatcherHandler extends IUnloadHook { - fn: WatcherFunction; - rm: () => void; -} diff --git a/shared/otel-core/src/constants/Constants.ts b/shared/otel-core/src/constants/Constants.ts index 33b7cf7ba..f78f707bc 100644 --- a/shared/otel-core/src/constants/Constants.ts +++ b/shared/otel-core/src/constants/Constants.ts @@ -9,6 +9,9 @@ * @ignore */ export const DisabledPropertyName: string = "Microsoft_ApplicationInsights_BypassAjaxInstrumentation"; + +export const ChannelControllerPriority = 500; + export const SampleRate = "sampleRate"; export const ProcessLegacy = "ProcessLegacy"; export const HttpMethod = "http.method"; @@ -17,21 +20,3 @@ export const DEFAULT_BREEZE_PATH = "/v2/track"; export const strNotSpecified = "not_specified"; export const strIkey = "iKey"; -// Channel controller priority constant -export const ChannelControllerPriority = 500; - -// String constants for event names -export const STR_EVENTS_DISCARDED = "eventsDiscarded"; -export const STR_EVENTS_SEND_REQUEST = "eventsSendRequest"; -export const STR_EVENTS_SENT = "eventsSent"; -export const STR_PERF_EVENT = "perfEvent"; -export const STR_OFFLINE_DROP = "offlineDrop"; -export const STR_OFFLINE_SENT = "offlineSent"; -export const STR_OFFLINE_STORE = "offlineStore"; - -// String constants for core properties -export const STR_GET_PERF_MGR = "_getPerfMgr"; -export const STR_CORE = "_core"; -export const STR_DISABLED = "disabled"; -export const STR_PRIORITY = "priority"; -export const STR_PROCESS_TELEMETRY = "processTelemetry"; diff --git a/shared/otel-core/src/constants/CoreInternalConstants.ts b/shared/otel-core/src/constants/CoreInternalConstants.ts deleted file mode 100644 index 49b7e1185..000000000 --- a/shared/otel-core/src/constants/CoreInternalConstants.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// ################################################################################################################################################### -// Note: DON'T Export these const from the package as we are still targeting IE/ES5 this will export a mutable variables that someone could change ### -// ################################################################################################################################################### - -export const UNDEFINED_VALUE: any = undefined; -export const STR_EMPTY = ""; -export const STR_CHANNELS = "channels"; -export const STR_CORE = "core"; -export const STR_CREATE_PERF_MGR = "createPerfMgr"; -export const STR_DISABLED = "disabled"; -export const STR_EXTENSION_CONFIG = "extensionConfig"; -export const STR_EXTENSIONS = "extensions"; -export const STR_PROCESS_TELEMETRY = "processTelemetry"; -export const STR_PRIORITY = "priority"; - -export const STR_EVENTS_SENT = "eventsSent"; -export const STR_EVENTS_DISCARDED = "eventsDiscarded"; -export const STR_EVENTS_SEND_REQUEST = "eventsSendRequest"; -export const STR_PERF_EVENT = "perfEvent"; -export const STR_OFFLINE_STORE = "offlineEventsStored"; -export const STR_OFFLINE_SENT = "offlineBatchSent"; -export const STR_OFFLINE_DROP = "offlineBatchDrop"; - -export const STR_GET_PERF_MGR = "getPerfMgr"; -export const STR_DOMAIN = "domain"; -export const STR_PATH = "path"; - -export const STR_NOT_DYNAMIC_ERROR = "Not dynamic - "; - -export const STR_REDACTED = "REDACTED"; -export const DEFAULT_SENSITIVE_PARAMS = ["sig", "Signature", "AWSAccessKeyId", "X-Goog-Signature"]; diff --git a/shared/otel-core/src/constants/InternalConstants.ts b/shared/otel-core/src/constants/InternalConstants.ts index 49b7e1185..12da0a457 100644 --- a/shared/otel-core/src/constants/InternalConstants.ts +++ b/shared/otel-core/src/constants/InternalConstants.ts @@ -31,4 +31,4 @@ export const STR_PATH = "path"; export const STR_NOT_DYNAMIC_ERROR = "Not dynamic - "; export const STR_REDACTED = "REDACTED"; -export const DEFAULT_SENSITIVE_PARAMS = ["sig", "Signature", "AWSAccessKeyId", "X-Goog-Signature"]; +export const DEFAULT_SENSITIVE_PARAMS = ["sig", "Signature", "AWSAccessKeyId", "X-Goog-Signature"]; \ No newline at end of file diff --git a/shared/otel-core/src/core/AppInsightsCore.ts b/shared/otel-core/src/core/AppInsightsCore.ts index ca1312ee2..0e29ebebf 100644 --- a/shared/otel-core/src/core/AppInsightsCore.ts +++ b/shared/otel-core/src/core/AppInsightsCore.ts @@ -4,8 +4,8 @@ import dynamicProto from "@microsoft/dynamicproto-js"; import { IPromise, createPromise, createSyncAllSettledPromise, doAwaitResponse } from "@nevware21/ts-async"; import { - ILazyValue, ITimerHandler, arrAppend, arrForEach, arrIndexOf, createDeferredCachedValue, createTimeout, deepExtend, hasDocument, - isFunction, isNullOrUndefined, isPlainObject, isPromiseLike, objDeepFreeze, objDefine, objForEachKey, objFreeze, objHasOwn, + ICachedValue, ILazyValue, ITimerHandler, arrAppend, arrForEach, arrIndexOf, createTimeout, deepExtend, getDeferred, hasDocument, + isFunction, isNullOrUndefined, isPlainObject, isPromiseLike, objAssign, objDeepFreeze, objDefine, objForEachKey, objFreeze, objHasOwn, scheduleTimeout, throwError } from "@nevware21/ts-utils"; import { cfgDfMerge } from "../config/ConfigDefaultHelpers"; @@ -39,21 +39,24 @@ import { IPlugin, ITelemetryPlugin } from "../interfaces/ai/ITelemetryPlugin"; import { ITelemetryPluginChain } from "../interfaces/ai/ITelemetryPluginChain"; import { ITelemetryUnloadState } from "../interfaces/ai/ITelemetryUnloadState"; import { ITelemetryUpdateState } from "../interfaces/ai/ITelemetryUpdateState"; +import { ISpanScope, ITraceProvider } from "../interfaces/ai/ITraceProvider"; import { ILegacyUnloadHook, IUnloadHook } from "../interfaces/ai/IUnloadHook"; import { IConfigDefaults } from "../interfaces/config/IConfigDefaults"; import { IDynamicConfigHandler, _IInternalDynamicConfigHandler } from "../interfaces/config/IDynamicConfigHandler"; import { IWatchDetails, WatcherFunction } from "../interfaces/config/IDynamicWatcher"; +import { ITraceCfg } from "../interfaces/otel/config/IOTelTraceCfg"; import { IOTelContextManager } from "../interfaces/otel/context/IOTelContextManager"; import { IOTelSpanContext } from "../interfaces/otel/trace/IOTelSpanContext"; +import { IOTelSpanOptions } from "../interfaces/otel/trace/IOTelSpanOptions"; import { IOTelTracer } from "../interfaces/otel/trace/IOTelTracer"; import { IOTelTracerOptions } from "../interfaces/otel/trace/IOTelTracerOptions"; import { IOTelTracerProvider } from "../interfaces/otel/trace/IOTelTracerProvider"; +import { IReadableSpan } from "../interfaces/otel/trace/IReadableSpan"; +import { _noopVoid } from "../internal/noopHelpers"; import { createContextManager } from "../otel/api/context/contextManager"; -import { createOTelSpanContext } from "../otel/api/trace/spanContext"; -import { createOTelTraceState } from "../otel/api/trace/traceState"; import { findW3cTraceState } from "../telemetry/W3cTraceState"; import { createUniqueNamespace } from "../utils/DataCacheHelper"; -import { getSetValue, isNotNullOrUndefined, proxyFunctionAs, proxyFunctions, toISOString } from "../utils/HelperFuncsCore"; +import { getSetValue, isNotNullOrUndefined, proxyFunctionAs, proxyFunctions, toISOString } from "../utils/HelperFuncs"; import { findW3cTraceParent } from "../utils/TraceParent"; import { doUnloadAll, runTargetUnload } from "./AsyncUtils"; import { createCookieMgr } from "./CookieMgr"; @@ -63,24 +66,22 @@ import { PerfManager, doPerf, getGblPerfMgr } from "./PerfManager"; import { createProcessTelemetryContext, createProcessTelemetryUnloadContext, createProcessTelemetryUpdateContext, createTelemetryProxyChain } from "./ProcessTelemetryContext"; -import { _getPluginState, createDistributedTraceContext, initializePlugins, sortPlugins } from "./TelemetryHelpers"; +import { + _getPluginState, createDistributedTraceContext, initializePlugins, isDistributedTraceContext, sortPlugins +} from "./TelemetryHelpers"; import { TelemetryInitializerPlugin } from "./TelemetryInitializerPlugin"; import { IUnloadHandlerContainer, UnloadHandler, createUnloadHandlerContainer } from "./UnloadHandlerContainer"; import { IUnloadHookContainer, createUnloadHookContainer } from "./UnloadHookContainer"; -// Enums -// Interfaces - AppInsights -// Interfaces - Config -// Interfaces - OTel -// OTel API Functions -// Telemetry Functions -// Utils +// import { IStatsBeat, IStatsBeatConfig, IStatsBeatState } from "../interfaces/ai/IStatsBeat"; +// import { IStatsMgr } from "../interfaces/ai/IStatsMgr"; const strValidationError = "Plugins must provide initialize method"; const strNotificationManager = "_notificationManager"; const strSdkUnloadingError = "SDK is still unloading..."; const strSdkNotInitialized = "SDK is not initialized"; const maxInitQueueSize = 100; const maxInitTimeout = 50000; +const maxAttributeCount = 128; // const strPluginUnloadFailed = "Failed to unload plugin"; // /** @@ -113,19 +114,71 @@ const defaultConfig: IConfigDefaults = objDeepFreeze({ [STR_CREATE_PERF_MGR]: UNDEFINED_VALUE, loggingLevelConsole: eLoggingSeverity.DISABLED, diagnosticLogInterval: UNDEFINED_VALUE, - traceHdrMode: eTraceHeadersMode.All + traceHdrMode: eTraceHeadersMode.All, + traceCfg: cfgDfMerge({ + generalLimits: cfgDfMerge({ + attributeValueLengthLimit: undefined, + attributeCountLimit: maxAttributeCount + }), + // spanLimits: cfgDfMerge({ + // attributeValueLengthLimit: undefined, + // attributeCountLimit: maxAttributeCount, + // linkCountLimit: maxAttributeCount, + // eventCountLimit: maxAttributeCount, + // attributePerEventCountLimit: maxAttributeCount, + // attributePerLinkCountLimit: maxAttributeCount + // }), + // idGenerator: null, + serviceName: null, + suppressTracing: false + }) // _sdk: { rdOnly: true, ref: true, v: defaultSdkConfig } }); +function _getDefaultConfig(core: IAppInsightsCore): IConfigDefaults { + let handlers = { + // Dynamic Default Error Handlers + errorHandlers: cfgDfMerge({ + attribError: (message: string, key: string, value: any) => { + core.logger.throwInternal(eLoggingSeverity.WARNING, _eInternalMessageId.AttributeError, message, { + attribName: key, + value: value + }); + }, + spanError: (message: string, spanName: string) => { + core.logger.throwInternal(eLoggingSeverity.WARNING, _eInternalMessageId.SpanError, message, { + spanName: spanName + }); + }, + debug: (message: string) => { + core.logger.debugToConsole(message); + }, + warn: (message: string) => { + core.logger.warnToConsole(message) + }, + error: (message: string) => { + core.logger.throwInternal(eLoggingSeverity.CRITICAL, _eInternalMessageId.TraceError, message); + }, + notImplemented: (message: string) => { + core.logger.throwInternal(eLoggingSeverity.CRITICAL, _eInternalMessageId.NotImplementedError, message); + } + }) + }; + + return objDeepFreeze(objAssign({}, defaultConfig as any, handlers)); +} + /** * Helper to create the default performance manager * @param core - The AppInsightsCore instance * @param notificationMgr - The notification manager */ -function _createPerfManager(core: IAppInsightsCore, notificationMgr: INotificationManager) { +/*#__NO_SIDE_EFFECTS__*/ +function _createPerfManager (core: IAppInsightsCore, notificationMgr: INotificationManager) { return new PerfManager(notificationMgr); } +/*#__NO_SIDE_EFFECTS__*/ function _validateExtensions(logger: IDiagnosticLogger, channelPriority: number, allExtensions: IPlugin[]): { core: IPlugin[], channels: IChannelControls[] } { // Concat all available extensions let coreExtensions: ITelemetryPlugin[] = []; @@ -169,6 +222,7 @@ function _validateExtensions(logger: IDiagnosticLogger, channelPriority: number, }; } +/*#__NO_SIDE_EFFECTS__*/ function _isPluginPresent(thePlugin: IPlugin, plugins: IPlugin[]) { let exists = false; @@ -204,6 +258,7 @@ function _deepMergeConfig(details: IWatchDetails, target: any, n } } +/*#__NO_SIDE_EFFECTS__*/ function _findWatcher(listeners: { rm: () => void, w: WatcherFunction }[], newWatcher: WatcherFunction) { let theListener: { rm: () => void, w: WatcherFunction } = null; let idx: number = -1; @@ -270,6 +325,7 @@ function _initDebugListener(configHandler: IDynamicConfigHandler } // Moved this outside of the closure to reduce the retained memory footprint +/*#__NO_SIDE_EFFECTS__*/ function _createUnloadHook(unloadHook: IUnloadHook): IUnloadHook { return objDefine({ rm: () => { @@ -278,18 +334,19 @@ function _createUnloadHook(unloadHook: IUnloadHook): IUnloadHook { }, "toJSON", { v: () => "aicore::onCfgChange<" + JSON.stringify(unloadHook) + ">" }); } -function _getParentTraceCtx(mode: eTraceHeadersMode): IOTelSpanContext | null { - let spanContext: IOTelSpanContext | null = null; +/*#__NO_SIDE_EFFECTS__*/ +function _getParentTraceCtx(mode: eTraceHeadersMode): IDistributedTraceContext | null { + let spanContext: IDistributedTraceContext | null = null; const parentTrace = (mode & eTraceHeadersMode.TraceParent) ? findW3cTraceParent() : null; const parentTraceState = (mode & eTraceHeadersMode.TraceState) ? findW3cTraceState() : null; if (parentTrace || parentTraceState) { - spanContext = createOTelSpanContext({ + spanContext = createDistributedTraceContext({ traceId: parentTrace ? parentTrace.traceId : null, spanId: parentTrace ? parentTrace.spanId : null, traceFlags: parentTrace ? parentTrace.traceFlags : UNDEFINED_VALUE, isRemote: true, // Mark as remote since it's from an external source - traceState: parentTraceState ? createOTelTraceState(parentTraceState) : null + traceState: parentTraceState }); } @@ -314,6 +371,11 @@ export class AppInsightsCore im */ public readonly pluginVersionString: string; + /** + * The root {@link IOTelContextManager} for this instance of the Core. + */ + public readonly context: IOTelContextManager; + /** * Returns a value that indicates whether the instance has already been previously initialized. */ @@ -324,11 +386,6 @@ export class AppInsightsCore im */ public getWParam: () => number; - /** - * The root {@link IOTelContextManager} for this instance of the Core. - */ - public readonly context: IOTelContextManager; - constructor() { // NOTE!: DON'T set default values here, instead set them in the _initDefaults() function as it is also called during teardown() let _configHandler: IDynamicConfigHandler; @@ -348,7 +405,7 @@ export class AppInsightsCore im let _isUnloading: boolean; let _telemetryInitializerPlugin: TelemetryInitializerPlugin; let _otelContext: ILazyValue; - let _serverOTelCtx: IOTelSpanContext | null; + let _serverOTelCtx: IDistributedTraceContext | null; let _serverTraceHdrMode: eTraceHeadersMode; let _internalLogsEventName: string | null; let _evtNamespace: string; @@ -356,7 +413,8 @@ export class AppInsightsCore im let _hookContainer: IUnloadHookContainer; let _debugListener: INotificationListener | null; let _traceCtx: IDistributedTraceContext | null; - let _rootTraceCtx: IDistributedTraceContext | null; + let _traceProvider: ICachedValue | null; + let _activeSpan: IReadableSpan | null; let _instrumentationKey: string | null; let _cfgListeners: { rm: () => void, w: WatcherFunction }[]; let _extensions: IPlugin[]; @@ -407,7 +465,7 @@ export class AppInsightsCore im throwError("Core cannot be initialized more than once"); } - _configHandler = createDynamicConfig(config, defaultConfig as any, logger || _self.logger, false); + _configHandler = createDynamicConfig(config, _getDefaultConfig(_self), logger || _self.logger, false); // Re-assigning the local config property so we don't have any references to the passed value and it can be garbage collected config = _configHandler.cfg; @@ -1024,6 +1082,140 @@ export class AppInsightsCore im _traceCtx = traceCtx || null; }; + _self.getTracer = (name: string, version?: string) => { + if (!_traceProvider || !_traceProvider.v || !_traceProvider.v.isAvailable()) { + return null; + } + + return _traceProvider.v.api.getTracer(name || "default") || null; + }; + + _self.startSpan = (name: string, options?: IOTelSpanOptions, parent?: IDistributedTraceContext): IReadableSpan | null => { + if (!_traceProvider || !_traceProvider.v || !_traceProvider.v.isAvailable()) { + // No trace provider available or provider is not ready + return null; + } + + return _traceProvider.v.createSpan(name, options, parent || _self.getTraceCtx()); + }; + + /** + * Return the current active span + * @param createNew - Optional flag to create a non-recording span if no active span exists, defaults to true + */ + _self.getActiveSpan = (createNew?: boolean): IReadableSpan | null => { + // Special case for when there is no active span but there is a trace provider + if (createNew !== false && _traceProvider && !_activeSpan && _traceProvider.v) { + // Now that we have a trace provider, ensure that we return an active span (non-recording) + _activeSpan = _traceProvider.v.createSpan(_traceProvider.v.getProviderId(), { + recording: false, + root: true + }); + } + + return _activeSpan; + }; + + /** + * Set the current Active Span + * @param span - The span to set as the active span + */ + _self.setActiveSpan = (span: IReadableSpan): ISpanScope => { + let theSpanContext: IDistributedTraceContext | null = null; + let currentCtx: IDistributedTraceContext = _self.getTraceCtx(); + let currentSpan: IReadableSpan | null = _activeSpan; + let scope: ISpanScope; + + if (span) { + let otelSpanContext: IDistributedTraceContext | IOTelSpanContext = null; + if (span.spanContext) { + // May be a valid IDistributedTraceContext or an OpenTelemetry SpanContext + otelSpanContext = span.spanContext(); + } else if ((span as any).context) { + // Legacy OpenTelemetry API support (Note: The returned context won't be a valid IDistributedTraceContext) + otelSpanContext = (span as any).context(); + } + + if (otelSpanContext) { + if (isDistributedTraceContext(otelSpanContext)) { + theSpanContext = otelSpanContext; + } else { + // Support Spans from other libraries that may not be using the IDistributedTraceContext + // If the spanContext is not a valid IDistributedTraceContext then we need to create a new one + // and optionally set the parentSpanContext if it exists + + // Create a new context using the current trace context as the parent + theSpanContext = createDistributedTraceContext(currentCtx); + + let parentContext: any = span.parentSpanContext; + if (!parentContext) { + if (span.parentSpanId) { + parentContext = { + traceId: (otelSpanContext as any).traceId, + spanId: span.parentSpanId + }; + } + } + + // Was there a defined parent context and is it different from the current basic context + if (parentContext && parentContext.traceId !== theSpanContext.traceId && + parentContext.spanId !== theSpanContext.spanId && + parentContext.traceFlags !== theSpanContext.traceFlags) { + + // Assign the parent details to this new context + theSpanContext.traceId = parentContext.traceId; + theSpanContext.spanId = parentContext.spanId; + theSpanContext.traceFlags = parentContext.traceFlags; + + // Now create a new "Child" context which is extending the parent context + theSpanContext = createDistributedTraceContext(theSpanContext); + } + + theSpanContext.traceId = (otelSpanContext as any).traceId; + theSpanContext.spanId = (otelSpanContext as any).spanId; + theSpanContext.traceFlags = (otelSpanContext as any).traceFlags; + } + } + } + + scope = { + host: _self, + span: span, + prvSpan: currentSpan, + restore: () => { + // Restore the current span and trace context + if (currentSpan) { + _self.setActiveSpan(currentSpan); + } else { + _activeSpan = null; + _self.setTraceCtx(currentCtx); + } + + // Clear the restore function, so that multiple calls to restore do not have any effect + scope.restore = _noopVoid; + } + }; + + // Change the active span to the new span + _activeSpan = span; + + // Set the current trace context for the core SDK + // This is REQUIRED for the SDK to correctly associate telemetry with the current span context + if (theSpanContext) { + _self.setTraceCtx(theSpanContext); + } + + return scope; + }; + + _self.setTraceProvider = (traceProvider: ICachedValue): void => { + _traceProvider = traceProvider; + }; + + _self.getTraceProvider = (): ITraceProvider | null => { + return _traceProvider ? _traceProvider.v : null; + }; + _self.addUnloadHook = _addUnloadHook; // Create the addUnloadCb @@ -1138,7 +1330,7 @@ export class AppInsightsCore im arrAppend(cfgExtensions, _extensions); _telemetryInitializerPlugin = new TelemetryInitializerPlugin(); - _otelContext = createDeferredCachedValue(() => createContextManager()); + _otelContext = getDeferred(() => createContextManager()); _serverOTelCtx = null; _serverTraceHdrMode = eTraceHeadersMode.None; _eventQueue = []; @@ -1159,6 +1351,7 @@ export class AppInsightsCore im _evtNamespace = createUniqueNamespace("AIBaseCore", true); _unloadHandlers = createUnloadHandlerContainer(); _traceCtx = null; + _traceProvider = null; _instrumentationKey = null; _hookContainer = createUnloadHookContainer(); _cfgListeners = []; @@ -1709,6 +1902,71 @@ export class AppInsightsCore im // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging } + /** + * Start a new span with the given name and optional parent context. + * The span will become the active span for its duration unless a different + * span is explicitly set as active. + * + * @param name - The name of the span + * @param options - Options for creating the span (kind, attributes, startTime) + * @param parent - Optional parent context. If not provided, uses the current active trace context + * @returns A new span instance, or null if no trace provider is available + * @since 3.4.0 + */ + public startSpan(name: string, options?: IOTelSpanOptions, parent?: IDistributedTraceContext): IReadableSpan | null { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + return null; + } + + /** + * Return the current active span, if no trace provider is available null will be returned + * but when a trace provider is available a span instance will always be returned, even if + * there is no active span (in which case a non-recording span will be returned). + * @param createNew - Optional flag to create a non-recording span if no active span exists, defaults to true. + * When false, returns the existing active span or null without creating a non-recording span. + * @returns The current active span or null if no trace provider is available or if createNew is false and no active span exists + * @since 3.4.0 + */ + public getActiveSpan(createNew?: boolean): IReadableSpan | null { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + return null; + } + + /** + * Set the current Active Span + * @param span - The span to set as the active span + * @returns An ISpanScope instance that provides the current scope, the span will always be the span passed in + * even when no trace provider is available + * @since 3.4.0 + */ + public setActiveSpan(span: IReadableSpan): ISpanScope { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + return null; + } + + /** + * Set the trace provider for creating spans. + * This allows different SKUs to provide their own span implementations. + * + * @param provider - The trace provider to use for span creation, it is passed as a cached value so that it may + * be implemented via a lazy / deferred initializer. + * @since 3.4.0 + */ + public setTraceProvider(provider: ICachedValue): void { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + } + + /** + * Get the current trace provider. + * + * @returns The current trace provider, or null if none is set + * @since 3.4.0 + */ + public getTraceProvider(): ITraceProvider | null { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + return null; + } + /** * Add this hook so that it is automatically removed during unloading * @param hooks - The single hook or an array of IInstrumentHook objects diff --git a/shared/otel-core/src/core/BaseTelemetryPlugin.ts b/shared/otel-core/src/core/BaseTelemetryPlugin.ts index d75067e2e..73ae606ae 100644 --- a/shared/otel-core/src/core/BaseTelemetryPlugin.ts +++ b/shared/otel-core/src/core/BaseTelemetryPlugin.ts @@ -22,7 +22,7 @@ import { ITelemetryUnloadState } from "../interfaces/ai/ITelemetryUnloadState"; import { ITelemetryUpdateState } from "../interfaces/ai/ITelemetryUpdateState"; import { ILegacyUnloadHook, IUnloadHook } from "../interfaces/ai/IUnloadHook"; import { IConfigDefaults } from "../interfaces/config/IConfigDefaults"; -import { isNotNullOrUndefined, proxyFunctionAs } from "../utils/HelperFuncsCore"; +import { isNotNullOrUndefined, proxyFunctionAs } from "../utils/HelperFuncs"; import { createProcessTelemetryContext, createProcessTelemetryUnloadContext, createProcessTelemetryUpdateContext } from "./ProcessTelemetryContext"; diff --git a/shared/otel-core/src/core/CookieMgr.ts b/shared/otel-core/src/core/CookieMgr.ts index d25c5c5f8..1184a3c10 100644 --- a/shared/otel-core/src/core/CookieMgr.ts +++ b/shared/otel-core/src/core/CookieMgr.ts @@ -2,8 +2,8 @@ // Licensed under the MIT License. import { IPromise } from "@nevware21/ts-async"; import { - ILazyValue, arrForEach, arrIndexOf, dumpObj, getDocument, getLazy, getNavigator, isArray, isFunction, isNullOrUndefined, isString, - isTruthy, isUndefined, objForEachKey, strEndsWith, strIndexOf, strLeft, strSubstring, strTrim, utcNow + ILazyValue, arrForEach, arrIndexOf, dumpObj, getDocument, getLazy, isArray, isFunction, isNullOrUndefined, isString, isTruthy, + isUndefined, objForEachKey, strEndsWith, strIndexOf, strLeft, strSubstring, strTrim, utcNow } from "@nevware21/ts-utils"; import { cfgDfMerge } from "../config/ConfigDefaultHelpers"; import { createDynamicConfig, onConfigChange } from "../config/DynamicConfig"; @@ -16,8 +16,8 @@ import { ICookieMgr, ICookieMgrConfig } from "../interfaces/ai/ICookieMgr"; import { IDiagnosticLogger } from "../interfaces/ai/IDiagnosticLogger"; import { IUnloadHook } from "../interfaces/ai/IUnloadHook"; import { IConfigDefaults } from "../interfaces/config/IConfigDefaults"; -import { getLocation, isIE } from "../utils/EnvUtils"; -import { getExceptionName, isNotNullOrUndefined, setValue, strContains } from "../utils/HelperFuncsCore"; +import { getLocation, getUserAgentString, isIE } from "../utils/EnvUtils"; +import { getExceptionName, isNotNullOrUndefined, setValue, strContains } from "../utils/HelperFuncs"; const strToGMTString = "toGMTString"; const strToUTCString = "toUTCString"; @@ -242,7 +242,7 @@ export function createCookieMgr(rootConfig?: IConfiguration, logger?: IDiagnosti // Only set same site if not also secure if (_allowUaSameSite === null) { - _allowUaSameSite = !uaDisallowsSameSiteNone((getNavigator() || {} as Navigator).userAgent); + _allowUaSameSite = !uaDisallowsSameSiteNone(getUserAgentString()); } if (_allowUaSameSite) { diff --git a/shared/otel-core/src/core/InstrumentHooks.ts b/shared/otel-core/src/core/InstrumentHooks.ts index 47aa95e13..a14fcbde7 100644 --- a/shared/otel-core/src/core/InstrumentHooks.ts +++ b/shared/otel-core/src/core/InstrumentHooks.ts @@ -6,7 +6,7 @@ import { getInst, objHasOwnProperty } from "@nevware21/ts-utils"; import { IInstrumentCallDetails, IInstrumentHook, IInstrumentHooks, IInstrumentHooksCallbacks, InstrumentorHooksCallback } from "../interfaces/ai/IInstrumentHooks"; -import { _getObjProto } from "../utils/HelperFuncsCore"; +import { _getObjProto } from "../utils/HelperFuncs"; const aiInstrumentHooks = "_aiHooks"; diff --git a/shared/otel-core/src/core/PerfManager.ts b/shared/otel-core/src/core/PerfManager.ts index 905dd9222..a934dd86f 100644 --- a/shared/otel-core/src/core/PerfManager.ts +++ b/shared/otel-core/src/core/PerfManager.ts @@ -6,6 +6,7 @@ import { STR_GET_PERF_MGR } from "../constants/InternalConstants"; import { INotificationManager } from "../interfaces/ai/INotificationManager"; import { IPerfEvent } from "../interfaces/ai/IPerfEvent"; import { IPerfManager, IPerfManagerProvider } from "../interfaces/ai/IPerfManager"; +import { _noopVoid } from "../internal/noopHelpers"; const strExecutionContextKey = "ctx"; const strParentContextKey = "ParentContextKey"; @@ -130,7 +131,7 @@ export class PerfEvent implements IPerfEvent { _self.time = utcNow() - _self.start; _self.exTime = _self.time - childTime; - _self.complete = () => {}; + _self.complete = _noopVoid; }; } } @@ -226,32 +227,14 @@ const doPerfActiveKey = "CoreUtils.doPerf"; export function doPerf(mgrSource: IPerfManagerProvider | IPerfManager, getSource: () => string, func: (perfEvt?: IPerfEvent) => T, details?: () => any, isAsync?: boolean) { if (mgrSource) { let perfMgr: IPerfManager = mgrSource as IPerfManager; - let perfProvider: IPerfManagerProvider = mgrSource as IPerfManagerProvider; - let thePerfMgr: any = mgrSource; - - let internalGetPerfMgr = thePerfMgr[STR_GET_PERF_MGR]; - if (isFunction(internalGetPerfMgr)) { - // Looks like a perf manager provider object using the internal accessor - perfMgr = internalGetPerfMgr.call(thePerfMgr); - } else { - let providerGetPerfMgr = perfProvider.getPerfMgr; - if (isFunction(providerGetPerfMgr)) { - perfMgr = providerGetPerfMgr.call(perfProvider); - } + if (perfMgr[STR_GET_PERF_MGR]) { + // Looks like a perf manager provider object + perfMgr = perfMgr[STR_GET_PERF_MGR](); } - + if (perfMgr) { - let hasCreate = isFunction(perfMgr.create); - let hasFire = isFunction(perfMgr.fire); - let hasSetCtx = isFunction(perfMgr.setCtx); - let hasGetCtx = isFunction(perfMgr.getCtx); - - if (!(hasCreate && hasFire && hasSetCtx)) { - return func(); - } - let perfEvt: IPerfEvent; - let currentActive: IPerfEvent = hasGetCtx ? perfMgr.getCtx(doPerfActiveKey) : null; + let currentActive: IPerfEvent = perfMgr.getCtx(doPerfActiveKey); try { perfEvt = perfMgr.create(getSource(), details, isAsync); if (perfEvt) { @@ -263,11 +246,11 @@ export function doPerf(mgrSource: IPerfManagerProvider | IPerfManager, getSou children = []; currentActive.setCtx(PerfEvent[strChildrenContextKey], children); } - + children.push(perfEvt); } } - + // Set this event as the active event now perfMgr.setCtx(doPerfActiveKey, perfEvt); return func(perfEvt); @@ -281,7 +264,7 @@ export function doPerf(mgrSource: IPerfManagerProvider | IPerfManager, getSou if (perfEvt) { perfMgr.fire(perfEvt); } - + // Reset the active event to the previous value perfMgr.setCtx(doPerfActiveKey, currentActive); } diff --git a/shared/otel-core/src/core/ProcessTelemetryContext.ts b/shared/otel-core/src/core/ProcessTelemetryContext.ts index bc0d70942..2a1b8f38e 100644 --- a/shared/otel-core/src/core/ProcessTelemetryContext.ts +++ b/shared/otel-core/src/core/ProcessTelemetryContext.ts @@ -2,9 +2,7 @@ // Licensed under the MIT License. "use strict"; -import { - arrForEach, dumpObj, isArray, isFunction, isNullOrUndefined, isUndefined, objForEachKey, objFreeze, objKeys -} from "@nevware21/ts-utils"; +import { arrForEach, dumpObj, isArray, isFunction, isNullOrUndefined, isUndefined, objForEachKey, objFreeze } from "@nevware21/ts-utils"; import { _applyDefaultValue } from "../config/ConfigDefaults"; import { createDynamicConfig } from "../config/DynamicConfig"; import { STR_CORE, STR_DISABLED, STR_EMPTY } from "../constants/InternalConstants"; @@ -12,7 +10,6 @@ import { _throwInternal, safeGetLogger } from "../diagnostics/DiagnosticLogger"; import { _eInternalMessageId, eLoggingSeverity } from "../enums/ai/LoggingEnums"; import { IAppInsightsCore } from "../interfaces/ai/IAppInsightsCore"; import { IConfiguration } from "../interfaces/ai/IConfiguration"; -import { IDiagnosticLogger } from "../interfaces/ai/IDiagnosticLogger"; import { IBaseProcessingContext, IProcessTelemetryContext, IProcessTelemetryUnloadContext, IProcessTelemetryUpdateContext } from "../interfaces/ai/IProcessTelemetryContext"; @@ -23,7 +20,7 @@ import { ITelemetryUnloadState } from "../interfaces/ai/ITelemetryUnloadState"; import { ITelemetryUpdateState } from "../interfaces/ai/ITelemetryUpdateState"; import { IConfigDefaults } from "../interfaces/config/IConfigDefaults"; import { IDynamicConfigHandler } from "../interfaces/config/IDynamicConfigHandler"; -import { proxyFunctions } from "../utils/HelperFuncsCore"; +import { _noopVoid } from "../internal/noopHelpers"; import { doPerf } from "./PerfManager"; import { _getPluginState } from "./TelemetryHelpers"; @@ -556,7 +553,7 @@ export function createTelemetryPluginProxy(plugin: ITelemetryPlugin, config: ICo return hasRun; } - if (!_processChain(unloadCtx, _callTeardown, "unload", () => {}, unloadState.isAsync)) { + if (!_processChain(unloadCtx, _callTeardown, "unload", _noopVoid, unloadState.isAsync)) { // Only called if we hasRun was not true unloadCtx.processNext(unloadState); } @@ -583,7 +580,7 @@ export function createTelemetryPluginProxy(plugin: ITelemetryPlugin, config: ICo return hasRun; } - if (!_processChain(updateCtx, _callUpdate, "update", () => {}, false)) { + if (!_processChain(updateCtx, _callUpdate, "update", _noopVoid, false)) { // Only called if we hasRun was not true updateCtx.processNext(updateState); } @@ -591,89 +588,3 @@ export function createTelemetryPluginProxy(plugin: ITelemetryPlugin, config: ICo return objFreeze(proxyChain); } - -/** - * This class will be removed! - * @deprecated use createProcessTelemetryContext() instead - */ -export class ProcessTelemetryContext implements IProcessTelemetryContext { - /** - * Gets the current core config instance - */ - public getCfg: () => IConfiguration; - - public getExtCfg: (identifier:string, defaultValue?: IConfigDefaults) => T; - - public getConfig: (identifier:string, field: string, defaultValue?: number | string | boolean | string[] | RegExp[] | Function) => number | string | boolean | string[] | RegExp[] | Function; - - /** - * Returns the IAppInsightsCore instance for the current request - */ - public core: () => IAppInsightsCore; - - /** - * Returns the current IDiagnosticsLogger for the current request - */ - public diagLog: () => IDiagnosticLogger; - - /** - * Helper to allow inherited classes to check and possibly shortcut executing code only - * required if there is a nextPlugin - */ - public hasNext: () => boolean; - - /** - * Returns the next configured plugin proxy - */ - public getNext: () => ITelemetryPluginChain; - - /** - * Helper to set the next plugin proxy - */ - public setNext: (nextCtx:ITelemetryPluginChain) => void; - - /** - * Call back for telemetry processing before it it is sent - * @param env - This is the current event being reported - * @param itemCtx - This is the context for the current request, ITelemetryPlugin instances - * can optionally use this to access the current core instance or define / pass additional information - * to later plugins (vs appending items to the telemetry item) - * @returns boolean (true) if there is no more plugins to process otherwise false or undefined (void) - */ - public processNext: (env: ITelemetryItem) => boolean | void; - - /** - * Synchronously iterate over the context chain running the callback for each plugin, once - * every plugin has been executed via the callback, any associated onComplete will be called. - * @param callback - The function call for each plugin in the context chain - */ - public iterate: (callback: (plugin: T) => void) => void; - - /** - * Create a new context using the core and config from the current instance - * @param plugins - The execution order to process the plugins, if null or not supplied - * then the current execution order will be copied. - * @param startAt - The plugin to start processing from, if missing from the execution - * order then the next plugin will be NOT set. - */ - public createNew: (plugins?:IPlugin[]|ITelemetryPluginChain, startAt?:IPlugin) => IProcessTelemetryContext; - - /** - * Set the function to call when the current chain has executed all processNext or unloadNext items. - */ - public onComplete: (onComplete: () => void) => void; - - /** - * Creates a new Telemetry Item context with the current config, core and plugin execution chain - * @param plugins - The plugin instances that will be executed - * @param config - The current config - * @param core - The current core instance - */ - constructor(pluginChain: ITelemetryPluginChain, config: IConfiguration, core: IAppInsightsCore, startAt?:IPlugin) { - let _self = this; - - let context = createProcessTelemetryContext(pluginChain, config, core, startAt); - // Proxy all functions of the context to this object - proxyFunctions(_self, context, objKeys(context) as any); - } -} diff --git a/shared/otel-core/src/core/SenderPostManager.ts b/shared/otel-core/src/core/SenderPostManager.ts index 6cdc74cc4..43977e2ff 100644 --- a/shared/otel-core/src/core/SenderPostManager.ts +++ b/shared/otel-core/src/core/SenderPostManager.ts @@ -17,8 +17,9 @@ import { import { ITelemetryUnloadState } from "../interfaces/ai/ITelemetryUnloadState"; import { IXDomainRequest } from "../interfaces/ai/IXDomainRequest"; import { IPayloadData, IXHROverride, OnCompleteCallback, SendPOSTFunction } from "../interfaces/ai/IXHROverride"; +import { _noopVoid } from "../internal/noopHelpers"; import { getLocation, isBeaconsSupported, isFetchSupported, isXhrSupported, useXDomainRequest } from "../utils/EnvUtils"; -import { _getAllResponseHeaders, formatErrorMessageXdr, formatErrorMessageXhr, getResponseText, openXhr } from "../utils/HelperFuncsCore"; +import { _getAllResponseHeaders, formatErrorMessageXdr, formatErrorMessageXhr, getResponseText, openXhr } from "../utils/HelperFuncs"; const STR_NO_RESPONSE_BODY = "NoResponseBody"; const _noResponseQs = "&" + STR_NO_RESPONSE_BODY + "=true"; @@ -34,6 +35,7 @@ declare var XDomainRequest: { * Manager SendPost functions * SendPostManger * @internal for internal use only + * @since 3.0.0 */ export class SenderPostManager { @@ -650,7 +652,7 @@ export class SenderPostManager { }; - xdr.onprogress = () => { }; + xdr.onprogress = _noopVoid; // XDomainRequest requires the same protocol as the hosting page. // If the protocol doesn't match, we can't send the telemetry :(. diff --git a/shared/otel-core/src/core/StatsBeat.ts b/shared/otel-core/src/core/StatsBeat.ts index a59ecadec..ef0578302 100644 --- a/shared/otel-core/src/core/StatsBeat.ts +++ b/shared/otel-core/src/core/StatsBeat.ts @@ -16,7 +16,7 @@ import { IStatsBeat, IStatsBeatConfig, IStatsBeatState, IStatsEndpointConfig } f import { IStatsMgr, IStatsMgrConfig } from "../interfaces/ai/IStatsMgr"; import { ITelemetryItem } from "../interfaces/ai/ITelemetryItem"; import { IPayloadData } from "../interfaces/ai/IXHROverride"; -import { isFeatureEnabled } from "../utils/HelperFuncsCore"; +import { isFeatureEnabled } from "../utils/HelperFuncs"; const STATS_COLLECTION_SHORT_INTERVAL: number = 900000; // 15 minutes const STATS_MIN_INTERVAL_SECONDS = 60; // 1 minute diff --git a/shared/otel-core/src/core/TelemetryHelpers.ts b/shared/otel-core/src/core/TelemetryHelpers.ts index 8a533c97e..f1ce02ccf 100644 --- a/shared/otel-core/src/core/TelemetryHelpers.ts +++ b/shared/otel-core/src/core/TelemetryHelpers.ts @@ -4,7 +4,7 @@ import { arrForEach, isFunction, objDefineProps } from "@nevware21/ts-utils"; import { STR_CORE, STR_EMPTY, STR_PRIORITY, STR_PROCESS_TELEMETRY, UNDEFINED_VALUE } from "../constants/InternalConstants"; import { IAppInsightsCore } from "../interfaces/ai/IAppInsightsCore"; -import { IDistributedTraceContext } from "../interfaces/ai/IDistributedTraceContext"; +import { IDistributedTraceContext, IDistributedTraceInit } from "../interfaces/ai/IDistributedTraceContext"; import { IProcessTelemetryContext, IProcessTelemetryUnloadContext } from "../interfaces/ai/IProcessTelemetryContext"; import { IPlugin, ITelemetryPlugin } from "../interfaces/ai/ITelemetryPlugin"; import { ITelemetryPluginChain } from "../interfaces/ai/ITelemetryPluginChain"; @@ -12,10 +12,13 @@ import { ITelemetryUnloadState } from "../interfaces/ai/ITelemetryUnloadState"; import { IUnloadableComponent } from "../interfaces/ai/IUnloadableComponent"; import { IW3cTraceState } from "../interfaces/ai/IW3cTraceState"; import { IOTelSpanContext } from "../interfaces/otel/trace/IOTelSpanContext"; +import { createOTelSpanContext } from "../otel/api/trace/spanContext"; +import { isOTelTraceState } from "../otel/api/trace/traceState"; import { createW3cTraceState } from "../telemetry/W3cTraceState"; import { generateW3CId } from "../utils/CoreUtils"; import { createElmNodeData } from "../utils/DataCacheHelper"; import { getLocation } from "../utils/EnvUtils"; +import { setProtoTypeName } from "../utils/HelperFuncs"; import { isValidSpanId, isValidTraceId } from "../utils/TraceParent"; export interface IPluginState { @@ -141,7 +144,8 @@ export function unloadComponents(components: any | IUnloadableComponent[], unloa return _doUnload(); } -function isDistributedTraceContext(obj: any): obj is IDistributedTraceContext { +/*#__NO_SIDE_EFFECTS__*/ +export function isDistributedTraceContext(obj: any): obj is IDistributedTraceContext { return obj && isFunction(obj.getName) && isFunction(obj.getTraceId) && @@ -182,9 +186,10 @@ function isDistributedTraceContext(obj: any): obj is IDistributedTraceContext { * - IDistributedTraceContext parents enable bidirectional updates and hierarchical management * - IOTelSpanContext parents are used only for initial data extraction and OpenTelemetry compatibility */ -export function createDistributedTraceContext(parent?: IDistributedTraceContext | IOTelSpanContext): IDistributedTraceContext { +/*#__NO_SIDE_EFFECTS__*/ +export function createDistributedTraceContext(parent?: IDistributedTraceContext | IOTelSpanContext | IDistributedTraceInit | undefined | null): IDistributedTraceContext { let parentCtx: IDistributedTraceContext = null; - let spanContext: IOTelSpanContext = null; + let initCtx: IDistributedTraceInit | IOTelSpanContext = null; let traceId = (parent && isValidTraceId(parent.traceId)) ? parent.traceId : generateW3CId(); let spanId = (parent && isValidSpanId(parent.spanId)) ? parent.spanId : STR_EMPTY; let traceFlags = parent ? parent.traceFlags : UNDEFINED_VALUE; @@ -197,7 +202,7 @@ export function createDistributedTraceContext(parent?: IDistributedTraceContext parentCtx = parent; pageName = parentCtx.getName(); } else { - spanContext = parent; + initCtx = parent; } } @@ -272,9 +277,21 @@ export function createDistributedTraceContext(parent?: IDistributedTraceContext function _getTraceState(): IW3cTraceState { if (!traceState) { - if (spanContext && spanContext.traceState) { - traceState = createW3cTraceState(spanContext.traceState.serialize() || STR_EMPTY, parentCtx ? parentCtx.traceState : undefined); - } else { + if (!parentCtx) { + // The passed in parent was not an IDistributedTraceContext + if (initCtx) { + if (isOTelTraceState(initCtx.traceState)) { + // This looks like an IOTelSpanContext, so we have to just use the passed traceState as-is as it doesn't support + // the W3cTraceState heirarchy methods + traceState = createW3cTraceState(initCtx.traceState.serialize() || STR_EMPTY, parentCtx ? parentCtx.traceState : undefined); + } else { + // This looks like an IDistributedTraceInit, so we can create a new W3cTraceState + traceState = createW3cTraceState(STR_EMPTY, initCtx.traceState || (parentCtx ? parentCtx.traceState : undefined)); + } + } + } + + if (!traceState) { traceState = createW3cTraceState(STR_EMPTY, parentCtx ? parentCtx.traceState : undefined); } } @@ -282,7 +299,8 @@ export function createDistributedTraceContext(parent?: IDistributedTraceContext return traceState; } - let traceCtx: IDistributedTraceContext = { + let otelSpanCtx: IOTelSpanContext = null; + let traceCtx: IDistributedTraceContext = setProtoTypeName({ getName: _getName, setName: _setPageNameFn(true), getTraceId: _getTraceId, @@ -296,8 +314,16 @@ export function createDistributedTraceContext(parent?: IDistributedTraceContext traceFlags, traceState, isRemote, - pageName - }; + pageName, + getOTelSpanContext: () => { + if (!otelSpanCtx) { + // Lazily create the OTel Span Context + otelSpanCtx = createOTelSpanContext(traceCtx); + } + + return otelSpanCtx; + } + }, "DistributedTraceContext"); return objDefineProps(traceCtx, { pageName: { @@ -326,6 +352,23 @@ export function createDistributedTraceContext(parent?: IDistributedTraceContext }, parentCtx: { g: () => parentCtx + }, + _parent: { + g: () => { + let result: any; + if (parentCtx) { + result = { + t: "traceCtx", + v: parentCtx + }; + } else if(initCtx) { + result = { + t: "initCtx", + v: initCtx + }; + } + return result; + } } }); } diff --git a/shared/otel-core/src/core/TelemetryInitializerPlugin.ts b/shared/otel-core/src/core/TelemetryInitializerPlugin.ts index a3182e66a..783cfe6f4 100644 --- a/shared/otel-core/src/core/TelemetryInitializerPlugin.ts +++ b/shared/otel-core/src/core/TelemetryInitializerPlugin.ts @@ -11,7 +11,7 @@ import { ITelemetryInitializerContainer, ITelemetryInitializerHandler, TelemetryInitializerFunction } from "../interfaces/ai/ITelemetryInitializers"; import { ITelemetryItem } from "../interfaces/ai/ITelemetryItem"; -import { getExceptionName } from "../utils/HelperFuncsCore"; +import { getExceptionName } from "../utils/HelperFuncs"; import { BaseTelemetryPlugin } from "./BaseTelemetryPlugin"; interface _IInternalTelemetryInitializerHandler { diff --git a/shared/otel-core/src/core/UnloadHandlerContainer.ts b/shared/otel-core/src/core/UnloadHandlerContainer.ts index a1ad189e7..d272ea41e 100644 --- a/shared/otel-core/src/core/UnloadHandlerContainer.ts +++ b/shared/otel-core/src/core/UnloadHandlerContainer.ts @@ -14,6 +14,7 @@ export interface IUnloadHandlerContainer { run: (itemCtx: IProcessTelemetryUnloadContext, unloadState: ITelemetryUnloadState) => void } +/*#__NO_SIDE_EFFECTS__*/ export function createUnloadHandlerContainer(): IUnloadHandlerContainer { let handlers: UnloadHandler[] = []; diff --git a/shared/otel-core/src/diagnostics/DiagnosticLogger.ts b/shared/otel-core/src/diagnostics/DiagnosticLogger.ts index 5538a4d77..0c604c49a 100644 --- a/shared/otel-core/src/diagnostics/DiagnosticLogger.ts +++ b/shared/otel-core/src/diagnostics/DiagnosticLogger.ts @@ -3,7 +3,7 @@ "use strict" import dynamicProto from "@microsoft/dynamicproto-js"; import { IPromise } from "@nevware21/ts-async"; -import { dumpObj, isFunction, isUndefined } from "@nevware21/ts-utils"; +import { dumpObj, isFunction, isUndefined, objDefine } from "@nevware21/ts-utils"; import { createDynamicConfig, onConfigChange } from "../config/DynamicConfig"; import { STR_EMPTY } from "../constants/InternalConstants"; import { getDebugExt } from "../core/DbgExtensionUtils"; @@ -37,7 +37,7 @@ const defaultValues: IConfigDefaults = { loggingLevelTelemetry: 1, maxMessageLimit: 25, enableDebug: false -} +}; const _logFuncs: { [key in eLoggingSeverity]: keyof IDiagnosticLogger} = { [eLoggingSeverity.DISABLED]: null, @@ -95,6 +95,7 @@ export class _InternalLogMessage{ } } +/*#__NO_SIDE_EFFECTS__*/ export function safeGetLogger(core: IAppInsightsCore, config?: IConfiguration): IDiagnosticLogger { return (core || {} as any).logger || new DiagnosticLogger(config); } @@ -102,6 +103,8 @@ export function safeGetLogger(core: IAppInsightsCore, config?: IConfiguration): export class DiagnosticLogger implements IDiagnosticLogger { public identifier = "DiagnosticLogger"; + public readonly dbgMode: boolean; + /** * The internal logging queue */ @@ -194,6 +197,10 @@ export class DiagnosticLogger implements IDiagnosticLogger { _unloadHandler = null; }; + objDefine(_self, "dbgMode", { + g: () => _enableDebug + }); + function _logInternalMessage(severity: LoggingSeverity, message: _InternalLogMessage): void { if (_areInternalMessagesThrottled()) { return; diff --git a/shared/otel-core/src/diagnostics/ThrottleMgr.ts b/shared/otel-core/src/diagnostics/ThrottleMgr.ts index 086a34c3a..4f669fd4b 100644 --- a/shared/otel-core/src/diagnostics/ThrottleMgr.ts +++ b/shared/otel-core/src/diagnostics/ThrottleMgr.ts @@ -3,16 +3,15 @@ import { arrForEach, arrIndexOf, isNullOrUndefined, mathFloor, mathMin, objForEachKey, strTrim } from "@nevware21/ts-utils"; import { onConfigChange } from "../config/DynamicConfig"; +import { _throwInternal, safeGetLogger } from "../diagnostics/DiagnosticLogger"; import { _eInternalMessageId, eLoggingSeverity } from "../enums/ai/LoggingEnums"; import { IAppInsightsCore } from "../interfaces/ai/IAppInsightsCore"; import { IConfig } from "../interfaces/ai/IConfig"; import { IConfiguration } from "../interfaces/ai/IConfiguration"; import { IDiagnosticLogger } from "../interfaces/ai/IDiagnosticLogger"; import { IThrottleInterval, IThrottleLocalStorageObj, IThrottleMgrConfig, IThrottleResult } from "../interfaces/ai/IThrottleMgr"; -import { isNotNullOrUndefined } from "../utils/HelperFuncsCore"; import { randomValue } from "../utils/RandomHelper"; import { utlCanUseLocalStorage, utlGetLocalStorage, utlSetLocalStorage } from "../utils/StorageHelperFuncs"; -import { safeGetLogger } from "./DiagnosticLogger"; const THROTTLE_STORAGE_PREFIX = "appInsightsThrottle"; @@ -204,7 +203,7 @@ export class ThrottleMgr { _queue = {}; _config = {}; _setCfgByKey(_eInternalMessageId.DefaultThrottleMsgKey); - _namePrefix = isNotNullOrUndefined(namePrefix)? namePrefix : ""; + _namePrefix = !isNullOrUndefined(namePrefix)? namePrefix : ""; core.addUnloadHook(onConfigChange(core.config, (details) => { let coreConfig = details.cfg; @@ -267,7 +266,7 @@ export class ThrottleMgr { } function _canThrottle(config: IThrottleMgrConfig, canUseLocalStorage: boolean, localStorageObj: IThrottleLocalStorageObj) { - if (config && !config.disabled && canUseLocalStorage && isNotNullOrUndefined(localStorageObj)) { + if (config && !config.disabled && canUseLocalStorage && !isNullOrUndefined(localStorageObj)) { let curDate = _getThrottleDate(); let date = localStorageObj.date; let interval = config.interval; @@ -291,7 +290,7 @@ export class ThrottleMgr { } function _getLocalStorageName(msgKey: _eInternalMessageId | number, prefix?: string) { - let fix = isNotNullOrUndefined(prefix)? prefix : ""; + let fix = !isNullOrUndefined(prefix)? prefix : ""; if (msgKey) { return THROTTLE_STORAGE_PREFIX + fix + "-" + msgKey; } @@ -376,7 +375,7 @@ export class ThrottleMgr { } function _sendMessage(msgID: _eInternalMessageId, logger: IDiagnosticLogger, message: string, severity?: eLoggingSeverity) { - logger.throwInternal( + _throwInternal(logger, severity || eLoggingSeverity.CRITICAL, msgID, message); diff --git a/shared/otel-core/src/enums/ai/DependencyTypes.ts b/shared/otel-core/src/enums/ai/DependencyTypes.ts new file mode 100644 index 000000000..0b943cef4 --- /dev/null +++ b/shared/otel-core/src/enums/ai/DependencyTypes.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { createEnumStyle } from "../EnumHelperFuncs"; + +export const enum eDependencyTypes { + InProc = "InProc", + QueueMessage = "Queue Message", + Sql = "SQL", + Http = "Http", + Grpc = "GRPC", + Wcf = "WCF Service", +} + +/** + * The EventsDiscardedReason enumeration contains a set of values that specify the reason for discarding an event. + */ +export const DependencyTypes = (/* @__PURE__ */ createEnumStyle({ + /** + * InProc + */ + InProc: eDependencyTypes.InProc, + + /** + * Quene Message + */ + QueueMessage: eDependencyTypes.QueueMessage, + + /** + * Sql + */ + Sql: eDependencyTypes.Sql, + + /** + * Http + */ + Http: eDependencyTypes.Http, + + /** + * Grpc + */ + Grpc: eDependencyTypes.Grpc, + + /** + * Wcf + */ + Wcf: eDependencyTypes.Wcf +})); + +export type DependencyTypes = string | eDependencyTypes; \ No newline at end of file diff --git a/shared/otel-core/src/enums/ai/LoggingEnums.ts b/shared/otel-core/src/enums/ai/LoggingEnums.ts index 080ae9f30..82f50a817 100644 --- a/shared/otel-core/src/enums/ai/LoggingEnums.ts +++ b/shared/otel-core/src/enums/ai/LoggingEnums.ts @@ -130,7 +130,11 @@ export const enum _eInternalMessageId { InitPromiseException = 112, StatsBeatManagerException = 113, StatsBeatException = 114, - VersionMismatch = 115, + AttributeError = 115, + SpanError = 116, + TraceError = 117, + NotImplementedError = 118, + VersionMismatch = 119 } export type _InternalMessageId = number | _eInternalMessageId; diff --git a/shared/otel-core/src/index.ts b/shared/otel-core/src/index.ts index 0267b52e3..0d05a7e04 100644 --- a/shared/otel-core/src/index.ts +++ b/shared/otel-core/src/index.ts @@ -1,87 +1,243 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// Trace Support -export { createOTelApi, traceApiDefaultConfigValues } from "./otel/api/OTelApi"; -export { OTelSdk } from "./otel/sdk/OTelSdk"; +export { IConfiguration } from "./interfaces/ai/IConfiguration"; +export { IChannelControls, MinChannelPriorty, IInternalOfflineSupport } from "./interfaces/ai/IChannelControls"; +export { IChannelControlsHost } from "./interfaces/ai/IChannelControlsHost"; +export { ITelemetryPlugin, IPlugin } from "./interfaces/ai/ITelemetryPlugin"; +export { IExceptionConfig } from "./interfaces/ai/IExceptionConfig"; +export { IAppInsightsCore, ILoadedPlugin } from "./interfaces/ai/IAppInsightsCore"; +export { ITelemetryItem, ICustomProperties, Tags } from "./interfaces/ai/ITelemetryItem"; +export { IBaseProcessingContext, IProcessTelemetryContext, IProcessTelemetryUnloadContext, IProcessTelemetryUpdateContext } from "./interfaces/ai/IProcessTelemetryContext"; +export { INotificationListener } from "./interfaces/ai/INotificationListener"; +export { ITelemetryPluginChain } from "./interfaces/ai/ITelemetryPluginChain"; +export { IDiagnosticLogger } from "./interfaces/ai/IDiagnosticLogger"; +export { InstrumentorHooksCallback, IInstrumentHooksCallbacks, IInstrumentHooks, IInstrumentHook, IInstrumentCallDetails } from "./interfaces/ai/IInstrumentHooks"; +export { IUnloadableComponent } from "./interfaces/ai/IUnloadableComponent"; +export { IPayloadData, SendPOSTFunction, IXHROverride, OnCompleteCallback } from "./interfaces/ai/IXHROverride"; +export { IUnloadHook, ILegacyUnloadHook } from "./interfaces/ai/IUnloadHook"; +export { eEventsDiscardedReason, EventsDiscardedReason, eBatchDiscardedReason, BatchDiscardedReason } from "./enums/ai/EventsDiscardedReason"; +export { eDependencyTypes, DependencyTypes } from "./enums/ai/DependencyTypes"; +export { SendRequestReason, TransportType } from "./enums/ai/SendRequestReason"; +//export { StatsType, eStatsType } from "./enums/ai/StatsType"; +export { TelemetryUpdateReason } from "./enums/ai/TelemetryUpdateReason"; +export { TelemetryUnloadReason } from "./enums/ai/TelemetryUnloadReason"; +export { eActiveStatus, ActiveStatus } from "./enums/ai/InitActiveStatusEnum"; +export { throwAggregationError } from "./core/AggregationError"; +export { AppInsightsCore } from "./core/AppInsightsCore"; +export { BaseTelemetryPlugin } from "./core/BaseTelemetryPlugin"; +export { randomValue, random32, mwcRandomSeed, mwcRandom32, newId } from "./utils/RandomHelper"; +export { Undefined, newGuid, generateW3CId } from "./utils/CoreUtils"; +export { runTargetUnload, doUnloadAll } from "./core/AsyncUtils"; +export { + normalizeJsName, toISOString, getExceptionName, strContains, setValue, getSetValue, + proxyAssign, proxyFunctions, proxyFunctionAs, createClassFromInterface, optimizeObject, + isNotUndefined, isNotNullOrUndefined, objExtend, isFeatureEnabled, getResponseText, formatErrorMessageXdr, formatErrorMessageXhr, prependTransports, + openXhr, _appendHeader, _getAllResponseHeaders, setObjStringTag, setProtoTypeName, isTimeSpan +} from "./utils/HelperFuncs"; +export { parseResponse } from "./core/ResponseHelpers"; +export { IXDomainRequest, IBackendResponse } from "./interfaces/ai/IXDomainRequest"; +export { _ISenderOnComplete, _ISendPostMgrConfig, _ITimeoutOverrideWrapper, _IInternalXhrOverride } from "./interfaces/ai/ISenderPostManager"; +export { SenderPostManager } from "./core/SenderPostManager"; +//export { IStatsBeat, IStatsBeatConfig, IStatsBeatKeyMap as IStatsBeatEndpoints, IStatsBeatState} from "./interfaces/ai/IStatsBeat"; +//export { IStatsEventData } from "./interfaces/ai/IStatsEventData"; +//export { IStatsMgr, IStatsMgrConfig } from "./interfaces/ai/IStatsMgr"; +//export { createStatsMgr } from "./core/StatsBeat"; +export { + isArray, isTypeof, isUndefined, isNullOrUndefined, isStrictUndefined, objHasOwnProperty as hasOwnProperty, isObject, isFunction, + strEndsWith, strStartsWith, isDate, isError, isString, isNumber, isBoolean, arrForEach, arrIndexOf, + arrReduce, arrMap, strTrim, objKeys, objCreate, objDefine, objDefineProp, objDefineAccessors, throwError, isSymbol, + isNotTruthy, isTruthy, objFreeze, objSeal, objToString, objDeepFreeze as deepFreeze, + getInst as getGlobalInst, hasWindow, getWindow, hasDocument, getDocument, hasNavigator, getNavigator, hasHistory, + getHistory, dumpObj, asString, objForEachKey, getPerformance, utcNow as dateNow, perfNow, + ObjDefinePropDescriptor +} from "@nevware21/ts-utils"; +export { EnumValue, createEnumStyle, createValueMap } from "./enums/EnumHelperFuncs"; +export { + attachEvent, detachEvent, addEventHandler, addEventListeners, addPageUnloadEventListener, addPageHideEventListener, addPageShowEventListener, + removeEventHandler, removeEventListeners, removePageUnloadEventListener, removePageHideEventListener, removePageShowEventListener, eventOn, eventOff, + mergeEvtNamespace, _IRegisteredEvents, __getRegisteredEvents +} from "./internal/EventHelpers"; +export { + getCrypto, getMsCrypto, getLocation, hasJSON, getJSON, + isReactNative, getConsole, isIE, getIEVersion, isSafari, + setEnableEnvMocks, isBeaconsSupported, isFetchSupported, useXDomainRequest, isXhrSupported, + findMetaTag, findNamedServerTiming, sendCustomEvent, dispatchEvent, createCustomDomEvent, fieldRedaction +} from "./utils/EnvUtils"; +export { + getGlobal, + strShimPrototype as strPrototype, + strShimFunction as strFunction, + strShimUndefined as strUndefined, + strShimObject as strObject +} from "@microsoft/applicationinsights-shims"; +export { NotificationManager } from "./core/NotificationManager"; +export { INotificationManager } from "./interfaces/ai/INotificationManager"; +export { IPerfEvent } from "./interfaces/ai/IPerfEvent"; +export { IPerfManager, IPerfManagerProvider } from "./interfaces/ai/IPerfManager"; +export { PerfEvent, PerfManager, doPerf, getGblPerfMgr, setGblPerfMgr } from "./core/PerfManager"; +export { IFeatureOptInDetails, IFeatureOptIn } from "./interfaces/ai/IFeatureOptIn"; +export { FeatureOptInMode, CdnFeatureMode } from "./enums/ai/FeatureOptInEnums"; +export { safeGetLogger, DiagnosticLogger, _InternalLogMessage, _throwInternal, _warnToConsole, _logInternalMessage } from "./diagnostics/DiagnosticLogger"; +export { + createProcessTelemetryContext + // Explicitly NOT exporting createProcessTelemetryUnloadContext() and createProcessTelemetryUpdateContext() as these should only be created internally +} from "./core/ProcessTelemetryContext"; +export { initializePlugins, sortPlugins, unloadComponents, createDistributedTraceContext, isDistributedTraceContext } from "./core/TelemetryHelpers"; +export { _eInternalMessageId, _InternalMessageId, LoggingSeverity, eLoggingSeverity } from "./enums/ai/LoggingEnums"; +export { InstrumentProto, InstrumentProtos, InstrumentFunc, InstrumentFuncs, InstrumentEvent } from "./core/InstrumentHooks"; +export { ICookieMgr, ICookieMgrConfig } from "./interfaces/ai/ICookieMgr"; +export { + createCookieMgr, safeGetCookieMgr, uaDisallowsSameSiteNone, areCookiesSupported +} from "./core/CookieMgr"; +export { IDbgExtension } from "./interfaces/ai/IDbgExtension"; +export { getDebugListener, getDebugExt } from "./core/DbgExtensionUtils"; +export { TelemetryInitializerFunction, ITelemetryInitializerHandler, ITelemetryInitializerContainer } from "./interfaces/ai/ITelemetryInitializers"; +export { createUniqueNamespace } from "./utils/DataCacheHelper"; +export { UnloadHandler, IUnloadHandlerContainer, createUnloadHandlerContainer } from "./core/UnloadHandlerContainer"; +export { IUnloadHookContainer, createUnloadHookContainer, _testHookMaxUnloadHooksCb } from "./core/UnloadHookContainer"; +export { ITelemetryUpdateState } from "./interfaces/ai/ITelemetryUpdateState"; +export { ITelemetryUnloadState } from "./interfaces/ai/ITelemetryUnloadState"; +export { IDistributedTraceContext, IDistributedTraceInit } from "./interfaces/ai/IDistributedTraceContext"; +export { ITraceParent } from "./interfaces/ai/ITraceParent"; +export { + createTraceParent, parseTraceParent, isValidTraceId, isValidSpanId, isValidTraceParent, isSampledFlag, formatTraceParent, findW3cTraceParent, + findAllScripts, INVALID_TRACE_ID, INVALID_SPAN_ID, scriptsInfo +} from "./utils/TraceParent"; + +// Dynamic Config definitions +export { IConfigCheckFn, IConfigDefaultCheck, IConfigDefaults, IConfigSetFn } from "./interfaces/config/IConfigDefaults"; +export { IDynamicConfigHandler } from "./interfaces/config/IDynamicConfigHandler"; +export { IDynamicPropertyHandler } from "./interfaces/config/IDynamicPropertyHandler"; +export { IWatchDetails, IWatcherHandler, WatcherFunction } from "./interfaces/config/IDynamicWatcher"; +export { createDynamicConfig, onConfigChange } from "./config/DynamicConfig"; +export { getDynamicConfigHandler, blockDynamicConversion, forceDynamicConversion } from "./config/DynamicSupport"; +export { cfgDfValidate, cfgDfMerge, cfgDfBoolean, cfgDfFunc, cfgDfString, cfgDfSet, cfgDfBlockPropValue } from "./config/ConfigDefaultHelpers"; +// W3c TraceState support +export { eW3CTraceFlags } from "./enums/W3CTraceFlags"; +export { IW3cTraceState } from "./interfaces/ai/IW3cTraceState"; +export { createW3cTraceState, findW3cTraceState, isW3cTraceState, snapshotW3cTraceState } from "./telemetry/W3cTraceState"; // ========================================================================== // OpenTelemetry exports // ========================================================================== -// Context -export { createContextManager } from "./otel/api/context/contextManager"; -export { createContext } from "./otel/api/context/context"; - -// Enums -export { eOTelSamplingDecision, OTelSamplingDecision } from "./enums/otel/OTelSamplingDecision"; -export { eOTelSpanKind, OTelSpanKind } from "./enums/otel/OTelSpanKind"; -export { eOTelSpanStatusCode, OTelSpanStatusCode } from "./enums/otel/OTelSpanStatus"; +// OpenTelemetry Trace support +export { IOTelTraceState } from "./interfaces/otel/trace/IOTelTraceState"; +export { IOTelSpan } from "./interfaces/otel/trace/IOTelSpan"; +export { IOTelTracer } from "./interfaces/otel/trace/IOTelTracer"; +export { IOTelTracerProvider } from "./interfaces/otel/trace/IOTelTracerProvider"; +export { IOTelTracerOptions } from "./interfaces/otel/trace/IOTelTracerOptions"; +export { ITraceProvider, ITraceHost, ISpanScope } from "./interfaces/ai/ITraceProvider"; +export { IOTelSpanOptions } from "./interfaces/otel/trace/IOTelSpanOptions"; +export { createOTelTraceState, isOTelTraceState } from "./otel/api/trace/traceState"; +export { createSpan } from "./otel/api/trace/span"; +export { createTraceProvider } from "./otel/api/trace/traceProvider"; +export { isSpanContext, wrapDistributedTrace, createOTelSpanContext } from "./otel/api/trace/spanContext"; +export { + createNonRecordingSpan, deleteContextSpan, getContextSpan, setContextSpan, setContextSpanContext, getContextActiveSpanContext, isSpanContextValid, + wrapSpanContext, isReadableSpan, isTracingSuppressed, suppressTracing, unsuppressTracing +} from "./otel/api/trace/utils"; -// OpenTelemetry Attribute Support -export { eAttributeChangeOp, AttributeChangeOp } from "./enums/otel/eAttributeChangeOp"; +export { + AzureMonitorSampleRate, ApplicationInsightsCustomEventName, MicrosoftClientIp, ApplicationInsightsMessageName, + ApplicationInsightsExceptionName, ApplicationInsightsPageViewName, ApplicationInsightsAvailabilityName, + ApplicationInsightsEventName, ApplicationInsightsBaseType, ApplicationInsightsMessageBaseType, + ApplicationInsightsExceptionBaseType, ApplicationInsightsPageViewBaseType, ApplicationInsightsAvailabilityBaseType, + ApplicationInsightsEventBaseType, ATTR_ENDUSER_ID, ATTR_ENDUSER_PSEUDO_ID, ATTR_HTTP_ROUTE, SEMATTRS_NET_PEER_IP, + SEMATTRS_NET_PEER_NAME, SEMATTRS_NET_HOST_IP, SEMATTRS_PEER_SERVICE, SEMATTRS_HTTP_USER_AGENT, SEMATTRS_HTTP_METHOD, + SEMATTRS_HTTP_URL, SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_HTTP_ROUTE, SEMATTRS_HTTP_HOST, SEMATTRS_DB_SYSTEM, + SEMATTRS_DB_STATEMENT, SEMATTRS_DB_OPERATION, SEMATTRS_DB_NAME, SEMATTRS_RPC_SYSTEM, SEMATTRS_RPC_GRPC_STATUS_CODE, + SEMATTRS_EXCEPTION_TYPE, SEMATTRS_EXCEPTION_MESSAGE, SEMATTRS_EXCEPTION_STACKTRACE, SEMATTRS_HTTP_SCHEME, + SEMATTRS_HTTP_TARGET, SEMATTRS_HTTP_FLAVOR, SEMATTRS_NET_TRANSPORT, SEMATTRS_NET_HOST_NAME, SEMATTRS_NET_HOST_PORT, + SEMATTRS_NET_PEER_PORT, SEMATTRS_HTTP_CLIENT_IP, SEMATTRS_ENDUSER_ID, ATTR_CLIENT_ADDRESS, ATTR_CLIENT_PORT, + ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, ATTR_URL_PATH, ATTR_URL_QUERY, ATTR_URL_SCHEME, + ATTR_ERROR_TYPE, ATTR_NETWORK_LOCAL_ADDRESS, ATTR_NETWORK_LOCAL_PORT, ATTR_NETWORK_PROTOCOL_NAME, + ATTR_NETWORK_PEER_ADDRESS, ATTR_NETWORK_PEER_PORT, ATTR_NETWORK_PROTOCOL_VERSION, ATTR_NETWORK_TRANSPORT, + ATTR_USER_AGENT_ORIGINAL, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_EXCEPTION_TYPE, + ATTR_EXCEPTION_MESSAGE, ATTR_EXCEPTION_STACKTRACE, EXP_ATTR_ENDUSER_ID, EXP_ATTR_ENDUSER_PSEUDO_ID, + EXP_ATTR_SYNTHETIC_TYPE, DBSYSTEMVALUES_MONGODB, DBSYSTEMVALUES_COSMOSDB, DBSYSTEMVALUES_MYSQL, + DBSYSTEMVALUES_POSTGRESQL, DBSYSTEMVALUES_REDIS, DBSYSTEMVALUES_DB2, DBSYSTEMVALUES_DERBY, DBSYSTEMVALUES_MARIADB, + DBSYSTEMVALUES_MSSQL, DBSYSTEMVALUES_ORACLE, DBSYSTEMVALUES_SQLITE, DBSYSTEMVALUES_OTHER_SQL, DBSYSTEMVALUES_HSQLDB, + DBSYSTEMVALUES_H2 +} from "./otel/attribute/SemanticConventions" + +// OpenTelemetry Core API Interfaces +export { IOTelApi } from "./interfaces/otel/IOTelApi"; +export { IOTelApiCtx } from "./interfaces/otel/IOTelApiCtx"; +export { IOTelAttributes, OTelAttributeValue, ExtendedOTelAttributeValue } from "./interfaces/otel/IOTelAttributes"; +export { OTelException, IOTelExceptionWithCode, IOTelExceptionWithMessage, IOTelExceptionWithName } from "./interfaces/IException"; +export { IOTelHrTime, OTelTimeInput } from "./interfaces/IOTelHrTime"; +export { createOTelApi } from "./otel/api/OTelApi"; +export { OTelSdk } from "./otel/sdk/OTelSdk"; -// --------------------------------------------------------------------------- -// Interfaces -// --------------------------------------------------------------------------- +// OpenTelemetry Trace Interfaces +export { ITraceApi } from "./interfaces/otel/trace/IOTelTraceApi"; +export { IOTelSpanCtx } from "./interfaces/otel/trace/IOTelSpanCtx"; +export { IOTelSpanStatus } from "./interfaces/otel/trace/IOTelSpanStatus"; +export { IReadableSpan } from "./interfaces/otel/trace/IReadableSpan"; +export { IOTelIdGenerator } from "./interfaces/otel/trace/IOTelIdGenerator"; +export { IOTelInstrumentationScope } from "./interfaces/otel/trace/IOTelInstrumentationScope"; +export { IOTelLink } from "./interfaces/otel/trace/IOTelLink"; +export { IOTelTracerCtx } from "./interfaces/otel/trace/IOTelTracerCtx"; +export { IOTelSampler } from "./interfaces/otel/trace/IOTelSampler"; +export { IOTelSamplingResult } from "./interfaces/otel/trace/IOTelSamplingResult"; +export { IOTelSpanContext } from "./interfaces/otel/trace/IOTelSpanContext"; +export { IOTelTimedEvent } from "./interfaces/otel/trace/IOTelTimedEvent"; -// Config -export { IOTelAttributeLimits } from "./interfaces/otel/config/IOTelAttributeLimits"; +// OpenTelemetry Configuration Interfaces export { IOTelConfig } from "./interfaces/otel/config/IOTelConfig"; +export { IOTelAttributeLimits } from "./interfaces/otel/config/IOTelAttributeLimits"; export { IOTelErrorHandlers } from "./interfaces/otel/config/IOTelErrorHandlers"; -export { IOTelTraceCfg } from "./interfaces/otel/config/IOTelTraceCfg"; +export { ITraceCfg } from "./interfaces/otel/config/IOTelTraceCfg"; + +// OpenTelemetry SDK Interfaces +export { IOTelSdk } from "./interfaces/otel/IOTelSdk"; +export { IOTelSdkCtx } from "./interfaces/otel/IOTelSdkCtx"; -// Context +// OpenTelemetry Context +export { createContextManager } from "./otel/api/context/contextManager"; +export { createContext } from "./otel/api/context/context"; export { IOTelContextManager } from "./interfaces/otel/context/IOTelContextManager"; export { IOTelContext } from "./interfaces/otel/context/IOTelContext"; -// Resources +// OpenTelemetry Resources export { IOTelResource, OTelMaybePromise, OTelRawResourceAttribute } from "./interfaces/otel/resources/IOTelResource"; -// Baggage +// OpenTelemetry Baggage export { IOTelBaggage } from "./interfaces/otel/baggage/IOTelBaggage"; export { IOTelBaggageEntry } from "./interfaces/otel/baggage/IOTelBaggageEntry"; export { OTelBaggageEntryMetadata, otelBaggageEntryMetadataSymbol } from "./interfaces/otel/baggage/OTelBaggageEntryMetadata"; -// Trace -export { IOTelIdGenerator } from "./interfaces/otel/trace/IOTelIdGenerator"; -export { IOTelInstrumentationScope } from "./interfaces/otel/trace/IOTelInstrumentationScope"; -export { IOTelLink } from "./interfaces/otel/trace/IOTelLink"; -export { IOTelTracerCtx } from "./interfaces/otel/trace/IOTelTracerCtx"; -export { IOTelTraceState } from "./interfaces/otel/trace/IOTelTraceState"; -export { IReadableSpan } from "./interfaces/otel/trace/IReadableSpan"; -export { IOTelSampler } from "./interfaces/otel/trace/IOTelSampler"; -export { IOTelSamplingResult } from "./interfaces/otel/trace/IOTelSamplingResult"; -export { IOTelSpan } from "./interfaces/otel/trace/IOTelSpan"; -export { IOTelSpanContext } from "./interfaces/otel/trace/IOTelSpanContext"; -export { IOTelSpanOptions } from "./interfaces/otel/trace/IOTelSpanOptions"; -export { IOTelSpanStatus } from "./interfaces/otel/trace/IOTelSpanStatus"; -export { IOTelTimedEvent } from "./interfaces/otel/trace/IOTelTimedEvent"; -export { IOTelTracerProvider } from "./interfaces/otel/trace/IOTelTracerProvider"; -export { IOTelTracer } from "./interfaces/otel/trace/IOTelTracer"; -export { IOTelTracerOptions } from "./interfaces/otel/trace/IOTelTracerOptions"; - -export { IOTelApi } from "./interfaces/otel/IOTelApi"; -export { IOTelApiCtx } from "./interfaces/otel/IOTelApiCtx"; -export { IOTelSdk } from "./interfaces/otel/IOTelSdk"; -export { IOTelSdkCtx } from "./interfaces/otel/IOTelSdkCtx"; +// OpenTelemetry Attribute Support +export { IAttributeContainer, IAttributeChangeInfo } from "./interfaces/otel/attribute/IAttributeContainer"; +export { eAttributeChangeOp, AttributeChangeOp } from "./enums/otel/eAttributeChangeOp"; +export { createAttributeContainer, addAttributes, isAttributeContainer, createAttributeSnapshot } from "./otel/attribute/attributeContainer"; +export { eAttributeFilter, AttributeFilter } from "./interfaces/otel/attribute/IAttributeContainer"; -export { IOTelTraceApi } from "./interfaces/otel/trace/IOTelTraceApi"; -export { IOTelSpanCtx } from "./interfaces/otel/trace/IOTelSpanCtx"; +// OpenTelemetry Enums +export { eOTelSamplingDecision, OTelSamplingDecision } from "./enums/otel/OTelSamplingDecision"; +export { eOTelSpanKind, OTelSpanKind } from "./enums/otel/OTelSpanKind"; +export { eOTelSpanStatusCode, OTelSpanStatusCode } from "./enums/otel/OTelSpanStatus"; -export { createTraceApi } from "./otel/api/trace/traceApi"; +// OpenTelemetry Helper Utilities +export { + hrTime, hrTimeToTimeStamp, hrTimeDuration, hrTimeToMilliseconds, timeInputToHrTime, millisToHrTime, hrTimeToNanoseconds, + addHrTimes, hrTimeToMicroseconds, zeroHrTime, nanosToHrTime, isTimeInput, isTimeInputHrTime +} from "./internal/timeHelpers"; +export { isAttributeValue, isAttributeKey, sanitizeAttributes } from "./internal/attributeHelpers"; +export { + getSyntheticType, isSyntheticSource, serializeAttribute, getUrl, getPeerIp, getHttpMethod, getHttpUrl, getHttpHost, getHttpScheme, + getHttpTarget, getNetPeerName, getNetPeerPort, getUserAgent, getLocationIp, getHttpStatusCode, getHttpClientIp, + getDependencyTarget, isSqlDB +} from "./internal/commonUtils"; -// Trace -export { createNonRecordingSpan } from "./otel/api/trace/nonRecordingSpan"; -export { isSpanContext, wrapDistributedTrace, createOTelSpanContext } from "./otel/api/trace/spanContext"; -export { createTracer } from "./otel/api/trace/tracer"; -export { createOTelTraceState, isOTelTraceState } from "./otel/api/trace/traceState"; +// OpenTelemetry Error Handlers export { - deleteContextSpan, getContextSpan, setContextSpan, setContextSpanContext, getContextActiveSpanContext, isSpanContextValid, wrapSpanContext, - isReadableSpan, isTracingSuppressed, suppressTracing, unsuppressTracing -} from "./otel/api/trace/utils"; + handleAttribError, handleSpanError, handleDebug, handleWarn, handleError, handleNotImplemented +} from "./internal/handleErrors"; // OpenTelemetry Error Classes export { OpenTelemetryError, OpenTelemetryErrorConstructor, getOpenTelemetryError, throwOTelError } from "./otel/api/errors/OTelError"; @@ -89,11 +245,7 @@ export { OTelInvalidAttributeError, throwOTelInvalidAttributeError } from "./ote export { OTelNotImplementedError, throwOTelNotImplementedError } from "./otel/api/errors/OTelNotImplementedError"; export { OTelSpanError, throwOTelSpanError } from "./otel/api/errors/OTelSpanError"; -export { IOTelAttributes, OTelAttributeValue, ExtendedOTelAttributeValue } from "./interfaces/otel/IOTelAttributes"; -export { OTelException, IOTelExceptionWithCode, IOTelExceptionWithMessage, IOTelExceptionWithName } from "./interfaces/IException"; -export { IOTelHrTime, OTelTimeInput } from "./types/time"; - -// Logs +// OpenTelemetry Logs export { IOTelLogger } from "./interfaces/otel/logs/IOTelLogger"; export { IOTelLogRecord, LogBody, LogAttributes } from "./interfaces/otel/logs/IOTelLogRecord"; export { IOTelLogRecordProcessor } from "./interfaces/otel/logs/IOTelLogRecordProcessor"; @@ -110,39 +262,57 @@ export { createLogger } from "./otel/sdk/OTelLogger"; export { createMultiLogRecordProcessor } from "./otel/sdk/OTelMultiLogRecordProcessor"; export { loadDefaultConfig, reconfigureLimits } from "./otel/sdk/config"; -// ========================================================================== -// Application Insights Common exports -// ========================================================================== +// ======================================== +// Application Insights Common Exports +// ======================================== -// Utils +// Utility functions export { - correlationIdSetPrefix, correlationIdGetPrefix, correlationIdCanIncludeCorrelationHeader, correlationIdGetCorrelationContext, - correlationIdGetCorrelationContextValue, dateTimeUtilsNow, dateTimeUtilsDuration, isInternalApplicationInsightsEndpoint + correlationIdSetPrefix, correlationIdGetPrefix, correlationIdCanIncludeCorrelationHeader, + correlationIdGetCorrelationContext, correlationIdGetCorrelationContextValue, + dateTimeUtilsNow, dateTimeUtilsDuration, isInternalApplicationInsightsEndpoint, + createDistributedTraceContextFromTrace } from "./utils/Util"; + +export { ThrottleMgr } from "./diagnostics/ThrottleMgr"; export { parseConnectionString, ConnectionStringParser } from "./telemetry/ConnectionStringParser"; export { ConnectionString } from "./interfaces/ai/ConnectionString"; export { FieldType } from "./enums/ai/Enums"; export { IRequestHeaders, RequestHeaders, eRequestHeaders } from "./telemetry/RequestResponseHeaders"; export { - DisabledPropertyName, ProcessLegacy, SampleRate, HttpMethod, DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, strNotSpecified, - STR_EVENTS_DISCARDED, STR_EVENTS_SEND_REQUEST, STR_EVENTS_SENT, STR_PERF_EVENT, STR_OFFLINE_DROP, STR_OFFLINE_SENT, - STR_OFFLINE_STORE, STR_GET_PERF_MGR, STR_CORE, STR_DISABLED, STR_PRIORITY, STR_PROCESS_TELEMETRY + DisabledPropertyName, ProcessLegacy, SampleRate, HttpMethod, + DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, strNotSpecified, + ChannelControllerPriority } from "./constants/Constants"; // Contracts export { IData as AIData } from "./interfaces/ai/contracts/IData"; export { IBase as AIBase } from "./interfaces/ai/contracts/IBase"; +export { IDomain } from "./interfaces/ai/contracts/IDomain"; export { ISerializable } from "./interfaces/ai/telemetry/ISerializable"; export { IEnvelope } from "./interfaces/ai/telemetry/IEnvelope"; - -// Telemetry +export { IStackFrame } from "./interfaces/ai/contracts/IStackFrame"; +export { IExceptionDetails } from "./interfaces/ai/contracts/IExceptionDetails"; +export { IExceptionData } from "./interfaces/ai/contracts/IExceptionData"; +export { IEventData } from "./interfaces/ai/contracts/IEventData"; +export { IMessageData } from "./interfaces/ai/contracts/IMessageData"; +export { IMetricData } from "./interfaces/ai/contracts/IMetricData"; +export { IDataPoint } from "./interfaces/ai/contracts/IDataPoint"; +export { DataPointType } from "./interfaces/ai/contracts/DataPointType"; +export { IPageViewPerfData } from "./interfaces/ai/contracts/IPageViewPerfData"; + +// Telemetry classes export { Envelope } from "./telemetry/ai/Common/Envelope"; export { Event } from "./telemetry/ai/Event"; export { Exception } from "./telemetry/ai/Exception"; export { Metric } from "./telemetry/ai/Metric"; export { PageView } from "./telemetry/ai/PageView"; export { IPageViewData } from "./interfaces/ai/contracts/IPageViewData"; -export { RemoteDependencyData } from "./telemetry/ai/RemoteDependencyData"; +export { IRemoteDependencyData } from "./interfaces/ai/contracts/IRemoteDependencyData"; +export { Trace } from "./telemetry/ai/Trace"; +export { PageViewPerformance } from "./telemetry/ai/PageViewPerformance"; + +// Telemetry interfaces export { IEventTelemetry } from "./interfaces/ai/IEventTelemetry"; export { ITraceTelemetry } from "./interfaces/ai/ITraceTelemetry"; export { IMetricTelemetry } from "./interfaces/ai/IMetricTelemetry"; @@ -150,220 +320,94 @@ export { IDependencyTelemetry } from "./interfaces/ai/IDependencyTelemetry"; export { IExceptionTelemetry, IAutoExceptionTelemetry, IExceptionInternal } from "./interfaces/ai/IExceptionTelemetry"; export { IPageViewTelemetry, IPageViewTelemetryInternal } from "./interfaces/ai/IPageViewTelemetry"; export { IPageViewPerformanceTelemetry, IPageViewPerformanceTelemetryInternal } from "./interfaces/ai/IPageViewPerformanceTelemetry"; -export { ITelemetryContext } from "./interfaces/ai/ITelemetryContext"; -export { Trace } from "./telemetry/ai/Trace"; -export { PageViewPerformance } from "./telemetry/ai/PageViewPerformance"; -export { Data } from "./telemetry/ai/Common/Data"; +export { IRequestTelemetry } from "./interfaces/ai/IRequestTelemetry"; + +// Severity level export { eSeverityLevel, SeverityLevel } from "./interfaces/ai/contracts/SeverityLevel"; + +// Configuration export { IConfig, ConfigurationManager } from "./interfaces/ai/IConfig"; export { IStorageBuffer } from "./interfaces/ai/IStorageBuffer"; -export { IContextTagKeys, ContextTagKeys } from "./interfaces/ai/contracts/ContextTagKeys"; -export { Extensions, CtxTagKeys } from "./interfaces/ai/PartAExtensions"; -export { - DataSanitizerValues, - dataSanitizeKeyAndAddUniqueness, dataSanitizeKey, dataSanitizeString, dataSanitizeUrl, dataSanitizeMessage, - dataSanitizeException, dataSanitizeProperties, dataSanitizeMeasurements, dataSanitizeId, dataSanitizeInput, - dsPadNumber -} from "./telemetry/ai/Common/DataSanitizer"; -export { TelemetryItemCreator, createTelemetryItem } from "./telemetry/TelemetryItemCreator"; export { ICorrelationConfig } from "./interfaces/ai/ICorrelationConfig"; -export { IAppInsights } from "./interfaces/ai/IAppInsights"; -export { eDistributedTracingModes, DistributedTracingModes, EventPersistence } from "./enums/ai/Enums"; -export { stringToBoolOrDefault, msToTimeSpan, getExtensionByName, isCrossOriginError } from "./utils/HelperFuncs"; -export { createDomEvent } from "./utils/DomHelperFuncs"; -export { - utlDisableStorage, utlEnableStorage, utlCanUseLocalStorage, utlGetLocalStorage, utlSetLocalStorage, utlRemoveStorage, - utlCanUseSessionStorage, utlGetSessionStorageKeys, utlGetSessionStorage, utlSetSessionStorage, utlRemoveSessionStorage, utlSetStoragePrefix -} from "./utils/StorageHelperFuncs"; -export { urlParseUrl, urlGetAbsoluteUrl, urlGetPathName, urlGetCompleteUrl, urlParseHost, urlParseFullHost } from "./utils/UrlHelperFuncs"; -export { IThrottleLimit, IThrottleInterval, IThrottleMgrConfig, IThrottleLocalStorageObj, IThrottleResult } from "./interfaces/ai/IThrottleMgr"; -export { IOfflineListener, createOfflineListener, IOfflineState, eOfflineValue, OfflineCallback } from "./utils/Offline"; -export { - createTraceParent, parseTraceParent, isValidTraceId, isValidSpanId, isValidTraceParent, isSampledFlag, formatTraceParent, - findW3cTraceParent, findAllScripts, createDistributedTraceContextFromTrace, INVALID_TRACE_ID, INVALID_SPAN_ID, scriptsInfo -} from "./utils/TraceParent"; -// Config-related exports -export { IConfigCheckFn, IConfigDefaultCheck, IConfigDefaults, IConfigSetFn } from "./interfaces/config/IConfigDefaults"; -export { IDynamicConfigHandler, _IInternalDynamicConfigHandler } from "./interfaces/config/IDynamicConfigHandler"; -export { IDynamicPropertyHandler } from "./interfaces/config/IDynamicPropertyHandler"; -export { IWatchDetails, IWatcherHandler, WatcherFunction, _IDynamicDetail } from "./interfaces/config/IDynamicWatcher"; -export { _IDynamicConfigHandlerState, _IDynamicGetter } from "./interfaces/config/_IDynamicConfigHandlerState"; - -// Enum utilities -export { createEnumStyle } from "./enums/EnumHelperFuncs"; - -// W3C Trace-related exports -export { eW3CTraceFlags } from "./enums/W3CTraceFlags"; -export { IDistributedTraceContext } from "./interfaces/ai/IDistributedTraceContext"; -export { ITraceParent } from "./interfaces/ai/ITraceParent"; -export { IW3cTraceState } from "./interfaces/ai/IW3cTraceState"; -export { eTraceHeadersMode } from "./enums/ai/TraceHeadersMode"; +// Context tags and keys +export { IContextTagKeys, ContextTagKeys } from "./interfaces/ai/contracts/ContextTagKeys"; +export { CtxTagKeys, Extensions } from "./interfaces/ai/PartAExtensions"; -// Stats and telemetry interfaces -export { INetworkStatsbeat } from "./interfaces/ai/INetworkStatsbeat"; -export { IStatsBeat, IStatsBeatState, IStatsBeatKeyMap, IStatsBeatConfig, IStatsEndpointConfig } from "./interfaces/ai/IStatsBeat"; -export { IStatsMgr, IStatsMgrConfig } from "./interfaces/ai/IStatsMgr"; -export { eStatsType } from "./enums/ai/StatsType"; +// Data types and envelope types +export { + EventDataType, ExceptionDataType, MetricDataType, PageViewDataType, + PageViewPerformanceDataType, RemoteDependencyDataType, RequestDataType, TraceDataType +} from "./telemetry/ai/DataTypes"; -// Utility functions export { - getCrypto, getMsCrypto, getLocation, hasJSON, getJSON, - isReactNative, getConsole, isIE, getIEVersion, isSafari, - setEnableEnvMocks, isBeaconsSupported, isFetchSupported, useXDomainRequest, isXhrSupported, - findMetaTag, findNamedServerTiming, sendCustomEvent, dispatchEvent, createCustomDomEvent, fieldRedaction, - findMetaTags, findNamedServerTimings -} from "./utils/EnvUtils"; -export { isBeaconsSupported as isBeaconApiSupported } from "./utils/EnvUtils"; + EventEnvelopeType, ExceptionEnvelopeType, MetricEnvelopeType, PageViewEnvelopeType, + PageViewPerformanceEnvelopeType, RemoteDependencyEnvelopeType, RequestEnvelopeType, TraceEnvelopeType +} from "./telemetry/ai/EnvelopeTypes"; -// Core helper functions +// Data sanitization export { - normalizeJsName, toISOString, getExceptionName, strContains, setValue, getSetValue, - proxyAssign, proxyFunctions, proxyFunctionAs, createClassFromInterface, optimizeObject, - isNotUndefined, isNotNullOrUndefined, objExtend, isFeatureEnabled, getResponseText, formatErrorMessageXdr, formatErrorMessageXhr, prependTransports, - openXhr, _appendHeader, _getAllResponseHeaders, convertAllHeadersToMap, setObjStringTag, setProtoTypeName, _getObjProto -} from "./utils/HelperFuncsCore"; -export { randomValue, random32, mwcRandomSeed, mwcRandom32, newId } from "./utils/RandomHelper"; + DataSanitizerValues, dataSanitizeKeyAndAddUniqueness, dataSanitizeKey, dataSanitizeString, + dataSanitizeUrl, dataSanitizeMessage, dataSanitizeException, dataSanitizeProperties, + dataSanitizeMeasurements, dataSanitizeId, dataSanitizeInput, dsPadNumber +} from "./telemetry/ai/Common/DataSanitizer"; -// Core interfaces -export { IAppInsightsCore } from "./interfaces/ai/IAppInsightsCore"; -export { IConfiguration } from "./interfaces/ai/IConfiguration"; -export { IDiagnosticLogger } from "./interfaces/ai/IDiagnosticLogger"; +// Telemetry item creator +export { TelemetryItemCreator, createTelemetryItem } from "./telemetry/TelemetryItemCreator"; + +// Application Insights interfaces +export { IAppInsights } from "./interfaces/ai/IAppInsights"; +export { ITelemetryContext } from "./interfaces/ai/ITelemetryContext"; +export { IPropertiesPlugin } from "./interfaces/ai/IPropertiesPlugin"; +export { IRequestContext } from "./interfaces/ai/IRequestContext"; + +// Context interfaces +export { IWeb } from "./interfaces/ai/context/IWeb"; +export { ISession } from "./interfaces/ai/context/ISession"; +export { ISessionManager } from "./interfaces/ai/context/ISessionManager"; export { IApplication } from "./interfaces/ai/context/IApplication"; export { IDevice } from "./interfaces/ai/context/IDevice"; export { IInternal } from "./interfaces/ai/context/IInternal"; export { ILocation } from "./interfaces/ai/context/ILocation"; -export { IOperatingSystem } from "./interfaces/ai/context/IOperatingSystem"; -export { ISession } from "./interfaces/ai/context/ISession"; -export { ISessionManager } from "./interfaces/ai/context/ISessionManager"; export { ISample } from "./interfaces/ai/context/ISample"; -export { ITelemetryTrace } from "./interfaces/ai/context/ITelemetryTrace"; +export { IOperatingSystem } from "./interfaces/ai/context/IOperatingSystem"; export { IUser, IUserContext } from "./interfaces/ai/context/IUser"; -export { IWeb } from "./interfaces/ai/context/IWeb"; -export { IPropertiesPlugin } from "./interfaces/ai/IPropertiesPlugin"; - -// Channel and plugin interfaces -export { IChannelControls, MinChannelPriorty, IInternalOfflineSupport } from "./interfaces/ai/IChannelControls"; -export { IChannelControlsHost } from "./interfaces/ai/IChannelControlsHost"; -export { ITelemetryPlugin, IPlugin } from "./interfaces/ai/ITelemetryPlugin"; -export { IExceptionConfig } from "./interfaces/ai/IExceptionConfig"; -export { ILoadedPlugin } from "./interfaces/ai/IAppInsightsCore"; -export { ITelemetryItem, ICustomProperties, Tags } from "./interfaces/ai/ITelemetryItem"; -export { IBaseProcessingContext, IProcessTelemetryContext, IProcessTelemetryUnloadContext, IProcessTelemetryUpdateContext } from "./interfaces/ai/IProcessTelemetryContext"; -export { INotificationListener } from "./interfaces/ai/INotificationListener"; -export { ITelemetryPluginChain } from "./interfaces/ai/ITelemetryPluginChain"; -export { InstrumentorHooksCallback, IInstrumentHooksCallbacks, IInstrumentHooks, IInstrumentHook, IInstrumentCallDetails } from "./interfaces/ai/IInstrumentHooks"; -export { IUnloadableComponent } from "./interfaces/ai/IUnloadableComponent"; -export { IPayloadData, SendPOSTFunction, IXHROverride, OnCompleteCallback } from "./interfaces/ai/IXHROverride"; -export { IUnloadHook, ILegacyUnloadHook } from "./interfaces/ai/IUnloadHook"; -export { IXDomainRequest, IBackendResponse } from "./interfaces/ai/IXDomainRequest"; -export { _ISenderOnComplete, _ISendPostMgrConfig, _ITimeoutOverrideWrapper, _IInternalXhrOverride } from "./interfaces/ai/ISenderPostManager"; -export { INotificationManager } from "./interfaces/ai/INotificationManager"; -export { IPerfEvent } from "./interfaces/ai/IPerfEvent"; -export { IPerfManager, IPerfManagerProvider } from "./interfaces/ai/IPerfManager"; -export { IFeatureOptInDetails, IFeatureOptIn } from "./interfaces/ai/IFeatureOptIn"; -export { ICookieMgr, ICookieMgrConfig } from "./interfaces/ai/ICookieMgr"; -export { IDbgExtension } from "./interfaces/ai/IDbgExtension"; -export { TelemetryInitializerFunction, ITelemetryInitializerHandler, ITelemetryInitializerContainer } from "./interfaces/ai/ITelemetryInitializers"; -export { ITelemetryUpdateState } from "./interfaces/ai/ITelemetryUpdateState"; -export { ITelemetryUnloadState } from "./interfaces/ai/ITelemetryUnloadState"; -export { IRequestContext } from "./interfaces/ai/IRequestContext"; +export { ITelemetryTrace } from "./interfaces/ai/context/ITelemetryTrace"; // Enums -export { eEventsDiscardedReason, EventsDiscardedReason, eBatchDiscardedReason, BatchDiscardedReason } from "./enums/ai/EventsDiscardedReason"; -export { SendRequestReason, TransportType } from "./enums/ai/SendRequestReason"; -export { TelemetryUpdateReason } from "./enums/ai/TelemetryUpdateReason"; -export { TelemetryUnloadReason } from "./enums/ai/TelemetryUnloadReason"; -export { eActiveStatus, ActiveStatus } from "./enums/ai/InitActiveStatusEnum"; -export { _eInternalMessageId, _InternalMessageId, LoggingSeverity, eLoggingSeverity } from "./enums/ai/LoggingEnums"; -export { FeatureOptInMode, CdnFeatureMode } from "./enums/ai/FeatureOptInEnums"; - -// DataCache helper -export { createUniqueNamespace, createElmNodeData } from "./utils/DataCacheHelper"; - -// Plugin identifiers -export const PropertiesPluginIdentifier = "AppInsightsPropertiesPlugin"; -export const BreezeChannelIdentifier = "AppInsightsChannelPlugin"; -export const AnalyticsPluginIdentifier = "ApplicationInsightsAnalytics"; - -// W3c TraceState support -export { createW3cTraceState, findW3cTraceState, isW3cTraceState, snapshotW3cTraceState } from "./telemetry/W3cTraceState"; - -// Core Utils -export { Undefined, newGuid, generateW3CId } from "./utils/CoreUtils"; - -// ========================================================================== -// AppInsightsCore exports (merged from @microsoft/otel-core-js) -// ========================================================================== +export { eDistributedTracingModes, DistributedTracingModes, EventPersistence } from "./enums/ai/Enums"; +export { eTraceHeadersMode } from "./enums/ai/TraceHeadersMode"; -// Core classes -export { throwAggregationError } from "./core/AggregationError"; -export { AppInsightsCore } from "./core/AppInsightsCore"; -export { BaseTelemetryPlugin } from "./core/BaseTelemetryPlugin"; -export { runTargetUnload, doUnloadAll } from "./core/AsyncUtils"; -export { parseResponse } from "./core/ResponseHelpers"; -export { SenderPostManager } from "./core/SenderPostManager"; +// Helper functions +export { stringToBoolOrDefault, msToTimeSpan, getExtensionByName, isCrossOriginError } from "./utils/HelperFuncs"; +export { createDomEvent } from "./utils/DomHelperFuncs"; -// Event helpers +// Storage helpers export { - attachEvent, detachEvent, addEventHandler, addEventListeners, addPageUnloadEventListener, addPageHideEventListener, addPageShowEventListener, - removeEventHandler, removeEventListeners, removePageUnloadEventListener, removePageHideEventListener, removePageShowEventListener, eventOn, eventOff, - mergeEvtNamespace, _IRegisteredEvents, __getRegisteredEvents -} from "./internal/EventHelpers"; - -// NotificationManager -export { NotificationManager } from "./core/NotificationManager"; - -// PerfManager -export { PerfEvent, PerfManager, doPerf, getGblPerfMgr, setGblPerfMgr } from "./core/PerfManager"; - -// DiagnosticLogger -export { safeGetLogger, DiagnosticLogger, _InternalLogMessage, _warnToConsole, _logInternalMessage, _throwInternal } from "./diagnostics/DiagnosticLogger"; - -// ProcessTelemetryContext -export { ProcessTelemetryContext, createProcessTelemetryContext } from "./core/ProcessTelemetryContext"; - -// TelemetryHelpers -export { initializePlugins, sortPlugins, unloadComponents, createDistributedTraceContext } from "./core/TelemetryHelpers"; - -// InstrumentHooks -export { InstrumentProto, InstrumentProtos, InstrumentFunc, InstrumentFuncs, InstrumentEvent } from "./core/InstrumentHooks"; - -// CookieMgr -export { createCookieMgr, safeGetCookieMgr, uaDisallowsSameSiteNone, areCookiesSupported } from "./core/CookieMgr"; - -// DbgExtensionUtils -export { getDebugListener, getDebugExt } from "./core/DbgExtensionUtils"; - -// UnloadHandlerContainer -export { UnloadHandler, IUnloadHandlerContainer, createUnloadHandlerContainer } from "./core/UnloadHandlerContainer"; - -// UnloadHookContainer -export { IUnloadHookContainer, createUnloadHookContainer, _testHookMaxUnloadHooksCb } from "./core/UnloadHookContainer"; - -// ThrottleMgr -export { ThrottleMgr } from "./diagnostics/ThrottleMgr"; + utlDisableStorage, utlEnableStorage, utlCanUseLocalStorage, utlGetLocalStorage, + utlSetLocalStorage, utlRemoveStorage, utlCanUseSessionStorage, utlGetSessionStorageKeys, + utlGetSessionStorage, utlSetSessionStorage, utlRemoveSessionStorage, utlSetStoragePrefix +} from "./utils/StorageHelperFuncs"; -// Dynamic Config definitions -export { createDynamicConfig, onConfigChange } from "./config/DynamicConfig"; -export { getDynamicConfigHandler, blockDynamicConversion, forceDynamicConversion } from "./config/DynamicSupport"; -export { cfgDfValidate, cfgDfMerge, cfgDfBoolean, cfgDfFunc, cfgDfString, cfgDfSet, cfgDfBlockPropValue } from "./config/ConfigDefaultHelpers"; +// URL helpers +export { + urlParseUrl, urlGetAbsoluteUrl, urlGetPathName, urlGetCompleteUrl, + urlParseHost, urlParseFullHost +} from "./utils/UrlHelperFuncs"; -// Re-exports from ts-utils for convenience +// Throttle manager interfaces export { - isArray, isTypeof, isUndefined, isNullOrUndefined, objHasOwnProperty as hasOwnProperty, isObject, isFunction, - strEndsWith, strStartsWith, isDate, isError, isString, isNumber, isBoolean, arrForEach, arrIndexOf, - arrReduce, arrMap, strTrim, objKeys, objDefineAccessors, throwError, isSymbol, - isNotTruthy, isTruthy, objFreeze, objSeal, objToString, objDeepFreeze as deepFreeze, - getInst as getGlobalInst, hasWindow, getWindow, hasDocument, getDocument, hasNavigator, getNavigator, hasHistory, - getHistory, dumpObj, asString, objForEachKey, getPerformance, utcNow as dateNow, perfNow -} from "@nevware21/ts-utils"; + IThrottleLimit, IThrottleInterval, IThrottleMgrConfig, + IThrottleLocalStorageObj, IThrottleResult +} from "./interfaces/ai/IThrottleMgr"; -// Re-exports from shims for convenience +// Offline support export { - getGlobal, - strShimPrototype as strPrototype, - strShimFunction as strFunction, - strShimUndefined as strUndefined, - strShimObject as strObject -} from "@microsoft/applicationinsights-shims"; + IOfflineListener, createOfflineListener, IOfflineState, + eOfflineValue, OfflineCallback +} from "./utils/Offline"; + +// Plugin identifiers +export const PropertiesPluginIdentifier = "AppInsightsPropertiesPlugin"; +export const BreezeChannelIdentifier = "AppInsightsChannelPlugin"; +export const AnalyticsPluginIdentifier = "ApplicationInsightsAnalytics"; diff --git a/shared/otel-core/src/interfaces/IOTelHrTime.ts b/shared/otel-core/src/interfaces/IOTelHrTime.ts index 4de35499c..b2b329ff8 100644 --- a/shared/otel-core/src/interfaces/IOTelHrTime.ts +++ b/shared/otel-core/src/interfaces/IOTelHrTime.ts @@ -1,10 +1,81 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/** + * High-resolution time represented as a tuple of [seconds, nanoseconds]. + * This is the base type for all OpenTelemetry high-resolution time values. + * + * @remarks + * The first element represents seconds since Unix epoch, and the second element + * represents nanoseconds (0-999,999,999) within that second. + * + * @example + * ```typescript + * const hrTime: OTelHrTimeBase = [1609459200, 500000000]; // 2021-01-01 00:00:00.5 UTC + * ``` + * + * @since 3.4.0 + */ export type OTelHrTimeBase = [number, number]; +/** + * Enhanced high-resolution time interface that extends the base tuple with additional properties. + * Provides a more structured way to work with high-resolution timestamps. + * + * @example + * ```typescript + * const hrTime: IOTelHrTime = { + * 0: 1609459200, // seconds since Unix epoch + * 1: 500000000, // nanoseconds (0-999,999,999) + * }; + * ``` + * + * @since 3.4.0 + */ export interface IOTelHrTime extends OTelHrTimeBase { + /** + * Seconds since Unix epoch (January 1, 1970 00:00:00 UTC). + * Must be a non-negative integer. + */ 0: number; + + /** + * Nanoseconds within the second specified by index 0. + * Must be in the range [0, 999999999]. + */ 1: number; - unixNano?: number; + + /** + * Optional total nanoseconds since Unix epoch. + * When provided, this should be equivalent to (this[0] * 1e9) + this[1]. + * + * @remarks + * This field may be used for more efficient time calculations or when + * working with systems that natively use nanosecond timestamps. + */ + // unixNano?: number; } + +/** + * Union type representing all valid time input formats accepted by OpenTelemetry APIs. + * + * @remarks + * - `IOTelHrTime`: High-resolution time with nanosecond precision + * - `number`: Milliseconds since Unix epoch (JavaScript Date.now() format) + * - `Date`: JavaScript Date object + * + * @example + * ```typescript + * // All of these are valid time inputs: + * const hrTime: OTelTimeInput = [1609459200, 500000000]; + * const msTime: OTelTimeInput = Date.now(); + * const dateTime: OTelTimeInput = new Date(); + * + * span.addEvent("event", {}, hrTime); + * span.addEvent("event", {}, msTime); + * span.addEvent("event", {}, dateTime); + * ``` + * + * @since 3.4.0 + */ +export type OTelTimeInput = IOTelHrTime | number | Date; diff --git a/shared/otel-core/src/interfaces/ai/IAppInsightsCore.ts b/shared/otel-core/src/interfaces/ai/IAppInsightsCore.ts index 52892023a..198b6ee0e 100644 --- a/shared/otel-core/src/interfaces/ai/IAppInsightsCore.ts +++ b/shared/otel-core/src/interfaces/ai/IAppInsightsCore.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { IPromise } from "@nevware21/ts-async"; -import { ITimerHandler } from "@nevware21/ts-utils"; +import { ICachedValue, ITimerHandler } from "@nevware21/ts-utils"; import { UnloadHandler } from "../../core/UnloadHandlerContainer"; import { eActiveStatus } from "../../enums/ai/InitActiveStatusEnum"; import { SendRequestReason } from "../../enums/ai/SendRequestReason"; @@ -11,7 +11,6 @@ import { IChannelControls } from "./IChannelControls"; import { IConfiguration } from "./IConfiguration"; import { ICookieMgr } from "./ICookieMgr"; import { IDiagnosticLogger } from "./IDiagnosticLogger"; -import { IDistributedTraceContext } from "./IDistributedTraceContext"; import { INotificationListener } from "./INotificationListener"; import { INotificationManager } from "./INotificationManager"; import { IPerfManagerProvider } from "./IPerfManager"; @@ -20,6 +19,7 @@ import { ITelemetryInitializerHandler, TelemetryInitializerFunction } from "./IT import { ITelemetryItem } from "./ITelemetryItem"; import { IPlugin, ITelemetryPlugin } from "./ITelemetryPlugin"; import { ITelemetryUnloadState } from "./ITelemetryUnloadState"; +import { ITraceHost, ITraceProvider } from "./ITraceProvider"; import { ILegacyUnloadHook, IUnloadHook } from "./IUnloadHook"; // import { IStatsBeat, IStatsBeatState } from "./IStatsBeat"; @@ -45,12 +45,7 @@ export interface ILoadedPlugin { remove: (isAsync?: boolean, removeCb?: (removed?: boolean) => void) => void; } -export interface IAppInsightsCore extends IPerfManagerProvider { - - /* - * Config object used to initialize AppInsights - */ - readonly config: CfgType; +export interface IAppInsightsCore extends IPerfManagerProvider, ITraceHost { /** * The current logger instance for this instance. @@ -66,12 +61,6 @@ export interface IAppInsightsCore void, sendReason?: SendRequestReason, cbTimeout?: number): boolean | void; /** - * Gets the current distributed trace active context for this instance - * @param createNew - Optional flag to create a new instance if one doesn't currently exist, defaults to true - */ - getTraceCtx(createNew?: boolean): IDistributedTraceContext | null; - - /** - * Sets the current distributed trace context for this instance if available + * Set the trace provider for creating spans. + * This allows different SKUs to provide their own span implementations. + * + * @param provider - The trace provider to use for span creation + * @since 3.4.0 */ - setTraceCtx(newTraceCtx: IDistributedTraceContext | null | undefined): void; + setTraceProvider(provider: ICachedValue): void; /** * Watches and tracks changes for accesses to the current config, and if the accessed config changes the diff --git a/shared/otel-core/src/interfaces/ai/IConfiguration.ts b/shared/otel-core/src/interfaces/ai/IConfiguration.ts index 24ce35d61..c3db75b74 100644 --- a/shared/otel-core/src/interfaces/ai/IConfiguration.ts +++ b/shared/otel-core/src/interfaces/ai/IConfiguration.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { IPromise } from "@nevware21/ts-async"; import { eTraceHeadersMode } from "../../enums/ai/TraceHeadersMode"; +import { IOTelConfig } from "../otel/config/IOTelConfig"; import { IAppInsightsCore } from "./IAppInsightsCore"; import { IChannelControls } from "./IChannelControls"; import { ICookieMgrConfig } from "./ICookieMgr"; @@ -14,7 +15,7 @@ import { ITelemetryPlugin } from "./ITelemetryPlugin"; /** * Configuration provided to SDK core */ -export interface IConfiguration { +export interface IConfiguration extends IOTelConfig { /** * Instrumentation key of resource. Either this or connectionString must be specified. */ @@ -257,13 +258,6 @@ export interface IConfiguration { * @defaultValue eTraceHeadersMode.All */ traceHdrMode?: eTraceHeadersMode; - - // TODO: Add IOTelConfig type back - /** - * [Optional] OpenTelemetry specific configuration - * @since 4.0.0 - */ - otelCfg?: any } ///** diff --git a/shared/otel-core/src/interfaces/ai/ICookieMgr.ts b/shared/otel-core/src/interfaces/ai/ICookieMgr.ts index 198b4ba99..0a727e8bd 100644 --- a/shared/otel-core/src/interfaces/ai/ICookieMgr.ts +++ b/shared/otel-core/src/interfaces/ai/ICookieMgr.ts @@ -134,4 +134,4 @@ export interface ICookieMgrConfig { * @since v3.3.10 */ disableCookieDefer?: boolean; -} +} \ No newline at end of file diff --git a/shared/otel-core/src/interfaces/ai/ICorrelationConfig.ts b/shared/otel-core/src/interfaces/ai/ICorrelationConfig.ts index c606544d9..4d6d2b337 100644 --- a/shared/otel-core/src/interfaces/ai/ICorrelationConfig.ts +++ b/shared/otel-core/src/interfaces/ai/ICorrelationConfig.ts @@ -70,7 +70,7 @@ export interface ICorrelationConfig { * This is used to determine which headers are sent with requests and how the * telemetry is correlated across services. * @default AI_AND_W3C - * @see {@link DistributedTracingModes} + * @see {@link eDistributedTracingModes} */ distributedTracingMode: DistributedTracingModes; diff --git a/shared/otel-core/src/interfaces/ai/IDiagnosticLogger.ts b/shared/otel-core/src/interfaces/ai/IDiagnosticLogger.ts index ff4567166..54ce5c17d 100644 --- a/shared/otel-core/src/interfaces/ai/IDiagnosticLogger.ts +++ b/shared/otel-core/src/interfaces/ai/IDiagnosticLogger.ts @@ -77,4 +77,9 @@ export interface IDiagnosticLogger { * / Promise to allow any listeners to wait for the operation to complete. */ unload?(isAsync?: boolean): void | IPromise; + + /** + * A flag that indicates whether this logger is in debug (throw real exceptions) mode + */ + readonly dbgMode?: boolean; } diff --git a/shared/otel-core/src/interfaces/ai/IDistributedTraceContext.ts b/shared/otel-core/src/interfaces/ai/IDistributedTraceContext.ts index 3778a766e..a19d0be4e 100644 --- a/shared/otel-core/src/interfaces/ai/IDistributedTraceContext.ts +++ b/shared/otel-core/src/interfaces/ai/IDistributedTraceContext.ts @@ -1,9 +1,188 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IOTelTraceState } from "../otel/trace/IOTelTraceState"; import { IW3cTraceState } from "./IW3cTraceState"; -export interface IDistributedTraceContext { +/** + * An object that can be used to populate a new {@link IDistributedTraceContext} instance, + * the included {@link IW3cTraceState} or {@link IOTelTraceState} is used as the parent of the + * created instances traceState + */ +export interface IDistributedTraceInit { + /** + * The unique identifier for the trace that this span belongs to. + * + * The trace ID is a globally unique identifier that connects all spans within a single + * distributed trace. It consists of 16 randomly generated bytes encoded as 32 lowercase + * hexadecimal characters, providing 128 bits of entropy to ensure worldwide uniqueness + * with practically sufficient probability. + * + * @remarks + * - Must be exactly 32 lowercase hexadecimal characters + * - Represents 128 bits (16 bytes) of random data + * - Shared by all spans within the same trace + * - Used for trace correlation across distributed systems + * - Should never be all zeros (invalid trace ID) + * + * @example + * ```typescript + * // Example trace ID format + * const traceId = "4bf92f3577b34da6a3ce929d0e0e4736"; + * + * // All spans in the same trace share this ID + * console.log(parentSpan.spanContext().traceId === childSpan.spanContext().traceId); // true + * ``` + */ + traceId: string; + + /** + * The unique identifier for this specific span within the trace. + * + * The span ID uniquely identifies this span within the trace and is used to establish + * parent-child relationships between spans. It consists of 8 randomly generated bytes + * encoded as 16 lowercase hexadecimal characters, providing 64 bits of entropy to + * ensure global uniqueness with practically sufficient probability. + * + * @remarks + * - Must be exactly 16 lowercase hexadecimal characters + * - Represents 64 bits (8 bytes) of random data + * - Unique within the trace (different spans have different span IDs) + * - Used as parent ID when creating child spans + * - Should never be all zeros (invalid span ID) + * + * @example + * ```typescript + * // Example span ID format + * const spanId = "00f067aa0ba902b7"; + * + * // Each span has a unique ID within the trace + * const parentId = parentSpan.spanContext().spanId; // "00f067aa0ba902b7" + * const childId = childSpan.spanContext().spanId; // "b9c7c989f97918e1" + * + * // Child span uses parent's span ID as its parent ID + * console.log(childSpan.parentSpanId === parentId); // true + * ``` + */ + spanId: string; + + /** + * Indicates whether this span context was propagated from a remote parent span. + * + * This flag distinguishes between spans created locally within the same process + * and spans that represent operations in remote services. Remote spans are typically + * created when trace context is received via HTTP headers, message queues, or other + * inter-process communication mechanisms. + * + * @defaultValue false - spans are considered local unless explicitly marked as remote + * + * @remarks + * - True only when span context was received from another process/service + * - Helps distinguish local vs. distributed trace segments + * - Used by tracing systems for visualization and analysis + * - Local child spans of remote parents are NOT considered remote themselves + * + * @example + * ```typescript + * // HTTP service receiving trace context + * const incomingSpanContext = extractSpanContextFromHeaders(request.headers); + * console.log(incomingSpanContext.isRemote); // true + * + * // Child span created locally + * const localChild = tracer.startSpan('local-operation', { + * parent: incomingSpanContext + * }); + * console.log(localChild.spanContext().isRemote); // false + * ``` + */ + isRemote?: boolean; + + /** + * Trace flags that control trace behavior and indicate sampling decisions. + * + * The trace flags are represented as a single byte (8-bit bitmap) that carries + * trace-level information. The least significant bit (0x01) indicates whether + * the trace is sampled. When this bit is set, it documents that the caller + * may have recorded trace data. Additional bits are reserved for future use + * and should be ignored when not understood. + * + * @remarks + * - Represented as a number (0-255) corresponding to 8 bits + * - Bit 0 (0x01): Sampled flag - indicates trace may contain recorded data + * - Bits 1-7: Reserved for future use, should be preserved during propagation + * - Used by sampling algorithms to make consistent decisions across services + * - See {@link eW3CTraceFlags} for standard flag values + * + * @example + * ```typescript + * // Check if trace is sampled + * const isSampled = (spanContext.traceFlags & 0x01) === 1; + * + * // Common flag values + * const UNSAMPLED = 0x00; // 00000000 - not sampled + * const SAMPLED = 0x01; // 00000001 - sampled + * + * // Preserving unknown flags during propagation + * const preservedFlags = spanContext.traceFlags | 0x01; // Set sampled bit while preserving others + * + * // W3C traceparent header format includes these flags + * const traceparent = `00-${traceId}-${spanId}-${traceFlags.toString(16).padStart(2, '0')}`; + * ``` + */ + traceFlags?: number; + + /** + * Vendor-specific trace state information for cross-system trace correlation. + * + * The trace state carries tracing-system-specific context in a standardized format + * defined by the W3C Trace Context specification. It allows multiple tracing systems + * to participate in the same trace by providing a mechanism for each system to add + * its own metadata without interfering with others. + * + * The trace state is formatted as a comma-separated list of key-value pairs, where + * each pair represents one tracing system's contribution. Keys should be unique + * within the trace state and follow specific naming conventions. + * + * @remarks + * - Maximum of 32 list members allowed + * - Each member format: `key=value` separated by commas + * - Keys should be namespaced to avoid conflicts (e.g., `vendor@system=value`) + * - Total size should not exceed 512 characters for practical header limits + * - Spaces around list members are ignored + * - Preserves vendor-specific information during trace propagation + * + * @see {@link https://www.w3.org/TR/trace-context/#tracestate-field | W3C Trace Context Specification} + * + * @example + * ```typescript + * // Single tracing system + * const singleVendor = { + * get: (key: string) => key === 'rojo' ? '00f067aa0ba902b7' : undefined, + * set: (key: string, value: string) => { ... }, + * unset: (key: string) => { ... }, + * serialize: () => 'rojo=00f067aa0ba902b7' + * }; + * + * // Multiple tracing systems + * const multiVendor = { + * serialize: () => 'rojo=00f067aa0ba902b7,congo=t61rcWkgMzE,vendor@system=custom-value' + * }; + * + * // Accessing trace state + * const rojoValue = spanContext.traceState?.get('rojo'); + * const serialized = spanContext.traceState?.serialize(); + * + * // HTTP header format (When the traceState is an IOTelTraceState) + * headers['tracestate'] = spanContext.traceState?.serialize() || ''; + * + * // HTTP header format (When the traceState is an IW3cTraceState) + * headers['tracestate'] = spanContext.traceState?.hdrs()[0] || ''; + * ``` + */ + traceState?: IW3cTraceState | IOTelTraceState; +} + +export interface IDistributedTraceContext extends IDistributedTraceInit { /** * Returns the current name of the page @@ -89,8 +268,12 @@ export interface IDistributedTraceContext { * with practically sufficient probability by being made as 16 randomly * generated bytes, encoded as a 32 lowercase hex characters corresponding to * 128 bits. + * @remarks It is NOT recommended that you dynamically change this value after creation and it is actively + * being used as this may affect anyone accessing this context (as a parent for instance). You should logically + * treat this as readonly after creation. * @remarks If you update this value, it will only update for the current context, not the parent context, - * if you need to update the current and ALL parent contexts, use the `setTraceId` method. + * if you need to update the current and ALL parent contexts, use the `setTraceId` method which + * provides the previous behavior. * @since 3.4.0 */ traceId: string; @@ -132,8 +315,8 @@ export interface IDistributedTraceContext { /** * Returns the current trace state which will be used to propgate context across different services. - * Updating (adding / removing keys) of the trace state will modify the current context. - * @remarks Unlike the OpenTelemetry {@link TraceState}, this value is a mutable object, so you can + * Updating (adding / removing keys) of the trace state will modify the current context.IOTelTraceState + * @remarks Unlike the OpenTelemetry {@link IOTelTraceState}, this value is a mutable object, so you can * modify it directly you do not need to reassign the new value to this property. * @since 3.4.0 */ diff --git a/shared/otel-core/src/interfaces/ai/IFeatureOptIn.ts b/shared/otel-core/src/interfaces/ai/IFeatureOptIn.ts index e8fc4aaf9..f05fd9173 100644 --- a/shared/otel-core/src/interfaces/ai/IFeatureOptIn.ts +++ b/shared/otel-core/src/interfaces/ai/IFeatureOptIn.ts @@ -13,14 +13,14 @@ export interface IFeatureOptInDetails { * Identifies configuration override values when given feature is enabled * NOTE: should use flat string for fields, for example, if you want to set value for extensionConfig.Ananlytics.disableAjaxTrackig in configurations, * you should use "extensionConfig.Ananlytics.disableAjaxTrackig" as field name: \{["extensionConfig.Analytics.disableAjaxTrackig"]:1\} - * Default: undefined + * @default undefined */ onCfg?: {[field: string]: any}; /** * Identifies configuration override values when given feature is disabled * NOTE: should use flat string for fields, for example, if you want to set value for extensionConfig.Ananlytics.disableAjaxTrackig in configurations, * you should use "extensionConfig.Ananlytics.disableAjaxTrackig" as field name: \{["extensionConfig.Analytics.disableAjaxTrackig"]:1\} - * Default: undefined + * @default undefined */ offCfg?: {[field: string]: any}; /** diff --git a/shared/otel-core/src/interfaces/ai/IRequestTelemetry.ts b/shared/otel-core/src/interfaces/ai/IRequestTelemetry.ts new file mode 100644 index 000000000..f277b1c7d --- /dev/null +++ b/shared/otel-core/src/interfaces/ai/IRequestTelemetry.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IPartC } from "./IPartC"; + +/** + * This defines the contract for request telemetry items that are passed to Application Insights API (track) + */ +export interface IRequestTelemetry extends IPartC { + /** + * Identifier of a request call instance. Used for correlation between request and other telemetry items. + */ + id: string; + + /** + * Name of the request. Represents code path taken to process request. Low cardinality value to allow better grouping of requests. For HTTP requests it represents the HTTP method and URL path template like 'GET /values/\{id\}'. + */ + name?: string; + + /** + * Request duration in milliseconds. + */ + duration: number; + + /** + * Indication of successful or unsuccessful call. + */ + success: boolean; + + /** + * Result of a request execution. HTTP status code for HTTP requests. + */ + responseCode: number; + + /** + * Source of the request. Examples are the instrumentation key of the caller or the ip address of the caller. + */ + source?: string; + + /** + * Request URL with all query string parameters. + */ + url?: string; +} \ No newline at end of file diff --git a/shared/otel-core/src/interfaces/ai/ITraceProvider.ts b/shared/otel-core/src/interfaces/ai/ITraceProvider.ts new file mode 100644 index 000000000..761afb381 --- /dev/null +++ b/shared/otel-core/src/interfaces/ai/ITraceProvider.ts @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IOTelApi } from "../otel/IOTelApi"; +import { IOTelContextManager } from "../otel/context/IOTelContextManager"; +import { IOTelSpanOptions } from "../otel/trace/IOTelSpanOptions"; +import { IReadableSpan } from "../otel/trace/IReadableSpan"; +import { IConfiguration } from "./IConfiguration"; +import { IDistributedTraceContext } from "./IDistributedTraceContext"; + +/** + * A trace provider interface that enables different SKUs to provide their own + * span implementations while being managed by the core SDK. + * + * This follows the OpenTelemetry TraceProvider pattern, allowing the core to + * delegate span creation to the appropriate implementation based on the SDK variant. + * + * @since 3.4.0 + */ +export interface ITraceProvider { + /** + * The OpenTelemetry API instance associated with this trace provider. + * This provides access to the tracer provider and other OpenTelemetry functionality. + * @since 3.4.0 + */ + readonly api: IOTelApi; + + /** + * Creates a new span with the given name and options. + * + * @param name - The name of the span + * @param options - Options for creating the span (kind, attributes, startTime) + * @param parent - Optional parent context. If not provided, uses the current active trace context + * @returns A new span instance specific to this provider's implementation + * @since 3.4.0 + */ + createSpan(name: string, options?: IOTelSpanOptions, parent?: IDistributedTraceContext): IReadableSpan; + + /** + * Gets the provider identifier for debugging and logging purposes. + * @returns A string identifying this trace provider implementation + * @since 3.4.0 + */ + getProviderId(): string; + + /** + * Determines if this provider is available and ready to create spans. + * @returns true if the provider can create spans, false otherwise + * @since 3.4.0 + */ + isAvailable(): boolean; +} + +/** + * Interface for OpenTelemetry trace operations. + * This interface provides span creation, context management, and trace provider operations + * that are common across different SDK implementations (Core, AISKU, etc.). + * + * @since 3.4.0 + */ +export interface ITraceHost { + + /* + * Config object that was used to initialize AppInsights / ITraceHost + */ + readonly config: CfgType; + + /** + * The root {@link IOTelContextManager} for this instance of the Core. + */ + readonly context: IOTelContextManager; + + /** + * Gets the current distributed trace active context for this instance + * @param createNew - Optional flag to create a new instance if one doesn't currently exist, defaults to true. By default this + * will use any located parent as defined by the {@link IConfiguration.traceHdrMode} configuration for each new instance created. + */ + getTraceCtx(createNew?: boolean): IDistributedTraceContext | null; + + /** + * Sets the current distributed trace context for this instance if available + */ + setTraceCtx(newTraceCtx: IDistributedTraceContext | null | undefined): void; + + /** + * Start a new span with the given name and optional parent context. + * + * Note: This method only creates and returns the span. It does not automatically + * set the span as the active trace context. Context management should be handled + * separately using setTraceCtx() if needed. + * + * @param name - The name of the span + * @param options - Options for creating the span (kind, attributes, startTime) + * @param parent - Optional parent context. If not provided, uses the current active trace context + * @returns A new span instance, or null if no trace provider is available + * @since 3.4.0 + * + * @see {@link IReadableSpan} - Interface for individual spans + * @see {@link IOTelSpanOptions} - Configuration options for span creation + */ + startSpan(name: string, options?: IOTelSpanOptions, parent?: IDistributedTraceContext): IReadableSpan | null; + + /** + * Return the current active span, if no trace provider is available null will be returned + * but when a trace provider is available a span instance will always be returned, even if + * there is no active span (in which case a non-recording span will be returned). + * @param createNew - Optional flag to create a non-recording span if no active span exists, defaults to true. + * When false, returns the existing active span or null without creating a non-recording span, which can improve + * performance when only checking if an active span exists. + * @returns The current active span or null if no trace provider is available or if createNew is false and no active span exists + * @since 3.4.0 + */ + getActiveSpan(createNew?: boolean): IReadableSpan | null; + + /** + * Set the current Active Span, if no trace provider is available the span will be not be set as the active span. + * @param span - The span to set as the active span + * @returns An ISpanScope instance that provides the current scope, the span will always be the span passed in + * even when no trace provider is available + * @since 3.4.0 + */ + setActiveSpan(span: IReadableSpan): ISpanScope + + /** + * Get the current trace provider. + * + * @returns The current trace provider, or null if none is set + * @since 3.4.0 + */ + getTraceProvider(): ITraceProvider | null; +} + +/** + * Represents the execution scope for a span, combining the trace instance and the active span. + * This interface is used as the context for executing functions within a span's scope. + * + * @since 3.4.0 + */ +export interface ISpanScope { + /** + * The trace host (core or AISKU instance). + * @since 3.4.0 + */ + readonly host: T; + + /** + * The active span for this execution scope. + * @since 3.4.0 + */ + readonly span: IReadableSpan; + + /** + * The previously active span before this scope was created, if any. + * @since 3.4.0 + */ + readonly prvSpan?: IReadableSpan; + + /** + * Restores the previous active span in the trace instance. + * @since 3.4.0 + */ + restore(): void; +} diff --git a/shared/otel-core/src/interfaces/ai/PartAExtensions.ts b/shared/otel-core/src/interfaces/ai/PartAExtensions.ts index b656311fb..7905a6501 100644 --- a/shared/otel-core/src/interfaces/ai/PartAExtensions.ts +++ b/shared/otel-core/src/interfaces/ai/PartAExtensions.ts @@ -14,4 +14,4 @@ export const Extensions = { SDKExt: "sdk" }; -export let CtxTagKeys = new ContextTagKeys(); +export let CtxTagKeys = (/* #__PURE__ */ new ContextTagKeys()); \ No newline at end of file diff --git a/shared/otel-core/src/interfaces/ai/contracts/ContextTagKeys.ts b/shared/otel-core/src/interfaces/ai/contracts/ContextTagKeys.ts index a6d82d8c9..59497c533 100644 --- a/shared/otel-core/src/interfaces/ai/contracts/ContextTagKeys.ts +++ b/shared/otel-core/src/interfaces/ai/contracts/ContextTagKeys.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { createClassFromInterface } from "../../../utils/HelperFuncsCore"; +import { createClassFromInterface } from "../../../utils/HelperFuncs"; function _aiNameFunc(baseName: string) { let aiName = "ai." + baseName + "."; diff --git a/shared/otel-core/src/interfaces/ai/contracts/IEnvelope.ts b/shared/otel-core/src/interfaces/ai/contracts/IEnvelope.ts deleted file mode 100644 index fe722f75e..000000000 --- a/shared/otel-core/src/interfaces/ai/contracts/IEnvelope.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// import { IBase } from "./IBase"; - -// /** -// * System variables for a telemetry item. -// */ -export interface IEnvelope { -} - -// /** -// * Envelope version. For internal use only. By assigning this the default, it will not be serialized within the payload unless changed to a value other than #1. -// */ -// ver: number; /* 1 */ - -// /** -// * Type name of telemetry data item. -// */ -// name: string; - -// /** -// * Event date time when telemetry item was created. This is the wall clock time on the client when the event was generated. There is no guarantee that the client's time is accurate. This field must be formatted in UTC ISO 8601 format, with a trailing 'Z' character, as described publicly on https://en.wikipedia.org/wiki/ISO_8601#UTC. Note: the number of decimal seconds digits provided are variable (and unspecified). Consumers should handle this, i.e. managed code consumers should not use format 'O' for parsing as it specifies a fixed length. Example: 2009-06-15T13:45:30.0000000Z. -// */ -// time: string; - -// /** -// * Sampling rate used in application. This telemetry item represents 1 / sampleRate actual telemetry items. -// */ -// sampleRate: number; /* 100.0 */ - -// /** -// * Sequence field used to track absolute order of uploaded events. -// */ -// seq: string; - -// /** -// * The application's instrumentation key. The key is typically represented as a GUID, but there are cases when it is not a guid. No code should rely on iKey being a GUID. Instrumentation key is case insensitive. -// */ -// iKey: string; - -// /** -// * Key/value collection of context properties. See ContextTagKeys for information on available properties. -// */ -// tags: any; /* {} */ - -// /** -// * Telemetry data item. -// */ -// data: IBase; -// } diff --git a/shared/otel-core/src/interfaces/ai/contracts/RequestData.ts b/shared/otel-core/src/interfaces/ai/contracts/RequestData.ts deleted file mode 100644 index ae7c34248..000000000 --- a/shared/otel-core/src/interfaces/ai/contracts/RequestData.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// import { IDomain } from "./IDomain"; - -// /** -// * An instance of Request represents completion of an external request to the application to do work and contains a summary of that request execution and the results. -// */ -// export class RequestData implements IDomain { - -// /** -// * Schema version -// */ -// public ver: number = 2; - -// /** -// * Identifier of a request call instance. Used for correlation between request and other telemetry items. -// */ -// public id: string; - -// /** -// * Source of the request. Examples are the instrumentation key of the caller or the ip address of the caller. -// */ -// public source: string; - -// /** -// * Name of the request. Represents code path taken to process request. Low cardinality value to allow better grouping of requests. For HTTP requests it represents the HTTP method and URL path template like 'GET /values/{id}'. -// */ -// public name: string; - -// /** -// * Indication of successful or unsuccessful call. -// */ -// public success: boolean; - -// /** -// * Request URL with all query string parameters. -// */ -// public url: string; - -// /** -// * Collection of custom properties. -// */ -// public properties: any = {}; - -// /** -// * Collection of custom measurements. -// */ -// public measurements: any = {}; -// } diff --git a/shared/otel-core/src/interfaces/ai/telemetry/ISerializable.ts b/shared/otel-core/src/interfaces/ai/telemetry/ISerializable.ts index 94420c19d..038dab426 100644 --- a/shared/otel-core/src/interfaces/ai/telemetry/ISerializable.ts +++ b/shared/otel-core/src/interfaces/ai/telemetry/ISerializable.ts @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { FieldType } from "../../../enums/ai/Enums"; + export interface ISerializable { /** * The set of fields for a serializable object. * This defines the serialization order and a value of true/false * for each field defines whether the field is required or not. */ - aiDataContract: any; + aiDataContract: { [key: string]: FieldType | (() => FieldType) }; } diff --git a/shared/otel-core/src/interfaces/otel/IOTelApi.ts b/shared/otel-core/src/interfaces/otel/IOTelApi.ts index ab8d8d679..2ec0f7cc7 100644 --- a/shared/otel-core/src/interfaces/otel/IOTelApi.ts +++ b/shared/otel-core/src/interfaces/otel/IOTelApi.ts @@ -1,29 +1,56 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { ITraceHost } from "../ai/ITraceProvider"; import { IOTelConfig } from "./config/IOTelConfig"; -import { IOTelContextManager } from "./context/IOTelContextManager"; -import { IOTelTraceApi } from "./trace/IOTelTraceApi"; +import { ITraceApi } from "./trace/IOTelTraceApi"; import { IOTelTracerProvider } from "./trace/IOTelTracerProvider"; -// import { IOTelMetricsApi } from "./metrics/IOTelMetricsApi"; -// import { IOTelPropagationApi } from "./propagation/IOTelPropagationApi"; +/** + * The main OpenTelemetry API interface that provides access to all OpenTelemetry functionality. + * This interface extends the IOTelTracerProvider and serves as the entry point for OpenTelemetry operations. + * + * @example + * ```typescript + * // Get a tracer from the API instance + * const tracer = otelApi.getTracer("my-component"); + * + * // Create a span + * const span = tracer.startSpan("operation"); + * + * // Access context manager + * const currentContext = otelApi.context.active(); + * + * // Access trace API + * const activeSpan = otelApi.trace.getActiveSpan(); + * ``` + * + * @since 3.4.0 + */ export interface IOTelApi extends IOTelTracerProvider { + /** + * The configuration object that contains all OpenTelemetry-specific settings. + * This includes tracing configuration, error handlers, and other OpenTelemetry options. + * + * @remarks + * Changes to this configuration after initialization may not take effect until + * the next telemetry operation, depending on the implementation. + */ cfg: IOTelConfig; /** - * The current ContextManager instance for this IOTelApi instance, this is effectively + * The current {@link ITraceHost} instance for this IOTelApi instance, this is effectively * the OpenTelemetry ContextAPI instance without the static methods. * @returns The ContextManager instance */ - context: IOTelContextManager; + host: ITraceHost; /** - * The current {@link IOTelTraceApi} instance for this IOTelApi instance, this is + * The current {@link ITraceApi} instance for this IOTelApi instance, this is * effectively the OpenTelemetry TraceAPI instance without the static methods. - * @returns The current {@link IOTelTraceApi} instance + * @returns The current {@link ITraceApi} instance */ - trace: IOTelTraceApi; + trace: ITraceApi; // propagation?: IOTelPropagationApi; diff --git a/shared/otel-core/src/interfaces/otel/IOTelApiCtx.ts b/shared/otel-core/src/interfaces/otel/IOTelApiCtx.ts index a35dd2a00..03775557b 100644 --- a/shared/otel-core/src/interfaces/otel/IOTelApiCtx.ts +++ b/shared/otel-core/src/interfaces/otel/IOTelApiCtx.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IDiagnosticLogger } from "../ai/IDiagnosticLogger"; -import { IOTelConfig } from "./config/IOTelConfig"; -import { IOTelTracerProvider } from "./trace/IOTelTracerProvider"; +import { ITraceHost } from "../ai/ITraceProvider"; /** - * The context for the current IOTelApi instance and it's configuration + * The context for the current IOTelApi instance linking it to the core SDK instance, + * including access to the core dynamic configuration. + * + * Note: Passing the core instance within a context object to allow future expansion + * without breaking changes or modifying signatures. Also allows easier mocking for tests. */ export interface IOTelApiCtx { - otelCfg: IOTelConfig; - - traceProvider: IOTelTracerProvider; - - diagLogger: IDiagnosticLogger; + /** + * The host instance associated with this OTel API instance + */ + host: ITraceHost; } diff --git a/shared/otel-core/src/interfaces/otel/attribute/IAttributeContainer.ts b/shared/otel-core/src/interfaces/otel/attribute/IAttributeContainer.ts index 747842799..06a17d3e4 100644 --- a/shared/otel-core/src/interfaces/otel/attribute/IAttributeContainer.ts +++ b/shared/otel-core/src/interfaces/otel/attribute/IAttributeContainer.ts @@ -26,6 +26,8 @@ export const enum eAttributeFilter { LocalOrDeleted = 2 } +export type AttributeFilter = number | eAttributeFilter; + /** * Information about what changed in an attribute container */ @@ -182,7 +184,7 @@ export interface IAttributeContainer IAttributeContainer diff --git a/shared/otel-core/src/interfaces/otel/config/IOTelAttributeLimits.ts b/shared/otel-core/src/interfaces/otel/config/IOTelAttributeLimits.ts index c4924c9e1..164416adb 100644 --- a/shared/otel-core/src/interfaces/otel/config/IOTelAttributeLimits.ts +++ b/shared/otel-core/src/interfaces/otel/config/IOTelAttributeLimits.ts @@ -1,14 +1,72 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/** + * Configuration interface for OpenTelemetry attribute limits. + * These limits help control the size and number of attributes to prevent + * excessive memory usage and ensure consistent performance. + * + * @example + * ```typescript + * const limits: IOTelAttributeLimits = { + * attributeCountLimit: 128, // Maximum 128 attributes + * attributeValueLengthLimit: 4096 // Maximum 4KB per attribute value + * }; + * ``` + * + * @remarks + * When limits are exceeded: + * - Additional attributes beyond `attributeCountLimit` are dropped + * - Attribute values longer than `attributeValueLengthLimit` are truncated + * - The behavior may vary based on the specific implementation + * + * @since 3.4.0 + */ export interface IOTelAttributeLimits { /** - * maxValueLen is maximum allowed attribute value size + * Maximum allowed length for attribute values in characters. + * + * @remarks + * - Values longer than this limit will be truncated + * - Applies to string attribute values only + * - Numeric and boolean values are not affected by this limit + * - Array values have this limit applied to each individual element + * + * @defaultValue 4096 + * + * @example + * ```typescript + * // If attributeValueLengthLimit is 100: + * span.setAttribute("description", "a".repeat(200)); // Will be truncated to 100 characters + * span.setAttribute("count", 12345); // Not affected (number) + * span.setAttribute("enabled", true); // Not affected (boolean) + * ``` */ attributeValueLengthLimit?: number; /** - * maxAttribs is number of attributes per span / trace + * Maximum number of attributes allowed per telemetry item. + * + * @remarks + * - Attributes added beyond this limit will be dropped + * - The order of attributes matters; earlier attributes take precedence + * - This limit applies to the total count of attributes, regardless of their type + * - Inherited or default attributes count toward this limit + * + * @defaultValue 128 + * + * @example + * ```typescript + * // If attributeCountLimit is 5: + * span.setAttributes({ + * "attr1": "value1", // Kept + * "attr2": "value2", // Kept + * "attr3": "value3", // Kept + * "attr4": "value4", // Kept + * "attr5": "value5", // Kept + * "attr6": "value6" // Dropped (exceeds limit) + * }); + * ``` */ attributeCountLimit?: number; } diff --git a/shared/otel-core/src/interfaces/otel/config/IOTelConfig.ts b/shared/otel-core/src/interfaces/otel/config/IOTelConfig.ts index 76477c896..befeeb957 100644 --- a/shared/otel-core/src/interfaces/otel/config/IOTelConfig.ts +++ b/shared/otel-core/src/interfaces/otel/config/IOTelConfig.ts @@ -2,9 +2,54 @@ // Licensed under the MIT License. import { IOTelErrorHandlers } from "./IOTelErrorHandlers"; -import { IOTelTraceCfg } from "./IOTelTraceCfg"; +import { ITraceCfg } from "./IOTelTraceCfg"; +/** + * OpenTelemetry configuration interface + * Provides configuration specific to the OpenTelemetry extensions + */ export interface IOTelConfig { - traceCfg?: IOTelTraceCfg; + /** + * Configuration interface for OpenTelemetry tracing functionality. + * This interface contains all the settings that control how traces are created, + * processed, and managed within the OpenTelemetry system. + * + * @example + * ```typescript + * const traceCfg: ITraceCfg = { + * serviceName: "my-service", + * generalLimits: { + * attributeCountLimit: 128, + * attributeValueLengthLimit: 4096 + * }, + * spanLimits: { + * attributeCountLimit: 128, + * linkCountLimit: 128, + * eventCountLimit: 128 + * } + * }; + * ``` + * + * @since 3.4.0 + */ + traceCfg?: ITraceCfg; + + /** + * Error handlers for OpenTelemetry operations. + * This interface allows you to specify custom error handling logic for various + * OpenTelemetry components, enabling better control over how errors are managed + * within the OpenTelemetry system. + * + * @see {@link IOTelErrorHandlers} + * + * @example + * ```typescript + * const errorHandlers: IOTelErrorHandlers = { + * attribError: (message, key, value) => { + * console.warn(`Attribute error for ${key}:`, message); + * } + * }; + * ``` + */ errorHandlers?: IOTelErrorHandlers; } diff --git a/shared/otel-core/src/interfaces/otel/config/IOTelErrorHandlers.ts b/shared/otel-core/src/interfaces/otel/config/IOTelErrorHandlers.ts index 39eedfa44..697c11e3b 100644 --- a/shared/otel-core/src/interfaces/otel/config/IOTelErrorHandlers.ts +++ b/shared/otel-core/src/interfaces/otel/config/IOTelErrorHandlers.ts @@ -1,45 +1,196 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/** + * Configuration interface for OpenTelemetry error handling callbacks. + * Provides hooks to customize how different types of errors and diagnostic + * messages are handled within the OpenTelemetry system. + * + * @example + * ```typescript + * const errorHandlers: IOTelErrorHandlers = { + * attribError: (message, key, value) => { + * console.warn(`Attribute error for ${key}:`, message); + * }, + * spanError: (message, spanName) => { + * logger.error(`Span ${spanName} error:`, message); + * }, + * warn: (message) => { + * logger.warn(message); + * }, + * error: (message) => { + * logger.error(message); + * } + * }; + * ``` + * + * @remarks + * If handlers are not provided, default behavior will be used: + * - `attribError`: Throws an `OTelInvalidAttributeError` + * - `spanError`: Logs to console or calls warn handler + * - `debug`: Logs to console.log + * - `warn`: Logs to console.warn + * - `error`: Logs to console.error + * - `notImplemented`: Logs to console.error + * + * @since 3.4.0 + */ export interface IOTelErrorHandlers { /** - * Handle / report an error. - * When not provided the default is to generally throw an {@link OTelInvalidAttributeError} - * @param message - the message to report - * @param key - the key that caused the error - * @param value - the value that caused the error + * Handles attribute-related errors, such as invalid attribute values or keys. + * Called when an attribute operation fails validation or processing. + * + * @param message - Descriptive error message explaining what went wrong + * @param key - The attribute key that caused the error + * @param value - The attribute value that caused the error (may be of any type) + * + * @remarks + * Common scenarios that trigger this handler: + * - Invalid attribute key format + * - Attribute value exceeds length limits + * - Unsupported attribute value type + * - Attribute count exceeds limits + * + * @default Throws an `OTelInvalidAttributeError` + * + * @example + * ```typescript + * attribError: (message, key, value) => { + * metrics.increment('otel.attribute.errors', { key, type: typeof value }); + * logger.warn(`Attribute ${key} rejected: ${message}`); + * } + * ``` */ - attribError?: (message: string, key: string, value: any) => void; /** - * There was an error with the span. - * @param message - the message to report - * @param spanName - the name of the span + * Handles span-related errors that occur during span operations. + * Called when a span operation fails or encounters an unexpected condition. + * + * @param message - Descriptive error message explaining the span error + * @param spanName - The name of the span that encountered the error + * + * @remarks + * Common scenarios that trigger this handler: + * - Span operation called on an ended span + * - Invalid span configuration + * - Span processor errors + * - Context propagation failures + * + * @default Logs to console or calls the warn handler + * + * @example + * ```typescript + * spanError: (message, spanName) => { + * metrics.increment('otel.span.errors', { span_name: spanName }); + * logger.error(`Span operation failed for "${spanName}": ${message}`); + * } + * ``` */ spanError?: (message: string, spanName: string) => void; /** - * Report a debug error - * @param message - the message to report + * Handles debug-level diagnostic messages. + * Used for detailed troubleshooting information that is typically + * only relevant during development or when diagnosing issues. + * + * @param message - Debug message to be handled + * + * @remarks + * Debug messages are typically: + * - Verbose operational details + * - Internal state information + * - Performance metrics + * - Development-time diagnostics + * + * @default Logs to console.log + * + * @example + * ```typescript + * debug: (message) => { + * if (process.env.NODE_ENV === 'development') { + * console.debug('[OTel Debug]', message); + * } + * } + * ``` */ debug?: (message: string) => void; /** - * Report a general wanring, should not be treated as fatal - * @param message - the message to report + * Handles warning-level messages for non-fatal issues. + * Used for conditions that are unusual but don't prevent continued operation. + * + * @param message - Warning message to be handled + * + * @remarks + * Warning scenarios include: + * - Configuration issues that fall back to defaults + * - Performance degradation + * - Deprecated API usage + * - Resource limit approaches + * + * @default Logs to console.warn + * + * @example + * ```typescript + * warn: (message) => { + * logger.warn('[OTel Warning]', message); + * metrics.increment('otel.warnings'); + * } + * ``` */ warn?: (message: string) => void; /** - * Report a general error - * @param message - the message to report + * Handles general error conditions that may affect functionality. + * Used for significant errors that should be investigated but may not be fatal. + * + * @param message - Error message to be handled + * + * @remarks + * Error scenarios include: + * - Failed network requests + * - Configuration validation failures + * - Resource allocation failures + * - Unexpected runtime conditions + * + * @default Logs to console.error + * + * @example + * ```typescript + * error: (message) => { + * logger.error('[OTel Error]', message); + * errorReporting.captureException(new Error(message)); + * } + * ``` */ error?: (message: string) => void; /** - * A general error handler for not implemented methods. - * @param message - the message to report + * Handles errors related to unimplemented functionality. + * Called when a method or feature is not yet implemented or is intentionally + * disabled in the current configuration. + * + * @param message - Message describing the unimplemented functionality + * + * @remarks + * Common scenarios: + * - Placeholder methods that haven't been implemented + * - Features disabled in the current build + * - Platform-specific functionality not available + * - Optional features not included in the configuration + * + * @default Logs to console.error + * + * @example + * ```typescript + * notImplemented: (message) => { + * logger.warn(`[OTel Not Implemented] ${message}`); + * if (process.env.NODE_ENV === 'development') { + * console.trace('Not implemented method called'); + * } + * } + * ``` */ notImplemented?: (message: string) => void; } diff --git a/shared/otel-core/src/interfaces/otel/config/IOTelSpanLimits.ts b/shared/otel-core/src/interfaces/otel/config/IOTelSpanLimits.ts index e77c8a59e..49a3b46ef 100644 --- a/shared/otel-core/src/interfaces/otel/config/IOTelSpanLimits.ts +++ b/shared/otel-core/src/interfaces/otel/config/IOTelSpanLimits.ts @@ -3,25 +3,129 @@ import { IOTelAttributeLimits } from "./IOTelAttributeLimits"; +/** + * Configuration interface for OpenTelemetry span-specific limits. + * Extends the general attribute limits with additional constraints specific to spans, + * including limits on events, links, and their associated attributes. + * + * @example + * ```typescript + * const spanLimits: IOTelSpanLimits = { + * // Inherited from IOTelAttributeLimits + * attributeCountLimit: 128, + * attributeValueLengthLimit: 4096, + * + * // Span-specific limits + * linkCountLimit: 128, + * eventCountLimit: 128, + * attributePerEventCountLimit: 32, + * attributePerLinkCountLimit: 32 + * }; + * ``` + * + * @remarks + * These limits help prevent spans from consuming excessive memory and ensure + * consistent performance even when dealing with complex traces that have many + * events, links, or attributes. + * + * @since 3.4.0 + */ export interface IOtelSpanLimits extends IOTelAttributeLimits { - + /** - * maxLinks is number of links per span + * Maximum number of links allowed per span. + * + * @remarks + * - Links added beyond this limit will be dropped + * - Links are typically added at span creation time + * - Each link represents a causal relationship with another span + * - Links added after creation may be subject to additional restrictions + * + * @defaultValue 128 + * + * @example + * ```typescript + * const span = tracer.startSpan("operation", { + * links: [ + * { context: relatedSpanContext1 }, + * { context: relatedSpanContext2 }, + * // ... up to linkCountLimit links + * ] + * }); + * ``` */ linkCountLimit?: number; - + /** - * maxEvents is number of message events per span + * Maximum number of events allowed per span. + * + * @remarks + * - Events added beyond this limit will be dropped + * - Events are typically used to mark significant points during span execution + * - Each event can have its own set of attributes (limited by attributePerEventCountLimit) + * - Events are ordered chronologically within the span + * + * @defaultValue 128 + * + * @example + * ```typescript + * // If eventCountLimit is 3: + * span.addEvent("started"); // Kept + * span.addEvent("processing"); // Kept + * span.addEvent("validation"); // Kept + * span.addEvent("completed"); // Dropped (exceeds limit) + * ``` */ eventCountLimit?: number; - + /** - * maxEventAttribs is the maximum number of attributes allowed per span event + * Maximum number of attributes allowed per span event. + * + * @remarks + * - This limit applies to each individual event within a span + * - Attributes added to events beyond this limit will be dropped + * - This is separate from the span's own attribute limits + * - Event attributes are useful for providing context about what happened at that point in time + * + * @defaultValue 32 + * + * @example + * ```typescript + * // If attributePerEventCountLimit is 2: + * span.addEvent("user_action", { + * "action": "click", // Kept + * "element": "button", // Kept + * "timestamp": "12345" // Dropped (exceeds per-event limit) + * }); + * ``` */ attributePerEventCountLimit?: number; - + /** - * maxLinkAttribs is the maximum number of attributes allowed per span link + * Maximum number of attributes allowed per span link. + * + * @remarks + * - This limit applies to each individual link within a span + * - Attributes added to links beyond this limit will be dropped + * - This is separate from the span's own attribute limits + * - Link attributes provide additional context about the relationship between spans + * + * @defaultValue 32 + * + * @example + * ```typescript + * // If attributePerLinkCountLimit is 2: + * const span = tracer.startSpan("operation", { + * links: [{ + * context: relatedSpanContext, + * attributes: { + * "relationship": "follows", // Kept + * "correlation_id": "abc123", // Kept + * "priority": "high" // Dropped (exceeds per-link limit) + * } + * }] + * }); + * ``` */ attributePerLinkCountLimit?: number; - } +} diff --git a/shared/otel-core/src/interfaces/otel/config/IOTelTraceCfg.ts b/shared/otel-core/src/interfaces/otel/config/IOTelTraceCfg.ts index a46ab8ec4..426d2cc53 100644 --- a/shared/otel-core/src/interfaces/otel/config/IOTelTraceCfg.ts +++ b/shared/otel-core/src/interfaces/otel/config/IOTelTraceCfg.ts @@ -7,15 +7,55 @@ import { IOTelSampler } from "../trace/IOTelSampler"; import { IOTelAttributeLimits } from "./IOTelAttributeLimits"; import { IOtelSpanLimits } from "./IOTelSpanLimits"; -export interface IOTelTraceCfg { +/** + * Configuration interface for OpenTelemetry compatible tracing functionality. + * This interface contains all the settings that control how traces are created, + * processed, and managed within the OpenTelemetry system. + * + * @example + * ```typescript + * const traceCfg: ITraceCfg = { + * serviceName: "my-service", + * generalLimits: { + * attributeCountLimit: 128, + * attributeValueLengthLimit: 4096 + * }, + * spanLimits: { + * attributeCountLimit: 128, + * linkCountLimit: 128, + * eventCountLimit: 128 + * } + * }; + * ``` + * + * @since 3.4.0 + */ +export interface ITraceCfg { contextManager?: IOTelContextManager; // textMapPropagator?: TextMapPropagator; sampler?: IOTelSampler; + /** + * Global attribute limits that apply to all telemetry items. + * These limits help prevent excessive memory usage and ensure consistent + * behavior across different telemetry types. + * + * @remarks + * These limits are inherited by more specific configurations unless overridden. + * For example, spans will use these limits unless `spanLimits` specifies different values. + */ generalLimits?: IOTelAttributeLimits; + // /** + // * Specific limits that apply only to spans. + // * These limits override the general limits for span-specific properties. + // * + // * @remarks + // * Includes limits for attributes, events, links, and their associated attributes. + // * This allows for fine-tuned control over span size and complexity. + // */ spanLimits?: IOtelSpanLimits; idGenerator?: IOTelIdGenerator; @@ -26,7 +66,29 @@ export interface IOTelTraceCfg { // instrumentations: (Instrumentation | Instrumentation[])[]; // resource: Resource; // resourceDetectors: Array; + + /** + * The name of the service generating telemetry data. + * This name will be included in all telemetry items as a resource attribute. + * + * @remarks + * The service name is crucial for identifying and filtering telemetry data + * in observability systems. It should be consistent across all instances + * of the same service. + * + * @example + * ```typescript + * serviceName: "user-authentication-service" + * ``` + */ serviceName?: string; // spanProcessors?: SpanProcessor[]; // traceExporter: SpanExporter; + + /** + * A flag that indicates whether the tracing (creating of a "trace" event) should be suppressed + * when a {@link IOTelSpan} ends and the span {@link IOTelSpan#isRecording | isRecording} is true. + * This value is also inherited by spans when they are created. + */ + suppressTracing?: boolean; } diff --git a/shared/otel-core/src/interfaces/otel/context/IOTelContext.ts b/shared/otel-core/src/interfaces/otel/context/IOTelContext.ts index b3cdef826..abecc30b0 100644 --- a/shared/otel-core/src/interfaces/otel/context/IOTelContext.ts +++ b/shared/otel-core/src/interfaces/otel/context/IOTelContext.ts @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IOTelApi } from "../IOTelApi"; + /** * Provides an OpenTelemetry compatible Interface for the Open Telemetry Api (1.9.0) Context type. * @since 3.4.0 */ export interface IOTelContext { + readonly api: IOTelApi; + /** * Get a value from the context. * diff --git a/shared/otel-core/src/interfaces/otel/logs/IOTelLogRecord.ts b/shared/otel-core/src/interfaces/otel/logs/IOTelLogRecord.ts index 2416dfffd..a9f86cb50 100644 --- a/shared/otel-core/src/interfaces/otel/logs/IOTelLogRecord.ts +++ b/shared/otel-core/src/interfaces/otel/logs/IOTelLogRecord.ts @@ -2,8 +2,8 @@ // Licensed under the MIT License. import { OTelSeverityNumber } from "../../../enums/otel/eOTelSeverityNumber"; +import { OTelTimeInput } from "../../../interfaces/IOTelHrTime"; import { OTelAnyValue, OTelAnyValueMap } from "../../../types/OTelAnyValue"; -import { OTelTimeInput } from "../../../types/time"; import { IOTelContext } from "../context/IOTelContext"; export type LogBody = OTelAnyValue; diff --git a/shared/otel-core/src/interfaces/otel/logs/IOTelReadableLogRecord.ts b/shared/otel-core/src/interfaces/otel/logs/IOTelReadableLogRecord.ts index 1b4bd22c8..0f46e0e8a 100644 --- a/shared/otel-core/src/interfaces/otel/logs/IOTelReadableLogRecord.ts +++ b/shared/otel-core/src/interfaces/otel/logs/IOTelReadableLogRecord.ts @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IDistributedTraceContext } from "../../.."; import { OTelSeverityNumber } from "../../../enums/otel/eOTelSeverityNumber"; -import { IOTelHrTime } from "../../../types/time"; +import { IOTelHrTime } from "../../../interfaces/IOTelHrTime"; import { IOTelResource } from "../resources/IOTelResource"; import { IOTelInstrumentationScope } from "../trace/IOTelInstrumentationScope"; -import { IOTelSpanContext } from "../trace/IOTelSpanContext"; import { LogAttributes, LogBody } from "./IOTelLogRecord"; export interface ReadableLogRecord { @@ -22,7 +22,7 @@ export interface ReadableLogRecord { /** * The SpanContext associated with the LogRecord. */ - readonly spanContext?: IOTelSpanContext; + readonly spanContext?: IDistributedTraceContext; /** * The severity text. diff --git a/shared/otel-core/src/interfaces/otel/logs/IOTelSdkLogRecord.ts b/shared/otel-core/src/interfaces/otel/logs/IOTelSdkLogRecord.ts index 863cabfe0..f1b2e3fdd 100644 --- a/shared/otel-core/src/interfaces/otel/logs/IOTelSdkLogRecord.ts +++ b/shared/otel-core/src/interfaces/otel/logs/IOTelSdkLogRecord.ts @@ -2,8 +2,9 @@ // Licensed under the MIT License. import { OTelSeverityNumber } from "../../../enums/otel/eOTelSeverityNumber"; +import { IOTelHrTime } from "../../../interfaces/IOTelHrTime"; import { OTelAnyValue } from "../../../types/OTelAnyValue"; -import { IOTelHrTime } from "../../../types/time"; +import { IDistributedTraceContext, IDistributedTraceInit } from "../../ai/IDistributedTraceContext"; import { IOTelResource } from "../resources/IOTelResource"; import { IOTelInstrumentationScope } from "../trace/IOTelInstrumentationScope"; import { IOTelSpanContext } from "../trace/IOTelSpanContext"; @@ -23,7 +24,7 @@ export interface IOTelSdkLogRecord { /** * The SpanContext associated with the LogRecord. */ - readonly spanContext?: IOTelSpanContext; + readonly spanContext?: IDistributedTraceContext | IDistributedTraceInit | IOTelSpanContext; /** * The Resource associated with the LogRecord. diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelLink.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelLink.ts index 28e5314a0..21cfc8e3e 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelLink.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelLink.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IDistributedTraceContext } from "../../ai/IDistributedTraceContext"; import { IOTelAttributes } from "../IOTelAttributes"; import { IOTelSpanContext } from "./IOTelSpanContext"; @@ -35,7 +36,7 @@ export interface IOTelLink { /** * The {@link IOTelSpanContext} of a linked span. */ - context: IOTelSpanContext; + context: IOTelSpanContext | IDistributedTraceContext; /** * A set of {@link IOTelAttributes} on the link. diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelSpan.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelSpan.ts index 60f92280d..86bb1e998 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelSpan.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelSpan.ts @@ -1,141 +1,489 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { OTelTimeInput } from "../../../types/time"; import { OTelException } from "../../IException"; +import { OTelTimeInput } from "../../IOTelHrTime"; +import { IDistributedTraceContext } from "../../ai/IDistributedTraceContext"; import { IOTelAttributes, OTelAttributeValue } from "../IOTelAttributes"; +import { IAttributeContainer } from "../attribute/IAttributeContainer"; import { IOTelLink } from "./IOTelLink"; -import { IOTelSpanContext } from "./IOTelSpanContext"; import { IOTelSpanStatus } from "./IOTelSpanStatus"; /** - * Provides an OpenTelemetry compatible Interface for the Open Telemetry Api (1.9.0) Span type. - * This interface is used to represent a span conforming to the OpenTelemetry API specification. + * Provides an OpenTelemetry compatible interface for spans conforming to the OpenTelemetry API specification (v1.9.0). * - * An interface that represents a span which is an operation within a trace. A Span can be thought - * of as a grouping mechanism for a set of operations that are executed as part of a single - * set of work. Spans are also used to identify the duration of the work performed by the - * operation. + * A span represents an operation within a trace and is the fundamental unit of work in distributed tracing. + * Spans can be thought of as a grouping mechanism for a set of operations that are executed as part of + * a single logical unit of work, providing timing information and contextual data about the operation. * - * Examples of span might include remote procedure calls or a in-process function calls to sub-components. - * A Trace has a single, top-level "root" Span that in turn may have zero or more child Spans, - * which in turn may have children. + * Spans form a tree structure within a trace, with a single root span that may have zero or more child spans, + * which in turn may have their own children. This hierarchical structure allows for detailed analysis of + * complex, multi-step operations across distributed systems. * * @since 3.4.0 + * * @remarks - * By default all spans created by this library implement the ISpan interface which also extends - * the {@link IReadableSpan} interface. + * - All spans created by this library implement the ISpan interface and extend the IReadableSpan interface + * - Spans should be ended by calling `end()` when the operation completes + * - Once ended, spans should generally not be used for further operations + * - Spans automatically track timing information from creation to end + * + * @example + * ```typescript + * // Basic span usage + * const span = tracer.startSpan('user-authentication'); + * span.setAttribute('user.id', '12345'); + * span.setAttribute('auth.method', 'oauth2'); + * + * try { + * const result = await authenticateUser(); + * span.setStatus({ code: SpanStatusCode.OK }); + * span.setAttribute('auth.success', true); + * } catch (error) { + * span.recordException(error); + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: 'Authentication failed' + * }); + * } finally { + * span.end(); + * } + * ``` */ export interface IOTelSpan { /** - * Returns the {@link IOTelSpanContext} object associated with this Span. + * Returns the span context object associated with this span. + * + * The span context is an immutable, serializable identifier that uniquely identifies + * this span within a trace. It contains the trace ID, span ID, and trace flags that + * can be used to create new child spans or propagate trace context across process boundaries. + * + * The returned span context remains valid even after the span has ended, making it + * useful for asynchronous operations and cross-service communication. * - * Get an immutable, serializable identifier for this span that can be used - * to create new child spans. Returned SpanContext is usable even after the - * span ends. + * @returns The immutable span context associated with this span * - * @returns the SpanContext object associated with this Span. + * @remarks + * - The span context is the primary mechanism for trace propagation + * - Context can be serialized and transmitted across network boundaries + * - Contains trace ID (unique to the entire trace) and span ID (unique to this span) + * + * @example + * ```typescript + * const span = tracer.startSpan('parent-operation'); + * const spanContext = span.spanContext(); + * + * // Use context to create child spans in other parts of the system + * const childSpan = tracer.startSpan('child-operation', { + * parent: spanContext + * }); + * + * // Context can be serialized for cross-service propagation + * const traceId = spanContext.traceId; + * const spanId = spanContext.spanId; + * ``` */ - spanContext(): IOTelSpanContext; + spanContext(): IDistributedTraceContext; /** - * Sets an attribute to the span. + * Sets a single attribute on the span with the specified key and value. + * + * Attributes provide contextual information about the operation represented by the span. + * They are key-value pairs that help with filtering, grouping, and understanding spans + * in trace analysis tools. Attributes should represent meaningful properties of the operation. * - * Sets a single Attribute with the key and value passed as arguments. + * @param key - The attribute key, should be descriptive and follow naming conventions + * @param value - The attribute value; null or undefined values are invalid and result in undefined behavior * - * @param key - the key for this attribute. - * @param value - the value for this attribute. Setting a value null or undefined is invalid - * and will result in undefined behavior. + * @returns This span instance for method chaining + * + * @remarks + * - Attribute keys should follow semantic conventions when available + * - Common attributes include service.name, http.method, db.statement, etc. + * - Setting null or undefined values is invalid and may cause unexpected behavior + * - Attributes set after span creation don't affect sampling decisions + * + * @example + * ```typescript + * const span = tracer.startSpan('http-request'); + * + * // Set individual attributes with descriptive keys + * span.setAttribute('http.method', 'POST') + * .setAttribute('http.url', 'https://api.example.com/users') + * .setAttribute('http.status_code', 201) + * .setAttribute('user.id', '12345') + * .setAttribute('request.size', 1024); + * ``` */ setAttribute(key: string, value: OTelAttributeValue): this; /** - * Sets attributes to the span. + * Sets multiple attributes on the span at once using an attributes object. + * + * This method allows efficient batch setting of multiple attributes in a single call. + * All attributes in the provided object will be added to the span, supplementing any + * existing attributes (duplicate keys will be overwritten). + * + * @param attributes - An object containing key-value pairs to set as span attributes * - * @param attributes - the attributes that will be added, null or undefined attribute - * values are considered to be invalid and will result in undefined behavior. + * @returns This span instance for method chaining + * + * @remarks + * - Null or undefined attribute values are invalid and will result in undefined behavior + * - More efficient than multiple `setAttribute` calls for bulk operations + * - Existing attributes with the same keys will be overwritten + * + * @example + * ```typescript + * const span = tracer.startSpan('database-query'); + * + * // Set multiple attributes efficiently + * span.setAttributes({ + * 'db.system': 'postgresql', + * 'db.name': 'user_database', + * 'db.table': 'users', + * 'db.operation': 'SELECT', + * 'db.rows_affected': 5, + * 'query.duration_ms': 15.7 + * }); + * ``` */ setAttributes(attributes: IOTelAttributes): this; /** - * Adds an event to the Span. + * The {@link IAttributeContainer | attribute container} associated with this span, providing + * advanced attribute management capabilities. Rather than using the {@link IReadableSpan#attributes} + * directly which returns a readonly {@link IOTelAttributes} map that is a snapshot of the attributes at + * the time of access, the attribute container offers methods to get, set, delete, and iterate over attributes + * with fine-grained control. + * It is recommended that you only access the {@link IReadableSpan#attributes} property sparingly due to the + * performance cost of taking a snapshot of all attributes. + */ + readonly attribContainer: IAttributeContainer; + + /** + * Adds an event to the span with optional attributes and timestamp. + * + * **Note: This method is currently not implemented and events will be dropped.** + * + * Events represent significant points in time during the span's execution. + * They provide additional context about what happened during the operation, + * such as cache hits/misses, validation steps, or other notable occurrences. + * + * @param name - The name of the event, should be descriptive of what occurred + * @param attributesOrStartTime - Event attributes object, or start time if third parameter is undefined + * @param startTime - Optional start time of the event; if not provided, current time is used * - * @param name - the name of the event. - * @param attributesOrStartTime - the attributes that will be added; these are - * associated with this event. Can be also a start time - * if type is {@link OTelTimeInput} and 3rd param is undefined - * @param startTime - start time of the event. + * @returns This span instance for method chaining + * + * @remarks + * - **Current implementation drops events - not yet supported** + * - Events are timestamped occurrences within a span's lifecycle + * - Useful for marking significant points like cache hits, retries, or validation steps + * - Should not be used for high-frequency events due to performance impact + * + * @example + * ```typescript + * const span = tracer.startSpan('user-registration'); + * + * // Add events to mark significant points + * span.addEvent('validation.started') + * .addEvent('validation.completed', { + * 'validation.result': 'success', + * 'validation.duration_ms': 23 + * }) + * .addEvent('database.save.started') + * .addEvent('database.save.completed', { + * 'db.rows_affected': 1 + * }); + * ``` */ addEvent(name: string, attributesOrStartTime?: IOTelAttributes | OTelTimeInput, startTime?: OTelTimeInput): this; /** - * Adds a single link to the span. + * Adds a single link to the span connecting it to another span. + * + * **Note: This method is currently not implemented and links will be dropped.** * - * Links added after the creation will not affect the sampling decision. - * It is preferred span links be added at span creation. + * Links establish relationships between spans that are not in a typical parent-child + * relationship. They are useful for connecting spans across different traces or + * for representing batch operations where multiple spans are related but not nested. * - * @param link - the link to add. + * @param link - The link object containing span context and optional attributes + * + * @returns This span instance for method chaining + * + * @remarks + * - **Current implementation drops links - not yet supported** + * - Links added after span creation do not affect sampling decisions + * - Prefer adding links during span creation when possible + * - Useful for batch operations, fan-out scenarios, or cross-trace relationships + * + * @example + * ```typescript + * const span = tracer.startSpan('batch-processor'); + * + * // Link to related spans from a batch operation + * span.addLink({ + * context: relatedSpan.spanContext(), + * attributes: { + * 'link.type': 'batch_item', + * 'batch.index': 1 + * } + * }); + * ``` */ addLink(link: IOTelLink): this; - + /** - * Adds multiple links to the span. + * Adds multiple links to the span in a single operation. + * + * **Note: This method is currently not implemented and links will be dropped.** + * + * This is an efficient way to establish multiple relationships between this span + * and other spans. Particularly useful for batch operations, fan-out scenarios, + * or when a single operation needs to reference multiple related operations. + * + * @param links - An array of link objects to add to the span * - * Links added after the creation will not affect the sampling decision. - * It is preferred span links be added at span creation. + * @returns This span instance for method chaining * - * @param links - the links to add. + * @remarks + * - **Current implementation drops links - not yet supported** + * - More efficient than multiple `addLink` calls for bulk operations + * - Links added after span creation do not affect sampling decisions + * - Consider span creation time linking for sampling-sensitive scenarios + * + * @example + * ```typescript + * const span = tracer.startSpan('aggregate-results'); + * + * // Link to multiple related spans from parallel operations + * span.addLinks([ + * { + * context: span1.spanContext(), + * attributes: { 'operation.type': 'data_fetch', 'source': 'database' } + * }, + * { + * context: span2.spanContext(), + * attributes: { 'operation.type': 'data_fetch', 'source': 'cache' } + * }, + * { + * context: span3.spanContext(), + * attributes: { 'operation.type': 'data_transform', 'stage': 'preprocessing' } + * } + * ]); + * ``` */ addLinks(links: IOTelLink[]): this; - + /** - * Sets a status to the span. If used, this will override the default Span - * status. Default is {@link eOTelSpanStatusCode.UNSET}. SetStatus overrides the value - * of previous calls to SetStatus on the Span. + * Sets the status of the span to indicate the success or failure of the operation. + * + * The span status provides a standardized way to indicate whether the operation + * completed successfully, encountered an error, or is in an unknown state. + * This status is used by observability tools to provide meaningful insights + * about system health and operation outcomes. + * + * @param status - The status object containing code and optional message + * + * @returns This span instance for method chaining + * + * @remarks + * - Default status is UNSET until explicitly set + * - Setting status overrides any previous status values + * - ERROR status should be accompanied by a descriptive message when possible + * - Status should reflect the final outcome of the operation + * + * @example + * ```typescript + * const span = tracer.startSpan('payment-processing'); * - * @param status - the SpanStatus to set. + * try { + * const result = await processPayment(paymentData); + * + * // Indicate successful completion + * span.setStatus({ + * code: SpanStatusCode.OK + * }); + * + * } catch (error) { + * // Indicate operation failed + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: 'Payment processing failed: ' + error.message + * }); + * + * span.recordException(error); + * } + * ``` */ setStatus(status: IOTelSpanStatus): this; /** - * Updates the Span name. + * Updates the name of the span, overriding the name provided during creation. + * + * Span names should be descriptive and represent the operation being performed. + * Updating the name can be useful when the operation's scope becomes clearer + * during execution, or when implementing generic spans that need specific naming + * based on runtime conditions. + * + * @param name - The new name for the span, should be descriptive of the operation * - * This will override the name provided when the span was created. + * @returns This span instance for method chaining * - * Upon this update, any sampling behavior based on Span name will depend on - * the implementation. + * @remarks + * - Name updates may affect sampling behavior depending on implementation + * - Choose names that are meaningful but not too specific to avoid cardinality issues + * - Follow naming conventions consistent with your observability strategy + * - Consider the impact on existing traces and dashboards when changing names * - * @param name - the Span name. + * @example + * ```typescript + * const span = tracer.startSpan('generic-operation'); + * + * // Update name based on runtime determination + * if (operationType === 'user-registration') { + * span.updateName('user-registration'); + * span.setAttribute('operation.type', 'registration'); + * } else if (operationType === 'user-login') { + * span.updateName('user-authentication'); + * span.setAttribute('operation.type', 'authentication'); + * } + * ``` */ updateName(name: string): this; /** - * Marks the end of Span execution. + * Marks the end of the span's execution and records the end timestamp. + * + * This method finalizes the span and makes it available for export to tracing systems. + * Once ended, the span should not be used for further operations. The span's duration + * is calculated from its start time to the end time provided or current time. + * + * @param endTime - Optional end time; if not provided, current time is used + * + * @remarks + * - This method does NOT return `this` to discourage chaining after span completion + * - Ending a span has no effect on child spans, which may continue running + * - Child spans can be ended independently after their parent has ended + * - The span becomes eligible for export once ended + * - Calling end() multiple times has no additional effect * - * Call to End of a Span MUST not have any effects on child spans. Those may - * still be running and can be ended later. + * @example + * ```typescript + * const span = tracer.startSpan('file-processing'); * - * Do not return `this`. The Span generally should not be used after it - * is ended so chaining is not desired in this context. + * try { + * // Perform the operation + * const result = await processFile(filePath); * - * @param endTime - the time to set as Span's end time. If not provided, - * use the current time as the span's end time. + * // Record success + * span.setStatus({ code: SpanStatusCode.OK }); + * span.setAttribute('file.size', result.size); + * + * } catch (error) { + * span.recordException(error); + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: error.message + * }); + * } finally { + * // Always end the span + * span.end(); + * // Don't use span after this point + * } + * + * // Custom end time example + * const customEndTime = Date.now() * 1000000; // nanoseconds + * span.end(customEndTime); + * ``` */ end(endTime?: OTelTimeInput): void; /** - * Returns the flag whether this span will be recorded. + * Returns whether this span is actively recording information. + * + * A recording span accepts and stores attributes, events, status, and other span data. + * Non-recording spans (typically due to sampling decisions) may ignore operations + * like setAttribute() to optimize performance. This method allows conditional + * logic to avoid expensive operations on non-recording spans. + * + * @returns True if the span is actively recording information, false otherwise + * + * @remarks + * - Recording status is typically determined at span creation time + * - Non-recording spans still provide valid span context for propagation + * - Use this check to avoid expensive attribute calculations for non-recording spans + * - Recording status remains constant throughout the span's lifetime * - * @returns true if this Span is active and recording information like events - * with the `AddEvent` operation and attributes using `setAttributes`. + * @example + * ```typescript + * const span = tracer.startSpan('data-processing'); + * + * // Only perform expensive operations if span is recording + * if (span.isRecording()) { + * const metadata = await expensiveMetadataCalculation(); + * span.setAttributes({ + * 'process.metadata': JSON.stringify(metadata), + * 'process.complexity': metadata.complexity, + * 'process.estimated_duration': metadata.estimatedMs + * }); + * } + * + * // Always safe to set basic attributes + * span.setAttribute('process.started', true); + * ``` */ isRecording(): boolean; /** - * Sets exception as a span event - * @param exception - the exception the only accepted values are string or Error - * @param time - the time to set as Span's event time. If not provided, - * use the current time. + * Records an exception as a span event with automatic error status handling. + * + * This method captures exception information and automatically creates a span event + * with standardized exception attributes. It's the recommended way to handle errors + * within spans, providing consistent error reporting across the application. + * + * @param exception - The exception to record; accepts string messages or Error objects + * @param time - Optional timestamp for when the exception occurred; defaults to current time + * + * @remarks + * - Automatically extracts exception type, message, and stack trace when available + * - Creates a standardized span event with exception details + * - Does NOT automatically set span status to ERROR - call setStatus() explicitly if needed + * - Exception events are useful for debugging and error analysis + * + * @example + * ```typescript + * const span = tracer.startSpan('risky-operation'); + * + * try { + * await performRiskyOperation(); + * span.setStatus({ code: SpanStatusCode.OK }); + * + * } catch (error) { + * // Record the exception details + * span.recordException(error); + * + * // Explicitly set error status + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: 'Operation failed due to: ' + error.message + * }); + * + * // Re-throw if needed + * throw error; + * } finally { + * span.end(); + * } + * + * // Recording string exceptions + * span.recordException('Custom error message occurred'); + * + * // Recording with custom timestamp + * const errorTime = Date.now() * 1000000; // nanoseconds + * span.recordException(error, errorTime); + * ``` */ recordException(exception: OTelException, time?: OTelTimeInput): void; } diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelSpanContext.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelSpanContext.ts index 15d468a68..66491976c 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelSpanContext.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelSpanContext.ts @@ -1,32 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IOTelTraceState } from "./IOTelTraceState"; +import { IDistributedTraceInit } from "../../ai/IDistributedTraceContext"; /** * A SpanContext represents the portion of a {@link IOTelSpan} which must be * serialized and propagated along side of a {@link IOTelBaggage}. */ -export interface IOTelSpanContext { - /** - * The ID of the trace that this span belongs to. It is worldwide unique - * with practically sufficient probability by being made as 16 randomly - * generated bytes, encoded as a 32 lowercase hex characters corresponding to - * 128 bits. - */ - traceId: string; - - /** - * The ID of the Span. It is globally unique with practically sufficient - * probability by being made as 8 randomly generated bytes, encoded as a 16 - * lowercase hex characters corresponding to 64 bits. - */ - spanId: string; - - /** - * Only true if the SpanContext was propagated from a remote parent. - */ - isRemote?: boolean; +export interface IOTelSpanContext extends IDistributedTraceInit { /** * Trace flags to propagate. @@ -39,22 +20,4 @@ export interface IOTelSpanContext { * see {@link eW3CTraceFlags} for valid flag values. */ traceFlags: number; - - /** - * Tracing-system-specific info to propagate. - * - * The tracestate field value is a `list` as defined below. The `list` is a - * series of `list-members` separated by commas `,`, and a list-member is a - * key/value pair separated by an equals sign `=`. Spaces and horizontal tabs - * surrounding `list-members` are ignored. There can be a maximum of 32 - * `list-members` in a `list`. - * More Info: https://www.w3.org/TR/trace-context/#tracestate-field - * - * Examples: - * Single tracing system (generic format): - * tracestate: rojo=00f067aa0ba902b7 - * Multiple tracing systems (with different formatting): - * tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE - */ - traceState?: IOTelTraceState; -} +} \ No newline at end of file diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelSpanCtx.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelSpanCtx.ts index 16d65a801..a7ed6a06d 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelSpanCtx.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelSpanCtx.ts @@ -1,14 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { OTelTimeInput } from "../../../types/time"; +import { OTelException } from "../../IException"; +import { OTelTimeInput } from "../../IOTelHrTime"; +import { IDistributedTraceContext } from "../../ai/IDistributedTraceContext"; import { IOTelApi } from "../IOTelApi"; import { IOTelAttributes } from "../IOTelAttributes"; import { IOTelContext } from "../context/IOTelContext"; import { IOTelResource } from "../resources/IOTelResource"; import { IOTelInstrumentationScope } from "./IOTelInstrumentationScope"; import { IOTelLink } from "./IOTelLink"; -import { IOTelSpanContext } from "./IOTelSpanContext"; import { IReadableSpan } from "./IReadableSpan"; /** @@ -23,31 +24,31 @@ export interface IOTelSpanCtx { /** * The current {@link IOTelResource} instance to use for this Span Context */ - resource: IOTelResource; + resource?: IOTelResource; /** * The current {@link IOTelInstrumentationScope} instrumentationScope instance to * use for this Span Context */ - instrumentationScope: IOTelInstrumentationScope; + instrumentationScope?: IOTelInstrumentationScope; /** * The context for the current instance */ - context: IOTelContext; + context?: IOTelContext; /* - * The current {@link IOTelSpanContext} instance to associated with the span + * The current {@link IDistributedTraceContext} instance to associated with the span * used to create the span. */ - spanContext: IOTelSpanContext; + spanContext: IDistributedTraceContext; /** * Identifies the user provided start time of the span */ startTime?: OTelTimeInput; - parentSpanContext?: IOTelSpanContext; + parentSpanContext?: IDistributedTraceContext; attributes?: IOTelAttributes; @@ -63,4 +64,16 @@ export interface IOTelSpanCtx { * @returns */ onEnd?: (span: IReadableSpan) => void; + + /** + * When an exception is recorded via the span's recordException API this callback will be called + * to process the exception. Unlike the OpenTelemetry spec this callback also provides the span instance + * to allow implementations to associate the exception with the span as needed. + * It is also called immediately when recordException is called rather than waiting until the span ends. + * @param span - The span associated with the exception + * @param exception - The exception to process + * @param time - The time the exception occurred + * @returns + */ + onException?: (span: IReadableSpan, exception: OTelException, time?: OTelTimeInput) => void; } diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelSpanOptions.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelSpanOptions.ts index 256712a5a..7ae509c9f 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelSpanOptions.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelSpanOptions.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { OTelSpanKind } from "../../../enums/otel/OTelSpanKind"; -import { OTelTimeInput } from "../../../types/time"; +import { OTelTimeInput } from "../../IOTelHrTime"; import { IOTelAttributes } from "../IOTelAttributes"; import { IOTelLink } from "./IOTelLink"; @@ -12,12 +12,16 @@ import { IOTelLink } from "./IOTelLink"; */ export interface IOTelSpanOptions { /** - * The SpanKind of a span - * @default {@link eOTelSpanKind.INTERNAL} + * The SpanKind of a span of this span, this is used to specify + * the relationship between the span and its parent span. + * @see {@link eOTelSpanKind} for possible values. + * @default eOTelSpanKind.INTERNAL */ kind?: OTelSpanKind; - - /** A span's attributes */ + + /** + * A span's attributes + */ attributes?: IOTelAttributes; /** {@link IOTelLink}s span to other spans */ @@ -28,4 +32,7 @@ export interface IOTelSpanOptions { /** The new span should be a root span. (Ignore parent from context). */ root?: boolean; + + /** Specify whether the span should be a recording span, default is true */ + recording?: boolean; } diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelTimedEvent.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelTimedEvent.ts index c9b612133..c226b4ca0 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelTimedEvent.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelTimedEvent.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IOTelHrTime } from "../../../types/time"; +import { IOTelHrTime } from "../../../interfaces/IOTelHrTime"; import { IOTelAttributes } from "../IOTelAttributes"; export interface IOTelTimedEvent { diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelTraceApi.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelTraceApi.ts index 9aaa878f8..38f00765b 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelTraceApi.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelTraceApi.ts @@ -1,17 +1,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IDistributedTraceContext } from "../../.."; +import { IDistributedTraceInit } from "../../ai/IDistributedTraceContext"; +import { ISpanScope } from "../../ai/ITraceProvider"; import { IOTelContext } from "../context/IOTelContext"; import { IOTelSpan } from "./IOTelSpan"; import { IOTelSpanContext } from "./IOTelSpanContext"; import { IOTelTracer } from "./IOTelTracer"; import { IOTelTracerOptions } from "./IOTelTracerOptions"; import { IOTelTracerProvider } from "./IOTelTracerProvider"; +import { IReadableSpan } from "./IReadableSpan"; /** - * IOTelTraceApi provides an interface definition for the OpenTelemetry TraceAPI + * ITraceApi provides an interface definition which is simular to the OpenTelemetry TraceAPI */ -export interface IOTelTraceApi { +export interface ITraceApi { /** * Set the current global tracer for the current API instance. * @param provider - The {@link IOTelTracerProvider} to be set as the global tracer provider for this API instance @@ -42,18 +46,18 @@ export interface IOTelTraceApi { disable(): void; /** - * Wrap the given {@link IOTelSpanContext} in a new non-recording {@link IOTelSpan} + * Wrap the given {@link IDistributedTraceContext} in a new non-recording {@link IReadableSpan} * - * @param spanContext - The {@link IOTelSpanContext} to be wrapped - * @returns a new non-recording {@link IOTelSpan} with the provided context + * @param spanContext - The {@link IDistributedTraceContext} to be wrapped + * @returns a new non-recording {@link IReadableSpan} with the provided context */ - wrapSpanContext(spanContext: IOTelSpanContext): IOTelSpan; + wrapSpanContext(spanContext: IDistributedTraceContext | IDistributedTraceInit | IOTelSpanContext): IReadableSpan; /** - * Returns true if this {@link IOTelSpanContext} is valid. - * @return true if this {@link IOTelSpanContext} is valid. + * Returns true if this {@link IDistributedTraceContext} is valid. + * @return true if this {@link IDistributedTraceContext} is valid. */ - isSpanContextValid(spanContext: IOTelSpanContext): boolean; + isSpanContextValid(spanContext: IDistributedTraceContext | IDistributedTraceInit | IOTelSpanContext): boolean; /** * Remove current span stored in the context @@ -67,12 +71,12 @@ export interface IOTelTraceApi { * * @param context - The {@link IOTelContext} to get span from */ - getSpan(context: IOTelContext): IOTelSpan | undefined; + getSpan(context: IOTelContext): IReadableSpan | undefined; /** * Gets the span from the current context, if one exists. */ - getActiveSpan(): IOTelSpan | undefined; + getActiveSpan(): IReadableSpan | undefined | null; /** * Wrap span context in a NoopSpan and set as span in a new @@ -88,7 +92,7 @@ export interface IOTelTraceApi { * * @param context - The {@Link IOTelContext} to get values from */ - getSpanContext(context: IOTelContext): IOTelSpanContext | undefined + getSpanContext(context: IOTelContext): IDistributedTraceContext | undefined /** * Set the span on a context @@ -97,4 +101,11 @@ export interface IOTelTraceApi { * @param span - The {@link IOTelSpan} to set as the active span for the context */ setSpan(context: IOTelContext, span: IOTelSpan): IOTelContext; + + /** + * Set or clear the current active span. + * @param span - The span to set as the active span, or null/undefined to clear the active span. + * @return An ISpanScope instance returned by the host, or void if there is no defined host. + */ + setActiveSpan(span: IReadableSpan | undefined | null): ISpanScope | undefined | null; } diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelTracer.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelTracer.ts index f0e91a3d6..2fe1c8fe1 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelTracer.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelTracer.ts @@ -4,80 +4,150 @@ import { IOTelContext } from "../context/IOTelContext"; import { IOTelSpan } from "./IOTelSpan"; import { IOTelSpanOptions } from "./IOTelSpanOptions"; +import { IReadableSpan } from "./IReadableSpan"; /** - * Implementations of the ITracer definition is used to create and manage {@link IOTelSpan} or - * {@link IReadableSpan} which track a trace. - * @see {@link IOTelSpan} - * @see {@link IOTelSpanOptions} + * OpenTelemetry tracer interface for creating and managing spans within a trace. + * + * A tracer is responsible for creating spans that represent units of work within a distributed system. + * Each tracer is typically associated with a specific instrumentation library or component, + * allowing for fine-grained control over how different parts of an application generate telemetry. + * + * @example + * ```typescript + * // Get a tracer instance + * const tracer = otelApi.getTracer('my-service'); + * + * // Create a simple span + * const span = tracer.startSpan('database-query'); + * span.setAttribute('db.operation', 'SELECT'); + * span.end(); + * + * // Create an active span with automatic context management + * tracer.startActiveSpan('process-request', (span) => { + * span.setAttribute('request.id', '12345'); + * + * // Any spans created within this block will be children of this span + * processRequest(); + * + * span.end(); + * }); + * ``` + * + * @see {@link IReadableSpan} - Interface for individual spans + * @see {@link IOTelSpanOptions} - Configuration options for span creation * @see {@link IOTelContext} + * + * @since 3.4.0 */ export interface IOTelTracer { /** - * Starts a new {@link IOTelSpan}. Start the span without setting it on context. + * Creates and starts a new span without setting it as the active span in the current context. * - * This method do NOT modify the current Context. + * This method creates a span but does NOT modify the current execution context. + * The caller is responsible for managing the span's lifecycle, including calling `end()` + * when the operation completes. + * + * @param name - The name of the span, should be descriptive of the operation being traced + * @param options - Optional configuration for span creation (parent context, attributes, etc.) + * @param context - Optional context to use for extracting the parent span; if not provided, uses current context + * + * @returns The newly created span, or null if span creation failed + * + * @remarks + * - The returned span must be manually ended by calling `span.end()` + * - This span will not automatically become the parent for spans created in nested operations + * - Use `startActiveSpan` if you want automatic context management * - * @param name - The name of the span - * @param options - SpanOptions used for span creation - * @param context - Context to use to extract parent - * @returns Span The newly created span * @example - * const span = tracer.startSpan('op'); - * span.setAttribute('key', 'value'); - * span.end(); + * ```typescript + * const span = tracer.startSpan('database-operation'); + * if (span) { + * try { + * span.setAttribute('db.table', 'users'); + * span.setAttribute('db.operation', 'SELECT'); + * + * // Perform database operation + * const result = await db.query('SELECT * FROM users'); + * + * span.setAttributes({ + * 'db.rows_affected': result.length, + * 'operation.success': true + * }); + * } catch (error) { + * span.setStatus({ + * code: SpanStatusCode.ERROR, + * message: error.message + * }); + * span.recordException(error); + * } finally { + * span.end(); // Always end the span + * } + * } + * ``` */ - startSpan(name: string, options?: IOTelSpanOptions, context?: IOTelContext): IOTelSpan; - + startSpan(name: string, options?: IOTelSpanOptions): IReadableSpan | null; + /** - * Starts a new {@link IOTelSpan} and calls the given function passing it the - * created span as first argument. - * Additionally the new span gets set in context and this context is activated - * for the duration of the function call. + * Creates and starts a new span, sets it as the active span in the current context, + * and executes a provided function within this context. + * + * This method creates a span, makes it active during the execution of the provided + * function, and automatically ends the span when the function completes (or throws). + * This provides automatic span lifecycle management and context propagation. + * + * @param name - The name of the span, should be descriptive of the operation being traced + * @param options - Optional configuration for span creation (parent context, attributes, etc.) + * @param fn - The function to execute within the span's active context + * + * @returns The result of executing the provided function + * + * @remarks + * - The span is automatically ended when the function completes or throws an exception + * - The span becomes the active parent for any spans created within the function + * - If the function throws an error, the span status is automatically set to ERROR + * - This is the recommended method for most tracing scenarios due to automatic lifecycle management + * - Multiple overloads available for different parameter combinations * - * @param name - The name of the span - * @param options - SpanOptions used for span creation - * @param context - Context to use to extract parent - * @param fn - function called in the context of the span and receives the newly created span as an argument - * @returns return value of fn - * @example - * ```ts - * const something = tracer.startActiveSpan('op', span => { - * try { - * do some work - * span.setStatus({code: SpanStatusCode.OK}); - * return something; - * } catch (err) { - * span.setStatus({ - * code: SpanStatusCode.ERROR, - * message: err.message, - * }); - * throw err; - * } finally { - * span.end(); - * } - * }); - *``` * @example - * ```ts - * const span = tracer.startActiveSpan('op', span => { - * try { - * do some work - * return span; - * } catch (err) { - * span.setStatus({ - * code: SpanStatusCode.ERROR, - * message: err.message, - * }); - * throw err; - * } - * }); - * do some more work - * span.end(); + * ```typescript + * // Synchronous operation with just name and function + * const result = tracer.startActiveSpan('user-service', (span) => { + * span.setAttribute('operation', 'get-user-details'); + * return { user: getUserData(), timestamp: new Date().toISOString() }; + * }); + * + * // With options + * const result2 = tracer.startActiveSpan('database-query', + * { attributes: { 'db.table': 'users' } }, + * (span) => { + * span.setAttribute('db.operation', 'SELECT'); + * return database.getUser('123'); + * } + * ); + * + * // With full context control + * const result3 = tracer.startActiveSpan('external-api', + * { attributes: { 'service.name': 'payment-api' } }, + * currentContext, + * async (span) => { + * try { + * const response = await fetch('/api/payment'); + * span.setAttributes({ + * 'http.status_code': response.status, + * 'operation.success': response.ok + * }); + * return response.json(); + * } catch (error) { + * span.setAttribute('error.type', error.constructor.name); + * throw error; // Error automatically recorded + * } + * } + * ); * ``` */ - startActiveSpan unknown>(name: string, fn: F): ReturnType; - startActiveSpan unknown>(name: string, options: IOTelSpanOptions,fn: F ): ReturnType; - startActiveSpan unknown>(name: string,options: IOTelSpanOptions, context: IOTelContext, fn: F): ReturnType; + startActiveSpan unknown>(name: string, fn: F): ReturnType; + startActiveSpan unknown>(name: string, options: IOTelSpanOptions,fn: F ): ReturnType; + startActiveSpan unknown>(name: string,options: IOTelSpanOptions, context: IOTelContext, fn: F): ReturnType; } diff --git a/shared/otel-core/src/interfaces/otel/trace/IOTelTracerProvider.ts b/shared/otel-core/src/interfaces/otel/trace/IOTelTracerProvider.ts index c7967eddf..c986902ee 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IOTelTracerProvider.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IOTelTracerProvider.ts @@ -1,9 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IPromise } from "@nevware21/ts-async"; import { IOTelTracer } from "./IOTelTracer"; import { IOTelTracerOptions } from "./IOTelTracerOptions"; +/** + * OpenTelemetry Trace API for getting tracers. + * This provides the standard OpenTelemetry trace API entry point. + */ export interface IOTelTracerProvider { /** * Returns a Tracer, creating one if one with the given name and version is @@ -18,5 +23,17 @@ export interface IOTelTracerProvider { * @returns Tracer A Tracer with the given name and version */ getTracer(name: string, version?: string, options?: IOTelTracerOptions): IOTelTracer; + + /** + * Forces the tracer provider to flush any buffered data. + * @returns A promise that resolves when the flush is complete. + */ + forceFlush?: () => IPromise | void; + + /** + * Shuts down the tracer provider and releases any resources. + * @returns A promise that resolves when the shutdown is complete. + */ + shutdown?: () => IPromise | void; } diff --git a/shared/otel-core/src/interfaces/otel/trace/IReadableSpan.ts b/shared/otel-core/src/interfaces/otel/trace/IReadableSpan.ts index df19d1ebb..cf8f4eed3 100644 --- a/shared/otel-core/src/interfaces/otel/trace/IReadableSpan.ts +++ b/shared/otel-core/src/interfaces/otel/trace/IReadableSpan.ts @@ -2,13 +2,13 @@ // Licensed under the MIT License. import { OTelSpanKind } from "../../../enums/otel/OTelSpanKind"; -import { IOTelHrTime } from "../../../types/time"; +import { IOTelHrTime } from "../../../interfaces/IOTelHrTime"; +import { IDistributedTraceContext } from "../../ai/IDistributedTraceContext"; import { IOTelAttributes } from "../IOTelAttributes"; import { IOTelResource } from "../resources/IOTelResource"; import { IOTelInstrumentationScope } from "./IOTelInstrumentationScope"; import { IOTelLink } from "./IOTelLink"; import { IOTelSpan } from "./IOTelSpan"; -import { IOTelSpanContext } from "./IOTelSpanContext"; import { IOTelSpanStatus } from "./IOTelSpanStatus"; import { IOTelTimedEvent } from "./IOTelTimedEvent"; @@ -33,12 +33,20 @@ export interface IReadableSpan extends IOTelSpan { * Identifies the type (or kind) that this span is representing. */ readonly kind: OTelSpanKind; - readonly spanContext: () => IOTelSpanContext; + readonly spanContext: () => IDistributedTraceContext; readonly parentSpanId?: string; - readonly parentSpanContext?: IOTelSpanContext; + readonly parentSpanContext?: IDistributedTraceContext; readonly startTime: IOTelHrTime; readonly endTime: IOTelHrTime; readonly status: IOTelSpanStatus; + + /** + * Provides a snapshot of the span's attributes at the time this span was ended. + * @returns A read-only snapshot of the span's attributes + * @remarks + * It is recommended that you only access this property sparingly due to the + * performance cost of taking a snapshot of all attributes. + */ readonly attributes: IOTelAttributes; readonly links: IOTelLink[]; readonly events: IOTelTimedEvent[]; diff --git a/shared/otel-core/src/internal/EventHelpers.ts b/shared/otel-core/src/internal/EventHelpers.ts index 4a5b657b1..19af8c335 100644 --- a/shared/otel-core/src/internal/EventHelpers.ts +++ b/shared/otel-core/src/internal/EventHelpers.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { arrForEach, arrIndexOf, getDocument, getWindow, isArray, objForEachKey, objKeys } from "@nevware21/ts-utils"; +import { + ICachedValue, arrForEach, arrIndexOf, createCachedValue, getDocument, getWindow, isArray, objForEachKey, objKeys +} from "@nevware21/ts-utils"; import { STR_EMPTY } from "../constants/InternalConstants"; import { createElmNodeData, createUniqueNamespace } from "../utils/DataCacheHelper"; @@ -10,15 +12,33 @@ const strAttachEvent = "attachEvent"; const strAddEventHelper = "addEventListener"; const strDetachEvent = "detachEvent"; const strRemoveEventListener = "removeEventListener"; -const strEvents = "events" +const strEvents = "events"; const strVisibilityChangeEvt: string = "visibilitychange"; const strPageHide: string = "pagehide"; const strPageShow: string = "pageshow"; const strUnload: string = "unload"; const strBeforeUnload: string = "beforeunload"; -const strPageHideNamespace = createUniqueNamespace("aiEvtPageHide"); -const strPageShowNamespace = createUniqueNamespace("aiEvtPageShow"); +let _strPageHideNamespace: ICachedValue; +let _strPageShowNamespace: ICachedValue; + +/*#__NO_SIDE_EFFECTS__*/ +function _getPageHideNamespace() { + // Note: Using a cached value instead of a lazy value as we want to ensure that the namespace is consistent + // across multiple calls as the `getLazy()` supports runtime invalidation via `setBypassLazyCache()` which would + // result in different namespaces being returned. + !_strPageHideNamespace && (_strPageHideNamespace = createCachedValue(createUniqueNamespace("aiEvtPageHide"))); + return _strPageHideNamespace.v; +} + +/*#__NO_SIDE_EFFECTS__*/ +function _getPageShowNamespace() { + // Note: Using a cached value instead of a lazy value as we want to ensure that the namespace is consistent + // across multiple calls as the `getLazy()` supports runtime invalidation via `setBypassLazyCache()` which would + // result in different namespaces being returned. + !_strPageShowNamespace && (_strPageShowNamespace = createCachedValue(createUniqueNamespace("aiEvtPageShow"))); + return _strPageShowNamespace.v; +} const rRemoveEmptyNs = /\.[\.]+/g; const rRemoveTrailingEmptyNs = /[\.]+$/; @@ -41,9 +61,20 @@ interface IAiEvents { [name: string]: IRegisteredEvent[] } -const elmNodeData = createElmNodeData("events"); -const eventNamespace = /^([^.]*)(?:\.(.+)|)/ +let _elmNodeData: ICachedValue>; + +/*#__NO_SIDE_EFFECTS__*/ +function _getElmNodeData() { + // Note: Using a cached value instead of a lazy value as we want to ensure that the namespace is consistent + // across multiple calls as the `getLazy()` supports runtime invalidation via `setBypassLazyCache()` which would + // result in different namespaces being returned. + !_elmNodeData && (_elmNodeData = createCachedValue(createElmNodeData("events"))); + return _elmNodeData.v; +} + +const eventNamespace = /^([^.]*)(?:\.(.+)|)/; +/*#__NO_SIDE_EFFECTS__*/ function _normalizeNamespace(name: string) { if (name && name.replace) { return name.replace(/^[\s\.]+|(?=[\s\.])[\.\s]+$/g, STR_EMPTY); @@ -52,6 +83,7 @@ function _normalizeNamespace(name: string) { return name; } +/*#__NO_SIDE_EFFECTS__*/ function _getEvtNamespace(eventName: string | undefined, evtNamespace?: string | string[] | null): IEventDetails { if (evtNamespace) { let theNamespace: string = STR_EMPTY; @@ -103,9 +135,10 @@ export interface _IRegisteredEvents { * @param evtNamespace - [Optional] Additional namespace(s) to append to the event listeners so they can be uniquely identified and removed based on this namespace, * if the eventName also includes a namespace the namespace(s) are merged into a single namespace */ +/*#__NO_SIDE_EFFECTS__*/ export function __getRegisteredEvents(target: any, eventName?: string, evtNamespace?: string | string[]): _IRegisteredEvents[] { let theEvents: _IRegisteredEvents[] = []; - let eventCache = elmNodeData.get(target, strEvents, {}, false); + let eventCache = _getElmNodeData().get(target, strEvents, {}, false); let evtName = _getEvtNamespace(eventName, evtNamespace); objForEachKey(eventCache, (evtType, registeredEvents) => { @@ -125,8 +158,9 @@ export function __getRegisteredEvents(target: any, eventName?: string, evtNamesp } // Exported for internal unit testing only +/*#__NO_SIDE_EFFECTS__*/ function _getRegisteredEvents(target: any, evtName: string, addDefault: boolean = true): IRegisteredEvent[] { - let aiEvts = elmNodeData.get(target, strEvents, {}, addDefault); + let aiEvts = _getElmNodeData().get(target, strEvents, {}, addDefault); let registeredEvents = aiEvts[evtName]; if (!registeredEvents) { registeredEvents = aiEvts[evtName] = []; @@ -183,18 +217,19 @@ function _unregisterEvents(target: any, evtName: IEventDetails, unRegFn: (regEve if (evtName.type) { _doUnregister(target, _getRegisteredEvents(target, evtName.type), evtName, unRegFn); } else { - let eventCache = elmNodeData.get(target, strEvents, {}); + let eventCache = _getElmNodeData().get(target, strEvents, {}); objForEachKey(eventCache, (evtType, events) => { _doUnregister(target, events, evtName, unRegFn); }); // Cleanup if (objKeys(eventCache).length === 0) { - elmNodeData.kill(target, strEvents); + _getElmNodeData().kill(target, strEvents); } } } +/*#__NO_SIDE_EFFECTS__*/ export function mergeEvtNamespace(theNamespace: string, namespaces?: string | string[] | null): string | string[] { let newNamespaces: string | string[]; @@ -233,7 +268,7 @@ export function eventOn(target: T, eventName: string, handlerRef: any, evtNam let evtName = _getEvtNamespace(eventName, evtNamespace); result = _doAttach(target, evtName, handlerRef, useCapture); - if (result && elmNodeData.accept(target)) { + if (result && _getElmNodeData().accept(target)) { let registeredEvent: IRegisteredEvent = { guid: _guid++, evtName: evtName, @@ -472,7 +507,7 @@ export function addPageHideEventListener(listener: any, excludeEvents?: string[] } // add the unique page show namespace to any provided namespace so we can only remove the ones added by "pagehide" - let newNamespaces = mergeEvtNamespace(strPageHideNamespace, evtNamespace); + let newNamespaces = mergeEvtNamespace(_getPageHideNamespace(), evtNamespace); let pageUnloadAdded = _addEventListeners([strPageHide], listener, excludeEvents, newNamespaces); if (!excludeEvents || arrIndexOf(excludeEvents, strVisibilityChangeEvt) === -1) { @@ -497,7 +532,7 @@ export function addPageHideEventListener(listener: any, excludeEvents?: string[] export function removePageHideEventListener(listener: any, evtNamespace?: string | string[] | null) { // add the unique page show namespace to any provided namespace so we only remove the ones added by "pagehide" - let newNamespaces = mergeEvtNamespace(strPageHideNamespace, evtNamespace); + let newNamespaces = mergeEvtNamespace(_getPageHideNamespace(), evtNamespace); removeEventListeners([strPageHide], listener, newNamespaces); removeEventListeners([strVisibilityChangeEvt], null, newNamespaces); } @@ -523,7 +558,7 @@ export function addPageShowEventListener(listener: any, excludeEvents?: string[] } // add the unique page show namespace to any provided namespace so we can only remove the ones added by "pageshow" - let newNamespaces = mergeEvtNamespace(strPageShowNamespace, evtNamespace); + let newNamespaces = mergeEvtNamespace(_getPageShowNamespace(), evtNamespace); let pageShowAdded = _addEventListeners([strPageShow], listener, excludeEvents, newNamespaces); pageShowAdded = _addEventListeners([strVisibilityChangeEvt], _handlePageVisibility, excludeEvents, newNamespaces) || pageShowAdded; @@ -544,7 +579,7 @@ export function addPageShowEventListener(listener: any, excludeEvents?: string[] */ export function removePageShowEventListener(listener: any, evtNamespace?: string | string[] | null) { // add the unique page show namespace to any provided namespace so we only remove the ones added by "pageshow" - let newNamespaces = mergeEvtNamespace(strPageShowNamespace, evtNamespace); + let newNamespaces = mergeEvtNamespace(_getPageShowNamespace(), evtNamespace); removeEventListeners([strPageShow], listener, newNamespaces); removeEventListeners([strVisibilityChangeEvt], null, newNamespaces); } diff --git a/shared/otel-core/src/internal/attributeHelpers.ts b/shared/otel-core/src/internal/attributeHelpers.ts index ee013b86e..2d6bdb2de 100644 --- a/shared/otel-core/src/internal/attributeHelpers.ts +++ b/shared/otel-core/src/internal/attributeHelpers.ts @@ -5,12 +5,14 @@ import { arrForEach, arrSlice, isArray, isObject, isString, objForEachKey } from import { IOTelApi } from "../interfaces/otel/IOTelApi"; import { IOTelAttributes, OTelAttributeValue } from "../interfaces/otel/IOTelAttributes"; import { createAttributeContainer } from "../otel/attribute/attributeContainer"; -import { handleWarn } from "./commonUtils"; +import { handleWarn } from "./handleErrors"; +/*#__NO_SIDE_EFFECTS__*/ function _isSupportedType(theType: string): boolean { return theType === "number" || theType === "boolean" || theType === "string"; } +/*#__NO_SIDE_EFFECTS__*/ function _isHomogeneousArray(arr: unknown[]): boolean { let type: string | undefined; let result = true; @@ -36,19 +38,38 @@ function _isHomogeneousArray(arr: unknown[]): boolean { return result; } +/** + * Helper to determine if the provided key is a valid attribute key + * @param key - The key to check + * @returns true if the key is a valid attribute key + */ +/*#__NO_SIDE_EFFECTS__*/ export function isAttributeKey(key: unknown): key is string { return isString(key) && !!key; } +/** + * Helper to determine if the provided value is a valid attribute value + * @param val - The value to check + * @returns true if the value is a valid attribute value + */ +/*#__NO_SIDE_EFFECTS__*/ export function isAttributeValue(val: unknown): val is OTelAttributeValue { let result = (val === null || _isSupportedType(typeof val)); - if (!val && isArray(val)) { + if (val && isArray(val)) { result = _isHomogeneousArray(val); } return result; } +/** + * Sanitize the provided attributes to ensure they conform to OTel attribute requirements + * @param otelApi - The OpenTelemetry API instance + * @param attributes - The attributes to sanitize + * @returns The sanitized attributes + */ +/*#__NO_SIDE_EFFECTS__*/ export function sanitizeAttributes(otelApi: IOTelApi, attributes: unknown): IOTelAttributes { let container = createAttributeContainer(otelApi.cfg); diff --git a/shared/otel-core/src/internal/commonUtils.ts b/shared/otel-core/src/internal/commonUtils.ts index 343c82408..c0047d7a4 100644 --- a/shared/otel-core/src/internal/commonUtils.ts +++ b/shared/otel-core/src/internal/commonUtils.ts @@ -2,109 +2,298 @@ // Licensed under the MIT License. import { IPromise, createRacePromise, createTimeoutPromise } from "@nevware21/ts-async"; -import { dumpObj, fnApply } from "@nevware21/ts-utils"; +import { ILazyValue, asString, dumpObj, isError, isObject, isPrimitive, safe, safeGetLazy } from "@nevware21/ts-utils"; +import { STR_EMPTY } from "../constants/InternalConstants"; +import { OTelAttributeValue } from "../interfaces/otel/IOTelAttributes"; +import { IAttributeContainer } from "../interfaces/otel/attribute/IAttributeContainer"; import { IOTelErrorHandlers } from "../interfaces/otel/config/IOTelErrorHandlers"; +import { + DBSYSTEMVALUES_DB2, DBSYSTEMVALUES_DERBY, DBSYSTEMVALUES_H2, DBSYSTEMVALUES_HSQLDB, DBSYSTEMVALUES_MARIADB, DBSYSTEMVALUES_MSSQL, + DBSYSTEMVALUES_ORACLE, DBSYSTEMVALUES_OTHER_SQL, DBSYSTEMVALUES_SQLITE +} from "../otel/attribute/SemanticConventions"; +import { getJSON } from "../utils/EnvUtils"; +import { handleError } from "./handleErrors"; + +const _hasJsonStringify: ILazyValue = (/*#__PURE__*/ safeGetLazy(() => !!getJSON().stringify, null)); + +const SYNTHETIC_TYPE = "user_agent.synthetic.type"; +const CLIENT_DOT = "client."; +const HTTP_DOT = "http."; +const NET_DOT = "net."; +const PEER_DOT = "peer."; +const ATTR_NETWORK_PEER_ADDRESS = "network.peer.address"; +const SEMATTRS_NET_PEER_IP = NET_DOT + PEER_DOT + "ip"; +const ATTR_CLIENT_ADDRESS = CLIENT_DOT + "address"; +const SEMATTRS_HTTP_CLIENT_IP = HTTP_DOT + "client_ip"; +const ATTR_USER_AGENT_ORIGINAL = "user_agent.original"; +const SEMATTRS_HTTP_USER_AGENT = HTTP_DOT + "user_agent"; +const ATTR_URL_FULL = "url.full"; +const SEMATTRS_HTTP_URL = HTTP_DOT + "url"; +const ATTR_HTTP_REQUEST_METHOD = HTTP_DOT + "request.method"; +const SEMATTRS_HTTP_METHOD = HTTP_DOT + "method"; +const ATTR_HTTP_RESPONSE_STATUS_CODE = HTTP_DOT + "response.status_code"; +const SEMATTRS_HTTP_STATUS_CODE = HTTP_DOT + "status_code"; +const ATTR_URL_SCHEME = "url.scheme"; +const SEMATTRS_HTTP_SCHEME = HTTP_DOT + "scheme"; +const ATTR_URL_PATH = "url.path"; +const ATTR_URL_QUERY = "url.query"; +const SEMATTRS_HTTP_TARGET = HTTP_DOT + "target"; +const ATTR_SERVER_ADDRESS = "server.address"; +const SEMATTRS_HTTP_HOST = HTTP_DOT + "host"; +const SEMATTRS_NET_PEER_NAME = NET_DOT + PEER_DOT + "name"; +const ATTR_CLIENT_PORT = CLIENT_DOT + "port"; +const ATTR_SERVER_PORT = "server.port"; +const SEMATTRS_NET_PEER_PORT = NET_DOT + PEER_DOT + "port"; +const SEMATTRS_PEER_SERVICE = PEER_DOT + "service"; /** - * Handle / report an error. - * When not provided the default is to generally throw an {@link InvalidAttributeError} - * @param handlers - The error handlers configuration - * @param message - The error message to report - * @param key - The attribute key that caused the error - * @param value - The attribute value that caused the error + * Get the URL from the attribute container + * @param container - The attribute container to extract the URL from + * @returns The constructed URL string */ -/*#__NO_SIDE_EFFECTS__*/ -export function handleAttribError(handlers: IOTelErrorHandlers, message: string, key: string, value: any) { - if (handlers.attribError) { - handlers.attribError(message, key, value); - } else { - handleWarn(handlers, message + " for [" + key + "]: " + dumpObj(value)); +/* #__NO_SIDE_EFFECTS__ */ +export function getUrl(container: IAttributeContainer): string { + let result = ""; + if (container) { + const httpMethod = getHttpMethod(container); + if (httpMethod) { + const httpUrl = getHttpUrl(container); + if (httpUrl) { + result = asString(httpUrl); + } else { + const httpScheme = getHttpScheme(container); + const httpTarget = getHttpTarget(container); + if (httpScheme && httpTarget) { + const httpHost = getHttpHost(container); + if (httpHost) { + result = httpScheme + "://" + httpHost + httpTarget; + } else { + const netPeerPort = getNetPeerPort(container); + if (netPeerPort) { + const netPeerName = getNetPeerName(container); + if (netPeerName) { + result = httpScheme + "://" + netPeerName + ":" + netPeerPort + httpTarget; + } else { + const netPeerIp = getPeerIp(container); + if (netPeerIp) { + result = httpScheme + "://" + netPeerIp + ":" + netPeerPort + httpTarget; + } + } + } + } + } + } + } } + + return result; } /** - * There was an error with the span. - * @param handlers - The error handlers configuration - * @param message - The message to report - * @param spanName - The name of the span + * Gets the synthetic type from the attribute container + * @param container - The attribute container to extract the synthetic type from + * @returns The synthetic type value */ -/*#__NO_SIDE_EFFECTS__*/ -export function handleSpanError(handlers: IOTelErrorHandlers, message: string, spanName: string) { - if (handlers.spanError) { - handlers.spanError(message, spanName); - } else { - handleWarn(handlers, "Span [" + spanName + "]: " + message); - } +/* #__NO_SIDE_EFFECTS__ */ +export function getSyntheticType(container: IAttributeContainer): OTelAttributeValue { + return container.get(SYNTHETIC_TYPE); } /** - * Report a general debug message, should not be treated as fatal - * @param handlers - The error handlers configuration - * @param message - The debug message to report + * Determine if the attribute container represents a synthetic source + * @param container - The attribute container to check + * @returns True if the attribute container is from a synthetic source */ -/*#__NO_SIDE_EFFECTS__*/ -export function handleDebug(handlers: IOTelErrorHandlers, message: string) { - if (handlers.debug) { - handlers.debug(message); - } else { - if (console) { - let fn = console.log; - fnApply(fn, console, [message]); - } - } +/* #__NO_SIDE_EFFECTS__ */ +export function isSyntheticSource(container: IAttributeContainer): boolean { + return !!getSyntheticType(container); } /** - * Report a general warning, should not be treated as fatal - * @param handlers - The error handlers configuration - * @param message - The warning message to report + * Serialize an attribute value to a string value + * @param value - The attribute value to serialize + * @returns The serialized string value */ -/*#__NO_SIDE_EFFECTS__*/ -export function handleWarn(handlers: IOTelErrorHandlers, message: string) { - if (handlers.warn) { - handlers.warn(message); +/* #__NO_SIDE_EFFECTS__ */ +export function serializeAttribute(value: any): string | number | bigint | boolean | undefined | symbol | null { + let result: string | number | boolean | null | undefined; + if (isError(value)) { + result = dumpObj(value); + } else if ((!value.toString || isObject(value)) && _hasJsonStringify.v) { + result = safe(getJSON().stringify, [value]).v; + } else if (isPrimitive(value)) { + // We keep primitives as-is, so that the standard attribute types are preserved + // These are converted as required in the sending channel(s) + result = value as any; } else { - if (console) { - let fn = console.warn || console.log; - fnApply(fn, console, [message]); - } + result = asString(value); } + + // Return scalar and undefined values + return result; } /** - * Report a general error, should not be treated as fatal - * @param handlers - The error handlers configuration - * @param message - The error message to report + * Peer address of the network connection - IP address or Unix domain socket name. + * + * @example 10.1.2.80 + * @example /tmp/my.sock + * @since 3.4.0 */ -/*#__NO_SIDE_EFFECTS__*/ -export function handleError(handlers: IOTelErrorHandlers, message: string) { - if (handlers.error) { - handlers.error(message); - } else if (handlers.warn) { - handlers.warn(message); - } else { - if (console) { - let fn = console.error || console.warn || console.log; - fnApply(fn, console, [message]); +/* #__NO_SIDE_EFFECTS__ */ +export function getPeerIp(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_NETWORK_PEER_ADDRESS) || container.get(SEMATTRS_NET_PEER_IP); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getLocationIp(container: IAttributeContainer): OTelAttributeValue | undefined { + let result: OTelAttributeValue | undefined; + if (container) { + const httpClientIp = getHttpClientIp(container); + if (httpClientIp) { + result = asString(httpClientIp); + } + + if (!result) { + const netPeerIp = getPeerIp(container); + if (netPeerIp) { + result = asString(netPeerIp); + } } } + + return result; } -/** - * A general error handler for not implemented methods. - * @param handlers - The error handlers configuration - * @param message - The message to report - */ -/*#__NO_SIDE_EFFECTS__*/ -export function handleNotImplemented(handlers: IOTelErrorHandlers, message: string) { - if (handlers.notImplemented) { - handlers.notImplemented(message); - } else { - if (console) { - let fn = console.error || console.log; - fnApply(fn, console, [message]); +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpClientIp(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_CLIENT_ADDRESS) || container.get(SEMATTRS_HTTP_CLIENT_IP); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getUserAgent(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_USER_AGENT_ORIGINAL) || container.get(SEMATTRS_HTTP_USER_AGENT); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpUrl(container: IAttributeContainer): OTelAttributeValue | undefined { + // Stable sem conv only supports populating url from `url.full` + if (container) { + return container.get(ATTR_URL_FULL) || container.get(SEMATTRS_HTTP_URL); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpMethod(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_HTTP_REQUEST_METHOD) || container.get(SEMATTRS_HTTP_METHOD); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpStatusCode(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_HTTP_RESPONSE_STATUS_CODE) || container.get(SEMATTRS_HTTP_STATUS_CODE); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpScheme(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_URL_SCHEME) || container.get(SEMATTRS_HTTP_SCHEME); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpTarget(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_URL_PATH) || container.get(ATTR_URL_QUERY) || container.get(SEMATTRS_HTTP_TARGET); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getHttpHost(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_SERVER_ADDRESS) || container.get(SEMATTRS_HTTP_HOST); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getNetPeerName(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return container.get(ATTR_CLIENT_ADDRESS) || container.get(SEMATTRS_NET_PEER_NAME); + } +} + +/* #__NO_SIDE_EFFECTS__ */ +export function getNetPeerPort(container: IAttributeContainer): OTelAttributeValue | undefined { + if (container) { + return ( + container.get(ATTR_CLIENT_PORT) || + container.get(ATTR_SERVER_PORT) || + container.get(SEMATTRS_NET_PEER_PORT) + ); + } +} + +export function getDependencyTarget(container: IAttributeContainer): string { + let result: string; + if (container) { + const peerService = container.get(SEMATTRS_PEER_SERVICE); + if (peerService) { + result = asString(peerService); + } + + if (!result) { + const httpHost = getHttpHost(container); + if (httpHost) { + result = asString(httpHost); + } + } + + if (!result) { + const httpUrl = getHttpUrl(container); + if (httpUrl) { + result = asString(httpUrl); + } + } + + if (!result) { + const netPeerName = getNetPeerName(container); + if (netPeerName) { + result = asString(netPeerName); + } + } + if (!result) { + const netPeerIp = getPeerIp(container); + if (netPeerIp) { + result = asString(netPeerIp); + } } } + + return result || STR_EMPTY; +} + +/** #__NO_SIDE_EFFECTS__ */ +export function isSqlDB(dbSystem: string): boolean { + return ( + dbSystem === DBSYSTEMVALUES_DB2 || + dbSystem === DBSYSTEMVALUES_DERBY || + dbSystem === DBSYSTEMVALUES_MARIADB || + dbSystem === DBSYSTEMVALUES_MSSQL || + dbSystem === DBSYSTEMVALUES_ORACLE || + dbSystem === DBSYSTEMVALUES_SQLITE || + dbSystem === DBSYSTEMVALUES_OTHER_SQL || + dbSystem === DBSYSTEMVALUES_HSQLDB || + dbSystem === DBSYSTEMVALUES_H2 + ); } /** @@ -115,7 +304,6 @@ export function handleNotImplemented(handlers: IOTelErrorHandlers, message: stri * @param promise - The promise to guard with the timeout. * @param timeout - Timeout in milliseconds before the promise is rejected. */ - export function callWithTimeout( handlers: IOTelErrorHandlers, promise: Promise, diff --git a/shared/otel-core/src/internal/handleErrors.ts b/shared/otel-core/src/internal/handleErrors.ts new file mode 100644 index 000000000..c035a7698 --- /dev/null +++ b/shared/otel-core/src/internal/handleErrors.ts @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { dumpObj, fnApply } from "@nevware21/ts-utils"; +import { IOTelErrorHandlers } from "../interfaces/otel/config/IOTelErrorHandlers"; + +/** + * Handle / report an error. + * When not provided the default is to generally throw an {@link OTelInvalidAttributeError} + * @param handlers - The error handlers configuration + * @param message - The error message to report + * @param key - The attribute key that caused the error + * @param value - The attribute value that caused the error + */ +/*#__NO_SIDE_EFFECTS__*/ +export function handleAttribError(handlers: IOTelErrorHandlers, message: string, key: string, value: any) { + if (handlers.attribError) { + handlers.attribError(message, key, value); + } else { + handleWarn(handlers, message + " for [" + key + "]: " + dumpObj(value)); + } +} + +/** + * There was an error with the span. + * @param handlers - The error handlers configuration + * @param message - The message to report + * @param spanName - The name of the span + */ +/*#__NO_SIDE_EFFECTS__*/ +export function handleSpanError(handlers: IOTelErrorHandlers, message: string, spanName: string) { + if (handlers.spanError) { + handlers.spanError(message, spanName); + } else { + handleWarn(handlers, "Span [" + spanName + "]: " + message); + } +} + +/** + * Report a general debug message, should not be treated as fatal + * @param handlers - The error handlers configuration + * @param message - The debug message to report + */ +/*#__NO_SIDE_EFFECTS__*/ +export function handleDebug(handlers: IOTelErrorHandlers, message: string) { + if (handlers.debug) { + handlers.debug(message); + } else { + if (console) { + let fn = console.log; + fnApply(fn, console, [message]); + } + } +} + +/** + * Report a general warning, should not be treated as fatal + * @param handlers - The error handlers configuration + * @param message - The warning message to report + */ +/*#__NO_SIDE_EFFECTS__*/ +export function handleWarn(handlers: IOTelErrorHandlers, message: string) { + if (handlers.warn) { + handlers.warn(message); + } else { + if (console) { + let fn = console.warn || console.log; + fnApply(fn, console, [message]); + } + } +} + +/** + * Report a general error, should not be treated as fatal + * @param handlers - The error handlers configuration + * @param message - The error message to report + */ +/*#__NO_SIDE_EFFECTS__*/ +export function handleError(handlers: IOTelErrorHandlers, message: string) { + if (handlers.error) { + handlers.error(message); + } else if (handlers.warn) { + handlers.warn(message); + } else { + if (console) { + let fn = console.error || console.warn || console.log; + fnApply(fn, console, [message]); + } + } +} + +/** + * A general error handler for not implemented methods. + * @param handlers - The error handlers configuration + * @param message - The message to report + */ +/*#__NO_SIDE_EFFECTS__*/ +export function handleNotImplemented(handlers: IOTelErrorHandlers, message: string) { + if (handlers.notImplemented) { + handlers.notImplemented(message); + } else { + if (console) { + let fn = console.error || console.log; + fnApply(fn, console, [message]); + } + } +} diff --git a/shared/otel-core/src/internal/noopHelpers.ts b/shared/otel-core/src/internal/noopHelpers.ts new file mode 100644 index 000000000..d675ef456 --- /dev/null +++ b/shared/otel-core/src/internal/noopHelpers.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * A simple function that does nothing and returns the current this (if any). + * @returns + */ +export function _noopThis(this: T): T { + return this; +} + +/** + * A simple function that does nothing and returns undefined. + */ +export function _noopVoid(this: T): void { +} \ No newline at end of file diff --git a/shared/otel-core/src/internal/timeHelpers.ts b/shared/otel-core/src/internal/timeHelpers.ts index 6ae342558..85a7c0e2c 100644 --- a/shared/otel-core/src/internal/timeHelpers.ts +++ b/shared/otel-core/src/internal/timeHelpers.ts @@ -2,28 +2,28 @@ // Licensed under the MIT License. import { - ICachedValue, ObjDefinePropDescriptor, createCachedValue, createDeferredCachedValue, getPerformance, isArray, isDate, isNullOrUndefined, - isNumber, mathFloor, mathRound, objDefine, objDefineProps, objFreeze, perfNow, strRepeat, strRight, throwTypeError + ICachedValue, ObjDefinePropDescriptor, createCachedValue, getDeferred, getPerformance, isArray, isDate, isNullOrUndefined, isNumber, + mathFloor, mathRound, objDefineProps, objFreeze, perfNow, strLeft, strRight, throwTypeError } from "@nevware21/ts-utils"; -import { IOTelHrTime, OTelTimeInput } from "../types/time"; -import { setObjStringTag, toISOString } from "../utils/HelperFuncsCore"; +import { IOTelHrTime, OTelTimeInput } from "../interfaces/IOTelHrTime"; +import { setObjStringTag, toISOString } from "../utils/HelperFuncs"; +import { INVALID_TRACE_ID } from "../utils/TraceParent"; const NANOSECOND_DIGITS = 9; -const NANOSECOND_DIGITS_IN_MILLIS = 6; +//const NANOSECOND_DIGITS_IN_MILLIS = 6; // Constants for time unit conversions and manipulation -const NANOS_IN_MILLIS = /*#__PURE__*/ 1000000; // Number of nanoseconds in a millisecond -const NANOS_IN_SECOND = /*#__PURE__*/ 1000000000; // Number of nanoseconds in a second -const MICROS_IN_SECOND = /*#__PURE__*/ 1000000; // Number of microseconds in a second -const MICROS_IN_MILLIS = /*#__PURE__*/ 1000; // Number of microseconds in a millisecond -const MILLIS_IN_SECOND = /*#__PURE__*/ 1000; // Number of milliseconds in a second - +const NANOS_IN_MILLIS = 1000000; // Number of nanoseconds in a millisecond +const NANOS_IN_SECOND = 1000000000; // Number of nanoseconds in a second +const MICROS_IN_SECOND = 1000000; // Number of microseconds in a second +const MICROS_IN_MILLIS = 1000; // Number of microseconds in a millisecond +const MILLIS_IN_SECOND = 1000; // Number of milliseconds in a second interface IOriginHrTime { to: number; hr: IOTelHrTime } -let cMillisToNanos: ICachedValue; +//let cMillisToNanos: ICachedValue; let cSecondsToNanos: ICachedValue; let cTimeOrigin: ICachedValue; let cNanoPadding: ICachedValue; @@ -36,7 +36,7 @@ function _notMutable() { * Initialize the cached value for converting milliseconds to nanoseconds. * @returns */ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function _initSecondsToNanos(): ICachedValue { if (!cSecondsToNanos) { cSecondsToNanos = createCachedValue(NANOS_IN_SECOND); @@ -48,7 +48,7 @@ function _initSecondsToNanos(): ICachedValue { * Initialize the time origin. * @returns */ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function _initTimeOrigin(): ICachedValue { if (!cTimeOrigin) { let timeOrigin = 0; @@ -72,7 +72,7 @@ function _initTimeOrigin(): ICachedValue { return cTimeOrigin; } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function _finalizeHrTime(hrTime: IOTelHrTime) { function _toString() { return "[" + hrTime[0] + ", " + hrTime[1] + "]"; @@ -83,7 +83,7 @@ function _finalizeHrTime(hrTime: IOTelHrTime) { return objFreeze(hrTime); } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function _createUnixNanoHrTime(unixNano: number): IOTelHrTime { // Create array with initial length of 2 const hrTime = [0, 0] as any as IOTelHrTime; @@ -92,16 +92,16 @@ function _createUnixNanoHrTime(unixNano: number): IOTelHrTime { // Define the array elements and other properties (avoid redefining length) objDefineProps(hrTime, { 0: { - l: createDeferredCachedValue(() => mathFloor(unixNano / NANOS_IN_SECOND)) + l: getDeferred(() => mathFloor(unixNano / NANOS_IN_SECOND)) }, 1: { - l: createDeferredCachedValue(() => unixNano % NANOS_IN_SECOND) - }, - unixNano: { - v: unixNano, - e: false, - w: false + l: getDeferred(() => unixNano % NANOS_IN_SECOND) }, + // unixNano: { + // v: unixNano, + // e: false, + // w: false + // }, // Override array mutating methods with single _notMutable function push: immutable, pop: immutable, @@ -117,15 +117,15 @@ function _createUnixNanoHrTime(unixNano: number): IOTelHrTime { return _finalizeHrTime(hrTime); } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function _createHrTime(seconds: number, nanoseconds: number): IOTelHrTime { const hrTime = [seconds, nanoseconds] as IOTelHrTime; - objDefine(hrTime, "unixNano", { - v: (seconds * NANOS_IN_SECOND) + nanoseconds, - w: false, - e: false - }); + // objDefine(hrTime, "unixNano", { + // v: (seconds * NANOS_IN_SECOND) + nanoseconds, + // w: false, + // e: false + // }); return _finalizeHrTime(hrTime); } @@ -134,7 +134,7 @@ function _createHrTime(seconds: number, nanoseconds: number): IOTelHrTime { * Returns a new HrTime object with zero values for seconds and nanoseconds. * @returns A HrTime object representing zero time. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function zeroHrTime(): IOTelHrTime { return _createUnixNanoHrTime(0); } @@ -144,7 +144,7 @@ export function zeroHrTime(): IOTelHrTime { * @param epochMillis - The number of milliseconds since the epoch (January 1, 1970). * @returns A HrTime object representing the converted time. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function millisToHrTime(epochMillis: number): IOTelHrTime { let result: IOTelHrTime; @@ -183,7 +183,7 @@ export function millisToHrTime(epochMillis: number): IOTelHrTime { * @param nanos - The number of nanoseconds since the epoch (January 1, 1970). * @returns A HrTime object representing the converted time. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function nanosToHrTime(nanos: number): IOTelHrTime { let result: IOTelHrTime; if (nanos > 0) { @@ -192,38 +192,37 @@ export function nanosToHrTime(nanos: number): IOTelHrTime { return result || zeroHrTime(); } -/** - * Converts a HrTime object to a number representing nanoseconds since epoch. - * Note: Due to JavaScript number limitations, values greater than Number.MAX_SAFE_INTEGER - * may lose precision. For very large time values, consider using string representation - * or splitting into separate second/nanosecond components. - * @param hrTime - The HrTime object to convert. - * @returns The number of nanoseconds represented by the HrTime object. - */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ -export function hrTimeToUnixNanos(hrTime: IOTelHrTime): number { - let value = hrTime.unixNano; - if (isNullOrUndefined(value)) { - // Handle legacy HRTime format using standard number operations - // First calculate seconds contribution to nanoseconds - const secondsInNanos = hrTime[0] * NANOS_IN_MILLIS; - // Add the additional nanoseconds - value = secondsInNanos + hrTime[1]; - - // // Add warning if we're approaching number precision limits - // if (Math.abs(value) > Number.MAX_SAFE_INTEGER) { - // console.warn("Time value exceeds safe integer limits, precision may be lost"); - // } - } - - return value; -} +// /** +// * Converts a HrTime object to a number representing nanoseconds since epoch. +// * Note: Due to JavaScript number limitations, values greater than Number.MAX_SAFE_INTEGER +// * may lose precision. For very large time values, consider using string representation +// * or splitting into separate second/nanosecond components. +// * @param hrTime - The HrTime object to convert. +// * @returns The number of nanoseconds represented by the HrTime object. +// */ +// export function hrTimeToUnixNanos(hrTime: IOTelHrTime): number { +// let value = hrTime.unixNano; +// if (isNullOrUndefined(value)) { +// // Handle legacy HRTime format using standard number operations +// // First calculate seconds contribution to nanoseconds +// const secondsInNanos = hrTime[0] * NANOS_IN_MILLIS; +// // Add the additional nanoseconds +// value = secondsInNanos + hrTime[1]; +// +// // // Add warning if we're approaching number precision limits +// // if (Math.abs(value) > Number.MAX_SAFE_INTEGER) { +// // console.warn("Time value exceeds safe integer limits, precision may be lost"); +// // } +// } +// +// return value; +// } /** * Returns an hrtime calculated via performance component. * @param performanceNow - The current time in milliseconds since the epoch. */ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export function hrTime(performanceNow?: number): IOTelHrTime { let result = millisToHrTime(isNumber(performanceNow) ? performanceNow : perfNow()); const perf = getPerformance(); @@ -239,7 +238,7 @@ export function hrTime(performanceNow?: number): IOTelHrTime { * Converts a TimeInput to an HrTime, defaults to _hrtime(). * @param time - The time input to convert. */ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export function timeInputToHrTime(time: OTelTimeInput): IOTelHrTime { let result: IOTelHrTime; @@ -267,7 +266,7 @@ export function timeInputToHrTime(time: OTelTimeInput): IOTelHrTime { * @param endTime - The end time of the duration * @returns The duration between startTime and endTime as an IOTelHrTime */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function hrTimeDuration(startTime: IOTelHrTime, endTime: IOTelHrTime): IOTelHrTime { const seconds = endTime[0] - startTime[0]; let nanos = endTime[1] - startTime[1]; @@ -288,10 +287,10 @@ export function hrTimeDuration(startTime: IOTelHrTime, endTime: IOTelHrTime): IO * Convert hrTime to timestamp, for example "2019-05-14T17:00:00.000123456Z" * @param time - The hrTime to convert. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function hrTimeToTimeStamp(time: IOTelHrTime): string { if (!cNanoPadding) { - cNanoPadding = createCachedValue(strRepeat("0", NANOSECOND_DIGITS)); + cNanoPadding = createCachedValue(strLeft(INVALID_TRACE_ID, NANOSECOND_DIGITS)); } const date = toISOString(new Date(time[0] * 1000)); @@ -302,7 +301,7 @@ export function hrTimeToTimeStamp(time: IOTelHrTime): string { * Convert hrTime to nanoseconds. * @param time - The hrTime to convert. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function hrTimeToNanoseconds(time: IOTelHrTime): number { let nanoSeconds = cSecondsToNanos || _initSecondsToNanos(); return time[0] * nanoSeconds.v + time[1]; @@ -312,7 +311,7 @@ export function hrTimeToNanoseconds(time: IOTelHrTime): number { * Convert hrTime to milliseconds. * @param time - The hrTime to convert. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function hrTimeToMilliseconds(time: IOTelHrTime): number { // Use integer math for the seconds part to avoid floating point precision loss const millisFromSeconds = time[0] * MILLIS_IN_SECOND; @@ -325,7 +324,7 @@ export function hrTimeToMilliseconds(time: IOTelHrTime): number { * Convert hrTime to microseconds. * @param time - The hrTime to convert. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function hrTimeToMicroseconds(time: IOTelHrTime): number { // Use integer math for the seconds part to avoid floating point precision loss const microsFromSeconds = time[0] * MICROS_IN_SECOND; @@ -338,7 +337,7 @@ export function hrTimeToMicroseconds(time: IOTelHrTime): number { * check if time is HrTime * @param value - The value to check. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function isTimeInputHrTime(value: unknown): value is IOTelHrTime { return isArray(value) && value.length === 2 && isNumber(value[0]) && isNumber(value[1]); } @@ -347,7 +346,7 @@ export function isTimeInputHrTime(value: unknown): value is IOTelHrTime { * check if input value is a correct types.TimeInput * @param value - The value to check. */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function isTimeInput(value: unknown): value is OTelTimeInput { return !isNullOrUndefined(value) && (isTimeInputHrTime(value) || isNumber(value) || isDate(value)); } @@ -358,7 +357,7 @@ export function isTimeInput(value: unknown): value is OTelTimeInput { * @param time2 - The second HrTime to add * @returns The sum of the two HrTime values as an IOTelHrTime */ -/*#__PURE__*/ /*@__NO_SIDE_EFFECTS__*/ +/*#__NO_SIDE_EFFECTS__*/ export function addHrTimes(time1: IOTelHrTime, time2: IOTelHrTime): IOTelHrTime { const seconds = time1[0] + time2[0]; let nanos = time1[1] + time2[1]; diff --git a/shared/otel-core/src/otel/api/OTelApi.ts b/shared/otel-core/src/otel/api/OTelApi.ts index f0453082e..b379cebb8 100644 --- a/shared/otel-core/src/otel/api/OTelApi.ts +++ b/shared/otel-core/src/otel/api/OTelApi.ts @@ -1,89 +1,25 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { ILazyValue, createDeferredCachedValue, fnApply, objDeepFreeze, objDefineProps } from "@nevware21/ts-utils"; -import { cfgDfMerge } from "../../config/ConfigDefaultHelpers"; -import { createDynamicConfig } from "../../config/DynamicConfig"; -import { safeGetLogger } from "../../diagnostics/DiagnosticLogger"; -import { IConfigDefaults } from "../../interfaces/config/IConfigDefaults"; -import { IDynamicConfigHandler } from "../../interfaces/config/IDynamicConfigHandler"; +import { ILazyValue, objDefineProps } from "@nevware21/ts-utils"; import { IOTelApi } from "../../interfaces/otel/IOTelApi"; import { IOTelApiCtx } from "../../interfaces/otel/IOTelApiCtx"; -import { IOTelConfig } from "../../interfaces/otel/config/IOTelConfig"; -import { IOTelErrorHandlers } from "../../interfaces/otel/config/IOTelErrorHandlers"; -import { IOTelTraceCfg } from "../../interfaces/otel/config/IOTelTraceCfg"; -import { IOTelContext } from "../../interfaces/otel/context/IOTelContext"; -import { IOTelContextManager } from "../../interfaces/otel/context/IOTelContextManager"; -import { IOTelTraceApi } from "../../interfaces/otel/trace/IOTelTraceApi"; -import { IOTelTracerProvider } from "../../interfaces/otel/trace/IOTelTracerProvider"; -import { createContext } from "./context/context"; -import { createContextManager } from "./context/contextManager"; -import { throwOTelInvalidAttributeError } from "./errors/OTelInvalidAttributeError"; -import { throwOTelNotImplementedError } from "./errors/OTelNotImplementedError"; -import { throwOTelSpanError } from "./errors/OTelSpanError"; -import { createTraceApi } from "./trace/traceApi"; - -export const traceApiDefaultConfigValues: IConfigDefaults = objDeepFreeze({ - traceCfg: cfgDfMerge({ - contextManager: null, - // textMapPropagator: null, - sampler: null, - generalLimits: cfgDfMerge({ - attributeValueLengthLimit: undefined, - attributeCountLimit: 128 - }), - spanLimits: cfgDfMerge({ - attributeValueLengthLimit: undefined, - attributeCountLimit: 128, - linkCountLimit: 128, - eventCountLimit: 128, - attributePerEventCountLimit: 128, - attributePerLinkCountLimit: 128 - }), - idGenerator: null, - serviceName: null - }), - errorHandlers: cfgDfMerge({ - attribError: throwOTelInvalidAttributeError, - spanError: throwOTelSpanError, - warn: logWarning, - notImplemented: throwOTelNotImplementedError - }) -}); - - -// TODO(!! IMPORTANT !!): This is a placeholder for the actual implementation of the logWarning function -function logWarning(message: string): void { - if (console) { - let fn = console.warn || console.log; - fnApply(fn, console, [message]); - } -} +import { ITraceApi } from "../../interfaces/otel/trace/IOTelTraceApi"; +import { setProtoTypeName } from "../../utils/HelperFuncs"; +import { _createTraceApi } from "./trace/traceApi"; +import { _createTracerProvider } from "./trace/tracerProvider"; +/*#__NO_SIDE_EFFECTS__*/ export function createOTelApi(otelApiCtx: IOTelApiCtx): IOTelApi { - let _logger = otelApiCtx.diagLogger || safeGetLogger(null); - let _configHandler: IDynamicConfigHandler = createDynamicConfig({} as IOTelConfig, traceApiDefaultConfigValues as any, _logger); - let _traceApi: ILazyValue; - let _baseContext: ILazyValue = createDeferredCachedValue(createContext); - let _context: ILazyValue = createDeferredCachedValue(() => createContextManager(_baseContext.v)); - let _traceProvider: IOTelTracerProvider = otelApiCtx.traceProvider; - - function _getTracer(name: string, version?: string) { - if (!_traceProvider) { - throwOTelNotImplementedError("No tracer provider configured - a tracer provider must be configured to retrieve tracers"); - } - - return _traceProvider.getTracer(name, version); - } + let _traceApi: ILazyValue; - let otelApi = objDefineProps({} as IOTelApi, { - cfg: { g: () => _configHandler.cfg }, + let otelApi = setProtoTypeName(objDefineProps(_createTracerProvider(otelApiCtx.host) as IOTelApi, { + cfg: { g: () => otelApiCtx.host.config }, trace: { g: () => _traceApi.v }, - context: { g: () => _context.v }, - getTracer: { g: () => _getTracer } - }); + host: { g: () => otelApiCtx.host } + }), "OTelApi"); - _traceApi = createDeferredCachedValue(() => createTraceApi(otelApi)); + _traceApi = _createTraceApi(otelApi); return otelApi } diff --git a/shared/otel-core/src/otel/api/context/context.ts b/shared/otel-core/src/otel/api/context/context.ts index a1534f212..7531e8106 100644 --- a/shared/otel-core/src/otel/api/context/context.ts +++ b/shared/otel-core/src/otel/api/context/context.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { ICachedValue, createCachedValue, newSymbol, objCreate, objDefine } from "@nevware21/ts-utils"; +import { IOTelApi } from "../../.."; import { IOTelContext } from "../../../interfaces/otel/context/IOTelContext"; let _InternalContextKey: ICachedValue @@ -13,9 +14,10 @@ let _InternalContextKey: ICachedValue * @param parent - The optional parent context. * @returns A new context instance. */ -export function createContext(parent?: IOTelContext): IOTelContext { +export function createContext(otelApi: IOTelApi, parent?: IOTelContext): IOTelContext { let theValues = objCreate(null); - let theContext = { + let theContext: IOTelContext = { + api: otelApi, getValue: _getValue, setValue: _setValue, deleteValue: _deleteValue @@ -27,6 +29,13 @@ export function createContext(parent?: IOTelContext): IOTelContext { _InternalContextKey = createCachedValue(newSymbol("OTelSdk$InternalContextKey")); } + // Make the api property read-only + objDefine(theContext, "api", { + v: otelApi, + w: false, + e: false + }); + objDefine(theContext as any, _InternalContextKey.v, { e: false, v: theValues @@ -45,7 +54,7 @@ export function createContext(parent?: IOTelContext): IOTelContext { } function _setValue(key: symbol, value: unknown) { - let newContext = createContext(parent); + let newContext = createContext(theContext.api, parent); ((newContext as any)[_InternalContextKey.v])[key] = value; return theContext; } diff --git a/shared/otel-core/src/otel/api/errors/OTelError.ts b/shared/otel-core/src/otel/api/errors/OTelError.ts index 75cce7004..75522e55d 100644 --- a/shared/otel-core/src/otel/api/errors/OTelError.ts +++ b/shared/otel-core/src/otel/api/errors/OTelError.ts @@ -17,6 +17,7 @@ export interface OpenTelemetryErrorConstructor("OpenTelemetryError", function (self, args) { diff --git a/shared/otel-core/src/otel/api/trace/span.ts b/shared/otel-core/src/otel/api/trace/span.ts index 9d2df8cfc..0e4b590d0 100644 --- a/shared/otel-core/src/otel/api/trace/span.ts +++ b/shared/otel-core/src/otel/api/trace/span.ts @@ -2,12 +2,13 @@ // Licensed under the MIT License. import { - ILazyValue, createDeferredCachedValue, dumpObj, isNullOrUndefined, isString, objDefineProps, objFreeze, objKeys, perfNow + ILazyValue, dumpObj, getDeferred, isNullOrUndefined, isString, objDefineProps, objFreeze, objIs, objKeys, perfNow } from "@nevware21/ts-utils"; import { STR_EMPTY, UNDEFINED_VALUE } from "../../../constants/InternalConstants"; import { OTelSpanKind, eOTelSpanKind } from "../../../enums/otel/OTelSpanKind"; import { eOTelSpanStatusCode } from "../../../enums/otel/OTelSpanStatus"; import { OTelException } from "../../../interfaces/IException"; +import { IOTelHrTime, OTelTimeInput } from "../../../interfaces/IOTelHrTime"; import { IOTelAttributes } from "../../../interfaces/otel/IOTelAttributes"; import { IAttributeContainer } from "../../../interfaces/otel/attribute/IAttributeContainer"; import { IOTelLink } from "../../../interfaces/otel/trace/IOTelLink"; @@ -16,11 +17,11 @@ import { IOTelSpanStatus } from "../../../interfaces/otel/trace/IOTelSpanStatus" import { IOTelTimedEvent } from "../../../interfaces/otel/trace/IOTelTimedEvent"; import { IReadableSpan } from "../../../interfaces/otel/trace/IReadableSpan"; import { isAttributeValue, sanitizeAttributes } from "../../../internal/attributeHelpers"; -import { handleAttribError, handleNotImplemented, handleSpanError, handleWarn } from "../../../internal/commonUtils"; +import { handleAttribError, handleNotImplemented, handleSpanError, handleWarn } from "../../../internal/handleErrors"; import { hrTime, hrTimeDuration, hrTimeToMilliseconds, isTimeInput, millisToHrTime, timeInputToHrTime, zeroHrTime } from "../../../internal/timeHelpers"; -import { IOTelHrTime, OTelTimeInput } from "../../../types/time"; +import { setProtoTypeName, updateProtoTypeName } from "../../../utils/HelperFuncs"; import { addAttributes, createAttributeContainer } from "../../attribute/attributeContainer"; export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpanKind): IReadableSpan { @@ -30,7 +31,7 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa let attributes: ILazyValue; let isEnded = false; let errorHandlers = otelCfg.errorHandlers || {}; - let spanStartTime: ILazyValue = createDeferredCachedValue(() => { + let spanStartTime: ILazyValue = getDeferred(() => { if (isNullOrUndefined(spanCtx.startTime)) { return hrTime(perfStartTime); } @@ -42,14 +43,20 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa let spanStatus: IOTelSpanStatus | undefined; let links: IOTelLink[] = []; let events: IOTelTimedEvent[] = []; - let droppedAttributesCount = 0; - let droppedEvents = 0; - let droppedLinks = 0; - let zeroDuration = zeroHrTime(); + let localDroppedAttributes = 0; + let localContainer: IAttributeContainer = null; + let localDroppedEvents = 0; + let localDroppedLinks = 0; let isRecording = spanCtx.isRecording !== false; + + if (otelCfg.traceCfg && otelCfg.traceCfg.suppressTracing) { + // Automatically disable the span from recording + isRecording = false; + } + let spanName = orgName || STR_EMPTY; if (isRecording) { - attributes = createDeferredCachedValue(() => createAttributeContainer(otelCfg, spanName, spanCtx.attributes)); + attributes = getDeferred(() => createAttributeContainer(otelCfg, spanName, spanCtx.attributes)); } function _handleIsEnded(operation: string, extraMsg?: string): boolean { @@ -59,13 +66,17 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa return isEnded; } + function _toString() { + return "ReadableSpan (\"" + spanName + "\")" + } + const zeroDuration = hrTime(0); let theSpan: IReadableSpan = { spanContext: () => spanContext, setAttribute: (key: string, value: any) => { let message: string; - if (value !== null && !_handleIsEnded("setAttribute")) { + if (value !== null && !_handleIsEnded("setAttribute") && isRecording) { if (!key || key.length === 0) { message = "Invalid attribute key: " + dumpObj(key); } else if (!isAttributeValue(value)) { @@ -74,21 +85,23 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa if (message) { handleAttribError(errorHandlers, message, key, value); - droppedAttributesCount++; - } else if (isRecording){ + localDroppedAttributes++; + } else if (attributes){ attributes.v.set(key, value); } else { - droppedAttributesCount++; + localDroppedAttributes++; } + } else { + localDroppedAttributes++; } return theSpan; }, setAttributes: (attrs: IOTelAttributes) => { - if (!_handleIsEnded("setAttributes") && isRecording) { + if (!_handleIsEnded("setAttributes") && isRecording && attributes) { addAttributes(attributes.v, attrs); } else { - droppedAttributesCount += (objKeys(attrs).length || 0); + localDroppedAttributes += (objKeys(attrs).length || 0); } return theSpan; @@ -100,7 +113,7 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa if (maxEvents > 0 && events.length >= maxEvents) { let droppedEvent = events.shift(); handleWarn(errorHandlers, "maxEvents reached (" + maxEvents + ") - dropping event: " + droppedEvent.name); - droppedEvents++; + localDroppedEvents++; } if (isTimeInput(attributesOrStartTime)) { @@ -118,13 +131,13 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa droppedAttributesCount: 0 }) } else { - droppedEvents++; + localDroppedEvents++; handleWarn(errorHandlers, "Span.addEvent: " + name + " not added - No events allowed"); } handleNotImplemented(errorHandlers, "Span.addEvent: " + name + " not added"); } else { - droppedEvents++; + localDroppedEvents++; } return theSpan; @@ -133,7 +146,7 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa if(!_handleIsEnded("addEvent") && isRecording) { handleNotImplemented(errorHandlers, "Span.addLink: " + link + " not added"); } else { - droppedLinks++; + localDroppedLinks++; handleWarn(errorHandlers, "Span.addLink: " + link + " not added - No links allowed"); } @@ -143,7 +156,7 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa if (!_handleIsEnded("addLinks") && isRecording) { handleNotImplemented(errorHandlers, "Span.addLinks: " + links + " not added"); } else { - droppedLinks += links.length; + localDroppedLinks += links.length; handleWarn(errorHandlers, "Span.addLinks: " + links + " not added - No links allowed"); } @@ -152,7 +165,7 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa setStatus: (newStatus: IOTelSpanStatus) => { if (!_handleIsEnded("setStatus")) { spanStatus = newStatus; - if (!isNullOrUndefined(spanStatus.message) && !isString(spanStatus.message)) { + if (!isNullOrUndefined(spanStatus) && !isNullOrUndefined(spanStatus.message) && !isString(spanStatus.message)) { spanStatus.message = dumpObj(spanStatus.message); } } @@ -160,8 +173,9 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa return theSpan; }, updateName: (name: string) => { - if (!_handleIsEnded("updateName")) { + if (!_handleIsEnded("updateName") && !objIs(spanName, name)) { spanName = name; + updateProtoTypeName(theSpan, _toString()); } return theSpan; @@ -169,37 +183,47 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa end: (endTime?: OTelTimeInput) => { let calcDuration: number; if (!_handleIsEnded("end", "You can only call end once")) { - isEnded = true; - - if (!isNullOrUndefined(endTime)) { - // User provided an end time - spanEndTime = timeInputToHrTime(endTime); - spanDuration = hrTimeDuration(spanStartTime.v, spanEndTime); - calcDuration = hrTimeToMilliseconds(spanDuration); - } else { - let perfEndTime = perfNow(); - calcDuration = perfEndTime - perfStartTime; - spanDuration = millisToHrTime(calcDuration); - spanEndTime = hrTime(perfEndTime); - } + try { + if (!isNullOrUndefined(endTime)) { + // User provided an end time + spanEndTime = timeInputToHrTime(endTime); + spanDuration = hrTimeDuration(spanStartTime.v, spanEndTime); + calcDuration = hrTimeToMilliseconds(spanDuration); + } else { + let perfEndTime = perfNow(); + calcDuration = perfEndTime - perfStartTime; + spanDuration = millisToHrTime(calcDuration); + spanEndTime = hrTime(perfEndTime); + } - if (calcDuration < 0) { - handleWarn(errorHandlers, "Span.end: duration is negative - startTime > endTime. Setting duration to 0 ms"); - spanDuration = zeroHrTime(); - spanEndTime = spanStartTime.v; - } + if (calcDuration < 0) { + handleWarn(errorHandlers, "Span.end: duration is negative - startTime > endTime. Setting duration to 0 ms"); + spanDuration = zeroHrTime(); + spanEndTime = spanStartTime.v; + } - if (droppedEvents > 0) { - handleWarn(errorHandlers, "Droped " + droppedEvents + " events"); - } + if (localDroppedEvents > 0) { + handleWarn(errorHandlers, "Droped " + localDroppedEvents + " events"); + } - spanCtx.onEnd && spanCtx.onEnd(theSpan); + // We don't mark as ended until after the onEnd callback to ensure that it can + // still read / change the span if required as well as ensuring that the returned + // value for isRecording is correct. + spanCtx.onEnd && spanCtx.onEnd(theSpan); + } finally { + // Ensure we mark as ended even if the onEnd throws + isEnded = true; + } } }, isRecording: () => isRecording && !isEnded, recordException: (exception: OTelException, time?: OTelTimeInput) => { if (!_handleIsEnded("recordException")) { - handleNotImplemented(errorHandlers, "Span.recordException: " + dumpObj(exception) + " not added"); + if (spanCtx.onException) { + spanCtx.onException(theSpan, exception, time); + } else { + handleNotImplemented(errorHandlers, "Span.recordException: " + dumpObj(exception) + " not handled"); + } } }, name: spanName, @@ -208,6 +232,7 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa endTime: zeroDuration, status: UNDEFINED_VALUE, attributes: UNDEFINED_VALUE, + attribContainer: UNDEFINED_VALUE, links: links, events: events, duration: zeroDuration, @@ -220,8 +245,10 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa parentSpanId: UNDEFINED_VALUE, parentSpanContext: UNDEFINED_VALUE }; + + theSpan = setProtoTypeName(theSpan, _toString()) as IReadableSpan; - // Make the relevant proerties dynamic (and read-only) + // Make the relevant properties dynamic (and read-only) objDefineProps(theSpan, { name: { g: () => spanName @@ -229,6 +256,9 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa kind: { v: kind || eOTelSpanKind.INTERNAL }, + traceContext: { + g: () => spanContext + }, startTime: { g: () => { return spanStartTime.v; @@ -251,11 +281,26 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa return attributes ? attributes.v.attributes : objFreeze({}); } }, + attribContainer: { + g: () => { + if (!attributes && !localContainer) { + // Create an empty container and cache it for future use (for performance only) + localContainer = createAttributeContainer(otelCfg, spanName); + } + + return attributes ? attributes.v : localContainer; + } + }, links: { g: () => { return links; } }, + events: { + g: () => { + return events; + } + }, duration: { g: () => { return spanDuration || zeroHrTime(); @@ -268,26 +313,26 @@ export function createSpan(spanCtx: IOTelSpanCtx, orgName: string, kind: OTelSpa }, droppedAttributesCount: { g: () => { - return attributes ? attributes.v.droppedAttributes : droppedAttributesCount; + return attributes ? attributes.v.droppedAttributes : localDroppedAttributes; } }, droppedEventsCount: { g: () => { - return droppedEvents; + return localDroppedEvents; } }, droppedLinksCount: { g: () => { - return droppedLinks; + return localDroppedLinks; } }, parentSpanContext: { - l: createDeferredCachedValue(() => { + l: getDeferred(() => { return spanCtx ? spanCtx.parentSpanContext : UNDEFINED_VALUE; }) }, parentSpanId: { - l: createDeferredCachedValue(() => { + l: getDeferred(() => { let parentSpanId = UNDEFINED_VALUE; if (spanCtx) { let parentSpanCtx = spanCtx.parentSpanContext; diff --git a/shared/otel-core/src/otel/api/trace/traceApi.ts b/shared/otel-core/src/otel/api/trace/traceApi.ts index 77c2be2f6..47c24043c 100644 --- a/shared/otel-core/src/otel/api/trace/traceApi.ts +++ b/shared/otel-core/src/otel/api/trace/traceApi.ts @@ -1,72 +1,74 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { ICachedValue, fnBind, getDeferred } from "@nevware21/ts-utils"; +import { UNDEFINED_VALUE } from "../../../constants/InternalConstants"; +import { IDistributedTraceContext } from "../../../interfaces/ai/IDistributedTraceContext"; import { IOTelApi } from "../../../interfaces/otel/IOTelApi"; -import { IOTelSpan } from "../../../interfaces/otel/trace/IOTelSpan"; -import { IOTelTraceApi } from "../../../interfaces/otel/trace/IOTelTraceApi"; -import { IOTelTracerOptions } from "../../../interfaces/otel/trace/IOTelTracerOptions"; +import { ITraceApi } from "../../../interfaces/otel/trace/IOTelTraceApi"; import { IOTelTracerProvider } from "../../../interfaces/otel/trace/IOTelTracerProvider"; -import { handleNotImplemented } from "../../../internal/commonUtils"; +import { IReadableSpan } from "../../../interfaces/otel/trace/IReadableSpan"; +import { handleNotImplemented } from "../../../internal/handleErrors"; +import { setProtoTypeName } from "../../../utils/HelperFuncs"; +import { throwOTelError } from "../errors/OTelError"; import { deleteContextSpan, getContextActiveSpanContext, getContextSpan, isSpanContextValid, setContextSpan, setContextSpanContext, wrapSpanContext } from "./utils"; /** - * Create a new instance of the OpenTelemetry Trace API - * @param otelApiCtx - * @returns + * @internal + * Create a new instance of the OpenTelemetry Trace API, this is bound to the + * provided instance of the traceProvider (the {@link IOTelApi} instance), + * to "change" (setGlobalTraceProvider) you MUST create a new instance of this API. + * @param otelApi - The IOTelApi instance associated with this instance + * @returns A new instance of the ITraceApi for the provided ITelApi */ -export function createTraceApi(otelApi: IOTelApi): IOTelTraceApi { - let errorHandlers = otelApi.cfg.errorHandlers || {}; +export function _createTraceApi(otelApi: IOTelApi): ICachedValue { let traceProvider: IOTelTracerProvider = otelApi; if (!traceProvider) { - traceProvider = createNoopTraceProvider(); + throwOTelError("Must provide an otelApi instance"); } - let traceApi: IOTelTraceApi = { - setGlobalTracerProvider: (provider: IOTelTracerProvider) => { - handleNotImplemented(errorHandlers, "setGlobalTracerProvider"); - return false; - }, + return getDeferred(() => { + return setProtoTypeName({ + setGlobalTracerProvider: (provider: IOTelTracerProvider) => { + handleNotImplemented(otelApi.host.config.errorHandlers, "setGlobalTracerProvider"); + return false; + }, - getTracerProvider: () => { - return traceProvider; - }, + getTracerProvider: () => { + return traceProvider; + }, - getTracer: (name: string, version?: string, options?: IOTelTracerOptions) => { - return traceProvider.getTracer(name, version, options); - }, + getTracer: fnBind(traceProvider.getTracer, traceProvider), - disable: () => { - traceProvider = createNoopTraceProvider(); - }, + disable: () => { + handleNotImplemented(otelApi.host.config.errorHandlers, "disableTraceApi"); + }, - wrapSpanContext: wrapSpanContext, + // We use fnBind to automatically inject the "otelApi" argument as the first argument to the wrapSpanContext function + wrapSpanContext: fnBind(wrapSpanContext, UNDEFINED_VALUE, [otelApi]) as unknown as (spanContext: IDistributedTraceContext) => IReadableSpan, - isSpanContextValid: isSpanContextValid, + isSpanContextValid: isSpanContextValid, - deleteSpan: deleteContextSpan, + deleteSpan: deleteContextSpan, - getSpan: getContextSpan, + getSpan: getContextSpan, - getActiveSpan: (): IOTelSpan | undefined => { - let theSpan: IOTelSpan | undefined; - theSpan = getContextSpan(otelApi.context.active()); + getActiveSpan: (): IReadableSpan | undefined | null => { + return otelApi.host ? otelApi.host.getActiveSpan() : null; + }, - return theSpan; - }, + setSpanContext: setContextSpanContext, - setSpanContext: setContextSpanContext, + getSpanContext: getContextActiveSpanContext, - getSpanContext: getContextActiveSpanContext, + setSpan: setContextSpan, - setSpan: setContextSpan - }; - - return traceApi; -} - -function createNoopTraceProvider(): IOTelTracerProvider { - throw new Error("Function not implemented."); + setActiveSpan(span: IReadableSpan | undefined | null) { + return otelApi.host ? otelApi.host.setActiveSpan(span) : null; + } + }, "TraceApi"); + }); } diff --git a/shared/otel-core/src/otel/api/trace/traceProvider.ts b/shared/otel-core/src/otel/api/trace/traceProvider.ts new file mode 100644 index 000000000..fc3e258f0 --- /dev/null +++ b/shared/otel-core/src/otel/api/trace/traceProvider.ts @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { objDefine, strSubstr } from "@nevware21/ts-utils"; +import { createDistributedTraceContext } from "../../../core/TelemetryHelpers"; +import { eOTelSpanKind } from "../../../enums/otel/OTelSpanKind"; +import { OTelTimeInput } from "../../../interfaces/IOTelHrTime"; +import { IDistributedTraceContext } from "../../../interfaces/ai/IDistributedTraceContext"; +import { ITraceHost, ITraceProvider } from "../../../interfaces/ai/ITraceProvider"; +import { IOTelApi } from "../../../interfaces/otel/IOTelApi"; +import { IOTelSpanCtx } from "../../../interfaces/otel/trace/IOTelSpanCtx"; +import { IOTelSpanOptions } from "../../../interfaces/otel/trace/IOTelSpanOptions"; +import { IReadableSpan } from "../../../interfaces/otel/trace/IReadableSpan"; +import { generateW3CId } from "../../../utils/CoreUtils"; +import { createSpan } from "./span"; + +/** + * @internal + * Creates a new trace provider adapter + * @param host - The trace host instance (typically IAppInsightsCore). + * @param traceName - The name of the trace provider. + * @param api - The OpenTelemetry API instance (as a lazy value). + * @param onEnd - Optional callback to be invoked when a span ends. + * @param onException - Optional callback to be invoked when an exception is recorded on a span. + * @returns The created trace provider. + */ +/*#__NO_SIDE_EFFECTS__*/ +export function createTraceProvider(host: ITraceHost, traceName: string, api: IOTelApi, onEnd?: (span: IReadableSpan) => void, onException?: (span: IReadableSpan, exception: any, time?: OTelTimeInput) => void): ITraceProvider { + let provider: ITraceProvider = { + api: null, + createSpan: (name: string, options?: IOTelSpanOptions, parent?: IDistributedTraceContext): IReadableSpan => { + let newCtx: IDistributedTraceContext; + let parentCtx: IDistributedTraceContext | undefined; + + if (options && options.root) { + newCtx = createDistributedTraceContext(); + } else { + newCtx = createDistributedTraceContext(parent || host.getTraceCtx()); + if (newCtx.parentCtx) { + parentCtx = newCtx.parentCtx; + } + } + + // Always generate a new spanId + newCtx.spanId = strSubstr(generateW3CId(), 0, 16); + + let spanCtx: IOTelSpanCtx = { + api: api, + spanContext: newCtx, + attributes: options ? options.attributes : undefined, + startTime: options ? options.startTime : undefined, + isRecording: options ? options.recording !== false : true, + onEnd: onEnd, + onException: onException + }; + + if (parentCtx) { + objDefine(spanCtx, "parentSpanContext", { + v: parentCtx, + w: false + }); + } + + return createSpan(spanCtx, name, options?.kind || eOTelSpanKind.INTERNAL); + }, + getProviderId: () => traceName, + isAvailable: () => !!onEnd + }; + + objDefine(provider, "api", { + v: api, + w: false + }); + + return provider; +} diff --git a/shared/otel-core/src/otel/api/trace/traceState.ts b/shared/otel-core/src/otel/api/trace/traceState.ts index 58131674d..6e733687a 100644 --- a/shared/otel-core/src/otel/api/trace/traceState.ts +++ b/shared/otel-core/src/otel/api/trace/traceState.ts @@ -17,6 +17,7 @@ function _initOTelTraceStateSymbol() { return _otelTraceState; } +/*#__NO_SIDE_EFFECTS__*/ function _createOTelTraceState(traceState: IW3cTraceState): IOTelTraceState { if (!_otelTraceState) { _otelTraceState = _initOTelTraceStateSymbol(); @@ -58,6 +59,7 @@ function _createOTelTraceState(traceState: IW3cTraceState): IOTelTraceState { * @remarks The OpenTelemetry TraceState is an immutable object, meaning that any changes made to the trace state will * @since 3.4.0 */ +/*#__NO_SIDE_EFFECTS__*/ export function isOTelTraceState(value: any): value is IOTelTraceState { if (!_otelTraceState) { _otelTraceState = _initOTelTraceStateSymbol(); @@ -80,6 +82,7 @@ export function isOTelTraceState(value: any): value is IOTelTraceState { * @remarks The OpenTelemetry TraceState is an immutable object, meaning that any changes made to the trace state will * @since 3.4.0 */ +/*#__NO_SIDE_EFFECTS__*/ export function createOTelTraceState(value?: string | IW3cTraceState | IOTelTraceState | null): IOTelTraceState { let traceState: IW3cTraceState | null = null; if (isOTelTraceState(value)) { diff --git a/shared/otel-core/src/otel/api/trace/tracer.ts b/shared/otel-core/src/otel/api/trace/tracer.ts index 2e6b07375..1fdd313a2 100644 --- a/shared/otel-core/src/otel/api/trace/tracer.ts +++ b/shared/otel-core/src/otel/api/trace/tracer.ts @@ -1,70 +1,52 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { arrSlice, objDefineProps } from "@nevware21/ts-utils"; -import { IOTelContext } from "../../../interfaces/otel/context/IOTelContext"; -import { IOTelContextManager } from "../../../interfaces/otel/context/IOTelContextManager"; -import { IOTelSpan } from "../../../interfaces/otel/trace/IOTelSpan"; +import { fnApply, isFunction } from "@nevware21/ts-utils"; +import { STR_EMPTY } from "../../../constants/InternalConstants"; +import { ISpanScope, ITraceHost } from "../../../interfaces/ai/ITraceProvider"; import { IOTelSpanOptions } from "../../../interfaces/otel/trace/IOTelSpanOptions"; import { IOTelTracer } from "../../../interfaces/otel/trace/IOTelTracer"; -import { IOTelTracerCtx } from "../../../interfaces/otel/trace/IOTelTracerCtx"; -import { setContextSpan } from "./utils"; - -interface ITracerOptions { - name: string; - version?: string; - schemaUrl?: string; -} +import { IReadableSpan } from "../../../interfaces/otel/trace/IReadableSpan"; +import { setProtoTypeName } from "../../../utils/HelperFuncs"; +import { startActiveSpan } from "./utils"; /** - * Creates a new Tracer instance using the provided {@link IOTelTracerCtx} to obtain - * the current context and context manager and to start new spans. - * @param tracerCtx - The current {@link IOTelTracerCtx} instance - * @returns A new Tracer instance + * @internal + * Create a tracer implementation. + * @param host - The ApplicationInsights core instance + * @returns A tracer object */ -export function createTracer(tracerCtx: IOTelTracerCtx, tracerOptions: ITracerOptions): IOTelTracer { - - function _startSpan(name: string, options?: IOTelSpanOptions, context?: IOTelContext): IOTelSpan { - return tracerCtx.startSpan(name, options, context || tracerCtx.ctxMgr.active()); - } - - function _startActiveSpan ReturnType>(name: string, arg2?: F | IOTelSpanOptions, arg3?: F | IOTelContext, arg4?: F): ReturnType | undefined { - - let theArgs = arrSlice(arguments); - let cnt = theArgs.length; - let opts: IOTelSpanOptions | undefined; - let ctx: IOTelContext | undefined; - let fn: F; - let ctxMgr: IOTelContextManager = tracerCtx.ctxMgr; - - if (cnt == 2) { - fn = arg2 as F; - } else if (cnt == 3) { - opts = arg2 as IOTelSpanOptions | undefined; - fn = arg3 as F; - } else if (cnt >= 4) { - opts = arg2 as IOTelSpanOptions | undefined; - ctx = arg3 as IOTelContext | undefined; - fn = arg4 as F; - } - - if (fn) { - let theCtx = ctx || ctxMgr.active(); - let span = _startSpan(name, opts, theCtx); - let theContext = setContextSpan(theCtx, span); - - return ctxMgr.with(theContext, fn, undefined, span); - } - } - - let tracer = objDefineProps({} as IOTelTracer, { - startSpan: { - v: _startSpan +export function _createTracer(host: ITraceHost, name?: string): IOTelTracer { + let tracer: IOTelTracer = setProtoTypeName({ + startSpan(spanName: string, options?: IOTelSpanOptions): IReadableSpan | null { + // Note: context is not used / needed for Application Insights / 1DS + if (host) { + return host.startSpan(spanName, options); + } + + return null; }, - startActiveSpan: { - v: _startActiveSpan + startActiveSpan) => ReturnType>(name: string, fnOrOptions?: F | IOTelSpanOptions, fn?: F): ReturnType { + // Figure out which parameter order was passed + let theFn: F | null = null; + let opts: IOTelSpanOptions | null = null; + + if (isFunction(fnOrOptions)) { + // startActiveSpan unknown>(name: string, fn: F): ReturnType; + theFn = fnOrOptions; + } else { + // startActiveSpan unknown>(name: string, options: IOTelSpanOptions, fn: F): ReturnType; or + opts = fnOrOptions as IOTelSpanOptions; + theFn = fn; + } + + if (theFn) { + return startActiveSpan(host, name, opts, (spanScope: ISpanScope) => { + return fnApply(theFn, spanScope, [spanScope.span, spanScope]); + }); + } } - }); + }, "OTelTracer" + (name ? (" (" + name + ")") : STR_EMPTY)); return tracer; } diff --git a/shared/otel-core/src/otel/api/trace/tracerProvider.ts b/shared/otel-core/src/otel/api/trace/tracerProvider.ts new file mode 100644 index 000000000..25993f8e4 --- /dev/null +++ b/shared/otel-core/src/otel/api/trace/tracerProvider.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IPromise } from "@nevware21/ts-async"; +import { ITraceHost } from "../../../interfaces/ai/ITraceProvider"; +import { IOTelTracer } from "../../../interfaces/otel/trace/IOTelTracer"; +import { IOTelTracerProvider } from "../../../interfaces/otel/trace/IOTelTracerProvider"; +import { _createTracer } from "./tracer"; + +/** + * @internal + * Create a trace implementation with tracer caching. + * @param core - The ApplicationInsights core instance + * @returns A trace object + */ +export function _createTracerProvider(host: ITraceHost): IOTelTracerProvider { + let tracers: { [key: string]: IOTelTracer } = {}; + + return { + getTracer(name: string, version?: string): IOTelTracer { + const tracerKey = (name|| "ai-web") + "@" + (version || "unknown"); + + if (!tracers[tracerKey]) { + tracers[tracerKey] = _createTracer(host); + } + + return tracers[tracerKey]; + }, + forceFlush(): IPromise | void { + // Nothing to flush + return; + }, + shutdown(): IPromise | void { + // Just clear the locally cached IOTelTracer instances so they can be garbage collected + tracers = {}; + host = null; + return; + } + }; +} diff --git a/shared/otel-core/src/otel/api/trace/utils.ts b/shared/otel-core/src/otel/api/trace/utils.ts index 31481a01e..c91fc3486 100644 --- a/shared/otel-core/src/otel/api/trace/utils.ts +++ b/shared/otel-core/src/otel/api/trace/utils.ts @@ -1,93 +1,296 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { ILazyValue, createDeferredCachedValue, isFunction, symbolFor } from "@nevware21/ts-utils"; +import { doAwait, doFinally } from "@nevware21/ts-async"; +import { ICachedValue, arrSlice, fnApply, getDeferred, isFunction, isObject, isPromiseLike, symbolFor } from "@nevware21/ts-utils"; import { UNDEFINED_VALUE } from "../../../constants/InternalConstants"; +import { createDistributedTraceContext, isDistributedTraceContext } from "../../../core/TelemetryHelpers"; +import { eOTelSpanKind } from "../../../enums/otel/OTelSpanKind"; +import { eOTelSpanStatusCode } from "../../../enums/otel/OTelSpanStatus"; +import { IAppInsightsCore } from "../../../interfaces/ai/IAppInsightsCore"; +import { IConfiguration } from "../../../interfaces/ai/IConfiguration"; +import { IDistributedTraceContext, IDistributedTraceInit } from "../../../interfaces/ai/IDistributedTraceContext"; +import { ISpanScope, ITraceHost } from "../../../interfaces/ai/ITraceProvider"; +import { IOTelApi } from "../../../interfaces/otel/IOTelApi"; +import { ITraceCfg } from "../../../interfaces/otel/config/IOTelTraceCfg"; import { IOTelContext } from "../../../interfaces/otel/context/IOTelContext"; import { IOTelSpan } from "../../../interfaces/otel/trace/IOTelSpan"; import { IOTelSpanContext } from "../../../interfaces/otel/trace/IOTelSpanContext"; +import { IOTelSpanCtx } from "../../../interfaces/otel/trace/IOTelSpanCtx"; +import { IOTelSpanOptions } from "../../../interfaces/otel/trace/IOTelSpanOptions"; import { IReadableSpan } from "../../../interfaces/otel/trace/IReadableSpan"; import { isValidSpanId, isValidTraceId } from "../../../utils/TraceParent"; -import { createNonRecordingSpan } from "./nonRecordingSpan"; +import { createSpan } from "./span"; const OTEL_SPAN_KEY = "OpenTelemetry Context Key SPAN"; const OTEL_SUPPRESS_TRACING_KEY = "OpenTelemetry SDK Context Key SUPPRESS_TRACING"; -const SPAN_CONTEXT_KEY: ILazyValue = (/* @__PURE__ */ createDeferredCachedValue(() => symbolFor(OTEL_SPAN_KEY))); -const SUPPRESS_TRACING_CONTEXT_KEY: ILazyValue = (/* @__PURE__ */ createDeferredCachedValue(() => symbolFor(OTEL_SUPPRESS_TRACING_KEY))); +const SPAN_CONTEXT_KEY: ICachedValue = (/* @__PURE__ */ getDeferred(() => symbolFor(OTEL_SPAN_KEY))); +const SUPPRESS_TRACING_CONTEXT_KEY: ICachedValue = (/* @__PURE__ */ getDeferred(() => symbolFor(OTEL_SUPPRESS_TRACING_KEY))); /** - * Remove the span from a context returning a new context with the span removed - * @param context - context to use as the parent span - * @returns A new Context with the span removed + * Internal helper to execute a callback function with a span set as the active span. + * Handles both synchronous and asynchronous (Promise-based) callbacks, ensuring + * the previous active span is properly restored after execution. + * @param scope - The span scope instance + * @param fn - The callback function to execute + * @param thisArg - The `this` context for the callback + * @param args - Array of arguments to pass to the callback + * @returns The result of the callback function */ -/*#__NO_SIDE_EFFECTS__*/ -export function deleteContextSpan(context: IOTelContext): IOTelContext { - return context.deleteValue(SPAN_CONTEXT_KEY.v); +function _executeWithActiveSpan( + scope: S, + fn: (...args: any) => any, + thisArg: any, + args: any[] +): R { + let isAsync = false; + try { + let result = fnApply(fn, thisArg || scope, args); + if (isPromiseLike(result)) { + isAsync = true; + return doFinally(result, function () { + // Restore previous active span after promise settles (resolves or rejects) + if (scope) { + scope.restore(); + } + }) as any; + } + return result; + } finally { + // Restore previous active span only if result is not a promise + // (promises handle restoration in their callbacks) + if (scope && !isAsync) { + scope.restore(); + } + } } /** - * Get the current span from a context if one exists - * @param context - context to get span from - * @returns The current span if one exists otherwise undefined + * Execute the callback `fn` function with the passed span as the active span + * Note: The callback will be executed even if the span is null. + * @param traceHost - The current trace host instance (core or AISKU instance) + * @param span - The span to set as the active span during the execution of the callback + * @param fn - the callback function + * @param thisArg - the `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @param _args - Additional arguments to be passed to the function */ -/*#__NO_SIDE_EFFECTS__*/ -export function getContextSpan(context: IOTelContext): IOTelSpan | undefined { - return context.getValue(SPAN_CONTEXT_KEY.v) as IOTelSpan; +export function withSpan | ISpanScope, ...args: A) => ReturnType>(traceHost: T, span: IReadableSpan, fn: F, thisArg?: ThisParameterType, ..._args: A) : ReturnType; + +/** + * Execute the callback `fn` function with the passed span as the active span + * Note: The callback will be executed even if the span is null. + * @param traceHost - The current trace host instance (core or AISKU instance) + * @param span - The span to set as the active span during the execution of the callback + * @param fn - the callback function + * @param thisArg - the `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @returns the result of the function + */ +export function withSpan | ISpanScope,...args: A) => ReturnType>(traceHost: T, span: IReadableSpan, fn: F, thisArg?: ThisParameterType): ReturnType { + const scope = traceHost.setActiveSpan(span); + return _executeWithActiveSpan(scope, fn, thisArg, arrSlice(arguments, 4)); } /** - * Set the span on a context returning a new context with the span set - * - * @param context - The context to use as the parent - * @param span - span to set active - * @returns A new Context with the span set + * Execute the callback `fn` function with the passed span as the active span. The callback receives + * an ISpanScope object as its first parameter and the `this` context (when no thisArg is provided). + * Note: The callback will be executed even if the span is null. + * @param traceHost - The current trace host instance (core or AISKU instance) + * @param span - The span to set as the active span during the execution of the callback + * @param fn - the callback function that receives an ISpanScope + * @param thisArg - the `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @returns The result of the function */ -/*#__NO_SIDE_EFFECTS__*/ -export function setContextSpan(context: IOTelContext, span: IOTelSpan): IOTelContext { - return context.setValue(SPAN_CONTEXT_KEY.v, span); +export function useSpan | ISpanScope, scope: ISpanScope) => ReturnType>(traceHost: T, span: IReadableSpan, fn: F, thisArg?: ThisParameterType) : ReturnType; + +/** + * Execute the callback `fn` function with the passed span as the active span. The callback receives + * an ISpanScope object as its first parameter and the `this` context (when no thisArg is provided). + * Note: The callback will be executed even if the span is null. + * @param traceHost - The current trace host instance (core or AISKU instance) + * @param span - The span to set as the active span during the execution of the callback + * @param fn - the callback function that receives an ISpanScope and additional arguments + * @param thisArg - the `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @param _args - Additional arguments to be passed to the function + * @returns The result of the function + */ +export function useSpan | ISpanScope, scope: ISpanScope, ...args: A) => ReturnType>(traceHost: T, span: IReadableSpan, fn: F, thisArg?: ThisParameterType, ..._args: A) : ReturnType; + +/** + * Execute the callback `fn` function with the passed span as the active span. The callback receives + * an ISpanScope object as its first parameter and the `this` context (when no thisArg is provided). + * Note: The callback will be executed even if the span is null. + * @param traceHost - The current trace host instance (core or AISKU instance) + * @param span - The span to set as the active span during the execution of the callback + * @param fn - the callback function that receives an ISpanScope and additional arguments + * @param thisArg - the `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @param _args - Additional arguments to be passed to the function + */ +export function useSpan | ISpanScope, scope: ISpanScope, ...args: A) => ReturnType>(traceHost: T, span: IReadableSpan, fn: F, thisArg?: ThisParameterType): ReturnType { + let scope = traceHost.setActiveSpan(span); + return _executeWithActiveSpan(scope, fn, thisArg, [scope].concat(arrSlice(arguments, 4))); } /** - * Wrap span context in a NoopSpan and set as span in a new context + * Creates and starts a new span, sets it as the active span in the current context, + * and executes a provided function within this context. * - * @param context - context to set active span on - * @param spanContext - span context to be wrapped + * This method creates a span, makes it active during the execution of the provided + * function, and automatically ends the span when the function completes (or throws). + * This provides automatic span lifecycle management and context propagation. If the function + * is asynchronous the span will be ended when the returned Promise resolves or rejects. + * Note: The callback will be executed even if the traceHost is unable to create a span (returns null). + * @param name - The name of the span, should be descriptive of the operation being traced + * @param fn - The function to execute within the span's active context + * @param thisArg - The `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @returns The result of executing the provided function + * @remarks + * - The span is automatically ended when the function completes or throws an exception + * - The span becomes the active parent for any spans created within the function + * - If the function throws an error, the span status is automatically set to ERROR + * - This is the recommended method for most tracing scenarios due to automatic lifecycle management + * - Multiple overloads available for different parameter combinations */ -/*#__NO_SIDE_EFFECTS__*/ -export function setContextSpanContext(context: IOTelContext, spanContext: IOTelSpanContext): IOTelContext { - return setContextSpan(context, createNonRecordingSpan(spanContext)); -} +export function startActiveSpan | ISpanScope, scope?: ISpanScope) => ReturnType>(traceHost: T, name: string, fn: F, thisArg?: ThisParameterType): ReturnType; /** - * Get the active span's {@link IOTelSpanContext} if one exists for the provided {@link IOTelContext} + * Creates and starts a new span, sets it as the active span in the current context, + * and executes a provided function within this context. * - * @param context - The Context to get the SpanContext from - * @returns The SpanContext if one exists otherwise undefined + * This method creates a span, makes it active during the execution of the provided + * function, and automatically ends the span when the function completes (or throws). + * This provides automatic span lifecycle management and context propagation. If the function + * is asynchronous the span will be ended when the returned Promise resolves or rejects. + * Note: The callback will be executed even if the traceHost is unable to create a span (returns null). + * @param name - The name of the span, should be descriptive of the operation being traced + * @param options - Optional configuration for span creation (parent context, attributes, etc.) + * @param fn - The function to execute within the span's active context + * @param thisArg - The `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @returns The result of executing the provided function + * @remarks + * - The span is automatically ended when the function completes or throws an exception + * - The span becomes the active parent for any spans created within the function + * - If the function throws an error, the span status is automatically set to ERROR + * - This is the recommended method for most tracing scenarios due to automatic lifecycle management + * - Multiple overloads available for different parameter combinations */ -/*#__NO_SIDE_EFFECTS__*/ -export function getContextActiveSpanContext(context: IOTelContext): IOTelSpanContext | undefined { - let theSpan = getContextSpan(context); - return theSpan ? theSpan.spanContext() : UNDEFINED_VALUE; +export function startActiveSpan | ISpanScope, scope?: ISpanScope) => ReturnType>(traceHost: T, name: string, options: IOTelSpanOptions, fn: F, thisArg?: ThisParameterType): ReturnType; + +/** + * Creates and starts a new span, sets it as the active span in the current context, + * and executes a provided function within this context. + * + * This method creates a span, makes it active during the execution of the provided + * function, and automatically ends the span when the function completes (or throws). + * This provides automatic span lifecycle management and context propagation. If the function + * is asynchronous the span will be ended when the returned Promise resolves or rejects. + * Note: The callback will be executed even if the traceHost is unable to create a span (returns null). + * @remarks + * This overloaded version supports both optional span creation options and the `this` argument for the callback. + * @param name - The name of the span, should be descriptive of the operation being traced + * @param optionsOrFn - Optional configuration for span creation (parent context, attributes, etc.) or the function to execute within the span's active context + * @param maybeFnOrThis - The function to execute within the span's active context or the `this` argument for the callback + * @param thisArg - The `this` argument for the callback. If not provided, ISpanScope is used as `this` + * @returns The result of executing the provided function + * @remarks + * - The span is automatically ended when the function completes or throws an exception + * - The span becomes the active parent for any spans created within the function + * - If the function throws an error, the span status is automatically set to ERROR + * - This is the recommended method for most tracing scenarios due to automatic lifecycle management + * - Multiple overloads available for different parameter combinations + */ +export function startActiveSpan | ISpanScope, scope?: ISpanScope) => ReturnType>(traceHost: T, name: string, optionsOrFn: IOTelSpanOptions | F, maybeFnOrThis?: F | ThisParameterType, thisArg?: ThisParameterType): ReturnType { + let options: IOTelSpanOptions = null; + let fn: F; + let that: ThisParameterType = thisArg; + + if (isFunction(optionsOrFn)) { + fn = optionsOrFn as F; + } else { + options = optionsOrFn as IOTelSpanOptions; + fn = maybeFnOrThis as F; + that = thisArg || (maybeFnOrThis as ThisParameterType); + } + + let span = traceHost.startSpan(name, options); + let useAsync = false; + + try { + let result = useSpan(traceHost, span, (scope: ISpanScope): ReturnType => { + return fnApply(fn, that, [scope]); + }); + + if (isPromiseLike(result)) { + useAsync = true; + + return doAwait(result, + (value) => value, + (reason) => { + if (span) { + span.setStatus({ code: reason ? eOTelSpanStatusCode.ERROR : eOTelSpanStatusCode.OK, message: reason ? reason.message || reason : undefined }); + } + }, + () => { + if (span) { + span.end(); + } + }) as ReturnType; + } + + return result; + } catch (e) { + if (span) { + span.setStatus({ code: e ? eOTelSpanStatusCode.ERROR : eOTelSpanStatusCode.OK, message: e ? e.message : undefined }); + } + throw e; + } finally { + // If the function returned a promise, we need to end the span when the promise resolves/rejects + if (!useAsync && span) { + span.end(); + } + } } - + /** - * Returns true if this {@link IOTelSpanContext} is valid. - * @return true if this {@link IOTelSpanContext} is valid. + * Returns true if the passed spanContext of type {@link IDistributedTraceContext} or {@link IDistributedTraceInit} is valid. + * @return true if this {@link IDistributedTraceContext} is valid. */ /*#__NO_SIDE_EFFECTS__*/ -export function isSpanContextValid(spanContext: IOTelSpanContext): boolean { - return isValidTraceId(spanContext.traceId) && isValidSpanId(spanContext.spanId); +export function isSpanContextValid(spanContext: IDistributedTraceContext | IDistributedTraceInit | IOTelSpanContext): boolean { + return spanContext ? (isValidTraceId(spanContext.traceId) && isValidSpanId(spanContext.spanId)) : false; } /** - * Wrap the given {@link IOTelSpanContext} in a new non-recording {@link IReadableSpan} + * Wrap the given {@link IDistributedTraceContext} in a new non-recording {@link IReadableSpan} * * @param spanContext - span context to be wrapped - * @returns a new non-recording {@link IOTelSpan} with the provided context + * @returns a new non-recording {@link IReadableSpan} with the provided context */ -/*#__NO_SIDE_EFFECTS__*/ -export function wrapSpanContext(spanContext: IOTelSpanContext): IOTelSpan { - return createNonRecordingSpan(spanContext); +export function wrapSpanContext(otelApi: IOTelApi, spanContext: IDistributedTraceContext | IDistributedTraceInit | IOTelSpanContext): IReadableSpan { + if (!isDistributedTraceContext(spanContext)) { + spanContext = createDistributedTraceContext(spanContext); + } + + // Return a non-recording span + return createNonRecordingSpan(otelApi, "wrapped(\"" + spanContext.spanId + "\")", spanContext); +} + +/** + * Return a non-recording span based on the provided spanContext using the otelApi instance as the + * owning instance. + * @param otelApi - The otelApi to use for creating the non-Recording Span + * @param spanName - The span name to associated with the span + * @param spanContext - The Span context to use for the span + * @returns A new span that is marked as a non-recording span + */ +export function createNonRecordingSpan(otelApi: IOTelApi, spanName: string, spanContext: IDistributedTraceContext | IDistributedTraceInit | IOTelSpanContext): IReadableSpan { + // Return a non-recording span + let spanCtx: IOTelSpanCtx = { + api: otelApi, + spanContext: isDistributedTraceContext(spanContext) ? spanContext : createDistributedTraceContext(spanContext), + isRecording: false + }; + + return createSpan(spanCtx, spanName, eOTelSpanKind.INTERNAL); } /** @@ -98,6 +301,7 @@ export function wrapSpanContext(spanContext: IOTelSpanContext): IOTelSpan { /*#__NO_SIDE_EFFECTS__*/ export function isReadableSpan(span: any): span is IReadableSpan { return !!span && + isObject(span) && "name" in span && "kind" in span && isFunction(span.spanContext) && @@ -126,6 +330,20 @@ export function isReadableSpan(span: any): span is IReadableSpan { isFunction(span.recordException); } +function _getTraceCfg(context: IOTelApi | ITraceHost | IConfiguration): ITraceCfg { + let traceCfg: ITraceCfg = null; + if (context) { + if ((context as IOTelApi).cfg && (context as IOTelApi).host) { + traceCfg = (context as IOTelApi).cfg.traceCfg; + } else if (isFunction((context as IAppInsightsCore).initialize) && (context as IAppInsightsCore).config) { + traceCfg = (context as IAppInsightsCore).config.traceCfg; + } else if ((context as IConfiguration).traceCfg) { + traceCfg = (context as IConfiguration).traceCfg; + } + } + + return traceCfg; +} /** * Set the suppress tracing flag on the context @@ -134,8 +352,13 @@ export function isReadableSpan(span: any): span is IReadableSpan { * @remarks This is used to suppress tracing for the current context and all child contexts */ /*#__NO_SIDE_EFFECTS__*/ -export function suppressTracing(context: IOTelContext): IOTelContext { - return context.setValue(SUPPRESS_TRACING_CONTEXT_KEY.v, true); +export function suppressTracing(context: T): T { + let traceCfg = _getTraceCfg(context); + if (traceCfg) { + traceCfg.suppressTracing = true; + } + + return context; } /** @@ -146,8 +369,13 @@ export function suppressTracing(context: IOTelContext): IOTelContext { * contexts. This is used to restore tracing after it has been suppressed */ /*#__NO_SIDE_EFFECTS__*/ -export function unsuppressTracing(context: IOTelContext): IOTelContext { - return context.deleteValue(SUPPRESS_TRACING_CONTEXT_KEY.v); +export function unsuppressTracing(context: T): T { + let traceCfg = _getTraceCfg(context); + if (traceCfg) { + traceCfg.suppressTracing = false; + } + + return context; } /** @@ -157,6 +385,67 @@ export function unsuppressTracing(context: IOTelContext): IOTelContext { * @remarks This is used to check if tracing is suppressed for the current context and all child */ /*#__NO_SIDE_EFFECTS__*/ -export function isTracingSuppressed(context: IOTelContext): boolean { - return context.getValue(SUPPRESS_TRACING_CONTEXT_KEY.v) === true; +export function isTracingSuppressed(context: T): boolean { + let result = false; + let traceCfg = _getTraceCfg(context); + if (traceCfg) { + result = !!traceCfg.suppressTracing; + } + + return result; +} + +/** + * Remove the span from a context returning a new context with the span removed + * @param context - context to use as the parent span + * @returns A new Context with the span removed + */ +/*#__NO_SIDE_EFFECTS__*/ +export function deleteContextSpan(context: IOTelContext): IOTelContext { + return context.deleteValue(SPAN_CONTEXT_KEY.v); +} + +/** + * Get the current span from a context if one exists + * @param context - context to get span from + * @returns The current span if one exists otherwise undefined + */ +/*#__NO_SIDE_EFFECTS__*/ +export function getContextSpan(context: IOTelContext): IReadableSpan | undefined { + return context.getValue(SPAN_CONTEXT_KEY.v) as IReadableSpan | undefined; +} + +/** + * Set the span on a context returning a new context with the span set + * + * @param context - The context to use as the parent + * @param span - span to set active + * @returns A new Context with the span set + */ +/*#__NO_SIDE_EFFECTS__*/ +export function setContextSpan(context: IOTelContext, span: IOTelSpan | IReadableSpan): IOTelContext { + return context.setValue(SPAN_CONTEXT_KEY.v, span); +} + +/** + * Wrap span context in a NoopSpan and set as span in a new context + * + * @param context - context to set active span on + * @param spanContext - span context to be wrapped + */ +/*#__NO_SIDE_EFFECTS__*/ +export function setContextSpanContext(context: IOTelContext, spanContext: IDistributedTraceContext | IOTelSpanContext): IOTelContext { + return setContextSpan(context, wrapSpanContext(context.api, spanContext)); +} + +/** + * Get the active span's {@link IOTelSpanContext} if one exists for the provided {@link IOTelContext} + * + * @param context - The Context to get the SpanContext from + * @returns The SpanContext if one exists otherwise undefined + */ +/*#__NO_SIDE_EFFECTS__*/ +export function getContextActiveSpanContext(context: IOTelContext): IDistributedTraceContext | undefined { + let theSpan = getContextSpan(context); + return theSpan ? theSpan.spanContext() : UNDEFINED_VALUE; } diff --git a/shared/otel-core/src/otel/attribute/SemanticConventions.ts b/shared/otel-core/src/otel/attribute/SemanticConventions.ts new file mode 100644 index 000000000..c81afe16d --- /dev/null +++ b/shared/otel-core/src/otel/attribute/SemanticConventions.ts @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const MICROSOFT_APPLICATIONINSIGHTS_NAME = "Microsoft.ApplicationInsights."; +const MICROSOFT_DOT = "microsoft."; +const CLIENT_DOT = "client."; +const HTTP_DOT = "http."; +const NET_DOT = "net."; +const PEER_DOT = "peer."; +const EXCEPTION_DOT = "exception."; +const ENDUSER_DOT = "enduser."; +const URL_DOT = "url."; +const DB_DOT = "db."; +const NETWORK_DOT = "network."; + +export const AzureMonitorSampleRate = MICROSOFT_DOT + "sample_rate"; +export const ApplicationInsightsCustomEventName = MICROSOFT_DOT + "custom_event.name"; +export const MicrosoftClientIp = MICROSOFT_DOT + CLIENT_DOT + "ip"; + +export const ApplicationInsightsMessageName = MICROSOFT_APPLICATIONINSIGHTS_NAME + "Message"; +export const ApplicationInsightsExceptionName = MICROSOFT_APPLICATIONINSIGHTS_NAME + "Exception"; +export const ApplicationInsightsPageViewName = MICROSOFT_APPLICATIONINSIGHTS_NAME + "PageView"; +export const ApplicationInsightsAvailabilityName = MICROSOFT_APPLICATIONINSIGHTS_NAME + "Availability"; +export const ApplicationInsightsEventName = MICROSOFT_APPLICATIONINSIGHTS_NAME + "Event"; + +export const ApplicationInsightsBaseType = "_MS.baseType"; +export const ApplicationInsightsMessageBaseType = "MessageData"; +export const ApplicationInsightsExceptionBaseType = "ExceptionData"; +export const ApplicationInsightsPageViewBaseType = "PageViewData"; +export const ApplicationInsightsAvailabilityBaseType = "AvailabilityData"; +export const ApplicationInsightsEventBaseType = "EventData"; + +export const ATTR_ENDUSER_ID = ENDUSER_DOT + "id"; +export const ATTR_ENDUSER_PSEUDO_ID = ENDUSER_DOT + "pseudo_id"; +export const ATTR_HTTP_ROUTE = HTTP_DOT + "route"; + +export const SEMATTRS_NET_PEER_IP = NET_DOT + PEER_DOT + "ip"; +export const SEMATTRS_NET_PEER_NAME = NET_DOT + PEER_DOT + "name"; +export const SEMATTRS_NET_HOST_IP = NET_DOT + "host.ip"; +export const SEMATTRS_PEER_SERVICE = PEER_DOT + "service"; +export const SEMATTRS_HTTP_USER_AGENT = HTTP_DOT + "user_agent"; +export const SEMATTRS_HTTP_METHOD = HTTP_DOT + "method"; +export const SEMATTRS_HTTP_URL = HTTP_DOT + "url"; +export const SEMATTRS_HTTP_STATUS_CODE = HTTP_DOT + "status_code"; +export const SEMATTRS_HTTP_ROUTE = HTTP_DOT + "route"; +export const SEMATTRS_HTTP_HOST = HTTP_DOT + "host"; +export const SEMATTRS_DB_SYSTEM = DB_DOT + "system"; +export const SEMATTRS_DB_STATEMENT = DB_DOT + "statement"; +export const SEMATTRS_DB_OPERATION = DB_DOT + "operation"; +export const SEMATTRS_DB_NAME = DB_DOT + "name"; +export const SEMATTRS_RPC_SYSTEM = "rpc.system"; +export const SEMATTRS_RPC_GRPC_STATUS_CODE = "rpc.grpc.status_code"; +export const SEMATTRS_EXCEPTION_TYPE = EXCEPTION_DOT + "type"; +export const SEMATTRS_EXCEPTION_MESSAGE = EXCEPTION_DOT + "message"; +export const SEMATTRS_EXCEPTION_STACKTRACE = EXCEPTION_DOT + "stacktrace"; +export const SEMATTRS_HTTP_SCHEME = HTTP_DOT + "scheme"; +export const SEMATTRS_HTTP_TARGET = HTTP_DOT + "target"; +export const SEMATTRS_HTTP_FLAVOR = HTTP_DOT + "flavor"; +export const SEMATTRS_NET_TRANSPORT = NET_DOT + "transport"; +export const SEMATTRS_NET_HOST_NAME = NET_DOT + "host.name"; +export const SEMATTRS_NET_HOST_PORT = NET_DOT + "host.port"; +export const SEMATTRS_NET_PEER_PORT = NET_DOT + PEER_DOT + "port"; +export const SEMATTRS_HTTP_CLIENT_IP = HTTP_DOT + "client_ip"; +export const SEMATTRS_ENDUSER_ID = ENDUSER_DOT + "id"; + +export const ATTR_CLIENT_ADDRESS = CLIENT_DOT + "address"; +export const ATTR_CLIENT_PORT = CLIENT_DOT + "port"; +export const ATTR_SERVER_ADDRESS = "server.address"; +export const ATTR_SERVER_PORT = "server.port"; +export const ATTR_URL_FULL = URL_DOT + "full"; +export const ATTR_URL_PATH = URL_DOT + "path"; +export const ATTR_URL_QUERY = URL_DOT + "query"; +export const ATTR_URL_SCHEME = URL_DOT + "scheme"; +export const ATTR_ERROR_TYPE = "error.type"; +export const ATTR_NETWORK_LOCAL_ADDRESS = NETWORK_DOT + "local.address"; +export const ATTR_NETWORK_LOCAL_PORT = NETWORK_DOT + "local.port"; +export const ATTR_NETWORK_PROTOCOL_NAME = NETWORK_DOT + "protocol.name"; +export const ATTR_NETWORK_PEER_ADDRESS = NETWORK_DOT + PEER_DOT + "address"; +export const ATTR_NETWORK_PEER_PORT = NETWORK_DOT + PEER_DOT + "port"; +export const ATTR_NETWORK_PROTOCOL_VERSION = NETWORK_DOT + "protocol.version"; +export const ATTR_NETWORK_TRANSPORT = NETWORK_DOT + "transport"; +export const ATTR_USER_AGENT_ORIGINAL = "user_agent.original"; +export const ATTR_HTTP_REQUEST_METHOD = HTTP_DOT + "request.method"; +export const ATTR_HTTP_RESPONSE_STATUS_CODE = HTTP_DOT + "response.status_code"; +export const ATTR_EXCEPTION_TYPE = EXCEPTION_DOT + "type"; +export const ATTR_EXCEPTION_MESSAGE = EXCEPTION_DOT + "message"; +export const ATTR_EXCEPTION_STACKTRACE = EXCEPTION_DOT + "stacktrace"; +export const EXP_ATTR_ENDUSER_ID = ENDUSER_DOT + "id"; +export const EXP_ATTR_ENDUSER_PSEUDO_ID = ENDUSER_DOT + "pseudo_id"; +export const EXP_ATTR_SYNTHETIC_TYPE = "synthetic.type"; + + +export const DBSYSTEMVALUES_MONGODB = "mongodb"; +export const DBSYSTEMVALUES_COSMOSDB = "cosmosdb"; +export const DBSYSTEMVALUES_MYSQL = "mysql"; +export const DBSYSTEMVALUES_POSTGRESQL = "postgresql"; +export const DBSYSTEMVALUES_REDIS = "redis"; +export const DBSYSTEMVALUES_DB2 = "db2"; +export const DBSYSTEMVALUES_DERBY = "derby"; +export const DBSYSTEMVALUES_MARIADB = "mariadb"; +export const DBSYSTEMVALUES_MSSQL = "mssql"; +export const DBSYSTEMVALUES_ORACLE = "oracle"; +export const DBSYSTEMVALUES_SQLITE = "sqlite"; +export const DBSYSTEMVALUES_OTHER_SQL = "other_sql"; +export const DBSYSTEMVALUES_HSQLDB = "hsqldb"; +export const DBSYSTEMVALUES_H2 = "h2"; diff --git a/shared/otel-core/src/otel/attribute/attributeContainer.ts b/shared/otel-core/src/otel/attribute/attributeContainer.ts index 366ca9c40..c77d303b2 100644 --- a/shared/otel-core/src/otel/attribute/attributeContainer.ts +++ b/shared/otel-core/src/otel/attribute/attributeContainer.ts @@ -12,11 +12,11 @@ import { IOTelAttributes, OTelAttributeValue } from "../../interfaces/otel/IOTel import { IAttributeChangeInfo, IAttributeContainer, eAttributeFilter } from "../../interfaces/otel/attribute/IAttributeContainer"; import { IOTelAttributeLimits } from "../../interfaces/otel/config/IOTelAttributeLimits"; import { IOTelConfig } from "../../interfaces/otel/config/IOTelConfig"; -import { IOTelTraceCfg } from "../../interfaces/otel/config/IOTelTraceCfg"; -import { handleAttribError } from "../../internal/commonUtils"; +import { ITraceCfg } from "../../interfaces/otel/config/IOTelTraceCfg"; +import { handleAttribError } from "../../internal/handleErrors"; -let _inheritedKey = "~[[inherited]]"; -let _deletedKey = "~[[deleted]]"; +const _inheritedKey = "~[[inherited]]"; +const _deletedKey = "~[[deleted]]"; let _containerId = 0; type IAttributeBranch = { [key: string]: IAttributeNode }; @@ -347,7 +347,7 @@ function _iterator(target: IAttributeBranch, cb: (prefix: string, key: let ctx: CreateIteratorContext | null = { v: undefined, n: _moveNext - } + }; if (parentAttribs) { if (isAttributeContainer(parentAttribs)) { @@ -675,12 +675,13 @@ export function createAttributeContainer(otelCfg: * ``` */ export function createAttributeContainer(otelCfg: IOTelConfig, name?: string | null | undefined, inheritAttrib?: IOTelAttributes | IAttributeContainer, attribLimits?: IOTelAttributeLimits): IAttributeContainer { - let traceCfg: IOTelTraceCfg = otelCfg.traceCfg || {}; + let traceCfg: ITraceCfg = otelCfg.traceCfg || {}; let nodes: { [key: string]: IAttributeNode } | null = null; let theSize: ICachedValue | null = null; let theDropped: ICachedValue | null = null; let limits: IOTelAttributeLimits = traceCfg.generalLimits || {}; let maxAttribs: number = limits.attributeCountLimit || 128; + // let maxValueLen: number = limits.attributeValueLengthLimit; let theAttributes: ICachedValue; let localAttributes: ICachedValue; let droppedAttribs = 0; @@ -692,6 +693,7 @@ export function createAttributeContainer(otelCfg: if (attribLimits) { maxAttribs = attribLimits.attributeCountLimit || maxAttribs; + // maxValueLen = attribLimits.attributeValueLengthLimit || maxValueLen; } // Determine if inheritAttrib is a container or plain attributes object @@ -875,7 +877,7 @@ export function createAttributeContainer(otelCfg: return _createUnloadHook(listeners, callback); } - } + }; function _listener(changeInfo: IAttributeChangeInfo) { // Invalidate caches when parent changes @@ -1075,6 +1077,7 @@ function _createSnapshotContainer(otelCfg: IOTelConfig, name: string | undefined * container.set("key1", "changed"); // snapshot2.get("key1") remains "value1" (previous value copied) * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function createAttributeSnapshot(otelCfg: IOTelConfig, name: string, source: IOTelAttributes | IAttributeContainer, attribLimits?: IOTelAttributeLimits): IAttributeContainer { let newContainer: IAttributeContainer; @@ -1110,6 +1113,7 @@ export function createAttributeSnapshot(otelCfg: IOTelConfig, name: string, sour * } * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function isAttributeContainer(value: any): value is IAttributeContainer { return value && isFunction(value.clear) && diff --git a/shared/otel-core/src/otel/resource/resource.ts b/shared/otel-core/src/otel/resource/resource.ts index 6841caa04..eebf7c362 100644 --- a/shared/otel-core/src/otel/resource/resource.ts +++ b/shared/otel-core/src/otel/resource/resource.ts @@ -8,7 +8,7 @@ import { IOTelAttributes, OTelAttributeValue } from "../../interfaces/otel/IOTel import { IAttributeContainer } from "../../interfaces/otel/attribute/IAttributeContainer"; import { IOTelResource, OTelRawResourceAttribute } from "../../interfaces/otel/resources/IOTelResource"; import { IOTelResourceCtx } from "../../interfaces/otel/resources/IOTelResourceCtx"; -import { handleDebug, handleError } from "../../internal/commonUtils"; +import { handleDebug, handleError } from "../../internal/handleErrors"; import { createAttributeContainer } from "../attribute/attributeContainer"; type ResourceKeyValue = [key: string, value: OTelAttributeValue | undefined]; diff --git a/shared/otel-core/src/otel/sdk/OTelLogRecord.ts b/shared/otel-core/src/otel/sdk/OTelLogRecord.ts index 693f00237..4fb5e5488 100644 --- a/shared/otel-core/src/otel/sdk/OTelLogRecord.ts +++ b/shared/otel-core/src/otel/sdk/OTelLogRecord.ts @@ -3,6 +3,8 @@ import { objForEachKey, objKeys, utcNow } from "@nevware21/ts-utils"; import { OTelSeverityNumber } from "../../enums/otel/eOTelSeverityNumber"; +import { IOTelHrTime } from "../../interfaces/IOTelHrTime"; +import { IDistributedTraceContext } from "../../interfaces/ai/IDistributedTraceContext"; import { OTelAttributeValue } from "../../interfaces/otel/IOTelAttributes"; import { IOTelErrorHandlers } from "../../interfaces/otel/config/IOTelErrorHandlers"; import { IOTelLogRecord, LogAttributes, LogBody } from "../../interfaces/otel/logs/IOTelLogRecord"; @@ -11,12 +13,10 @@ import { IOTelLogRecordLimits } from "../../interfaces/otel/logs/IOTelLogRecordL import { IOTelLoggerProviderSharedState } from "../../interfaces/otel/logs/IOTelLoggerProviderSharedState"; import { IOTelResource } from "../../interfaces/otel/resources/IOTelResource"; import { IOTelInstrumentationScope } from "../../interfaces/otel/trace/IOTelInstrumentationScope"; -import { IOTelSpanContext } from "../../interfaces/otel/trace/IOTelSpanContext"; import { isAttributeValue } from "../../internal/attributeHelpers"; -import { handleWarn } from "../../internal/commonUtils"; +import { handleWarn } from "../../internal/handleErrors"; import { timeInputToHrTime } from "../../internal/timeHelpers"; import { OTelAnyValue } from "../../types/OTelAnyValue"; -import { IOTelHrTime } from "../../types/time"; import { getContextActiveSpanContext, isSpanContextValid } from "../api/trace/utils"; export function createLogRecord( @@ -43,7 +43,7 @@ export function createLogRecord( const logRecordLimits: Required = sharedState.logRecordLimits; const handlers: IOTelErrorHandlers = {}; - let spanContext: IOTelSpanContext | undefined; + let spanContext: IDistributedTraceContext | undefined; if (context) { const activeSpanContext = getContextActiveSpanContext(context); if (activeSpanContext && isSpanContextValid(activeSpanContext)) { @@ -192,7 +192,7 @@ export function createLogRecord( get hrTimeObserved(): IOTelHrTime { return hrTimeObserved; }, - get spanContext(): IOTelSpanContext | undefined { + get spanContext(): IDistributedTraceContext | undefined { return spanContext; }, get resource(): IOTelResource { diff --git a/shared/otel-core/src/otel/sdk/OTelLoggerProvider.ts b/shared/otel-core/src/otel/sdk/OTelLoggerProvider.ts index caccecf3c..535f94103 100644 --- a/shared/otel-core/src/otel/sdk/OTelLoggerProvider.ts +++ b/shared/otel-core/src/otel/sdk/OTelLoggerProvider.ts @@ -10,7 +10,7 @@ import { IOTelLoggerProvider } from "../../interfaces/otel/logs/IOTelLoggerProvi import { IOTelLoggerProviderConfig } from "../../interfaces/otel/logs/IOTelLoggerProviderConfig"; import { IOTelLoggerProviderSharedState } from "../../interfaces/otel/logs/IOTelLoggerProviderSharedState"; import { createLoggerProviderSharedState } from "../../internal/LoggerProviderSharedState"; -import { handleWarn } from "../../internal/commonUtils"; +import { handleWarn } from "../../internal/handleErrors"; import { createResource } from "../resource/resource"; import { createLogger } from "./OTelLogger"; import { loadDefaultConfig, reconfigureLimits } from "./config"; diff --git a/shared/otel-core/src/otel/sdk/config.ts b/shared/otel-core/src/otel/sdk/config.ts index b798b76b5..c55d7703b 100644 --- a/shared/otel-core/src/otel/sdk/config.ts +++ b/shared/otel-core/src/otel/sdk/config.ts @@ -4,7 +4,7 @@ import { strTrim } from "@nevware21/ts-utils"; import { IOTelErrorHandlers } from "../../interfaces/otel/config/IOTelErrorHandlers"; import { IOTelLogRecordLimits } from "../../interfaces/otel/logs/IOTelLogRecordLimits"; -import { handleWarn } from "../../internal/commonUtils"; +import { handleWarn } from "../../internal/handleErrors"; /** * Retrieves a number from an environment variable. @@ -15,6 +15,7 @@ import { handleWarn } from "../../internal/commonUtils"; * @param {string} key - The name of the environment variable to retrieve. * @returns {number | undefined} - The number value or `undefined`. */ +// TODO: Remove this function as this MUST be using the common shared dynamic configuration and not loading its own isolaoted instance. export function getNumberFromEnv(key: string): number | undefined { // Handle browser environments where process is not defined const handlers: IOTelErrorHandlers = {}; @@ -36,6 +37,7 @@ export function getNumberFromEnv(key: string): number | undefined { return value; } +// TODO: Remove this function as this MUST be using the common shared dynamic configuration and not loading its own isolaoted instance. export function loadDefaultConfig() { return { forceFlushTimeoutMillis: 30000, @@ -58,6 +60,7 @@ export function loadDefaultConfig() { * configures the model specific limits by using the values from the general ones. * @param logRecordLimits User provided limits configuration */ +// TODO: Remove this function as this MUST be using the common shared dynamic configuration and not loading its own isolaoted instance. export function reconfigureLimits( logRecordLimits: IOTelLogRecordLimits ): Required { diff --git a/shared/otel-core/src/telemetry/TelemetryItemCreator.ts b/shared/otel-core/src/telemetry/TelemetryItemCreator.ts index 122d3182a..b36e10a67 100644 --- a/shared/otel-core/src/telemetry/TelemetryItemCreator.ts +++ b/shared/otel-core/src/telemetry/TelemetryItemCreator.ts @@ -5,7 +5,7 @@ import { isNullOrUndefined, objForEachKey, throwError } from "@nevware21/ts-util import { strIkey, strNotSpecified } from "../constants/Constants"; import { IDiagnosticLogger } from "../interfaces/ai/IDiagnosticLogger"; import { ITelemetryItem } from "../interfaces/ai/ITelemetryItem"; -import { toISOString } from "../utils/HelperFuncsCore"; +import { toISOString } from "../utils/HelperFuncs"; import { dataSanitizeString } from "./ai/Common/DataSanitizer"; /** diff --git a/shared/otel-core/src/telemetry/W3cTraceState.ts b/shared/otel-core/src/telemetry/W3cTraceState.ts index 899977f0d..02546ac63 100644 --- a/shared/otel-core/src/telemetry/W3cTraceState.ts +++ b/shared/otel-core/src/telemetry/W3cTraceState.ts @@ -2,12 +2,13 @@ // Licensed under the MIT License. import { - ICachedValue, WellKnownSymbols, arrForEach, arrIndexOf, createCachedValue, createDeferredCachedValue, getKnownSymbol, isArray, - isFunction, isNullOrUndefined, isString, objDefine, objDefineProps, safe, strSplit + ICachedValue, arrForEach, arrIndexOf, createCachedValue, isArray, isFunction, isNullOrUndefined, isString, objDefineProps, + safeGetDeferred, strSplit } from "@nevware21/ts-utils"; import { STR_EMPTY } from "../constants/InternalConstants"; import { IW3cTraceState } from "../interfaces/ai/IW3cTraceState"; import { findMetaTags, findNamedServerTimings } from "../utils/EnvUtils"; +import { setObjStringTag } from "../utils/HelperFuncs"; const MAX_TRACE_STATE_MEMBERS = 32; const MAX_TRACE_STATE_LEN = 512; @@ -26,7 +27,7 @@ const NBLK_CHAR = "\x21-\x2B\\--\x3C\x3E-\x7E"; const TRACESTATE_VALUE = "[\x20" + NBLK_CHAR + "]{0,255}[" + NBLK_CHAR + "]"; // https://www.w3.org/TR/trace-context-1/#tracestate-header -const TRACESTATE_KVP_REGEX = new RegExp("^\\s*((?:" + SIMPLE_KEY + "|" + MULTI_TENANT_KEY + ")=(" + TRACESTATE_VALUE + "))\\s*$"); +const TRACESTATE_KVP_REGEX = (/*#__PURE__*/ new RegExp("^\\s*((?:" + SIMPLE_KEY + "|" + MULTI_TENANT_KEY + ")=(" + TRACESTATE_VALUE + "))\\s*$")); /** * @internal @@ -188,6 +189,7 @@ function _keys(items: ITraceStateMember[], parent?: IW3cTraceState | null): stri * @param parent - The parent trace state to check for keys * @returns true if the items are empty, false otherwise */ +/*#__NO_SIDE_EFFECTS__*/ function _isEmpty(items: ITraceStateMember[], parent?: IW3cTraceState | null): boolean { let delKeys: string[]; let isEmpty = true; @@ -229,6 +231,7 @@ function _isEmpty(items: ITraceStateMember[], parent?: IW3cTraceState | null): b * @param value - The value to check * @returns - True if the value looks like a distributed trace state instance */ +/*#__NO_SIDE_EFFECTS__*/ export function isW3cTraceState(value: any): value is IW3cTraceState { return !!(value && isArray(value.keys) && isFunction(value.get) && isFunction(value.set) && isFunction(value.del) && isFunction(value.hdrs)); } @@ -244,8 +247,9 @@ export function isW3cTraceState(value: any): value is IW3cTraceState { * @param parent - The parent trace state to inherit any existing keys from. * @returns - A new distributed trace state instance */ +/*#__NO_SIDE_EFFECTS__*/ export function createW3cTraceState(value?: string | null, parent?: IW3cTraceState | null): IW3cTraceState { - let cachedItems: ICachedValue = createDeferredCachedValue(() => safe(_parseTraceStateList, [value || STR_EMPTY]).v || []); + let cachedItems: ICachedValue = safeGetDeferred(_parseTraceStateList, [], [value || STR_EMPTY]); function _get(key: string): string | undefined { let value: string | undefined; @@ -387,8 +391,7 @@ export function createW3cTraceState(value?: string | null, parent?: IW3cTraceSta } }); - - objDefine(traceStateList, getKnownSymbol(WellKnownSymbols.toStringTag), { g: _toString }); + setObjStringTag(traceStateList, _toString); return traceStateList; } @@ -403,6 +406,7 @@ export function createW3cTraceState(value?: string | null, parent?: IW3cTraceSta * @param traceState - The trace state instance to snapshot * @returns A new independent instance of IW3cTraceState with all current key/value pairs captured */ +/*#__NO_SIDE_EFFECTS__*/ export function snapshotW3cTraceState(traceState: IW3cTraceState): IW3cTraceState { // Create a new independent instance with no parent // This ensures the returned instance is completely independent from future changes @@ -434,6 +438,7 @@ export function snapshotW3cTraceState(traceState: IW3cTraceState): IW3cTraceStat * @param selectIdx - If the found value is comma separated which is the preferred entry to select, defaults to the first * @returns */ +/*#__NO_SIDE_EFFECTS__*/ export function findW3cTraceState(): IW3cTraceState { const name = "tracestate"; let traceState: IW3cTraceState = null; diff --git a/shared/otel-core/src/telemetry/ai/Common/Data.ts b/shared/otel-core/src/telemetry/ai/Common/Data.ts deleted file mode 100644 index 10515d639..000000000 --- a/shared/otel-core/src/telemetry/ai/Common/Data.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { FieldType } from "../../../enums/ai/Enums"; -import { IData } from "../../../interfaces/ai/contracts/IData"; -import { ISerializable } from "../../../interfaces/ai/telemetry/ISerializable"; - -export class Data implements IData, ISerializable { - - /** - * The data contract for serializing this object. - */ - public aiDataContract = { - baseType: FieldType.Required, - baseData: FieldType.Required - } - - /** - * Name of item (B section) if any. If telemetry data is derived straight from this, this should be null. - */ - public baseType: string; - - /** - * Container for data item (B section). - */ - public baseData: TDomain; - - /** - * Constructs a new instance of telemetry data. - */ - constructor(baseType: string, data: TDomain) { - this.baseType = baseType; - this.baseData = data; - } -} diff --git a/shared/otel-core/src/telemetry/ai/Common/DataSanitizer.ts b/shared/otel-core/src/telemetry/ai/Common/DataSanitizer.ts index 3c0bcedc3..efce32458 100644 --- a/shared/otel-core/src/telemetry/ai/Common/DataSanitizer.ts +++ b/shared/otel-core/src/telemetry/ai/Common/DataSanitizer.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { asString, isObject, isString, objForEachKey, strSubstr, strSubstring, strTrim } from "@nevware21/ts-utils"; +import { _throwInternal } from "../../../diagnostics/DiagnosticLogger"; import { _eInternalMessageId, eLoggingSeverity } from "../../../enums/ai/LoggingEnums"; import { IConfiguration } from "../../../interfaces/ai/IConfiguration"; import { IDiagnosticLogger } from "../../../interfaces/ai/IDiagnosticLogger"; @@ -44,6 +45,7 @@ export const enum DataSanitizerValues { MAX_EXCEPTION_LENGTH = 32768 } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeKeyAndAddUniqueness(logger: IDiagnosticLogger, key: any, map: any) { const origLength = key.length; let field = dataSanitizeKey(logger, key); @@ -61,6 +63,7 @@ export function dataSanitizeKeyAndAddUniqueness(logger: IDiagnosticLogger, key: return field; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeKey(logger: IDiagnosticLogger, name: any) { let nameTrunc: String; if (name) { @@ -70,19 +73,18 @@ export function dataSanitizeKey(logger: IDiagnosticLogger, name: any) { // truncate the string to 150 chars if (name.length > DataSanitizerValues.MAX_NAME_LENGTH) { nameTrunc = strSubstring(name, 0, DataSanitizerValues.MAX_NAME_LENGTH); - if (logger) { - logger.throwInternal( - eLoggingSeverity.WARNING, - _eInternalMessageId.NameTooLong, - "name is too long. It has been truncated to " + DataSanitizerValues.MAX_NAME_LENGTH + " characters.", - { name }); - } + _throwInternal(logger, + eLoggingSeverity.WARNING, + _eInternalMessageId.NameTooLong, + "name is too long. It has been truncated to " + DataSanitizerValues.MAX_NAME_LENGTH + " characters.", + { name }, true); } } return nameTrunc || name; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeString(logger: IDiagnosticLogger, value: any, maxLength: number = DataSanitizerValues.MAX_STRING_LENGTH) { let valueTrunc : String; if (value) { @@ -90,19 +92,18 @@ export function dataSanitizeString(logger: IDiagnosticLogger, value: any, maxLen value = strTrim(asString(value)); if (value.length > maxLength) { valueTrunc = strSubstring(value, 0, maxLength); - if (logger) { - logger.throwInternal( - eLoggingSeverity.WARNING, - _eInternalMessageId.StringValueTooLong, - "string value is too long. It has been truncated to " + maxLength + " characters.", - { value }); - } + _throwInternal(logger, + eLoggingSeverity.WARNING, + _eInternalMessageId.StringValueTooLong, + "string value is too long. It has been truncated to " + maxLength + " characters.", + { value }, true); } } return valueTrunc || value; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeUrl(logger: IDiagnosticLogger, url: any, config?: IConfiguration) { if (isString(url)) { url = fieldRedaction(url, config); @@ -110,23 +111,24 @@ export function dataSanitizeUrl(logger: IDiagnosticLogger, url: any, config?: IC return dataSanitizeInput(logger, url, DataSanitizerValues.MAX_URL_LENGTH, _eInternalMessageId.UrlTooLong); } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeMessage(logger: IDiagnosticLogger, message: any) { let messageTrunc : String; if (message) { if (message.length > DataSanitizerValues.MAX_MESSAGE_LENGTH) { messageTrunc = strSubstring(message, 0, DataSanitizerValues.MAX_MESSAGE_LENGTH); - if (logger) { - logger.throwInternal( - eLoggingSeverity.WARNING, _eInternalMessageId.MessageTruncated, - "message is too long, it has been truncated to " + DataSanitizerValues.MAX_MESSAGE_LENGTH + " characters.", - { message }); - } + _throwInternal(logger, + eLoggingSeverity.WARNING, _eInternalMessageId.MessageTruncated, + "message is too long, it has been truncated to " + DataSanitizerValues.MAX_MESSAGE_LENGTH + " characters.", + { message }, + true); } } return messageTrunc || message; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeException(logger: IDiagnosticLogger, exception: any) { let exceptionTrunc : String; if (exception) { @@ -134,17 +136,16 @@ export function dataSanitizeException(logger: IDiagnosticLogger, exception: any) let value:string = "" + exception; if (value.length > DataSanitizerValues.MAX_EXCEPTION_LENGTH) { exceptionTrunc = strSubstring(value, 0, DataSanitizerValues.MAX_EXCEPTION_LENGTH); - if (logger) { - logger.throwInternal( - eLoggingSeverity.WARNING, _eInternalMessageId.ExceptionTruncated, "exception is too long, it has been truncated to " + DataSanitizerValues.MAX_EXCEPTION_LENGTH + " characters.", - { exception }); - } + _throwInternal(logger, + eLoggingSeverity.WARNING, _eInternalMessageId.ExceptionTruncated, "exception is too long, it has been truncated to " + DataSanitizerValues.MAX_EXCEPTION_LENGTH + " characters.", + { exception }, true); } } return exceptionTrunc || exception; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeProperties(logger: IDiagnosticLogger, properties: any) { if (properties) { const tempProps: any = {}; @@ -154,9 +155,7 @@ export function dataSanitizeProperties(logger: IDiagnosticLogger, properties: an try { value = getJSON().stringify(value); } catch (e) { - if (logger) { - logger.throwInternal(eLoggingSeverity.WARNING, _eInternalMessageId.CannotSerializeObjectNonSerializable, "custom property is not valid", { exception: e }); - } + _throwInternal(logger,eLoggingSeverity.WARNING, _eInternalMessageId.CannotSerializeObjectNonSerializable, "custom property is not valid", { exception: e}, true); } } value = dataSanitizeString(logger, value, DataSanitizerValues.MAX_PROPERTY_LENGTH); @@ -169,6 +168,7 @@ export function dataSanitizeProperties(logger: IDiagnosticLogger, properties: an return properties; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeMeasurements(logger: IDiagnosticLogger, measurements: any) { if (measurements) { const tempMeasurements: any = {}; @@ -183,30 +183,31 @@ export function dataSanitizeMeasurements(logger: IDiagnosticLogger, measurements return measurements; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeId(logger: IDiagnosticLogger, id: string): string { return id ? dataSanitizeInput(logger, id, DataSanitizerValues.MAX_ID_LENGTH, _eInternalMessageId.IdTooLong).toString() : id; } +/*#__NO_SIDE_EFFECTS__*/ export function dataSanitizeInput(logger: IDiagnosticLogger, input: any, maxLength: number, _msgId: _eInternalMessageId) { let inputTrunc : String; if (input) { input = strTrim(asString(input)); if (input.length > maxLength) { inputTrunc = strSubstring(input, 0, maxLength); - if (logger) { - logger.throwInternal( - eLoggingSeverity.WARNING, - _msgId, - "input is too long, it has been truncated to " + maxLength + " characters.", - { data: input }, - true); - } + _throwInternal(logger, + eLoggingSeverity.WARNING, + _msgId, + "input is too long, it has been truncated to " + maxLength + " characters.", + { data: input }, + true); } } return inputTrunc || input; } +/*#__NO_SIDE_EFFECTS__*/ export function dsPadNumber(num: number) { const s = "00" + num; return strSubstr(s, s.length - 3); diff --git a/shared/otel-core/src/telemetry/ai/Common/Envelope.ts b/shared/otel-core/src/telemetry/ai/Common/Envelope.ts index e6be55ce3..c08e68abe 100644 --- a/shared/otel-core/src/telemetry/ai/Common/Envelope.ts +++ b/shared/otel-core/src/telemetry/ai/Common/Envelope.ts @@ -6,7 +6,7 @@ import { FieldType } from "../../../enums/ai/Enums"; import { IDiagnosticLogger } from "../../../interfaces/ai/IDiagnosticLogger"; import { IBase } from "../../../interfaces/ai/contracts/IBase"; import { IEnvelope } from "../../../interfaces/ai/telemetry/IEnvelope"; -import { toISOString } from "../../../utils/HelperFuncsCore"; +import { toISOString } from "../../../utils/HelperFuncs"; import { dataSanitizeString } from "./DataSanitizer"; export class Envelope implements IEnvelope { diff --git a/shared/otel-core/src/telemetry/ai/DataTypes.ts b/shared/otel-core/src/telemetry/ai/DataTypes.ts new file mode 100644 index 000000000..13e6ae95e --- /dev/null +++ b/shared/otel-core/src/telemetry/ai/DataTypes.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export const EventDataType = "EventData"; +export const ExceptionDataType = "ExceptionData"; +export const MetricDataType = "MetricData"; +export const PageViewDataType = "PageviewData"; +export const PageViewPerformanceDataType = "PageviewPerformanceData"; +export const RemoteDependencyDataType = "RemoteDependencyData"; +export const RequestDataType = "RequestData"; +export const TraceDataType = "MessageData"; diff --git a/shared/otel-core/src/telemetry/ai/EnvelopeTypes.ts b/shared/otel-core/src/telemetry/ai/EnvelopeTypes.ts new file mode 100644 index 000000000..f13a3a5fc --- /dev/null +++ b/shared/otel-core/src/telemetry/ai/EnvelopeTypes.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* #__NO_SIDE_EFFECTS__# */ +function _AddPrefix(name: string) { + return "Microsoft.ApplicationInsights.{0}." + name; +} + +export const EventEnvelopeType = (/*#__PURE__*/ _AddPrefix("Event")); +export const ExceptionEnvelopeType = (/*#__PURE__*/ _AddPrefix("Exception")); +export const MetricEnvelopeType = (/*#__PURE__*/ _AddPrefix("Metric")); +export const PageViewEnvelopeType = (/*#__PURE__*/ _AddPrefix("Pageview")); +export const PageViewPerformanceEnvelopeType = (/*#__PURE__*/ _AddPrefix("PageviewPerformance")); +export const RemoteDependencyEnvelopeType = (/*#__PURE__*/ _AddPrefix("RemoteDependency")); +export const RequestEnvelopeType = (/*#__PURE__*/ _AddPrefix("Request")); +export const TraceEnvelopeType = (/*#__PURE__*/ _AddPrefix("Message")); \ No newline at end of file diff --git a/shared/otel-core/src/telemetry/ai/Event.ts b/shared/otel-core/src/telemetry/ai/Event.ts index 4e21e8d79..769fa3e39 100644 --- a/shared/otel-core/src/telemetry/ai/Event.ts +++ b/shared/otel-core/src/telemetry/ai/Event.ts @@ -7,10 +7,19 @@ import { IDiagnosticLogger } from "../../interfaces/ai/IDiagnosticLogger"; import { IEventData } from "../../interfaces/ai/contracts/IEventData"; import { ISerializable } from "../../interfaces/ai/telemetry/ISerializable"; import { dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString } from "./Common/DataSanitizer"; +import { EventDataType } from "./DataTypes"; +import { EventEnvelopeType } from "./EnvelopeTypes"; export class Event implements IEventData, ISerializable { - public static envelopeType = "Microsoft.ApplicationInsights.{0}.Event"; - public static dataType = "EventData"; + /** + * @deprecated Use the constant EventEnvelopeType instead. + */ + public static envelopeType = EventEnvelopeType; + + /** + * @deprecated Use the constant EventDataType instead. + */ + public static dataType = EventDataType; public aiDataContract = { ver: FieldType.Required, diff --git a/shared/otel-core/src/telemetry/ai/Exception.ts b/shared/otel-core/src/telemetry/ai/Exception.ts index 9ef9fee38..1514fa009 100644 --- a/shared/otel-core/src/telemetry/ai/Exception.ts +++ b/shared/otel-core/src/telemetry/ai/Exception.ts @@ -19,6 +19,8 @@ import { ISerializable } from "../../interfaces/ai/telemetry/ISerializable"; import { dataSanitizeException, dataSanitizeMeasurements, dataSanitizeMessage, dataSanitizeProperties, dataSanitizeString } from "./Common/DataSanitizer"; +import { ExceptionDataType } from "./DataTypes"; +import { ExceptionEnvelopeType } from "./EnvelopeTypes"; // These Regex covers the following patterns // 1. Chrome/Firefox/IE/Edge: @@ -538,9 +540,15 @@ export function _formatErrorCode(errorObj:any) { } export class Exception implements IExceptionData, ISerializable { + /** + * @deprecated Use the constant ExceptionEnvelopeType instead. + */ + public static envelopeType = ExceptionEnvelopeType; - public static envelopeType = "Microsoft.ApplicationInsights.{0}.Exception"; - public static dataType = "ExceptionData"; + /** + * @deprecated Use the constant ExceptionDataType instead. + */ + public static dataType = ExceptionDataType; public id?: string; public problemGroup?: string; @@ -697,7 +705,7 @@ export class Exception implements IExceptionData, ISerializable { public static formatError = _formatErrorCode; } -const exDetailsAiDataContract = objFreeze({ +const exDetailsAiDataContract = (/*#__PURE__*/ objFreeze({ id: FieldType.Default, outerId: FieldType.Default, typeName: FieldType.Required, @@ -705,7 +713,7 @@ const exDetailsAiDataContract = objFreeze({ hasFullStack: FieldType.Default, stack: FieldType.Default, parsedStack: FieldType.Array -}); +})); interface _IExceptionDetails extends IExceptionDetails, ISerializable { toInterface: () => IExceptionDetailsInternal; @@ -845,13 +853,13 @@ export interface _IParsedStackFrame extends IStackFrame, ISerializable { sizeInBytes: number; } -const stackFrameAiDataContract = objFreeze({ +const stackFrameAiDataContract = (/*#__PURE__*/ objFreeze({ level: FieldType.Required, method: FieldType.Required, assembly: FieldType.Default, fileName: FieldType.Default, line: FieldType.Default -}); +})); export function _extractStackFrame(frame: string, level: number): _IParsedStackFrame | undefined { let theFrame: _IParsedStackFrame; diff --git a/shared/otel-core/src/telemetry/ai/Metric.ts b/shared/otel-core/src/telemetry/ai/Metric.ts index c2666cb1e..a302e5b22 100644 --- a/shared/otel-core/src/telemetry/ai/Metric.ts +++ b/shared/otel-core/src/telemetry/ai/Metric.ts @@ -8,11 +8,19 @@ import { IMetricData } from "../../interfaces/ai/contracts/IMetricData"; import { ISerializable } from "../../interfaces/ai/telemetry/ISerializable"; import { DataPoint } from "./Common/DataPoint"; import { dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString } from "./Common/DataSanitizer"; +import { MetricDataType } from "./DataTypes"; +import { MetricEnvelopeType } from "./EnvelopeTypes"; export class Metric implements IMetricData, ISerializable { + /** + * @deprecated Use the constant MetricEnvelopeType instead. + */ + public static envelopeType = MetricEnvelopeType; - public static envelopeType = "Microsoft.ApplicationInsights.{0}.Metric"; - public static dataType = "MetricData"; + /** + * @deprecated Use the constant MetricDataType instead. + */ + public static dataType = MetricDataType; public aiDataContract = { ver: FieldType.Required, diff --git a/shared/otel-core/src/telemetry/ai/PageView.ts b/shared/otel-core/src/telemetry/ai/PageView.ts index 95ee33d1b..9c101f09c 100644 --- a/shared/otel-core/src/telemetry/ai/PageView.ts +++ b/shared/otel-core/src/telemetry/ai/PageView.ts @@ -10,11 +10,19 @@ import { msToTimeSpan } from "../../utils/HelperFuncs"; import { dataSanitizeId, dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString, dataSanitizeUrl } from "./Common/DataSanitizer"; +import { PageViewDataType } from "./DataTypes"; +import { PageViewEnvelopeType } from "./EnvelopeTypes"; export class PageView implements IPageViewData, ISerializable { + /** + * @deprecated Use the constant PageViewEnvelopeType instead. + */ + public static envelopeType = PageViewEnvelopeType; - public static envelopeType = "Microsoft.ApplicationInsights.{0}.Pageview"; - public static dataType = "PageviewData"; + /** + * @deprecated Use the constant PageViewDataType instead. + */ + public static dataType = PageViewDataType; public aiDataContract = { ver: FieldType.Required, diff --git a/shared/otel-core/src/telemetry/ai/PageViewPerformance.ts b/shared/otel-core/src/telemetry/ai/PageViewPerformance.ts index c6c331e2a..0b11d83ca 100644 --- a/shared/otel-core/src/telemetry/ai/PageViewPerformance.ts +++ b/shared/otel-core/src/telemetry/ai/PageViewPerformance.ts @@ -8,11 +8,19 @@ import { IPageViewPerformanceTelemetry } from "../../interfaces/ai/IPageViewPerf import { IPageViewPerfData } from "../../interfaces/ai/contracts/IPageViewPerfData"; import { ISerializable } from "../../interfaces/ai/telemetry/ISerializable"; import { dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString, dataSanitizeUrl } from "./Common/DataSanitizer"; +import { PageViewPerformanceDataType } from "./DataTypes"; +import { PageViewPerformanceEnvelopeType } from "./EnvelopeTypes"; export class PageViewPerformance implements IPageViewPerfData, ISerializable { + /** + * @deprecated Use the constant PageViewPerformanceEnvelopeType instead. + */ + public static envelopeType = PageViewPerformanceEnvelopeType; - public static envelopeType = "Microsoft.ApplicationInsights.{0}.PageviewPerformance"; - public static dataType = "PageviewPerformanceData"; + /** + * @deprecated Use the constant PageViewPerformanceDataType instead. + */ + public static dataType = PageViewPerformanceDataType; public aiDataContract = { ver: FieldType.Required, diff --git a/shared/otel-core/src/telemetry/ai/RemoteDependencyData.ts b/shared/otel-core/src/telemetry/ai/RemoteDependencyData.ts deleted file mode 100644 index c5dea53e0..000000000 --- a/shared/otel-core/src/telemetry/ai/RemoteDependencyData.ts +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { FieldType } from "../../enums/ai/Enums"; -import { IDiagnosticLogger } from "../../interfaces/ai/IDiagnosticLogger"; -import { IRemoteDependencyData } from "../../interfaces/ai/contracts/IRemoteDependencyData"; -import { ISerializable } from "../../interfaces/ai/telemetry/ISerializable"; -import { msToTimeSpan } from "../../utils/HelperFuncs"; -import { AjaxHelperParseDependencyPath } from "../../utils/Util"; -import { dataSanitizeMeasurements, dataSanitizeProperties, dataSanitizeString, dataSanitizeUrl } from "./Common/DataSanitizer"; - -export class RemoteDependencyData implements IRemoteDependencyData, ISerializable { - - public static envelopeType = "Microsoft.ApplicationInsights.{0}.RemoteDependency"; - public static dataType = "RemoteDependencyData"; - - public aiDataContract = { - id: FieldType.Required, - ver: FieldType.Required, - name: FieldType.Default, - resultCode: FieldType.Default, - duration: FieldType.Default, - success: FieldType.Default, - data: FieldType.Default, - target: FieldType.Default, - type: FieldType.Default, - properties: FieldType.Default, - measurements: FieldType.Default, - - kind: FieldType.Default, - value: FieldType.Default, - count: FieldType.Default, - min: FieldType.Default, - max: FieldType.Default, - stdDev: FieldType.Default, - dependencyKind: FieldType.Default, - dependencySource: FieldType.Default, - commandName: FieldType.Default, - dependencyTypeName: FieldType.Default - } - - /** - * Schema version - */ - public ver: number; // = 2; - - /** - * Name of the command initiated with this dependency call. Low cardinality value. Examples are stored procedure name and URL path template. - */ - public name: string; - - /** - * Identifier of a dependency call instance. Used for correlation with the request telemetry item corresponding to this dependency call. - */ - public id: string; - - /** - * Result code of a dependency call. Examples are SQL error code and HTTP status code. - */ - public resultCode: string; - - /** - * Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. - */ - public duration: string; - - /** - * Indication of successful or unsuccessful call. - */ - public success: boolean; /* true */ - - /** - * Command initiated by this dependency call. Examples are SQL statement and HTTP URL's with all query parameters. - */ - public data: string; - - /** - * Target site of a dependency call. Examples are server name, host address. - */ - public target: string; - - /** - * Dependency type name. Very low cardinality value for logical grouping of dependencies and interpretation of other fields like commandName and resultCode. Examples are SQL, Azure table, and HTTP. - */ - public type: string; - - /** - * Collection of custom properties. - */ - public properties: any; // = {}; - - /** - * Collection of custom measurements. - */ - public measurements: any; // = {}; - - /** - * Constructs a new instance of the RemoteDependencyData object - */ - constructor(logger: IDiagnosticLogger, id: string, absoluteUrl: string, commandName: string, value: number, success: boolean, resultCode: number, method?: string, requestAPI: string = "Ajax", correlationContext?: string, properties?: Object, measurements?: Object) { - let _self = this; - - _self.ver = 2; - _self.id = id; - _self.duration = msToTimeSpan(value); - _self.success = success; - _self.resultCode = resultCode + ""; - - _self.type = dataSanitizeString(logger, requestAPI); - - const dependencyFields = AjaxHelperParseDependencyPath(logger, absoluteUrl, method, commandName); - _self.data = dataSanitizeUrl(logger, commandName) || dependencyFields.data; // get a value from hosturl if commandName not available - _self.target = dataSanitizeString(logger, dependencyFields.target); - if (correlationContext) { - _self.target = `${_self.target} | ${correlationContext}`; - } - _self.name = dataSanitizeString(logger, dependencyFields.name); - - _self.properties = dataSanitizeProperties(logger, properties); - _self.measurements = dataSanitizeMeasurements(logger, measurements); - } -} diff --git a/shared/otel-core/src/telemetry/ai/Trace.ts b/shared/otel-core/src/telemetry/ai/Trace.ts index e9e56bc98..f04bfb890 100644 --- a/shared/otel-core/src/telemetry/ai/Trace.ts +++ b/shared/otel-core/src/telemetry/ai/Trace.ts @@ -8,11 +8,19 @@ import { IMessageData } from "../../interfaces/ai/contracts/IMessageData"; import { SeverityLevel } from "../../interfaces/ai/contracts/SeverityLevel"; import { ISerializable } from "../../interfaces/ai/telemetry/ISerializable"; import { dataSanitizeMeasurements, dataSanitizeMessage, dataSanitizeProperties } from "./Common/DataSanitizer"; +import { TraceDataType } from "./DataTypes"; +import { TraceEnvelopeType } from "./EnvelopeTypes"; export class Trace implements IMessageData, ISerializable { + /** + * @deprecated Use the constant TraceEnvelopeType instead. + */ + public static envelopeType = TraceEnvelopeType; - public static envelopeType = "Microsoft.ApplicationInsights.{0}.Message"; - public static dataType = "MessageData"; + /** + * @deprecated Use the constant TraceDataType instead. + */ + public static dataType = TraceDataType; public aiDataContract = { ver: FieldType.Required, diff --git a/shared/otel-core/src/types/time.ts b/shared/otel-core/src/types/time.ts deleted file mode 100644 index 5c299ae8d..000000000 --- a/shared/otel-core/src/types/time.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IOTelHrTime, OTelHrTimeBase } from "../interfaces/IOTelHrTime"; - -export { IOTelHrTime, OTelHrTimeBase }; - -export type OTelTimeInput = IOTelHrTime | number | Date; diff --git a/shared/otel-core/src/utils/DataCacheHelper.ts b/shared/otel-core/src/utils/DataCacheHelper.ts index 7d3b56cc3..cd02749a2 100644 --- a/shared/otel-core/src/utils/DataCacheHelper.ts +++ b/shared/otel-core/src/utils/DataCacheHelper.ts @@ -3,7 +3,7 @@ import { objDefine } from "@nevware21/ts-utils"; import { STR_EMPTY } from "../constants/InternalConstants"; -import { normalizeJsName } from "./HelperFuncsCore"; +import { normalizeJsName } from "./HelperFuncs"; import { newId } from "./RandomHelper"; const version = "#version#"; diff --git a/shared/otel-core/src/utils/EnvUtils.ts b/shared/otel-core/src/utils/EnvUtils.ts index 55ca1acc0..ed2382901 100644 --- a/shared/otel-core/src/utils/EnvUtils.ts +++ b/shared/otel-core/src/utils/EnvUtils.ts @@ -4,11 +4,12 @@ import { getGlobal, strShimObject, strShimPrototype, strShimUndefined } from "@microsoft/applicationinsights-shims"; import { - arrForEach, getDocument, getInst, getNavigator, getPerformance, hasNavigator, isFunction, isNullOrUndefined, isString, isUndefined, - mathMax, strContains, strIndexOf, strSubstring + ICachedValue, arrForEach, createCachedValue, getDeferred, getDocument, getInst, getLazy, getNavigator, getPerformance, hasNavigator, + isFunction, isNullOrUndefined, isString, isUndefined, mathMax, strIndexOf, strSubstring } from "@nevware21/ts-utils"; import { DEFAULT_SENSITIVE_PARAMS, STR_EMPTY, STR_REDACTED } from "../constants/InternalConstants"; import { IConfiguration } from "../interfaces/ai/IConfiguration"; +import { strContains } from "./HelperFuncs"; // TypeScript removed this interface so we need to declare the global so we can check for it's existence. declare var XDomainRequest: any; @@ -31,11 +32,13 @@ const strMsie = "msie"; const strTrident = "trident/"; const strXMLHttpRequest = "XMLHttpRequest"; -let _isTrident: boolean = null; -let _navUserAgentCheck: string = null; +// Local cached properties which are used to avoid multiple checks within the module +let _isTrident: ICachedValue; +let _navUserAgentCheck: string; let _enableMocks = false; -let _useXDomainRequest: boolean | null = null; -let _beaconsSupported: boolean | null = null; +let _useXDomainRequest: ICachedValue; +let _beaconsSupported: ICachedValue; +let _userAgent: ICachedValue; function _hasProperty(theClass: any, property: string) { let supported = false; @@ -65,6 +68,19 @@ function _hasProperty(theClass: any, property: string) { return supported; } +/*#__NO_SIDE_EFFECTS__*/ +export function getUserAgentString(): string { + if (!_userAgent) { + // Use lazy to allow mocking + _userAgent = getLazy(() => { + let nav = getNavigator() || {} as Navigator; + return nav.userAgent || STR_EMPTY; + }); + } + + return _userAgent.v; +} + /** * Enable the lookup of test mock objects if requested * @param enabled - A flag to enable or disable the mock @@ -78,6 +94,7 @@ export function setEnableEnvMocks(enabled: boolean) { * This helper is used to access the location object without causing an exception * "Uncaught ReferenceError: location is not defined" */ +/*#__NO_SIDE_EFFECTS__*/ export function getLocation(checkForMock?: boolean): Location | null { if (checkForMock && _enableMocks) { let mockLocation = getInst("__mockLocation") as Location; @@ -96,6 +113,7 @@ export function getLocation(checkForMock?: boolean): Location | null { /** * Returns the global console object */ +/*#__NO_SIDE_EFFECTS__*/ export function getConsole(): Console | null { if (typeof console !== strShimUndefined) { return console; @@ -111,6 +129,7 @@ export function getConsole(): Console | null { * exception will be thrown. * Defined as a function to support lazy / late binding environments. */ +/*#__NO_SIDE_EFFECTS__*/ export function hasJSON(): boolean { return Boolean((typeof JSON === strShimObject && JSON) || getInst(strJSON) !== null); } @@ -120,6 +139,7 @@ export function hasJSON(): boolean { * This helper is used to access the JSON object without causing an exception * "Uncaught ReferenceError: JSON is not defined" */ +/*#__NO_SIDE_EFFECTS__*/ export function getJSON(): JSON | null { if (hasJSON()) { return JSON || getInst(strJSON); @@ -133,6 +153,7 @@ export function getJSON(): JSON | null { * This helper is used to access the crypto object from the current * global instance which could be window or globalThis for a web worker */ +/*#__NO_SIDE_EFFECTS__*/ export function getCrypto(): Crypto | null { return getInst(strCrypto); } @@ -142,6 +163,7 @@ export function getCrypto(): Crypto | null { * This helper is used to access the crypto object from the current * global instance which could be window or globalThis for a web worker */ +/*#__NO_SIDE_EFFECTS__*/ export function getMsCrypto(): Crypto | null { return getInst(strMsCrypto); } @@ -149,6 +171,7 @@ export function getMsCrypto(): Crypto | null { /** * Returns whether the environment is reporting that we are running in a React Native Environment */ +/*#__NO_SIDE_EFFECTS__*/ export function isReactNative(): boolean { // If running in React Native, navigator.product will be populated var nav = getNavigator(); @@ -162,25 +185,26 @@ export function isReactNative(): boolean { /** * Identifies whether the current environment appears to be IE */ +/*#__NO_SIDE_EFFECTS__*/ export function isIE() { - let nav = getNavigator(); - if (nav && (nav.userAgent !== _navUserAgentCheck || _isTrident === null)) { + let userAgent = getUserAgentString(); + if (!_isTrident || userAgent !== _navUserAgentCheck) { // Added to support test mocking of the user agent - _navUserAgentCheck = nav.userAgent; - let userAgent = (_navUserAgentCheck || STR_EMPTY).toLowerCase(); - _isTrident = (strContains(userAgent, strMsie) || strContains(userAgent, strTrident)); + _navUserAgentCheck = userAgent; + let lwrUserAgent = _navUserAgentCheck.toLowerCase(); + _isTrident = createCachedValue( strContains(lwrUserAgent, strMsie) || strContains(lwrUserAgent, strTrident)); } - return _isTrident; + return _isTrident.v; } /** * Gets IE version returning the document emulation mode if we are running on IE, or null otherwise */ +/*#__NO_SIDE_EFFECTS__*/ export function getIEVersion(userAgentStr: string = null): number { if (!userAgentStr) { - let navigator = getNavigator() || ({} as Navigator); - userAgentStr = navigator ? (navigator.userAgent || STR_EMPTY).toLowerCase() : STR_EMPTY; + userAgentStr = getUserAgentString(); } var ua = (userAgentStr || STR_EMPTY).toLowerCase(); @@ -198,10 +222,10 @@ export function getIEVersion(userAgentStr: string = null): number { return null; } +/*#__NO_SIDE_EFFECTS__*/ export function isSafari(userAgentStr ?: string) { if (!userAgentStr || !isString(userAgentStr)) { - let navigator = getNavigator() || ({} as Navigator); - userAgentStr = navigator ? (navigator.userAgent || STR_EMPTY).toLowerCase() : STR_EMPTY; + userAgentStr = getUserAgentString().toLowerCase(); } var ua = (userAgentStr || STR_EMPTY).toLowerCase(); @@ -209,17 +233,19 @@ export function isSafari(userAgentStr ?: string) { } /** - * Checks if HTML5 Beacons are supported in the current environment. + * Checks if HTML5 Beacons are supported in the current environment. There is a side effect (for testing) + * when `useCached` is set to `false`, it will reset the cached value causing all future calls to + * use the new re-evaluated value for all future calls. * @param useCached - [Optional] used for testing to bypass the cached lookup, when `true` this will * cause the cached global to be reset. * @returns True if supported, false otherwise. */ export function isBeaconsSupported(useCached?: boolean): boolean { - if (_beaconsSupported === null || useCached === false) { - _beaconsSupported = hasNavigator() && Boolean(getNavigator().sendBeacon); + if (!_beaconsSupported || useCached === false) { + _beaconsSupported = createCachedValue(hasNavigator() && !!(getNavigator().sendBeacon)); } - return _beaconsSupported; + return _beaconsSupported.v; } /** @@ -227,6 +253,7 @@ export function isBeaconsSupported(useCached?: boolean): boolean { * @param withKeepAlive - [Optional] If True, check if fetch is available and it supports the keepalive feature, otherwise only check if fetch is supported * @returns True if supported, otherwise false */ +/*#__NO_SIDE_EFFECTS__*/ export function isFetchSupported(withKeepAlive?: boolean): boolean { let isSupported = false; try { @@ -242,21 +269,27 @@ export function isFetchSupported(withKeepAlive?: boolean): boolean { return isSupported; } +/*#__NO_SIDE_EFFECTS__*/ export function useXDomainRequest(): boolean | undefined { - if (_useXDomainRequest === null) { - _useXDomainRequest = (typeof XDomainRequest !== strShimUndefined); - if (_useXDomainRequest && isXhrSupported()) { - _useXDomainRequest = _useXDomainRequest && !_hasProperty(getInst(strXMLHttpRequest), "withCredentials"); - } + if (!_useXDomainRequest) { + _useXDomainRequest = getDeferred(() => { + let useXDomainRequest = (typeof XDomainRequest !== strShimUndefined); + if (useXDomainRequest && isXhrSupported()) { + useXDomainRequest = useXDomainRequest && !_hasProperty(getInst(strXMLHttpRequest), "withCredentials"); + } + + return !!useXDomainRequest; + }); } - return _useXDomainRequest; + return _useXDomainRequest.v; } /** * Checks if XMLHttpRequest is supported * @returns True if supported, otherwise false */ +/*#__NO_SIDE_EFFECTS__*/ export function isXhrSupported(): boolean { let isSupported = false; try { @@ -269,7 +302,7 @@ export function isXhrSupported(): boolean { return isSupported; } - +/*#__NO_SIDE_EFFECTS__*/ function _getNamedValue(values: any, name: string): T[] { let items: T[] = []; if (values) { @@ -289,6 +322,7 @@ function _getNamedValue(values: any, name: string): T[] { * Helper function to fetch the named meta-tag from the page. * @param name - The name of the meta-tag to find. */ +/*#__NO_SIDE_EFFECTS__*/ export function findMetaTag(name: string): any { let tags = findMetaTags(name); if (tags.length > 0) { @@ -304,6 +338,7 @@ export function findMetaTag(name: string): any { * @param name - The name of the meta-tag to find. * @returns - An array of meta-tag values. */ +/*#__NO_SIDE_EFFECTS__*/ export function findMetaTags(name: string): string[] { let tags: string[] = []; let doc = getDocument(); @@ -321,6 +356,7 @@ export function findMetaTags(name: string): string[] { * Helper function to fetch the named server timing value from the page response (first navigation event). * @param name - The name of the server timing value to find. */ +/*#__NO_SIDE_EFFECTS__*/ export function findNamedServerTiming(name: string): any { let value: any; let serverTimings = findNamedServerTimings(name); @@ -337,6 +373,7 @@ export function findNamedServerTiming(name: string): any { * @param name - The name of the server timing value to find. * @returns - An array of server timing values. */ +/*#__NO_SIDE_EFFECTS__*/ export function findNamedServerTimings(name: string): string[] { let values: string[] = []; let perf = getPerformance(); @@ -365,6 +402,7 @@ export function dispatchEvent(target:EventTarget, evnt: Event | CustomEvent): bo } +/*#__NO_SIDE_EFFECTS__*/ export function createCustomDomEvent(eventName: string, details?: any): CustomEvent { let event: CustomEvent = null; let detail = {detail: details || null } as CustomEventInit; @@ -381,8 +419,6 @@ export function createCustomDomEvent(eventName: string, details?: any): CustomEv return event; } - - export function sendCustomEvent(evtName: string, cfg?: any, customDetails?: any): boolean { let global = getGlobal(); if (global && (global as any).CustomEvent) { @@ -401,6 +437,7 @@ export function sendCustomEvent(evtName: string, cfg?: any, customDetails?: any) * @param url - The URL string to redact * @returns The URL with user information redacted */ +/*#__NO_SIDE_EFFECTS__*/ function redactUserInfo(url: string): string { return url.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^:@]{1,200}):([^@]{1,200})@(.*)$/, "$1REDACTED:REDACTED@$4"); } @@ -410,6 +447,7 @@ function redactUserInfo(url: string): string { * @param url - The URL string to redact * @returns The URL with sensitive query parameters redacted */ +/*#__NO_SIDE_EFFECTS__*/ function redactQueryParameters(url: string, config?: IConfiguration): string { let sensitiveParams: string[]; const questionMarkIndex = strIndexOf(url, "?"); @@ -500,8 +538,9 @@ function redactQueryParameters(url: string, config?: IConfiguration): string { * @param config - Configuration object that contain redactUrls setting. * @returns The redacted URL string or the original string if no redaction was needed or possible. */ +/*#__NO_SIDE_EFFECTS__*/ export function fieldRedaction(input: string, config: IConfiguration): string { - if (!input || !isString(input) || input.indexOf(" ") !== -1) { + if (!input || !isString(input) || strIndexOf(input, " ") !== -1) { return input; } const isRedactionDisabled = config && config.redactUrls === false; diff --git a/shared/otel-core/src/utils/HelperFuncs.ts b/shared/otel-core/src/utils/HelperFuncs.ts index fa4697dc7..f28c6aa2c 100644 --- a/shared/otel-core/src/utils/HelperFuncs.ts +++ b/shared/otel-core/src/utils/HelperFuncs.ts @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { ObjAssign, ObjClass } from "@microsoft/applicationinsights-shims"; +import { ObjAssign, ObjClass, ObjProto } from "@microsoft/applicationinsights-shims"; import { - WellKnownSymbols, arrForEach, asString as asString21, getKnownSymbol, isArray, isBoolean, isError, isFunction, isNullOrUndefined, - isNumber, isObject, isPlainObject, isString, isUndefined, objCreate, objDeepFreeze, objDefine, objForEachKey, objHasOwn, - objSetPrototypeOf, safe, strIndexOf, strTrim + ICachedValue, WellKnownSymbols, arrForEach, asString as asString21, createCachedValue, getKnownSymbol, isArray, isBoolean, isError, + isFunction, isNullOrUndefined, isNumber, isObject, isPlainObject, isString, isUndefined, mathFloor, mathRound, newSymbol, objCreate, + objDeepFreeze, objDefine, objForEachKey, objGetPrototypeOf, objHasOwn, objSetPrototypeOf, safe, strIndexOf, strSplit, strTrim } from "@nevware21/ts-utils"; import { STR_EMPTY } from "../constants/InternalConstants"; import { FeatureOptInMode } from "../enums/ai/FeatureOptInEnums"; import { TransportType } from "../enums/ai/SendRequestReason"; import { IConfiguration } from "../interfaces/ai/IConfiguration"; +import { IPlugin } from "../interfaces/ai/ITelemetryPlugin"; import { IXDomainRequest } from "../interfaces/ai/IXDomainRequest"; // RESTRICT and AVOID circular dependencies you should not import other contained modules or export the contents of this file directly @@ -21,12 +22,16 @@ const rCamelCase = /-([a-z])/g; const rNormalizeInvalid = /([^\w\d_$])/g; const rLeadingNumeric = /^(\d+[\w\d_$])/; +let _ProtoNameTag: ICachedValue; + export let _getObjProto = Object[strGetPrototypeOf]; +/*#__NO_SIDE_EFFECTS__*/ export function isNotUndefined(value: T): value is T { return !isUndefined(value); } +/*#__NO_SIDE_EFFECTS__*/ export function isNotNullOrUndefined(value: T): value is T { return !isNullOrUndefined(value); } @@ -38,6 +43,7 @@ export function isNotNullOrUndefined(value: T): value is T { * This is a simplified version * @param name - The name to validate */ +/*#__NO_SIDE_EFFECTS__*/ export function normalizeJsName(name: string): string { let value = name; @@ -61,6 +67,7 @@ export function normalizeJsName(name: string): string { * @param value - The string value to check for the existence of the search value * @param search - The value search within the value */ +/*#__NO_SIDE_EFFECTS__*/ export function strContains(value: string, search: string): boolean { if (value && search) { return strIndexOf(value, search) !== -1; @@ -72,6 +79,7 @@ export function strContains(value: string, search: string): boolean { /** * Convert a date to I.S.O. format in IE8 */ +/*#__NO_SIDE_EFFECTS__*/ export function toISOString(date: Date) { return date && date.toISOString() || STR_EMPTY; } @@ -81,6 +89,7 @@ export const deepFreeze: (obj: T) => T = objDeepFreeze; /** * Returns the name of object if it's an Error. Otherwise, returns empty string. */ +/*#__NO_SIDE_EFFECTS__*/ export function getExceptionName(object: any): string { if (isError(object)) { return object.name; @@ -247,6 +256,7 @@ export function proxyFunctions(target: T, source: S | (() => S), functions * Only instance properties (hasOwnProperty) values are copied from the defaults to the new instance * @param defaults - Simple helper */ +/*#__NO_SIDE_EFFECTS__*/ export function createClassFromInterface(defaults?: T) { return class { constructor() { @@ -265,14 +275,26 @@ export function createClassFromInterface(defaults?: T) { * in the debug output and also in the DevTools watchers window when inspecting the object etc. * @param target - The object to set the toStringTag symbol on * @param nameOrFunc - The name or function to use for the toStringTag + * @returns The target object */ -export function setObjStringTag(target: any, nameOrFunc: string | (() => string)): any { - safe(objDefine, [target, getKnownSymbol(WellKnownSymbols.toStringTag), isFunction(nameOrFunc) ? { g: nameOrFunc } : { v: nameOrFunc }]); +export function setObjStringTag(target: T, nameOrFunc: string | (() => string)): T { + safe(objDefine, [target, getKnownSymbol(WellKnownSymbols.toStringTag), isFunction(nameOrFunc) ? { g: nameOrFunc, e: false } : { v: nameOrFunc, w: false, e: false }]); return target; } -export function setProtoTypeName(target: any, name: string) { +/** + * Introduce an intermediate prototype to the target object and that sets the toStringTag on that prototype, + * this avoids directly modifying the target object and also allows multiple different "types" to be + * applied to the same target object if required. + * This is done as a best effort approach and may not always succeed due to security / object model restrictions + * So if it fails then it will fallback to just defining the toStringTag on the target object, which also may fail + * resulting in no change. + * @param target - The object to set the toStringTag symbol on + * @param name - The name or function to use for the toStringTag + * @returns The target object + */ +export function setProtoTypeName(target: T, name: string): T { if (target) { let proto = _getObjProto(target); let done = false; @@ -281,6 +303,19 @@ export function setProtoTypeName(target: any, name: string) { safe(() => { // Create a new intermediate prototype that extends the current prototype let newProto = setObjStringTag(objCreate(proto), name); + if (!_ProtoNameTag) { + // Note: Using a cached value instead of a lazy value as we want to ensure that the namespace is consistent + // across multiple calls as the `getLazy()` supports runtime invalidation via `setBypassLazyCache()` which would + // result in different namespaces being returned. + _ProtoNameTag = createCachedValue(newSymbol("ai$ProtoName")); + } + + // Tag this new prototype + objDefine(newProto, _ProtoNameTag.v as any, { + v: true, + w: false, + e: false + }); objSetPrototypeOf(target, newProto); done = true; @@ -289,7 +324,35 @@ export function setProtoTypeName(target: any, name: string) { if (!done) { // Either no prototype or we failed to set it so just define the toStringTag on the target - setObjStringTag(target, name); + safe(setObjStringTag, [target, name]); + } + } + + return target; +} + +/** + * Update the introduced intermediate prototype name of the target object. + * @param target - The object to look for the prototype name and update + * @param name - The updated name to apply + * @returns The target Object + */ +export function updateProtoTypeName(target: T, name: string): T { + if (_ProtoNameTag) { + // Find the Parent Proto + while (target && target !== ObjProto && !(target as any)[_ProtoNameTag.v]) { + let protoTarget = objGetPrototypeOf(target); + if (target === protoTarget) { + // Break out of any recursive case (happens on some runtimes) where the prototype of an + // object is the same prototype + break; + } + target = protoTarget; + } + + if ((target as any)[_ProtoNameTag.v]) { + // Found it so update + safe(setObjStringTag, [target, name]); } } @@ -303,6 +366,7 @@ export function setProtoTypeName(target: any, name: string) { * This helps when iterating using for..in, objKeys() and objForEach() * @param theObject - The object to be optimized if possible */ +/*#__NO_SIDE_EFFECTS__*/ export function optimizeObject(theObject: T): T { // V8 Optimization to cause the JIT compiler to create a new optimized object for looking up the own properties // primarily for object with <= 19 properties for >= 20 the effect is reduced or non-existent @@ -398,6 +462,7 @@ export const asString = asString21; * @param sdkDefaultState - Optional default state to return if the feature is not defined * @returns True if the feature is enabled, false if the feature is disabled, or undefined if the feature is not defined and no default state is provided. */ +/*#__NO_SIDE_EFFECTS__*/ export function isFeatureEnabled(feature?: string, cfg?: T, sdkDefaultState?: boolean): boolean | undefined { let ft = cfg && cfg.featureOptIn && cfg.featureOptIn[feature]; if (feature && ft) { @@ -414,6 +479,7 @@ export function isFeatureEnabled(feat return sdkDefaultState; } +/*#__NO_SIDE_EFFECTS__*/ export function getResponseText(xhr: XMLHttpRequest | IXDomainRequest) { try { return xhr.responseText; @@ -424,6 +490,7 @@ export function getResponseText(xhr: XMLHttpRequest | IXDomainRequest) { return null; } +/*#__NO_SIDE_EFFECTS__*/ export function formatErrorMessageXdr(xdr: IXDomainRequest, message?: string): string { if (xdr) { return "XDomainRequest,Response:" + getResponseText(xdr) || STR_EMPTY; @@ -432,6 +499,7 @@ export function formatErrorMessageXdr(xdr: IXDomainRequest, message?: string): s return message; } +/*#__NO_SIDE_EFFECTS__*/ export function formatErrorMessageXhr(xhr: XMLHttpRequest, message?: string): string { if (xhr) { return "XMLHttpRequest,Status:" + xhr.status + ",Response:" + getResponseText(xhr) || xhr.response || STR_EMPTY; @@ -440,6 +508,7 @@ export function formatErrorMessageXhr(xhr: XMLHttpRequest, message?: string): st return message; } +/*#__NO_SIDE_EFFECTS__*/ export function prependTransports(theTransports: TransportType[], newTransports: TransportType | TransportType[]) { if (newTransports) { if (isNumber(newTransports)) { @@ -513,13 +582,14 @@ export function openXhr(method: string, urlString: string, withCredentials?: boo * @internal */ // tslint:disable-next-line: align +/*#__NO_SIDE_EFFECTS__*/ export function convertAllHeadersToMap(headersString: string): { [headerName: string]: string } { - let headers = {}; + let headers:any = {}; if (isString(headersString)) { let headersArray = strTrim(headersString).split(/[\r\n]+/); arrForEach(headersArray, (headerEntry) => { if (headerEntry) { - let idx = headerEntry.indexOf(": "); + let idx = strIndexOf(headerEntry, ": "); if (idx !== -1) { // The new spec has the headers returning all as lowercase -- but not all browsers do this yet let header = strTrim(headerEntry.substring(0, idx)).toLowerCase(); @@ -576,3 +646,94 @@ export function _getAllResponseHeaders(xhr: XMLHttpRequest, isOneDs?: boolean) { return theHeaders; } + +/*#__NO_SIDE_EFFECTS__*/ +export function stringToBoolOrDefault(str: any, defaultValue = false): boolean { + if (str === undefined || str === null) { + return defaultValue; + } + + return str.toString().toLowerCase() === "true"; +} + +/** + * Convert ms to c# time span format + */ +/*#__NO_SIDE_EFFECTS__*/ +export function msToTimeSpan(totalms: number | string): string { + if (isTimeSpan(totalms)) { + // Already in time span format + return totalms; + } + + if (isNaN(totalms) || totalms < 0) { + totalms = 0; + } + + totalms = mathRound(totalms); + + let ms = STR_EMPTY + totalms % 1000; + let sec = STR_EMPTY + mathFloor(totalms / 1000) % 60; + let min = STR_EMPTY + mathFloor(totalms / (1000 * 60)) % 60; + let hour = STR_EMPTY + mathFloor(totalms / (1000 * 60 * 60)) % 24; + const days = mathFloor(totalms / (1000 * 60 * 60 * 24)); + + ms = ms.length === 1 ? "00" + ms : ms.length === 2 ? "0" + ms : ms; + sec = sec.length < 2 ? "0" + sec : sec; + min = min.length < 2 ? "0" + min : min; + hour = hour.length < 2 ? "0" + hour : hour; + + return (days > 0 ? days + "." : STR_EMPTY) + hour + ":" + min + ":" + sec + "." + ms; +} + +/*#__NO_SIDE_EFFECTS__*/ +export function getExtensionByName(extensions: IPlugin[], identifier: string): IPlugin | null { + let extension: IPlugin = null; + arrForEach(extensions, (value) => { + if (value.identifier === identifier) { + extension = value; + return -1; + } + }); + + return extension; +} + +/*#__NO_SIDE_EFFECTS__*/ +export function isCrossOriginError(message: string|Event, url: string, lineNumber: number, columnNumber: number, error: Error | Event): boolean { + return !error && isString(message) && (message === "Script error." || message === "Script error"); +} + +/** + * A helper method to determine whether the provided value is in a ISO time span format (DD.HH:MM:SS.MMMMMM) + * @param value - The value to check + * @returns True if the value is in a time span format; false otherwise + */ +/*#__NO_SIDE_EFFECTS__*/ +export function isTimeSpan(value: any): value is string { + let result = false; + + if (isString(value)) { + const parts = strSplit(value, ":"); + if (parts.length === 3) { + // Looks like a candidate, now validate each part + const daysHours = strSplit(parts[0], "."); + if (daysHours.length === 2) { + result = !isNaN(parseInt(daysHours[0] || "0")) && !isNaN(parseInt(daysHours[1] || "0")); + } else { + result = !isNaN(parseInt(daysHours[0] || "0")); + } + + result = result && !isNaN(parseInt(parts[1] || "0")); + + const secondsParts = strSplit(parts[2], "."); + if (secondsParts.length === 2) { + result = result && !isNaN(parseInt(secondsParts[0] || "0")) && !isNaN(parseInt(secondsParts[1] || "0")); + } else { + result = result && !isNaN(parseInt(secondsParts[0] || "0")); + } + } + } + + return result; +} diff --git a/shared/otel-core/src/utils/Offline.ts b/shared/otel-core/src/utils/Offline.ts index a8ad79694..cdc90ce59 100644 --- a/shared/otel-core/src/utils/Offline.ts +++ b/shared/otel-core/src/utils/Offline.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { arrForEach, getDocument, getNavigator, getWindow, isNullOrUndefined, isUndefined } from "@nevware21/ts-utils"; +import { arrForEach, arrIndexOf, getDocument, getNavigator, getWindow, isNullOrUndefined, isUndefined } from "@nevware21/ts-utils"; import { IUnloadHook } from "../interfaces/ai/IUnloadHook"; import { eventOff, eventOn, mergeEvtNamespace } from "../internal/EventHelpers"; import { createUniqueNamespace } from "./DataCacheHelper"; @@ -167,7 +167,7 @@ export function createOfflineListener(parentEvtNamespace?: string | string[]): I // Define rm as an instance of IUnloadHook return { rm: () => { - let index = listenerList.indexOf(callback); + let index = arrIndexOf(listenerList, callback); if (index > -1){ return listenerList.splice(index, 1); } else { diff --git a/shared/otel-core/src/utils/RandomHelper.ts b/shared/otel-core/src/utils/RandomHelper.ts index 7a4df225e..8a0124117 100644 --- a/shared/otel-core/src/utils/RandomHelper.ts +++ b/shared/otel-core/src/utils/RandomHelper.ts @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { mathFloor, utcNow } from "@nevware21/ts-utils"; +import { mathFloor, mathRandom, utcNow } from "@nevware21/ts-utils"; import { STR_EMPTY } from "../constants/InternalConstants"; import { getCrypto, getMsCrypto, isIE } from "./EnvUtils"; const UInt32Mask = 0x100000000; const MaxUInt32 = 0xffffffff; -const SEED1 = 123456789 -const SEED2 = 987654321 +const SEED1 = 123456789; +const SEED2 = 987654321; // MWC based Random generator (for IE) let _mwcSeeded = false; @@ -31,7 +31,7 @@ function _autoSeedMwc() { // and bitwise XOR with the current milliseconds try { const now = utcNow() & 0x7fffffff; - _mwcSeed(((Math.random() * UInt32Mask) ^ now) + now); + _mwcSeed(((mathRandom() * UInt32Mask) ^ now) + now); } catch (e) { // Don't crash if something goes wrong } @@ -76,7 +76,7 @@ export function random32(signed?: boolean) { if (value === 0) { // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) - value = mathFloor((UInt32Mask * Math.random()) | 0); + value = mathFloor((UInt32Mask * mathRandom()) | 0); } if (!signed) { diff --git a/shared/otel-core/src/utils/StorageHelperFuncs.ts b/shared/otel-core/src/utils/StorageHelperFuncs.ts index 8b4ef2881..45380cba3 100644 --- a/shared/otel-core/src/utils/StorageHelperFuncs.ts +++ b/shared/otel-core/src/utils/StorageHelperFuncs.ts @@ -2,14 +2,16 @@ // Licensed under the MIT License. import { dumpObj, getGlobal, getInst as getGlobalInst, isNullOrUndefined, objForEachKey } from "@nevware21/ts-utils"; +import { STR_EMPTY } from "../constants/InternalConstants"; +import { _throwInternal } from "../diagnostics/DiagnosticLogger"; import { StorageType } from "../enums/ai/Enums"; import { _eInternalMessageId, eLoggingSeverity } from "../enums/ai/LoggingEnums"; import { IDiagnosticLogger } from "../interfaces/ai/IDiagnosticLogger"; -import { getExceptionName } from "./HelperFuncsCore"; +import { getExceptionName } from "./HelperFuncs"; let _canUseLocalStorage: boolean = undefined; let _canUseSessionStorage: boolean = undefined; -let _storagePrefix: string = ""; +let _storagePrefix: string = STR_EMPTY; /** * Gets the localStorage object if available @@ -71,7 +73,7 @@ export function utlDisableStorage() { } export function utlSetStoragePrefix(storagePrefix: string) { - _storagePrefix = storagePrefix || ""; + _storagePrefix = storagePrefix || STR_EMPTY; } /** @@ -103,7 +105,7 @@ export function utlGetLocalStorage(logger: IDiagnosticLogger, name: string): str } catch (e) { _canUseLocalStorage = false; - logger.throwInternal( + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.BrowserCannotReadLocalStorage, "Browser failed read of local storage. " + getExceptionName(e), @@ -122,7 +124,7 @@ export function utlSetLocalStorage(logger: IDiagnosticLogger, name: string, data } catch (e) { _canUseLocalStorage = false; - logger.throwInternal( + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.BrowserCannotWriteLocalStorage, "Browser failed write to local storage. " + getExceptionName(e), @@ -141,7 +143,7 @@ export function utlRemoveStorage(logger: IDiagnosticLogger, name: string): boole } catch (e) { _canUseLocalStorage = false; - logger.throwInternal( + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.BrowserFailedRemovalFromLocalStorage, "Browser failed removal of local storage item. " + getExceptionName(e), @@ -179,7 +181,7 @@ export function utlGetSessionStorage(logger: IDiagnosticLogger, name: string): s } catch (e) { _canUseSessionStorage = false; - logger.throwInternal( + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.BrowserCannotReadSessionStorage, "Browser failed read of session storage. " + getExceptionName(e), @@ -198,7 +200,7 @@ export function utlSetSessionStorage(logger: IDiagnosticLogger, name: string, da } catch (e) { _canUseSessionStorage = false; - logger.throwInternal( + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.BrowserCannotWriteSessionStorage, "Browser failed write to session storage. " + getExceptionName(e), @@ -217,7 +219,7 @@ export function utlRemoveSessionStorage(logger: IDiagnosticLogger, name: string) } catch (e) { _canUseSessionStorage = false; - logger.throwInternal( + _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.BrowserFailedRemovalFromSessionStorage, "Browser failed removal of session storage item. " + getExceptionName(e), diff --git a/shared/otel-core/src/utils/TraceParent.ts b/shared/otel-core/src/utils/TraceParent.ts index e636b257b..3b0b27ec4 100644 --- a/shared/otel-core/src/utils/TraceParent.ts +++ b/shared/otel-core/src/utils/TraceParent.ts @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { arrForEach, isArray, isNullOrUndefined, isString, strLeft, strTrim } from "@nevware21/ts-utils"; +import { arrForEach, isArray, isNullOrUndefined, isString, strIndexOf, strLeft, strTrim } from "@nevware21/ts-utils"; import { STR_EMPTY } from "../constants/InternalConstants"; import { eW3CTraceFlags } from "../enums/W3CTraceFlags"; -import { IDistributedTraceContext } from "../interfaces/ai/IDistributedTraceContext"; import { ITraceParent } from "../interfaces/ai/ITraceParent"; -import { ITelemetryTrace } from "../interfaces/ai/context/ITelemetryTrace"; import { generateW3CId } from "./CoreUtils"; import { findMetaTag, findNamedServerTiming } from "./EnvUtils"; +// using {0,16} for leading and trailing whitespace just to constrain the possible runtime of a random string const TRACE_PARENT_REGEX = /^([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})(-[^\s]{1,64})?$/i; const DEFAULT_VERSION = "00"; const INVALID_VERSION = "ff"; @@ -17,7 +16,6 @@ export const INVALID_TRACE_ID = "00000000000000000000000000000000"; export const INVALID_SPAN_ID = "0000000000000000"; const SAMPLED_FLAG = 0x01; - function _isValid(value: string, len: number, invalidValue?: string): boolean { if (value && value.length === len && value !== invalidValue) { return !!value.match(/^[\da-f]*$/i); @@ -53,7 +51,9 @@ function _formatFlags(value: number): string { * @param spanId - The parent/span id to use, a new random value will be generated if it is invalid. * @param flags - The traceFlags to use, defaults to zero (0) if not supplied or invalid * @param version - The version to used, defaults to version "01" if not supplied or invalid. + * @returns */ +/*#__NO_SIDE_EFFECTS__*/ export function createTraceParent(traceId?: string, spanId?: string, flags?: number, version?: string): ITraceParent { return { version: _isValid(version, 2, INVALID_VERSION) ? version : DEFAULT_VERSION, @@ -68,31 +68,37 @@ export function createTraceParent(traceId?: string, spanId?: string, flags?: num * * @param value - The value to be parsed * @param selectIdx - If the found value is comma separated which is the preferred entry to select, defaults to the first + * @returns */ +/*#__NO_SIDE_EFFECTS__*/ export function parseTraceParent(value: string, selectIdx?: number): ITraceParent { if (!value) { + // Don't pass a null/undefined or empty string return null; } if (isArray(value)) { + // The value may have been encoded on the page into an array so handle this automatically value = value[0] || STR_EMPTY; } if (!value || !isString(value) || value.length > 8192) { + // limit potential processing based on total length return null; } - if (value.indexOf(",") !== -1) { + if (strIndexOf(value, ",") !== -1) { let values = value.split(","); value = values[selectIdx > 0 && values.length > selectIdx ? selectIdx : 0]; } + // See https://www.w3.org/TR/trace-context/#versioning-of-traceparent TRACE_PARENT_REGEX.lastIndex = 0; const match = TRACE_PARENT_REGEX.exec(strTrim(value)); - if (!match || - match[1] === INVALID_VERSION || - match[2] === INVALID_TRACE_ID || - match[3] === INVALID_SPAN_ID) { + if (!match || // No match + match[1] === INVALID_VERSION || // version ff is forbidden + match[2] === INVALID_TRACE_ID || // All zeros is considered to be invalid + match[3] === INVALID_SPAN_ID) { // All zeros is considered to be invalid return null; } @@ -105,28 +111,43 @@ export function parseTraceParent(value: string, selectIdx?: number): ITraceParen } /** - * Is the provided W3c Trace Id a valid string representation. + * Is the provided W3c Trace Id a valid string representation, it must be a 32-character string + * of lowercase hexadecimal characters for example, 4bf92f3577b34da6a3ce929d0e0e4736. + * If all characters as zero (00000000000000000000000000000000) it will be considered an invalid value. + * @param value - The W3c trace Id to be validated + * @returns true if valid otherwise false */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidTraceId(value: string): boolean { return _isValid(value, 32, INVALID_TRACE_ID); } /** - * Is the provided W3c span id (aka. parent id) a valid string representation. + * Is the provided W3c span id (aka. parent id) a valid string representation, it must be a 16-character + * string of lowercase hexadecimal characters, for example, 00f067aa0ba902b7. + * If all characters are zero (0000000000000000) this is considered an invalid value. + * @param value - The W3c span id to be validated + * @returns true if valid otherwise false */ +/*#__NO_SIDE_EFFECTS__*/ export function isValidSpanId(value: string): boolean { return _isValid(value, 16, INVALID_SPAN_ID); } /** - * Validates that the provided ITraceParent instance conforms to the currently supported specifications. + * Validates that the provided ITraceParent instance conforms to the currently supported specifications + * @param value - The parsed traceParent value + * @returns */ -export function isValidTraceParent(value: ITraceParent): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isValidTraceParent(value: ITraceParent) { if (!value || - !_isValid(value.version, 2, INVALID_VERSION) || - !_isValid(value.traceId, 32, INVALID_TRACE_ID) || - !_isValid(value.spanId, 16, INVALID_SPAN_ID) || - !_isValid(_formatFlags(value.traceFlags), 2)) { + !_isValid(value.version, 2, INVALID_VERSION) || + !_isValid(value.traceId, 32, INVALID_TRACE_ID) || + !_isValid(value.spanId, 16, INVALID_SPAN_ID) || + !_isValid(_formatFlags(value.traceFlags), 2)) { + + // Each known field must contain a valid value return false; } @@ -135,8 +156,11 @@ export function isValidTraceParent(value: ITraceParent): boolean { /** * Is the parsed traceParent indicating that the trace is currently sampled. + * @param value - The parsed traceParent value + * @returns */ -export function isSampledFlag(value: ITraceParent): boolean { +/*#__NO_SIDE_EFFECTS__*/ +export function isSampledFlag(value: ITraceParent) { if (isValidTraceParent(value)) { return (value.traceFlags & SAMPLED_FLAG) === SAMPLED_FLAG; } @@ -145,10 +169,18 @@ export function isSampledFlag(value: ITraceParent): boolean { } /** - * Format the ITraceParent value as a string using the supported and known version formats. + * Format the ITraceParent value as a string using the supported and know version formats. + * So even if the passed traceParent is a later version the string value returned from this + * function will convert it to only the known version formats. + * This currently only supports version "00" and invalid "ff" + * @param value - The parsed traceParent value + * @returns */ -export function formatTraceParent(value: ITraceParent): string { +/*#__NO_SIDE_EFFECTS__*/ +export function formatTraceParent(value: ITraceParent) { if (value) { + // Special Note: This only supports formatting as version 00, future versions should encode any known supported version + // So parsing a future version will populate the correct version value but reformatting will reduce it to version 00. let flags = _formatFlags(value.traceFlags); if (!_isValid(flags, 2)) { flags = "01"; @@ -156,9 +188,11 @@ export function formatTraceParent(value: ITraceParent): string { let version = value.version || DEFAULT_VERSION; if (version !== "00" && version !== "ff") { + // Reduce version to "00" version = DEFAULT_VERSION; } + // Format as version 00 return `${version.toLowerCase()}-${_formatValue(value.traceId, 32, INVALID_TRACE_ID).toLowerCase()}-${_formatValue(value.spanId, 16, INVALID_SPAN_ID).toLowerCase()}-${flags.toLowerCase()}`; } @@ -167,129 +201,19 @@ export function formatTraceParent(value: ITraceParent): string { /** * Helper function to fetch the passed traceparent from the page, looking for it as a meta-tag or a Server-Timing header. + * @param selectIdx - If the found value is comma separated which is the preferred entry to select, defaults to the first + * @returns */ export function findW3cTraceParent(selectIdx?: number): ITraceParent { const name = "traceparent"; let traceParent: ITraceParent = parseTraceParent(findMetaTag(name), selectIdx); if (!traceParent) { - traceParent = parseTraceParent(findNamedServerTiming(name), selectIdx); + traceParent = parseTraceParent(findNamedServerTiming(name), selectIdx) } return traceParent; } -export function createDistributedTraceContextFromTrace(telemetryTrace?: ITelemetryTrace, parentCtx?: IDistributedTraceContext): IDistributedTraceContext { - let trace = telemetryTrace || {} as ITelemetryTrace; - - function _getName(): string { - return trace.name; - } - - function _setName(newValue: string): void { - if (parentCtx) { - parentCtx.setName(newValue); - } - - trace.name = newValue; - } - - function _getTraceId(): string { - return trace.traceID; - } - - function _setTraceId(newValue: string): void { - if (parentCtx) { - parentCtx.setTraceId(newValue); - } - - if (isValidTraceId(newValue)) { - trace.traceID = newValue; - } - } - - function _getSpanId(): string { - return trace.parentID; - } - - function _setSpanId(newValue: string): void { - if (parentCtx) { - parentCtx.setSpanId(newValue); - } - - if (isValidSpanId(newValue)) { - trace.parentID = newValue; - } - } - - function _getTraceFlags(): number | undefined { - return trace.traceFlags; - } - - function _setTraceFlags(newValue?: number): void { - if (parentCtx) { - parentCtx.setTraceFlags(newValue); - } - - trace.traceFlags = newValue; - } - - let ctx = { - getName: _getName, - setName: _setName, - getTraceId: _getTraceId, - setTraceId: _setTraceId, - getSpanId: _getSpanId, - setSpanId: _setSpanId, - getTraceFlags: _getTraceFlags, - setTraceFlags: _setTraceFlags, - isRemote: parentCtx ? parentCtx.isRemote : false, - traceState: parentCtx ? parentCtx.traceState : undefined, - parentCtx: parentCtx || null - } as IDistributedTraceContext; - - Object.defineProperty(ctx, "pageName", { - configurable: true, - enumerable: true, - get: _getName, - set: (newValue: string) => { - trace.name = newValue; - } - }); - - Object.defineProperty(ctx, "traceId", { - configurable: true, - enumerable: true, - get: _getTraceId, - set: (newValue: string) => { - if (isValidTraceId(newValue)) { - trace.traceID = newValue; - } - } - }); - - Object.defineProperty(ctx, "spanId", { - configurable: true, - enumerable: true, - get: _getSpanId, - set: (newValue: string) => { - if (isValidSpanId(newValue)) { - trace.parentID = newValue; - } - } - }); - - Object.defineProperty(ctx, "traceFlags", { - configurable: true, - enumerable: true, - get: _getTraceFlags, - set: (newValue?: number) => { - trace.traceFlags = newValue; - } - }); - - return ctx; -} - export interface scriptsInfo { url: string; crossOrigin?: string; @@ -300,6 +224,8 @@ export interface scriptsInfo { /** * Find all script tags in the provided document and return the information about them. + * @param doc - The document to search for script tags + * @returns */ export function findAllScripts(doc: any): scriptsInfo[] { let scripts = doc.getElementsByTagName("script"); diff --git a/shared/otel-core/src/utils/UrlHelperFuncs.ts b/shared/otel-core/src/utils/UrlHelperFuncs.ts index 3a4bcf8da..e76ef62e1 100644 --- a/shared/otel-core/src/utils/UrlHelperFuncs.ts +++ b/shared/otel-core/src/utils/UrlHelperFuncs.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { getDocument, isString } from "@nevware21/ts-utils"; +import { STR_EMPTY } from "../constants/InternalConstants"; let _document: any = getDocument() || {}; @@ -65,11 +66,11 @@ export function urlGetCompleteUrl(method: string, absoluteUrl: string) { // Fallback method to grab host from url if document.createElement method is not available export function urlParseHost(url: string, inclPort?: boolean) { - let fullHost = urlParseFullHost(url, inclPort) || ""; + let fullHost = urlParseFullHost(url, inclPort) || STR_EMPTY; if (fullHost) { const match = fullHost.match(/(www\d{0,5}\.)?([^\/:]{1,256})(:\d{1,20})?/i); if (match != null && match.length > 3 && isString(match[2]) && match[2].length > 0) { - return match[2] + (match[3] || ""); + return match[2] + (match[3] || STR_EMPTY); } } @@ -81,15 +82,15 @@ export function urlParseFullHost(url: string, inclPort?: boolean) { if (url) { const match = url.match(/(\w{1,150}):\/\/([^\/:]{1,256})(:\d{1,20})?/i); if (match != null && match.length > 2 && isString(match[2]) && match[2].length > 0) { - result = match[2] || ""; + result = match[2] || STR_EMPTY; if (inclPort && match.length > 2) { - const protocol = (match[1] || "").toLowerCase(); - let port = match[3] || ""; + const protocol = (match[1] || STR_EMPTY).toLowerCase(); + let port = match[3] || STR_EMPTY; // IE includes the standard port so pass it off if it's the same as the protocol if (protocol === "http" && port === ":80") { - port = ""; + port = STR_EMPTY; } else if (protocol === "https" && port === ":443") { - port = ""; + port = STR_EMPTY; } result += port; diff --git a/shared/otel-core/src/utils/Util.ts b/shared/otel-core/src/utils/Util.ts index a31fd8d04..02fa5a641 100644 --- a/shared/otel-core/src/utils/Util.ts +++ b/shared/otel-core/src/utils/Util.ts @@ -3,8 +3,12 @@ import { arrForEach, arrIndexOf, getPerformance, isNullOrUndefined, strIndexOf, utcNow as dateNow } from "@nevware21/ts-utils"; import { DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH } from "../constants/Constants"; +import { STR_EMPTY } from "../constants/InternalConstants"; +import { createDistributedTraceContext } from "../core/TelemetryHelpers"; import { ICorrelationConfig } from "../interfaces/ai/ICorrelationConfig"; import { IDiagnosticLogger } from "../interfaces/ai/IDiagnosticLogger"; +import { IDistributedTraceContext } from "../interfaces/ai/IDistributedTraceContext"; +import { ITelemetryTrace } from "../interfaces/ai/context/ITelemetryTrace"; import { RequestHeaders, eRequestHeaders } from "../telemetry/RequestResponseHeaders"; import { dataSanitizeString } from "../telemetry/ai/Common/DataSanitizer"; import { urlParseFullHost, urlParseUrl } from "./UrlHelperFuncs"; @@ -170,3 +174,23 @@ export function dateTimeUtilsDuration(start: number, end: number): number { return result; } + +/** + * Creates a IDistributedTraceContext from an optional telemetryTrace + * @param telemetryTrace - The telemetryTrace instance that is being wrapped + * @param parentCtx - An optional parent distributed trace instance, almost always undefined as this scenario is only used in the case of multiple property handlers. + * @returns A new IDistributedTraceContext instance that is backed by the telemetryTrace or temporary object + * @deprecated This function is deprecated and will be removed in a future version. Use the createDistributedTraceContext function instead and set the necessary properties + * on the context object directly. + */ +export function createDistributedTraceContextFromTrace(telemetryTrace?: ITelemetryTrace, parentCtx?: IDistributedTraceContext): IDistributedTraceContext { + let traceCtx: IDistributedTraceContext = createDistributedTraceContext(parentCtx); + if (telemetryTrace) { + traceCtx.pageName = telemetryTrace.name || traceCtx.pageName || STR_EMPTY; // The name of the page + traceCtx.traceId = telemetryTrace.traceID || traceCtx.traceId || STR_EMPTY; // 16 byte hex string + traceCtx.spanId = telemetryTrace.parentID || traceCtx.spanId || STR_EMPTY; // 8 byte hex string + traceCtx.traceFlags = (!isNullOrUndefined(telemetryTrace.traceFlags) ? telemetryTrace.traceFlags : traceCtx.traceFlags) || 0; // 1 byte hex string + } + + return traceCtx +} diff --git a/shared/otel-noop/Tests/Unit/src/index.tests.ts b/shared/otel-noop/Tests/Unit/src/index.tests.ts index 686f1a70c..546538ff9 100644 --- a/shared/otel-noop/Tests/Unit/src/index.tests.ts +++ b/shared/otel-noop/Tests/Unit/src/index.tests.ts @@ -1,7 +1,7 @@ -import { OTelLoggerTests } from "./sdk/OTelLogger.Tests"; -import { OTelLoggerProviderTests } from "./sdk/OTelLoggerProvider.Tests"; +import { NoopOTelLoggerTests } from "./sdk/NoopOTelLogger.Tests"; +// import { NoopOTelLoggerProviderTests } from "./sdk/NoopOTelLoggerProvider.Tests"; export function runTests() { - new OTelLoggerTests().registerTests(); - new OTelLoggerProviderTests().registerTests(); + new NoopOTelLoggerTests().registerTests(); + // new NoopOTelLoggerProviderTests().registerTests(); } diff --git a/shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLogger.Tests.ts b/shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLogger.Tests.ts new file mode 100644 index 000000000..c7ffd1fd3 --- /dev/null +++ b/shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLogger.Tests.ts @@ -0,0 +1,143 @@ +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +import { + createLoggerProvider, + IOTelLogger, + IOTelInstrumentationScope, + isFunction +} from "@microsoft/otel-core-js"; +import { createNoopLogRecordProcessor } from "../../../../src/api/logger/noopLogRecordProcessor"; + +// ************************************* +// Note: NOOP implementations should only rely on otel-core-js for the interfaces and NONE of the implementation code. +// As this project MUST function in es5 mode while otel-core-js does not. +// ************************************* + + +// W3C TraceFlags - Sampled = 1 +const eW3CTraceFlags_Sampled = 1; + +type LoggerWithScope = IOTelLogger & { instrumentationScope: IOTelInstrumentationScope }; + +export class NoopOTelLoggerTests extends AITestClass { + public testInitialize() { + super.testInitialize(); + } + + public testCleanup() { + super.testCleanup(); + } + + // private setup() { + // const logProcessor = createNoopLogRecordProcessor(); + // const provider = createLoggerProvider({ + // processors: [logProcessor] + // }); + // const logger = provider.getLogger("test name", "test version", { + // schemaUrl: "test schema url" + // }) as LoggerWithScope; + // return { logger, logProcessor, provider }; + // } + + public registerTests() { + this.testCase({ + name: "Logger: factory returns logger instance", + test: () => { + const logProcessor = createNoopLogRecordProcessor(); + + Assert.ok(logProcessor, "Should return an instance of log processor"); + Assert.ok(isFunction(logProcessor.onEmit), "Log processor should have onEmit function"); + Assert.ok(isFunction(logProcessor.forceFlush), "Log processor should have forceFlush function"); + Assert.ok(isFunction(logProcessor.shutdown), "Log processor should have shutdown function"); + } + }); + + // this.testCase({ + // name: "Logger: factory returns logger instance", + // test: () => { + // const logProcessor = createNoopLogRecordProcessor(); + // const provider = createLoggerProvider({ processors: [logProcessor] }); + // const sharedState = provider._sharedState; + // const scope: IOTelInstrumentationScope = { + // name: "test name", + // version: "test version", + // schemaUrl: "test schema url" + // }; + // 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"); + // } + // }); + + // this.testCase({ + // name: "Logger: should emit a logRecord instance", + // test: () => { + // const { logger, logProcessor } = this.setup(); + // const callSpy = this.sandbox.spy(logProcessor, "onEmit"); + // logger.emit({ + // body: "test log body" + // }); + // Assert.ok(callSpy.called, "onEmit should be called"); + // callSpy.restore(); + // } + // }); + + // this.testCase({ + // name: "Logger: should make log record instance readonly after emit it", + // test: () => { + // const { logger, logProcessor } = this.setup(); + // let readonlyCalled = false; + // this.sandbox.stub(logProcessor, "onEmit").callsFake((logRecord) => { + // const originalMakeReadonly = logRecord._makeReadonly; + // logRecord._makeReadonly = () => { + // readonlyCalled = true; + // originalMakeReadonly.call(logRecord); + // }; + // }); + // logger.emit({ + // body: "test log body" + // }); + // Assert.ok(readonlyCalled, "_makeReadonly should be called"); + // } + // }); + + // this.testCase({ + // name: "Logger: should emit with current Context", + // test: () => { + // const { logger, logProcessor } = this.setup(); + // const callSpy = this.sandbox.spy(logProcessor, "onEmit"); + // logger.emit({ + // body: "test log body" + // }); + // const contextManager = createContextManager(); + // const currentContext = contextManager.active(); + // Assert.ok(callSpy.called, "onEmit should be called"); + // Assert.equal(callSpy.args[0][1], currentContext, "Should emit with current context"); + // callSpy.restore(); + // } + // }); + + // this.testCase({ + // name: "Logger: should emit with Context specified in LogRecord", + // test: () => { + // const { logger, logProcessor } = this.setup(); + // const spanContext: IOTelSpanContext = { + // traceId: "d4cda95b652f4a1592b449d5929fda1b", + // spanId: "6e0c63257de34c92", + // traceFlags: eW3CTraceFlags_Sampled + // }; + // const ROOT_CONTEXT = createContext(null as unknown as IOTelApi); + // const activeContext = setContextSpanContext(ROOT_CONTEXT, spanContext); + // const logRecordData: IOTelLogRecord = { + // context: activeContext + // }; + + // const callSpy = this.sandbox.spy(logProcessor, "onEmit"); + // logger.emit(logRecordData); + // Assert.ok(callSpy.called, "onEmit should be called"); + // Assert.equal(callSpy.args[0][1], activeContext, "Should emit with specified context"); + // callSpy.restore(); + // } + // }); + } +} \ No newline at end of file diff --git a/shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLoggerProvider.Tests.ts b/shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLoggerProvider.Tests.ts new file mode 100644 index 000000000..39787fab7 --- /dev/null +++ b/shared/otel-noop/Tests/Unit/src/sdk/NoopOTelLoggerProvider.Tests.ts @@ -0,0 +1,404 @@ +// import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +// import { createPromise, IPromise } from "@nevware21/ts-async"; + +// import { createNoopLogger } from "../../../../src/api/noop/noopLogger"; +// import { createNoopLogRecordProcessor } from "../../../../src/api/noop/noopLogRecordProcessor"; +// import { +// IOTelAttributes, +// IOTelLogRecord, +// IOTelLoggerProviderSharedState, +// DEFAULT_LOGGER_NAME, +// IOTelLogger, +// IOTelInstrumentationScope, +// loadDefaultConfig, +// IOTelResource, +// OTelRawResourceAttribute +// } from "@microsoft/otel-core-js"; + + +//************************************* +// Note: NOOP implementations should only rely on otel-core-js for the interfaces and NONE of the implementation code. +// As this project MUST function in es5 mode while otel-core-js does not. +//************************************* + +// type LoggerProviderInstance = ReturnType; +// type MultiLogRecordProcessorInstance = ReturnType; +// type LoggerWithScope = IOTelLogger & { instrumentationScope: IOTelInstrumentationScope }; + +// export class OTelLoggerProviderTests extends AITestClass { +// public testInitialize() { +// super.testInitialize(); +// // No global OpenTelemetry APIs are mutated by LoggerProvider, so no global stubs required here. +// } + +// public testCleanup() { +// super.testCleanup(); +// } + +// public registerTests() { +// this.testCase({ +// name: "LoggerProvider: constructor without options should construct an instance", +// test: () => { +// const provider = createLoggerProvider(); +// Assert.equal(typeof provider.getLogger, "function", "Should create a LoggerProvider instance"); +// const sharedState = provider._sharedState; +// Assert.ok(sharedState.loggers instanceof Map, "Should expose shared state instance"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: constructor without options should use noop processor by default", +// test: (): IPromise => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.registeredLogRecordProcessors.length, 0, "Expected no processors to be registered by default"); +// const flushResult = sharedState.activeProcessor.forceFlush(); +// return flushResult.then(() => undefined); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: constructor should register provided processors", +// test: () => { +// const logRecordProcessor = createNoopLogRecordProcessor(); +// const provider = createLoggerProvider({ +// processors: [logRecordProcessor] +// }); +// const sharedState = this._getSharedState(provider); +// const activeProcessor = sharedState.activeProcessor as MultiLogRecordProcessorInstance; +// Assert.equal(activeProcessor.processors.length, 1, "Should register one processor"); +// Assert.equal(activeProcessor.processors[0], logRecordProcessor, "Should use the provided processor instance"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: constructor should use default resource when not provided", +// test: () => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// const resource = sharedState.resource; +// Assert.ok(!!resource, "Should have a resource available"); +// Assert.deepEqual(resource.attributes || {}, {} as IOTelAttributes, "Should default resource attributes to empty object"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: constructor should honor provided resource", +// test: () => { +// const passedInResource = this._createTestResource({ foo: "bar" }); +// const provider = createLoggerProvider({ +// resource: passedInResource +// }); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.resource, passedInResource, "Should use the provided resource instance"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: constructor should use default forceFlushTimeoutMillis when omitted", +// test: () => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.forceFlushTimeoutMillis, loadDefaultConfig().forceFlushTimeoutMillis, "Should use default forceFlush timeout"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: logRecordLimits should default values when not provided", +// test: () => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// Assert.deepEqual(sharedState.logRecordLimits, { +// attributeCountLimit: 128, +// attributeValueLengthLimit: Infinity +// }, "Should use default logRecord limits"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: logRecordLimits should respect provided attributeCountLimit", +// test: () => { +// const provider = createLoggerProvider({ +// logRecordLimits: { +// attributeCountLimit: 100 +// } +// }); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.logRecordLimits.attributeCountLimit, 100, "Should use provided attributeCountLimit"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: logRecordLimits should respect provided attributeValueLengthLimit", +// test: () => { +// const provider = createLoggerProvider({ +// logRecordLimits: { +// attributeValueLengthLimit: 10 +// } +// }); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.logRecordLimits.attributeValueLengthLimit, 10, "Should use provided attributeValueLengthLimit"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: logRecordLimits should allow negative attributeValueLengthLimit", +// test: () => { +// const provider = createLoggerProvider({ +// logRecordLimits: { +// attributeValueLengthLimit: -10 +// } +// }); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.logRecordLimits.attributeValueLengthLimit, -10, "Should preserve provided negative attributeValueLengthLimit"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: logRecordLimits should use default attributeValueLengthLimit when omitted", +// test: () => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.logRecordLimits.attributeValueLengthLimit, Infinity, "Should default attributeValueLengthLimit to Infinity"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: logRecordLimits should use default attributeCountLimit when omitted", +// test: () => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.logRecordLimits.attributeCountLimit, 128, "Should default attributeCountLimit to 128"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: getLogger should default name when invalid", +// test: () => { +// const provider = createLoggerProvider(); +// const logger = provider.getLogger("") as LoggerWithScope; +// Assert.equal(logger.instrumentationScope.name, DEFAULT_LOGGER_NAME, "Should use default logger name when name is invalid"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: getLogger should create new logger when name not seen", +// test: () => { +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); +// Assert.equal(sharedState.loggers.size, 0, "Should start with no loggers"); +// provider.getLogger("test name"); +// Assert.equal(sharedState.loggers.size, 1, "Should register logger for new name"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: getLogger should create unique loggers per scope", +// test: () => { +// const testName = "test name"; +// const testVersion = "test version"; +// const testSchemaUrl = "test schema url"; +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); + +// Assert.equal(sharedState.loggers.size, 0, "Should start with no loggers"); +// provider.getLogger(testName); +// Assert.equal(sharedState.loggers.size, 1, "Should add logger for name only"); +// provider.getLogger(testName, testVersion); +// Assert.equal(sharedState.loggers.size, 2, "Should add logger for name and version"); +// provider.getLogger(testName, testVersion, { schemaUrl: testSchemaUrl }); +// Assert.equal(sharedState.loggers.size, 3, "Should add logger for name, version, and schemaUrl"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: getLogger should reuse logger when scope matches", +// test: () => { +// const testName = "test name"; +// const testVersion = "test version"; +// const testSchemaUrl = "test schema url"; +// const provider = createLoggerProvider(); +// const sharedState = this._getSharedState(provider); + +// Assert.equal(sharedState.loggers.size, 0, "Should start with no loggers"); +// provider.getLogger(testName); +// Assert.equal(sharedState.loggers.size, 1, "Should add first logger"); +// const logger1 = provider.getLogger(testName, testVersion, { schemaUrl: testSchemaUrl }); +// Assert.equal(sharedState.loggers.size, 2, "Should add scoped logger"); +// const logger2 = provider.getLogger(testName, testVersion, { schemaUrl: testSchemaUrl }); +// Assert.equal(sharedState.loggers.size, 2, "Should not add duplicate scoped logger"); +// const scopedLogger = logger2 as LoggerWithScope; +// Assert.equal(scopedLogger.instrumentationScope.name, testName, "Should expose instrumentation scope name"); +// Assert.equal(scopedLogger.instrumentationScope.version, testVersion, "Should expose instrumentation scope version"); +// Assert.equal(scopedLogger.instrumentationScope.schemaUrl, testSchemaUrl, "Should expose instrumentation scope schemaUrl"); +// Assert.equal(logger1, logger2, "Should reuse existing scoped logger"); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: forceFlush should invoke all registered processors", +// test: (): IPromise => { +// const processor1 = createNoopLogRecordProcessor(); +// const processor2 = createNoopLogRecordProcessor(); +// const forceFlushStub1 = this.sandbox.stub(processor1, "forceFlush").resolves(); +// const forceFlushStub2 = this.sandbox.stub(processor2, "forceFlush").resolves(); +// const provider = createLoggerProvider({ processors: [processor1, processor2] }); + +// return createPromise((resolve, reject) => { +// provider.forceFlush().then(() => { +// try { +// Assert.equal(forceFlushStub1.callCount, 1, "Should call forceFlush on first processor once"); +// Assert.equal(forceFlushStub2.callCount, 1, "Should call forceFlush on second processor once"); +// resolve(); +// } catch (e) { +// reject(e); +// } +// }).catch(reject); +// }); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: forceFlush should propagate processor errors", +// test: (): IPromise => { +// const processor1 = createNoopLogRecordProcessor(); +// const processor2 = createNoopLogRecordProcessor(); +// const forceFlushStub1 = this.sandbox.stub(processor1, "forceFlush").rejects("Error"); +// const forceFlushStub2 = this.sandbox.stub(processor2, "forceFlush").rejects("Error"); +// const provider = createLoggerProvider({ processors: [processor1, processor2] }); + +// return createPromise((resolve, reject) => { +// provider.forceFlush().then(() => { +// reject(new Error("Successful forceFlush not expected")); +// }).catch(() => { +// try { +// Assert.equal(forceFlushStub1.callCount, 1, "Should attempt to forceFlush first processor even when errors occur"); +// Assert.equal(forceFlushStub2.callCount, 1, "Should attempt to forceFlush second processor even when errors occur"); +// resolve(); +// } catch (e) { +// reject(e); +// } +// }); +// }); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: shutdown should invoke processor shutdown", +// test: (): IPromise => { +// const processor = createNoopLogRecordProcessor(); +// const shutdownStub = this.sandbox.stub(processor, "shutdown").resolves(); +// const provider = createLoggerProvider({ processors: [processor] }); + +// return createPromise((resolve, reject) => { +// provider.shutdown().then(() => { +// try { +// Assert.equal(shutdownStub.callCount, 1, "Should call shutdown on processor once"); +// resolve(); +// } catch (e) { +// reject(e); +// } +// }).catch(reject); +// }); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: shutdown should return noop logger for new requests", +// test: (): IPromise => { +// const provider = createLoggerProvider(); +// return createPromise((resolve, reject) => { +// provider.shutdown().then(() => { +// 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(logger.emit.length, expectedNoopLogger.emit.length, "Noop emit signature should match expected noop logger"); +// let threw = false; +// try { +// logger.emit({} as IOTelLogRecord); +// } catch (e) { +// threw = true; +// } +// Assert.ok(!threw, "Noop logger emit should not throw after shutdown"); +// resolve(); +// } catch (e) { +// reject(e); +// } +// }).catch(reject); +// }); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: forceFlush after shutdown should not call processors", +// test: (): IPromise => { +// const logRecordProcessor = createNoopLogRecordProcessor(); +// const provider = createLoggerProvider({ processors: [logRecordProcessor] }); +// const forceFlushStub = this.sandbox.stub(logRecordProcessor, "forceFlush").resolves(); +// const warnStub = this.sandbox.stub(console, "warn"); + +// return createPromise((resolve, reject) => { +// provider.shutdown().then(() => { +// provider.forceFlush().then(() => { +// try { +// Assert.equal(forceFlushStub.callCount, 0, "forceFlush should not be called after shutdown"); +// Assert.equal(warnStub.callCount, 1, "Should emit a warning when forceFlush is called post-shutdown"); +// resolve(); +// } catch (e) { +// reject(e); +// } +// }).catch(reject); +// }).catch(reject); +// }); +// } +// }); + +// this.testCase({ +// name: "LoggerProvider: second shutdown should not re-run processor shutdown", +// test: (): IPromise => { +// const logRecordProcessor = createNoopLogRecordProcessor(); +// const provider = createLoggerProvider({ processors: [logRecordProcessor] }); +// const shutdownStub = this.sandbox.stub(logRecordProcessor, "shutdown").resolves(); +// const warnStub = this.sandbox.stub(console, "warn"); + +// return createPromise((resolve, reject) => { +// provider.shutdown().then(() => { +// provider.shutdown().then(() => { +// try { +// Assert.equal(shutdownStub.callCount, 1, "Processor shutdown should only be called once"); +// Assert.equal(warnStub.callCount, 1, "Should warn on repeated shutdown calls"); +// resolve(); +// } catch (e) { +// reject(e); +// } +// }).catch(reject); +// }).catch(reject); +// }); +// } +// }); +// } + +// private _getSharedState(provider: LoggerProviderInstance): IOTelLoggerProviderSharedState { +// return provider._sharedState; +// } + +// private _createTestResource(attributes: IOTelAttributes = {} as IOTelAttributes): IOTelResource { +// const resourceAttributes: IOTelAttributes = {} as IOTelAttributes; +// const rawAttributes: OTelRawResourceAttribute[] = []; +// for (const key in attributes) { +// if (Object.prototype.hasOwnProperty.call(attributes, key)) { +// resourceAttributes[key] = attributes[key]; +// rawAttributes.push([key, attributes[key]]); +// } +// } + +// const resource: IOTelResource = { +// attributes: resourceAttributes, +// merge: () => resource, +// getRawAttributes: () => rawAttributes +// }; + +// return resource; +// } +// } \ No newline at end of file diff --git a/shared/otel-noop/Tests/Unit/src/sdk/OTelLogger.Tests.ts b/shared/otel-noop/Tests/Unit/src/sdk/OTelLogger.Tests.ts deleted file mode 100644 index ab17dd562..000000000 --- a/shared/otel-noop/Tests/Unit/src/sdk/OTelLogger.Tests.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { AITestClass, Assert } from "@microsoft/ai-test-framework"; -import { - createLoggerProvider, - createContext, - IOTelLogger, - IOTelLogRecord, - IOTelSpanContext, - IOTelInstrumentationScope, - createContextManager, - setContextSpanContext, - createLogger, - IOTelLoggerProviderSharedState -} from "@microsoft/otel-core-js"; -import { createNoopLogRecordProcessor } from "../../../../src/api/noop/noopLogRecordProcessor"; - -// W3C TraceFlags - Sampled = 1 -const eW3CTraceFlags_Sampled = 1; - -type LoggerWithScope = IOTelLogger & { instrumentationScope: IOTelInstrumentationScope }; - -export class OTelLoggerTests extends AITestClass { - public testInitialize() { - super.testInitialize(); - } - - public testCleanup() { - super.testCleanup(); - } - - private setup() { - const logProcessor = createNoopLogRecordProcessor(); - const provider = createLoggerProvider({ - processors: [logProcessor] - }); - const logger = provider.getLogger("test name", "test version", { - schemaUrl: "test schema url" - }) as LoggerWithScope; - return { logger, logProcessor, provider }; - } - - public registerTests() { - - this.testCase({ - name: "Logger: factory returns logger instance", - test: () => { - const logProcessor = createNoopLogRecordProcessor(); - const provider = createLoggerProvider({ processors: [logProcessor] }); - const sharedState = provider._sharedState; - const scope: IOTelInstrumentationScope = { - name: "test name", - version: "test version", - schemaUrl: "test schema url" - }; - 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"); - } - }); - - this.testCase({ - name: "Logger: should emit a logRecord instance", - test: () => { - const { logger, logProcessor } = this.setup(); - const callSpy = this.sandbox.spy(logProcessor, "onEmit"); - logger.emit({ - body: "test log body" - }); - Assert.ok(callSpy.called, "onEmit should be called"); - callSpy.restore(); - } - }); - - this.testCase({ - name: "Logger: should make log record instance readonly after emit it", - test: () => { - const { logger, logProcessor } = this.setup(); - let readonlyCalled = false; - this.sandbox.stub(logProcessor, "onEmit").callsFake((logRecord) => { - const originalMakeReadonly = logRecord._makeReadonly; - logRecord._makeReadonly = () => { - readonlyCalled = true; - originalMakeReadonly.call(logRecord); - }; - }); - logger.emit({ - body: "test log body" - }); - Assert.ok(readonlyCalled, "_makeReadonly should be called"); - } - }); - - this.testCase({ - name: "Logger: should emit with current Context", - test: () => { - const { logger, logProcessor } = this.setup(); - const callSpy = this.sandbox.spy(logProcessor, "onEmit"); - logger.emit({ - body: "test log body" - }); - const contextManager = createContextManager(); - const currentContext = contextManager.active(); - Assert.ok(callSpy.called, "onEmit should be called"); - Assert.equal(callSpy.args[0][1], currentContext, "Should emit with current context"); - callSpy.restore(); - } - }); - - this.testCase({ - name: "Logger: should emit with Context specified in LogRecord", - test: () => { - const { logger, logProcessor } = this.setup(); - const spanContext: IOTelSpanContext = { - traceId: "d4cda95b652f4a1592b449d5929fda1b", - spanId: "6e0c63257de34c92", - traceFlags: eW3CTraceFlags_Sampled - }; - const ROOT_CONTEXT = createContext(); - const activeContext = setContextSpanContext(ROOT_CONTEXT, spanContext); - const logRecordData: IOTelLogRecord = { - context: activeContext - }; - - const callSpy = this.sandbox.spy(logProcessor, "onEmit"); - logger.emit(logRecordData); - Assert.ok(callSpy.called, "onEmit should be called"); - Assert.equal(callSpy.args[0][1], activeContext, "Should emit with specified context"); - callSpy.restore(); - } - }); - } -} \ No newline at end of file diff --git a/shared/otel-noop/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts b/shared/otel-noop/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts deleted file mode 100644 index 41df9b24b..000000000 --- a/shared/otel-noop/Tests/Unit/src/sdk/OTelLoggerProvider.Tests.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { AITestClass, Assert } from "@microsoft/ai-test-framework"; -import { createPromise, IPromise } from "@nevware21/ts-async"; - -import { createNoopLogger } from "../../../../src/api/noop/noopLogger"; -import { createNoopLogRecordProcessor } from "../../../../src/api/noop/noopLogRecordProcessor"; -import { - IOTelAttributes, - IOTelLogRecord, - IOTelLoggerProviderSharedState, - DEFAULT_LOGGER_NAME, - createLoggerProvider, - IOTelLogger, - IOTelInstrumentationScope, - createMultiLogRecordProcessor, - loadDefaultConfig, - IOTelResource, - OTelRawResourceAttribute -} from "@microsoft/otel-core-js"; - -type LoggerProviderInstance = ReturnType; -type MultiLogRecordProcessorInstance = ReturnType; -type LoggerWithScope = IOTelLogger & { instrumentationScope: IOTelInstrumentationScope }; - -export class OTelLoggerProviderTests extends AITestClass { - public testInitialize() { - super.testInitialize(); - // No global OpenTelemetry APIs are mutated by LoggerProvider, so no global stubs required here. - } - - public testCleanup() { - super.testCleanup(); - } - - public registerTests() { - this.testCase({ - name: "LoggerProvider: constructor without options should construct an instance", - test: () => { - const provider = createLoggerProvider(); - Assert.equal(typeof provider.getLogger, "function", "Should create a LoggerProvider instance"); - const sharedState = provider._sharedState; - Assert.ok(sharedState.loggers instanceof Map, "Should expose shared state instance"); - } - }); - - this.testCase({ - name: "LoggerProvider: constructor without options should use noop processor by default", - test: (): IPromise => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.registeredLogRecordProcessors.length, 0, "Expected no processors to be registered by default"); - const flushResult = sharedState.activeProcessor.forceFlush(); - return flushResult.then(() => undefined); - } - }); - - this.testCase({ - name: "LoggerProvider: constructor should register provided processors", - test: () => { - const logRecordProcessor = createNoopLogRecordProcessor(); - const provider = createLoggerProvider({ - processors: [logRecordProcessor] - }); - const sharedState = this._getSharedState(provider); - const activeProcessor = sharedState.activeProcessor as MultiLogRecordProcessorInstance; - Assert.equal(activeProcessor.processors.length, 1, "Should register one processor"); - Assert.equal(activeProcessor.processors[0], logRecordProcessor, "Should use the provided processor instance"); - } - }); - - this.testCase({ - name: "LoggerProvider: constructor should use default resource when not provided", - test: () => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - const resource = sharedState.resource; - Assert.ok(!!resource, "Should have a resource available"); - Assert.deepEqual(resource.attributes || {}, {} as IOTelAttributes, "Should default resource attributes to empty object"); - } - }); - - this.testCase({ - name: "LoggerProvider: constructor should honor provided resource", - test: () => { - const passedInResource = this._createTestResource({ foo: "bar" }); - const provider = createLoggerProvider({ - resource: passedInResource - }); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.resource, passedInResource, "Should use the provided resource instance"); - } - }); - - this.testCase({ - name: "LoggerProvider: constructor should use default forceFlushTimeoutMillis when omitted", - test: () => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.forceFlushTimeoutMillis, loadDefaultConfig().forceFlushTimeoutMillis, "Should use default forceFlush timeout"); - } - }); - - this.testCase({ - name: "LoggerProvider: logRecordLimits should default values when not provided", - test: () => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - Assert.deepEqual(sharedState.logRecordLimits, { - attributeCountLimit: 128, - attributeValueLengthLimit: Infinity - }, "Should use default logRecord limits"); - } - }); - - this.testCase({ - name: "LoggerProvider: logRecordLimits should respect provided attributeCountLimit", - test: () => { - const provider = createLoggerProvider({ - logRecordLimits: { - attributeCountLimit: 100 - } - }); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.logRecordLimits.attributeCountLimit, 100, "Should use provided attributeCountLimit"); - } - }); - - this.testCase({ - name: "LoggerProvider: logRecordLimits should respect provided attributeValueLengthLimit", - test: () => { - const provider = createLoggerProvider({ - logRecordLimits: { - attributeValueLengthLimit: 10 - } - }); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.logRecordLimits.attributeValueLengthLimit, 10, "Should use provided attributeValueLengthLimit"); - } - }); - - this.testCase({ - name: "LoggerProvider: logRecordLimits should allow negative attributeValueLengthLimit", - test: () => { - const provider = createLoggerProvider({ - logRecordLimits: { - attributeValueLengthLimit: -10 - } - }); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.logRecordLimits.attributeValueLengthLimit, -10, "Should preserve provided negative attributeValueLengthLimit"); - } - }); - - this.testCase({ - name: "LoggerProvider: logRecordLimits should use default attributeValueLengthLimit when omitted", - test: () => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.logRecordLimits.attributeValueLengthLimit, Infinity, "Should default attributeValueLengthLimit to Infinity"); - } - }); - - this.testCase({ - name: "LoggerProvider: logRecordLimits should use default attributeCountLimit when omitted", - test: () => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.logRecordLimits.attributeCountLimit, 128, "Should default attributeCountLimit to 128"); - } - }); - - this.testCase({ - name: "LoggerProvider: getLogger should default name when invalid", - test: () => { - const provider = createLoggerProvider(); - const logger = provider.getLogger("") as LoggerWithScope; - Assert.equal(logger.instrumentationScope.name, DEFAULT_LOGGER_NAME, "Should use default logger name when name is invalid"); - } - }); - - this.testCase({ - name: "LoggerProvider: getLogger should create new logger when name not seen", - test: () => { - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - Assert.equal(sharedState.loggers.size, 0, "Should start with no loggers"); - provider.getLogger("test name"); - Assert.equal(sharedState.loggers.size, 1, "Should register logger for new name"); - } - }); - - this.testCase({ - name: "LoggerProvider: getLogger should create unique loggers per scope", - test: () => { - const testName = "test name"; - const testVersion = "test version"; - const testSchemaUrl = "test schema url"; - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - - Assert.equal(sharedState.loggers.size, 0, "Should start with no loggers"); - provider.getLogger(testName); - Assert.equal(sharedState.loggers.size, 1, "Should add logger for name only"); - provider.getLogger(testName, testVersion); - Assert.equal(sharedState.loggers.size, 2, "Should add logger for name and version"); - provider.getLogger(testName, testVersion, { schemaUrl: testSchemaUrl }); - Assert.equal(sharedState.loggers.size, 3, "Should add logger for name, version, and schemaUrl"); - } - }); - - this.testCase({ - name: "LoggerProvider: getLogger should reuse logger when scope matches", - test: () => { - const testName = "test name"; - const testVersion = "test version"; - const testSchemaUrl = "test schema url"; - const provider = createLoggerProvider(); - const sharedState = this._getSharedState(provider); - - Assert.equal(sharedState.loggers.size, 0, "Should start with no loggers"); - provider.getLogger(testName); - Assert.equal(sharedState.loggers.size, 1, "Should add first logger"); - const logger1 = provider.getLogger(testName, testVersion, { schemaUrl: testSchemaUrl }); - Assert.equal(sharedState.loggers.size, 2, "Should add scoped logger"); - const logger2 = provider.getLogger(testName, testVersion, { schemaUrl: testSchemaUrl }); - Assert.equal(sharedState.loggers.size, 2, "Should not add duplicate scoped logger"); - const scopedLogger = logger2 as LoggerWithScope; - Assert.equal(scopedLogger.instrumentationScope.name, testName, "Should expose instrumentation scope name"); - Assert.equal(scopedLogger.instrumentationScope.version, testVersion, "Should expose instrumentation scope version"); - Assert.equal(scopedLogger.instrumentationScope.schemaUrl, testSchemaUrl, "Should expose instrumentation scope schemaUrl"); - Assert.equal(logger1, logger2, "Should reuse existing scoped logger"); - } - }); - - this.testCase({ - name: "LoggerProvider: forceFlush should invoke all registered processors", - test: (): IPromise => { - const processor1 = createNoopLogRecordProcessor(); - const processor2 = createNoopLogRecordProcessor(); - const forceFlushStub1 = this.sandbox.stub(processor1, "forceFlush").resolves(); - const forceFlushStub2 = this.sandbox.stub(processor2, "forceFlush").resolves(); - const provider = createLoggerProvider({ processors: [processor1, processor2] }); - - return createPromise((resolve, reject) => { - provider.forceFlush().then(() => { - try { - Assert.equal(forceFlushStub1.callCount, 1, "Should call forceFlush on first processor once"); - Assert.equal(forceFlushStub2.callCount, 1, "Should call forceFlush on second processor once"); - resolve(); - } catch (e) { - reject(e); - } - }).catch(reject); - }); - } - }); - - this.testCase({ - name: "LoggerProvider: forceFlush should propagate processor errors", - test: (): IPromise => { - const processor1 = createNoopLogRecordProcessor(); - const processor2 = createNoopLogRecordProcessor(); - const forceFlushStub1 = this.sandbox.stub(processor1, "forceFlush").rejects("Error"); - const forceFlushStub2 = this.sandbox.stub(processor2, "forceFlush").rejects("Error"); - const provider = createLoggerProvider({ processors: [processor1, processor2] }); - - return createPromise((resolve, reject) => { - provider.forceFlush().then(() => { - reject(new Error("Successful forceFlush not expected")); - }).catch(() => { - try { - Assert.equal(forceFlushStub1.callCount, 1, "Should attempt to forceFlush first processor even when errors occur"); - Assert.equal(forceFlushStub2.callCount, 1, "Should attempt to forceFlush second processor even when errors occur"); - resolve(); - } catch (e) { - reject(e); - } - }); - }); - } - }); - - this.testCase({ - name: "LoggerProvider: shutdown should invoke processor shutdown", - test: (): IPromise => { - const processor = createNoopLogRecordProcessor(); - const shutdownStub = this.sandbox.stub(processor, "shutdown").resolves(); - const provider = createLoggerProvider({ processors: [processor] }); - - return createPromise((resolve, reject) => { - provider.shutdown().then(() => { - try { - Assert.equal(shutdownStub.callCount, 1, "Should call shutdown on processor once"); - resolve(); - } catch (e) { - reject(e); - } - }).catch(reject); - }); - } - }); - - this.testCase({ - name: "LoggerProvider: shutdown should return noop logger for new requests", - test: (): IPromise => { - const provider = createLoggerProvider(); - return createPromise((resolve, reject) => { - provider.shutdown().then(() => { - 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(logger.emit.length, expectedNoopLogger.emit.length, "Noop emit signature should match expected noop logger"); - let threw = false; - try { - logger.emit({} as IOTelLogRecord); - } catch (e) { - threw = true; - } - Assert.ok(!threw, "Noop logger emit should not throw after shutdown"); - resolve(); - } catch (e) { - reject(e); - } - }).catch(reject); - }); - } - }); - - this.testCase({ - name: "LoggerProvider: forceFlush after shutdown should not call processors", - test: (): IPromise => { - const logRecordProcessor = createNoopLogRecordProcessor(); - const provider = createLoggerProvider({ processors: [logRecordProcessor] }); - const forceFlushStub = this.sandbox.stub(logRecordProcessor, "forceFlush").resolves(); - const warnStub = this.sandbox.stub(console, "warn"); - - return createPromise((resolve, reject) => { - provider.shutdown().then(() => { - provider.forceFlush().then(() => { - try { - Assert.equal(forceFlushStub.callCount, 0, "forceFlush should not be called after shutdown"); - Assert.equal(warnStub.callCount, 1, "Should emit a warning when forceFlush is called post-shutdown"); - resolve(); - } catch (e) { - reject(e); - } - }).catch(reject); - }).catch(reject); - }); - } - }); - - this.testCase({ - name: "LoggerProvider: second shutdown should not re-run processor shutdown", - test: (): IPromise => { - const logRecordProcessor = createNoopLogRecordProcessor(); - const provider = createLoggerProvider({ processors: [logRecordProcessor] }); - const shutdownStub = this.sandbox.stub(logRecordProcessor, "shutdown").resolves(); - const warnStub = this.sandbox.stub(console, "warn"); - - return createPromise((resolve, reject) => { - provider.shutdown().then(() => { - provider.shutdown().then(() => { - try { - Assert.equal(shutdownStub.callCount, 1, "Processor shutdown should only be called once"); - Assert.equal(warnStub.callCount, 1, "Should warn on repeated shutdown calls"); - resolve(); - } catch (e) { - reject(e); - } - }).catch(reject); - }).catch(reject); - }); - } - }); - } - - private _getSharedState(provider: LoggerProviderInstance): IOTelLoggerProviderSharedState { - return provider._sharedState; - } - - private _createTestResource(attributes: IOTelAttributes = {} as IOTelAttributes): IOTelResource { - const resourceAttributes: IOTelAttributes = {} as IOTelAttributes; - const rawAttributes: OTelRawResourceAttribute[] = []; - for (const key in attributes) { - if (Object.prototype.hasOwnProperty.call(attributes, key)) { - resourceAttributes[key] = attributes[key]; - rawAttributes.push([key, attributes[key]]); - } - } - - const resource: IOTelResource = { - attributes: resourceAttributes, - merge: () => resource, - getRawAttributes: () => rawAttributes - }; - - return resource; - } -} \ No newline at end of file diff --git a/shared/otel-noop/Tests/tsconfig.json b/shared/otel-noop/Tests/tsconfig.json index f551b584c..18ba555ae 100644 --- a/shared/otel-noop/Tests/tsconfig.json +++ b/shared/otel-noop/Tests/tsconfig.json @@ -5,7 +5,7 @@ "noImplicitAny": false, "module": "amd", "moduleResolution": "node", - "target": "es6", + "target": "es5", "importHelpers": true, "noEmitHelpers": true, "skipLibCheck": true, diff --git a/shared/otel-noop/package.json b/shared/otel-noop/package.json index 34ceef87b..d1c2cd7e2 100644 --- a/shared/otel-noop/package.json +++ b/shared/otel-noop/package.json @@ -72,7 +72,7 @@ "@microsoft/otel-core-js": "0.0.1-alpha", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", - "@nevware21/ts-async": ">= 0.5.4 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x" } } diff --git a/shared/otel-noop/src/api/logger/noopLogRecordProcessor.ts b/shared/otel-noop/src/api/logger/noopLogRecordProcessor.ts new file mode 100644 index 000000000..f5cda6971 --- /dev/null +++ b/shared/otel-noop/src/api/logger/noopLogRecordProcessor.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IOTelLogRecordProcessor } from "@microsoft/otel-core-js"; +import { _noopResolvedPromise, _noopVoid } from "../noop/noopHelpers"; + +export function createNoopLogRecordProcessor(): IOTelLogRecordProcessor { + + return { + forceFlush: _noopResolvedPromise(undefined), + + onEmit: _noopVoid, + + shutdown: _noopResolvedPromise(undefined) + }; +} diff --git a/shared/otel-noop/src/api/noop/noopLogger.ts b/shared/otel-noop/src/api/logger/noopLogger.ts similarity index 57% rename from shared/otel-noop/src/api/noop/noopLogger.ts rename to shared/otel-noop/src/api/logger/noopLogger.ts index df370fde9..48212d2d7 100644 --- a/shared/otel-noop/src/api/noop/noopLogger.ts +++ b/shared/otel-noop/src/api/logger/noopLogger.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IOTelLogRecord, IOTelLogger } from "@microsoft/otel-core-js"; +import { IOTelLogger } from "@microsoft/otel-core-js"; +import { _noopVoid } from "../noop/noopHelpers"; export function createNoopLogger(): IOTelLogger { return { - emit(_logRecord: IOTelLogRecord): void {} + emit: _noopVoid }; } diff --git a/shared/otel-noop/src/api/noop/nonRecordingSpan.ts b/shared/otel-noop/src/api/noop/nonRecordingSpan.ts index 7db5518af..1e978de23 100644 --- a/shared/otel-noop/src/api/noop/nonRecordingSpan.ts +++ b/shared/otel-noop/src/api/noop/nonRecordingSpan.ts @@ -1,16 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { + INVALID_SPAN_ID, INVALID_TRACE_ID, IOTelSpanContext, IOTelSpanStatus, IReadableSpan, eOTelSpanKind, eOTelSpanStatusCode, eW3CTraceFlags +} from "@microsoft/otel-core-js"; import { createDeferredCachedValue, objDefineProps, objFreeze } from "@nevware21/ts-utils"; -import { UNDEFINED_VALUE } from "../../../constants/InternalConstants"; -import { eW3CTraceFlags } from "../../../enums/W3CTraceFlags"; -import { eOTelSpanKind } from "../../../enums/otel/OTelSpanKind"; -import { eOTelSpanStatusCode } from "../../../enums/otel/OTelSpanStatus"; -import { IOTelSpan } from "../../../interfaces/otel/trace/IOTelSpan"; -import { IOTelSpanContext } from "../../../interfaces/otel/trace/IOTelSpanContext"; -import { IOTelSpanStatus } from "../../../interfaces/otel/trace/IOTelSpanStatus"; -import { IReadableSpan } from "../../../interfaces/otel/trace/IReadableSpan"; -import { INVALID_SPAN_ID, INVALID_TRACE_ID } from "../../../utils/TraceParent"; +import { UNDEFINED_VALUE } from "../../internal/InternalConstants"; // Inline noop helpers - these don't need the full noop package function _noopThis(this: T): T { @@ -32,7 +27,7 @@ function _createNoopSpanContext(): IOTelSpanContext { }); } -export function createNonRecordingSpan(spanContext?: IOTelSpanContext, name?: string): IOTelSpan { +export function createNonRecordingSpan(spanContext?: IOTelSpanContext, name?: string): IReadableSpan { function _spanSontext() { return spanContext || _createNoopSpanContext(); } diff --git a/shared/otel-noop/src/api/noop/noopContextMgr.ts b/shared/otel-noop/src/api/noop/noopContextMgr.ts index 71d873ae2..bce3db302 100644 --- a/shared/otel-noop/src/api/noop/noopContextMgr.ts +++ b/shared/otel-noop/src/api/noop/noopContextMgr.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IOTelContext, IOTelContextManager } from "@microsoft/otel-core-js"; +import { IOTelApi, IOTelContext, IOTelContextManager } from "@microsoft/otel-core-js"; import { arrSlice, fnApply } from "@nevware21/ts-utils"; import { _noopThis } from "./noopHelpers"; import { createNoopProxy } from "./noopProxy"; @@ -12,7 +12,7 @@ import { createNoopProxy } from "./noopProxy"; * @param parentContext - The parent context to use as the root context * @returns - A new Noop Context Manager */ -export function createNoopContextMgr(parentContext?: IOTelContext): IOTelContextManager { +export function createNoopContextMgr(otelApi: IOTelApi, parentContext?: IOTelContext): IOTelContextManager { function _getValue(key: symbol): unknown { return parentContext ? parentContext.getValue(key) : undefined; } @@ -22,6 +22,7 @@ export function createNoopContextMgr(parentContext?: IOTelContext): IOTelContext active: { v: () => createNoopProxy({ props: { + api: { v: otelApi }, getValue: { v: _getValue }, setValue: { v: _noopThis as IOTelContext["setValue"] }, deleteValue: { v: _noopThis as IOTelContext["deleteValue"] } diff --git a/shared/otel-noop/src/api/noop/noopHelpers.ts b/shared/otel-noop/src/api/noop/noopHelpers.ts index 0b34a2a27..820d4a7dc 100644 --- a/shared/otel-noop/src/api/noop/noopHelpers.ts +++ b/shared/otel-noop/src/api/noop/noopHelpers.ts @@ -1,4 +1,4 @@ - +import { createSyncRejectedPromise, createSyncResolvedPromise } from "@nevware21/ts-async"; /** * A simple function that does nothing and returns the current this (if any). @@ -12,4 +12,26 @@ export function _noopThis(this: T): T { * A simple function that does nothing and returns undefined. */ export function _noopVoid(this: T): void { -} \ No newline at end of file +} + +/** + * Return a function that returns a resolved promise with the provided value when called. + * @param value - The value to resolve the promise with. + * @returns A function that returns a resolved promise with the provided value when called. + */ +export function _noopResolvedPromise(value?: T) { + return function (): Promise { + return createSyncResolvedPromise(value); + } +} + +/** + * Return a function that returns a rejected promise with the provided error when called. + * @param error - The error to reject the promise with. + * @returns A function that returns a rejected promise with the provided error when called. + */ +export function _noopRejectedPromise(error: any) { + return function (): Promise { + return createSyncRejectedPromise(error); + } +} diff --git a/shared/otel-noop/src/api/noop/noopLogRecordProcessor.ts b/shared/otel-noop/src/api/noop/noopLogRecordProcessor.ts deleted file mode 100644 index 242dd1124..000000000 --- a/shared/otel-noop/src/api/noop/noopLogRecordProcessor.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IOTelContext, IOTelLogRecordProcessor, IOTelSdkLogRecord } from "@microsoft/otel-core-js"; -import { IPromise, createSyncResolvedPromise } from "@nevware21/ts-async"; - -export function createNoopLogRecordProcessor(): IOTelLogRecordProcessor { - - return { - forceFlush(): IPromise { - return createSyncResolvedPromise(undefined); - }, - - onEmit(_logRecord: IOTelSdkLogRecord, _context?: IOTelContext): void {}, - - shutdown(): IPromise { - return createSyncResolvedPromise(undefined); - } - }; -} diff --git a/shared/otel-noop/src/api/noop/noopTracerProvider.ts b/shared/otel-noop/src/api/noop/noopTracerProvider.ts index cce05ebe3..77f7ad916 100644 --- a/shared/otel-noop/src/api/noop/noopTracerProvider.ts +++ b/shared/otel-noop/src/api/noop/noopTracerProvider.ts @@ -2,13 +2,20 @@ // Licensed under the MIT License. import { - IOTelContext, IOTelContextManager, IOTelSpan, IOTelSpanOptions, IOTelTracer, IOTelTracerCtx, IOTelTracerProvider, createNonRecordingSpan, - createTracer, getContextActiveSpanContext, isSpanContext, isSpanContextValid + IOTelContext, IOTelSpan, IOTelSpanOptions, IOTelTracer, IOTelTracerProvider, IReadableSpan, eOTelSpanStatusCode, + getContextActiveSpanContext, isSpanContext, isSpanContextValid } from "@microsoft/otel-core-js"; -import { ILazyValue, createDeferredCachedValue, objDefineProps } from "@nevware21/ts-utils"; -import { createNoopContextMgr } from "./noopContextMgr"; +import { ILazyValue, createDeferredCachedValue, isFunction } from "@nevware21/ts-utils"; +import { createNonRecordingSpan } from "./nonRecordingSpan"; +import { _noopVoid } from "./noopHelpers"; import { createNoopProxy } from "./noopProxy"; +interface ITracerOptions { + name: string; + version?: string; + schemaUrl?: string; +} + /** * Createa a Noop Context Manager that returns Noop Contexts, if no parent context is provided * the returned context will always return undefined for all values. @@ -16,7 +23,7 @@ import { createNoopProxy } from "./noopProxy"; */ export function createNoopTracerProvider(): IOTelTracerProvider { - function _startSpan(name: string, options?: IOTelSpanOptions, context?: IOTelContext): IOTelSpan { + function _startSpan(name: string, options?: IOTelSpanOptions, context?: IOTelContext): IReadableSpan { let opts = options || {}; if (!opts.root) { let parentContext = context || getContextActiveSpanContext(context); @@ -27,22 +34,51 @@ export function createNoopTracerProvider(): IOTelTracerProvider { return createNonRecordingSpan(null, name); } - - let noopMgr: ILazyValue = createDeferredCachedValue(() => createNoopContextMgr()); - let tracerCtx: IOTelTracerCtx = objDefineProps( - { - ctxMgr: null, - startSpan: _startSpan - }, { - ctxMgr: { - l: noopMgr - }, - context: { - g: () => noopMgr.v.active() + + function _startActiveSpan ReturnType>(name: string, arg2?: F | IOTelSpanOptions, arg3?: F | IOTelContext, arg4?: F): ReturnType | undefined { + let options: IOTelSpanOptions = null; + let ctx: IOTelContext | undefined; + let fn: F; + + if (isFunction(arg2)) { + fn = arg2 as F; + } else if (isFunction(arg3)) { + options = arg2 as IOTelSpanOptions; + fn = arg3 as F; + } else { + options = arg2 as IOTelSpanOptions; + ctx = arg3 as IOTelContext; + fn = arg4 as F; + } + + let span = _startSpan(name, options); + let useAsync = false; + + try { + return fn(span); + } catch (e) { + if (span) { + span.setStatus({ code: e ? eOTelSpanStatusCode.ERROR : eOTelSpanStatusCode.OK, message: e ? e.message : undefined }); + } + throw e; + } finally { + // If the function returned a promise, we need to end the span when the promise resolves/rejects + if (!useAsync && span) { + span.end(); + } + } + } + + function _createNoopTracer(): IOTelTracer { + return createNoopProxy({ + props: { + startSpan: { v: _startSpan }, + startActiveSpan: { v: _startActiveSpan } } }); - - let tracer: ILazyValue = createDeferredCachedValue(() => createTracer(tracerCtx, { name: "NoopTracer" })); + } + + let tracer: ILazyValue = createDeferredCachedValue(_createNoopTracer); return createNoopProxy({ props: { @@ -50,7 +86,9 @@ export function createNoopTracerProvider(): IOTelTracerProvider { v: (name: string, version?: string): IOTelTracer => { return tracer.v; } - } + }, + forceFlush: { v: _noopVoid }, + shutdown: { v: _noopVoid } } }); } diff --git a/shared/otel-noop/src/internal/InternalConstants.ts b/shared/otel-noop/src/internal/InternalConstants.ts new file mode 100644 index 000000000..3b246fdef --- /dev/null +++ b/shared/otel-noop/src/internal/InternalConstants.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// ################################################################################################################################################### +// Note: DON'T Export these const from the package as we are still targeting IE/ES5 this will export a mutable variables that someone could change ### +// ################################################################################################################################################### + +// Generally you should only put values that are used more than 2 times and then only if not already exposed as a constant (such as SdkCoreNames) +// as when using "short" named values from here they will be will be minified smaller than the SdkCoreNames[eSdkCoreNames.xxxx] value. + +export const UNDEFINED_VALUE: undefined = undefined; diff --git a/shared/otel-noop/src/otel-noop-js.ts b/shared/otel-noop/src/otel-noop-js.ts index f0869b3eb..37940e8c3 100644 --- a/shared/otel-noop/src/otel-noop-js.ts +++ b/shared/otel-noop/src/otel-noop-js.ts @@ -13,6 +13,6 @@ export { INoopProxyConfig, INoopProxyProp, NoopProxyProps } from "./interfaces/n export { createNoopProxy } from "./api/noop/noopProxy"; export { _noopThis, _noopVoid } from "./api/noop/noopHelpers"; export { createNoopContextMgr } from "./api/noop/noopContextMgr"; -export { createNoopLogger } from "./api/noop/noopLogger"; -export { createNoopLogRecordProcessor } from "./api/noop/noopLogRecordProcessor"; +export { createNoopLogger } from "./api/logger/noopLogger"; +export { createNoopLogRecordProcessor } from "./api/logger/noopLogRecordProcessor"; export { createNoopTracerProvider } from "./api/noop/noopTracerProvider"; diff --git a/tools/chrome-debug-extension/package.json b/tools/chrome-debug-extension/package.json index 987a70d53..6ad86f32c 100644 --- a/tools/chrome-debug-extension/package.json +++ b/tools/chrome-debug-extension/package.json @@ -46,8 +46,8 @@ "@microsoft/otel-core-js": "0.0.1-alpha", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x", + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x", "file-saver": "^2.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/tools/rollup-es5/Tests/Unit/src/index.tests.ts b/tools/rollup-es5/Tests/Unit/src/index.tests.ts index 8530df5c6..26c27164b 100644 --- a/tools/rollup-es5/Tests/Unit/src/index.tests.ts +++ b/tools/rollup-es5/Tests/Unit/src/index.tests.ts @@ -1,4 +1,4 @@ -import { Es5RollupTests } from './Es5Rollup.Tests'; +import { Es5RollupTests } from "./Es5Rollup.Tests"; export function runTests() { new Es5RollupTests().registerTests(); diff --git a/tools/shims/Tests/Unit/src/shims.tests.ts b/tools/shims/Tests/Unit/src/shims.tests.ts index 7a7a9adf7..0b83c5529 100644 --- a/tools/shims/Tests/Unit/src/shims.tests.ts +++ b/tools/shims/Tests/Unit/src/shims.tests.ts @@ -1,6 +1,5 @@ import { ShimsTests } from "./ShimsTests"; export function runTests() { - // TODO: Add back tests - //new ShimsTests().registerTests(); + new ShimsTests().registerTests(); } diff --git a/tools/shims/Tests/UnitTests.html b/tools/shims/Tests/UnitTests.html index 3e0a84894..99d6eed35 100644 --- a/tools/shims/Tests/UnitTests.html +++ b/tools/shims/Tests/UnitTests.html @@ -22,7 +22,7 @@ modules.add("qunit"); loadCommonModules(modules, function() { - var testModule = modules.add("Tests/Unit/src/shims.tests", "./Unit/dist/shims.tests.js") + var testModule = modules.add("Tests/Unit/src/shims.tests", "./Unit/dist/shimstests.js"); testModule.run = function (tests) { console && console.log("Starting tests"); QUnit.start(); diff --git a/tools/shims/package.json b/tools/shims/package.json index 3faede0ff..deb0c3a72 100644 --- a/tools/shims/package.json +++ b/tools/shims/package.json @@ -42,13 +42,11 @@ "@microsoft/applicationinsights-rollup-es5": "1.0.2", "grunt": "^1.5.3", "grunt-cli": "^1.4.3", - "grunt-rollup": "^12.0.0", "@nevware21/grunt-ts-plugin": "^0.5.1", "@nevware21/grunt-eslint-ts": "^0.5.1", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", - "@rollup/plugin-typescript": "^12.1.2", "rollup": "^3.20.0", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-minify-es": "^1.1.1", @@ -56,6 +54,6 @@ "typescript": "^4.9.3" }, "dependencies": { - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } } diff --git a/tools/shims/rollup.config.js b/tools/shims/rollup.config.js index 98fe35911..a9fdda198 100644 --- a/tools/shims/rollup.config.js +++ b/tools/shims/rollup.config.js @@ -67,7 +67,7 @@ const browserUmdRollupConfigFactory = (isProduction) => { nodeResolve(), cleanup(), es5Poly(), - // es5Check() + es5Check() ] }; @@ -114,7 +114,7 @@ const moduleRollupConfigFactory = (format, isProduction) => { nodeResolve(), cleanup(), es5Poly(), - // es5Check() + es5Check() ] }; diff --git a/version.json b/version.json index b69e31c01..4f3ea2a4a 100644 --- a/version.json +++ b/version.json @@ -40,14 +40,6 @@ "package": "extensions/applicationinsights-properties-js/package.json", "release": "4.0.0-beta" }, - "@microsoft/applicationinsights-common": { - "package": "shared/AppInsightsCommon/package.json", - "release": "4.0.0-beta" - }, - "@microsoft/applicationinsights-core-js": { - "package": "shared/AppInsightsCore/package.json", - "release": "4.0.0-beta" - }, "applicationinsights-web-config": { "package": "tools/config/package.json", "release": "1.0.6"