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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sillowww/lily

v0.2.0

Published

a small logging library for javascript.

Downloads

6

Readme

lily

a small logging library for javascript.

installation

your local package manager's method of installation.

why lily?

it has a pretty name.

lily is simple, designed for my very simple mind and has, i think? everything a simpler project would need.

all the other logging libs seemed very old and complex to me (i just didn't want to read the docs actually but same thing).

quick start

import logger from "lily";

logger.info("hello world");
logger.error("something went wrong", { userId: 123 });

// create child loggers with scope
const dbLogger = logger.child("database");
dbLogger.info("connected to postgres");

const userLogger = dbLogger.child("users");
userLogger.debug("user query executed", { query: "SELECT * FROM users" });

core concepts

transports

transports are where your logs go. console, files, http endpoints, databases - anywhere you want, lily supports a ConsoleTransport and FileTransport out of the box, but you can create your own.

import { Logger, ConsoleTransport, FileTransport } from "lily";

const logger = new Logger("myapp");
logger.addTransport(new ConsoleTransport());
logger.addTransport(new FileTransport({ filename: "app.log" }));

// this goes to both console and file
logger.info("user logged in");

formatters

formatters control how your logs look. pretty colours for dev, & structured json for prod.

import { ConsoleFormatter, JsonFormatter } from "lily";

// pretty colours for humans
const prettyFormatter = new ConsoleFormatter({
  colourize: true,
  timeFormat: "locale",
});

// structured data for machines
const jsonFormatter = new JsonFormatter();

scopes

scopes give your logs context, they're like breadcrumbs through your application.

const apiLogger = logger.child("api");
const authLogger = apiLogger.child("auth");

// scope: [app/api/auth]
authLogger.info("login attempt", { email: "[email protected]" });

log levels

logger.trace("detailed debugging info");
logger.debug("debug information");
logger.info("general information");
logger.warn("warning messages");
logger.error("error messages");
logger.fatal("critical errors");

set log level globally:

import { Logger, LogLevel } from "lily";
Logger.setGlobalLevel(LogLevel.WARN); // only warnings and above

or via environment:

LOG_LEVEL=DEBUG node app.js

custom transports

creating a custom transport is simple - just impl the Transport interface and wow your logs are going to space or idk wherever you want them to go.

import { Transport, LogEntry } from "lily";

class DiscordTransport implements Transport {
  constructor(private webhookUrl: string) {}

  async log(entry: LogEntry): Promise<void> {
    if (entry.level < LogLevel.ERROR) return; // errors only

    await fetch(this.webhookUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        content: `(${entry.scope.join("/")}) ${entry.message}`,
      }),
    });
  }
}
class DiscordTransport implements Transport {
  constructor(private webhookUrl: string) {}

  async log(entry: LogEntry): Promise<void> {
    if (entry.level < LogLevel.ERROR) return; // errors only

    await fetch(this.webhookUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        content: `[${entry.scope.join("/")}] ${entry.message}`,
      }),
    });
  }
}

logger.addTransport(
  new DiscordTransport("https://discord.com/api/webhooks/..."),
);

custom formatters

formatters convert LogEntry objects to strings:

import { LogEntry, LogLevel } from "lily";

class SimpleFormatter {
  format(entry: LogEntry): string {
    const level = LogLevel[entry.level];
    const scope = entry.scope.length > 0 ? `[${entry.scope.join("/")}] ` : "";
    return `${level}: ${scope}${entry.message}`;
  }
}

// use it
const transport = new ConsoleTransport(new SimpleFormatter());
logger.addTransport(transport);

custom logger setup

build your perfect logger:

import {
  Logger,
  LogLevel,
  ConsoleTransport,
  FileTransport,
  ConsoleFormatter,
  JsonFormatter,
} from "lily";

const logger = new Logger("myapp", {
  level: LogLevel.DEBUG,
  colourize: process.env.NODE_ENV !== "production",
});

// development: pretty console output
if (process.env.NODE_ENV === "development") {
  logger.addTransport(
    new ConsoleTransport(
      new ConsoleFormatter({
        colourize: true,
        timeFormat: "time",
      }),
    ),
  );
}

// production: structured file logs
if (process.env.NODE_ENV === "production") {
  logger.addTransport(
    new FileTransport({
      filename: "app.log",
      formatter: new JsonFormatter(),
    }),
  );
}

export default logger;

environment variables

  • LOG_LEVEL - set log level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
  • NO_COLOUR - disable colours in output
  • NODE_ENV=test - automatically disables colours

metadata and context

add structured data to your logs:

// add metadata to individual logs
logger.info("user action", {
  userId: 123,
  action: "login",
  ip: "192.168.1.1",
});

// create logger with persistent metadata
const requestLogger = logger.withMetadata({
  requestId: "req-abc123",
  userId: 456,
});

requestLogger.info("processing request"); // includes metadata
requestLogger.error("request failed"); // includes metadata

api reference

logger methods

logger.trace(message: string, ...args: unknown[])
logger.debug(message: string, ...args: unknown[])
logger.info(message: string, ...args: unknown[])
logger.warn(message: string, ...args: unknown[])
logger.error(message: string, ...args: unknown[])
logger.fatal(message: string, ...args: unknown[])

logger.child(scope: string | string[], options?: LoggerOptions): Logger
logger.withMetadata(metadata: Record<string, unknown>): Logger

logger.addTransport(transport: Transport): void
logger.removeTransport(transport: Transport): void
logger.clearTransports(): void

static methods

Logger.setGlobalLevel(level: LogLevel): void
Logger.getGlobalLevel(): LogLevel

examples

basic file logging

import { Logger, FileTransport, JsonFormatter } from "lily";

const logger = new Logger("app");
logger.addTransport(
  new FileTransport({
    filename: "application.log",
    formatter: new JsonFormatter(),
  }),
);

logger.info("application started");

multiple transports

import {
  Logger,
  ConsoleTransport,
  FileTransport,
  ConsoleFormatter,
  JsonFormatter,
} from "lily";

const logger = new Logger("api");

// pretty console output
logger.addTransport(
  new ConsoleTransport(new ConsoleFormatter({ colourize: true })),
);

// structured file output
logger.addTransport(
  new FileTransport({
    filename: "api.log",
    formatter: new JsonFormatter(),
  }),
);

logger.info("server starting on port 3000");

request logging middleware

import { logger } from "./logger";

app.use((req, res, next) => {
  const requestLogger = logger.child("http").withMetadata({
    requestId: crypto.randomUUID(),
    method: req.method,
    url: req.url,
  });

  requestLogger.info("request started");

  res.on("finish", () => {
    requestLogger.info("request completed", {
      statusCode: res.statusCode,
    });
  });

  req.logger = requestLogger;
  next();
});

license

gpl-3.0 - see [LICENSE.GPL-3.0] for details.