@holz/log-collector
Version:
Send all logs to a central collector
76 lines (50 loc) • 3.33 kB
Markdown
# `/log-collector`
Supports replacing the global logging destination for all loggers in your app.
## Purpose
Let's say you're building an app, and your app uses libraries that manage logs with Holz. Your app uploads logs to a central logging backend and you want to include logs from some of those libraries. That's where `/log-collector` comes in.
On startup you set a global log collector. Libraries detect this automatically and **replace** their log destination with whatever you provide. Now you control all logs and can selectively upload to your backend.
Other use cases:
- **Central Log Files:** Redirect all server logs to a file, including logs from specific libraries.
- **External Debuggers:** Depending on your environment, it may be useful to view logs in a separate application like [OTel Desktop Viewer](https://github.com/CtrlSpice/otel-desktop-viewer) or [DebugView](https://docs.microsoft.com/en-us/sysinternals/downloads/debugview). A log collector can act as the central exporter.
- **Interactive TUIs:** Renderers like [Ink](https://github.com/vadimdemedes/ink) expect control of the screen. Logs can damage rendering. A solution might redirect logs to a file stream or a custom logs window.
- **Patching Broken Logs:** If an unexpected input starts throwing errors, you have the ability to patch it without making upstream changes.
## Usage in Apps
Set a global log collector as soon as the app starts. This is typically done in the main entry file of your app.
```typescript
import { setGlobalLogCollector } from '/log-collector';
setGlobalLogCollector({
// Called for every log in your app so long as `condition` returns true.
processor: (log) => {
// ...
},
// [Optional] Decide which logs to collect. Defaults to all logs.
condition: (log) => log.origin[0] === 'some-library',
});
```
- **processor:** Called for every log so long as `condition` returns true. The signature is a Holz plugin, which means you can pass your app's log pipeline verbatim (whatever you passed to `createLogger`).
- **condition:** A function that decides which logs to collect. By default it captures everything. If it returns `false`, logs are sent to their original destination.
---
To remove a global log collector and restore defaults, call `unsetGlobalLogCollector`:
```typescript
import { unsetGlobalLogCollector } from '/log-collector';
unsetGlobalLogCollector();
```
This is primarily used in tests.
## Usage in Libraries
> [!NOTE]
> This plugin is included by default with `/logger`.
This should be the **first plugin** in your log pipeline.
```typescript
import { createLogCollector } from '/log-collector';
import { createLogger } from '/core';
const logger = createLogger(
createLogCollector({
fallback: (log) => {
// Default log backend. Called if no global log collector is set.
},
}),
);
```
- **fallback:** Called if no global log collector is set, or if `condition` returns false. This is the default log backend. The signature is a Holz plugin.
The expectation is the collector captures logs before any side effects happen, such as logging to the console or sending to a file.
Be aware that `logger.withMiddleware(...)` runs before the default logging backend, so take care to avoid side effects in those handlers.