GreplineGrepline SDKNode instrumentation guide
SDK-first docs

Ship useful Grepline traces from your Node services.

Install the SDK, add your project API key, and capture Express requests, custom work, outbound fetch calls, queue jobs, annotations, and events without writing raw ingest payloads.

1

Create a project and copy its API key.

2

Install @grepline/node in your Node service.

3

Initialize the SDK once during startup.

4

Register Express middleware or wrap custom work with spans.

5

Deploy and open the project traces view.

Project setup

Get your project API key

Every SDK install sends traces to a single Grepline project. Create or select a project in the app, then copy its API key before you initialize the SDK.

  • New projects show the API key immediately after creation.
  • Existing projects expose the current key from project settings.
  • Store only GREPLINE_API_KEY in your app environment. Regenerating the key invalidates the previous key immediately.
.env
bash
GREPLINE_API_KEY=grepline_your_project_key
Install

Add the Node SDK

Install the SDK in the service you want to observe. The current package exposes CommonJS, ESM, and TypeScript types.

  • Use the package in API servers, workers, or any Node process that can reach your Grepline backend.
  • Initialize once during process startup before middleware, spans, events, or fetch wrapping are used.
  • The SDK batches spans and events, so it is safe to use in request-heavy services.
terminal
bash
npm install @grepline/node
Initialize

Configure the SDK once

Call the SDK init method during startup with your project API key and service name.

  • apiKey should come from GREPLINE_API_KEY.
  • service should be stable and human-readable, such as api, checkout-api, or billing-worker.
  • env and version are optional but make filtering and baseline comparisons more useful.
observability.js
const { Grepline } = require("@grepline/node");

Grepline.init({
  endpoint: "http://localhost:3001",
  apiKey: process.env.GREPLINE_API_KEY,
  service: "checkout-api",
  env: process.env.NODE_ENV || "development",
  version: process.env.npm_package_version || "unknown",
});
HTTP server

Instrument Express requests

The Express middleware creates one root http.server span per request and keeps child spans, annotations, and events attached to the active trace.

  • Register body parsing first if you want request bodies to be available for optional capture.
  • Register the Express middleware before your routes.
  • Server spans are marked error when the response status is 500 or higher.
server.js
const express = require("express");
const { Grepline } = require("@grepline/node");

Grepline.init({
  endpoint: "http://localhost:3001",
  apiKey: process.env.GREPLINE_API_KEY,
  service: "api",
});

const app = express();

app.use(express.json());
app.use(Grepline.expressMiddleware());

app.post("/checkout", async (req, res, next) => {
  try {
    await Grepline.captureSpan("checkout.flow", async () => {
      Grepline.annotate({ userId: req.body.userId });
      await createOrder(req.body);
    });

    res.json({ ok: true });
  } catch (error) {
    next(error);
  }
});
Custom work

Wrap important operations in spans

Use captureSpan for database calls, queue publishing, external providers, expensive computations, or any operation you want to see in the trace timeline.

  • captureSpan returns the wrapped function result.
  • If the wrapped function throws, the span is marked error and the original error is rethrown.
  • Nested captureSpan calls automatically become child spans of the active request or worker trace.
checkout.js
const order = await Grepline.captureSpan(
  "db.query: create order",
  async () => createOrder(input),
  {
    kind: "internal",
    attributes: {
      table: "orders",
      operation: "insert",
    },
  },
);
Context

Add annotations and events

Annotations enrich the active span. Events record timestamped moments inside the current trace without creating another timed span.

  • Use annotate for searchable request context such as tenant, plan, mode, or sanitized IDs.
  • Use captureEvent for moments such as validation completed, retry scheduled, or worker loaded job.
  • Sensitive keys including authorization, cookie, password, token, secret, and apiKey are redacted by default.
checkout.js
Grepline.annotate({
  userId: "user_123",
  checkoutMode: "standard",
});

Grepline.captureEvent("checkout.payment_attempted", {
  provider: "demo-payments",
  amount: 5000,
});
Outbound HTTP

Trace fetch calls

wrapFetch returns a drop-in fetch function that records http.client spans with method, URL, host, path, response status, and response URL.

  • Create the wrapped fetch after SDK initialization.
  • Use the wrapped function for payment providers, internal services, webhooks, and other outbound HTTP calls.
  • The wrapper preserves the original fetch return value.
payments.js
const tracedFetch = Grepline.wrapFetch(fetch);

const response = await tracedFetch("https://payments.example.com/charge", {
  method: "POST",
  body: JSON.stringify({ amount: 5000 }),
});
Workers

Propagate trace context through queues

Queue helpers keep async worker spans connected to the request trace that created the job.

  • injectQueueContext adds a shallow _grepline object to the payload when a trace is active.
  • extractQueueContext reconnects the worker callback to the original trace.
  • Wrap worker processing in captureSpan with kind queue.consume for clear timelines.
queues.js
await queue.add(
  "order_created",
  Grepline.injectQueueContext({ orderId }),
);

worker.process(async (job) => {
  return Grepline.extractQueueContext(job.data, async () => {
    await Grepline.captureSpan(
      "queue.consume: order_created",
      () => processOrder(job.data.orderId),
      { kind: "queue.consume", attributes: { queue: "orders" } },
    );
  });
});
Options

Tune capture, batching, and redaction

The SDK defaults are conservative: traces are batched, request and response headers are off, body capture is off, and common secret fields are redacted.

  • flushIntervalMs defaults to 5000 and maxBatchSize defaults to 50.
  • captureRequestHeaders, captureResponseHeaders, and captureRequestBody default to false.
  • Set debug to true while wiring the SDK locally to see exporter failures in the console.
config.js
Grepline.init({
  endpoint: "http://localhost:3001",
  apiKey: process.env.GREPLINE_API_KEY,
  service: "api",
  flushIntervalMs: 5000,
  maxBatchSize: 50,
  captureRequestHeaders: false,
  captureResponseHeaders: false,
  captureRequestBody: false,
  debug: false,
});
Lifecycle

Flush before shutdown

The SDK flushes on an interval, when a batch is full, and during process shutdown where possible. For graceful exits, call shutdown yourself.

  • Use flush when you need to send the current batch without stopping the timer.
  • Use shutdown in SIGINT, SIGTERM, worker drain, or server close handlers.
  • shutdown clears the interval and sends any remaining spans and events.
shutdown.js
process.once("SIGINT", () => {
  void Grepline.shutdown().finally(() => process.exit(0));
});

await Grepline.flush();
Debugging

When traces do not appear

Most setup issues come from initialization order, the backend URL, or the project API key. Turn on debug mode first, then verify the basics.

  • SDK initialization must run before expressMiddleware, wrapFetch, captureSpan, annotate, or captureEvent.
  • endpoint should be your Grepline backend base URL, for example http://localhost:3001, not the frontend URL.
  • If headers or bodies are missing, enable the matching capture option and confirm redaction is not hiding the key.
debug.js
Grepline.init({
  endpoint: "http://localhost:3001",
  apiKey: process.env.GREPLINE_API_KEY,
  service: "api",
  debug: true,
});