Skip to main content

Overview

@kubiks/otel-mongodb provides comprehensive OpenTelemetry instrumentation for the MongoDB Node.js driver. Capture spans for all database operations with detailed metadata about collections, queries, and execution metrics.
MongoDB Trace Visualization
Visualize your MongoDB operations with detailed span information including collection names, operation types, and execution metrics.

Installation

npm install @kubiks/otel-mongodb
Peer Dependencies: @opentelemetry/api >= 1.9.0, mongodb >= 5.0.0

Quick Start

import { MongoClient } from "mongodb";
import { instrumentMongoClient } from "@kubiks/otel-mongodb";

const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();

instrumentMongoClient(client, {
  captureFilters: true,
  peerName: "mongodb.example.com",
  peerPort: 27017,
});

const db = client.db("myapp");
const users = db.collection("users");
const user = await users.findOne({ email: "user@example.com" });
instrumentMongoClient wraps the client you already use—no configuration changes needed. Every database operation creates a client span with useful attributes.

What Gets Traced

This instrumentation automatically traces all major MongoDB operations including:

Find Operations

find, findOne

Insert Operations

insertOne, insertMany

Update Operations

updateOne, updateMany, findOneAndUpdate

Delete Operations

deleteOne, deleteMany, findOneAndDelete

Aggregation

aggregate

Count Operations

countDocuments

Configuration

With Filter Capture

instrumentMongoClient(client, {
  captureFilters: true,        // Capture query filters (default: false)
  peerName: "mongodb.example.com",
  peerPort: 27017,
});
Filter capture is disabled by default to protect sensitive data. Only enable in secure, development environments or ensure filters don’t contain sensitive information.

Span Attributes

Each span includes rich metadata about the database operation following OpenTelemetry semantic conventions:
AttributeDescriptionExample
db.systemConstant value mongodbmongodb
db.operationMongoDB operation typefindOne, insertMany
db.mongodb.collectionCollection nameusers
db.nameDatabase namemyapp
net.peer.nameMongoDB server hostnamemongodb.example.com
net.peer.portMongoDB server port27017
mongodb.filterQuery filter (when enabled){"status":"active"}
mongodb.result_countNumber of documents returned42
mongodb.inserted_countNumber of documents inserted5
mongodb.matched_countNumber of documents matched (updates)10
mongodb.modified_countNumber of documents modified8
mongodb.deleted_countNumber of documents deleted15
mongodb.execution_time_msQuery execution time (when enabled)42.5
mongodb.pipelineAggregation pipeline[{"$match":...}]
The instrumentation captures query metadata to help with debugging and monitoring, while optionally capturing filters based on your security requirements.

Usage Examples

Basic Find Operations

import { users } from "@/lib/mongodb";

const user = await users.findOne({ email: "user@example.com" });

// Traced with:
// - db.operation: "findOne"
// - db.mongodb.collection: "users"
// - mongodb.result_count: 1

Insert Operations

const result = await users.insertOne({
  name: "John Doe",
  email: "john@example.com",
  status: "active",
});

// Traced with:
// - db.operation: "insertOne"
// - mongodb.inserted_count: 1

Update Operations

const result = await users.updateOne(
  { email: "user@example.com" },
  { $set: { status: "inactive" } }
);

// Traced with:
// - db.operation: "updateOne"
// - mongodb.matched_count: 1
// - mongodb.modified_count: 1

Delete Operations

const result = await users.deleteOne({ email: "user@example.com" });

// Traced with:
// - db.operation: "deleteOne"
// - mongodb.deleted_count: 1

Aggregation Pipeline

const pipeline = [
  { $match: { status: "active" } },
  { $group: { _id: "$country", count: { $sum: 1 } } },
  { $sort: { count: -1 } },
  { $limit: 10 },
];

const results = await users.aggregate(pipeline).toArray();

// Traced with:
// - db.operation: "aggregate"
// - mongodb.pipeline: [{"$match":...},{"$group":...}]
// - mongodb.result_count: 10

Count Operations

const count = await users.countDocuments({ status: "active" });

// Traced with:
// - db.operation: "countDocuments"
// - mongodb.result_count: 42

Complete Integration Example

Here’s a complete example of MongoDB with OpenTelemetry in a Next.js application:

Setup

lib/mongodb.ts
import { MongoClient } from "mongodb";
import { instrumentMongoClient } from "@kubiks/otel-mongodb";

if (!process.env.MONGODB_URI) {
  throw new Error("MONGODB_URI environment variable is not set");
}

const client = new MongoClient(process.env.MONGODB_URI);
let clientPromise: Promise<MongoClient>;

if (process.env.NODE_ENV === "development") {
  // In development, use a global variable to preserve the connection
  let globalWithMongo = global as typeof globalThis & {
    _mongoClientPromise?: Promise<MongoClient>;
  };

  if (!globalWithMongo._mongoClientPromise) {
    clientPromise = client.connect();
    instrumentMongoClient(client, {
      captureFilters: true,
      peerName: new URL(process.env.MONGODB_URI).hostname,
      peerPort: parseInt(new URL(process.env.MONGODB_URI).port || "27017"),
    });
    globalWithMongo._mongoClientPromise = clientPromise;
  } else {
    clientPromise = globalWithMongo._mongoClientPromise;
  }
} else {
  // In production, create a new connection
  clientPromise = client.connect();
  instrumentMongoClient(client, {
    captureFilters: false, // Disable in production
    peerName: new URL(process.env.MONGODB_URI).hostname,
    peerPort: parseInt(new URL(process.env.MONGODB_URI).port || "27017"),
  });
}

export default clientPromise;

export async function getDatabase() {
  const client = await clientPromise;
  return client.db("myapp");
}

export async function getCollection<T = any>(name: string) {
  const db = await getDatabase();
  return db.collection<T>(name);
}

Usage in Server Actions

app/actions/users.ts
"use server";

import { getCollection } from "@/lib/mongodb";
import { ObjectId } from "mongodb";

export async function getUser(userId: string) {
  const users = await getCollection("users");
  return await users.findOne({ _id: new ObjectId(userId) });
}

export async function createUser(data: { name: string; email: string }) {
  const users = await getCollection("users");
  const result = await users.insertOne({
    ...data,
    createdAt: new Date(),
    status: "active",
  });
  return { id: result.insertedId.toString() };
}

export async function updateUser(userId: string, data: Partial<{ name: string; email: string }>) {
  const users = await getCollection("users");
  const result = await users.updateOne(
    { _id: new ObjectId(userId) },
    { $set: { ...data, updatedAt: new Date() } }
  );
  return { success: result.modifiedCount > 0 };
}

export async function deleteUser(userId: string) {
  const users = await getCollection("users");
  const result = await users.deleteOne({ _id: new ObjectId(userId) });
  return { success: result.deletedCount > 0 };
}

export async function getActiveUsers() {
  const users = await getCollection("users");
  return await users.find({ status: "active" }).toArray();
}

Usage in API Routes

app/api/stats/route.ts
import { NextResponse } from "next/server";
import { getCollection } from "@/lib/mongodb";

export async function GET() {
  const users = await getCollection("users");
  
  const stats = await users.aggregate([
    {
      $group: {
        _id: "$status",
        count: { $sum: 1 },
      },
    },
    {
      $project: {
        status: "$_id",
        count: 1,
        _id: 0,
      },
    },
  ]).toArray();
  
  return NextResponse.json({ stats });
}

Best Practices

Always reuse the MongoDB client connection rather than creating new connections:
// Good: Reuse client
const client = await clientPromise;

// Bad: Create new client each time
const client = new MongoClient(uri);
await client.connect();
Only enable filter capture in development or when filters don’t contain sensitive data:
instrumentMongoClient(client, {
  captureFilters: process.env.NODE_ENV === "development",
});
Ensure proper indexes are created for frequently queried fields:
await users.createIndex({ email: 1 }, { unique: true });
await users.createIndex({ status: 1 });
Always handle MongoDB errors in your application:
try {
  const user = await users.findOne({ email });
  if (!user) {
    throw new Error("User not found");
  }
  return user;
} catch (error) {
  console.error("MongoDB error:", error);
  throw error;
}

Troubleshooting

Ensure OpenTelemetry is initialized before connecting to MongoDB:
import { NodeSDK } from "@opentelemetry/sdk-node";

const sdk = new NodeSDK({
  // ... configuration
});

sdk.start();

// Then connect to MongoDB
const client = await clientPromise;
Make sure your MongoDB URI is correct and the server is accessible:
MONGODB_URI=mongodb://localhost:27017/myapp
# or for MongoDB Atlas:
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/myapp
Ensure you’ve configured peer information:
instrumentMongoClient(client, {
  peerName: "mongodb.example.com",
  peerPort: 27017,
});

Resources

License

MIT