console.log is fine for debugging. It's not fine for production.
Alepha has a structured logging system that knows where logs come from, supports log levels, and outputs pretty colors in dev or JSON in production.
Use $logger() in any service:
1import { $logger } from "alepha/logger"; 2 3class PaymentService { 4 log = $logger(); 5 6 async processPayment(orderId: string, amount: number) { 7 this.log.info("Processing payment", { orderId, amount }); 8 9 try {10 await this.stripe.charge(amount);11 this.log.info("Payment successful", { orderId });12 } catch (error) {13 this.log.error("Payment failed", { orderId, error });14 throw error;15 }16 }17}
Output in development (pretty):
[14:32:05.123] INFO <app.PaymentService> Processing payment { orderId: "ord_123", amount: 99.99 }
[14:32:05.456] INFO <app.PaymentService> Payment successful { orderId: "ord_123" }
Output in production (JSON, one line):
1{"level":"info","time":"2024-01-15T14:32:05.123Z","module":"app.PaymentService","msg":"Processing payment","orderId":"ord_123","amount":99.99}
JSON logs are parseable by Datadog, Grafana Loki, CloudWatch, etc.
By default, logs go to stdout and are synchronous. This means every log.info() call blocks until the message is written.
For development, this is fine. You want logs immediately, and the performance hit is negligible.
For high-throughput production systems, synchronous logging can become a bottleneck. An async destination with buffering is planned for a future release. For now, if you're logging thousands of messages per second, consider:
LOG_LEVEL=warn)Alepha supports six levels (from quietest to noisiest):
1// SILENT - nothing logged2this.log.error("Something broke"); // ERROR - red3this.log.warn("Something unexpected"); // WARN - yellow4this.log.info("Normal operations"); // INFO - default visible5this.log.debug("Detailed debugging info"); // DEBUG - hidden by default6this.log.trace("Very detailed tracing"); // TRACE - extremely verbose
Set via LOG_LEVEL environment variable:
LOG_LEVEL=debug npx alepha dev # see debug and above
LOG_LEVEL=warn npx alepha start # quiet production, only warnings+
LOG_LEVEL=trace npx alepha dev # see absolutely everything
LOG_LEVEL=silent npx alepha start # complete silence
This is where Alepha shines. You can set different levels for different modules:
# global info, but debug for payments module
LOG_LEVEL="info,app.payments:debug" npx alepha dev
# multiple module configs
LOG_LEVEL="alepha.core:trace,app.users:debug,warn" npx alepha dev
# use equals or colon - both work
LOG_LEVEL="app.payments=debug,info" npx alepha dev
# semicolon separator also works
LOG_LEVEL="alepha:trace;app:debug;info" npx alepha dev
Need to debug an entire namespace? Use wildcards:
# all alepha.* modules at debug level
LOG_LEVEL="alepha.*:debug,info" npx alepha dev
# all modules ending in .test at silent
LOG_LEVEL="*.test:silent,info" npx alepha dev
The logger uses first match wins with prefix matching:
1// config: "alepha:debug,alepha.core:trace,info"2 3// "alepha.core" matches "alepha" first → DEBUG (not TRACE!)4// order matters: put specific patterns first5 6// better: "alepha.core:trace,alepha:debug,info"7// now "alepha.core" matches first → TRACE
This is huge. You can turn on trace logging for just the one service causing problems without drowning in noise from the rest of the app.
The logger automatically includes the module name. If you're using $module, logs show the full path:
1const PaymentsModule = $module({2 name: "app.payments",3 services: [PaymentService, RefundService],4});5 6// logs from PaymentService show: <app.payments.PaymentService>
This makes it trivial to filter logs in production:
grep "app.payments" /var/log/myapp.log
Want to log every HTTP request? Alepha does it automatically when you enable the server module. You'll see:
[14:32:05.100] INFO <alepha.server> --> GET /api/users
[14:32:05.150] INFO <alepha.server> <-- GET /api/users 200 50ms
Winston:
1// lots of configuration 2const logger = winston.createLogger({ 3 level: 'info', 4 format: winston.format.combine( 5 winston.format.timestamp(), 6 winston.format.json() 7 ), 8 transports: [new winston.transports.Console()], 9});10 11logger.info("message", { orderId: "123" });12// doesn't know which service it came from
Pino:
1// fast, but manual setup2const logger = pino({ level: 'info' });3const childLogger = logger.child({ module: 'PaymentService' });4childLogger.info({ orderId: "123" }, "message");
Alepha:
1// zero config, automatic context2class PaymentService {3 log = $logger(); // knows it's PaymentService4 5 doThing() {6 this.log.info("message", { orderId: "123" });7 // output includes module name automatically8 }9}
1// log when entering/exiting important operations2async processOrder(order: Order) {3 this.log.info("Processing order", { orderId: order.id });4 5 // ... lots of internal work ...6 7 this.log.info("Order processed", { orderId: order.id, duration: elapsed });8}
1// bad 2this.log.error("Payment failed"); 3 4// good 5this.log.error("Payment failed", { 6 orderId, 7 amount, 8 customerId, 9 errorCode: error.code,10});
1// bad - password in logs2this.log.info("User login", { email, password });3 4// good5this.log.info("User login", { email });
1this.log.trace("Entering function with args", { args }); // extreme detail2this.log.debug("Cache hit for key xyz"); // debugging info3this.log.info("User created"); // normal operations4this.log.warn("Rate limit approaching"); // concerning but not broken5this.log.error("Database connection lost"); // something broke
| Need | Solution |
|---|---|
| Basic logging | $logger() |
| JSON logs for production | LOG_FORMAT=json (default in prod) |
| Debug specific module | LOG_LEVEL="module.name:debug,info" |
| Debug with wildcards | LOG_LEVEL="app.*:debug,info" |
Logging is unglamorous but essential. Alepha makes it structured, filterable, and production-ready out of the box.