Skip to content

Commit 95f9e4f

Browse files
committed
feat: Add support for case-insensitive property name lookups in telemetry converters
1 parent 49f1403 commit 95f9e4f

12 files changed

Lines changed: 126 additions & 83 deletions

File tree

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ By default, trace telemetry submits:
153153
- **rendered message** in trace's standard *message* property.
154154
- **severity** in trace's standard *severityLevel* property.
155155
- **timestamp** in trace's standard *timestamp* property.
156-
- **operation id** from `operationId` property, or the `LogEvent.TraceId` property.
156+
- **operation id** from `OperationId` property, or the `LogEvent.TraceId` property.
157157
- **operation parent id** from `ParentSpanId` property.
158158
- **operation name** from `OperationName` property.
159-
- **component version** from `version` property.
159+
- **component version** from `Version` property.
160160
- **messageTemplate** in *customDimensions*.
161161
- **custom log properties** as *customDimensions*.
162162

@@ -165,21 +165,21 @@ Event telemetry submits:
165165
- **message template** as *event name*.
166166
- **renderedMessage** in *customDimensions*.
167167
- **timestamp** in event's standard *timestamp* property.
168-
- **operation id** from `operationId` property, or the `LogEvent.TraceId` property.
168+
- **operation id** from `OperationId` property, or the `LogEvent.TraceId` property.
169169
- **operation parent id** from `ParentSpanId` property.
170170
- **operation name** from `OperationName` property.
171-
- **component version** from `version` property.
171+
- **component version** from `Version` property.
172172
- **custom log properties** as *customDimensions*.
173173

174174
Exception telemetry submits:
175175

176176
- **exception** as standard AI exception.
177177
- **severity** in trace's standard *severityLevel* property.
178178
- **timestamp** in trace's standard *timestamp* property.
179-
- **operation id** from `operationId` property, or the `LogEvent.TraceId` property.
179+
- **operation id** from `OperationId` property, or the `LogEvent.TraceId` property.
180180
- **operation parent id** from `ParentSpanId` property.
181181
- **operation name** from `OperationName` property.
182-
- **component version** from `version` property.
182+
- **component version** from `Version` property.
183183
- **custom log properties** as *customDimensions*.
184184

185185
> Note that **log context** properties are also included in *customDimensions* when Serilog is configured
@@ -356,7 +356,7 @@ System.Threading.Thread.Sleep(1000);
356356

357357
Application Insight's operation id is set from the following sources in order of precedence:
358358

359-
1. `operationId` LogEvent property
359+
1. `OperationId` LogEvent property
360360
2. `TraceId` LogEvent property
361361

362362
This can be set like so:
@@ -377,7 +377,7 @@ public class OperationIdEnricher : ILogEventEnricher
377377

378378
## Including Version
379379

380-
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
381381
present, AI's operation version will include the value from this property.
382382

383383
## Using with SerilogTracing
@@ -392,12 +392,12 @@ The following `LogEvent` properties are mapped to Application Insights telemetry
392392
| `SpanId` | `Id` (for Request/Dependency telemetry) | From SpanId captured in LogEvent |
393393
| `ParentSpanId` | `Context.Operation.ParentId` | |
394394
| `OperationName` | `Context.Operation.Name` | |
395-
| `operationId` | `Context.Operation.Id` | Overrides TraceId |
396-
| `version` | `Context.Component.Version` | |
395+
| `OperationId` | `Context.Operation.Id` | Overrides TraceId |
396+
| `Version` | `Context.Component.Version` | |
397397

398398
If present, `Baggage` is forwarded to Application Insights custom dimensions (`telemetry.Properties`).
399399

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

402402
### Enriching from `Activity` (explicit opt-in)
403403

src/Serilog.Sinks.ApplicationInsights/Sinks/ApplicationInsights/TelemetryConverters/EventTelemetryConverter.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,25 @@ namespace Serilog.Sinks.ApplicationInsights.TelemetryConverters;
1010

1111
public class EventTelemetryConverter : TelemetryConverterBase
1212
{
13-
/// <inheritdoc cref="EventTelemetryConverter(bool, bool, bool, bool)"/>
13+
/// <inheritdoc cref="EventTelemetryConverter(bool, bool, bool, bool, bool)"/>
1414
public EventTelemetryConverter()
15-
: this(false, false, false, false)
15+
: this(false, false, false, false, true)
1616
{
1717
}
1818

19-
/// <inheritdoc cref="TelemetryConverterBase(bool, bool, bool, bool)"/>
19+
/// <inheritdoc cref="TelemetryConverterBase(bool, bool, bool, bool, bool)"/>
2020
public EventTelemetryConverter(
2121
bool includeOperationIdPropertyAsTelemetryProperty,
2222
bool includeParentSpanIdPropertyAsTelemetryProperty,
2323
bool includeOperationNamePropertyAsTelemetryProperty,
24-
bool includeVersionPropertyAsTelemetryProperty)
24+
bool includeVersionPropertyAsTelemetryProperty,
25+
bool ignorePropertyNameCase)
2526
: base(
2627
includeOperationIdPropertyAsTelemetryProperty,
2728
includeParentSpanIdPropertyAsTelemetryProperty,
2829
includeOperationNamePropertyAsTelemetryProperty,
29-
includeVersionPropertyAsTelemetryProperty)
30+
includeVersionPropertyAsTelemetryProperty,
31+
ignorePropertyNameCase)
3032
{
3133
}
3234

src/Serilog.Sinks.ApplicationInsights/Sinks/ApplicationInsights/TelemetryConverters/TelemetryConverterBase.cs

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ public abstract class TelemetryConverterBase : ITelemetryConverter
6161
readonly bool _includeParentSpanIdPropertyAsTelemetryProperty;
6262
readonly bool _includeOperationNamePropertyAsTelemetryProperty;
6363
readonly bool _includeVersionPropertyAsTelemetryProperty;
64+
readonly bool _ignorePropertyNameCase;
6465

6566
/// <summary>
6667
/// Creates an instance of <see cref="TelemetryConverterBase" /> using default value formatter (
6768
/// <see cref="ApplicationInsightsJsonValueFormatter" />).
6869
/// </summary>
6970
public TelemetryConverterBase()
70-
: this(false, false, false, false)
71+
: this(false, false, false, false, true)
7172
{
7273
}
7374

@@ -91,18 +92,29 @@ public TelemetryConverterBase()
9192
/// if set to <c>true</c> the <see cref="VersionProperty" /> is added to the
9293
/// telemetry properties. Otherwise it is only set as <c>ITelemetry.Context.Component.Version</c>.
9394
/// </param>
95+
/// <param name="ignorePropertyNameCase">
96+
/// <para>
97+
/// if set to <c>true</c> property name lookups are case insensitive.
98+
/// </para>
99+
/// <para>
100+
/// The main use case set it to <c>true</c> for maximum compatibility with various logging frameworks
101+
/// but it has a performance impact when there are many properties on the <see cref="LogEvent" />.
102+
/// </para>
103+
/// </param>
94104
public TelemetryConverterBase(
95105
bool includeOperationIdPropertyAsTelemetryProperty,
96106
bool includeParentSpanIdPropertyAsTelemetryProperty,
97107
bool includeOperationNamePropertyAsTelemetryProperty,
98-
bool includeVersionPropertyAsTelemetryProperty)
108+
bool includeVersionPropertyAsTelemetryProperty,
109+
bool ignorePropertyNameCase)
99110
{
100111
ValueFormatter = new ApplicationInsightsJsonValueFormatter();
101112

102113
_includeOperationIdPropertyAsTelemetryProperty = includeOperationIdPropertyAsTelemetryProperty;
103114
_includeParentSpanIdPropertyAsTelemetryProperty = includeParentSpanIdPropertyAsTelemetryProperty;
104115
_includeOperationNamePropertyAsTelemetryProperty = includeOperationNamePropertyAsTelemetryProperty;
105116
_includeVersionPropertyAsTelemetryProperty = includeVersionPropertyAsTelemetryProperty;
117+
_ignorePropertyNameCase = ignorePropertyNameCase;
106118
}
107119

108120
#pragma warning disable CS1591
@@ -193,33 +205,27 @@ public void ForwardPropertiesToTelemetryProperties(LogEvent logEvent,
193205
telemetryProperties.Properties.Add(TelemetryPropertiesMessageTemplate, logEvent.MessageTemplate.Text);
194206

195207
if (telemetryProperties is ITelemetry telemetry)
196-
{
197208
PopulateTelemetryFromLogEvent(logEvent, telemetry);
198209

199-
if (telemetry.Context?.Component != null
200-
&& logEvent.Properties.TryGetValue(VersionProperty, out var version))
201-
telemetry.Context.Component.Version = version.ToString().Trim('\"');
202-
}
203-
204210
var baggageWasForwarded = ForwardActivityBaggage(logEvent, telemetryProperties, formatProvider);
205211
ForwardSimpleProperties(logEvent, telemetryProperties, baggageWasForwarded);
206212
}
207213

208-
private static void PopulateTelemetryFromLogEvent(LogEvent logEvent, ITelemetry telemetry)
214+
private void PopulateTelemetryFromLogEvent(LogEvent logEvent, ITelemetry telemetry)
209215
{
210216
// Operation.Id (TraceId)
211-
if (TrySetOperationIdFromLogEvent(logEvent, telemetry, out var operationId))
217+
if (TryGetOperationIdFromLogEvent(logEvent, out var operationId))
212218
telemetry.Context.Operation.Id = operationId;
213219
else if (logEvent.TraceId is ActivityTraceId traceId)
214220
telemetry.Context.Operation.Id = traceId.ToHexString();
215221

216222
// Operation.ParentId (ParentSpanId)
217-
if (logEvent.Properties.TryGetValue(ParentSpanIdProperty, out var parentSpanIdProp))
218-
telemetry.Context.Operation.ParentId = parentSpanIdProp.ToString().Trim('"');
223+
if (TryGetParentSpanIdFromLogEvent(logEvent, out var parentSpanId))
224+
telemetry.Context.Operation.ParentId = parentSpanId;
219225

220226
// Operation.Name (OperationName)
221-
if (logEvent.Properties.TryGetValue(OperationNameProperty, out var operationNameProp))
222-
telemetry.Context.Operation.Name = operationNameProp.ToString().Trim('"');
227+
if (TryGetOperationNameFromLogEvent(logEvent, out var operationName))
228+
telemetry.Context.Operation.Name = operationName;
223229

224230
// Set Id for RequestTelemetry and DependencyTelemetry
225231
if (logEvent.SpanId is ActivitySpanId spanId)
@@ -229,31 +235,51 @@ private static void PopulateTelemetryFromLogEvent(LogEvent logEvent, ITelemetry
229235
else if (telemetry is DependencyTelemetry dep)
230236
dep.Id = spanId.ToHexString();
231237
}
238+
239+
if (telemetry.Context?.Component != null
240+
&& TryGetVersionFromLogEvent(logEvent, out var version))
241+
telemetry.Context.Component.Version = version;
232242
}
233243

234-
private static bool TrySetOperationIdFromLogEvent(LogEvent logEvent, ITelemetry telemetry, out string operationId)
244+
private bool TryGetOperationIdFromLogEvent(LogEvent logEvent, out string operationId)
245+
=> TryGetPropertyFromLogEventIgnoreCase(logEvent, OperationIdProperty, out operationId);
246+
247+
private bool TryGetParentSpanIdFromLogEvent(LogEvent logEvent, out string operationId)
248+
=> TryGetPropertyFromLogEventIgnoreCase(logEvent, ParentSpanIdProperty, out operationId);
249+
250+
private bool TryGetOperationNameFromLogEvent(LogEvent logEvent, out string operationId)
251+
=> TryGetPropertyFromLogEventIgnoreCase(logEvent, OperationNameProperty, out operationId);
252+
253+
private bool TryGetVersionFromLogEvent(LogEvent logEvent, out string version)
254+
=> TryGetPropertyFromLogEventIgnoreCase(logEvent, VersionProperty, out version);
255+
256+
private bool TryGetPropertyFromLogEventIgnoreCase(LogEvent logEvent, string propertyName, out string value)
235257
{
236-
operationId = null;
237-
if (logEvent.Properties.TryGetValue(OperationIdProperty, out var operationIdProp))
258+
value = null;
259+
if (_ignorePropertyNameCase)
260+
{
261+
value = logEvent.Properties
262+
.FirstOrDefault(p => string.Equals(p.Key, propertyName, StringComparison.OrdinalIgnoreCase))
263+
.Value?
264+
.ToString();
265+
}
266+
else if (logEvent.Properties.TryGetValue(propertyName, out var operationIdProp))
238267
{
239-
operationId = operationIdProp.ToString();
268+
value = operationIdProp.ToString();
240269
}
241270
else
242271
{
243-
operationId = logEvent.Properties
244-
.FirstOrDefault(p => string.Equals(p.Key, OperationIdProperty, StringComparison.OrdinalIgnoreCase))
245-
.Value?
246-
.ToString();
272+
return false;
247273
}
248274

249-
if (string.IsNullOrEmpty(operationId))
275+
if (string.IsNullOrEmpty(value))
250276
return false;
251277

252-
operationId = operationId.Trim('\"');
278+
value = value.Trim('\"');
253279
return true;
254280
}
255281

256-
private static bool ForwardActivityBaggage(LogEvent logEvent, ISupportProperties telemetryProperties, IFormatProvider formatProvider)
282+
private bool ForwardActivityBaggage(LogEvent logEvent, ISupportProperties telemetryProperties, IFormatProvider formatProvider)
257283
{
258284
if (!logEvent.Properties.TryGetValue(BaggageProperty, out var baggageProp)
259285
|| baggageProp is not StructureValue baggageStructure)
@@ -282,15 +308,16 @@ private void ForwardSimpleProperties(LogEvent logEvent, ISupportProperties telem
282308
var skipParentSpanId = !_includeParentSpanIdPropertyAsTelemetryProperty;
283309
var skipOperationName = !_includeOperationNamePropertyAsTelemetryProperty;
284310
var skipVersion = !_includeVersionPropertyAsTelemetryProperty;
311+
var stringComparison = _ignorePropertyNameCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
285312

286313
foreach (var property in logEvent.Properties)
287314
{
288315
if (property.Value is null) continue;
289-
if (skipOperationId && OperationIdProperty.Equals(property.Key, StringComparison.Ordinal)) continue;
290-
if (skipParentSpanId && ParentSpanIdProperty.Equals(property.Key, StringComparison.Ordinal)) continue;
291-
if (skipOperationName && OperationNameProperty.Equals(property.Key, StringComparison.Ordinal)) continue;
292-
if (skipVersion && VersionProperty.Equals(property.Key, StringComparison.Ordinal)) continue;
293-
if (skipBaggage && BaggageProperty.Equals(property.Key, StringComparison.Ordinal)) continue;
316+
if (skipOperationId && OperationIdProperty.Equals(property.Key, stringComparison)) continue;
317+
if (skipParentSpanId && ParentSpanIdProperty.Equals(property.Key, stringComparison)) continue;
318+
if (skipOperationName && OperationNameProperty.Equals(property.Key, stringComparison)) continue;
319+
if (skipVersion && VersionProperty.Equals(property.Key, stringComparison)) continue;
320+
if (skipBaggage && BaggageProperty.Equals(property.Key, stringComparison)) continue;
294321
if (telemetryProperties.Properties.ContainsKey(property.Key)) continue;
295322

296323
ValueFormatter.Format(property.Key, property.Value, telemetryProperties.Properties);

src/Serilog.Sinks.ApplicationInsights/Sinks/ApplicationInsights/TelemetryConverters/TraceTelemetryConverter.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,25 @@ public class TraceTelemetryConverter : TelemetryConverterBase
1414
{
1515
static readonly MessageTemplateTextFormatter MessageTemplateTextFormatter = new("{Message:lj}");
1616

17-
/// <inheritdoc cref="EventTelemetryConverter(bool, bool, bool, bool)"/>
17+
/// <inheritdoc cref="EventTelemetryConverter(bool, bool, bool, bool, bool)"/>
1818
public TraceTelemetryConverter()
19-
: this(false, false, false, false)
19+
: this(false, false, false, false, true)
2020
{
2121
}
2222

23-
/// <inheritdoc cref="TelemetryConverterBase(bool, bool, bool, bool)"/>
23+
/// <inheritdoc cref="TelemetryConverterBase(bool, bool, bool, bool, bool)"/>
2424
public TraceTelemetryConverter(
2525
bool includeOperationIdPropertyAsTelemetryProperty,
2626
bool includeParentSpanIdPropertyAsTelemetryProperty,
2727
bool includeOperationNamePropertyAsTelemetryProperty,
28-
bool includeVersionPropertyAsTelemetryProperty)
28+
bool includeVersionPropertyAsTelemetryProperty,
29+
bool ignorePropertyNameCase)
2930
: base(
3031
includeOperationIdPropertyAsTelemetryProperty,
3132
includeParentSpanIdPropertyAsTelemetryProperty,
3233
includeOperationNamePropertyAsTelemetryProperty,
33-
includeVersionPropertyAsTelemetryProperty)
34+
includeVersionPropertyAsTelemetryProperty,
35+
ignorePropertyNameCase)
3436
{
3537
}
3638

test/Serilog.Sinks.ApplicationInsights.Tests/TelemetryConverters/Event/IncludeOperationIdTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Serilog.Sinks.ApplicationInsights.Tests.TelemetryConverters.Event;
66
public class IncludeOperationIdTest : ApplicationInsightsTest
77
{
88
public IncludeOperationIdTest()
9-
: base(new EventTelemetryConverter(true, false, false, false), true, true)
9+
: base(new EventTelemetryConverter(true, false, false, false, true), true, true)
1010
{
1111
}
1212

test/Serilog.Sinks.ApplicationInsights.Tests/TelemetryConverters/Event/IncludeOperationNameTest.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ namespace Serilog.Sinks.ApplicationInsights.Tests.TelemetryConverters.Event;
66
public class IncludeOperationNameTest : ApplicationInsightsTest
77
{
88
public IncludeOperationNameTest()
9-
: base(new EventTelemetryConverter(false, false, true, false), true, true)
9+
: base(new EventTelemetryConverter(false, false, true, false, true), true, true)
1010
{
1111
}
1212

13-
[Fact]
14-
public void OperationIdIsSetAsTraceProperty()
13+
[Theory]
14+
[InlineData("Hello, {operationName}!", "operationName", "foo-operation-name")]
15+
[InlineData("Hello, {OperationName}!", "OperationName", "bar-operation-name")]
16+
public void OperationIdIsSetAsTraceProperty(string pattern, string operationNameKey, string expectedOperationName)
1517
{
1618
using var activity = new System.Diagnostics.Activity("TestActivity");
1719
activity.Start();
1820

19-
Logger.Information("Hello, {OperationName}!", "foo-operation-name");
21+
Logger.Information(pattern, expectedOperationName);
2022

21-
Assert.Equal("foo-operation-name", LastSubmittedEventTelemetry.Properties["OperationName"]);
23+
Assert.Equal(expectedOperationName, LastSubmittedEventTelemetry.Properties[operationNameKey]);
2224
}
2325
}

test/Serilog.Sinks.ApplicationInsights.Tests/TelemetryConverters/Event/IncludeParentSpanIdTest.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ namespace Serilog.Sinks.ApplicationInsights.Tests.TelemetryConverters.Event;
66
public class IncludeSpanIdTest : ApplicationInsightsTest
77
{
88
public IncludeSpanIdTest()
9-
: base(new EventTelemetryConverter(false, true, false, false), true, true)
9+
: base(new EventTelemetryConverter(false, true, false, false, true), true, true)
1010
{
1111
}
1212

13-
[Fact]
14-
public void OperationIdIsSetAsTraceProperty()
13+
[Theory]
14+
[InlineData("Hello, {parentSpanId}!", "parentSpanId", "foo-parent-span-id")]
15+
[InlineData("Hello, {ParentSpanId}!", "ParentSpanId", "bar-parent-span-id")]
16+
public void ParentSpanIdIsSetAsTraceProperty(string pattern, string parentSpanIdKey, string expectedParentSpanId)
1517
{
1618
using var activity = new System.Diagnostics.Activity("TestActivity");
1719
activity.Start();
1820

19-
Logger.Information("Hello, {ParentSpanId}!", "foo-parent-span-id");
21+
Logger.Information(pattern, expectedParentSpanId);
2022

21-
Assert.Equal("foo-parent-span-id", LastSubmittedEventTelemetry.Properties["ParentSpanId"]);
23+
Assert.Equal(expectedParentSpanId, LastSubmittedEventTelemetry.Properties[parentSpanIdKey]);
2224
}
2325
}

test/Serilog.Sinks.ApplicationInsights.Tests/TelemetryConverters/Event/IncludeVersionTest.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ namespace Serilog.Sinks.ApplicationInsights.Tests.TelemetryConverters.Event;
66
public class IncludeVersionTest : ApplicationInsightsTest
77
{
88
public IncludeVersionTest()
9-
: base(new EventTelemetryConverter(false, false, false, true), true, true)
9+
: base(new EventTelemetryConverter(false, false, false, true, true), true, true)
1010
{
1111
}
1212

13-
[Fact]
14-
public void OperationIdIsSetAsTraceProperty()
13+
[Theory]
14+
[InlineData("Hello, {version}!", "version", "v1.3.3.7")]
15+
[InlineData("Hello, {Version}!", "Version", "v4.2.0")]
16+
public void VersionSetAsTraceProperty(string pattern, string versionKey, string expectedVersion)
1517
{
1618
using var activity = new System.Diagnostics.Activity("TestActivity");
1719
activity.Start();
1820

19-
Logger.Information("Hello, {version}!", "v1.3.3.7");
21+
Logger.Information(pattern, expectedVersion);
2022

21-
Assert.Equal("v1.3.3.7", LastSubmittedEventTelemetry.Properties["version"]);
23+
Assert.Equal(expectedVersion, LastSubmittedEventTelemetry.Properties[versionKey]);
2224
}
2325
}

0 commit comments

Comments
 (0)