.NET logging to UptimeEye (Serilog, OpenTelemetry)

For .NET the OpenTelemetry route is the smoothest: Serilog's OpenTelemetry sink or the OpenTelemetry.Exporter.OpenTelemetryProtocol package send structured records straight to UptimeEye, with message template properties as fields. In containers, JSON console output plus the platform agent works just as well.

Endpoint: https://logs.uptimeeye.com/v1/ingest/otlp/v1/logs

1.Serilog with the OpenTelemetry sink

Program.cs
// dotnet add package Serilog.AspNetCore
// dotnet add package Serilog.Sinks.OpenTelemetry
using Serilog;
using Serilog.Sinks.OpenTelemetry;

builder.Host.UseSerilog((ctx, lc) => lc
    .ReadFrom.Configuration(ctx.Configuration)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.OpenTelemetry(o =>
    {
        o.Endpoint = "https://logs.uptimeeye.com/v1/ingest/otlp/v1/logs";
        o.Protocol = OtlpProtocol.HttpProtobuf;
        o.Headers = new Dictionary<string, string>
        {
            ["Authorization"] = $"Bearer {Environment.GetEnvironmentVariable("UPTIMEEYE_INGEST_KEY")}",
            ["VL-Stream-Fields"] = "service.name,deployment.environment",
        };
        o.ResourceAttributes = new Dictionary<string, object>
        {
            ["service.name"] = "checkout",
            ["deployment.environment"] = "prod",
        };
    }));

// later
Log.Information("payment authorized for {OrderId} in {DurationMs} ms", "4711", 812);
Note: Template properties (OrderId, DurationMs) become attributes and therefore fields; the rendered message is the log body.

2.Microsoft.Extensions.Logging with the OpenTelemetry SDK

Program.cs
// dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Exporter;

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.SetResourceBuilder(ResourceBuilder.CreateDefault()
        .AddService("checkout")
        .AddAttributes(new[] { new KeyValuePair<string, object>("deployment.environment", "prod") }));
    logging.AddOtlpExporter(o =>
    {
        o.Endpoint = new Uri("https://logs.uptimeeye.com/v1/ingest/otlp/v1/logs");
        o.Protocol = OtlpExportProtocol.HttpProtobuf;
        o.Headers = $"Authorization=Bearer {Environment.GetEnvironmentVariable("UPTIMEEYE_INGEST_KEY")}";
    });
});

3.Containers: JSON console output

Keep the platform agent in charge. AddJsonConsole writes one JSON object per line; Message is the message and Timestamp the time.

Program.cs
builder.Logging.ClearProviders();
builder.Logging.AddJsonConsole(o =>
{
    o.IncludeScopes = true;
    o.TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fffZ";
    o.UseUtcTimestamp = true;
});
// agent side: _msg_field=Message&_time_field=Timestamp

Fields you get

These show up in the fields panel and can be used in every filter:

  • service.name, deployment.environment
  • severity_text
  • message template properties (OrderId, DurationMs)
  • Category / logger name
  • exception details

Tips

  • Use message templates ({OrderId}), never string interpolation — only templates produce fields.
  • Add Enrich.WithProperty("env", "prod") if you want a plain env field next to the resource attribute.

FAQ

Does Serilog.Sinks.Http work too?
Yes, with a custom IHttpClient that adds the Authorization header and requestUri set to /v1/ingest/jsonline?_msg_field=RenderedMessage&_time_field=Timestamp. The OpenTelemetry sink needs no custom code, so it is the recommended path.
Where do I get the ingest key?
In the app under API Keys → New API Key → type “Log ingest”. The key starts with ue_ingest_ and is shown once. Management keys (ue_live_) are refused by the ingest endpoint.
How do I check that logs arrive?
Open Logs in the app, pick the 15m range and search for service:=<your service>. New lines are searchable within about a second; Live tail shows them with a ~6 s delay.