Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: add Kubernetes enricher #28

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
<PackageReference Include="Serilog" Version="2.9.0" />
</ItemGroup>

</Project>
</Project>
34 changes: 34 additions & 0 deletions src/Arcus.Observability.Telemetry.Serilog/KubernetesEnricher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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
{
/// <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("KUBERNETES_NODE_NAME", logEvent, propertyFactory);
tomkerkhove marked this conversation as resolved.
Show resolved Hide resolved
EnrichEnvironmentVariable("KUBERNETES_POD_NAME", logEvent, propertyFactory);
EnrichEnvironmentVariable("KUBERNETES_NAMESPACE", logEvent, propertyFactory);
}

private static void EnrichEnvironmentVariable(string name, LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
string value = Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
if (!String.IsNullOrWhiteSpace(value))
{
LogEventProperty property = propertyFactory.CreateProperty(name, value);
tomkerkhove marked this conversation as resolved.
Show resolved Hide resolved
logEvent.AddPropertyIfAbsent(property);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using Arcus.Observability.Telemetry.Serilog;
using Serilog;
using Serilog.Events;
using Xunit;

namespace Arcus.Observability.Tests.Unit.Telemetry
{
[Trait("Category", "Unit")]
public class KubernetesEnricherTests
tomkerkhove marked this conversation as resolved.
Show resolved Hide resolved
{
[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();
var 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, kubernetesNodeName, nodeName);
ContainsLogProperty(logEvent, kubernetesPodName, podName);
ContainsLogProperty(logEvent, kubernetesNamespace, @namespace);
}

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);
}
}
}
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, EnvironmentVariableTarget.Process);
}
}
}