Skip to main content

Logging, Tracing, and OpenTelemetry

Logging with ILogger and Scopes

This project uses Microsoft's ILogger interface for logging. It provides a standardized and extensible way to capture events within the application.

Using scopes enables hierarchical organization of log messages and allows contextual information to be attached to each entry.

ILogger Basics

The ILogger interface is part of Microsoft.Extensions.Logging and provides methods to log messages at various severity levels (e.g., Information, Warning, Error).

Logging can be enabled using either WebApplication.CreateBuilder, Host.CreateDefaultBuilder, or the AddLogging extension method on the IServiceCollection.

You can log from your code by injecting ILogger<MyEntityController> (or a similar type) into your component.

Using Scopes

Scopes define a logical boundary in which all log entries are automatically enriched with contextual metadata. This is especially useful for correlating logs related to a specific request or operation.

By default, the ResourceWatcher starts a new scope for every watch event it processes. Each scope includes:

  • EventType: Type of the received watch event (Added, Modified, Deleted, Error, Bookmark)
  • Kind: Custom Resource Definition (CRD) kind
  • Namespace: CRD namespace
  • Name: CRD name
  • ResourceVersion: CRD resource version

You can create additional scopes in your code using logger.BeginScope(state), where state can be either a string or a custom object.

To include scopes in the logging output, they must be explicitly enabled either via configuration or code:

appsettings.json:

"Logging": {
"Console": {
"FormatterName": "Simple",
"FormatterOptions": {
"IncludeScopes": true
}
},
"LogLevel": {
"Default": "Information",
"KubeOps": "Trace"
}
}

Programmatic configuration:

builder
.ConfigureLogging((hostBuilderContext, loggingBuilder) =>
{
loggingBuilder
.AddSimpleConsole(options => options.IncludeScopes = true);
});
tip

To enable scopes with OpenTelemetry, configure it as follows:

"OpenTelemetry": {
"IncludeScopes": true,
"ParseStateValues": true,
"IncludeFormattedMessage": true
}

The scope state must be an IReadOnlyDictionary<string, object?> to ensure correct serialization and inclusion in log entries.

Tracing with System.Diagnostics and ActivitySource

For distributed tracing, this project uses System.Diagnostics in combination with ActivitySource. Activities can be started using ActivitySource.StartActivity.

The operator registers an ActivitySource instance with the operator name in the dependency injection (DI) container. To use a custom ActivitySource, simply register your own, the DI will provide the last registered instance when requested.

tip

You can configure the operator name (and thus the ActivitySource name) via OperatorSettings:

const string OperatorName = "my-operator";

builder
.Services
.AddKubernetesOperator(settings => settings.Name = OperatorName);

If you're using OpenTelemetry, tracing must be explicitly configured in code. Make sure to add the same source name used when creating the ActivitySource.

Also, create a ResourceBuilder to name your service properly in trace output:

builder.Services
.AddOpenTelemetry()
.WithTracing(tracerProviderBuilder =>
tracerProviderBuilder
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: OperatorName, serviceVersion: "1.0.0"))
.AddSource(OperatorName));

OpenTelemetry Configuration for Azure Logging

To use OpenTelemetry with Azure, it is recommended to adopt the Azure Monitor OpenTelemetry Distro. You can enable it via code:

builder.Services
.AddOpenTelemetry()
.UseAzureMonitor();

Full Example Configuration in Program.cs (or Startup.cs)

A complete setup with logging, tracing, and OpenTelemetry might look like this:

appsettings.json:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"KubeOps": "Trace"
},
"Console": {
"FormatterName": "Simple",
"FormatterOptions": {
"IncludeScopes": true,
"SingleLine": true
}
},
"OpenTelemetry": {
"IncludeScopes": true,
"ParseStateValues": true,
"IncludeFormattedMessage": true
}
}
}

Program.cs:

const string OperatorName = "my-platform-operator";

var builder = WebApplication.CreateBuilder(args);

builder
.Services
.AddKubernetesOperator(settings => settings.Name = OperatorName)
.RegisterComponents();

builder
.Services
.AddOpenTelemetry()
.WithTracing(tracerProviderBuilder =>
tracerProviderBuilder
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: OperatorName, serviceVersion: "1.0.0"))
.AddSource(OperatorName))
.UseAzureMonitor();