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

marketplace-logger

v1.1.1

Published

Structured, isomorphic logging library for the marketplace platform. Runs in Node.js (NestJS backend), Next.js server-side (Server Components, Route Handlers, Middleware), and the browser (Client Components) without any changes to the import.

Downloads

916

Readme

@rush/logging

Structured, isomorphic logging library for the marketplace platform. Runs in Node.js (NestJS backend), Next.js server-side (Server Components, Route Handlers, Middleware), and the browser (Client Components) without any changes to the import.


Installation

pnpm i marketplace-logger

Core concepts

Every log entry is a JSON object written to stdout (Node.js) or console.log (browser) with a fixed shape that log aggregation pipelines (Datadog, CloudWatch, etc.) can parse reliably.

{
  "level": "INFO",
  "category": "Payment",
  "timestamp": "2026-04-29T14:32:00.000Z",
  "caller_function": "PaymentService.createIntent",
  "step": "stripe_call",
  "message": "Creating Stripe PaymentIntent",
  "traceId": "a1b2c3d4-...",
  "service": "marketplace-backend",
  "duration": 142,
  "metadata": { "amount": 50000, "currency": "usd" }
}

The library is built around three primitives:

| Export | What it does | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | BaseStructuredLogger | Base class — extend it to create your logger; constructor takes serviceName, a traceIdResolver function, and an optional writer | | traceStorage | Isomorphic wrapper over AsyncLocalStorage — propagates traceId across async calls on the server; no-op stub in the browser | | startTimer() | Returns a function that, when called, returns elapsed milliseconds — use it to populate duration |


Quick start

1. Extend BaseStructuredLogger

You never instantiate BaseStructuredLogger directly. Create a subclass that provides:

  • The service name — identifies the emitting application in log aggregators
  • A traceIdResolver — a function returning the current traceId or null
  • (Optional) A writer — defaults to process.stdout.write; override for browser environments
import { BaseStructuredLogger, traceStorage } from '@org/logging';

class MyLogger extends BaseStructuredLogger {
  constructor() {
    super('my-service', () => traceStorage.getStore()?.traceId ?? null);
  }
}

export const logger = new MyLogger();

2. Log something

import { LogCategory } from '@org/logging';

logger.info({
  category: LogCategory.Payment,
  callerFunction: 'CheckoutService.createOrder',
  step: 'validate_cart',
  message: 'Cart validation passed',
  metadata: { itemCount: 3 },
});

logger.error({
  category: LogCategory.Blockchain,
  callerFunction: 'TokenService.deliver',
  step: 'static_call',
  message: 'Simulation failed',
  metadata: { reason: 'insufficient balance' },
});

logger.critical({
  category: LogCategory.Blockchain,
  callerFunction: 'TokenService.deliver',
  step: 'onchain_transfer',
  message: 'Transfer halted due to unrecoverable error',
  metadata: { error: new Error('nonce too low') },
});

3. Measure duration

import { startTimer, LogCategory } from '@org/logging';

const elapsed = startTimer();

await stripe.paymentIntents.create({ ... });

logger.info({
  category: LogCategory.Payment,
  callerFunction: 'PaymentService.createIntent',
  step: 'stripe_api_call',
  message: 'PaymentIntent created',
  duration: elapsed(), // milliseconds
});

API reference

BaseStructuredLogger<TCategory>

class BaseStructuredLogger<TCategory extends string = string> {
  constructor(
    serviceName: string,
    traceIdResolver: () => string | null,
    writer?: (json: string) => void,    // default: process.stdout.write
    environment?: AppEnvironment,       // default: "production"
  );

  info(params: LogParams<TCategory>): void;
  debug(params: LogParams<TCategory>): void;  // no-op when environment === "production"
  warn(params: LogParams<TCategory>): void;
  error(params: LogParams<TCategory>): void;
  critical(params: LogParams<TCategory>): void;
}

AppEnvironment

type AppEnvironment = "development" | "staging" | "production";

Controls whether debug() calls are emitted. When environment is "production", calls to debug() are silently dropped. Any other value enables them. The default is "production" — if omitted, debug logs are suppressed.


The generic parameter `TCategory` constrains which category values the logger accepts. It defaults to `string` (permissive). Pass a narrower type to get compile-time exhaustiveness — see [Extending LogCategory](#extending-logcategory).

### `LogParams<TCategory>`

```ts
interface LogParams<TCategory extends string = LogCategory> {
  category: TCategory;   // required — domain classification
  callerFunction: string; // required — "ClassName.methodName"
  step: string;           // required — granular step within the function
  message: string;        // required — human-readable description
  duration?: number;      // optional — milliseconds, use with startTimer()
  metadata?: Record<string, unknown>; // optional — any structured context
}

Metadata notes:

- `Error` values inside `metadata` are serialized to `{ name, message, stack, cause? }`.

LogLevel

enum LogLevel {
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARNING = 'WARNING',
  ERROR = 'ERROR',
  CRITICAL = 'CRITICAL',
}

LogCategory

LogCategory is a const object (not an enum), which makes it spreadable and therefore extensible by consumers — see Extending LogCategory.

const LogCategory = {
  // Shared — backend + frontend
  Blockchain: 'Blockchain',
  BuyBack: 'BuyBack',
  P2P: 'P2P',
  Wallet: 'Wallet',
  Payment: 'Payment',
  Timing: 'Timing',

  // Frontend
  UserInteraction: 'UserInteraction', // clicks, form submissions, CTAs
  Navigation: 'Navigation',           // route changes, page views
  ApiCall: 'ApiCall',                 // HTTP/WS calls from the client
  ClientError: 'ClientError',         // uncaught exceptions, error boundaries
  Auth: 'Auth',                       // login, logout, token refresh
  WalletConnection: 'WalletConnection', // connect/disconnect, network switch
  Marketplace: 'Marketplace',         // NFT/listing views, search, filters
  Checkout: 'Checkout',               // cart, checkout steps, order confirmation
  UI: 'UI',                           // toasts, modals, notifications
  Experiment: 'Experiment',           // feature flags, A/B tests
} as const;

type LogCategory = typeof LogCategory[keyof typeof LogCategory];

traceStorage

const traceStorage: {
  getStore(): TraceContext | undefined;
  run<T>(store: TraceContext, fn: () => T): T;
};

interface TraceContext {
  traceId: string;
}

On Node.js this is backed by AsyncLocalStorage — any getStore() call inside the run() callback (and anywhere down the async call chain) will return the stored context. On the browser it is a no-op stub: run() calls fn() immediately and getStore() always returns undefined.

startTimer

function startTimer(): () => number;

Captures performance.now() at call time. The returned function computes Math.round(now - start) in milliseconds. Works in both Node.js and browser.


Integration guides

NestJS

Create a logger service and a middleware that seeds the trace context at request boundaries. Neither file belongs in this library — they live in the backend repo.

structured-logger.service.ts

import { Injectable } from '@nestjs/common';
import { BaseStructuredLogger, traceStorage } from '@org/logging';
import type { AppEnvironment } from '@org/logging';

@Injectable()
export class StructuredLoggerService extends BaseStructuredLogger {
  constructor() {
    super(
      'marketplace-backend',
      () => traceStorage.getStore()?.traceId ?? null,
      undefined,
      process.env.APP_ENV as AppEnvironment ?? 'production',
    );
  }
}

trace-id.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
import { traceStorage } from '@org/logging';

@Injectable()
export class TraceIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction): void {
    const traceId = (req.headers['x-trace-id'] as string) ?? randomUUID();
    res.setHeader('x-trace-id', traceId);
    traceStorage.run({ traceId }, () => next());
  }
}

logging.module.ts

import { Global, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { StructuredLoggerService } from './structured-logger.service';
import { TraceIdMiddleware } from './trace-id.middleware';

@Global()
@Module({
  providers: [StructuredLoggerService],
  exports: [StructuredLoggerService],
})
export class LoggingModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(TraceIdMiddleware).forRoutes('*');
  }
}

Inject StructuredLoggerService anywhere in the application — because the module is @Global(), no need to import LoggingModule in each feature module.


Next.js (App Router)

Next.js runs code in three distinct environments. Use a different logger instance for each.

Next.js Middleware — propagate traceId as a header

The Edge Runtime does not support AsyncLocalStorage. Propagate the trace ID via a request header so Server Components can read it.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  const traceId = req.headers.get('x-trace-id') ?? crypto.randomUUID();
  const res = NextResponse.next();
  res.headers.set('x-trace-id', traceId);
  res.headers.set('x-forwarded-trace-id', traceId); // readable by Server Components
  return res;
}

Server Components and Route Handlers

// lib/logger.server.ts
import { BaseStructuredLogger, traceStorage } from '@org/logging';
import type { AppEnvironment } from '@org/logging';
import { headers } from 'next/headers';

class NextServerLogger extends BaseStructuredLogger {
  constructor() {
    super(
      'marketplace-frontend',
      () =>
        traceStorage.getStore()?.traceId ??
        headers().get('x-forwarded-trace-id') ??
        null,
      undefined,
      process.env.APP_ENV as AppEnvironment ?? 'production',
    );
  }
}

export const logger = new NextServerLogger();
// app/api/properties/route.ts
import { logger } from '@/lib/logger.server';
import { LogCategory } from '@org/logging';

export async function GET() {
  logger.info({
    category: LogCategory.Payment,
    callerFunction: 'GET /api/properties',
    step: 'handler_start',
    message: 'Fetching property list',
  });
}

Client Components

The writer is overridden to use console.log because process.stdout is not available in the browser. The traceId is read from sessionStorage, where it is seeded by the root layout (see below).

// lib/logger.client.ts
import { BaseStructuredLogger } from '@org/logging';
import type { AppEnvironment } from '@org/logging';

class NextClientLogger extends BaseStructuredLogger {
  constructor() {
    super(
      'marketplace-frontend-client',
      () => sessionStorage.getItem('traceId'),
      (json) => console.log(json),
      process.env.NEXT_PUBLIC_APP_ENV as AppEnvironment ?? 'production',
    );
  }
}

export const clientLogger = new NextClientLogger();
// app/components/BuyButton.tsx
'use client';
import { clientLogger } from '@/lib/logger.client';
import { LogCategory } from '@org/logging';

export function BuyButton() {
  const handleClick = () => {
    clientLogger.info({
      category: LogCategory.Payment,
      callerFunction: 'BuyButton.handleClick',
      step: 'user_interaction',
      message: 'User initiated purchase',
    });
  };
  return <button onClick={handleClick}>Buy</button>;
}

Seeding traceId from server into the browser

Add this to your root layout so that client-side logs share the same traceId as the server-side logs for the same page load:

// app/layout.tsx
import { headers } from 'next/headers';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const traceId = headers().get('x-forwarded-trace-id') ?? '';
  return (
    <html>
      <body>
        <script
          dangerouslySetInnerHTML={{
            __html: `sessionStorage.setItem('traceId','${traceId}')`,
          }}
        />
        {children}
      </body>
    </html>
  );
}

Log output format

All log entries are emitted as a single-line JSON string. The fields are fixed — do not change field names without coordinating with log aggregation pipelines.

| Field | Type | Always present | Description | | ----------------- | ---------------- | -------------- | ------------------------------------------------- | | level | LogLevel | ✅ | DEBUG | INFO | WARNING | ERROR | | category | LogCategory | ✅ | Domain classification | | timestamp | string | ✅ | ISO 8601 UTC | | caller_function | string | ✅ | "ClassName.methodName" | | step | string | ✅ | Granular step within the function | | message | string | ✅ | Human-readable description | | traceId | string \| null | ✅ | Request trace ID; null if no context | | service | string | ✅ | Emitting application name | | duration | number | ❌ | Elapsed milliseconds — only when provided | | metadata | object | ❌ | Arbitrary structured context — only when provided |


Extending LogCategory

LogCategory is a const object, so you can spread it and add your own values. The logger's generic parameter TCategory then constrains the allowed categories to exactly your extended set — no string escape hatch, full autocomplete.

1. Define your extended categories

// lib/log-category.ts
import { LogCategory } from '@org/logging';

export const AppLogCategory = {
  ...LogCategory,
  // Add your own
  NFT: 'NFT',
  Analytics: 'Analytics',
  LiveAuction: 'LiveAuction',
} as const;

export type AppLogCategory = typeof AppLogCategory[keyof typeof AppLogCategory];

2. Create a typed logger subclass

// lib/logger.client.ts
import { BaseStructuredLogger } from '@org/logging';
import type { AppEnvironment } from '@org/logging';

class AppLogger extends BaseStructuredLogger<AppLogCategory> {
  constructor() {
    super(
      'marketplace-frontend',
      () => sessionStorage.getItem('traceId'),
      (json) => console.log(json),
      process.env.NEXT_PUBLIC_APP_ENV as AppEnvironment ?? 'production',
    );
  }
}

export const logger = new AppLogger();

3. Use it — TypeScript enforces only valid categories

import { logger } from '@/lib/logger.client';
import { AppLogCategory } from '@/lib/log-category';

// ✅ Built-in categories still work
logger.info({ category: AppLogCategory.Payment, ... });

// ✅ Your custom categories work
logger.info({ category: AppLogCategory.NFT, ... });
logger.info({ category: AppLogCategory.LiveAuction, ... });

// ❌ Compile error — typos and unknown categories are caught
logger.info({ category: 'Inventado', ... });

Note: if you do not pass a type parameter to BaseStructuredLogger, the default is string — the logger accepts any string as category, which is permissive but loses type safety. Always pass your category type when creating a subclass.


Stability guarantees

The following are considered breaking changes and will be released as a major version:

  • Any change to StructuredLogEntry field names (log aggregation pipelines depend on these)
  • Any change to the BaseStructuredLogger constructor signature
  • Removing or renaming any export from the public index.ts
  • Removing or renaming any existing key in LogCategory or any value in LogLevel

Contributing

pnpm install
pnpm build       # compile to dist/
pnpm test        # run unit tests
pnpm lint        # TypeScript type check

Commits follow Conventional Commits. Versioning follows semver.