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.
Create a project and copy its API key.
Install @grepline/node in your Node service.
Initialize the SDK once during startup.
Register Express middleware or wrap custom work with spans.
Deploy and open the project traces view.
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.
GREPLINE_API_KEY=grepline_your_project_keyAdd 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.
npm install @grepline/nodeConfigure 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.
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",
});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.
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);
}
});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.
const order = await Grepline.captureSpan(
"db.query: create order",
async () => createOrder(input),
{
kind: "internal",
attributes: {
table: "orders",
operation: "insert",
},
},
);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.
Grepline.annotate({
userId: "user_123",
checkoutMode: "standard",
});
Grepline.captureEvent("checkout.payment_attempted", {
provider: "demo-payments",
amount: 5000,
});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.
const tracedFetch = Grepline.wrapFetch(fetch);
const response = await tracedFetch("https://payments.example.com/charge", {
method: "POST",
body: JSON.stringify({ amount: 5000 }),
});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.
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" } },
);
});
});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.
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,
});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.
process.once("SIGINT", () => {
void Grepline.shutdown().finally(() => process.exit(0));
});
await Grepline.flush();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.
Grepline.init({
endpoint: "http://localhost:3001",
apiKey: process.env.GREPLINE_API_KEY,
service: "api",
debug: true,
});