logctx
v1.0.0
Published
A Pino-based logger with context-aware logging using async_hooks
Maintainers
Readme
📦 logctx
A lightweight, context-aware logger built on top of Pino, using
async_hooksand supporting decorators, DI, and easy context propagation across HTTP or Kafka requests.
🚀 Features
- ✅ Automatically injects context like
userId,traceId, etc., into logs - ✅ Built with
async_hooksfor request-safe storage - ✅ Compatible with any Node.js framework (Express, Kafka, etc.)
- ✅ Optional class decorator
@WithLogger()for automatic logger injection - ✅ Customizable context shape with zero config by default
- ✅ Built-in support for Pino transports like
pino-pretty - ✅ Supports JSON or human-readable log formats via env
- ✅ Optional log-to-file support with file rotation capability
- ✅ Supports a generic logging middleware compatible with any framework
📦 Installation
npm install logctxOr with Yarn:
yarn add logctx🛠️ Basic Usage
1. Import and use the logger
import { runWithContext, logger } from 'logctx';
runWithContext({ userId: '123', traceId: 'xyz' }, () => {
logger.info('This will include userId and traceId');
});logger is a ready-to-use singleton — no need to construct your own ContextualLogger unless you want a separate instance.
2. Use @WithLogger() Decorator
import { WithLogger } from 'logctx';
@WithLogger()
class UserService {
private log: any;
doWork() {
this.log.info('Logged with contextual metadata');
}
}
@WithLogger()works with plainnew UserService()— no DI container required. It also plays nicely with one if you're already usingtypedior similar, since it only adds a non-enumerablelogproperty and doesn't touch the constructor's arguments.
3. Configure Custom Context Getter (Optional)
By default, the logger uses whatever was passed via runWithContext.
But you can override this globally:
import { configureLoggerContext } from 'logctx';
configureLoggerContext(() => ({
userId: 'fallback-user',
tenantId: 'default-tenant'
}));⚙️ Environment Variables
| Variable | Description | Default |
| --------------- | ------------------------------------------------ | ---------------- |
| APP_NAME | Name to appear in logs | logctx |
| LOG_LEVEL | Logging level (debug, info, warn, error) | info |
| LOG_FORMAT | Log output format: json or pretty | json |
| LOG_TO_FILE | Enable file logging: true or false | false |
| LOG_FILE_PATH | File path to store logs (if LOG_TO_FILE=true) | ./logs/app.log |
🧠 API Reference
runWithContext(context, callback)
Wraps a function with the provided context for async propagation.
runWithContext({ userId: 'abc' }, () => {
logger.log.info('userId will be injected');
});configureLoggerContext(getterFn)
Globally defines a fallback context getter if none is set.
configureLoggerContext(() => ({ tenant: 'main', traceId: 'auto' }));ContextualLogger
Provides trace, debug, info, warn, error, and fatal methods that automatically merge in the current context.
const logger = new ContextualLogger();
logger.info('message');
logger.log.info(...)still works for backward compatibility, butlogger.info(...)is the preferred form.
@WithLogger()
Injects this.log into any class. Works well with service or controller patterns.
@WithLogger()
class MyService {
private log: LoggerType; // optional for type hint
doSomething() {
this.log.info('Message with context');
}
}With middleware
Here's how you can integrate logctx into an Express application to enable contextual logging based on headers, query parameters, cookies, and route parameters.
import { logctxMiddleware, logger } from "logctx";
import express from "express";
const app = express();
// One line — sets up a context for every request. No wrapper function needed.
app.use(logctxMiddleware({
headers: ['x-request-id', 'user-agent'],
queries: ['queryParam'],
cookies: ['sessionId'],
params: ['param1', 'param2'],
}));
// Sample route using contextual logger
app.get('/', (req, res) => {
logger.info('Request received');
res.send('Hello from Express with Contextual Logger!');
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
⚠️ The older
createContextLogger(req, next, options)helper still works, but it callsnext()for you — don't callnext()again afterwards, or your handler chain will run twice. PreferlogctxMiddleware(options)above; it avoids the issue entirely.
💡 Use Cases
- Log
userId,tenantId,traceIdacross microservices - Contextual logging for HTTP, WebSocket, Kafka
- Decorator-based logging without boilerplate
- Enforce consistent metadata across logs
- Switch easily between JSON and pretty logs using environment
- Persist logs to disk for production systems
- Use middleware with any NodeJS frameworks.
🧪 Example
import { runWithContext, logger } from 'logctx';
runWithContext({ userId: 'U001' }, () => {
logger.info('User event started'); // includes userId automatically
});👨💻 Contributing
Pull requests, issues, and suggestions welcome!
git clone https://github.com/AjayKrP/logctx.git
npm install
npm run build