Package detail

@notjustcoders/one-logger-client-sdk

stormsidali20011.1kMIT1.0.13

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

logging, tracing, observability, monitoring

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