.NET logs installation

  1. Install OpenTelemetry packages

    Required

    For the complete SDK reference, see the OpenTelemetry .NET docs.

    Terminal
    dotnet add package OpenTelemetry
    dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
    dotnet add package OpenTelemetry.Extensions.Hosting

    OpenTelemetry.Extensions.Hosting wires OpenTelemetry into the host builder. For a plain console app that builds its own LoggerFactory, you can leave it out.

  2. Get your project token

    Required

    You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.

    Important: Use your project token which starts with phc_. Do not use a personal API key (which starts with phx_).

    You can find your project token in Project settings.

  3. Configure the SDK

    Required

    Point the OpenTelemetry logging provider at PostHog. This attaches to the standard ILogger pipeline, so anything already logging through ILogger is exported without changing your call sites.

    C#
    using OpenTelemetry;
    using OpenTelemetry.Exporter;
    using OpenTelemetry.Logs;
    using OpenTelemetry.Resources;
    var builder = WebApplication.CreateBuilder(args);
    builder.Logging.AddOpenTelemetry(logging =>
    {
    logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("my-service"));
    // send the rendered message as the log body, and export scope values as attributes
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
    logging.AddOtlpExporter(options =>
    {
    options.Endpoint = new Uri("https://us.i.posthog.com/i/v1/logs");
    options.Protocol = OtlpExportProtocol.HttpProtobuf;
    options.Headers = "Authorization=Bearer <ph_project_token>";
    });
    });

    Note: With HttpProtobuf, the Endpoint is used as-is, so include the full /i/v1/logs path. Don't use the base OTEL_EXPORTER_OTLP_ENDPOINT variable, which appends its own /v1/logs.

    Alternatively, configure the exporter with environment variables and call AddOtlpExporter() with no arguments:

    Terminal
    OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="https://us.i.posthog.com/i/v1/logs"
    OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer <ph_project_token>"
    OTEL_EXPORTER_OTLP_LOGS_PROTOCOL="http/protobuf"
    OTEL_SERVICE_NAME="my-service"

    You can also pass the project token as a query parameter instead of a header, though it will then appear in proxy and CDN access logs:

    C#
    options.Endpoint = new Uri("https://us.i.posthog.com/i/v1/logs?token=<ph_project_token>");

    For a console app or any process without a host builder, create the logger factory directly and keep it alive for the lifetime of the process — disposing it flushes pending logs:

    C#
    using var loggerFactory = LoggerFactory.Create(builder =>
    {
    builder.AddOpenTelemetry(logging =>
    {
    logging.IncludeFormattedMessage = true;
    logging.AddOtlpExporter(options =>
    {
    options.Endpoint = new Uri("https://us.i.posthog.com/i/v1/logs");
    options.Protocol = OtlpExportProtocol.HttpProtobuf;
    options.Headers = "Authorization=Bearer <ph_project_token>";
    });
    });
    });
    var logger = loggerFactory.CreateLogger<Program>();
  4. Use ILogger

    Required

    Log through ILogger as usual. Message template placeholders become searchable attributes, so pass structured values as parameters rather than interpolating them into the string:

    C#
    public class CheckoutService(ILogger<CheckoutService> logger)
    {
    public void Complete(string userId, string orderId)
    {
    logger.LogInformation("User action {UserId} {Action}", userId, "login");
    logger.LogWarning("Deprecated API used {Endpoint}", "/old-api");
    logger.LogError("Database connection failed {Error}", "Connection timeout");
    }
    }

    Important: logger.LogInformation($"User {userId} logged in") produces a different message body on every call and no attributes. Use the template form above instead.

    To link logs to a person and their session replay, add the identifiers as a scope. These keys are matched exactly, so posthog_distinct_id and other snake_case variants won't link:

    C#
    using (logger.BeginScope(new Dictionary<string, object>
    {
    ["posthogDistinctId"] = userId,
    ["sessionId"] = sessionId,
    }))
    {
    logger.LogInformation("Checkout completed {OrderId}", orderId);
    }

    Scope values are only exported when IncludeScopes = true is set, as in the configuration above.

  5. Test your setup

    Recommended

    Once everything is configured, test that logs are flowing into PostHog:

    1. Send a test log from your application
    2. Check the PostHog Logs interface for your log entries
    3. Verify the logs appear in your project
    View your logs in PostHog
  6. Next steps

    Checkpoint
    What you can do with your logs

    ActionDescription
    Why you need logsWhat logs show you that nothing else does
    Search logsUse the search interface to find specific log entries
    Filter by levelFilter by INFO, WARN, ERROR, etc.
    Link session replayConnect logs to users and session replays by passing posthogDistinctId and sessionId
    Link logs to a personSurface every log emitted on behalf of a user on their PostHog person profile
    Logging best practicesLearn what to log, how to structure logs, and patterns that make logs useful in production

    Troubleshoot common issues

Was this page useful?