@unikvs/debug
Explains how to use @unikvs/debug, which logs read and write operations and data with @logtape/logtape.
Overview
@unikvs/debug is a Transformer plugin that passes data through unchanged while logging the operation kind, the target key, and debug information about the data with @logtape/logtape. Transformer
It reads the unikvs:action and unikvs:key variables, so using it through UniKvs records which operation read or wrote data for which key. It does not transform data.
This transformer is always open. No explicit open or close is needed; isOpen always returns true and name is always "Debug".
Install it as follows. @logtape/logtape is also required to output logs.
npm install @unikvs/debug @logtape/logtapepnpm add @unikvs/debug @logtape/logtapeyarn add @unikvs/debug @logtape/logtapebun add @unikvs/debug @logtape/logtapenub add @unikvs/debug @logtape/logtapeaube add @unikvs/debug @logtape/logtapeUsage
Configure LogTape first, then register it with appendTransformer. It becomes the upstream of subsequently registered storages.
import { Debug } from "@unikvs/debug";
import { Memory } from "@unikvs/memory";
import { configureSync, getConsoleSink } from "@logtape/logtape";
import { UniKvs, type PlainValue } from "unikvs";
configureSync({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: ["unikvs", "@unikvs/debug"],
sinks: ["console"],
lowestLevel: "debug",
},
],
});
const kvs = UniKvs.config<{ greeting: PlainValue<string> }>()
.appendTransformer(new Debug())
.appendStorage(new Memory())
.create();
await kvs.open();
await kvs.set("greeting", "hello");
const greeting = await kvs.get("greeting"); // logs reads and writes to the console
await kvs.close();
set is recorded as a write and get as a read. For stream values, set and stream are recorded chunk by chunk.
What is Recorded
Each log record has the following properties.
| Property | Description |
|---|---|
action |
The unikvs:action variable. It is one of set, get, or stream for this transformer. |
key |
The unikvs:key variable. |
direction |
"write" or "read". "write" is data written to storage, and "read" is data read from storage. |
type |
The data type, determined by the default callback with typeName() from type-name. |
length |
The string length when the data is a string. |
byteLength |
The exact byte count when the data is a TypedArray or DataView. |
Data without unikvs:action or unikvs:key in vars is not recorded, regardless of the filters.
The logger category is ["unikvs", "@unikvs/debug"] and the level is debug.
Options
The constructor arguments are as follows.
| Argument | Type | Required | Description |
|---|---|---|---|
options |
DebugOptions |
No | Omitting it behaves the same as an empty object. |
options.keyFilter |
IDebugKeyFilter ((key: string) => boolean) |
No | Narrows the keys to record. Only keys for which it returns true are recorded. |
options.actionFilter |
IDebugActionFilter ((action: string) => boolean) |
No | Narrows the operations to record. Only operations for which it returns true are recorded. |
options.getDebugInfo |
IDebugInfoCallback ((args: DebugInfoArgs) => Record<string, unknown>) |
No | Returns additional debug information. The return value is spread into the log properties. |
Key Filter
When keyFilter is specified, only keys for which it returns true are recorded. Logs without unikvs:key in vars are not recorded.
import { Debug } from "@unikvs/debug";
const debug = new Debug({
keyFilter: (key) => key.startsWith("user:"),
});
Action Filter
When actionFilter is specified, only operations for which it returns true are recorded. Logs without unikvs:action in vars are not recorded.
import { Debug } from "@unikvs/debug";
const debug = new Debug({
actionFilter: (action) => action === "get" || action === "stream",
});
Additional Debug Information
When getDebugInfo is specified, the returned object is spread into the log properties. The argument is the DebugInfoArgs type and has vars, action, key, direction, and data. data is the data that was read or written; for streams, it is a chunk.
The default callback is the static method Debug.getDefaultDebugInfo. Call it to add your own information on top of the default.
import { Debug } from "@unikvs/debug";
const debug = new Debug({
getDebugInfo: (args) => ({
...Debug.getDefaultDebugInfo(args),
preview: typeof args.data === "string" ? args.data.slice(0, 16) : null,
}),
});
Streams
getEncodable and getDecodable pass input through unchanged while recording a log entry for each chunk. Filters are applied per chunk as well.
Notes
- Displaying logs requires a LogTape configuration. Without one, logs are not output anywhere.
- Data is not transformed. Using it with other Transformers does not affect the result. Its position in the chain changes what data you can observe, so you can inspect data after upstream transformations.
- The record level is
debug. SetlowestLevelto"debug"or"trace"in the LogTape configuration, or nothing is recorded. - The data itself is not recorded by default. If you return the content from
getDebugInfo, be careful not to leave confidential information in logs.
Examples
An example combining filters with additional debug information. It records only reads of keys starting with user: and, when the data is a string, records its first 16 characters as preview.
import { Debug } from "@unikvs/debug";
import { Memory } from "@unikvs/memory";
import { configureSync, getConsoleSink } from "@logtape/logtape";
import { UniKvs, type PlainValue } from "unikvs";
configureSync({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: ["unikvs", "@unikvs/debug"],
sinks: ["console"],
lowestLevel: "debug",
},
],
});
const debug = new Debug({
actionFilter: (action) => action === "get" || action === "stream",
keyFilter: (key) => key.startsWith("user:"),
getDebugInfo: (args) => ({
...Debug.getDefaultDebugInfo(args),
preview: typeof args.data === "string" ? args.data.slice(0, 16) : null,
}),
});
const kvs = UniKvs.config<{ "user:1": PlainValue<string>; settings: PlainValue<string> }>()
.appendTransformer(debug)
.appendStorage(new Memory())
.create();
await kvs.open();
await kvs.set("user:1", "Alice");
await kvs.set("settings", "dark");
await kvs.get("user:1"); // recorded
await kvs.get("settings"); // not recorded because the key does not match
await kvs.close();