Table of Contents

OpenTelemetry logs with Serilog

All Neos .NET projects include the Serilog.Sinks.OpenTelemetry package.

This page focuses on log emission through Serilog. For the end-to-end platform flow (processes -> collector -> exporters), see Observability architecture.

Configuration

By default, the OpenTelemetry sink is disabled. You can enable it per process.

Development example for a cluster backend in appsettings.Development.json:

{
  "Serilog": {
    "WriteTo": [
      {
        "Name": "OpenTelemetry",
        "Args": {
          "endpoint": "http://127.0.0.1:4318"
        }
      }
    ]
  }
}

Production example using environment variables from a Kubernetes Secret:

Serilog__WriteTo__0__Name=OpenTelemetry
Serilog__WriteTo__0__Args__endpoint=http://127.0.0.1:4318

The same principle applies to other .NET processes in the Neos ecosystem (report server, gateway, task runners, and others).

By default, instance metadata (service.instance.id) is resolved from the machine name (Environment.MachineName). In Kubernetes, this value is typically the pod name. The Neos runtime automatically injects service.instance.id as an OTLP resource attribute on every configured OpenTelemetry sink at startup, ensuring instance-level correlation in observability backends is consistent with the traces and metrics pipeline. The value is also added as a Serilog log event property so it remains visible in non-OTLP sinks (Application Insights, console).

Service names

The service.name resource attribute identifies the origin service in observability backends (Application Insights, Jaeger, etc.). Traces and metrics emitted by the .NET OpenTelemetry SDK carry an automatically set service.name. Logs emitted by Serilog.Sinks.OpenTelemetry carry a separate, sink-level service.name that must be configured in resourceAttributes. When no resourceAttributes section is provided, the Serilog sink falls back to the default OpenTelemetry SDK value, which on .NET resolves to unknown_service:dotnet.

Service names per Neos process

When the Neos Helm chart is used and logsEnabled is resolved to true, the chart automatically injects a resourceAttributes block with the correct service.name for each process. The table below lists the value used for each service.

Process service.name
Business cluster backend (multitenant) <clusterName>-<clusterVersion> — non-alphanumeric characters replaced by -, lowercased. Example: technicaldemos-3-1-0-alpha-6791
Business cluster task runner (multitenant) <clusterName>-<clusterVersion>-task-runner. Example: technicaldemos-3-1-0-alpha-6791-task-runner
Business cluster backend (monotenant) <clusterName> — non-alphanumeric characters replaced by -, lowercased. Example: technicaldemos
Business cluster task runner (monotenant) <clusterName>-task-runner. Example: technicaldemos-task-runner
Task Scheduler backend taskscheduler
Task Scheduler task runner taskscheduler-task-runner
Support Center backend supportcenter
Support Center task runner supportcenter-task-runner
Neos AI backend neosai
Neos AI task runner neos-ai-task-runner
Tenant Management backend tenants
Tenant Management task runner tenants-task-runner
License Management backend licensemanagement
License Management task runner licensemanagement-task-runner
Report server reporting
Gateway gateway
Note

For multitenant business clusters, service.name is derived by applying the same normalization as the Dapr App ID: every non-alphanumeric character is replaced with - and the result is lowercased, then the cluster version is appended (<clusterName>-<clusterVersion>). The version is configured per cluster in Helm values and injected as ClusterVersion in appsettings.json — this is not the chart version. The backend and task runner of the same cluster receive distinct ConfigMaps and a distinct service.name (-task-runner suffix) so that their logs can be differentiated in the observability backend.

Note

For monotenant business clusters, service.name matches the Dapr App ID exactly: <clusterName> for the backend and <clusterName>-task-runner for the task runner, with no version suffix. This keeps the telemetry identity consistent with the Dapr routing ID used for that cluster.

Note

All built-in services use their stable Dapr App ID as service.name, with no version suffix. The backend and task runner of each service receive distinct ConfigMaps and a distinct service.name (task runner suffix varies per service, see table above). This ensures consistency between the Dapr routing ID and the telemetry service.name.

Manual configuration

When the chart does not inject resourceAttributes (e.g. when using a custom appsettings.json or a local development environment), set service.name explicitly:

{
  "Serilog": {
    "WriteTo": [
      {
        "Name": "OpenTelemetry",
        "Args": {
          "endpoint": "http://127.0.0.1:4318",
          "resourceAttributes": {
            "service.name": "my-cluster-1-0-0"
          }
        }
      }
    ]
  }
}
Tip

The endpoint http://127.0.0.1:4318 is an OTLP/HTTP local example. In Kubernetes, use your collector service endpoint.

Note

See Serilog.Sinks.OpenTelemetry documentation for all available sink options.

Tip

When using the Neos Helm chart, the sink endpoint and protocol are automatically injected and kept in sync with the global OTLP configuration. To override them for logs without affecting other signals, use observability.otlp.logs.endpoint and observability.otlp.logs.protocol in Helm values instead of additionalSerilogConfiguration. See Per-signal endpoint routing for details.