@sandsoftwaresolutions/log-kit
v0.1.0
Published
Small structured JSON logger that hides passwords, tokens and cookies automatically, never throws, and has no dependencies.
Maintainers
Readme
@sandsoftwaresolutions/log-kit
A small logger that writes one JSON object per line and hides secrets for you. A password in a request body, a token in a URL, an Authorization header: they are replaced before the line is written, at any depth, so one careless log.info("got request", { req }) does not leak them. Logging never throws. No dependencies.
Install
npm install @sandsoftwaresolutions/log-kitWorks with import and require(), and ships TypeScript types for both.
Usage
import { createLogger, parseLevel } from "@sandsoftwaresolutions/log-kit";
const log = createLogger({ name: "api", level: parseLevel(process.env.LOG_LEVEL, "info") });
log.info("user signed in", { userId: 7, password: "hunter2" });
// {"time":"2026-10-21T15:24:33.330Z","level":"info","name":"api","msg":"user signed in","userId":7,"password":"[redacted]"}
log.error("save failed", { err }); // errors become { name, message, code, stack, cause }
log.error(err); // message from the error
const requestLog = log.child({ requestId });Add the request id from @sandsoftwaresolutions/request-id to every line:
const log = createLogger({ mixin: () => ({ requestId: currentRequestId() }) });What is hidden
- By key name, any case, at any depth:
password,passwd,passphrase,secret,token(alsoaccessToken,refreshToken),authorization,cookie,api key(apiKey,x-api-key),private key,credential,otp,pin. Because it is a substring match, a field likemaxTokensis hidden too; rename it or useredact.defaults: falsewith your ownkeys. - Inside texts:
Bearer …andBasic …values, the password inhttps://user:password@host, and query parameters whose name containstoken,key,secret,password,signature,sigorcode. - E-mail addresses only when you ask:
redact: { maskEmails: true }turns[email protected]intoj***@example.nl.
createLogger({ redact: { keys: ["iban", /^bsn$/i], replacement: "***" } });sanitize(value, options) does the same for any value, for example before sending data to an error tracker.
Tamed values
Circular references, bigint, getters that throw, functions, Date (valid or not), Map, Set, buffers, very deep objects, very long texts (cut at 10,000 characters) and very long arrays (cut at 100) all become safe JSON. If your write function or your mixin fails, the line is dropped or written without it, and the request goes on.
Fields that clash
time, level, name and msg belong to the logger. A field with one of those names is kept as field_time, field_msg, and so on.
Options
| Option | Default | |
|---|---|---|
| level | info | debug, info, warn, error, silent; change later with log.level = "debug" |
| name | none | Added as name |
| base | {} | Fields on every line |
| redact | see above | keys, defaults, replacement, maskEmails, maxDepth, maxStringLength, maxItems |
| write | standard output | (line, level) => void, for files, a worker, or tests |
| mixin | none | Extra fields computed per line |
| time | ISO clock | For tests |
Not included
No log files, rotation, transports or pretty printing. Pipe the output to your platform (Docker, systemd, a log shipper), or pass your own write.
MIT
