@treasurenet/logging-middleware
v1.1.5
Published
A lightweight Express.js middleware for structured logging, request ID tracing, and response logging with log4js.
Readme
@treasurenet/logging-middleware
A production-ready logging middleware for Express.js applications, supporting structured logs, request ID tracking, console output coloring, and automatic response logging using log4js.
✨ Features
- ✅ Automatic request ID tracing via
AsyncLocalStorage - ✅ Colored console logs
- ✅ JSON-formatted file logs
- ✅ Separate
access.log,error.log,app.log - ✅ Middleware for request/response logging
- ✅ Built-in 404 and error handling
- ✅ Pluggable sanitization of sensitive data
- ✅ Standardized 7-digit error code system
- ✅ Custom error classes with automatic HTTP status mapping
- ✅ Error code categorization by service and error type
📦 Installation
npm install @treasurenet/logging-middleware🚀 Usage
1. Basic Express Integration
const express = require('express');
const {
logger,
requestLogger,
errorHandler,
notFoundHandler
} = require('@treasurenet/logging-middleware');
const app = express();
app.use(express.json());
// Logging middleware
app.use(requestLogger());
// Your routes
app.get('/hello', (req, res) => {
logger.info('Hello route called');
res.send({ code: 0, message: 'Hello World' });
});
// 404 handler (should be after routes)
app.use(notFoundHandler);
// Error handler (last)
app.use(errorHandler);
app.listen(3000, () => {
logger.info('Server running on port 3000');
});2. Non-Express Usage (Worker/Cron)
const {
logger,
runWithContext,
normalizeError,
logError,
DATAPROVIDER_SERVER_ERROR
} = require('@treasurenet/logging-middleware');
async function collectMinerData() {
return runWithContext(
'collect-miner-data',
async ({ traceId }) => {
logger.info({ message: 'task running', data: { traceId } });
// your business logic
return { success: true };
},
{
logger,
context: { module: 'miner' },
defaultErrorCode: DATAPROVIDER_SERVER_ERROR.QUERY_ERROR,
}
);
}
async function main() {
try {
await collectMinerData();
} catch (error) {
// Normalize unknown errors into ApplicationError
const appError = normalizeError(
error,
DATAPROVIDER_SERVER_ERROR.QUERY_ERROR
);
// Structured error logging with 7-digit errorCode
logError(logger, appError, {
message: 'miner collection failed',
data: { job: 'miner' },
});
}
}
main();Structured Logger Payload (logger.info(obj))
Both forms are supported:
logger.info('simple text log');
logger.info({
message: 'task running',
latencyMs: 120,
errorCode: 5300703,
taskName: 'collect-block-events',
fromBlock: 100,
toBlock: 200,
});When using object payload, common fields are parsed into top-level log fields:
| Field | Alias | Description |
| --- | --- | --- |
| message | msg | Main log message |
| errorCode | code | 7-digit standardized error code |
| latencyMs | duration | Cost time in milliseconds |
| route | url | Request route or URL |
| method | - | HTTP method |
| status | - | HTTP status code |
| clientIp | - | Client IP |
| userAgent | - | User-Agent |
| contentLength | - | Response length |
Any extra fields are put into data automatically.
Note:
traceIdis resolved from context (requestLoggerorrunWithContext), not from payload top-level fields.
3. Using Standardized Error Codes
const {
ValidationError,
NotFoundError,
DatabaseError,
TOKENLOCKER_CLIENT_ERROR,
TOKENLOCKER_SERVER_ERROR,
logger
} = require('@treasurenet/logging-middleware');
// Example: Validation error
app.post('/plans', async (req, res, next) => {
try {
if (!req.body.planName) {
throw new ValidationError(
'Plan name is required',
TOKENLOCKER_CLIENT_ERROR.INVALID_PLAN_NAME
);
}
// Check if plan already exists
const existingPlan = await Plan.findOne({ name: req.body.planName });
if (existingPlan) {
throw new ConflictError(
'Plan name already exists',
TOKENLOCKER_CLIENT_ERROR.PLAN_NAME_ALREADY_EXISTS
);
}
// Create plan
const plan = await Plan.create(req.body);
res.json({ success: true, data: plan });
} catch (error) {
next(error); // Error handler will process it
}
});
// Example: Not found error
app.get('/plans/:id', async (req, res, next) => {
try {
const plan = await Plan.findById(req.params.id);
if (!plan) {
throw new NotFoundError(
'Plan not found',
TOKENLOCKER_CLIENT_ERROR.PLAN_NOT_FOUND
);
}
res.json({ success: true, data: plan });
} catch (error) {
next(error);
}
});
// Example: Database error
app.post('/plans/:id/lock', async (req, res, next) => {
try {
const result = await createLockedRecord(req.params.id, req.body);
res.json({ success: true, data: result });
} catch (error) {
// Wrap database errors with proper error code
next(new DatabaseError(
'Failed to create locked record',
TOKENLOCKER_SERVER_ERROR.CREATE_LOCKED_RECORD_FAILED,
{ cause: error }
));
}
});🔢 Error Code System
Error Code Format
The middleware implements a 7-digit hierarchical error code system with the format X-YY-ZZ-AA:
X (1 digit): HTTP status category
2= 2xx Success4= 4xx Client Error5= 5xx Server Error
YY (2 digits): Service/Module
00= Common/Unclassified10= Logging Middleware20= TokenLocker Service30= DataProvider Service40= Gateway Service50-99= Reserved for future services
ZZ (2 digits): Error Category
01= Validation02= Authentication03= Authorization04= Not Found05= Conflict06= Business Logic07= Database08= External Service09= Network10= Configuration11= Message Queue12= Transaction50= Internal99= Unknown
AA (2 digits): Specific Error (01-99)
Trailing zeros indicate broader categories:
5000000= Server error (broadest)5200000= Server error in TokenLocker service5200100= Server error in TokenLocker, validation category5200101= Server error in TokenLocker, validation category, specific error #1
Available Error Codes
Common Errors
const { COMMON_CLIENT_ERROR, COMMON_SERVER_ERROR } = require('@treasurenet/logging-middleware');
// Client errors (4xx)
COMMON_CLIENT_ERROR.INVALID_PARAMETER // 4000101
COMMON_CLIENT_ERROR.MISSING_REQUIRED_FIELD // 4000102
COMMON_CLIENT_ERROR.INVALID_FORMAT // 4000103
COMMON_CLIENT_ERROR.MISSING_TOKEN // 4000201
COMMON_CLIENT_ERROR.INVALID_TOKEN // 4000202
COMMON_CLIENT_ERROR.TOKEN_EXPIRED // 4000203
COMMON_CLIENT_ERROR.INSUFFICIENT_PERMISSIONS // 4000301
COMMON_CLIENT_ERROR.ACCESS_DENIED // 4000302
COMMON_CLIENT_ERROR.RESOURCE_NOT_FOUND // 4000401
COMMON_CLIENT_ERROR.RESOURCE_ALREADY_EXISTS // 4000501
// Server errors (5xx)
COMMON_SERVER_ERROR.DATABASE_CONNECTION_FAILED // 5000701
COMMON_SERVER_ERROR.DATABASE_QUERY_FAILED // 5000702
COMMON_SERVER_ERROR.EXTERNAL_API_UNAVAILABLE // 5000801
COMMON_SERVER_ERROR.EXTERNAL_API_TIMEOUT // 5000802
COMMON_SERVER_ERROR.REQUEST_TIMEOUT // 5000901
COMMON_SERVER_ERROR.INTERNAL_SERVER_ERROR // 5005001TokenLocker Service Errors
const { TOKENLOCKER_CLIENT_ERROR, TOKENLOCKER_SERVER_ERROR } = require('@treasurenet/logging-middleware');
// Client errors
TOKENLOCKER_CLIENT_ERROR.INVALID_PLAN_NAME // 4200101
TOKENLOCKER_CLIENT_ERROR.INVALID_ACCOUNT_ADDRESS // 4200102
TOKENLOCKER_CLIENT_ERROR.INVALID_AMOUNT_FORMAT // 4200103
TOKENLOCKER_CLIENT_ERROR.PLAN_NOT_FOUND // 4200401
TOKENLOCKER_CLIENT_ERROR.LOCKED_RECORD_NOT_FOUND // 4200402
TOKENLOCKER_CLIENT_ERROR.PLAN_NAME_ALREADY_EXISTS // 4200501
TOKENLOCKER_CLIENT_ERROR.PLAN_ALREADY_DELETED // 4200601
TOKENLOCKER_CLIENT_ERROR.RECORD_INVALID_STATUS // 4200602
// Server errors
TOKENLOCKER_SERVER_ERROR.CREATE_PLAN_FAILED // 5200701
TOKENLOCKER_SERVER_ERROR.UPDATE_PLAN_STATUS_FAILED // 5200702
TOKENLOCKER_SERVER_ERROR.GET_ABI_FAILED // 5200801
TOKENLOCKER_SERVER_ERROR.BLOCKCHAIN_TRANSACTION_FAILED // 5200802
TOKENLOCKER_SERVER_ERROR.KMS_SIGNING_FAILED // 5200803
TOKENLOCKER_SERVER_ERROR.MQ_CHANNEL_UNAVAILABLE // 5201101
TOKENLOCKER_SERVER_ERROR.TRANSACTION_REVERTED // 5201203DataProvider Service Errors
const { DATAPROVIDER_CLIENT_ERROR, DATAPROVIDER_SERVER_ERROR } = require('@treasurenet/logging-middleware');
// Client errors
DATAPROVIDER_CLIENT_ERROR.INVALID_BLOCK_RANGE // 4300101
DATAPROVIDER_CLIENT_ERROR.INVALID_CONTRACT_ADDRESS // 4300102
DATAPROVIDER_CLIENT_ERROR.ABI_NOT_FOUND // 4300401
DATAPROVIDER_CLIENT_ERROR.CONTRACT_NOT_FOUND // 4300402
// Server errors
DATAPROVIDER_SERVER_ERROR.UPDATE_BLOCK_NUMBER_FAILED // 5300701
DATAPROVIDER_SERVER_ERROR.SAVE_EVENT_FAILED // 5300702
DATAPROVIDER_SERVER_ERROR.WEB3_CONNECTION_FAILED // 5300801
DATAPROVIDER_SERVER_ERROR.CONTRACT_CALL_TIMEOUT // 5300802
DATAPROVIDER_SERVER_ERROR.BLOCKCHAIN_QUERY_TIMEOUT // 5300901
DATAPROVIDER_SERVER_ERROR.TRANSACTION_ROLLED_BACK // 5301201Gateway Service Errors
const { GATEWAY_CLIENT_ERROR, GATEWAY_SERVER_ERROR } = require('@treasurenet/logging-middleware');
// Client errors
GATEWAY_CLIENT_ERROR.INVALID_PARAMETER // 4400101
GATEWAY_CLIENT_ERROR.MISSING_REQUIRED_FIELD // 4400102
GATEWAY_CLIENT_ERROR.INVALID_ACCESS_TOKEN // 4400201
GATEWAY_CLIENT_ERROR.FORBIDDEN_RESOURCE // 4400301
GATEWAY_CLIENT_ERROR.ENDPOINT_NOT_FOUND // 4400401
GATEWAY_CLIENT_ERROR.RESOURCE_NOT_FOUND // 4400402
GATEWAY_CLIENT_ERROR.INVALID_BUSINESS_STATE // 4400601
// Server errors
GATEWAY_SERVER_ERROR.DATABASE_QUERY_FAILED // 5400701
GATEWAY_SERVER_ERROR.RPC_UNAVAILABLE // 5400801
GATEWAY_SERVER_ERROR.EXTERNAL_API_FAILED // 5400802
GATEWAY_SERVER_ERROR.INTERNAL_SERVER_ERROR // 5405001Error Classes
const {
ApplicationError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ConflictError,
BusinessLogicError,
DatabaseError,
ExternalServiceError,
NetworkError,
TransactionError,
InternalServerError
} = require('@treasurenet/logging-middleware');
// All error classes accept: (message, errorCode, options)
// Options: { statusCode, details, cause }
// Example with details
throw new ValidationError(
'Invalid plan configuration',
TOKENLOCKER_CLIENT_ERROR.INVALID_PLAN_NAME,
{
details: {
field: 'planName',
value: req.body.planName,
constraint: 'must be 3-50 characters'
}
}
);
// Example with cause (wrapping another error)
try {
await database.query('...');
} catch (err) {
throw new DatabaseError(
'Failed to query plans',
TOKENLOCKER_SERVER_ERROR.QUERY_FAILED,
{ cause: err }
);
}Error Response Format
When an error is thrown and caught by the error handler, the response will be:
{
"error": "Bad Request",
"errorCode": 4200101,
"message": "Invalid plan name",
"traceId": "550e8400-e29b-41d4-a716-446655440000"
}error message is derived from HTTP status:
400->Bad Request401->Unauthorized403->Forbidden404->Not Found409->Conflict422->Unprocessable Entity500->Internal Server Error
For not found errors:
{
"error": "Not Found",
"errorCode": 4400402,
"message": "Resource not found",
"traceId": "550e8400-e29b-41d4-a716-446655440000"
}Note: the example above is for business-level
NotFoundError(custom code). The built-innotFoundHandlerreturnsCOMMON_CLIENT_ERROR.ENDPOINT_NOT_FOUND(4000402) by default.
For server errors (5xx) in production, the message is hidden:
{
"error": "Internal Server Error",
"errorCode": 5200701,
"traceId": "550e8400-e29b-41d4-a716-446655440000"
}Helper Functions
const {
getHttpStatusFromErrorCode,
getErrorCategory,
getServiceName,
isValidErrorCode
} = require('@treasurenet/logging-middleware');
// Get HTTP status from error code
getHttpStatusFromErrorCode(4200401); // Returns: 404
getHttpStatusFromErrorCode(5200701); // Returns: 500
// Get error category name
getErrorCategory(4200101); // Returns: 'VALIDATION'
getErrorCategory(5200701); // Returns: 'DATABASE'
// Get service name
getServiceName(4200101); // Returns: 'TOKENLOCKER'
getServiceName(5300701); // Returns: 'DATAPROVIDER'
getServiceName(4400101); // Returns: 'GATEWAY'
// Validate error code
isValidErrorCode(4200101); // Returns: true
isValidErrorCode(123); // Returns: falseAdding Custom Service Error Codes
To add error codes for a new service, follow this pattern:
// In your service code
const { errorCodes } = require('@treasurenet/logging-middleware');
// Define your service code (coordinate with other services to avoid conflicts)
const MY_SERVICE_CODE = 50; // Use 50-89 for custom services
// Define custom error codes
const MY_SERVICE_CLIENT_ERROR = {
VALIDATION: 4500100,
INVALID_INPUT: 4500101,
NOT_FOUND: 4500400,
RESOURCE_NOT_FOUND: 4500401,
};
const MY_SERVICE_SERVER_ERROR = {
DATABASE: 5500700,
QUERY_FAILED: 5500701,
EXTERNAL_SERVICE: 5500800,
API_CALL_FAILED: 5500801,
};
module.exports = {
MY_SERVICE_CLIENT_ERROR,
MY_SERVICE_SERVER_ERROR,
};📁 Output Logs
By default, logs are written to:
logs/
├── app.log # all application logs
├── error.log # only error level logs
└── access.log # all incoming requests and responses🔒 Sensitive Fields
Request and response bodies are automatically sanitized for fields like:
password, token, authorization🌐 Nginx in Front (avoid duplicate x-request-id)
When Nginx sits in front of the Express app, make sure it reuses a single trace ID instead of appending another one. Example:
http {
# Prefer client-provided X-Request-ID; otherwise generate one.
map $http_x_request_id $req_id {
"" $request_id;
default $http_x_request_id;
}
server {
...
# Do NOT add_header X-Request-ID ... here (would create a second header)
proxy_set_header X-Request-Id $req_id;
proxy_set_header X-Correlation-Id $req_id;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
...
}
}Why: the middleware already sets x-request-id on the response using the incoming ID. If Nginx also adds the header on the way out, clients will see two values.
📝 License
MIT © Treasurenet Foundation
