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

@notjustcoders/one-logger-client-sdk

v1.0.13

Published

A comprehensive logging and tracing client SDK with console and remote transports

Readme

@notjustcoders/one-logger-client-sdk

A comprehensive logging and tracing client SDK with console and remote transports for Node.js and browser environments.

Installation

pnpm add @notjustcoders/one-logger-client-sdk

Quick Start

import { initializeOneLogger, logger, wrappedSpan, wrappedObject, wrappedClass } from '@notjustcoders/one-logger-client-sdk';

// Initialize the logger once at app startup
initializeOneLogger({
  name: 'your-app-name',
  description: 'Your application description',
  isDev: true, // Use console transport for development
  logger: {
    batchSize: 10, // Send logs in batches of 10
    flushInterval: 5000 // Flush logs every 5 seconds
  },
  tracer: {
    batchSize: 1,
    flushInterval: 5000,
    useHttpTransport: false // Use console transport for dev
  }
});

// Use logger anywhere
logger.info('Hello from One Logger!', { userId: '123' });
logger.warn('Warning message', { context: 'validation' });
logger.error('Error occurred', { error: 'details' });

// Example with wrappedClass (see section below for more details)
class MyService {
  constructor() { logger.info('MyService created'); }
  doSomething() { logger.info('Doing something'); return 'done'; }
  static staticMethod() { logger.info('Static method called'); return 'static done'; }
}
const TracedService = wrappedClass('MyService', MyService);
const instance = new TracedService();
instance.doSomething();
TracedService.staticMethod();
logger.info('Hello from One Logger!', { userId: '123' });
logger.warn('Warning message', { context: 'validation' });
logger.error('Error occurred', { error: 'details' });

Tracing with Spans

// Wrap functions for automatic tracing
const fetchUserData = wrappedSpan(
  'fetchUserData',
  async (userId: string) => {
    logger.info('Fetching user data', { userId });
    // Your async logic here
    const userData = await api.getUser(userId);
    return userData;
  },
  (userId) => ({ userId, operation: 'fetch' }) // metadata
);

// Nested spans are automatically tracked
const processUser = wrappedSpan(
  'processUser',
  async (userId: string) => {
    const userData = await fetchUserData(userId); // Creates nested span
    const processed = await validateUser(userData); // Another nested span
    return processed;
  },
  { layer: 'business-logic' }
);

Tracing with Wrapped Classes

For entire classes, you can use wrappedClass to automatically trace the constructor and all methods (instance and static):

import { wrappedClass, logger } from '@notjustcoders/one-logger-client-sdk';

class GreeterService {
  private greeting: string;

  constructor(greeting: string) {
    this.greeting = greeting;
    logger.info('GreeterService initialized', { greeting });
  }

  greet(name: string): string {
    const message = `${this.greeting}, ${name}!`;
    logger.info('Greeting generated', { name, message });
    return message;
  }

  static staticGreet(name: string): string {
    const message = `Hello from static, ${name}!`;
    logger.info('Static greeting generated', { name, message });
    return message;
  }
}

// Wrap the entire class
const TracedGreeterService = wrappedClass(
  'GreeterService',
  GreeterService,
  (methodName, ...args) => ({
    method: methodName, // 'constructor', 'greet', or 'staticGreet'
    argsCount: args.length,
    type: methodName === 'constructor' || GreeterService.prototype.hasOwnProperty(methodName) ? 'instance' : 'static'
  })
);

// Constructor and all method calls are now automatically traced
const greeter = new TracedGreeterService('Hi'); // Constructor span
const message1 = greeter.greet('Alice'); // Instance method span
const message2 = TracedGreeterService.staticGreet('Bob'); // Static method span

Tracing with Wrapped Objects

For classes or objects with multiple methods, use wrappedObject to automatically trace all method calls:

class UserService {
  private users: { id: string; name: string }[] = [];

  async createUser(name: string): Promise<{ id: string; name: string }> {
    // Simulate async operation
    await new Promise(resolve => setTimeout(resolve, 100));
    
    const user = { id: Math.random().toString(36).substr(2, 9), name };
    this.users.push(user);
    logger.info('User created successfully', { userId: user.id, name });
    return user;
  }

  getUser(id: string): { id: string; name: string } | undefined {
    const user = this.users.find(user => user.id === id);
    logger.info('User lookup', { userId: id, found: !!user });
    return user;
  }

  getAllUsers(): { id: string; name: string }[] {
    logger.info('Retrieved all users', { count: this.users.length });
    return [...this.users];
  }
}

// Wrap the entire service - all methods will be automatically traced
const userService = new UserService();
const tracedUserService = wrappedObject(
  'UserService',
  userService,
  (methodName, ...args) => ({
    method: methodName,
    argsCount: args.length,
    layer: 'service'
  })
);

// All method calls are now automatically traced
const user = await tracedUserService.createUser('Alice');
const foundUser = tracedUserService.getUser(user.id);
const allUsers = tracedUserService.getAllUsers();

Configuration Options

import { initializeOneLogger, createContextAdapter } from '@notjustcoders/one-logger-client-sdk';

initializeOneLogger({
  name: 'my-app',
  description: 'My application',
  isDev: false, // Set to false for production
  logger: {
    batchSize: 10, // Number of logs to batch before sending (default: 10)
    flushInterval: 5000, // Flush interval in milliseconds (default: 5000)
  },
  tracer: {
    batchSize: 10, // Number of spans to batch before sending
    flushInterval: 10000, // Flush interval in milliseconds
    useHttpTransport: true, // Use HTTP transport for production
    contextAdapter: createContextAdapter(), // Enable async context propagation
  }
});

Logger Batch Configuration

The logger now supports batching to improve performance by sending multiple logs in a single request:

  • batchSize: Number of logs to collect before sending a batch (default: 10)
  • flushInterval: Maximum time in milliseconds to wait before flushing pending logs (default: 5000)

Logs are automatically sent when either the batch size is reached or the flush interval expires. You can also manually flush pending logs:

import { logger } from '@notjustcoders/one-logger-client-sdk';

// Manually flush any pending logs
await logger.flush();

// Configure batch settings at runtime
logger.setBatchConfig(20, 10000); // 20 logs per batch, 10 second flush interval

Context Adapters for Async Context Propagation

Context adapters enable proper span context propagation across asynchronous operations in Node.js environments. This ensures that nested spans maintain their parent-child relationships even when crossing async boundaries.

Automatic Context Adapter

import { initializeOneLogger, createContextAdapter } from '@notjustcoders/one-logger-client-sdk';

initializeOneLogger({
  name: 'my-app',
  tracer: {
    contextAdapter: createContextAdapter(), // Automatically detects environment
  }
});

The createContextAdapter() function automatically detects your environment:

  • Node.js: Uses AsyncLocalStorage for proper async context propagation and isolation
  • Browser: Falls back to a stack-based approach (note: browsers lack native APIs for async context isolation, making it impossible to maintain perfect span ordering across all async operations)

Manual Context Adapter Configuration

import { initializeOneLogger, getContextAdapter, setContextAdapter } from '@notjustcoders/one-logger-client-sdk';

// Get the current context adapter
const adapter = getContextAdapter();

// Set a custom context adapter (useful for testing)
setContextAdapter(customAdapter);

Benefits of Context Adapters

  1. Proper Span Nesting: Maintains parent-child relationships across async operations
  2. Automatic Context Propagation: No manual span passing required
  3. Framework Integration: Works seamlessly with Express, Fastify, and other Node.js frameworks

Example with Async Operations

const processOrder = wrappedSpan(
  'processOrder',
  async (orderId: string) => {
    // This span is automatically the parent
    logger.info('Processing order', { orderId });
    
    // These async operations maintain the span context
    const order = await fetchOrder(orderId); // Child span
    const payment = await processPayment(order); // Child span
    const shipping = await scheduleShipping(order); // Child span
    
    return { order, payment, shipping };
  }
);

const fetchOrder = wrappedSpan(
  'fetchOrder',
  async (orderId: string) => {
    // This automatically becomes a child of processOrder
    await new Promise(resolve => setTimeout(resolve, 100));
    return { id: orderId, items: ['item1', 'item2'] };
  }
);

Without Context Adapter:

[DEMO] Trace abc123
↳ processOrder ✓ (350ms)
[DEMO] Trace def456  // ❌ New trace - context lost
↳ fetchOrder ✓ (100ms)

With Context Adapter:

[DEMO] Trace abc123
↳ processOrder ✓ (350ms)
  ↳ fetchOrder ✓ (100ms)  // ✅ Proper nesting maintained
  ↳ processPayment ✓ (150ms)
  ↳ scheduleShipping ✓ (100ms)

Example Output

Console Transport

When using console transport in development, you'll see formatted output like:

Wrapped Spans:

[DEMO] Trace abc123-def456-ghi789
↳ processUser ✓ (320ms) metadata: {"layer":"business-logic"}
  ↳ fetchUserData ✓ (201ms) metadata: {"userId":"123","operation":"fetch"}
  ↳ validateUser ✓ (101ms) metadata: {"layer":"validation"}

Wrapped Classes:

[DEMO] Trace mno123-pqr456-stu789
↳ GreeterService.constructor ✓ (10ms) metadata: {"method":"constructor","argsCount":1,"type":"instance"}
↳ GreeterService.greet ✓ (5ms) metadata: {"method":"greet","argsCount":1,"type":"instance"}
↳ GreeterService.staticGreet ✓ (3ms) metadata: {"method":"staticGreet","argsCount":1,"type":"static"}

Wrapped Objects:

[DEMO] Trace def456-ghi789-jkl012
↳ UserService.createUser ✓ (120ms) metadata: {"method":"createUser","argsCount":1,"layer":"service"}
↳ UserService.getUser ✓ (5ms) metadata: {"method":"getUser","argsCount":1,"layer":"service"}
↳ UserService.getAllUsers ✓ (2ms) metadata: {"method":"getAllUsers","argsCount":0,"layer":"service"}

HTTP Transport

(TODO: Add an example image here)

Repository

License

MIT