most loggers want configuring before they're useful. console.log wants nothing
and gives you grey mush. plip sits in between. import it, call it, read the
output. configure it later if you need to.
import { plip } from '@ru-dr/plip';
plip.info("Welcome to Plip!");
plip.success("Everything is working perfectly");
plip.warn("This might need your attention");
plip.error("Something went wrong");no runtime dependencies. ESM and CommonJS builds, both with types. seven levels:
verbose, trace, debug, info, success, warn, error. success is
there because "it worked" is a thing you actually want to say, and most loggers
make you say it in info.
server and browser want different things
a server wants timestamps and JSON that a log aggregator can parse. a browser wants colour and shape so you can scan it. most loggers pick one and leave you to argue with the defaults.
plip ships both as presets: ssrLogger and csrLogger, with factory functions
if you want to change them. CSR is the default, since the first place you use a
logger is usually the browser tab in front of you.
it detects Node, the browser and Deno. Deno has a trap worth knowing about — it
ships a process shim, so you have to check for Deno first or you'll think
you're in Node. reading env vars has the same shape of problem, since a browser
bundle without a process shim throws on import. both reads are guarded.
the colour code
the colour layer is hand-written ANSI. that's most of why there are no dependencies. two things it gets right that the obvious version doesn't.
nested styles don't get cut short. wrapping text in a colour code is easy until the text already contains a reset, which ends your outer style early. plip reopens the inner reset so the outer one survives.
JSON highlighting matches whole tokens, not punctuation. a string value with a quote or a colon inside it would otherwise get highlighted wrong.
colour detection respects NO_COLOR, FORCE_COLOR, isTTY, TERM, and the CI
variables for GitHub Actions, GitLab, CircleCI and Travis. nobody wants escape
codes in a log file.
transports
four: console, file, remote, browser. flush() on the logger drains them, so a
process can finish writing before it exits. the interesting part is what each one
does when things go wrong.
- file queues each write behind the one still running, instead of dropping the entry. concurrent logs can't interleave into garbage.
- remote batches ten entries or five seconds, whichever comes first, and
stops buffering at a thousand. an endpoint that stays down can't eat memory.
its timer is
unref'd, so it never holds a Node process open just to flush logs. - browser trims to its size limit as it goes rather than re-stringifying the whole buffer every time it drops something.
withColors(), withContext() and child() return a new logger instead of
changing the one you called them on. child loggers share the parent's transports,
so a family of loggers has one transport lifecycle rather than a private copy
each.
what v2 fixed
v2 is a good lesson in config that lies. withColors(),
withSyntaxHighlighting() and levels() did nothing to console output. the
console transport was re-formatting every entry using the config it was built
with, so anything you changed afterwards got thrown away. enableTimestamp and
enableStructuredOutput were accepted and then ignored. transport failures
disappeared into Promise.allSettled.
the fix was to stop copying state into the transport at all. the logger already decides what to filter, so the transport doesn't need its own snapshot of it — and a snapshot is exactly what went stale on child loggers.
coverage moved with it. RemoteTransport 6% to 93%, BrowserTransport 32% to 100%, FileTransport 73% to 100%, adapters roughly 50% to 99%. the suite went from 78 tests to 147. there's more test code than source code now.
v2 also dropped emoji from the output. lines are plain [LEVEL] message. the
emoji were fun for about a week and unreadable in a log aggregator forever.
one thing to watch
production settings are worked out when the package is first imported, so
NODE_ENV has to be set before that happens. in production everything is off
until you turn it back on.
silent-by-default is the safe way to get it wrong, but the import order will bite someone, so it's written down rather than left to be discovered.
framework bits
adapters for React and Next.js. the Next.js one strips authorization, cookie,
x-api-key and x-auth-token out of request headers before they reach a log
line.
worth being clear about the limit: that's request headers in one adapter. plip does not scrub your log payloads. message text is written as given, and the security policy says so.
running it
npm install @ru-dr/plipNode 16 or newer. CI typechecks, lints, runs the tests with coverage, builds, and
then actually imports the built package both ways — a real require() and a real
dynamic import() — to check the entry points resolve. a broken exports map
otherwise shows up in someone else's project instead of mine. the same checks run
before publish.
MIT.