# Logging

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from dist/js/log.js and STANDARDS.md. Do not edit: change the source and run the generator.

The SDK has one logger. Its own messages and yours go through it, so one viewer (the dev tools Logs tab, or `mountLogs`) shows everything. The default level is `warn`, so a production page is quiet. The header of `js/log.js`, verbatim:

```text
SDK logging: one small logger every part of the SDK uses, so a page mistake or a failure is never silent, and the same logger is there
for an app's own messages (createLogger('checkout')).

  import { createLogger } from './log.js';
  const log = createLogger('invokers');
  log.warn('data-open="#nope" matched no element', { control });

Levels, lowest first: debug, info, warn, error, silent. An entry is emitted when its level is at or above the level for its scope
(a per-scope level wins over the global one). Emitted entries go to the OUTPUTS the routing names for that level:
  console   the browser console (built in)
  toast     a pk-toast (loaded on first use)          alert   a pk-alert notice at the top of the page (loaded on first use)
  your own  registerLogOutput('telemetry', entry => ...)
The default routing sends every level to the console only and the default level is 'warn', so a production page stays quiet.
Configure in code:
  configureLogging({ level: 'info', scopes: { loader: 'debug' }, routes: { error: ['console', 'toast'], warn: ['console'] } });
or without touching code, in this order of precedence: ?pk-log=debug in the URL, <html data-pk-log="debug">, the saved settings
(configureLogging(..., { persist: true }), which the Settings page and the dev tools write) and localStorage['pk-log'].
Whatever the level, the last few hundred entries are kept in a ring buffer (getLogBuffer()) and every entry goes to the sinks
(addLogSink(fn)), which is how the logs viewer and PlainKit.Blazor's ILogger bridge see everything. Framework-free; never throws.
From the browser console: PkLog.setLogLevel('debug').
```

## Exports of `dist/js/log.js`

`BUFFER_SIZE`, `CONFIG_KEY`, `DEFAULT_LEVEL`, `DEFAULT_ROUTES`, `LEVELS`, `addLogSink`, `clearLogBuffer`, `configureLogging`, `createLogger`, `getLogBuffer`, `getLogLevel`, `getLogOutputs`, `getLoggingConfig`, `isLogEnabled`, `levelFrom`, `log`, `normalizeConfig`, `parseConfig`, `registerLogOutput`, `resetLogging`, `setLogLevel`.

## Scopes and levels

- **Scopes**: `loader`, `invokers`, the tag name for an element (`pk-input`), the module name for a tool (`scorecard`, `code-explorer`, `theme-editor`, `quality`, `performance`), your own name for an app.
- **error**: something the page asked for did not happen and cannot recover (a module failed to load, a run failed).
- **warn**: a mistake the SDK worked around (a bad attribute value that fell back to its default, an unknown tag, a selector that matches nothing, a saved value that could not be read). Say what was wrong and what was used instead. In an element, say it once per instance (`warnOnce`) so a re-render loop cannot flood.
- **Deprecation**: an item that will go is marked `deprecated: { since, remove, message }` in its element meta (element, prop, event or slot; see `tools/element-api.mjs`); the generated module warns once per page through the element's logger (`js/deprecation.js`, imported only by elements that deprecate something, never by the base). Keep it for one minor version, remove it in the release `remove` names.
- **info**: rare, useful milestones an app might want to see. The SDK itself seldom uses it.
- **debug**: lifecycle and expected fallbacks (element defined, connected, a prop changed, a module loaded and how long it took, a tool mounted, blocked storage, an unsupported browser API). Guard anything costly to build with `isLogEnabled('debug', scope)`; the default level is `warn`, so a quiet page prints nothing extra.

## Turn it on

Without touching code: add `?pk-log=debug` to the address, or `data-pk-log="debug"` on `<html>`. In code:

```js
import { createLogger, configureLogging } from './plainkit/js/log.js';

configureLogging({ level: 'info', scopes: { checkout: 'debug' }, routes: { error: ['console', 'toast'] } });
const log = createLogger('checkout');
log.info('order placed', { id: 42 });
```

From the browser console: `PkLog.setLogLevel('debug')`. Settings saved with `configureLogging(settings, { persist: true })` survive a reload; `resetLogging()` forgets them. See `tools.md` for `mountLogs` (the viewer) and `mountLogSettings` (an editor for these settings).

## Measure your own page

The header of `js/measure.js`, verbatim:

```text
Measure a page in a real browser: Core Web Vitals-style timings (largest paint, layout shift, long tasks, slowest interaction) read from
buffered PerformanceObservers, the style-recalculation cost of a theme switch, and an off-screen iframe harness that loads one of your own
pages and reports its vitals, load time and DOM size. Any app can measure its own pages with it; the SDK scorecard is one consumer.
Only same-origin pages can be measured (the browser blocks reading a frame from another origin). Framework-free; imports the logger only.
```

```js
import { measurePage } from './plainkit/js/measure.js';

const { loadMs, nodes, lcp, cls, longTasks, inp } = await measurePage('/checkout');
```

`measurePage(url)` loads a same-origin page off-screen and resolves those numbers; `watchVitals(window)` and `recalcMs(document)` are the live pieces it is built from.
