npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

colorjslogger

v5.0.0

Published

Write colorful logs in browser console with download capabilities and application-specific prefixes.

Readme

🎨 ColorJSLogger

Colorful, structured browser logs that live in memory — and download as a file.

No servers. No network calls. No telemetry. Nothing ever leaves the browser.

npm version CI minzipped dependencies types license

Install · Quick start · API reference · Client-side by design · Redaction · Security


let username = 'Suhaib Janjua';
jslogger.log('AuthService', `Authentication successful: User "${username}" loggedin.`);
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Authentication successful: User "Suhaib Janjua" loggedin.

Five color-coded levels, a configurable app name on every line, a bounded in-memory history, and a one-call download of the whole session as a .log file. Zero dependencies, ~2 KB gzipped.


🔒 Client-side by design

This is the part that matters most: ColorJSLogger has no backend, and never will.

Every entry your app logs is composed in the browser, redacted in the browser, and appended to a plain JavaScript array held in page memory. That array is the entire storage layer. When you call downloadLogs(), the library stitches those entries into a string, wraps it in a Blob, and triggers a normal browser download.

   jslogger.info(…)                                    downloadLogs()
          │                                                   │
          ▼                                                   ▼
   ┌──────────────┐    redact at    ┌──────────────────┐   ┌────────────┐
   │ compose line │ ─── capture ──▶ │  _entries[]      │──▶│ Blob → <a> │──▶ 💾 user's disk
   └──────────────┘                 │  (page memory,   │   └────────────┘
          │                         │   max 10000)     │
          ▼                         └──────────────────┘
   🖥️  console.log

| | | | --- | --- | | 🚫 No network | The source contains no fetch, no XMLHttpRequest, no WebSocket, no sendBeacon, no image beacons. Nothing is transmitted anywhere. | | 🚫 No persistence | Nothing is written to localStorage, sessionStorage, IndexedDB, or cookies. Close the tab and the buffer is gone. | | 🚫 No telemetry | No analytics, no phone-home, no version check. | | ✅ Memory only | A bounded string[] in page memory — capped at 10000 entries (~1 MB), oldest evicted first, so a long-lived SPA cannot leak memory through it. | | ✅ User-initiated download | The file is produced locally via Blob + URL.createObjectURL(). The user chooses to save it; it never touches a server. | | ✅ Zero dependencies | Nothing in your bundle but this library — no transitive supply chain. |

Note: because the buffer is ordinary page memory, it is readable by devtools and by any script running on the page, and the downloaded file is plaintext on disk. That is a deliberate trade — see Security.


📦 Install

npm install colorjslogger

The package has a default export. There is no named export of the logger.

import jslogger from 'colorjslogger';

jslogger.setAppName('JSLogger');
jslogger.info('Boot', 'Application started');

TypeScript definitions ship with the package and are picked up automatically — no @types install needed.

The UMD build exposes the global ColorJSLogger (with jslogger as a legacy alias):

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jslogger.min.js"></script>
<script>
  ColorJSLogger.setAppName('JSLogger');
  ColorJSLogger.info('Boot', 'Application started');
</script>
<script type="module">
  import jslogger from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/jslogger.esm.js';

  jslogger.info('Boot', 'Imported as an ES module');
</script>

Pin the version rather than using @latest, so a future major cannot change behaviour under a cached page.

Entry points

| Field | File | Format | | --- | --- | --- | | main / require | dist/jslogger.js | UMD (global ColorJSLogger) | | module / import | dist/jslogger.esm.js | ES module | | types | dist/index.d.ts | TypeScript | | — | dist/jslogger.min.js | UMD, minified, for script tags |


🚀 Quick start

import jslogger from 'colorjslogger';

jslogger.setAppName('JSLogger');

let username = 'Suhaib Janjua';
jslogger.info('AuthService', `Authentication successful: User "${username}" loggedin.`);
jslogger.success('AuthService', `Profile for "${username}" synced successfully.`);
jslogger.warning('TokenService', 'Access token expires in 60 seconds.');
jslogger.error('ApiService', 'Failed to parse response from /api/v1/profile.');

jslogger.downloadLogs('session.log');
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Authentication successful: User "Suhaib Janjua" loggedin.
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Profile for "Suhaib Janjua" synced successfully.
Mon Aug 12 2019 22:37:57 | JSLogger | [TokenService] :: Access token expires in 60 seconds.
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: Failed to parse response from /api/v1/profile.

Anatomy of a log line

Every method takes exactly two arguments — a process label and a message.

Mon Aug 12 2019 22:37:57 │ JSLogger │ [AuthService] :: Authentication successful…
└──────── timestamp ────┘ └ appName ┘ └── process ──┘     └────── message ──────┘
      automatic            setAppName()   argument 1          argument 2

📅 Every example in this README is timestamped Mon Aug 12 2019 22:37:57 — the day this logger was first designed and written. Your own logs carry the real time of the call.


📚 API reference

Every example below assumes jslogger.setAppName('JSLogger') has been called.

Logging methods

All seven share one signature: (process, message). message may be a string or an object; objects are redacted by key, then serialised.

| Method | Console | In-memory buffer | | --- | --- | --- | | info | ⚫ black | ✅ | | success | 🟢 green | ✅ | | warning | 🟠 orange | ✅ | | error | 🔴 red | ✅ | | debug | 🔵 blue — only if verbose | ✅ always | | internal | 🚫 never printed | ✅ | | log | ⚫ alias for info | ✅ |


info

General application flow. Printed in black, and recorded.

let username = 'Suhaib Janjua';
jslogger.info('AuthService', `Authentication successful: User "${username}" loggedin.`);
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Authentication successful: User "Suhaib Janjua" loggedin.

success

An operation completed. Printed in green.

jslogger.success('AuthService', `Profile for "${username}" synced successfully.`);
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Profile for "Suhaib Janjua" synced successfully.

warning

Something recoverable needs attention. Printed in orange.

jslogger.warning('TokenService', 'Access token expires in 60 seconds.');
Mon Aug 12 2019 22:37:57 | JSLogger | [TokenService] :: Access token expires in 60 seconds.

error

Something failed. Printed in red.

jslogger.error('ApiService', 'Failed to parse response from /api/v1/profile.');
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: Failed to parse response from /api/v1/profile.

debug

Development detail. Always recorded in the buffer, but only printed to the console when verbose mode is on — so you can keep debug output out of a production console while still capturing it in the downloadable file.

jslogger.debug('TokenService', 'Token refresh initiated.');
// VERBOSE is false by default → nothing in the console…
console.log(jslogger.getEntryCount()); // → 1  …but it IS in the buffer
(no console output)
jslogger.setLevelToVerbose(true);
jslogger.debug('TokenService', 'Token refresh initiated.');
Mon Aug 12 2019 22:37:57 | JSLogger | [TokenService] :: Token refresh initiated.

internal

Recorded in the buffer and included in the download, but never printed to the console. Use it to keep noisy diagnostics out of the user's console view while still capturing them for the log file.

jslogger.internal('AuthService', 'Session 4f2a opened for user 4821; refresh scheduled in 3600s.');
(no console output — but present in getLogs() and the downloaded file)

Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Session 4f2a opened for user 4821; refresh scheduled in 3600s.

⚠️ internal() is a noise-control tool, not a security boundary. The entry still enters the buffer and still appears in the downloaded file. What protects sensitive values is redaction, which runs on every method alike.


log

Alias for info.

let username = 'Suhaib Janjua';
jslogger.log('AuthService', `Authentication successful: User "${username}" loggedin.`);
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Authentication successful: User "Suhaib Janjua" loggedin.

Logging an object

Pass an object instead of a string and it is redacted by key, then serialised:

jslogger.info('AuthService', { user: 'Suhaib Janjua', role: 'admin', password: 'hunter2' });
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: {"user":"Suhaib Janjua","role":"admin","password":"[REDACTED]"}

Configuration

setAppName(name)

Sets the prefix on every line. Default is ColorJSLogger.

jslogger.info('AuthService', 'Connection in progress');   // before
jslogger.setAppName('JSLogger');
jslogger.info('AuthService', 'Connection in progress');   // after
Mon Aug 12 2019 22:37:57 | ColorJSLogger | [AuthService] :: Connection in progress
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: Connection in progress

Non-string or empty input is rejected, keeping the previous name:

jslogger.setAppName('');
ColorJSLogger: Invalid app name provided

setLevelToVerbose(isVerbose)

Controls whether debug() reaches the console. Default false. Coerced with Boolean(), so any truthy value enables it.

jslogger.setLevelToVerbose(true);
jslogger.debug('TokenService', 'Token refresh initiated.');
Mon Aug 12 2019 22:37:57 | JSLogger | [TokenService] :: Token refresh initiated.

setMaxEntries(n) · getMaxEntries()

Sets how many entries are retained before the oldest are evicted. Default 10000, which holds roughly 1 MB at a measured average of ~112 bytes per entry.

jslogger.setMaxEntries(3);
for (let i = 1; i <= 5; i++) jslogger.info('Loop', `entry-${i}`);

console.log(jslogger.getEntryCount());  // → 3
console.log(jslogger.getLogs());
Mon Aug 12 2019 22:37:57 | JSLogger | [Loop] :: entry-3
Mon Aug 12 2019 22:37:57 | JSLogger | [Loop] :: entry-4
Mon Aug 12 2019 22:37:57 | JSLogger | [Loop] :: entry-5

entry-1 and entry-2 were evicted — oldest first. Nonsense values are rejected and leave the current cap untouched:

jslogger.setMaxEntries(0);        // also: -5, '100', null, NaN, Infinity
console.log(jslogger.getMaxEntries());  // → 10000, unchanged
ColorJSLogger: setMaxEntries() expects a positive number; ignoring

Reading and downloading

getLogs()

Returns the retained window as one string, oldest first, newline-separated.

jslogger.info('AuthService', `User "${username}" loggedin.`);
jslogger.info('ApiService', 'Profile loaded.');

console.log(jslogger.getLogs());
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: User "Suhaib Janjua" loggedin.
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: Profile loaded.

getEntryCount()

How many entries are currently retained.

console.log(jslogger.getEntryCount());
2

downloadLogs(filename?)

Builds a Blob from getLogs() and triggers a browser download. Entirely local — no upload, no server, no network. Outside a browser it warns and does nothing.

jslogger.downloadLogs();                // JSLogger-Mon Aug 12 2019 22:37:57.log
jslogger.downloadLogs('session.log');   // explicit filename

The saved file contains exactly the retained window:

Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: User "Suhaib Janjua" loggedin.
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: Profile loaded.

In Node (or any environment without document):

ColorJSLogger: downloadLogs() is only available in browser environments

clearLogs()

Empties the buffer.

jslogger.clearLogs();
console.log(jslogger.getEntryCount());  // → 0
console.log(jslogger.getLogs());        // → '' (empty string)

Metadata

version() · about()

console.log(jslogger.version());
console.log(jslogger.about());
5.0.0
Website: https://github.com/suhaibjanjua/colorjslogger 
 Copyright: (c) 2019-2026 Suhaib Janjua

🛡️ Redaction

Credentials are stripped at capture — before the entry reaches the buffer. The buffer, the console, and the downloaded file all observe the same redacted string. Redaction is never a display-time filter, so there is no code path where a raw value sits in memory waiting to be exported.

Two layers run on every entry.

1. Object payloads — matched by key

Recursive through nested objects and arrays, case-insensitive, matched as substrings so token also catches access_token and refreshToken. Your original object is never mutated, and circular references are handled rather than thrown.

Redacted by default: authorization, x-rainbow-app-auth, x-rainbow-api-key, password/pass, token, secret, code.

jslogger.info('AuthService', { user: 'Suhaib Janjua', role: 'admin', password: 'hunter2' });
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: {"user":"Suhaib Janjua","role":"admin","password":"[REDACTED]"}

2. Composed lines — matched by shape

Catches secrets interpolated into a string before the library ever saw them: key shapes (token=…, password: …) and value shapes (Bearer …, Basic …, bare JWTs).

jslogger.internal('TokenService', `token=${accessToken}`);
Mon Aug 12 2019 22:37:57 | JSLogger | [TokenService] :: token=[REDACTED]
jslogger.internal('ApiService', `Authorization: Basic ${btoa('suhaib:hunter2')}`);
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: Authorization: [REDACTED]

Value-shape rules are deliberately narrow — there is no generic "long base64 blob" heuristic — so UUIDs, git SHAs and request IDs survive intact:

jslogger.info('ApiService', 'requestId=550e8400-e29b-41d4-a716-446655440000');
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: requestId=550e8400-e29b-41d4-a716-446655440000

addRedactedKeys(keys)

Extend the deny-list. Strings match case-insensitively as substrings; pass a RegExp for exact control.

jslogger.addRedactedKeys(['ssn', 'account_number', /^pin$/i]);
jslogger.info('Profile', { user: 'Suhaib Janjua', ssn: '123-45-6789' });
Mon Aug 12 2019 22:37:57 | JSLogger | [Profile] :: {"user":"Suhaib Janjua","ssn":"[REDACTED]"}

setRedactionMode(mode) · setAllowedKeys(keys)

Blacklist mode is the default because it is safe with no configuration. Whitelist mode inverts it: only approved keys survive, including fields you never anticipated.

jslogger.setAllowedKeys(['user', 'status']);
jslogger.setRedactionMode('whitelist');

jslogger.info('ApiService', {
  user: 'Suhaib Janjua',
  status: 200,
  sessionKey: 'sk_live_abc123',
  ip: '10.0.0.4',
});
Mon Aug 12 2019 22:37:57 | JSLogger | [ApiService] :: {"user":"Suhaib Janjua","status":200,"sessionKey":"[REDACTED]","ip":"[REDACTED]"}

Note ip — never deny-listed, but redacted anyway because it is not on the allow-list. That is the point of whitelist mode. Allow-list entries match the whole key, case-insensitively; call setAllowedKeys() before switching modes or every field comes out redacted.

resetRedaction()

Restores the built-in blacklist defaults, discarding any custom keys, mode, or allow-list.

jslogger.resetRedaction();

🔐 Security

Redact-at-capture guarantee. Every log method funnels through one code path that composes the entry, redacts it, and only then writes it to the buffer. The buffer never holds a raw sensitive value, so the downloaded file cannot leak one that redaction matched.

What redaction cannot do. Both layers are key- and shape-based, and no such scheme can catch a secret in free-form prose that carries neither:

jslogger.info('AuthService', 'the password is hunter2');
Mon Aug 12 2019 22:37:57 | JSLogger | [AuthService] :: the password is hunter2

There is no key to match and no recognisable credential shape. Redaction is a safety net for realistic accidents — a stringified request body, an interpolated header, a logged options object — not a guarantee that anything you pass will be sanitised. What you choose to log remains your responsibility.

Known limits, stated plainly:

  • Prose containing a bare secret is not caught (above).
  • Opaque credentials with no key and no distinctive shape — a raw session ID, an API key that looks like any other random string — are not caught. Register their key names via addRedactedKeys(), or use whitelist mode.
  • Substring key matching over-redacts by design: code also matches zipcode. Over-redacting is the safe direction.
  • Redaction applies to what you pass in. It cannot reach a secret already concatenated into a string in a shape it does not recognise.

No client-side logger is a secure store. The buffer is plain JavaScript memory in the user's browser: readable from the console, from any script on the page, and from any extension with page access. The downloaded file is plaintext on disk. Redaction reduces the blast radius of logs you ship around — attached to a support ticket, pasted into a chat — it does not make the browser a safe place to keep secrets. Treat anything reaching the client as already disclosed to the user, and keep real secrets server-side.

To report a vulnerability, see SECURITY.md.


🧩 TypeScript

Definitions ship with the package — nothing extra to install.

import jslogger, { type RedactionMode } from 'colorjslogger';

jslogger.setAppName('JSLogger');
jslogger.setMaxEntries(500);

const mode: RedactionMode = 'whitelist';
jslogger.setRedactionMode(mode);

const retained: number = jslogger.getEntryCount();

🎮 Demo

▶ Live demo — open your browser console to see the colored output, then try the download button.

npm run demo   # node examples/node-demo.js

🤝 Contributing

See CONTRIBUTING.md.

npm install
npm test        # lint + typecheck + jest
npm run build   # UMD + ESM + types into dist/

📄 License

MIT — see LICENSE.md.