# Logging from Blazor

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from PkLogging.cs, PkOptions.cs, PkRuntime.cs and the package README. Do not edit: change the source and run the generator.

The SDK has one logger (`js/log.js`). Configure it, and optionally forward its entries to `ILogger`, through `PkOptions.Logging`:

```csharp
builder.Services.AddPlainKit(o =>
{
    o.Logging.Level = PkLogLevel.Info;                      // the SDK's global level, applied at startup
    o.Logging.Scopes["loader"] = PkLogLevel.Debug;          // a level for one scope
    o.Logging.Routes[PkLogLevel.Error] = ["console", "toast"];
    o.Logging.ForwardToILogger = true;                      // off by default
    o.Logging.ForwardMinimumLevel = PkLogLevel.Warn;        // the forwarder's own filter
    o.Logging.ForwardScopes.Add("pk-*");                    // empty means every scope; ForwardExcludeScopes wins
});
```

Forwarded entries map debug, info, warn, error to Debug, Information, Warning, Error. The browser is untrusted input to this bridge (any JavaScript
on the page can call it), so every forwarded entry is written under the fixed category `PlainKit.Browser` — never a category the client picks — with
the scope kept in the message (`[browser:<scope>] ...`) instead; control characters are stripped, the scope, message and detail are capped in
length, and calls are rate-limited per circuit (50/second, with anything dropped logged once the window closes). To see the forwarder work, put a
misspelt element on a page, for example `<pk-buton></pk-buton>`: the SDK's loader logs the warning "`<pk-buton>` is not a Plainkit element" (scope
`loader`, once per tag and page), and it appears in your `ILogger` output as a `Warning` in the category `PlainKit.Browser`. The forwarder starts
with the first PlainKit component (or `IPkLog` call) on a circuit or page and stops with it. Inject `IPkLog` to write your own entries into the SDK log, so the logs viewer (`PkLogs`, the dev tools' Logs tab) shows them beside the SDK's; entries written that way are not echoed back to `ILogger`, so there is no loop. Use `ILogger` for your logs as usual; `IPkLog` is for messages you want in the browser-side log. It works in Blazor Server and in standalone WebAssembly (both verified, see Alpha status), and its calls do not throw while prerendering or after the circuit disconnects.

```csharp
@inject IPkLog PkLog
await PkLog.WriteAsync(PkLogLevel.Warn, "checkout", "Card declined", detail: orderId);
await PkLog.SetLevelAsync(PkLogLevel.Debug);
```

**Version check.** The package and the JavaScript it serves are one version. When the page loads a different copy (a stale cache, a CDN or a self-hosted copy of `core/dist`), the runtime logs one warning per runtime, once at startup, to `ILogger` (category `PlainKit.blazor`) and to the SDK log (scope `blazor`, so the Logs tab shows it); the Blazor dev tools tab shows both versions as well.

## How the bridge works

- `PkOptions.Logging` (a `PkLoggingOptions`) is applied to the SDK logger (`configureLogging`) when the first PlainKit component (or an `IPkLog` call) runs on a circuit or page.
- With `ForwardToILogger = true`, a JavaScript sink sends each SDK entry to .NET, which writes it to `ILogger` with the category `PlainKit.<scope>` (an empty scope is `PlainKit`). `debug`, `info`, `warn`, `error` map to Debug, Information, Warning, Error; `silent` is never forwarded. The forwarder filters by `ForwardMinimumLevel` (default Warn), `ForwardScopes` (empty means all; a trailing `*` matches a prefix) and `ForwardExcludeScopes` (wins).
- `IPkLog` writes into the SDK log from .NET (so `PkLogs` and the dev tools Logs tab show your entries beside the SDK's). Entries written this way are not echoed back to `ILogger`. Its calls never throw while prerendering or after the circuit disconnects (they are dropped).

## PkLoggingOptions

| Member | Description |
|---|---|
| `public PkLogLevel? Level` | The SDK's global level, applied at startup with `configureLogging`. Null leaves what the page decided (default `warn`, or the URL, page attribute or saved settings). |
| `public IDictionary<string, PkLogLevel> Scopes` | A level per scope (`loader`, `pk-dialog`, ...), which wins over Level for that scope. |
| `public IDictionary<PkLogLevel, IList<string>> Routes` | Where each level is sent: the output names `console`, `toast`, `alert` or one an app registered. A level not listed keeps its routing. |
| `public bool ForwardToILogger` | Whether SDK entries are also written to ILogger (category `PlainKit.<scope>`). Off by default. |
| `public PkLogLevel ForwardMinimumLevel` | The lowest SDK level that is forwarded (default Warn). The SDK sends every entry to its sinks whatever its level, so this is the forwarder's own filter, independent of Level. |
| `public IList<string> ForwardScopes` | The scopes that are forwarded. Empty means all. An entry ending in `*` matches a prefix (`pk-*`). |
| `public IList<string> ForwardExcludeScopes` | Scopes that are never forwarded; same matching as ForwardScopes, and it wins over it. |

## IPkLog

| Member | Description |
|---|---|
| `ValueTask WriteAsync(PkLogLevel level, string scope, string message, string? detail = null)` | Writes an entry. scope names the speaker (an app's own scope, e.g. `checkout`); Silent writes nothing. |
| `ValueTask SetLevelAsync(PkLogLevel level)` | Sets the SDK's global log level. |
| `ValueTask ConfigureAsync(PkLoggingOptions settings)` | Merges settings into the SDK's logging configuration (level, per-scope levels, routes). The forwarding settings are read only at startup. |
