Skip to content

Commit a4f2371

Browse files
authored
Merge pull request #237 from hangy/serilog-tracing-support
feat: Make ambient `Activity` tracing opt-in and enhance mapping config
2 parents 8d7a17e + 95f9e4f commit a4f2371

27 files changed

Lines changed: 917 additions & 124 deletions

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ csharp_space_around_declaration_statements = false
4949
csharp_space_before_open_square_brackets = false
5050
csharp_space_between_empty_square_brackets = false
5151
csharp_space_between_square_brackets = false
52+
53+
# C# code style - Accessibility modifiers
54+
dotnet_style_require_accessibility_modifiers = omit_if_default

README.md

Lines changed: 114 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1-
# Serilog.Sinks.ApplicationInsights [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Sinks.ApplicationInsights.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.ApplicationInsights/)
1+
# Serilog.Sinks.ApplicationInsights [![Build status](https://github.com/serilog-contrib/serilog-sinks-applicationinsights/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/serilog-contrib/serilog-sinks-applicationinsights/actions) [![NuGet Version](https://img.shields.io/nuget/v/Serilog.Sinks.ApplicationInsights.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.ApplicationInsights/) [![NuGet Downloads](https://img.shields.io/nuget/dt/Serilog.Sinks.ApplicationInsights.svg)](https://www.nuget.org/packages/Serilog.Sinks.ApplicationInsights/)
22

33
A sink for Serilog that writes events to Microsoft Application Insights. This sink comes with several defaults that send
44
Serilog `LogEvent` messages to Application Insights as either `EventTelemetry` or `TraceTelemetry`.
55

6+
## Install
7+
8+
```powershell
9+
dotnet add package Serilog.Sinks.ApplicationInsights
10+
```
11+
612
## Configuring
713

8-
The simplest way to configure Serilog to send data to a Application Insights dashboard via instrumentation key is to use
9-
current active *telemetry configuration* which is already initialised in most application types like ASP.NET Core, Azure
10-
Functions etc.:
14+
The recommended way to configure the sink is to reuse the `TelemetryConfiguration` (or `TelemetryClient`) already configured by your application (for example via dependency injection in ASP.NET Core, Azure Functions, Worker Services).
1115

1216
```csharp
13-
var log = new LoggerConfiguration()
14-
.WriteTo.ApplicationInsights(TelemetryConfiguration.Active, TelemetryConverter.Traces)
17+
Log.Logger = new LoggerConfiguration()
18+
.WriteTo.ApplicationInsights(
19+
telemetryConfiguration, // from DI (recommended)
20+
TelemetryConverter.Traces)
1521
.CreateLogger();
1622
```
1723

18-
.. or as `EventTelemetry`:
24+
If you don't have an existing `TelemetryConfiguration` (uncommon), you can use the connection string overload:
1925

2026
```csharp
21-
var log = new LoggerConfiguration()
22-
.WriteTo.ApplicationInsights(TelemetryConfiguration.Active, TelemetryConverter.Events)
27+
Log.Logger = new LoggerConfiguration()
28+
.WriteTo.ApplicationInsights(
29+
"<your Application Insights connection string>",
30+
TelemetryConverter.Traces)
2331
.CreateLogger();
2432
```
2533

34+
Legacy: some older application types used `TelemetryConfiguration.Active`. This is not recommended on modern .NET and may be deprecated depending on the Application Insights SDK version.
35+
2636
> You can also pass an *instrumentation key* and this sink will create a new `TelemetryConfiguration` based on it,
2737
> however it's actively discouraged compared to using already initialised telemetry configuration, as your telemetry
2838
> won't
@@ -48,7 +58,7 @@ in `ConfigureServices`.
4858
Log.Logger = new LoggerConfiguration()
4959
.WriteTo.ApplicationInsights(
5060
serviceProvider.GetRequiredService<TelemetryConfiguration>(),
51-
TelemetryConverter.Traces)
61+
TelemetryConverter.Traces)
5262
.CreateLogger();
5363
```
5464

@@ -57,10 +67,10 @@ startup errors can be caught and properly logged. The problem is that now we're
5767
to setup the logger early, but we need the `TelemetryConfiguration` which still haven't been added to our DI container.
5868

5969
Luckily [from version 4.0.x of the `Serilog.Extensions.Hosting` we have the possibility to configure a bootstrap logger](https://nblumhardt.com/2020/10/bootstrap-logger/)
60-
to capture early errors, and then change it using DI dependant services once they are configured.
70+
to capture early errors, and then change it using DI-dependent services once they are configured.
6171

6272
```csharp
63-
// dotnet add package serilog.extensions.hosting -v 4.0.0-*
73+
// dotnet add package Serilog.Extensions.Hosting
6474
6575
public static class Program
6676
{
@@ -88,8 +98,8 @@ public static class Program
8898
Host.CreateDefaultBuilder(args)
8999
.UseSerilog((context, services, loggerConfiguration) => loggerConfiguration
90100
.WriteTo.ApplicationInsights(
91-
services.GetRequiredService<TelemetryConfiguration>(),
92-
TelemetryConverter.Traces))
101+
services.GetRequiredService<TelemetryConfiguration>(),
102+
TelemetryConverter.Traces))
93103
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
94104
}
95105
```
@@ -120,7 +130,7 @@ with [ReadFrom.Configuration(configuration)](https://github.com/serilog/serilog-
120130
"Args": {
121131
"connectionString": "[your connection string here]",
122132
"telemetryConverter":
123-
"Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
133+
"Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
124134
}
125135
}
126136
],
@@ -143,6 +153,10 @@ By default, trace telemetry submits:
143153
- **rendered message** in trace's standard *message* property.
144154
- **severity** in trace's standard *severityLevel* property.
145155
- **timestamp** in trace's standard *timestamp* property.
156+
- **operation id** from `OperationId` property, or the `LogEvent.TraceId` property.
157+
- **operation parent id** from `ParentSpanId` property.
158+
- **operation name** from `OperationName` property.
159+
- **component version** from `Version` property.
146160
- **messageTemplate** in *customDimensions*.
147161
- **custom log properties** as *customDimensions*.
148162

@@ -151,13 +165,21 @@ Event telemetry submits:
151165
- **message template** as *event name*.
152166
- **renderedMessage** in *customDimensions*.
153167
- **timestamp** in event's standard *timestamp* property.
168+
- **operation id** from `OperationId` property, or the `LogEvent.TraceId` property.
169+
- **operation parent id** from `ParentSpanId` property.
170+
- **operation name** from `OperationName` property.
171+
- **component version** from `Version` property.
154172
- **custom log properties** as *customDimensions*.
155173

156174
Exception telemetry submits:
157175

158176
- **exception** as standard AI exception.
159177
- **severity** in trace's standard *severityLevel* property.
160178
- **timestamp** in trace's standard *timestamp* property.
179+
- **operation id** from `OperationId` property, or the `LogEvent.TraceId` property.
180+
- **operation parent id** from `ParentSpanId` property.
181+
- **operation name** from `OperationName` property.
182+
- **component version** from `Version` property.
161183
- **custom log properties** as *customDimensions*.
162184

163185
> Note that **log context** properties are also included in *customDimensions* when Serilog is configured
@@ -232,26 +254,26 @@ private class CustomConverter : TraceTelemetryConverter
232254
telemetry.Context.User.Id = logEvent.Properties["UserId"].ToString();
233255
}
234256
// post-process the telemetry's context to contain the operation id
235-
if (logEvent.Properties.ContainsKey("operation_Id"))
257+
if (logEvent.Properties.ContainsKey("OperationId"))
236258
{
237-
telemetry.Context.Operation.Id = logEvent.Properties["operation_Id"].ToString();
259+
telemetry.Context.Operation.Id = logEvent.Properties["OperationId"].ToString();
238260
}
239261
// post-process the telemetry's context to contain the operation parent id
240-
if (logEvent.Properties.ContainsKey("operation_parentId"))
262+
if (logEvent.Properties.ContainsKey("ParentSpanId"))
241263
{
242-
telemetry.Context.Operation.ParentId = logEvent.Properties["operation_parentId"].ToString();
264+
telemetry.Context.Operation.ParentId = logEvent.Properties["ParentSpanId"].ToString();
243265
}
244266
// typecast to ISupportProperties so you can manipulate the properties as desired
245-
ISupportProperties propTelematry = (ISupportProperties)telemetry;
267+
ISupportProperties propTelemetry = (ISupportProperties)telemetry;
246268

247269
// find redundant properties
248-
var removeProps = new[] { "UserId", "operation_parentId", "operation_Id" };
249-
removeProps = removeProps.Where(prop => propTelematry.Properties.ContainsKey(prop)).ToArray();
270+
var removeProps = new[] { "UserId", "ParentSpanId", "OperationId" };
271+
removeProps = removeProps.Where(prop => propTelemetry.Properties.ContainsKey(prop)).ToArray();
250272

251273
foreach (var prop in removeProps)
252274
{
253275
// remove redundant properties
254-
propTelematry.Properties.Remove(prop);
276+
propTelemetry.Properties.Remove(prop);
255277
}
256278

257279
yield return telemetry;
@@ -270,7 +292,7 @@ instance, let's include `renderedMessage` in event telemetry:
270292
```csharp
271293
private class IncludeRenderedMessageConverter : EventTelemetryConverter
272294
{
273-
public override void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,
295+
public override void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,
274296
ISupportProperties telemetryProperties, IFormatProvider formatProvider)
275297
{
276298
base.ForwardPropertiesToTelemetryProperties(logEvent, telemetryProperties, formatProvider,
@@ -299,18 +321,17 @@ You can control when AI shall flush its messages, for example when your applicat
299321
// private TelemetryClient _telemetryClient;
300322
301323
// ...
302-
_telemetryClient = new TelemetryClient()
303-
{
304-
InstrumentationKey = "<My AI Instrumentation Key>"
305-
};
324+
var telemetryConfiguration = TelemetryConfiguration.CreateDefault();
325+
telemetryConfiguration.ConnectionString = "<your Application Insights connection string>";
326+
327+
_telemetryClient = new TelemetryClient(telemetryConfiguration);
306328
```
307329

308330
2) Use that custom `TelemetryClient` to initialize the Sink:
309331

310332
```csharp
311333
var log = new LoggerConfiguration()
312-
.WriteTo
313-
.ApplicationInsights(_telemetryClient, TelemetryConverter.Events)
334+
.WriteTo.ApplicationInsights(_telemetryClient, TelemetryConverter.Events)
314335
.CreateLogger();
315336
```
316337

@@ -325,16 +346,18 @@ _telemetryClient.Flush();
325346
326347
await Task.Delay(1000);
327348

328-
// or
349+
// or
329350
330351
System.Threading.Thread.Sleep(1000);
331352

332353
```
333354

334355
## Including Operation Id
335356

336-
Application Insight's operation id is pushed out if you set `operationId` LogEvent property. If it's present, AI's
337-
operation id will be overridden by the value from this property.
357+
Application Insight's operation id is set from the following sources in order of precedence:
358+
359+
1. `OperationId` LogEvent property
360+
2. `TraceId` LogEvent property
338361

339362
This can be set like so:
340363

@@ -346,22 +369,75 @@ public class OperationIdEnricher : ILogEventEnricher
346369
{
347370
if (logEvent.Properties.TryGetValue("RequestId", out var requestId))
348371
{
349-
logEvent.AddPropertyIfAbsent(new LogEventProperty("operationId", requestId));
372+
logEvent.AddPropertyIfAbsent(new LogEventProperty("OperationId", requestId));
350373
}
351374
}
352375
}
353376
```
354377

355378
## Including Version
356379

357-
Application Insight supports component version and is pushed out if you set `version` log event property. If it's
380+
Application Insight supports component version and is pushed out if you set `Version` log event property. If it's
358381
present, AI's operation version will include the value from this property.
359382

383+
## Using with SerilogTracing
384+
385+
[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.
386+
387+
The following `LogEvent` properties are mapped to Application Insights telemetry:
388+
389+
| LogEvent Property | Application Insights Telemetry | Notes |
390+
|-------------------|---------------------------------|-------|
391+
| `TraceId` | `Context.Operation.Id` | From TraceId captured in LogEvent |
392+
| `SpanId` | `Id` (for Request/Dependency telemetry) | From SpanId captured in LogEvent |
393+
| `ParentSpanId` | `Context.Operation.ParentId` | |
394+
| `OperationName` | `Context.Operation.Name` | |
395+
| `OperationId` | `Context.Operation.Id` | Overrides TraceId |
396+
| `Version` | `Context.Component.Version` | |
397+
398+
If present, `Baggage` is forwarded to Application Insights custom dimensions (`telemetry.Properties`).
399+
400+
Precedence for `Context.Operation.Id`: `OperationId` property > `TraceId` property (when both `OperationId` and `TraceId` properties are absent).
401+
402+
### Enriching from `Activity` (explicit opt-in)
403+
404+
This sink is designed to work well with Serilog's asynchronous/batched processing. To keep telemetry deterministic, adding `OperationName` and `Baggage` from the ambient `Activity` is an explicit opt-in: copy the values onto the `LogEvent` before it reaches the sink.
405+
406+
The sink includes an enricher that adds this by default. Enable it using the provided `Enrich` extension method:
407+
408+
```csharp
409+
Log.Logger = new LoggerConfiguration()
410+
.Enrich.WithActivityDetails(includeOperationName: true, includeBaggage: true)
411+
.WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces)
412+
.CreateLogger();
413+
```
414+
415+
## Upgrading to 5.0 (from 4.x)
416+
417+
This is a new major release (5.0). Notable changes:
418+
419+
- **OperationName and Baggage are opt-in:** they are only forwarded when present as `LogEvent` properties (use the built-in enricher above or your own enricher).
420+
- **Less redundancy in custom dimensions by default:** operation-related values are set on `ITelemetry.Context` and are not duplicated into `telemetry.Properties` unless enabled.
421+
422+
### `TelemetryConverterBase` constructor flags
423+
424+
Converters derived from `TelemetryConverterBase` can be configured to also include selected operation-related values in `telemetry.Properties` (custom dimensions):
425+
426+
```csharp
427+
public TelemetryConverterBase(
428+
bool includeOperationIdPropertyAsTelemetryProperty,
429+
bool includeParentSpanIdPropertyAsTelemetryProperty,
430+
bool includeOperationNamePropertyAsTelemetryProperty,
431+
bool includeVersionPropertyAsTelemetryProperty)
432+
```
433+
434+
If you previously relied on these values being present in `telemetry.Properties`, enable the relevant flags when constructing your converter, or post-process telemetry in a custom converter.
435+
360436
## Using with Azure Functions
361437

362438
Azure functions has out of the box integration with Application Insights, which automatically logs functions execution
363439
start, end, and any exception. Please refer to
364-
the [original documenation](https://docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring) on how to
440+
the [original documentation](https://docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring) on how to
365441
enable it.
366442

367443
This sink can enrich AI messages, preserving *operation_Id* and other context information which is *already provided by
@@ -377,7 +453,7 @@ namespace MyFunctions
377453
{
378454
public override void Configure(IFunctionsHostBuilder builder)
379455
{
380-
builder.Services.AddSingleton<ILoggerProvider>((sp) =>
456+
builder.Services.AddSingleton<ILoggerProvider>((sp) =>
381457
{
382458
Log.Logger = new LoggerConfiguration()
383459
.Enrich.FromLogContext()
@@ -390,7 +466,7 @@ namespace MyFunctions
390466
}
391467
```
392468

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

396472
See also: [Serilog Documentation](https://github.com/serilog/serilog/wiki)

src/Serilog.Sinks.ApplicationInsights/LoggerConfigurationApplicationInsightsExtensions.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Copyright 2016 Serilog Contributors
2-
//
2+
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
6-
//
6+
//
77
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
8+
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -49,8 +49,7 @@ public static LoggerConfiguration ApplicationInsights(
4949
var client = new TelemetryClient(telemetryConfiguration ?? TelemetryConfiguration.Active);
5050
#pragma warning restore CS0618
5151

52-
return loggerConfiguration.Sink(new ApplicationInsightsSink(client, telemetryConverter),
53-
restrictedToMinimumLevel, levelSwitch);
52+
return loggerConfiguration.ApplicationInsights(client, telemetryConverter, restrictedToMinimumLevel, levelSwitch);
5453
}
5554

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

75-
return loggerConfiguration.Sink(new ApplicationInsightsSink(client, telemetryConverter),
76-
restrictedToMinimumLevel, levelSwitch);
74+
return loggerConfiguration.ApplicationInsights(client, telemetryConverter, restrictedToMinimumLevel, levelSwitch);
7775
}
7876

7977
/// <summary>
@@ -93,11 +91,9 @@ public static LoggerConfiguration ApplicationInsights(
9391
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
9492
LoggingLevelSwitch levelSwitch = null)
9593
{
96-
return loggerConfiguration.Sink(new ApplicationInsightsSink(telemetryClient, telemetryConverter),
97-
restrictedToMinimumLevel, levelSwitch);
94+
return loggerConfiguration.Sink(new ApplicationInsightsSink(telemetryClient, telemetryConverter), restrictedToMinimumLevel, levelSwitch);
9895
}
9996

100-
10197
/// <summary>
10298
/// Adds a Serilog sink that writes <see cref="LogEvent">log events</see> to Microsoft Application Insights
10399
/// using a custom <see cref="ITelemetry" /> converter / constructor. Only use in rare cases when your application
@@ -125,7 +121,6 @@ public static LoggerConfiguration ApplicationInsights(
125121

126122
var client = new TelemetryClient(config);
127123

128-
return loggerConfiguration.Sink(new ApplicationInsightsSink(client, telemetryConverter),
129-
restrictedToMinimumLevel, levelSwitch);
124+
return loggerConfiguration.ApplicationInsights(client, telemetryConverter, restrictedToMinimumLevel, levelSwitch);
130125
}
131-
}
126+
}

0 commit comments

Comments
 (0)