> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # .NET Logs installation 1. 1 ## Install OpenTelemetry packages Required For the complete SDK reference, see the [OpenTelemetry .NET docs](https://opentelemetry.io/docs/languages/dotnet/). Terminal ```bash 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. 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](https://app.posthog.com/settings). 3. 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# ```csharp 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 "; }); }); ``` > **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 ```bash OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="https://us.i.posthog.com/i/v1/logs" OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer " 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# ```csharp options.Endpoint = new Uri("https://us.i.posthog.com/i/v1/logs?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# ```csharp 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 "; }); }); }); var logger = loggerFactory.CreateLogger(); ``` 4. 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# ```csharp public class CheckoutService(ILogger 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# ```csharp using (logger.BeginScope(new Dictionary { ["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. 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](https://app.posthog.com/logs) 7. ## Next steps Checkpoint *What you can do with your logs* | Action | Description | | --- | --- | | **[Why you need logs](/docs/logs/basics.md)** | What logs show you that nothing else does | | **[Search logs](/docs/logs/search.md)** | Use the search interface to find specific log entries | | **Filter by level** | Filter by `INFO`, `WARN`, `ERROR`, etc. | | **[Link session replay](/docs/logs/link-session-replay.md)** | Connect logs to users and session replays by passing `posthogDistinctId` and `sessionId` | | **[Link logs to a person](/docs/logs/link-person.md)** | Surface every log emitted on behalf of a user on their PostHog person profile | | **[Logging best practices](/docs/logs/best-practices.md)** | Learn what to log, how to structure logs, and patterns that make logs useful in production | [Troubleshoot common issues](/docs/logs/troubleshooting.md) ### Still have questions? Ask PostHog AI ### Was this page useful? HelpfulCould be better