npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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: traceId is resolved from context (requestLogger or runWithContext), 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 Success
    • 4 = 4xx Client Error
    • 5 = 5xx Server Error
  • YY (2 digits): Service/Module

    • 00 = Common/Unclassified
    • 10 = Logging Middleware
    • 20 = TokenLocker Service
    • 30 = DataProvider Service
    • 40 = Gateway Service
    • 50-99 = Reserved for future services
  • ZZ (2 digits): Error Category

    • 01 = Validation
    • 02 = Authentication
    • 03 = Authorization
    • 04 = Not Found
    • 05 = Conflict
    • 06 = Business Logic
    • 07 = Database
    • 08 = External Service
    • 09 = Network
    • 10 = Configuration
    • 11 = Message Queue
    • 12 = Transaction
    • 50 = Internal
    • 99 = Unknown
  • AA (2 digits): Specific Error (01-99)

Trailing zeros indicate broader categories:

  • 5000000 = Server error (broadest)
  • 5200000 = Server error in TokenLocker service
  • 5200100 = Server error in TokenLocker, validation category
  • 5200101 = 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      // 5005001

TokenLocker 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        // 5201203

DataProvider 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    // 5301201

Gateway 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  // 5405001

Error 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 Request
  • 401 -> Unauthorized
  • 403 -> Forbidden
  • 404 -> Not Found
  • 409 -> Conflict
  • 422 -> Unprocessable Entity
  • 500 -> 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-in notFoundHandler returns COMMON_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: false

Adding 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