Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 5x 5x 6x 1x 1x 6x 6x 5x 5x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 154x 154x 154x 154x 154x 154x 154x 154x 154x 154x 154x 462x 462x 154x 154x 154x 154x 154x 154x 154x 152x 6x 6x | /**
* Pino transport that caches log entries to disk via cacache.
*
* Each log entry is stored using its ULID `id` as the cache key.
* The log timestamp is stored in the entry's cacache metadata so that
* a retention cleanup can sort and prune the oldest entries without
* reading their full content.
*
* Usage in Pino configuration:
* ```typescript
* import pino from 'pino';
*
* const logger = pino({
* transport: {
* target: './pino-cacache.ts',
* options: {
* cachePath: '/tmp/blong-log-cache',
* stripKeys: ['id', 'time'],
* retentionCount: 10000,
* },
* },
* });
* ```
*/
import * as cacache from 'cacache';
import os from 'node:os';
import path from 'node:path';
import build from 'pino-abstract-transport';
function resolveHome(filepath: string): string {
return filepath.startsWith('~/') ? path.join(os.homedir(), filepath.slice(2)) : filepath;
}
export interface CacacheTransportOptions {
/** Directory where cacache stores log entries. */
cachePath: string;
/** Keys to strip from each stored log entry (default: ['id', 'time']). */
stripKeys?: string[];
/** Maximum number of log entries to retain (default: 10000). */
retentionCount?: number;
}
const RETENTION_STATE_KEY = '__blong_retention_state__';
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
async function pruneOldEntries(cachePath: string, retentionCount: number): Promise<void> {
const index = await cacache.ls(cachePath);
// Collect all real log entries (skip the retention-state entry itself)
const entries = Object.values(index).filter(e => e.key !== RETENTION_STATE_KEY);
if (entries.length <= retentionCount) {
return;
}
// Sort ascending by stored timestamp so we delete the oldest first
entries.sort((a, b) => {
const ta: number = (a.metadata as {timestamp?: number} | null)?.timestamp ?? 0;
const tb: number = (b.metadata as {timestamp?: number} | null)?.timestamp ?? 0;
return ta - tb;
});
const toDelete = entries.slice(0, entries.length - retentionCount);
for (const entry of toDelete) {
await cacache.rm.entry(cachePath, entry.key);
}
// Garbage-collect content that is no longer referenced by any index entry
await cacache.verify(cachePath);
}
async function retentionCheckRun(cachePath: string, retentionCount: number): Promise<void> {
let lastCleanup = 0;
try {
const stateEntry = await cacache.get(cachePath, RETENTION_STATE_KEY);
const state = JSON.parse(stateEntry.data.toString()) as {lastCleanup: number};
lastCleanup = state.lastCleanup;
} catch {
// No state yet — treat as never cleaned up
}
if (Date.now() - lastCleanup < ONE_DAY_MS) {
return;
}
await pruneOldEntries(cachePath, retentionCount);
const state = JSON.stringify({lastCleanup: Date.now()});
await cacache.put(cachePath, RETENTION_STATE_KEY, Buffer.from(state));
}
export default async function transport(options: CacacheTransportOptions) {
const {
cachePath: rawCachePath,
stripKeys = ['id', 'time', 'pid'],
retentionCount = 10000,
} = options;
const cachePath = resolveHome(rawCachePath);
// Run retention check once on transport startup (at most once per day)
retentionCheckRun(cachePath, retentionCount).catch(() => {
// Retention errors must not crash the transport
});
return build(async function (source) {
for await (const obj of source) {
const entry: Record<string, unknown> =
typeof obj === 'string' ? (JSON.parse(obj) as Record<string, unknown>) : obj;
const id = entry.id as string | undefined;
const timestamp = entry.time as number | undefined;
if (!id) {
continue;
}
// Strip configured keys before storing
const stripped: Record<string, unknown> = {...entry};
for (const key of stripKeys) {
delete stripped[key];
}
await cacache
.put(cachePath, id, Buffer.from(JSON.stringify(stripped)), {
metadata: {timestamp},
})
.catch(() => {
// Storage errors must not crash the transport
});
}
});
}
|