Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c00d3f9
feat: Enhance telemetry with operation context properties
hangy Dec 3, 2025
fc1ba91
Remove unnecessary TraceId/SpanId from log event properties
hangy Dec 5, 2025
f7af03d
feat: Add operation name to log events using Activity context
hangy Dec 6, 2025
9317b9b
refactor: Simplify sink configuration by removing unnecessary wrapper
hangy Dec 17, 2025
7a3f125
fix: Don't set `OperationNameProperty` from inside the `ApplicationIn…
hangy Dec 17, 2025
3db4866
feat: Add ActivityOperationNameEnricher to enrich log events with cur…
hangy Dec 17, 2025
0722bae
feat: Add ActivityBaggageEnricher to enrich log events with Activity …
hangy Dec 17, 2025
e3d6e29
feat: Forward Activity Baggage to telemetry properties in TelemetryCo…
hangy Dec 17, 2025
4140676
feat: Add enrichment extensions for operation name and baggage in Log…
hangy Dec 17, 2025
2dd05c3
feat: Enhance telemetry with operation name and baggage in tests
hangy Dec 17, 2025
cc11781
feat: Add support for optionally including operation ID, parent span …
hangy Dec 17, 2025
06a7e22
feat: Update README to enhance configuration instructions and add new…
hangy Dec 17, 2025
efaa9c6
refactor: Use pattern matching
hangy Dec 23, 2025
18ebe29
perf: Use `Enum.GetName` instead of `.ToString()`
hangy Dec 23, 2025
ad2bb9e
style: dotnet_style_require_accessibility_modifiers = omit_if_default
hangy Dec 23, 2025
eea7447
feat: Support different casing of `OperationId`
hangy Dec 23, 2025
49f1403
feat: Consolidate operation name and baggage enrichment into a single…
hangy Dec 23, 2025
95f9e4f
feat: Add support for case-insensitive property name lookups in telem…
hangy Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 45 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ By default, trace telemetry submits:
- **rendered message** in trace's standard *message* property.
- **severity** in trace's standard *severityLevel* property.
- **timestamp** in trace's standard *timestamp* property.
- **operation id** from `operationId` property, or the `LogEvent.TraceId` property.
Comment thread
hangy marked this conversation as resolved.
Outdated
- **operation parent id** from `ParentSpanId` property.
- **operation name** from `OperationName` property.
- **component version** from `version` property.
- **messageTemplate** in *customDimensions*.
- **custom log properties** as *customDimensions*.

Expand All @@ -151,13 +155,21 @@ Event telemetry submits:
- **message template** as *event name*.
- **renderedMessage** in *customDimensions*.
- **timestamp** in event's standard *timestamp* property.
- **operation id** from `operationId` property, or the `LogEvent.TraceId` property.
- **operation parent id** from `ParentSpanId` property.
- **operation name** from `OperationName` property.
- **component version** from `version` property.
- **custom log properties** as *customDimensions*.

Exception telemetry submits:

- **exception** as standard AI exception.
- **severity** in trace's standard *severityLevel* property.
- **timestamp** in trace's standard *timestamp* property.
- **operation id** from `operationId` property, or the `LogEvent.TraceId` property.
- **operation parent id** from `ParentSpanId` property.
- **operation name** from `OperationName` property.
- **component version** from `version` property.
- **custom log properties** as *customDimensions*.

> Note that **log context** properties are also included in *customDimensions* when Serilog is configured
Expand Down Expand Up @@ -232,26 +244,26 @@ private class CustomConverter : TraceTelemetryConverter
telemetry.Context.User.Id = logEvent.Properties["UserId"].ToString();
}
// post-process the telemetry's context to contain the operation id
if (logEvent.Properties.ContainsKey("operation_Id"))
if (logEvent.Properties.ContainsKey("operationId"))
{
telemetry.Context.Operation.Id = logEvent.Properties["operation_Id"].ToString();
telemetry.Context.Operation.Id = logEvent.Properties["operationId"].ToString();
}
// post-process the telemetry's context to contain the operation parent id
if (logEvent.Properties.ContainsKey("operation_parentId"))
if (logEvent.Properties.ContainsKey("ParentSpanId"))
{
telemetry.Context.Operation.ParentId = logEvent.Properties["operation_parentId"].ToString();
telemetry.Context.Operation.ParentId = logEvent.Properties["ParentSpanId"].ToString();
}
// typecast to ISupportProperties so you can manipulate the properties as desired
ISupportProperties propTelematry = (ISupportProperties)telemetry;
ISupportProperties propTelemetry = (ISupportProperties)telemetry;

// find redundant properties
var removeProps = new[] { "UserId", "operation_parentId", "operation_Id" };
removeProps = removeProps.Where(prop => propTelematry.Properties.ContainsKey(prop)).ToArray();
var removeProps = new[] { "UserId", "ParentSpanId", "operationId" };
removeProps = removeProps.Where(prop => propTelemetry.Properties.ContainsKey(prop)).ToArray();

foreach (var prop in removeProps)
{
// remove redundant properties
propTelematry.Properties.Remove(prop);
propTelemetry.Properties.Remove(prop);
}

yield return telemetry;
Expand All @@ -270,7 +282,7 @@ instance, let's include `renderedMessage` in event telemetry:
```csharp
private class IncludeRenderedMessageConverter : EventTelemetryConverter
{
public override void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,
public override void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,
ISupportProperties telemetryProperties, IFormatProvider formatProvider)
{
base.ForwardPropertiesToTelemetryProperties(logEvent, telemetryProperties, formatProvider,
Expand Down Expand Up @@ -325,16 +337,18 @@ _telemetryClient.Flush();

await Task.Delay(1000);

// or
// or

System.Threading.Thread.Sleep(1000);

```

## Including Operation Id

Application Insight's operation id is pushed out if you set `operationId` LogEvent property. If it's present, AI's
operation id will be overridden by the value from this property.
Application Insight's operation id is set from the following sources in order of precedence:

1. `operationId` LogEvent property
2. `TraceId` LogEvent property

This can be set like so:

Expand All @@ -357,6 +371,23 @@ public class OperationIdEnricher : ILogEventEnricher
Application Insight supports component version and is pushed out if you set `version` log event property. If it's
present, AI's operation version will include the value from this property.

## Using with SerilogTracing

[SerilogTracing](https://github.com/serilog-tracing/serilog-tracing) provides tracing primitives that integrate with Serilog's structured logging. When used with this sink, tracing context is automatically included in Application Insights telemetry.

The following LogEvent properties are mapped to Application Insights telemetry:

| LogEvent Property | Application Insights Telemetry | Notes |
|-------------------|---------------------------------|-------|
| `TraceId` | `Context.Operation.Id` | From TraceId captured in LogEvent |
| `SpanId` | `Id` (for Request/Dependency telemetry) | From SpanId captured in LogEvent |
| `ParentSpanId` | `Context.Operation.ParentId` | |
| `OperationName` | `Context.Operation.Name` | |
| `operationId` | `Context.Operation.Id` | Overrides TraceId |
| `version` | `Context.Component.Version` | |

Precedence for `Context.Operation.Id`: `operationId` property > `TraceId` property (when both `operationId` and `TraceId` properties are absent).

## Using with Azure Functions

Azure functions has out of the box integration with Application Insights, which automatically logs functions execution
Expand All @@ -377,7 +408,7 @@ namespace MyFunctions
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider>((sp) =>
builder.Services.AddSingleton<ILoggerProvider>((sp) =>
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
Expand All @@ -390,7 +421,7 @@ namespace MyFunctions
}
```

Copyright &copy; 2022 Serilog Contributors - Provided under
Copyright &copy; 2025 Serilog Contributors - Provided under
the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html).

See also: [Serilog Documentation](https://github.com/serilog/serilog/wiki)
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright 2016 Serilog Contributors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down Expand Up @@ -49,8 +49,7 @@ public static LoggerConfiguration ApplicationInsights(
var client = new TelemetryClient(telemetryConfiguration ?? TelemetryConfiguration.Active);
#pragma warning restore CS0618

return loggerConfiguration.Sink(new ApplicationInsightsSink(client, telemetryConverter),
restrictedToMinimumLevel, levelSwitch);
return loggerConfiguration.ApplicationInsights(client, telemetryConverter, restrictedToMinimumLevel, levelSwitch);
}

/// <summary>
Expand All @@ -72,8 +71,7 @@ public static LoggerConfiguration ApplicationInsights(
var client = new TelemetryClient(TelemetryConfiguration.Active);
#pragma warning restore CS0618

return loggerConfiguration.Sink(new ApplicationInsightsSink(client, telemetryConverter),
restrictedToMinimumLevel, levelSwitch);
return loggerConfiguration.ApplicationInsights(client, telemetryConverter, restrictedToMinimumLevel, levelSwitch);
}

/// <summary>
Expand All @@ -93,10 +91,13 @@ public static LoggerConfiguration ApplicationInsights(
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
{
return loggerConfiguration.Sink(new ApplicationInsightsSink(telemetryClient, telemetryConverter),
restrictedToMinimumLevel, levelSwitch);
}
var wrapper = LoggerSinkConfiguration.Wrap(
Comment thread
hangy marked this conversation as resolved.
Outdated
wrappedSink => new ApplicationInsightsSink(telemetryClient, telemetryConverter),
configure => { }
);

return loggerConfiguration.Sink(wrapper, restrictedToMinimumLevel, levelSwitch);
}

/// <summary>
/// Adds a Serilog sink that writes <see cref="LogEvent">log events</see> to Microsoft Application Insights
Expand Down Expand Up @@ -125,7 +126,6 @@ public static LoggerConfiguration ApplicationInsights(

var client = new TelemetryClient(config);

return loggerConfiguration.Sink(new ApplicationInsightsSink(client, telemetryConverter),
restrictedToMinimumLevel, levelSwitch);
return loggerConfiguration.ApplicationInsights(client, telemetryConverter, restrictedToMinimumLevel, levelSwitch);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright 2016 Serilog Contributors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
Expand Down Expand Up @@ -101,6 +102,15 @@ public virtual void Emit(LogEvent logEvent)

try
{
var activity = Activity.Current;
if (activity is { OperationName: not null })
{
logEvent.AddOrUpdateProperty(
new LogEventProperty(
TelemetryConverterBase.OperationNameProperty,
new ScalarValue(activity.OperationName)));
Comment thread
hangy marked this conversation as resolved.
Outdated
}

var telemetries = _telemetryConverter.Convert(logEvent, _formatProvider);

// if 'null' is returned (& we therefore there's nothing to track), the logEvent is basically skipped
Expand All @@ -121,7 +131,7 @@ public virtual void Emit(LogEvent logEvent)

#endregion

#region AI specifc Helper methods
#region AI specific Helper methods

/// <summary>
/// Hands over the <paramref name="telemetry" /> to the AI telemetry client.
Expand All @@ -139,7 +149,7 @@ protected virtual void TrackTelemetry(ITelemetry telemetry)
_telemetryClient?.Track(telemetry);
}

#endregion AI specifc Helper methods
#endregion AI specific Helper methods

#region Implementation of IDisposable

Expand Down Expand Up @@ -195,7 +205,7 @@ protected virtual void Dispose(bool disposeManagedResources)
IsDisposing = false;
}
}

#if NET6_0_OR_GREATER
/// <summary>
/// Disposes the sink and flushes telemetry to App Insights.
Expand All @@ -204,7 +214,7 @@ public async ValueTask DisposeAsync()
{
if (IsDisposing || IsDisposed)
return;

try
{
IsDisposing = true;
Expand All @@ -220,4 +230,4 @@ public async ValueTask DisposeAsync()
#endif

#endregion Implementation of IDisposable
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Diagnostics;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Serilog.Events;
Expand Down Expand Up @@ -39,6 +35,16 @@ public abstract class TelemetryConverterBase : ITelemetryConverter
/// </summary>
public const string OperationIdProperty = "operationId";

/// <summary>
/// Property that is included when in log context, will be pushed out as AI parent span id.
/// </summary>
public const string ParentSpanIdProperty = "ParentSpanId";
Comment thread
hangy marked this conversation as resolved.

/// <summary>
/// Property that is included when in log context, will be pushed out as AI operation name.
/// </summary>
public const string OperationNameProperty = "OperationName";

/// <summary>
/// Property that is included when in log context, will be pushed out as AI component version.
/// </summary>
Expand Down Expand Up @@ -72,7 +78,8 @@ public virtual ExceptionTelemetry ToExceptionTelemetry(
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
if (logEvent.Exception == null) throw new ArgumentException("Must have an Exception", nameof(logEvent));

var exceptionTelemetry = new ExceptionTelemetry(logEvent.Exception) {
var exceptionTelemetry = new ExceptionTelemetry(logEvent.Exception)
{
SeverityLevel = ToSeverityLevel(logEvent.Level),
Timestamp = logEvent.Timestamp
};
Expand Down Expand Up @@ -143,15 +150,27 @@ public void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,

if (telemetryProperties is ITelemetry telemetry)
{
if (logEvent.Properties.TryGetValue(OperationIdProperty, out var operationId))
telemetry.Context.Operation.Id = operationId.ToString().Trim('\"');
else
// Operation.Id (TraceId)
if (logEvent.Properties.TryGetValue(OperationIdProperty, out var operationIdProp))
telemetry.Context.Operation.Id = operationIdProp.ToString().Trim('"');
else if (logEvent.TraceId is ActivityTraceId traceId)
telemetry.Context.Operation.Id = traceId.ToHexString();

// Operation.ParentId (ParentSpanId)
if (logEvent.Properties.TryGetValue(ParentSpanIdProperty, out var parentSpanIdProp))
telemetry.Context.Operation.ParentId = parentSpanIdProp.ToString().Trim('"');

// Operation.Name (OperationName)
if (logEvent.Properties.TryGetValue(OperationNameProperty, out var operationNameProp))
telemetry.Context.Operation.Name = operationNameProp.ToString().Trim('"');

// Set Id for RequestTelemetry and DependencyTelemetry
if (logEvent.SpanId is ActivitySpanId spanId)
{
if (logEvent.TraceId is ActivityTraceId traceId)
telemetry.Context.Operation.Id = traceId.ToHexString();

if (logEvent.SpanId is ActivitySpanId spanId)
telemetry.Context.Operation.ParentId = spanId.ToHexString();
if (telemetry is RequestTelemetry req)
req.Id = spanId.ToHexString();
else if (telemetry is DependencyTelemetry dep)
dep.Id = spanId.ToHexString();
}

if (logEvent.Properties.TryGetValue(VersionProperty, out var version)
Expand Down Expand Up @@ -188,4 +207,4 @@ public void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,

return null;
}
}
}
Loading