lightlogger docs

← Back to dashboard

Quickstart

pip install lightlogger
import lightlogger
lightlogger.start()          # UI now live at http://127.0.0.1:4356
lightlogger.info("user logged in")

Two lines to a live, dark-theme log dashboard in your browser. Zero config, zero third-party dependencies.

API reference

start(port=4356, host="127.0.0.1", max_logs=5000, capture_logging=True, open_browser=False)

Starts the dashboard server on a background thread. Calling it again while already running is a no-op, not an error.

lightlogger.start(port=8080, open_browser=True)

stop()

Stops the server and detaches the logging handler. Mostly useful for tests and notebooks — most scripts never need to call this.

lightlogger.stop()

debug(msg, data=None)

Logs a debug-level message.

lightlogger.debug("cache miss", data={"key": "user:42"})

info(msg, data=None)

Logs an info-level message.

lightlogger.info("user logged in")

warn(msg, data=None)

Logs a warn-level message.

lightlogger.warn("retrying after timeout")

error(msg, data=None)

Logs an error-level message.

lightlogger.error("payment failed", data={"order_id": 123})

var(name, value)

Logs any variable as an expandable JSON blob under name.

lightlogger.var("cart", cart_dict)

request(method, url, status, duration_ms)

Logs one HTTP request/response as a single formatted line. Level follows the status code: <400 info, 4xx warn, 5xx error.

lightlogger.request("GET", "/api/users", 200, 12.4)
lightlogger.request("POST", "/api/orders", 500, 812.0)  # -> error

group(name)

Context manager that groups related log lines into a collapsible tree in the UI. Nests, and is safe across threads and asyncio tasks — each gets its own independent group context.

with lightlogger.group("process_order #4821"):
    lightlogger.info("validating cart")
    lightlogger.info("charging payment", data={"amount": 49.99})
    with lightlogger.group("send_notifications"):
        lightlogger.info("email sent")
        lightlogger.info("sms sent")

They render as a collapsible tree — click to expand, nested groups indent, a red badge appears if anything inside failed.

FAQ

Port 4356 is already in use — what happens?
lightlogger auto-increments to the next free port (4357, 4358, …) and prints the actual bound URL to stdout. You never need to configure this yourself.
Why don't my logging.info(...) calls show up?
start(capture_logging=True) (the default) attaches a handler to the root logger, but Python's root logger defaults to level WARNING — and lightlogger deliberately never forces that level open, since doing so would also unmute your app's other existing handlers, which is more disruptive than "zero code changes" is meant to be. Call logging.basicConfig(level=logging.INFO) yourself if you want .debug()/.info() calls to appear too; .warning() and above always show up out of the box.
Can I run this in production?
No. lightlogger binds to 127.0.0.1 only by default and is meant for local development. It is not an observability platform — don't run it against a public-facing process, and don't leave it running longer than your debugging session needs.
Will lightlogger run my app out of memory?
No — logs live in a fixed-size ring buffer (collections.deque(maxlen=max_logs), default 5000). Once it's full, the oldest record is dropped for every new one that arrives, so memory use is bounded no matter how long your app runs.