Recount
Node.js

Database spans

Trace MongoDB queries automatically with wrapCollection, or record SQL and NoSQL queries as database spans in Node.js.

MongoDB - wrapCollection

Wrap a MongoDB collection once and every operation is traced automatically. Import from @refactorlabs/recount/mongodb:

import { wrapCollection } from '@refactorlabs/recount/mongodb';

const usersCol = wrapCollection(db.collection('users'));

Then use it exactly as you would a normal collection:

await recount.startTrace('Process application', async () => {
  // db span recorded automatically - filter, collection, duration, found/not
  const user = await usersCol.findOne({ accountId: req.accountId });

  const results = await usersCol
    .find({ status: 'active', plan: 'enterprise' })
    .toArray();
});

Each span captures the operation name, collection, filter/pipeline, and a result summary (found, count, insertedId, etc.). Document contents are never recorded - only metadata about the query result.

Naming spans

By default the span name is collection.operation (e.g. users.findOne). Wrap the call with recount.named() to give it a meaningful name:

const user = await recount.named('Load account', () =>
  usersCol.findOne({ accountId: req.accountId })
);
// → db span named 'Load account' instead of 'users.findOne'

Cursor iteration

find() and aggregate() return a wrapped cursor - however you drain it, one span is recorded once iteration finishes, with count set to however many documents were read. .toArray(), for await, .forEach(), and .next() are all supported:

// for await
for await (const user of usersCol.find({ status: 'active' })) {
  process(user);
}

// .forEach()
await usersCol.find({ status: 'active' }).forEach((user) => process(user));

// .next()
const cursor = usersCol.find({ status: 'active' });
let user;
while ((user = await cursor.next()) !== null) {
  process(user);
}

close() (and Symbol.asyncDispose, so using works) finalizes the span too, for cases where you stop draining early. rewind() resets the count so a second drain records its own span. tryNext() is tracked but doesn't end the span on a null result, since tailable/awaitData cursors return null while still open - close() is what ends those.

Redacting filter values

If your filters contain PII, pass a redact option:

const customersCol = wrapCollection(db.collection('customers'), {
  redact: ['name', 'billing.accountName', 'billing.accountNumber']
});

Redact paths are applied to the span's input (the filter object). See Redaction for syntax.

Supported operations

findOne, find (cursor - see Cursor iteration), insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, replaceOne, findOneAndUpdate, findOneAndDelete, aggregate (cursor - see Cursor iteration), countDocuments

Install

mongodb is a peer dependency - use the version already in your project:

npm install @refactorlabs/recount
# mongodb already in your project

Manual spans

For SQL databases or when you need full control, use addSpan with type: 'db' directly.

SQL

recount.addSpan('db', 'Fetch users', {
  metadata: { table: 'users', queryType: 'sql' },
  input: { query: 'SELECT * FROM users WHERE active = $1', params: [true] },
  output: { count: 42 },
  durationMs: 12
});

NoSQL

recount.addSpan('db', 'Fetch users', {
  metadata: { collection: 'users', queryType: 'nosql' },
  input: {
    filter: { active: true },
    projection: { name: 1, email: 1 },
    sort: { createdAt: -1 }
  },
  output: { count: 42 },
  durationMs: 8
});

Metadata fields

FieldTypeDescription
queryType'sql' | 'nosql'Controls how the query is displayed in the dashboard
tablestringTable name (SQL), shown in the span header
collectionstringCollection name (NoSQL), shown in the span header

Output convention

Pass summary stats (counts, affected rows, timings) rather than raw documents.


Types

import type { DbSpanOptions, DbSpanMetadata } from '@refactorlabs/recount';
type DbSpanMetadata = {
  collection?: string;
  table?: string;
  queryType?: 'sql' | 'nosql';
};

type DbSpanOptions = {
  metadata?: DbSpanMetadata;
  input?: JsonObject;
  output?: JsonObject;
  durationMs?: number;
};

On this page