fix(docs):Docs and Exports
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish unknown status

- Add documentation for recently released changes
- Update some exports to include enums
This commit is contained in:
2026-08-21 11:45:56 -03:00
parent 2134ba58b8
commit f464fa5017
18 changed files with 1350 additions and 1307 deletions
+67 -34
View File
@@ -3,10 +3,11 @@
Intentional, namespaced logging for TypeScript browser and Node.js applications.
```ts
import { createLogger } from "@mifi/logger";
import { createLogger } from '@mifi/logger';
const logger = createLogger({ environment: "production", namespace: "WIDGET" });
logger.child("Button").debug("Rendered", () => ({ expensive: "only evaluated when shown" }));
const logger = createLogger({ environment: 'production', namespace: 'WIDGET' });
logger.child('Button').debug('Rendered', () => ({ expensive: 'only evaluated when shown' }));
logger.debug('response', async () => ({ data: await fetchBody() })); // settled before sinks
```
## Install
@@ -20,22 +21,23 @@ Requires Node.js `>=24`. Published to the private `@mifi` registry.
## Quick start
```ts
import { createLogger } from "@mifi/logger";
import { createLogger } from '@mifi/logger';
const logger = createLogger({
environment: "development", // "development" | "staging" | "production"
namespace: "API", // optional; string or string[] joined with ":"
environment: 'development', // "development" | "staging" | "production"
namespace: 'API', // optional; string or string[] joined with ":"
});
logger.trace("very detailed");
logger.debug("diagnostic", () => ({ snapshot: heavyWork() }));
logger.info("ready");
logger.warn("slow response", { ms: 1200 });
logger.error("request failed", error);
logger.assert(userId, "missing user id");
logger.trace('very detailed');
logger.debug('diagnostic', () => ({ snapshot: heavyWork() }));
logger.debug('response', async () => ({ data: await res.json(), url: res.url }));
logger.info('ready');
logger.warn('slow response', { ms: 1200 });
logger.error('request failed', error);
logger.assert(userId, 'missing user id');
const auth = logger.child("auth"); // namespace → "API:auth"
auth.info("token refreshed");
const auth = logger.child('auth'); // namespace → "API:auth"
auth.info('token refreshed');
```
Console utility methods (`group`, `groupCollapsed`, `table`, `time`, `count`, `dir`, …) are available and gated by the same level policy as `info`.
@@ -64,9 +66,9 @@ Highest wins:
### Browser session override
```js
sessionStorage.setItem("showLoggingFor", "debug");
sessionStorage.setItem('showLoggingFor', 'debug');
// or restrict to namespaces (exact or descendants):
sessionStorage.setItem("showLoggingFor", "debug:WIDGET,API");
sessionStorage.setItem('showLoggingFor', 'debug:WIDGET,API');
```
`WIDGET` matches `WIDGET` and `WIDGET:Button`.
@@ -127,20 +129,20 @@ Override with `logLevel` on `createSentrySink`. Console policy stays independent
`trace` / `debug` / `log` / `info` / `warn` / `error` (and `assert`) accept a trailing options object. Console utilities (`group`, `time`, …) do not.
```ts
import * as Sentry from "@sentry/nextjs";
import { createLogger } from "@mifi/logger";
import { createSentrySink } from "@mifi/logger/sentry";
import * as Sentry from '@sentry/nextjs';
import { createLogger } from '@mifi/logger';
import { createSentrySink } from '@mifi/logger/sentry';
const logger = createLogger({
environment: "production",
namespace: "API",
environment: 'production',
namespace: 'API',
sentry: createSentrySink(Sentry, { logs: true }),
});
logger.error("Request failed", error, { requestId: "req_1" });
logger.warn("slow", { ms: 1200 }); // → Sentry Logs in production
logger.info("investigating", { orderId }, { sentry: true }); // force Logs
logger.error("expected", err, { suppressSentry: true }); // console only (when enabled)
logger.error('Request failed', error, { requestId: 'req_1' });
logger.warn('slow', { ms: 1200 }); // → Sentry Logs in production
logger.info('investigating', { orderId }, { sentry: true }); // force Logs
logger.error('expected', err, { suppressSentry: true }); // console only (when enabled)
```
| Option | Effect |
@@ -150,7 +152,7 @@ logger.error("expected", err, { suppressSentry: true }); // console only (when e
A last argument is treated as options only when every key is `sentry` or `suppressSentry`. Prefer the third-argument form when you also pass data.
Logs payload: string message + flat primitive attributes (including `logger.namespace`). Nested objects are not unfurled.
Logs payload: string message + flat primitive attributes (including `logger.namespace`). Nested objects are not unfurled. When emit was deferred for thenables, the payload also includes `async: true`.
Issue severities use Sentrys union (`warning`, not logger `warn`). The sink maps `warn``warning` and `trace``debug` for Issues only; Logs keep logger level names (`sentry.logger.warn`).
@@ -159,16 +161,17 @@ Issue severities use Sentrys union (`warning`, not logger `warn`). The sink m
Replace the default destinations entirely with `sinks`:
```ts
import { createLogger, createConsoleSink, type LogSink } from "@mifi/logger";
import { createLogger, createConsoleSink, type LogSink } from '@mifi/logger';
const analyticsSink: LogSink = {
emit(event) {
if (event.sendToConsole) analytics.track("log", event);
if (event.sendToConsole) analytics.track('log', event);
// event.async === true when args were settled asynchronously
},
};
const logger = createLogger({
environment: "production",
environment: 'production',
sinks: [createConsoleSink(), analyticsSink],
});
```
@@ -192,16 +195,46 @@ All public APIs include JSDoc with parameter and example documentation in the Ty
## Lazy arguments
Any function passed **after** the first argument is invoked only if the event is emitted:
Any function passed **after** the first argument is invoked only if the event will actually be emitted (console and/or Sentry). That keeps expensive snapshots cheap when the level is suppressed.
```ts
logger.debug("state", () => buildHugeSnapshot()); // skipped when debug is suppressed
logger.debug("response", async () => ({ data: await res.json(), url }));
logger.debug('state', () => buildHugeSnapshot()); // skipped when debug is suppressed
logger.error('failed', error, () => ({ body: pendingBody }));
```
Top-level thenables (including Promises returned from lazy factories, or a Promise passed directly as an argument) are settled before sinks run. The log call stays fire-and-forget (`void`). Settled events set `LogEvent.async`, and the console sink renders an `[async]` label suffix. Nested promises inside plain objects are not walked — compose them inside the async factory. Async emits may appear out of order relative to sync logs.
### Async factories and Promises
Rejection reasons replace rejected thenables in that argument slot; other arguments still emit.
Factories may be `async`, or you may pass a top-level `Promise` as an argument. Thenables are **settled before sinks run**; the log method stays fire-and-forget (`void`).
```ts
logger.debug('response', async () => ({
data: await res.json(),
url: res.url,
}));
logger.error('failed', { id }, responsePromise);
```
| Behavior | Detail |
| ----------------- | -------------------------------------------------------------------------------------------- |
| Unwrap scope | **Shallow** — only top-level thenables in the argument list |
| Nested promises | Not walked; compose with `await` / `Promise.all` inside the factory |
| Call return type | Still `void` (no `await logger.debug(...)`) |
| Timestamp | Captured at call time (before settle), so async logs correlate with the failure moment |
| `LogEvent.async` | `true` when emit was deferred; omitted for fully sync emits |
| Console | Label gains an `[async]` suffix, e.g. `[API][async]` |
| Sentry Logs | Attribute `async: true` when set |
| Ordering | Async emits may appear after later sync logs |
| Rejections | Rejection `reason` replaces that argument slot; other args still emit (`Promise.allSettled`) |
| Console utilities | `group` / `time` / etc. do not settle thenables (emit path only) |
```ts
// Nested promises — compose inside the factory:
logger.debug('route', async () => {
const [body, next] = await Promise.all([res.json(), nextRoute]);
return { body, next };
});
```
## Releases