C# / .NET
HttpClient and System.Text.Json ship with the framework, so nothing collides with what your application pins.
Before you start
- Where your InsightRecorder runs
- The base URL every call goes to —
http://localhost:8080when you are running it locally, your own hostname otherwise. - An API key
- Settings → API keys, scoped to
logs:writeandbug:write. The token (crk_…) is shown exactly once — it is hashed at rest — and is revocable. Do not use a login JWT: it expires within the hour and is not meant for machines.
- 01
Install
No package references of its own.
dotnet add package InsightRecorder.Insight - 02
Register the client
As a singleton. An injected HttpClient is neither disposed nor mutated — with IHttpClientFactory both lifetime and configuration belong to the container.
var client = new InsightClient(new InsightOptions { BaseUrl = builder.Configuration["Insight:BaseUrl"], ApiKey = builder.Configuration["Insight:ApiKey"], }); await using var shipper = client.CreateShipper(); - 03
Ship a log line
Bridge your ILogger provider to this call.
shipper.Send(new LogEntry("checkout failed") { Level = "error", TraceId = Activity.Current?.TraceId.ToString(), }.With("order_id", "1234")); - 04
Verify it is working
Ship one line and dispose the shipper. Disposal is what flushes, which is why a hosted service's StopAsync is the right place for it.
shipper.Send(new LogEntry("insightrecorder smoke test") { Level = "error", TraceId = "smoke-1", }); await shipper.DisposeAsync();Then look for the line at /app/logs and the report at /app/bugs.
If nothing shows upNothing arrived? Check shipper.Dropped — a rising count means the queue is undersized. Delivery errors are silent unless you set OnError. Never set Timeout on an injected HttpClient: it throws once that client has made a request, which is why this SDK builds a per-request linked token instead.
Before you ship
- — A body-less POST needs :httpc's 4-tuple form; the SDK handles that so summarize/analyze calls work.
Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/csharp directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.
# Install and wire the InsightRecorder .NET SDK
Paste this into a coding agent (Claude Code, Cursor, …) running inside your
.NET service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.
---
You are working in a .NET service. Install and wire the InsightRecorder SDK
(`InsightRecorder.Insight`) so it ships its logs to InsightRecorder and reports unhandled
exceptions.
**Context you need:**
- InsightRecorder collects application logs and bug reports. This SDK is the supported
way for a .NET service to talk to it.
- Requires **.NET 8+**. It has **zero third-party dependencies** — `HttpClient`
and `System.Text.Json` ship with the framework — so it cannot conflict with
versions this project already pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
`bug:write` to report errors). **Do not** use a login JWT: those expire after
an hour and are not meant for machines.
Add `bug:read` for anything the SDK reads back (`ListBugs/GetBug`), and `logs:read` if you query logs. A write-only key is the common mistake: shipping works, the first read returns 403.
- **Wire trace correlation if the application uses OpenTelemetry.** A log line
and the request that wrote it are one investigation, and that join is the
reason to ship logs here rather than anywhere else — see the "Trace
correlation" section of the README for what this language needs (several do it
automatically; the rest take one hook). Without it, every log call has to carry
`trace_id` by hand, which nobody sustains past the first week.
**Steps:**
1. `dotnet add package InsightRecorder.Insight` in the service project (not the test
project).
2. Read configuration through the project's existing `IConfiguration` — bind
`Insight:BaseUrl` and `Insight:ApiKey`, sourced from environment variables
(`Insight__BaseUrl`, `Insight__ApiKey`) or the configured secret store. Never
hard-code them, and never put the key in `appsettings.json`. If either is
empty, the service must start normally with shipping disabled — telemetry is
never a startup dependency.
3. Register the client as a **singleton** in `Program.cs` / `Startup.cs`,
following the project's DI style:
```csharp
builder.Services.AddSingleton(_ => new InsightClient(new InsightOptions
{
BaseUrl = builder.Configuration["Insight:BaseUrl"]!,
ApiKey = builder.Configuration["Insight:ApiKey"]!,
}));
builder.Services.AddSingleton(sp => sp.GetRequiredService<InsightClient>().CreateShipper());
```
4. Bridge logging. If the project uses `Microsoft.Extensions.Logging` (it almost
certainly does), add a small `ILoggerProvider`/`ILogger` implementation whose
`Log` method builds a `LogEntry` and calls `shipper.Send(...)`, and register
it **alongside** the existing providers, never replacing them. Map: log level
→ `Level`, the formatted message → the entry message,
`Activity.Current?.TraceId` → `TraceId`, and scope/state values → `.With(key, value)`.
If the project uses Serilog, write the equivalent `ILogEventSink`.
5. Report unhandled exceptions where this project already handles them — an
exception-handling middleware, an `IExceptionHandler` (ASP.NET Core 8), or an
MVC filter:
```csharp
await client.ReportBugAsync(new Dictionary<string, object?>
{
["title"] = $"{e.GetType().Name}: {e.Message}",
["severity"] = "P0",
});
```
Report and then let the exception continue, so existing handling still runs.
6. Flush on shutdown. Register an `IHostedService` whose `StopAsync` calls
`await shipper.DisposeAsync()`, or resolve and dispose it from
`IHostApplicationLifetime.ApplicationStopping`. Without it the final batch is
lost.
**Rules — do not violate these:**
- Do **not** write a logger provider that awaits an HTTP call per log event. The
SDK batches in the background, caps each request at `BatchSize`, retries with
backoff, and respects the server's quota.
- Do **not** `await` or block on `shipper.Send(...)` — it is synchronous and
non-blocking on purpose, and drops when its queue is full.
- Do **not** create a new `HttpClient` per request; if the project uses
`IHttpClientFactory`, pass the client through `InsightOptions.HttpClient` (the
SDK will not dispose one it did not create).
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
reports: they routinely carry credentials and personal data.
**Verify before you finish:**
1. `dotnet build` and `dotnet test` pass.
2. Run the service with the configuration set, exercise an endpoint, then
confirm the log lines appear in InsightRecorder at `/app/logs`.
3. Trigger a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with the configuration **absent**.
5. Report what you changed, and state explicitly where the shipper is disposed.
If anything is ambiguous — which logging stack, how DI is wired, how shutdown is
handled — inspect the repository and follow what is already there. Do not
restructure the service to fit the SDK.