@devminister/applog-client
v0.0.3
Published
Application log client for the Monitoring platform. Buffered, batched delivery with retry. Works standalone or as a NestJS module.
Maintainers
Readme
@devminister/applog-client
Application log client for the Monitoring platform. Send structured business logs (auth, payments, orders, notifications) with buffered, batched delivery, retry, and duration tracking.
Works as a standalone Node.js client or as a NestJS module with automatic HTTP request logging.
Install
pnpm add @devminister/applog-clientQuick Start
Standalone (Express, Fastify, any Node.js app)
import { createAppLogClient } from '@devminister/applog-client';
const appLog = createAppLogClient({
apiUrl: 'https://monitor.example.com',
clientId: process.env.MONITOR_CLIENT_ID!,
clientSecret: process.env.MONITOR_CLIENT_SECRET!,
});
// Simple logging
appLog.info('auth', 'user.login', {
message: 'User logged in',
userId: 'user-123',
metadata: { method: 'oauth', provider: 'google' },
tags: ['web'],
});
appLog.error('payment', 'charge.failed', {
message: 'Card declined',
userId: 'user-123',
metadata: { reason: 'insufficient_funds', amount: 99.99 },
});
// Duration tracking
const end = appLog.startTimer('payment', 'payment.charge');
await processPayment(order);
end({ userId: order.userId, metadata: { orderId: order.id } });
// Shutdown (flushes remaining buffer)
process.on('SIGTERM', async () => {
await appLog.shutdown();
process.exit(0);
});NestJS Module
// app.module.ts
import { AppLogModule, AppLogInterceptor } from '@devminister/applog-client';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
imports: [
AppLogModule.register({
apiUrl: process.env.MONITOR_API_URL,
clientId: process.env.APPLOG_CLIENT_ID,
clientSecret: process.env.APPLOG_CLIENT_SECRET,
// HTTP interceptor options
excludePaths: ['/api/health', '/api/health/system'],
logBodies: true,
logHeaders: true,
maxBodySize: 10240,
}),
],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: AppLogInterceptor,
},
],
})
export class AppModule {}With ConfigService (async)
AppLogModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
apiUrl: config.get('MONITOR_API_URL'),
clientId: config.get('APPLOG_CLIENT_ID'),
clientSecret: config.get('APPLOG_CLIENT_SECRET'),
defaultCategory: 'my-service',
defaultTags: ['production'],
excludePaths: ['/api/health'],
logBodies: true,
logHeaders: true,
}),
})Inject and use in services
import { AppLogNestService } from '@devminister/applog-client';
@Injectable()
export class PaymentService {
constructor(private readonly appLog: AppLogNestService) {}
async charge(order: Order) {
const end = this.appLog.startTimer('payment', 'payment.charge', {
userId: order.userId,
});
try {
const result = await this.stripe.charge(order);
end({ message: `Charged $${order.total}`, metadata: { orderId: order.id } });
return result;
} catch (err) {
this.appLog.error('payment', 'payment.charge.failed', {
message: err.message,
userId: order.userId,
metadata: { orderId: order.id, error: err.message },
});
throw err;
}
}
}HTTP Interceptor
The AppLogInterceptor automatically logs every HTTP request with full context:
- Method, path, status code, duration
- Request/response bodies (with configurable size limit)
- Request headers (content-type, user-agent, origin)
- Client IP and user-agent
- Query parameters
- Error messages and stack traces (for failed requests)
- User ID extracted from
request.user.idorrequest.user.sub - Controller and handler names as context
Sensitive Field Redaction
Request/response bodies are automatically sanitized. The following fields are redacted:
password, token, accessToken, refreshToken, authorization, secret, creditCard, cardNumber, cvv, ssn
Path Exclusion
Use excludePaths to skip logging for health checks or other noisy endpoints:
AppLogModule.register({
// ...credentials
excludePaths: ['/api/health', '/metrics', '/api/docs'],
})Exclude Body Logging
Use excludeLogBodies to skip body capture for specific paths while still logging the request itself (useful for auth endpoints or file uploads):
AppLogModule.register({
// ...credentials
logBodies: true,
excludeLogBodies: ['/api/auth/login', '/api/upload', '/api/users/register'],
})Disabled Mode
When disabled: true, the interceptor still logs to the NestJS console (method, path, status, duration) but does not send logs to the monitoring API.
API
Log Methods
appLog.debug(category, action, options?)
appLog.info(category, action, options?)
appLog.warn(category, action, options?)
appLog.error(category, action, options?)
appLog.fatal(category, action, options?)Options
| Field | Type | Description |
|--------------|----------|------------------------------------------|
| message | string | Human-readable log message |
| metadata | object | Any structured data (JSON) |
| userId | string | User identifier |
| duration | number | Duration in milliseconds |
| tags | string[] | Searchable tags |
| method | string | HTTP method (GET, POST, etc.) |
| path | string | Request path / URL |
| statusCode | number | HTTP status code |
| context | string | Log context (e.g. controller name) |
| pid | number | Process ID |
Timer
const end = appLog.startTimer(category, action, options?);
// ... do work ...
end(extraOptions?); // logs with duration automaticallyLow-level
appLog.push(payload) // Push a raw AppLogPayload
appLog.flush() // Force flush buffer
appLog.shutdown() // Stop timer + flush
appLog.bufferSize // Current buffer lengthConfiguration
| Option | Type | Default | Description |
|--------------------|----------|---------|--------------------------------------------------|
| apiUrl | string | — | Monitoring API base URL (required) |
| clientId | string | — | Environment client ID (required) |
| clientSecret | string | — | Environment client secret (required) |
| batchSize | number | 50 | Auto-flush when buffer reaches this size |
| flushInterval | number | 5000 | Flush interval in ms |
| maxBufferSize | number | 1000 | Max buffer size (oldest logs dropped when full) |
| timeout | number | 10000 | HTTP request timeout in ms |
| maxRetries | number | 3 | Retry attempts with exponential backoff |
| defaultCategory | string | — | Default category when none specified |
| defaultTags | string[] | [] | Tags added to every log |
| disabled | boolean | false | Disable API logging (console logging still works)|
| logger | object | console | Custom logger { debug, warn, error } |
| excludePaths | string[] | [] | Paths to skip in the HTTP interceptor |
| logBodies | boolean | true | Capture request/response bodies |
| logHeaders | boolean | true | Capture request headers |
| maxBodySize | number | 10240 | Max body size to capture (characters) |
| excludeLogBodies | string[] | [] | Paths to skip body capture (request still logged)|
Category & Action Conventions
| Category | Example Actions |
|----------------|--------------------------------------------------------------|
| http | Controller.handler (auto-logged by interceptor) |
| auth | user.login, user.logout, user.register, token.refresh |
| payment | payment.charge, payment.refund, subscription.create |
| order | order.create, order.update, order.cancel, order.ship |
| notification | email.send, sms.send, push.send |
| file | file.upload, file.download, file.delete |
| admin | user.ban, config.update, data.export |
| integration | webhook.receive, api.call, sync.complete |
How It Works
- Logs are buffered in memory
- Buffer is flushed when it reaches
batchSizeor everyflushIntervalms - Logs are sent as a batch POST to
/api/app-logs/ingestwith Basic Auth - On failure, retries with exponential backoff (200ms, 400ms, 800ms)
- Failed batches are re-added to the buffer if space is available
- Buffer has a hard cap (
maxBufferSize) — oldest logs are dropped when full - On shutdown, remaining buffer is flushed
License
MIT
