generated from arcus-azure/arcus.github.template
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* FEAT: add Serilog assembly version enricher * FEAT: add Kubernetes enrichment of environment * FEAT: add Kubernetes enrichment of environment * PR-REVERT: version enricher * Update Arcus.Observability.Telemetry.Serilog.csproj * PR-FIX: use lower version of named arguments * PR-SUG: make Kubernetes environment information configurable * PR-SUG: simplify Kubernetes variable configuration * PR-FIX: use correct log property names * PR-SUG: revert configurability of the Kubernetes variables and properties * Update telemetry-enrichment.md
- Loading branch information
1 parent
2b94a81
commit 3ee7f98
Showing
4 changed files
with
247 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
src/Arcus.Observability.Telemetry.Serilog/KubernetesEnricher.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using System; | ||
using Serilog.Core; | ||
using Serilog.Events; | ||
|
||
namespace Arcus.Observability.Telemetry.Serilog | ||
{ | ||
/// <summary> | ||
/// Enrichment on log events that automatically adds Kubernetes information from the environment. | ||
/// </summary> | ||
public class KubernetesEnricher : ILogEventEnricher | ||
{ | ||
private const string NodeNameVariable = "KUBERNETES_NODE_NAME", | ||
PodNameVariable = "KUBERNETES_POD_NAME", | ||
NamespaceVariable = "KUBERNETES_NAMESPACE"; | ||
|
||
private const string NodeNameProperty = "NodeName", | ||
PodNameProperty = "PodName", | ||
NamespaceProperty = "Namespace"; | ||
|
||
/// <summary> | ||
/// Enrich the log event. | ||
/// </summary> | ||
/// <param name="logEvent">The log event to enrich.</param> | ||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param> | ||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) | ||
{ | ||
EnrichEnvironmentVariable(NodeNameVariable, NodeNameProperty, logEvent, propertyFactory); | ||
EnrichEnvironmentVariable(PodNameVariable, PodNameProperty, logEvent, propertyFactory); | ||
EnrichEnvironmentVariable(NamespaceVariable, NamespaceProperty, logEvent, propertyFactory); | ||
} | ||
|
||
private static void EnrichEnvironmentVariable( | ||
string environmentVariableName, | ||
string logPropertyName, | ||
LogEvent logEvent, | ||
ILogEventPropertyFactory propertyFactory) | ||
{ | ||
string value = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); | ||
if (!String.IsNullOrWhiteSpace(value)) | ||
{ | ||
LogEventProperty property = propertyFactory.CreateProperty(logPropertyName, value); | ||
logEvent.AddPropertyIfAbsent(property); | ||
} | ||
} | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
src/Arcus.Observability.Tests.Unit/Telemetry/KubernetesEnricherTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using Arcus.Observability.Telemetry.Serilog; | ||
using Serilog; | ||
using Serilog.Events; | ||
using Xunit; | ||
|
||
namespace Arcus.Observability.Tests.Unit.Telemetry | ||
{ | ||
[Trait("Category", "Unit")] | ||
public class KubernetesEnricherTests | ||
{ | ||
[Fact] | ||
public void LogEvent_WithKubernetesEnricher_HasEnvironmentInformation() | ||
{ | ||
// Arrange | ||
const string kubernetesNodeName = "KUBERNETES_NODE_NAME", | ||
kubernetesPodName = "KUBERNETES_POD_NAME", | ||
kubernetesNamespace = "KUBERNETES_NAMESPACE"; | ||
|
||
string nodeName = $"node-{Guid.NewGuid()}"; | ||
string podName = $"pod-{Guid.NewGuid()}"; | ||
string @namespace = $"namespace-{Guid.NewGuid()}"; | ||
|
||
var spy = new InMemoryLogSink(); | ||
ILogger logger = new LoggerConfiguration() | ||
.Enrich.With<KubernetesEnricher>() | ||
.WriteTo.Sink(spy) | ||
.CreateLogger(); | ||
|
||
using (TemporaryEnvironmentVariable.Create(kubernetesNodeName, nodeName)) | ||
using (TemporaryEnvironmentVariable.Create(kubernetesPodName, podName)) | ||
using (TemporaryEnvironmentVariable.Create(kubernetesNamespace, @namespace)) | ||
{ | ||
// Act | ||
logger.Information("This log event should be enriched with Kubernetes information"); | ||
} | ||
|
||
// Assert | ||
LogEvent logEvent = Assert.Single(spy.CurrentLogEmits); | ||
Assert.NotNull(logEvent); | ||
|
||
ContainsLogProperty(logEvent, "NodeName", nodeName); | ||
ContainsLogProperty(logEvent, "PodName", podName); | ||
ContainsLogProperty(logEvent, "Namespace", @namespace); | ||
} | ||
|
||
[Fact] | ||
public void LogEventWithNodeNameProperty_WithKubernetesEnricher_HasEnvironmentInformation() | ||
{ | ||
// Arrange | ||
string expectedNodeName = $"node-{Guid.NewGuid()}"; | ||
string ignoredNodeName = $"node-{Guid.NewGuid()}"; | ||
|
||
var spy = new InMemoryLogSink(); | ||
ILogger logger = new LoggerConfiguration() | ||
.Enrich.With<KubernetesEnricher>() | ||
.WriteTo.Sink(spy) | ||
.CreateLogger(); | ||
|
||
using (TemporaryEnvironmentVariable.Create("KUBERNETES_NODE_NAME", ignoredNodeName)) | ||
{ | ||
// Act | ||
logger.Information("This log even already has a Kubernetes NodeName {NodeName}", expectedNodeName); | ||
} | ||
|
||
// Assert | ||
LogEvent logEvent = Assert.Single(spy.CurrentLogEmits); | ||
Assert.NotNull(logEvent); | ||
|
||
ContainsLogProperty(logEvent, "NodeName", expectedNodeName); | ||
Assert.DoesNotContain(logEvent.Properties, prop => prop.Key == "PodName"); | ||
Assert.DoesNotContain(logEvent.Properties, prop => prop.Key == "Namespace"); | ||
} | ||
|
||
[Fact] | ||
public void LogEventWithPodNameProperty_WithKubernetesEnricher_HasEnvironmentInformation() | ||
{ | ||
// Arrange | ||
string expectedPodName = $"pod-{Guid.NewGuid()}"; | ||
string ignoredPodName = $"pod-{Guid.NewGuid()}"; | ||
|
||
var spy = new InMemoryLogSink(); | ||
ILogger logger = new LoggerConfiguration() | ||
.Enrich.With<KubernetesEnricher>() | ||
.WriteTo.Sink(spy) | ||
.CreateLogger(); | ||
|
||
using (TemporaryEnvironmentVariable.Create("KUBERNETES_POD_NAME", ignoredPodName)) | ||
{ | ||
// Act | ||
logger.Information("This log even already has a Kubernetes PodName {PodName}", expectedPodName); | ||
} | ||
|
||
// Assert | ||
LogEvent logEvent = Assert.Single(spy.CurrentLogEmits); | ||
Assert.NotNull(logEvent); | ||
|
||
ContainsLogProperty(logEvent, "PodName", expectedPodName); | ||
Assert.DoesNotContain(logEvent.Properties, prop => prop.Key == "NodeName"); | ||
Assert.DoesNotContain(logEvent.Properties, prop => prop.Key == "Namespace"); | ||
} | ||
|
||
[Fact] | ||
public void LogEventWithNamespaceProperty_WithKubernetesEnricher_HasEnvironmentInformation() | ||
{ | ||
// Arrange | ||
string expectedNamespace = $"namespace-{Guid.NewGuid()}"; | ||
string ignoredNamespace = $"namespace-{Guid.NewGuid()}"; | ||
|
||
var spy = new InMemoryLogSink(); | ||
ILogger logger = new LoggerConfiguration() | ||
.Enrich.With<KubernetesEnricher>() | ||
.WriteTo.Sink(spy) | ||
.CreateLogger(); | ||
|
||
using (TemporaryEnvironmentVariable.Create("KUBERNETES_NAMESPACE", ignoredNamespace)) | ||
{ | ||
// Act | ||
logger.Information("This log even already has a Kubernetes Namespace {Namespace}", expectedNamespace); | ||
} | ||
|
||
// Assert | ||
LogEvent logEvent = Assert.Single(spy.CurrentLogEmits); | ||
Assert.NotNull(logEvent); | ||
|
||
ContainsLogProperty(logEvent, "Namespace", expectedNamespace); | ||
Assert.DoesNotContain(logEvent.Properties, prop => prop.Key == "NodeName"); | ||
Assert.DoesNotContain(logEvent.Properties, prop => prop.Key == "PodName"); | ||
} | ||
|
||
private static void ContainsLogProperty(LogEvent logEvent, string name, string expectedValue) | ||
{ | ||
(string key, LogEventPropertyValue actual) = | ||
Assert.Single(logEvent.Properties, prop => prop.Key == name); | ||
|
||
string actualValue = actual.ToString().Trim('\"'); | ||
Assert.Equal(expectedValue, actualValue); | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/Arcus.Observability.Tests.Unit/Telemetry/TemporaryEnvironmentVariable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System; | ||
using GuardNet; | ||
|
||
namespace Arcus.Observability.Tests.Unit.Telemetry | ||
{ | ||
/// <summary> | ||
/// Represents a temporary environment variable that gets removed when the model gets disposed. | ||
/// </summary> | ||
public class TemporaryEnvironmentVariable : IDisposable | ||
{ | ||
private readonly string _name; | ||
|
||
private TemporaryEnvironmentVariable(string name) | ||
{ | ||
Guard.NotNullOrWhitespace(name, nameof(name)); | ||
|
||
_name = name; | ||
} | ||
|
||
public static TemporaryEnvironmentVariable Create(string name, string value) | ||
{ | ||
Guard.NotNullOrWhitespace(name, nameof(name)); | ||
Guard.NotNullOrWhitespace(value, nameof(value)); | ||
|
||
Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process); | ||
return new TemporaryEnvironmentVariable(name); | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
Environment.SetEnvironmentVariable(_name, value: null, target: EnvironmentVariableTarget.Process); | ||
} | ||
} | ||
} |