Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 9, 2026

GitHub Issue (If applicable): #

PR Type

  • Feature

What is the current behavior?

ITelemetry only tracks events with string properties and double measurements. No support for exception telemetry, contextual scoping, or severity levels.

What is the new behavior?

Breaking API Changes

Added exception tracking and exception severity enum to ITelemetry, plus CreateScope as an extension method:

public enum ExceptionSeverity
{
    Critical, Error, Warning, Info, Debug
}

// Extension method in TelemetryExtensions class
public static ITelemetry CreateScope(
    this ITelemetry telemetry,
    IReadOnlyDictionary<string, string>? properties = null,
    IReadOnlyDictionary<string, double>? measurements = null);

// New interface method
public interface ITelemetry
{
    // Existing members...
    
    void TrackException(
        Exception exception,
        IReadOnlyDictionary<string, string>? properties = null,
        IReadOnlyDictionary<string, double>? measurements = null,
        ExceptionSeverity severity = ExceptionSeverity.Error);
}

Architecture

Scope Implementation:

  • CreateScope is an extension method (in TelemetryExtensions) that works with any ITelemetry implementation
  • Returns a ScopedTelemetry wrapper - an implementation-agnostic class that handles scope merging
  • Scope wrapper does not dispose the inner telemetry instance (only disposes the scope itself)
  • Eliminates code duplication across implementations
  • Avoids fragile object initializer patterns

Scope Semantics:

  • Scopes nest and merge: child properties override parent on key conflicts
  • Event-specific properties override scope properties
  • Applies to both TrackEvent and TrackException
  • All scope logic centralized in ScopedTelemetry wrapper

Backend Implementations

Application Insights (Telemetry):

  • Maps ExceptionSeveritySeverityLevel
  • Uses TelemetryClient.TrackException(ExceptionTelemetry)
  • No implementation-specific scope handling needed (uses wrapper)
  • Thread-safe lock-free chaining works correctly on single-threaded WASM (CompareExchange succeeds immediately, Thread.Yield() is a no-op)
  • Helper methods use Core pattern to eliminate code duplication

File-based (FileTelemetry):

{
  "Type": "exception",
  "Timestamp": "...",
  "Severity": "Error",
  "Exception": {
    "Type": "System.InvalidOperationException",
    "Message": "...",
    "StackTrace": "..."
  },
  "Properties": { "scopeKey": "scopeValue" },
  "Measurements": { "duration": 123.45 }
}

Example Usage

var telemetry = new Telemetry(key, prefix, assembly);

// Scoped context automatically enriches all events/exceptions via extension method
using var scope = telemetry.CreateScope(
    properties: new Dictionary<string, string> { ["component"] = "DataService" });

scope.TrackException(
    new TimeoutException("Query timeout"),
    properties: new Dictionary<string, string> { ["query"] = "SELECT *" },
    measurements: new Dictionary<string, double> { ["duration_ms"] = 5000 },
    severity: ExceptionSeverity.Warning);
// Result includes both "component" (from scope) and "query" (from call)

Code Quality

All duplicate code has been eliminated through refactoring:

  • GetEventProperties and GetEventMeasures overloads consolidated using shared Core helper methods
  • Both IDictionary and IReadOnlyDictionary overloads delegate to GetEventPropertiesCore and GetEventMeasuresCore that accept IEnumerable<KeyValuePair<T>>
  • Similar consolidation pattern used in ScopedTelemetry for merge operations

OTel-Ready

Contract carries structured exception data, severity, and scoped context sufficient for future OpenTelemetry backend without API changes.

PR Checklist

  • Tested code with current supported SDKs
  • Docs have been added/updated which fit documentation template (N/A - internal library)
  • Unit Tests and/or UI Tests for the changes have been added
  • Wasm UI Tests are not showing unexpected any differences (N/A)
  • Contains NO breaking changes
  • Updated the Release Notes
  • Associated with an issue (GitHub or internal)

Breaking Changes: This PR introduces breaking changes to ITelemetry interface - implementations must add TrackException method. The ExceptionSeverity enum is new. CreateScope is provided as an extension method, so no implementation changes required for scope support.

Other information

Testing: 33 tests pass (14 existing + 19 new). New tests cover exception tracking, severity mapping, nested scope merging, and override behavior.

Security: CodeQL analysis shows 0 vulnerabilities. All telemetry operations maintain no-throw guarantees.

Architecture: Scope implementation uses extension method + wrapper pattern per code review feedback, eliminating duplication and avoiding fragile patterns in concrete implementations. The scope wrapper correctly does not dispose the inner telemetry instance, which may be shared across multiple scopes or used directly. All helper methods follow the Core pattern to consolidate duplicate implementations and reduce code duplication.

Platform Compatibility: Thread-safe lock-free chaining pattern (using Interlocked.CompareExchange with Thread.Yield) works correctly on all platforms including single-threaded WASM, where concurrent access doesn't occur and the exchange succeeds immediately.

Internal Issue (If applicable):

Original prompt

This section details on the original issue you should resolve

<issue_title>Telemetry: exception tracking + scoped context + severity (OTel‑ready contract)</issue_title>
<issue_description>## Context
This issue applies to Uno.DevTools.Telemetry (repo: unoplatform/uno.devtools.telemetry).

We want to add exception reporting to telemetry, support scoped context (nested/merged), and introduce severity as an enum (mapped to Application Insights ExceptionTelemetry.SeverityLevel). This is a breaking change to ITelemetry, but acceptable for our ecosystem.

Goals

  • Add exception telemetry to the core interface.
  • Add scoped context that automatically enriches events/exceptions.
  • Introduce severity as an enum (mapped to App Insights native field).
  • Keep the contract OTel‑ready (no OTel implementation yet).

OTel‑ready (contract only)

  • The interface must carry enough structured data (exception details, severity, scoped properties/measurements) to allow a future OpenTelemetry backend without changing the public contract again.
  • We do not enforce OTel attribute keys now.

Proposed API (draft)

ITelemetry

public interface ITelemetry
{
    // existing members...

    ITelemetry CreateScope(
        IReadOnlyDictionary<string, string>? properties = null,
        IReadOnlyDictionary<string, double>? measurements = null);

    void TrackException(
        Exception exception,
        IReadOnlyDictionary<string, string>? properties = null,
        IReadOnlyDictionary<string, double>? measurements = null,
        TelemetrySeverity severity = TelemetrySeverity.Error);
}

Severity enum

public enum TelemetrySeverity
{
    Critical,
    Error,
    Warning,
    Info,
    Debug
}

Scope semantics

  • Scopes are nested and merged.
  • Child scope properties override parent properties on key conflict.
  • Scope properties/measurements are applied to all subsequent TrackEvent and TrackException calls within the scope.
  • source can be provided via scope (e.g., source = "MyComponent.Method") or via properties.

Application Insights mapping

  • Use TelemetryClient.TrackException.
  • Map TelemetrySeverity to ExceptionTelemetry.SeverityLevel.
  • Properties remain string key/value; measurements are double.

FileTelemetry behavior

  • Exceptions are written as structured JSON:
{
  "type": "exception",
  "timestamp": "...",
  "severity": "Error",
  "exception": {
    "type": "...",
    "message": "...",
    "stacktrace": "..."
  },
  "properties": { ... },
  "measurements": { ... }
}

Testing

  • Exception telemetry routes to correct backend (App Insights + FileTelemetry).
  • Scope merge/override behavior (nested scopes).
  • Severity enum maps to ExceptionTelemetry.SeverityLevel.
  • No‑throw guarantees (telemetry must not crash host apps).

Open questions

  • Should BeginScope default to an empty scope when both dictionaries are null?
  • Any preference on default severity for unhandled exceptions?

This issue is filed in unoplatform/uno-private but is for Uno.DevTools.Telemetry changes.</issue_description>

Comments on the Issue (you are @copilot in this section)

  • Fixes unoplatform/uno-private#1678

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…elemetry

- Add TelemetrySeverity enum with Critical, Error, Warning, Info, Debug levels
- Add CreateScope method to ITelemetry for nested/merged scoped context
- Add TrackException method to ITelemetry with severity support
- Implement exception tracking in Telemetry class (Application Insights backend)
- Implement exception tracking in FileTelemetry class with structured JSON
- Implement scoped context in both Telemetry and FileTelemetry classes
- Update TelemetryAdapter and TelemetryGenericFactory to delegate new methods
- Scope properties/measurements override parent values on key conflicts
- All telemetry operations maintain no-throw guarantees

Co-authored-by: carldebilly <[email protected]>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI and others added 3 commits January 9, 2026 15:15
- Add ExceptionTelemetryTests with 10 test cases covering:
  - Basic exception tracking
  - Exception with properties and measurements
  - Severity level mapping (Critical, Error, Warning, Info, Debug)
  - Default severity behavior
  - Null safety checks
  - Stack trace inclusion

- Add ScopedTelemetryTests with 13 test cases covering:
  - Scope property and measurement inheritance
  - Nested scope merging
  - Child scope overrides parent on key conflicts
  - Event properties override scope properties
  - Scope applies to both events and exceptions
  - Empty scope handling
  - Multiple events in same scope
  - Deeply nested scopes

All 33 tests pass (14 existing + 19 new)

Co-authored-by: carldebilly <[email protected]>
- Extract common logic from overloaded GetEventProperties/GetEventMeasures methods in Telemetry class
- Extract common logic from overloaded MergeProperties/MergeMeasurements methods in FileTelemetry class
- Use IEnumerable<KeyValuePair<T>> as common base for both IDictionary and IReadOnlyDictionary
- Maintain public overloads for type safety while reducing duplication
- All 33 tests still pass

Co-authored-by: carldebilly <[email protected]>
- Add TelemetryTypeEvent and TelemetryTypeException constants
- Replace string literals with constants to prevent typos
- Ensures consistency across telemetry entry types

Co-authored-by: carldebilly <[email protected]>
Copilot AI changed the title [WIP] Add exception tracking and severity to telemetry interface feat: Add exception tracking, scoped context, and severity to telemetry (OTel-ready contract) Jan 9, 2026
Copilot AI requested a review from carldebilly January 9, 2026 15:22
Copilot AI and others added 2 commits January 9, 2026 16:29
…nostic wrapper

- Remove CreateScope from ITelemetry interface (now extension method)
- Add ScopedTelemetry wrapper class for implementation-agnostic scope support
- Add TelemetryExtensions with CreateScope extension method
- Remove scope-related fields and logic from Telemetry and FileTelemetry classes
- Scope merging now handled by ScopedTelemetry wrapper instead of each implementation
- Eliminates code duplication and fragile object initializer pattern
- All 33 tests still pass

Co-authored-by: carldebilly <[email protected]>
- Remove unused MergeScopeProperties and MergeScopeMeasurements methods
- Consolidate MergeProperties/MergeMeasurements overloads using Core methods
- Fix bug in MergePropertiesCore (was creating Dictionary<double> instead of Dictionary<string>)
- Eliminate code duplication in merge logic

Co-authored-by: carldebilly <[email protected]>
Copy link
Member

@carldebilly carldebilly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Some reviews...

- Fix ScopedTelemetry.Dispose() to not dispose inner telemetry (scope wrapper shouldn't dispose shared instance)
- Rename TelemetrySeverity to ExceptionSeverity to reflect it's only used for exceptions
- Add comments explaining Thread.Yield() behavior on single-threaded WASM (it's a no-op, safe to use)
- All 33 tests still pass

Co-authored-by: carldebilly <[email protected]>
Copilot AI changed the title feat: Add exception tracking, scoped context, and severity to telemetry (OTel-ready contract) feat: Add exception tracking, scoped context, and exception severity to telemetry (OTel-ready contract) Jan 9, 2026
Copilot AI requested a review from carldebilly January 9, 2026 20:12
@carldebilly carldebilly marked this pull request as ready for review January 9, 2026 20:33
Copilot AI review requested due to automatic review settings January 9, 2026 20:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds exception tracking, scoped context, and severity levels to the Uno.DevTools.Telemetry library, creating an OpenTelemetry-ready contract without implementing OTel backends yet.

Key Changes

  • Introduces TrackException method to ITelemetry interface with an ExceptionSeverity enum (Critical, Error, Warning, Info, Debug)
  • Adds CreateScope extension method that returns a ScopedTelemetry wrapper for nested, merged contextual properties and measurements
  • Implements exception tracking in both Application Insights backend (with severity mapping to SeverityLevel) and file-based telemetry (as structured JSON)

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Uno.DevTools.Telemetry/ExceptionSeverity.cs New enum defining five severity levels for exception telemetry
src/Uno.DevTools.Telemetry/ITelemetry.cs Adds TrackException method to core interface
src/Uno.DevTools.Telemetry/TelemetryExtensions.cs New extension method CreateScope providing default scope implementation
src/Uno.DevTools.Telemetry/ScopedTelemetry.cs Internal wrapper class that merges scope properties/measurements with event-specific data
src/Uno.DevTools.Telemetry/Telemetry.cs Application Insights implementation with severity mapping and lock-free exception tracking
src/Uno.DevTools.Telemetry/FileTelemetry.cs File-based implementation serializing exceptions as structured JSON
src/Uno.DevTools.Telemetry/TelemetryAdapter.cs Updated adapter to forward TrackException calls
src/Uno.DevTools.Telemetry/TelemetryServiceCollectionExtensions.cs Updated DI factory to implement TrackException
src/Uno.DevTools.Telemetry.Tests/ExceptionTelemetryTests.cs 11 new tests covering exception tracking scenarios
src/Uno.DevTools.Telemetry.Tests/ScopedTelemetryTests.cs 10 new tests covering scope nesting, merging, and override behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 310 to 327
private IDictionary<string, string>? GetEventProperties(IReadOnlyDictionary<string, string>? properties)
{
if (properties == null)
{
return _commonProperties is IDictionary<string, string> commonProperties
? commonProperties
: new Dictionary<string, string>(_commonProperties?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
?? new Dictionary<string, string>(0));
}

var eventProperties = new Dictionary<string, string>(_commonProperties?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
?? new Dictionary<string, string>(0));
foreach (var property in properties)
{
eventProperties[property.Key] = property.Value;
}
return eventProperties;
}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate implementation of GetEventProperties that differs only in parameter type (IReadOnlyDictionary vs IDictionary). Consider consolidating both overloads to call a shared private helper method that accepts IEnumerable<KeyValuePair<string, string>>? to eliminate the code duplication.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidated in commit d80c145. Both overloads now delegate to shared GetEventPropertiesCore and GetEventMeasuresCore helper methods that accept IEnumerable<KeyValuePair<T>>, eliminating the duplication.

Comment on lines 277 to 289
private IDictionary<string, double> GetEventMeasures(IReadOnlyDictionary<string, double>? measurements)
{
var eventMeasurements = new Dictionary<string, double>(_commonMeasurements?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
?? new Dictionary<string, double>(0));
if (measurements != null)
{
foreach (var measurement in measurements)
{
eventMeasurements[measurement.Key] = measurement.Value;
}
}
return eventMeasurements;
}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate implementation of GetEventMeasures that differs only in parameter type (IReadOnlyDictionary vs IDictionary). Consider consolidating both overloads to call a shared private helper method that accepts IEnumerable<KeyValuePair<string, double>>? to eliminate the code duplication.

Copilot uses AI. Check for mistakes.
- Extract common logic into GetEventPropertiesCore and GetEventMeasuresCore helper methods
- Overloads for IDictionary and IReadOnlyDictionary now delegate to core methods accepting IEnumerable
- Eliminates code duplication while maintaining type-safe public overloads
- All 33 tests still pass

Co-authored-by: carldebilly <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants