monten
v0.1.5
Published
Lightweight observability utilities for Express-based Node.js backends (experimental).
Maintainers
Readme
⚠️ Experimental: APIs and behavior may change in minor versions. Use with caution in critical systems.
Features
| Feature | Description |
| ------------------------- | ----------------------------------------------------- |
| 🕐 HTTP Timing | Measure total request latency with drop-in middleware |
| 📊 Error Tracking | Automatic error rate tracking with stack traces |
| 🗄️ DB Timing | Explicit instrumentation via withDbTiming helper |
| 🔗 Request Context | Per-request scoping with AsyncLocalStorage |
| 📝 Structured Logging | JSON-only logs with requestId correlation |
| 📦 Metrics Batching | In-memory buffer with periodic flushing |
| 🔌 Zero Coupling | Fully removable without breaking your app |
Technologies
| Technology | Purpose | | --------------------- | ---------------------------------------------------- | | TypeScript | Type-safe implementation with full declaration files | | Node.js | Runtime environment (v18+) | | AsyncLocalStorage | Native request-scoped context propagation | | Express | Compatible middleware architecture | | JSON | Structured logging format |
Installation
npm install montenyarn add montenpnpm add montenQuick Start
import express from 'express'
import { initObservability, monten } from 'monten'
const app = express()
// Initialize observability
initObservability({
serviceName: 'users-service',
enableLogging: true,
enableMetrics: true,
})
// Add middleware (single line!)
app.use(monten())
app.get('/health', (_req, res) => {
res.status(200).json({ ok: true })
})
app.listen(3000)Database Timing
Use withDbTiming to measure any async operation — works with Prisma, Sequelize, raw SQL, Redis, HTTP calls, etc.
import { withDbTiming } from 'monten'
import { prisma } from './prismaClient'
app.get('/users/:id', async (req, res, next) => {
try {
const user = await withDbTiming(() =>
prisma.user.findUnique({ where: { id: req.params.id } })
)
if (!user) {
return res.status(404).json({ error: 'Not found' })
}
res.json(user)
} catch (err) {
next(err)
}
})API Reference
initObservability(config)
Initialize the observability system. Call once at app startup.
initObservability({
serviceName: 'my-service', // Service identifier in logs/metrics
enableLogging: true, // Enable structured JSON logging
enableMetrics: true, // Enable metrics collection & flushing
metricsFlushIntervalMs: 10000, // Optional: flush interval (default: 10s)
})monten()
Express middleware that captures request timing, errors, and emits logs/metrics.
app.use(monten())withDbTiming<T>(fn: () => Promise<T>): Promise<T>
Wrap any async function to measure its duration and accumulate it in the request context.
const result = await withDbTiming(() => db.query('SELECT * FROM users'))Log Output
Each request produces a structured JSON log entry:
{
"level": "info",
"message": "http_request",
"timestamp": 1704307200000,
"serviceName": "users-service",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"fields": {
"method": "GET",
"path": "/users/123",
"statusCode": 200,
"latencyMs": 45,
"dbTimeMs": 12
}
}Architecture
┌─────────────────────────────────────────────────────────┐
│ Express App │
├─────────────────────────────────────────────────────────┤
│ monten() │
│ ┌───────────────────────────────────────────────────┐ │
│ │ AsyncLocalStorage Context │ │
│ │ • requestId │ │
│ │ • startTimeMs │ │
│ │ • dbTimeMs (accumulated) │ │
│ └───────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Route Handlers │
│ └─> withDbTiming(() => prisma.user.find(...)) │
├─────────────────────────────────────────────────────────┤
│ On Response Finish: │
│ • Structured JSON log → stdout │
│ • Metric record → in-memory buffer │
├─────────────────────────────────────────────────────────┤
│ Background Flusher (setInterval) │
│ • Drains buffer periodically │
│ • Sends to pluggable sink │
└─────────────────────────────────────────────────────────┘Removing the Library
This library is designed to be fully removable:
- Remove
initObservability()call - Remove
app.use(monten()) - Remove
withDbTiming()wrappers (just call the inner function directly)
Your application will continue to function normally.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feat/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feat/amazing-feature) - Open a Pull Request
License
MIT © 2026
