Logging that helps at 3am

console.log discipline, structured logs with pino, and Python's logging module — plus where those lines actually show up on the Console and Logs pages.

When something breaks while you're asleep, your logs are the only witness. Good logging turns "the server crashed, I have no idea why" into "the database timed out at 03:12, here's the line". This guide covers the discipline that makes logs useful, structured logging in Node and Python, and where every line ends up in the panel.

At a glance
You need A Falix application server (Node.js or Python)
Plan Any
Time Twenty minutes

Where your logs go on Falix

Everything your app prints to standard output shows up live on the Console page — that's your main window while a server runs. Two Falix facts shape how you print:

  • Node flips your server's status to online when it sees a line containing Listening, so a startup log like Listening on port 25565 is both good practice and a status signal.
  • Python buffers output; use print("...", flush=True), or the logging module below, so your lines appear immediately instead of getting stuck.

The Logs page is a separate log-file browser with refresh/auto-refresh, download, share, and search (with optional regex). It shines for games that write log files; for application servers the Console is where the action is. See The Console.

console.log discipline (the free baseline)

console.log is fine — most bots and apps never need more. What matters is how you use it:

  • Log events, not everything. Startup, key actions, and every error — not a line per loop iteration. Noise buries the one line you need.
  • Include context. console.log('user banned', userId, guildId) beats console.log('done'). Log the values that let you reconstruct what happened.
  • Send errors to console.error. It writes to stderr, which keeps errors distinguishable from normal output.
  • Never log secrets. Tokens, passwords and API keys don't belong in logs — logs get shared when you ask for help.
  • Log the error object, not just a message. console.error('login failed', err) prints the stack trace; console.error('login failed') throws away the one clue that matters.

Level up: structured logging in Node with pino

Once an app grows, you'll want to dial verbosity up and down without editing code everywhere. A logger with levels does that. pino is small, fast, and outputs one JSON object per line:

const pino = require('pino');
const log = pino({ level: process.env.LOG_LEVEL || 'info' });

log.info({ port: 25565 }, 'service starting');
log.warn('low on memory');
log.error(new Error('database connection failed'), 'request failed');
log.debug('you only see this when LOG_LEVEL=debug');

Add pino to your package.json and it installs on start. Each call prints a JSON line with a numeric level — info is 30, warn 40, error 50, debug 20 — so {"level":50,...,"msg":"request failed"} is unmistakably an error, and the error's stack is captured as a field. Because the level comes from LOG_LEVEL, you can set that variable to debug when hunting a bug and back to info after, no code change. The Console shows the JSON as-is; for a prettier local view, the pino-pretty formatter exists, but raw JSON is perfectly readable and ideal for searching.

🎯 Good to know: winston is the other popular Node logger and works the same way (levels, structured output) — pick either. The habit (levels + context + errors as objects) matters more than the library.

Structured logging in Python (no dependency)

Python's standard library logging module needs nothing installed. Configure it once at startup:

import logging, sys

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
    stream=sys.stdout,
)
log = logging.getLogger("myapp")

log.info("service starting on port %s", 25565)
log.warning("low on memory")
try:
    risky()
except Exception:
    log.exception("unhandled error while handling request")

That prints timestamped, levelled lines like 2026-07-18 04:03:39 INFO myapp service starting on port 25565, and log.exception(...) inside an except block logs your message plus the full traceback automatically — exactly what you want at 3am. Streaming to sys.stdout means the lines land on the Console immediately, sidestepping the buffering that plagues bare print.

Log levels: which to use when

Level Use it for
error Something failed and needs attention — a crash, a failed request, a rejected login
warn Something's off but survivable — a retry, a missing optional config, high memory
info Normal milestones — startup, a command run, a scheduled job fired
debug Detailed tracing you only want while investigating — off in normal running

The 3am checklist

Whatever tool you use, always log these:

  • A startup line with the port (for Node, it also flips your status online).
  • Every unhandled error, with its stack trace.
  • External calls that can fail — database, another API, a webhook — on both success and failure.
  • Never tokens, passwords, or personal data.

Reading and sharing logs

When you need help, don't retype errors — the panel shares them for you. Use the Share button on the Console (or the Logs page) to hand someone a link to the exact output, and the Logs page's regex search to find one error string in a wall of lines. See Getting help.

Troubleshooting

  • My logs don't appear — Python buffering; use logging (streamed to stdout) or print(..., flush=True).
  • The Console is too noisy to read — raise your log level (set LOG_LEVEL=info or warn) so debug lines drop out.
  • An error shows no cause — you logged a message but not the error object. Log the exception itself (console.error(msg, err) / log.exception(msg)) to capture the stack.
  • A secret leaked into a shared log — rotate it immediately, then stop logging it. See Keep secrets out of Git.

Next steps

Was this guide helpful?