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

analogger

v2.12.0

Published

Js Logger

Readme

nycrc Coverage npm version


Analogger is a logger for Node and Browser environments that is highly customizable. It logs to terminals, browser DOM and inspectors and files.

Installation

npm install analogger

Usage

CommonJs (in Node)

const {anaLogger}  = require("analogger");

ESM (in Node)

import {anaLogger} from "analogger"

As ESM module (In the Browser)

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Demo</title>

 <!-- AnaLogger Theme -->
 <link rel="stylesheet" href="../dist/analogger.min.css">

 <!-- or another AnaLogger Theme 
  <link rel="stylesheet" href="../dist/ana-light.min.css">
 -->
 
</head>
<body>

<div id="analogger" class="analogger">
</div>

<!-- AnaLogger Main -->
<script type="module">
 import {anaLogger} from "../dist/analogger-browser.min.mjs";
 anaLogger.applyPredefinedFormat();
 anaLogger.setOptions({logToDom: "#analogger"});
 anaLogger.log({lid: 100000}, "Test Log example C1");
</script>

</body>
</html>

In the Browser via a module bundler

// Read your module bundler documentation to load a style file
import "./node_modules/analogger/dist/analogger.min.css"
import {anaLogger} from "./node_modules/analogger/dist/analogger-browser.min.mjs";

Preview

Terminal

img_1.png

Inspector

img.png

DOM

img.png

FileSystem

img.png

Remote logging

Preview


Quick start

Start logging with AnaLogger

const {anaLogger}  = require("analogger");
anaLogger.log("something");

Start logging with AnaLogger and override the console

const {AnaLogger}  = require("analogger");
// This call will override the console methods
AnaLogger.startLogger();

// Use console method with new formatting
console.log("something");

Generate another AnaLogger instance

If you want to have different logger with different purposes, you can create multiple instances. You may want to have a logger that writes to a file specific information, while another one sends its logs to a remote server for instance.

// Get the class first
const {AnaLogger}  = require("analogger");
const analogger = new AnaLogger();
analogger.log("something");

Using main instance

// Get the main instance directly
const {anaLogger}  = require("analogger");
analogger.log("something");

Getting AnaLogger main instance

Now, that you have multiple instances, you may want to retrieve the main instance by calling the getInstance method.

const {AnaLogger}  = require("analogger");
const myConsole = AnaLogger.getInstance(0);      // Retrieve first instance => (0)
myConsole.log("something");

API

log() / info() / warn() / error()

Display a message in the terminal or the inspector, depending on where the process runs.

Example 1

anaLogger.log(`I'am some log`);
anaLogger.info(`I'am some log`);
anaLogger.warn(`I'am some log`);
anaLogger.error(`I'am some log`);

Example 2

Display log in red
anaLogger.log({color: "red"}, `I am some log in red`);
Display log in blue on red in bold and underlined
anaLogger.log({color: "blue", bgColor: "red", underline: true, bold: true}, `I am some log in blue on a red 
background in bold and underlined`);

Example 3

Change log color and symbol
anaLogger.log({color: "#00FFAA", symbol: "🎧"}, `I'am some log in red`);

Example 4

Set Log ID
anaLogger.log({lid: 1234}, `I'am some log with log id`);

Example 5

Set symbol by name
anaLogger.log({lid: 1234, symbol: "check"}, `I'am some log with a check symbol`);
anaLogger.log({lid: 1235, symbol: "radioactive"}, `I'am some log with a radioactive symbol`);
anaLogger.log({lid: 1236, symbol: "scissors"}, `I'am some log with some scissors symbol`);

💻 ↴

[01:16:11]           : (1234) ✔  "I'am some log with a check symbol"
[01:16:11]           : (1235) ☢  "I'am some log with a radioactive symbol"
[01:16:11]           : (1236) ✂  "I'am some log with some scissors symbol"

Example 6: The "only" filter

You can filter logs globally so that only specific identifiers are displayed. This supports exact strings, partial matches, or regular expressions.
// Only show logs where the LID or context contains "API"
anaLogger.setOptions({ only: "API" });

anaLogger.log({ lid: "API_123" }, "This matches and will be seen");
anaLogger.log({ lid: "WEB_456" }, "This is blocked and hidden");

Example 7: The "order" option

Enforce a call sequence by assigning an order number to each log entry. If a log with a lower order value appears after one with a higher value, AnaLogger emits a console warning. Calls without an order are transparent and do not affect the sequence.
anaLogger.log({lid: "API",     order: 1},  "This matches");
anaLogger.log({lid: "API_123", order: 2},  "This shows API_123");
anaLogger.log({lid: "WEB_456", order: 35}, "This is blocked");

If the calls arrive out of sequence — for example, API_123 (order 2) appearing after WEB_456 (order 35) — the following warning is printed to the console:

! Order mismatch:  [API_123| order: 2] appeared after [WEB_456| order: 35]

Notes:

  • The check is per-instance, so different AnaLogger instances track their own sequences independently.
  • Calls filtered out by only, targets, or log levels are not counted toward the sequence.
  • Equal order values (non-decreasing) are always allowed; only strictly lower values trigger the warning.

Example 8: The "maxSeen" option

Limit how many times a given lid may be logged. Once the count exceeds maxSeen, every subsequent call emits a console warning. The limit and the counter are both tied to the lid string, so different lids track independently.
anaLogger.log({lid: "API", maxSeen: 1}, "I'm first");   // OK — seen 1 time
anaLogger.log({lid: "API", maxSeen: 1}, "I'm second");  // Warning — seen 2 times
anaLogger.log({lid: "API", maxSeen: 1}, "I'm third");   // Warning — seen 3 times

When the limit is exceeded the following warning is printed to the console:

! MaxSeen exceeded: [API| maxSeen: 1] has been seen 2 time(s)
! MaxSeen exceeded: [API| maxSeen: 1] has been seen 3 time(s)

Notes:

  • The counter is per-instance and per-lid — different lids each have their own count.
  • Calls filtered out by only, targets, or log levels are not counted.
  • maxSeen can be combined with order in the same context object.

Example 9: The "test" option and report()

Attach an inline assertion to any log call. Pass test: false (or a function that returns false) and AnaLogger records a failure and emits a warning immediately. Call report() at any point to print a summary of all pass/fail results.
anaLogger.log({lid: "API_1", test: false},      "I'm first");   // fails — boolean
anaLogger.log({lid: "API_2", test: () => false}, "I'm second");  // fails — function
anaLogger.log({lid: "API_3", test: true},        "I'm third");   // passes
anaLogger.log({lid: "API_4", test: () => true},  "I'm fourth");  // passes

anaLogger.report();

Failing calls emit an immediate warning:

! Test failed: [API_1] I'm first
! Test failed: [API_2] I'm second

report() then prints a bordered summary. If any test failed the output is bold red; if all tests passed it is bold green:

================== ANALOGGER TEST RESULT ================
  Total  : 4
  Passed : 2
  Failed : 2
==========================================================

report() also returns the counts as an object, so you can assert on them programmatically:

const {total, passed, failed} = anaLogger.report();

Notes:

  • test accepts a plain boolean or a zero-argument function — both are resolved to a boolean.
  • Every call that carries a test key is recorded, whether it passes or fails.
  • Calls without a test key are completely transparent and never appear in the report.
  • Results accumulate across the lifetime of the instance. Call anaLogger._testResults = [] to reset between suites if needed.

listSymbols()

Display the list of supported symbols.

Analogger.listSymbols()

💻 ↴

✈   airplane 
⚓   anchor
◀   arrow_backward
↘   arrow_lower_right
↙   arrow_lower_left

... (And more)

alert()

anaLogger.alert(`I'am some log`);

Display the browser native message box if run from it; otherwise, it displays the message in the terminal.


setOptions()

| Options | default | Expect | Description | |-----------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------| | silent | false | boolean | Hide logs from console (not errors) | | hideLog | false | boolean | Same as above (silent has precedence over hideLog) |
| hideError | false | boolean | Hide errors from console |
| hideHookMessage | false | boolean | Hide the automatic message shown when some native console methods are overridden | | hidePassingTests | false | boolean | Hide Live test results |
| keepBreadcrumb | false | boolean | Show brief history graph | | logToDom | false | string (DOM Selector) | display log in a DOM container | | logToFile | false | string (File path) | write log to a file if running from Node | | logToRemote | undefined | string (URL) | Send log to a remote (more info in the next version) | | logMaxSize | 0 | number | Set maximum size for the log file | | compressArchives | false | boolean | Whether to archive and compress the logs after deleting an archive | | compressionLevel | 1 | number | Archive compression level (0 to 9 with 0 = no compression) | | addArchiveTimestamp | true | boolean | Whether to add a timestamp to the generated rotated logs | | logMaxArchives | 3 | number | Maximum number of log files to use | | requiredLogLevel | "LOG" | "LOG" / "INFO" / "WARN" / "ERROR" | Define the log level from which the system can show a log entry | | enableDate | false | boolean | Show date + time (instead of time only) | | enableMillisec | false | boolean | Show milliseconds in the log time (e.g., 12:22:04,555) | | logToLocalStorage | false | boolean | Persist logs in browser localStorage | | logToLocalStorageMax | 50 | number | Max entries to keep in localStorage | | logToLocalStorageSize | 10000 | number | Max size in bytes for localStorage logs | | logToRemoteMaxEntries | undefined | number | Number of entries to keep before relaying the data to the remote | | logToRemoteDebounce | undefined | number | Time in ms between two posts where the data can be sent to the server | | compressArchives | false | boolean | If true, rotates log files into a .tar.gz archive | | compressionLevel | 1 | number | Gzip compression level (0-9) | | addArchiveTimestamp | true | boolean | Appends a consistent timestamp to rotated files | | forceLidOn | false | boolean | Automatically generates a short hash LID if one isn't provided | | only | undefined | string/Regex/Array | Filter logs to show only those matching specific IDs or patterns | | resetMaxSeen | undefined | array | Resets maxSeen counters | | resetOrder | undefined | boolean | Resets the internal tracking for the order sequence | | order | undefined | number | Enforce call order — emits a console warning if a log with a lower order value appears after one with a higher value | | maxSeen | undefined | number | Emits a console warning when the same lid is logged more times than this limit | | test | undefined | boolean / () => boolean | Records a pass/fail result for the log call; emits a warning immediately on failure. Use report() to see the full summary |

// No hook alert message + Log messages in the div #analogger
anaLogger.setOptions({hideHookMessage: true, logToDom: "#analogger"})

Examples

Write logs to file only
anaLogger.setOptions({silent: true, logToFile: logFilePath});
Write logs to both file and console
anaLogger.setOptions({silent: false, logToFile: logFilePath});

Write logs to a remote server
// Use a predefined remote server
anaLogger.setOptions({logToRemote: true});                                           

// Use your remote server (You are responsible for the back-end implementation)
anaLogger.setOptions({logToRemoteUrl: "http://your.server.com/data"});                  

// Batch remote logs (Send when 10 entries are reached OR after 5 seconds)
anaLogger.setOptions({
    logToRemote: true,
    logToRemoteMaxEntries: 10,
    logToRemoteDebounce: 5000
});

Your server must support the POST method.

Example

The data received by your server may look like this:

[
    [{"lid": 123888, "color": "blue"}, "My message 1"], 
    [{"lid": 123999, "color": "#654785"}, "My message 2"]
]

Your server must support the POST method.

Backend implementation example

To receive remote logs from the frontend, you can implement a simple endpoint in your Node.js/Express-like backend:

// Endpoint to receive remote logs from the frontend
router.post("/api/logs", (req, res) => {
    try {
        const logs = req.body;
        if (Array.isArray(logs)) {
            if (logs.length > 0) {
                for (const log of logs) {
                    const context = log[0] || {};
                    const message = log[1];
                    anaLogger.log({
                        lid: "REMOTE00",
                        symbol: "airplane"
                    }, `[${context.lid}][${context.contextName}] ${message}`);
                }
            }
        }
        res.json({ success: true });
    } catch (error) {
        anaLogger.error({ lid: "API10017" }, "Error processing remote logs:", error);
        res.status(status.INTERNAL_SERVER_ERROR).json({ success: false, error: status[status.INTERNAL_SERVER_ERROR] });
    }
});

Write logs to the Remote Logging module

You can also use the Remote-Logging module if you don't want to implement the back-end.

https://www.npmjs.com/package/remote-logging
Note that Remote Logging is free to use, with no license as there is no use in bundle it in an application.

1- Launch a remote from the command line

$> npx remote-logging

# or if AnaLogger is installed globally
$> analogger
If you're on Windows, the system may ask you permission to reach the port. Select private access.
On Linux, You will have to open port 12000 by default. To change it, pass the option --port number to the command above.

2- Copy the server URL in the AnaLogger options (In your client code)

Copy URL


// Enable log to remote
anaLogger.setOptions({logToRemote: true});                  // <= By default, if only this option is set,
                                                            // logToRemote will be set to "http://localhost:12000/analogger"


// Enter server URLs
anaLogger.setOptions({logToRemoteUrl: "http://192.168.1.20:2000/analogger"});           // Standard message       
anaLogger.setOptions({logToRemoteBinaryUrl: "http://192.168.1.20:2000/uploaded"});      // Screenshots            

3- That's it

Every call to anaLogger.log will send the log to your server

anaLogger.log({lid: 1000}, `Example 1`)
anaLogger.lid({lid: 1000}, `Example 1`)
# Test the server is listening from the command line
> curl --request POST 'http://localhost:12000/analogger' --header 'Content-Type: application/json' --data-raw '[[{"lid": 123888, "color": "blue"}, "My message 1"], [{"lid": 123999, "color": "blue"}, "My message 2"]]

Test from CLI

Example

Data received by your server may look like this:

[
    [{"lid": 123888, "color": "blue"}, "My message 1"], 
    [{"lid": 123999, "color": "#654785"}, "My message 2"]
]
Scroll down to the bottom to see a complete example

attachConsole();

Allows to use of the methods defined in the anaLogger instance directly from the console


// Attach methods like keepLogHistory(), hasSeenLid(), etc. to the console
anaLogger.attachConsole();

console.keepLogHistory();

[1, -1, 3, -1, -1].forEach((n) =>
{
    if (n === -1)
    {
      if (!console.hasSeenLid(3000))
      {
         console.log({lid: 3000}, `-1 is not allowed`);          
      }        
    }
})

overrideConsole()

anaLogger.setOptions({silent: true, hideError: false})
console.log(`Log Before override`);
anaLogger.overrideConsole()
console.log(`Log After override`);

Override console.log, console.info and console.warn. If you already have many console.log running in your system, it allows hiding them all in one go.

In this example, the terminal (or inspector) will not show the message "Log After override". All following messages either.


overrideError()

Same as above, but for console.error.


removeOverride() | removeOverrideError()

Remove overridden console methods.


rawLog() | rawInfo() | rawWarn() | rawInfo()

Use native console format after overrides.

anaLogger.overrideConsole();
console.log(`Example 1`);              // <= Will use new format
console.rawLog(`Example 2`);           // <= Will use native format

console.log({raw: true}, `Example 2`); // <= Will use native format

setContext()

Context

Using setContext Method

The setContext method allows you to register a custom context with your AnaLogger instance. This context can then be used to provide specific formatting in your log messages.

Example

// Define a context
anaLogger.setContext('EMAIL_WORK', {
    lid: 1234,
    symbol: 'email',
    color: 'orange'
});

// Use the logger with the new context
anaLogger.log({lid: "EM1234", contextName: "EMAIL_WORK"}, 'This is a log message with the custom context.');
anaLogger.log({lid: "EM1235", contextName: "EMAIL_WORK"}, 'This is another log message using the same formatting as the above.');

setContexts()

Contexts

Context allow grouping logs by functionality.

Examples
const LOG_CONTEXTS = {STANDARD: null, TEST: {color: "#B18904"}, C1: null, C2: null, C3: null, DEFAULT: {}}
const DEFAULT_LOG_TARGETS = {ALL: "ALL", DEV1: "TOM", DEV2: "TIM", USER: "USER"};

// LOG_CONTEXTS will be modified by setContexts
anaLogger.setContexts(LOG_CONTEXTS);

anaLogger.log(LOG_CONTEXTS.C1, `Test Log example C1`);
anaLogger.log(LOG_CONTEXTS.C2, `Test Log example C2`);
anaLogger.log(LOG_CONTEXTS.C3, `Test Log example C3`);

See LOG_CONTEXTS.C1 in this example to categorize the functionality we want to monitor. For instance, LOG_CONTEXTS.INVESTIGATING_TIMER_EFFECT could display output related to something that has to do with a timer.

The "Testing log 2" log will not appear in the console or the terminal.

Preview In a terminal (NodeJs)

img.png

Preview In a browser (ESM)

img_1.png


setDefaultContext()

You can apply a default settings for every output with setDefaultContext. All contexts inherit from the default context.

Examples

anaLogger.setDefaultContext({color: "gray", symbol: "check", contextName: "LOG"});

anaLogger.log({lid: 100000}, `Test Log example C1`);        // <- Use default (grey color and the check symbol)
anaLogger.log({lid: 100010}, `Test Log example C2`);        // <- Use default
anaLogger.log({lid: 100020}, `Test Log example C3`);        // <- Use default

anaLogger.log({contextName: "LOG", lid: 100030, symbol: "cross"}, "Test Log example C4");
anaLogger.log({contextName: "INFO", lid: 100040, symbol: "no_entry"}, "Test Log example C4");
anaLogger.log({contextName: "WARN", lid: 100050, symbol: "raised_hand"}, "Test Log example C4");

💻 ↴

[04:32:38]        LOG: (100000) ✔  "Test Log example C1"
[04:32:38]        LOG: (100010) ✔  "Test Log example C2"
[04:32:38]        LOG: (100020) ✔  "Test Log example C3"
[04:32:38]        LOG: (100030) ❌  "Test Log example C4"
[04:32:38]       INFO: (100040) ⛔  "Test Log example C5"
[04:32:38]       WARN: (100050) ✋  "Test Log example C6"

Callbacks

You can also set some callbacks to be called when a log is about to be displayed.

anaLogger.setDefaultContext({
    /**
     * Allow to modify the context before it is used
     * @param context
     */
    onContext: function (context) {
        context.lid = "MOD12345";
    },
    /**
     * Allow to modify the raw message before it is displayed
     * @param rawMessage
     * @param extras
     * @returns {string}
     */
    onMessage: function (rawMessage, extras) {
        return rawMessage + " ↘";
    },
    /**
     * Allow to modify the formatted message before it is displayed
     * @param formattedMessage
     * @param logCounter
     * @returns {`${string}: ${string}`}
     */
    onOutput: function (formattedMessage, {logCounter}) {

        return `${logCounter}: ${formattedMessage}`;
    }
});

setTargets() / setActiveTarget()

Targets

Targets allow filtering logs by targets. For example, they can be developers, testers, roles, etc. and setActiveTarget() will ignore logs for non-targeted users.

Examples

// "ALL" & "USER" are predefined targets
const LOG_TARGETS = ["GROUP1", "GROUP2", "TOM", "TIM"/*, "ALL", "USER"*/];

// Contexts define how the log should be seen 
const LOG_CONTEXTS = {STANDARD: null, TEST: {color: "#B18904", symbol: "⏰"}, C1: null, C2: null, C3: null, DEFAULT: {}}

// Important. Set the default behaviour.
// By default, no log will be displayed when the target is missing from `anaLogger.log`
anaLogger.setDefaultContext({
    target: DEFAULT_LOG_TARGETS.NONE // or "NONE" (value can be "ALL", "NONE", "USER")
})

// LOG_CONTEXTS will be modified by setContexts
anaLogger.setContexts(LOG_CONTEXTS);

// Allowed targets = "ALL", "TOM", "TIM", "USER"
anaLogger.setTargets("GROUP1", "GROUP2", "TOM", "TIM"/*, "ALL", "USER"*/); 

// Assign an active target
anaLogger.setActiveTarget("TOM");                          // <- You are "TOM"

// Seen because you're TOM
anaLogger.log({target: "TOM"}, `Testing log 1`);           // You will see this

// Not seen (only for TIM)
anaLogger.log({target: "MATT"}, `Testing log 2`);           // You will not see this

// Here we set the allowed targets
anaLogger.setTargets({TOM: "TOM", GROUP1: "GROUP1", GROUP2: "GROUP2"});
anaLogger.setActiveTargets(["TOM", "GROUP1"]);
anaLogger.log({target: "TOM"}, `You will see this`);
anaLogger.log({target: "TIM"}, `You will not see this`);
anaLogger.log({target: "GROUP1"}, `You will see this`);

// By default, the system will not display logs for non-targeted users (@see setDefaultContext above)
anaLogger.setActiveTarget("NonDefinedTarget");
anaLogger.log({lid: "WEB35388"}, `You will not see this`);
anaLogger.log({lid: "WEB35388", target: "NonDefinedTarget"}, `You will not see this`);

// Clear the active target, so we can see everything
anaLogger.setActiveTarget(null);
anaLogger.log({lid: "WEB35388"}, `You will see this`);

// No target defined the active target will see this
anaLogger.log({context: null}, `Testing log 6`);           // You will see this    
anaLogger.log(`Testing log 4`);                            // You will see this

To assign the active target, you could use IPs, read a file, read an environment variable, etc. It is all up to your implementation.

Examples:

IP Based
anaLogger.setTargets({DEV1: "192.168.12.45", DEV: "192.168.12.46"});
// or anaLogger.setTargets("192.168.12.45", "192.168.12.46")
anaLogger.setActiveTarget(require('ip').address());   
File based
// Example 2: File  
anaLogger.setTargets({DEV1: "fg890234ru20u93r2303092pkid0293"});
// or anaLogger.setTargets("fg890234ru20u93r2303092pkid0293")
anaLogger.setActiveTarget(require('./something.json').key);
Fetch
// Example 3: Fetch
anaLogger.setTargets({DEV1: "fg890234ru20u93r2303092pkid0293"});
const key = (await (await fetch('/private-api/me')).json()).key
anaLogger.setActiveTarget(key);
Environment system based
// Example 4: Environment variables
anaLogger.setActiveTarget(process.env.DEVELOPER);     // <= Assuming it has been set on the system host

Note that two targets cannot be overridden: {ALL: "ALL", USER: "USER"}. The system always adds them to the allowed list, so they will still be set even if a call to setTargets() is empty.

// Two implicit targets "ALL" and "USER"  
analogger.setTargets()

loadLids()

Context

Using loadLids for Predefined Log Messages

The loadLids method allows you to register a collection of predefined log message templates, identified by unique Log IDs (Lids). This promotes consistency and simplifies logging by allowing you to reference these templates instead of writing out full messages repeatedly.

Example

With loadLids, you can define a set of log messages that can be reused throughout your application. Instead of scattered, repetitive messages, Log IDs (lid) offer clear, centralized definitions.

// lids.js
const LIDS = {
    API35390: {
        message: "API logging initialized",
        contextName: "TEST",
        color      : "green",
        symbol     : "check"
    },
    API35391: {
        message: "Error API logging initialized",
    },
    API65341: {
        message: "The username doesn't match the userID: {{username1}} !== {{username2}}"
    },
    AUTH1001: {
        message: "Authentication failed for user: {{username}}",
        color: "red",
        symbol: "alert"
    },
    DB205: {
        message: "Database query took {{duration}}ms"
    }
};

module.exports = LIDS;
// main.js
const {anaLogger} = require("analogger");
const ligs = require("./lids.js");
anaLogger.loadLids(LIDS);

anaLogger.log({lid: "API35390", color: "green"}, "API logging about to be initialized");
// => DEFAULT: (API35390) ✔  API logging about to be initialized

anaLogger.log(LIDS.API35390);
// =>  TEST: (API35390) ✔  API logging initialized

anaLogger.error(LIDS.API35391);
// => ERROR: (API35391) ❌  Error API logging initialized

anaLogger.log(LIDS.API65341, {username1: "test", username2: "test2"});
// => DEFAULT: (API65341) ✔  The username doesn't match the userID: test !== test2

anaLogger.log(LIDS.API65341, "Some other messages");
// => DEFAULT: (API65341) ✔  The username doesn't match the userID: {{username1}} !== {{username2}}•Some other messages

getLids

Returns all loaded lids

const lids = anaLogger.getLids();

{
    API35390: {
        message: 'API logging initialized',
                contextName: 'TEST',
                color: 'green',
                symbol: 'check',
                lid: 'API35390',
                callCount: 2,
                callTimes: [ 1745412629482, 1745412629504 ],
                target: [ 'ALL', 'USER', 'NONE' ],
                dates: [ '2025-04-23 13:50:29.482', '2025-04-23 13:50:29.504' ]
    },
    API35391: {
        message: 'Error API logging initialized',
                lid: 'API35391',
                callCount: 1,
                callTimes: [ 1745412629507 ],
                target: [ 'ALL', 'USER', 'NONE' ],
                dates: [ '2025-04-23 13:50:29.507' ]
    },
    API65341: {
        message: "The username doesn't match the userID: {{username1}} !== {{username2}}",
                lid: 'API65341',
                callCount: 2,
                callTimes: [ 1745412629509, 1745412629510 ],
                target: [ 'ALL', 'USER', 'NONE' ],
                dates: [ '2025-04-23 13:50:29.509', '2025-04-23 13:50:29.510' ]
    },
    WEB35382: {
        message: 'Some log message',
                lid: 'WEB35382',
                callCount: 5,
                callTimes: [
            1745412629511,
            1745412629512,
            1745412629514,
            1745412629515,
            1745412629517
        ],
                dates: [
            '2025-04-23 13:50:29.511',
            '2025-04-23 13:50:29.512',
            '2025-04-23 13:50:29.514',
            '2025-04-23 13:50:29.515',
            '2025-04-23 13:50:29.517'
        ]
    }
}

forceLid(true);

Force the system to generate the lid even if it is not defined

anaLogger.forceLid(true);

forceResolveErrorLineCall(true);

Add the stack trace to the error message context

anaLogger.forceResolveErrorLineCall(true);

forceResolveLineCall(true);

Add the stack trace to the log message context

anaLogger.forceResolveLineCall(true);

assert()

You can set some tests directly in the code. It serves as early feedback. It is helpful to guarantee that the code runs straight away rather than waiting for the CI to send its feedback.

anaLogger.assert(1 === 1)
anaLogger.assert(1 === 2)
anaLogger.assert(()=>true, true)
anaLogger.assert((a, b)=> a === b, true, 2, 2)

setErrorHandlerForUserTarget()

It tells whether a log has already been displayed. keepLogHistory must be activated

anaLogger.keepLogHistory()

anaLogger.log({lid: 1234}, `My name is log`)
anaLogger.hasSeenLid(1234)          // true
anaLogger.hasSeenLid(1000)          // false

// Optional
anaLogger.releaseLogHistory()

isBrowser()()

It tells whether the console runs from the browser

anaLogger.isBrowser()

keepLogHistory()

Keeps log entries in memory

anaLogger.keepLogHistory()

releaseLogHistory()

It tells the system to no longer keep log entries in memory

anaLogger.releaseLogHistory()

resetLogHistory()

Delete memorized log entries

anaLogger.resetLogHistory()

getLogHistory()

Returns log entries

anaLogger.getLogHistory()

setErrorHandlerForUserTarget()

When an error is detected and should be seen by your app consumers explicitly (for instance, you want to display a dialogue box to them), you can set a handler here. All other console.error will be working as usual (logging messages).

    anaLogger.setErrorHandlerForUserTarget(function (context/*, ...args*/)
    {
        // Detect whether we are in a browser
        if (context.environment === anaLogger.ENVIRONMENT_TYPE.BROWSER)
        {
            // When the Browser detects an error, users will see this message
            anaLogger.alert(`Users explicitly see this message`)
        }
    });

    anaLogger.setActiveTarget(LOG_TARGETS.USER);
    anaLogger.error({target: LOG_TARGETS.USER}, "Salut user!");     // Display an alert box
    anaLogger.error("Hi user!");                                    // Log the message to the inspector

Multiple instances

It is possible to generate multiple AnaLogger instances so that they can point to different targets.

Example

const AnaLogger = require("analogger");                 // or import AnaLogger from "analogger"
const anaLoggerInstance1 = new AnaLogger();
const anaLoggerInstance2 = new AnaLogger();

anaLoggerInstance1.setOptions({logToDom: ".analogger"});
anaLoggerInstance2.setOptions({logToFile: "./logme.log"});

Automated Tests

When combined with Remote-logger, you can use AnaLogger as a Mocha reporter.

See: https://www.npmjs.com/package/remote-logging

Preview


Take a screenshot

takeScreenshot()

You can take a screenshot via the "html-to-image" plugin (integrated in the module).

"html-to-image" is an external npm package this module uses to take screenshots.

  1. Run a logging server from the command line
$> analogger --port 8754
  1. Report the address displayed in the console into your client code

Browser to Remote Logging view

  1. Set up the client (Browser in our case)

HTML (Client-Side)

  1. Link AnaLogger to the remote
// Load the AnaLogger library ( Available in ./node_modules/analogger/browser/ )
import {anaLogger} from "./browser/ana-logger.mjs";

anaLogger.setOptions({logToRemote: "http://localhost:8754/analogger", loadHtmlToImage: true});
anaLogger.takeScreenshot({selector: "body"});

Plugins

Implement a plugin

// Load anaLogger
import {anaLogger}  from "./ana-logger.mjs";

// Implement
const doSomething = ({node = document.body, container = document.body} = {}) =>
{
    console.log(`Hello plugin`)
};

// Register the plugin
anaLogger.addPlugin("doSomething", doSomething);



Test workflow nycrc Coverage npm version

Analogger is a highly customizable logger for Node and Browser environments. It logs to terminals, browser DOM, files, and remote servers.


🚀 New Features (v1.24+)

💾 Local Storage Persistence (Browser)

You can now persist logs in the browser's Local Storage. This is useful for debugging issues that occur across page refreshes.

// Enable local storage logging
anaLogger.setOptions({ 
    logToLocalStorage: true, 
    logToLocalStorageMax: 100, // Keep last 100 logs
    logToLocalStorageSize: 50000 // Limit to 50KB
});

// Restore logs from previous session after page reload
anaLogger.restoreLogs();

🗄️ Advanced Log Rotation & Archiving (Node) Manage large log files with automatic rotation and .tar.gz compression.

anaLogger.setOptions({
    logToFile: "./app.log",
    logMaxSize: 5 * 1024 * 1024, // 5MB
    logMaxArchives: 5,
    compressArchives: true,      // Archive rotated logs into .tar.gz
    compressionLevel: 6,         // Gzip level (1-9)
    addArchiveTimestamp: true    // Add ISO timestamps to filenames
});

New Helper Methods

restoreLogs()

(Browser only) Reads the history from localStorage and re-prints the logs to the current console. Perfect for diagnosing crashes that trigger a page reload.

getLids()

Returns a deep clone of all loaded Lids, including statistics like callCount, callTimes, and human-readable dates.

forceResolveLineCall(bool)

When enabled, AnaLogger captures the exact file and line number where the log was called and attaches it to the context.

Manual LID strings

If you don't want to pass objects, you can use a shorthand string format:

// AnaLogger will parse this string as a context object
anaLogger.log("lid: USR_LOGIN, color: purple", "User logged in");

Package

📁 package                
│
└───📁 src
│   │
│   └─ 📝 ana-logger.cjs                         ⇽ AnaLogger Node version for CommonJs
│   
└───📁 esm
│   │
│   └─ 📝 ana-logger.mjs                         ⇽ AnaLogger Node version for ES Modules
│   
└───📁 browser (ESM)
│   │─ 📝 ana-logger.mjs                         ⇽ AnaLogger browser version
│ 
└───📁 dist (minified)
│   │─ 📝 analogger.min.css                      ⇽ Default Theme file
│   │─ 📝 ana-light.min.css                      ⇽ Another Theme file
│   │─ 📝 analogger-browser.min.mjs              ⇽ AnaLogger browser version


Snapshots

Snapshots let you capture the set of log IDs (lids) seen at a specific point in time, persist them, and later compare two captures side by side. This is useful for regression checks — verifying that the same lids fire in two different runs, or that a code change does not add or remove unexpected log calls.

Snapshots work in both Node.js (persisted as JSON files under os.tmpdir()/analogger-snapshots/) and the browser (persisted in localStorage).

keepLogHistory() must be called before any logging starts, as snapshots read from the in-memory log history.


startSnapshotProcess()

Marks the start of a snapshot window. Any subsequent takeSnapshot() call will only capture lids logged from this point forward, ignoring everything already in the history. Calling it again moves the window start to the current position.

anaLogger.keepLogHistory();

anaLogger.log({ lid: "BOOT_001" }, "application boot");  // excluded
anaLogger.log({ lid: "BOOT_002" }, "config loaded");     // excluded

anaLogger.startSnapshotProcess();

anaLogger.log({ lid: "WEB_001" }, "request received");   // included
anaLogger.log({ lid: "WEB_002" }, "response sent");      // included

anaLogger.takeSnapshot("SNAP01");   // captures only WEB_001 and WEB_002

If startSnapshotProcess() is never called, takeSnapshot() captures the entire history.


takeSnapshot()

Captures all lids logged since the last startSnapshotProcess() (or since session start) and persists the snapshot under a unique ID.

Returns the array of captured entries so you can inspect or assert on them programmatically.

takeSnapshot(snapshotID, options?)

| Parameter | Type | Default | Description | |---|---|---|---| | snapshotID | string | — | Unique label for this snapshot, e.g. "SNAP01" | | options.messages | boolean | true | Store the log message alongside each lid | | options.context | boolean | true | Store the full context object alongside each lid |

anaLogger.keepLogHistory();
anaLogger.log({ lid: "API_001" }, "initialised");
anaLogger.log({ lid: "WEB_002" }, "request in");

// Capture lids + messages + context (defaults)
const entries = anaLogger.takeSnapshot("SNAP01");
// entries = [{ lid: "API_001", message: "initialised", context: {...} }, ...]

// Capture lids only (lighter footprint)
anaLogger.takeSnapshot("SNAP02", { messages: false, context: false });

In Node.js the snapshot is written to a file and its path is logged:

[AnaLogger] Snapshot "SNAP01" saved - 2 lid(s) captured.
[AnaLogger] Snapshot persisted to: /tmp/analogger-snapshots/analogger_snapshot_default_SNAP01.json

Because the file lives in os.tmpdir(), snapshots persist across Node process restarts — you can take SNAP01 in one run and compare it in a later one.


compareSnapshots()

Loads two previously saved snapshots and prints a side-by-side diff table using console.table, which gives automatic column alignment in both Node.js terminals and browser devtools.

compareSnapshots(snapshotID1, snapshotID2, displayOpts?)

| Parameter | Type | Default | Description | |---|---|---|---| | snapshotID1 | string | — | ID of the first snapshot | | snapshotID2 | string | — | ID of the second snapshot | | displayOpts[0].messages | boolean | false | Show a message column for snapshot 1 | | displayOpts[1].messages | boolean | false | Show a message column for snapshot 2 |

Returns { onlyInSnap1, onlyInSnap2, inBoth } for programmatic use, or null if either snapshot is not found.

Basic comparison (lids only)

anaLogger.compareSnapshots("SNAP01", "SNAP02");
Snapshot comparison: "SNAP01"  vs  "SNAP02"
  SNAP01 captured at 09:04:28 - 3 lid(s)
  SNAP02 captured at 09:04:31 - 4 lid(s)

┌─────────┬─────────┬─────────┐
│ (index) │  SNAP01 │  SNAP02 │
├─────────┼─────────┼─────────┤
│    0    │ API_001 │ API_001 │
│    1    │ WEB_002 │ WEB_002 │
│    2    │ DB_003  │         │
│    3    │         │ WEB_004 │
└─────────┴─────────┴─────────┘

With message columns

anaLogger.compareSnapshots("SNAP01", "SNAP02", [{ messages: true }, { messages: true }]);
┌─────────┬─────────┬──────────────────────┬─────────┬──────────────────────┐
│ (index) │  SNAP01 │   SNAP01 message     │  SNAP02 │   SNAP02 message     │
├─────────┼─────────┼──────────────────────┼─────────┼──────────────────────┤
│    0    │ API_001 │ 'initialised'        │ API_001 │ 'initialised'        │
│    1    │ WEB_002 │ 'request in'         │         │                      │
│    2    │         │                      │ WEB_004 │ 'new endpoint added' │
└─────────┴─────────┴──────────────────────┴─────────┴──────────────────────┘

You can also enable messages for only one side:

// Messages only for SNAP02
anaLogger.compareSnapshots("SNAP01", "SNAP02", [{ messages: false }, { messages: true }]);

In the browser, compareSnapshots also injects a colour-coded HTML widget into the #analogger DOM container (or document.body as a fallback): lids present in both snapshots are shown in neutral, snap1-only rows are highlighted in red, and snap2-only rows in green.

Using the return value

const { onlyInSnap1, onlyInSnap2, inBoth } = anaLogger.compareSnapshots("SNAP01", "SNAP02");

if (onlyInSnap2.length > 0) {
    console.warn("New lids appeared in SNAP02:", onlyInSnap2);
}

Full workflow example

const { anaLogger } = require("analogger");

anaLogger.keepLogHistory();

// --- First run (or first phase) ---
anaLogger.startSnapshotProcess();
anaLogger.log({ lid: "API_001" }, "initialised");
anaLogger.log({ lid: "WEB_002" }, "request received");
anaLogger.log({ lid: "DB_003"  }, "query executed");
anaLogger.takeSnapshot("SNAP01");

// --- Second run (or second phase, e.g. after a code change) ---
anaLogger.startSnapshotProcess();
anaLogger.log({ lid: "API_001" }, "initialised");
anaLogger.log({ lid: "WEB_002" }, "request received");
// DB_003 is gone, WEB_004 is new
anaLogger.log({ lid: "WEB_004" }, "cache hit");
anaLogger.takeSnapshot("SNAP02");

// --- Compare ---
const diff = anaLogger.compareSnapshots("SNAP01", "SNAP02", [{ messages: true }, { messages: true }]);
// diff.onlyInSnap1 => ["DB_003"]   (removed)
// diff.onlyInSnap2 => ["WEB_004"]  (added)
// diff.inBoth      => ["API_001", "WEB_002"]

Changelog

current:
  • Add an option to show the date and time in logs
  • Make the alert method display the lid when detected
  • Keep format when displaying multiple lines for one entry
  • Add order option to detect and warn on out-of-sequence log calls
  • Add maxSeen option to warn when a lid is logged more times than allowed
  • Add test context option and report() method for inline assertions and test summaries
  • Add startSnapshotProcess() to mark the start of a snapshot window
  • Add takeSnapshot(id, opts?) to capture lids at a point in time; persists to localStorage (browser) or os.tmpdir() (Node); returns captured entries
  • Add compareSnapshots(id1, id2, displayOpts?) to diff two snapshots side by side using console.table; optional message columns via [{ messages: true }, { messages: true }]
1.23.2:
  • Remove the message 'No AnaLogger instance found'
1.23.0:
  • Add startLogger method to automatically override the console
1.22.1:
  • Fix screenshot endpoint
  • Fix remote breaking in browser due to misconfiguration
1.22.0:
  • Review display for js primitive types
  • Use native console table
  • Set up AnaLogger as a Mocha reporter
  • Launch a remote from AnaLogger
  • Fix remote-related options passed to the setOptions method
  • Launch a remote server from the CLI
1.21.4:
  • Default the remote to localhost when enabled
1.21.0:
  • Restore the same behaviour as before for getLogHistory
  • Add functionalities to AnaLogger instances
1.20.4:
  • Fix log level applied one level down
  • Update themes
1.20.3:
  • Apply empty log id by default for contexts
  • Apply missing symbols
1.20.2:
  • Add missing files from the build
  • Recolor title bar
  • Add a theme file
  • Add a header and o footer to the layout
  • Use native console table on browsers
  • Apply better environment detection
  • Review logToRemote option
1.20.1:
  • Review sentence failure for screenshot
  • Fix plugin validation
  • Export and validate plugin names
1.20.0:
  • Fix default address for remote binary
  • Fix error on a missing callback
  • Fix log displayed twice
  • Add takeScreenshot plugin