Overview
HTTP Ledger allows you to customize how logs are categorized and formatted through two powerful options: customLogLevel and customFormatter. These enable integration with your existing logging infrastructure and custom log requirements.
customLogLevel
customLogLevel
(logData: LogData) => LogLevel
Custom function to determine the log level based on log data. Must return 'info', 'warn', or 'error'.
Default Behavior
Without a custom log level function, HTTP Ledger uses this logic:
// From src/utils/advancedFeatures.ts:50-61
function getDefaultLogLevel ( logData : LogData ) : LogLevel {
if ( logData . error ) {
return 'error' ;
}
if ( logData . statusCode >= 400 ) {
return 'warn' ;
}
return 'info' ;
}
Basic Usage
app . use ( logger ({
customLogLevel : ( logData ) => {
if ( logData . statusCode >= 500 ) return 'error' ;
if ( logData . statusCode >= 400 ) return 'warn' ;
return 'info' ;
}
}));
Advanced Examples
Log Level Based on Response Time
app . use ( logger ({
customLogLevel : ( logData ) => {
// Slow requests are warnings
if ( logData . timeTaken > 1000 ) return 'warn' ;
// Errors
if ( logData . statusCode >= 400 ) return 'error' ;
return 'info' ;
}
}));
Log Level Based on Endpoint
app . use ( logger ({
customLogLevel : ( logData ) => {
// Admin endpoints always warn level
if ( logData . url . startsWith ( '/admin' )) return 'warn' ;
// Critical endpoints errors
if ( logData . url . startsWith ( '/api/payment' ) && logData . statusCode >= 400 ) {
return 'error' ;
}
return 'info' ;
}
}));
Combining Multiple Criteria
import logger , { LogData , LogLevel } from 'http-ledger' ;
app . use ( logger ({
customLogLevel : ( logData : LogData ) : LogLevel => {
// Server errors
if ( logData . statusCode >= 500 ) return 'error' ;
// Client errors on critical paths
if ( logData . statusCode >= 400 && logData . url . includes ( '/checkout' )) {
return 'error' ;
}
// Slow responses
if ( logData . timeTaken > 2000 ) return 'warn' ;
// Large responses
if ( logData . responseSize > 1000000 ) return 'warn' ;
// Client errors on non-critical paths
if ( logData . statusCode >= 400 ) return 'warn' ;
return 'info' ;
}
}));
customFormatter
(logData: LogData) => unknown
Custom function to transform log data before it’s logged. Receives the complete LogData object and should return the formatted data.
Basic Usage
app . use ( logger ({
customFormatter : ( logData ) => ({
... logData ,
environment: process . env . NODE_ENV ,
service: 'user-api' ,
version: '1.0.0'
})
}));
app . use ( logger ({
customFormatter : ( logData ) => ({
// Original log data
... logData ,
// Additional metadata
timestamp: new Date (). toISOString (),
environment: process . env . NODE_ENV ,
service: process . env . SERVICE_NAME ,
version: process . env . APP_VERSION ,
region: process . env . AWS_REGION ,
// Computed fields
isSlowRequest: logData . timeTaken > 1000 ,
isLargePayload: logData . requestSize > 100000 ,
requestType: logData . method === 'GET' ? 'read' : 'write'
})
}));
Restructuring Logs
app . use ( logger ({
customFormatter : ( logData ) => ({
// Flatten structure for log aggregation systems
'@timestamp' : logData . timestamp . request ,
'http.method' : logData . method ,
'http.url' : logData . url ,
'http.status_code' : logData . statusCode ,
'http.response_time_ms' : logData . timeTaken ,
'http.request_size_bytes' : logData . requestSize ,
'http.response_size_bytes' : logData . responseSize ,
'user_agent' : logData . userAgent ,
'client_ip' : logData . ipInfo ?. ip
})
}));
app . use ( logger ({
customFormatter : ( logData ) => ({
'@timestamp' : logData . timestamp . request ,
'@version' : '1' ,
message: ` ${ logData . method } ${ logData . url } ${ logData . statusCode } ` ,
logger_name: 'http-ledger' ,
level: logData . logLevel ?. toUpperCase () || 'INFO' ,
http: {
method: logData . method ,
url: logData . url ,
status_code: logData . statusCode ,
response_time: logData . timeTaken ,
request_size: logData . requestSize ,
response_size: logData . responseSize ,
version: logData . httpVersion
},
user_agent: logData . userAgent ,
geo: logData . ipInfo ,
error: logData . error
})
}));
app . use ( logger ({
customFormatter : ( logData ) => ({
ddsource: 'nodejs' ,
service: 'my-api' ,
hostname: logData . hostname ,
message: ` ${ logData . method } ${ logData . url } completed with status ${ logData . statusCode } ` ,
status: logData . logLevel || 'info' ,
http: {
method: logData . method ,
url: logData . url ,
status_code: logData . statusCode ,
useragent: logData . userAgent
},
network: {
client: {
ip: logData . ipInfo ?. ip
}
},
duration: logData . timeTaken * 1000000 , // Convert to nanoseconds
error: logData . error ? {
message: logData . error . message ,
stack: logData . error . stack
} : undefined
})
}));
Combining Both Options
You can use both customLogLevel and customFormatter together:
const express = require ( 'express' );
const logger = require ( 'http-ledger' );
const app = express ();
app . use ( logger ({
customLogLevel : ( logData ) => {
if ( logData . statusCode >= 500 ) return 'error' ;
if ( logData . statusCode >= 400 ) return 'warn' ;
if ( logData . timeTaken > 1000 ) return 'warn' ;
return 'info' ;
},
customFormatter : ( logData ) => ({
... logData ,
environment: process . env . NODE_ENV ,
service: 'my-api' ,
// Add performance indicators
performance: {
isSlow: logData . timeTaken > 1000 ,
isVeryLarge: logData . responseSize > 1000000
},
// Categorize the request
category: (() => {
if ( logData . url . startsWith ( '/admin' )) return 'admin' ;
if ( logData . url . startsWith ( '/api' )) return 'api' ;
return 'web' ;
})()
})
}));
import express from 'express' ;
import logger , { LogData , LogLevel , ApiLoggerOptions } from 'http-ledger' ;
const app = express ();
const config : ApiLoggerOptions = {
customLogLevel : ( logData : LogData ) : LogLevel => {
if ( logData . statusCode >= 500 ) return 'error' ;
if ( logData . statusCode >= 400 ) return 'warn' ;
if ( logData . timeTaken > 1000 ) return 'warn' ;
return 'info' ;
},
customFormatter : ( logData : LogData ) => ({
... logData ,
environment: process . env . NODE_ENV || 'development' ,
service: 'my-api' ,
performance: {
isSlow: logData . timeTaken > 1000 ,
isVeryLarge: logData . responseSize > 1000000
},
category: logData . url . startsWith ( '/admin' ) ? 'admin' :
logData . url . startsWith ( '/api' ) ? 'api' : 'web'
})
};
app . use ( logger ( config ));
Integration Examples
Winston Logger Integration
const winston = require ( 'winston' );
const winstonLogger = winston . createLogger ({
transports: [ new winston . transports . Console ()]
});
app . use ( logger ({
customLogLevel : ( logData ) => {
// Map to Winston levels
if ( logData . statusCode >= 500 ) return 'error' ;
if ( logData . statusCode >= 400 ) return 'warn' ;
return 'info' ;
},
onLog : ( logData ) => {
winstonLogger . log ( logData . logLevel || 'info' , 'HTTP Request' , logData );
}
}));
Pino Logger Integration
const pino = require ( 'pino' );
const pinoLogger = pino ();
app . use ( logger ({
customFormatter : ( logData ) => ({
req: {
method: logData . method ,
url: logData . url ,
headers: logData . headers ,
remoteAddress: logData . ipInfo ?. ip
},
res: {
statusCode: logData . statusCode ,
responseTime: logData . timeTaken
}
}),
onLog : ( logData ) => {
pinoLogger [ logData . logLevel || 'info' ]( logData );
}
}));
Error Handling
If customLogLevel or customFormatter throw an error, the middleware continues with default behavior. Errors are logged but don’t break request handling.
From src/index.ts:244-246:
// Apply custom log level if provided
if ( customLogLevel ) {
log . logLevel = customLogLevel ( log );
}
// Apply custom formatter if provided
if ( customFormatter ) {
return customFormatter ( log ) as LogData ;
}
Best Practices
Keep It Simple Custom functions should be fast and not throw errors. Avoid async operations.
Test Thoroughly Test custom functions with various log data scenarios, including errors.
Document Format If using custom formatters, document your log format for team members.
Monitor Performance Complex formatters can add overhead. Monitor impact on request timing.
External Integrations Use onLog callback to send formatted logs to external services
API Reference Complete API documentation for all logger options