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 🙏

© 2025 – Pkg Stats / Ryan Hefner

cherrytracer

v0.0.13

Published

Universal, lightweight observability SDK for Cherrytracer

Downloads

1,441

Readme

🍒 Cherrytracer Client SDK

The simplest way to add observability to your app. Zero config. Zero boilerplate. Just magic. ✨

🎯 What is Cherrytracer?

Cherrytracer helps you understand what's happening in your app by automatically tracking:

  • API requests and their timings
  • Database queries
  • External API calls (Stripe, OpenAI, etc.)
  • Errors and where they happen
  • User actions and flows

The best part? It does this automatically. No manual instrumentation. No passing traceIds everywhere. Just works.

🚀 Quick Start

Installation

npm install cherrytracer
# or
bun add cherrytracer
# or
yarn add cherrytracer

Basic Setup

import { Cherrytracer } from "cherrytracer";

const tracer = new Cherrytracer({
  apiKey: "your-api-key",      // Get this from cherrytracer.io
  projectId: "your-project-id"
});

✨ Features

1. 🔗 Auto Context Propagation

The Problem: Normally you'd have to manually pass traceId everywhere:

// 😫 Traditional way - so much boilerplate!
async function getUser(id, traceId) {
  tracer.info("Fetching user", { id }, traceId);
  const user = await db.find(id, traceId);
  return user;
}

The Solution: Cherrytracer does this automatically:

// 🎉 Just log naturally!
async function getUser(id) {
  tracer.info("Fetching user", { id });  // Auto-linked to parent trace!
  const user = await db.find(id);
  return user;
}

How it works: When you call tracer.info() inside a trace() block, it automatically knows which request/trace it belongs to. No manual work needed!

2. 🔒 Auto Data Scrubbing

The Problem: Sensitive data (passwords, tokens, API keys) can accidentally get logged.

The Solution: Cherrytracer automatically redacts 30+ sensitive field names:

tracer.info("User login", {
  email: "[email protected]",        // ✅ Logged
  username: "john_doe",             // ✅ Logged
  password: "secret123",            // ❌ Becomes [REDACTED]
  apiKey: "sk_live_1234...",       // ❌ Becomes [REDACTED]
  token: "eyJhbGciOiJIUz..."       // ❌ Becomes [REDACTED]
});

Protected fields include: password, token, apiKey, secret, authorization, cookie, ssn, creditcard, and more!

3. 🌐 Auto Fetch Instrumentation

The Problem: External API calls are black boxes - you don't know how long they take or if they fail.

The Solution: Every fetch() is automatically traced:

// Just use fetch() normally
await fetch("https://api.stripe.com/v1/charges");

// Cherrytracer automatically:
// ✅ Creates a span for this request
// ✅ Logs the duration
// ✅ Logs status codes and errors
// ✅ Injects trace headers (for distributed tracing!)

Bonus: If the external service supports trace headers, you'll get end-to-end tracing across services!

📖 Common Use Cases

Middleware (Express, Koa, Hono, etc.)

Wrap your entire request in a trace span:

// Express
app.use(async (req, res, next) => {
  return tracer.trace(`${req.method} ${req.url}`, async () => {
    await next();
  });
});

// Koa
app.use(async (ctx, next) => {
  return tracer.trace(`${ctx.method} ${ctx.url}`, async () => {
    await next();
  });
});

Now every log from that request is automatically linked! 🎯

Database Queries

async function getUser(id) {
  tracer.info("Fetching user", { id });
  
  const user = await db.users.findUnique({ 
    where: { id } 
  });
  
  tracer.info("User found", { userId: user.id });
  return user;
}

Both logs automatically link to the parent request. No extra work needed!

External API Calls

async function sendEmail(to, subject, body) {
  // This fetch is automatically traced!
  const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
    method: "POST",
    headers: { "Authorization": `Bearer ${SENDGRID_KEY}` },
    body: JSON.stringify({ to, subject, body })
  });
  
  return response.json();
}

In your dashboard, you'll see:

  • How long SendGrid took to respond
  • The status code (200, 400, 500, etc.)
  • Any errors that occurred
  • A trace header was sent to SendGrid!

4. 🚨 Red Button Error Hook

The Problem: Crashes and uncaught errors can slip through before you ever get a chance to log them. Browser page crashes, unhandledRejections, or Node uncaughtExceptions can go unnoticed.

The Solution: Cherrytracer automatically installs the Red Button Error Hook that listens for browser window.onerror/onunhandledrejection and Node uncaughtException/unhandledRejection. When one of these fires it:

  • Deduplicates the error so you don't spam the dashboard.
  • Annotates it with location, origin/promise info, and the constructor name.
  • Flushes the log immediately (sendBeacon/keepalive in browsers, sync flush + optional exit delay in Node).
  • Respects existing handlers in passive mode so you can safely layer it on top of other instrumentation.

You can still keep your normal try/catch logging, but now crashes bubble straight into Cherrytracer even when nothing catches them.

Error Handling

app.get("/api/payment/:id", async (req, res) => {
  return tracer.trace("GET /api/payment", async (span) => {
    try {
      const payment = await chargeUser(req.params.id);
      res.json(payment);
    } catch (error) {
      // Log the error with context
      span.error("Payment failed", { 
        error: error.message,
        userId: req.params.id 
      });
      res.status(500).json({ error: "Payment failed" });
    }
  });
});

The error log is automatically linked to the request trace!

⚠️ In addition to the manual span example above, Cherrytracer's Red Button Error Hook sits in the background (unless captureErrors is disabled). It captures any browser onerror/onunhandledrejection or Node uncaughtException/unhandledRejection, deduplicates the exception, enriches it with metadata, flushes the bundle immediately, and—when running in Node—holds the process for exitDelayMs before process.exit(1) so the trace actually reaches the dashboard. Switch to captureErrors: "passive" to keep your own global handlers fully in control while still benefiting from the hook when nothing else catches the crash.

🛠️ API Reference

Initialize

const tracer = new Cherrytracer({
  apiKey: string;              // Required: Your API key
  projectId: string;           // Required: Your project ID
  
  // Optional (with defaults):
  baseUrl?: string;            // Default: production URL
  flushInterval?: number;      // Default: 2000ms
  batchSize?: number;          // Default: 50 events
  enabled?: boolean;           // Default: true
  
  // Features (enabled by default):
  autoInstrument?: boolean;    // Default: true (auto-trace fetch)
  scrubSensitiveData?: boolean;// Default: true (redact passwords etc.)
  propagateTraceContext?: boolean; // Default: true (inject trace headers)
  sensitiveKeys?: string[];    // Custom keys to scrub
  captureErrors?: boolean | "passive"; // Default: true (Red Button Error Hook)
  exitDelayMs?: number;        // Default: 100 (Node only, delay before process.exit)
});

Logging

tracer.info(message, data);   // Info level
tracer.warn(message, data);   // Warning level
tracer.error(message, data);  // Error level
tracer.debug(message, data);  // Debug level

Tracing

// Automatic span with callback
await tracer.trace("operation name", async (span) => {
  // Your code here
  span.info("Something happened");
  span.error("Uh oh", { reason: "..." });
});

// Manual span control
const span = tracer.startSpan("operation name");
// Do work...
span.end();

Manual Flush

await tracer.flush();  // Immediately send all queued logs

🎨 Real-World Example

Here's a complete API endpoint with Cherrytracer:

import { Cherrytracer } from "cherrytracer";
import express from "express";

const app = express();
const tracer = new Cherrytracer({
  apiKey: process.env.CHERRY_API_KEY,
  projectId: "my-saas-app"
});

// Middleware: Trace all requests
app.use(async (req, res, next) => {
  return tracer.trace(`${req.method} ${req.url}`, async () => {
    await next();
  });
});

// Get user profile
app.get("/api/user/:id", async (req, res) => {
  // All these logs auto-link to the request trace!
  tracer.info("Fetching user profile", { userId: req.params.id });
  
  const user = await db.users.findUnique({ 
    where: { id: req.params.id } 
  });
  
  if (!user) {
    tracer.warn("User not found", { userId: req.params.id });
    return res.status(404).json({ error: "Not found" });
  }
  
  // Fetch payment data from Stripe (auto-traced!)
  const charges = await fetch(
    `https://api.stripe.com/v1/customers/${user.stripeId}/charges`
  ).then(r => r.json());
  
  tracer.info("Request completed", { 
    userId: user.id,
    chargesCount: charges.data.length 
  });
  
  res.json({ user, charges });
});

app.listen(3000);

In your dashboard, you'll see:

  • The full request trace with timing
  • Each log linked to the request
  • How long the database query took
  • How long Stripe took to respond
  • Any errors with full context

🔧 Configuration Examples

Development Mode

const tracer = new Cherrytracer({
  apiKey: "ct_test_...",
  projectId: "dev",
  baseUrl: "http://localhost:3000",
  flushInterval: 1000,  // Flush more frequently in dev
});

Production Mode

const tracer = new Cherrytracer({
  apiKey: process.env.CHERRY_API_KEY,
  projectId: process.env.CHERRY_PROJECT_ID,
  flushInterval: 5000,   // Batch more in production
  batchSize: 100,
});

Disable in Tests

const tracer = new Cherrytracer({
  apiKey: "ct_test_...",
  projectId: "test",
  enabled: process.env.NODE_ENV !== "test",
});

### Control the Red Button Hook

```typescript
const tracer = new Cherrytracer({
  apiKey: "ct_test_...",
  projectId: "test",
  captureErrors: "passive", // respects existing global handlers
});

const tracerWithoutHook = new Cherrytracer({
  apiKey: "ct_test_...",
  projectId: "test",
  captureErrors: false,      // never installs the hook
});

Custom Sensitive Keys

const tracer = new Cherrytracer({
  apiKey: "...",
  projectId: "...",
  sensitiveKeys: ["ssn", "socialSecurityNumber", "driverLicense"],
});

Disable Auto-Instrumentation

const tracer = new Cherrytracer({
  apiKey: "...",
  projectId: "...",
  autoInstrument: false,        // Disable fetch auto-tracing
  scrubSensitiveData: false,    // Disable auto-scrubbing
});

🌍 Framework Compatibility

Cherrytracer works with any JavaScript/TypeScript framework:

  • ✅ Express.js
  • ✅ Koa
  • ✅ Hono
  • ✅ Next.js
  • ✅ Remix
  • ✅ Nuxt
  • ✅ SvelteKit
  • ✅ Bun
  • ✅ Node.js
  • ✅ Deno
  • ✅ Browser apps
  • ✅ Serverless functions
  • ✅ Edge functions

🏗️ Idiomatic NestJS (Express + Fastify)

If you want a fully Nest-style solution that also works with Fastify, wrap every request in a global CherrytracerInterceptor and trace the pipeline from top to bottom.

1. Interceptor implementation

// cherrytracer.interceptor.ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable, from, lastValueFrom } from 'rxjs';
import { Cherrytracer } from 'cherrytracer';

@Injectable()
export class CherrytracerInterceptor implements NestInterceptor {
  constructor(private readonly tracer: Cherrytracer) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const httpCtx = context.switchToHttp();
    const req = httpCtx.getRequest<Request>();
    const spanName = `${req.method} ${req.originalUrl ?? req.url}`;

    return from(
      this.tracer.trace(spanName, async (span) => {
        try {
          const result = await lastValueFrom(next.handle());
          return result;
        } catch (err: any) {
          span.error?.('Request failed', {
            message: err?.message ?? String(err),
            stack: err?.stack,
          });
          throw err;
        }
      }),
    );
  }
}

2. Wire it up globally

// cherrytracer.provider.ts
import { Cherrytracer } from 'cherrytracer';

export const CHERRYTRACER = Symbol('CHERRYTRACER');

export const cherrytracerProvider = {
  provide: CHERRYTRACER,
  useFactory: () => {
    return new Cherrytracer({
      apiKey: process.env.CHERRYTRACER_API_KEY!,
      projectId: process.env.CHERRYTRACER_PROJECT_ID!,
    });
  },
};

// app.module.ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { cherrytracerProvider } from './cherrytracer.provider';
import { CherrytracerInterceptor } from './cherrytracer.interceptor';

@Module({
  imports: [],
  providers: [
    cherrytracerProvider,
    CherrytracerInterceptor,
    {
      provide: APP_INTERCEPTOR,
      useClass: CherrytracerInterceptor,
    },
  ],
})
export class AppModule {}

If you prefer loose coupling, inject the CHERRYTRACER token into the interceptor instead of depending on the class directly:

import {
  CallHandler,
  ExecutionContext,
  Inject,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { CHERRYTRACER } from './cherrytracer.provider';
import { Cherrytracer } from 'cherrytracer';

@Injectable()
export class CherrytracerInterceptor implements NestInterceptor {
  constructor(@Inject(CHERRYTRACER) private readonly tracer: Cherrytracer) {}

  // ...same intercept() as above
}

🤔 FAQ

How does auto context propagation work?

We use AsyncLocalStorage in Node/Bun environments and a synchronous stack-based approach in browsers. This allows child function calls to automatically inherit the parent trace context without manual passing.

Will this slow down my app?

No! Logs are batched and sent asynchronously. The overhead is negligible (microseconds per log).

What if I want to disable features?

No problem! All features can be disabled individually via configuration options.

Can I use this in the browser?

Yes! Cherrytracer works in both server and browser environments. Just use a browser-safe API key.

Does this work with serverless/edge functions?

Absolutely! Cherrytracer works great with Vercel, Cloudflare Workers, Lambda, etc.

📚 Learn More

📄 License

MIT


Made with 🍒 by indie hackers, for indie hackers.