Overview
HTTP Ledger provides full TypeScript support with comprehensive type definitions. All configuration options, log data structures, and callbacks are fully typed.Installation
npm install http-ledger
@types package needed.
Basic TypeScript Usage
Import with types
import express from 'express';
import logger, { ApiLoggerOptions, LogData, LogLevel } from 'http-ledger';
const app = express();
Configure with type safety
const options: ApiLoggerOptions = {
logBody: true,
logResponse: true,
maskFields: ['password', 'token'],
};
app.use(logger(options));
Type Definitions
ApiLoggerOptions
Configuration options for the logger middleware:interface ApiLoggerOptions {
logBody?: boolean; // Whether to log the request body
logResponse?: boolean; // Whether to log the response body
logQueryParams?: boolean; // Whether to log query parameters
excludedHeaders?: string[]; // Headers to exclude from logs
getIpInfo?: (ip: string) => Promise<IpInfo>; // Function to get IP info
onLog?: (logData: LogData) => void | Promise<void>; // Callback for log data
maskFields?: string[]; // Fields to mask in logs
customLogLevel?: (logData: LogData) => LogLevel; // Custom log level function
customFormatter?: (logData: LogData) => unknown; // Custom formatter function
autoGenerateRequestId?: boolean; // Auto-generate request IDs
shouldLog?: (req: Request, res: Response) => boolean; // Function to decide whether to log
logSampling?: number; // Sampling rate (0-1)
}
LogData
Structure of the log data object:interface LogData {
method: string;
url: string;
statusCode: number;
timeTaken: number; // Time in milliseconds
requestSize: number; // Size in bytes
responseSize: number; // Size in bytes
timestamp: Timestamp;
headers: Record<string, string | string[] | undefined>;
queryParams: Record<string, unknown>;
body?: unknown; // Optional request body
responseBody?: unknown; // Optional response body
ipInfo?: IpInfo; // Optional IP information
error?: LogError; // Optional error object
userAgent?: string; // User-Agent header
referer?: string; // Referer header
requestContentType?: string; // Request Content-Type
responseContentType?: string; // Response Content-Type
httpVersion?: string; // HTTP protocol version
requestId?: string; // Request ID
hostname?: string; // Server hostname
logLevel?: LogLevel; // Log level
}
LogLevel
Log level type:type LogLevel = 'info' | 'warn' | 'error';
Timestamp
Timestamp information:interface Timestamp {
request: string; // ISO string of request start time
response?: string; // ISO string of response end time
}
IpInfo
IP information structure:interface IpInfo {
ip?: string;
country?: string;
region?: string;
city?: string;
timezone?: string;
[key: string]: unknown; // Additional custom fields
}
LogError
Error information in logs:interface LogError {
message: string;
name?: string;
stack?: string;
code?: string | number;
[key: string]: unknown; // Additional error properties
}
Type-Safe Configuration
Basic Configuration
import express from 'express';
import logger, { ApiLoggerOptions } from 'http-ledger';
const app = express();
const loggerConfig: ApiLoggerOptions = {
logBody: true,
logResponse: true,
excludedHeaders: ['authorization', 'cookie'],
maskFields: ['password', 'token', 'secret'],
};
app.use(logger(loggerConfig));
Custom Log Level Function
import { LogData, LogLevel, ApiLoggerOptions } from 'http-ledger';
const customLogLevel = (logData: LogData): LogLevel => {
if (logData.statusCode >= 500) return 'error';
if (logData.statusCode >= 400) return 'warn';
if (logData.timeTaken > 1000) return 'warn'; // Slow requests
return 'info';
};
const options: ApiLoggerOptions = {
customLogLevel,
};
app.use(logger(options));
Custom Formatter with Type Safety
import { LogData } from 'http-ledger';
interface CustomLogData extends LogData {
environment: string;
service: string;
isSlowRequest: boolean;
requestCategory: 'read' | 'write';
}
const customFormatter = (logData: LogData): CustomLogData => {
return {
...logData,
environment: process.env.NODE_ENV || 'development',
service: 'user-api',
isSlowRequest: logData.timeTaken > 1000,
requestCategory: logData.method === 'GET' ? 'read' : 'write',
};
};
app.use(
logger({
customFormatter,
}),
);
Typed IP Info Function
import { IpInfo } from 'http-ledger';
interface GeoIpResponse {
country_name: string;
city: string;
region: string;
timezone: string;
}
const getIpInfo = async (ip: string): Promise<IpInfo> => {
try {
const response = await fetch(`https://ipapi.co/${ip}/json/`);
const data: GeoIpResponse = await response.json();
return {
country: data.country_name,
city: data.city,
region: data.region,
timezone: data.timezone,
};
} catch (error) {
console.error('Failed to fetch IP info:', error);
return {};
}
};
app.use(
logger({
getIpInfo,
}),
);
Typed onLog Callback
import { LogData } from 'http-ledger';
const onLog = async (logData: LogData): Promise<void> => {
// Type-safe access to log data
const { method, url, statusCode, timeTaken } = logData;
// Send to external service
try {
await fetch(process.env.LOG_ENDPOINT!, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LOG_API_KEY}`,
},
body: JSON.stringify({
timestamp: new Date().toISOString(),
level: logData.logLevel || 'info',
message: `${method} ${url} - ${statusCode} (${timeTaken}ms)`,
data: logData,
}),
});
} catch (error) {
console.error('Failed to send log:', error);
}
};
app.use(
logger({
onLog,
}),
);
Typed shouldLog Function
import { Request, Response } from 'express';
import { ApiLoggerOptions } from 'http-ledger';
const shouldLog = (req: Request, res: Response): boolean => {
// Skip health checks
if (req.path === '/health') return false;
// Skip successful static file requests
if (
req.method === 'GET' &&
req.path.startsWith('/static/') &&
res.statusCode === 200
) {
return false;
}
// Skip OPTIONS requests
if (req.method === 'OPTIONS') return false;
return true;
};
const options: ApiLoggerOptions = {
shouldLog,
};
app.use(logger(options));
Complete TypeScript Example
import express, { Request, Response, NextFunction } from 'express';
import logger, {
ApiLoggerOptions,
LogData,
LogLevel,
IpInfo,
} from 'http-ledger';
const app = express();
app.use(express.json());
const isDevelopment = process.env.NODE_ENV === 'development';
// Type-safe IP info function
const getIpInfo = async (ip: string): Promise<IpInfo> => {
try {
const response = await fetch(`https://ipapi.co/${ip}/json/`);
const data = await response.json();
return {
country: data.country_name,
city: data.city,
region: data.region,
};
} catch (error) {
return {};
}
};
// Type-safe log level function
const customLogLevel = (logData: LogData): LogLevel => {
if (logData.statusCode >= 500) return 'error';
if (logData.statusCode >= 400) return 'warn';
if (logData.timeTaken > 1000) return 'warn';
return 'info';
};
// Type-safe custom formatter
interface EnhancedLogData extends LogData {
environment: string;
service: string;
isSlowRequest: boolean;
}
const customFormatter = (logData: LogData): EnhancedLogData => ({
...logData,
environment: process.env.NODE_ENV || 'development',
service: 'api-gateway',
isSlowRequest: logData.timeTaken > 1000,
});
// Type-safe onLog callback
const onLog = async (logData: LogData): Promise<void> => {
if (logData.logLevel === 'error') {
// Send to error tracking service
console.error('Error log:', logData.error);
}
};
// Type-safe shouldLog function
const shouldLog = (req: Request, res: Response): boolean => {
return req.path !== '/health' && req.method !== 'OPTIONS';
};
// Type-safe configuration
const loggerOptions: ApiLoggerOptions = {
logBody: isDevelopment,
logResponse: isDevelopment,
logQueryParams: true,
excludedHeaders: ['authorization', 'cookie'],
maskFields: ['password', 'token', 'secret', 'apiKey'],
autoGenerateRequestId: true,
logSampling: isDevelopment ? 1.0 : 0.1,
getIpInfo,
customLogLevel,
customFormatter,
onLog,
shouldLog,
};
app.use(logger(loggerOptions));
// Routes
app.get('/api/users', (req: Request, res: Response) => {
res.json({ users: [] });
});
app.post('/api/users', (req: Request, res: Response) => {
res.status(201).json({ id: 1, ...req.body });
});
// Error handling
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
res.status(500).json({
error: err.message,
stack: isDevelopment ? err.stack : undefined,
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Type Inference
TypeScript automatically infers types in most cases:// Type inference works automatically
app.use(
logger({
// TypeScript knows logData is LogData
customLogLevel: (logData) => {
// Auto-complete and type checking work here
return logData.statusCode >= 500 ? 'error' : 'info';
},
// TypeScript knows ip is string
getIpInfo: async (ip) => {
return { country: 'US' };
},
}),
);
CommonJS with TypeScript
If using CommonJS with TypeScript:import logger = require('http-ledger');
import type { ApiLoggerOptions, LogData } from 'http-ledger';
const options: ApiLoggerOptions = {
logBody: true,
};
app.use(logger(options));
All type definitions are exported from the main package entry point. Import them alongside the default export for full type safety.