Skip to content

Breaking Changes

Kieron Lanning edited this page Feb 8, 2026 · 5 revisions

Breaking Changes

This page documents breaking changes between major versions to help you migrate your code.

Table of Contents

v3 to v4

Version 4 introduces two major breaking changes: namespace consolidation and OpenTelemetry-aligned naming conventions.

Namespace Consolidation

Impact: High - requires code changes in all projects using v3

v4 consolidates all attributes into a single namespace to simplify imports and improve developer experience.

Migration Required

Before (v3):

using Purview.Telemetry.Activities;
using Purview.Telemetry.Logging;
using Purview.Telemetry.Metrics;

[ActivitySource("myapp")]
[Logger]
[Meter]
interface IMyTelemetry
{
    [Activity]
    Activity? DoSomething([Baggage]int id);
    
    [Info]
    void LogMessage(string message);
    
    [Counter]
    void IncrementCounter([InstrumentMeasurement]int value);
}

After (v4):

using Purview.Telemetry;  // Single namespace!

[ActivitySource("myapp")]
[Logger]
[Meter]
interface IMyTelemetry
{
    [Activity]
    Activity? DoSomething([Baggage]int id);
    
    [Info]
    void LogMessage(string message);
    
    [Counter]
    void IncrementCounter([InstrumentMeasurement]int value);
}

What Changed

All attributes are now in the unified Purview.Telemetry namespace:

v3 Namespace v4 Namespace Affected Attributes
Purview.Telemetry.Activities Purview.Telemetry [ActivitySource], [Activity], [Event], [Context], [Baggage]
Purview.Telemetry.Logging Purview.Telemetry [Logger], [Log], [Debug], [Info], [Warning], [Error], [Critical]
Purview.Telemetry.Metrics Purview.Telemetry [Meter], [Counter], [AutoCounter], [Histogram], [Observable*], [UpDownCounter]
Purview.Telemetry Purview.Telemetry [Tag], [TelemetryGeneration], [Exclude]

Migration Steps

  1. Find and replace imports in your codebase:

    • Replace using Purview.Telemetry.Activities; with using Purview.Telemetry;
    • Replace using Purview.Telemetry.Logging; with using Purview.Telemetry;
    • Replace using Purview.Telemetry.Metrics; with using Purview.Telemetry;
  2. Remove duplicate imports - if you had all three imports in a file, you now only need one

  3. Rebuild and test your project

OpenTelemetry-Aligned Naming

Impact: Medium to High - changes generated telemetry names (may break dashboards/queries)

Introduced in: v4.0.0-alpha.5

Default behavior: Enabled

v4 defaults to OpenTelemetry semantic conventions for generated telemetry names, improving observability and cross-platform compatibility. This is a breaking change if you rely on specific telemetry names in dashboards, queries, or monitoring tools.

What Changed

Telemetry Type v3 Behaviour v4 Default (OpenTelemetry) Example Change
ActivitySource Name Assembly name lowercased Assembly name casing preserved "myapp""MyApp"
Activity Names Method name lowercased Method name casing preserved "getentity""GetEntity"
Tag/Baggage Keys Lowercased, smashed compounds snake_case with underscores "entityid""entity_id"
Metric Instrument Names Lowercased, smashed Hierarchical with meter prefix "recordcount""myapp.products.record.count"
Metric Tag Keys Lowercased, smashed snake_case with underscores "requestcount""request_count"

Examples

Activities

Before (v3):

[ActivitySource("MyApp")]
interface IOrderTelemetry
{
    [Activity]
    Activity? ProcessingOrder([Baggage]int orderId, [Tag]string customerName);
}

// Generated ActivitySource name: "myapp"
// Generated Activity name: "processingorder"
// Generated tag keys: "orderid", "customername"

After (v4 OpenTelemetry mode - DEFAULT):

[ActivitySource("MyApp")]
interface IOrderTelemetry
{
    [Activity]
    Activity? ProcessingOrder([Baggage]int orderId, [Tag]string customerName);
}

// Generated ActivitySource name: "MyApp"
// Generated Activity name: "ProcessingOrder"
// Generated tag keys: "order_id", "customer_name"
Metrics

Before (v3):

[Meter("MyApp.Products")]
interface IProductMetrics
{
    [Counter]
    void RecordProductCount([InstrumentMeasurement]int count, [Tag]string productType);
}

// Generated meter name: "myapp.products"
// Generated instrument name: "recordproductcount"
// Generated tag key: "producttype"

After (v4 OpenTelemetry mode - DEFAULT):

[Meter("MyApp.Products")]
interface IProductMetrics
{
    [Counter]
    void RecordProductCount([InstrumentMeasurement]int count, [Tag]string productType);
}

// Generated meter name: "MyApp.Products"
// Generated instrument name: "myapp.products.record.product.count"
//   (meter name in lowercase + instrument name in snake_case)
// Generated tag key: "product_type"

Important

In OpenTelemetry mode, metric instrument names automatically include the meter name as a prefix (converted to lowercase with dots), following OpenTelemetry best practices for hierarchical metric naming.

Logging

Before (v3):

[Logger]
interface IUserServiceTelemetry
{
    [Info]
    void UserLoggedIn(int userId, string userName);
}

// Generated log properties: "userid", "username"

After (v4 OpenTelemetry mode - DEFAULT):

[Logger]
interface IUserServiceTelemetry
{
    [Info]
    void UserLoggedIn(int userId, string userName);
}

// Generated log properties: "user_id", "user_name"

Migration Options

You have three migration paths:

Option 1: Adopt OpenTelemetry Naming (Recommended)

Best for: New projects, or projects already using OpenTelemetry conventions

Simply upgrade to v4 and update your:

  • Dashboard queries to use snake_case keys (entity_id instead of entityid)
  • Metric queries to use hierarchical names (myapp.products.record.count instead of recordcount)
  • Activity source names with proper casing (MyApp instead of myapp)

Benefits:

  • Better interoperability with OpenTelemetry ecosystem
  • More readable telemetry data
  • Industry-standard naming conventions
  • Future-proof for OpenTelemetry tooling
Option 2: Revert to v3 Legacy Naming

Best for: Existing projects with extensive dashboards/queries that would be costly to update

Use the [TelemetryGeneration] attribute to opt into legacy mode:

Assembly-wide (all interfaces):

using Purview.Telemetry;

[assembly: TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]

Per-interface:

using Purview.Telemetry;

[TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]
[ActivitySource("MyApp")]
[Logger]
[Meter]
interface IMyTelemetry
{
    // This interface will use v3-style naming
}

Result: Generated code will use v3 naming conventions (lowercase, smashed compounds)

Option 3: Mixed Mode (Advanced)

You can use different conventions for different interfaces:

using Purview.Telemetry;

// New interface using OpenTelemetry conventions (default)
[ActivitySource("MyApp")]
interface INewFeatureTelemetry
{
    [Activity]
    Activity? ProcessNewFeature([Tag]int featureId);
    // Uses OpenTelemetry naming: "feature_id"
}

// Legacy interface keeping v3 conventions
[TelemetryGeneration(NamingConvention = NamingConvention.Legacy)]
[ActivitySource("MyApp")]
interface ILegacyTelemetry
{
    [Activity]
    Activity? ProcessLegacy([Tag]int entityId);
    // Uses Legacy naming: "entityid"
}

Available Naming Conventions

public enum NamingConvention
{
    /// <summary>
    /// v3 behavior: lowercase, smashed compound words (no separators)
    /// Example: "entityid", "recordcount", "myapp"
    /// </summary>
    Legacy = 0,
    
    /// <summary>
    /// v4 default: OpenTelemetry conventions
    /// - Preserves casing for ActivitySource/Activity names
    /// - Uses snake_case for tags/properties (entity_id)
    /// - Uses hierarchical dot.separated for metrics (myapp.products.record.count)
    /// </summary>
    OpenTelemetry = 1
}

Migration Checklist

  • Update package reference to v4.0.0-prerelease.1
  • Consolidate namespace imports to using Purview.Telemetry;
  • Decide on naming convention:
    • Option A: Keep OpenTelemetry naming (default) - update dashboards/queries
    • Option B: Add [assembly: TelemetryGeneration(NamingConvention = NamingConvention.Legacy)] to revert to v3 naming
    • Option C: Mix conventions per-interface using [TelemetryGeneration] attribute
  • Rebuild project and review generated code
  • Update monitoring dashboards to match new naming (if using OpenTelemetry mode)
  • Update metric queries/alerts to use new hierarchical names (if using OpenTelemetry mode)
  • Test telemetry collection in your observability platform
  • Update documentation/runbooks with new telemetry names

Impact Assessment

Low Impact Scenarios:

  • New projects with no existing dashboards
  • Projects that don't query telemetry by specific field names
  • Projects using generic telemetry queries

Medium Impact Scenarios:

  • Projects with basic dashboards that can be easily updated
  • Projects with moderate number of metric queries
  • Projects using standard OpenTelemetry tools

High Impact Scenarios:

  • Projects with extensive custom dashboards
  • Projects with many hardcoded metric queries
  • Projects with automated alerting based on specific metric names
  • Multi-team projects where changing names requires coordination

Tip

For high-impact scenarios, consider using NamingConvention.Legacy initially, then gradually migrating to OpenTelemetry conventions during a planned maintenance window.

v1 and v2 to v3

Logging Event Name Generation

A change was made to the available options and the default value, to better support the default means of generation used by the Microsoft Logging/ Telemetry source generators.

Previously, the default event name generated for the logging method also included a trimmed down version of the class name, and the method name combined.

For example:

[Logger]
interface IServiceTelemetry
{
    void LogAThing(int theThing);
}

Important

In v1 and v2, the default event name for LogAThing would have been Service.LogAThing. As of v3, the default has changed to LogAThing.

This is supported by the following changes to the LogPrefixType's field.

Field Old Behaviour New Behaviour
Default Generated a prefix based on the generated class name. Generates no suffix.
NoSuffix Generated no suffix. Field removed
TrimmedClassName Previously the default behaviour. New field, generates a suffix based on the generated class name.

To return to the previous behaviour, set one of the following:

  • LoggerGenerationAttribute.DefaultPrefixType to LogPrefixType.TrimmedClassName at the assembly level.
  • LoggerAttribute.PrefixType to LogPrefixType.TrimmedClassName at the interface level.

Clone this wiki locally