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 🙏

© 2026 – Pkg Stats / Ryan Hefner

mysql2-otel-instrumentation

v0.1.7

Published

OpenTelemetry instrumentation for mysql2 via Node.js diagnostic channels — no monkey-patching, no require-in-the-middle

Readme

mysql2-otel-instrumentation

npm version

OpenTelemetry instrumentation for mysql2 via Node.js diagnostic channels.

This package subscribes to mysql2's built-in TracingChannel events to produce OpenTelemetry spans and metrics. No monkey-patching, no require-in-the-middle, no import-in-the-middle.

Compatible with OpenTelemetry JS API and SDK 1.9+.

Why this package?

The OTel JS ecosystem is moving toward library-owned instrumentation (see @fastify/otel as precedent). This package replaces @opentelemetry/instrumentation-mysql2 with a cleaner architecture:

  • Pure diagnostics_channel subscriber -- mysql2 >=3.20.0 emits structured lifecycle events natively
  • No RITM/IITM -- no module loading interception, no patching of internals
  • Stable semantic conventions only -- no legacy attribute baggage
  • Metrics included -- operation duration and connection timing histograms out of the box
  • Connection visibility -- spans for connect and pool connect, not just queries

Migrating from @opentelemetry/instrumentation-mysql2? See the Migration Guide.

Requirements

  • Node.js >= 22
  • mysql2 >= 3.20.0
  • @opentelemetry/api >= 1.9.0

Installation

npm install mysql2-otel-instrumentation

Quick Start

Note: You'll need the OpenTelemetry SDK packages alongside this instrumentation: npm install @opentelemetry/sdk-trace-node @opentelemetry/instrumentation

import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { MySQL2Instrumentation } from "mysql2-otel-instrumentation";

const provider = new NodeTracerProvider();
provider.register();

registerInstrumentations({
  instrumentations: [new MySQL2Instrumentation()],
});

That's it. Every connection.query(), connection.execute(), new Connection(), and pool.getConnection() call is now traced automatically.

With metrics

To also collect operation duration and connection timing metrics, configure a MeterProvider:

import {
  MeterProvider,
  PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";

const meterProvider = new MeterProvider({
  readers: [
    new PeriodicExportingMetricReader({
      exporter: new OTLPMetricExporter(),
    }),
  ],
});

If no MeterProvider is configured, metrics are silently no-op'd.

Configuration

Pass options to the constructor:

new MySQL2Instrumentation({
  maskStatement: true,
  requestHook(span, info) {
    span.setAttribute("app.query.table", extractTable(info.query));
  },
  responseHook(span, info) {
    span.setAttribute(
      "app.query.rowCount",
      Array.isArray(info.queryResults) ? info.queryResults.length : 0
    );
  },
});

Options

| Option | Type | Default | Description | | ------------------- | ----------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | requestHook | (span: Span, info: QueryRequestInfo) => void | undefined | Called on query/execute start. Add custom attributes from the query, parameters, or connection info. | | responseHook | (span: Span, info: QueryResponseInfo) => void | undefined | Called on query/execute completion. Add custom attributes from query results. | | maskStatement | boolean | false | If true, masks the SQL in db.query.text using maskStatementHook before setting the attribute. | | maskStatementHook | (query: string) => string | Replaces integers and quoted strings with ? | Custom function to mask the SQL statement. Only used when maskStatement is true. | | recordExceptions | boolean | true | If true, calls span.recordException(error) on error spans, producing an exception event with message and stack trace. | | metrics | boolean | true | If false, skip emitting db.client.* histogram metrics entirely. No metric instruments are created and no records are made. |

Hook signatures

interface QueryRequestInfo {
  query: string; // SQL text
  values: unknown; // Bind parameter values
  database: string; // Database name
  serverAddress: string; // Host or socket path
  serverPort: number | undefined; // Port (undefined for unix sockets)
}

interface QueryResponseInfo {
  queryResults: unknown; // Result rows/info from mysql2
}

Hooks are wrapped in try/catch -- a throwing hook will never break your application or the instrumentation.

Instrumented Operations

This package subscribes to four TracingChannel events emitted by mysql2:

| Channel | Span Name | When | | --------------------- | ----------------------------------- | -------------------------------------------- | | mysql2:query | SQL verb (e.g., SELECT, INSERT) | connection.query() | | mysql2:execute | SQL verb (e.g., SELECT, INSERT) | connection.execute() (prepared statements) | | mysql2:connect | mysql.connect | New connection handshake | | mysql2:pool:connect | mysql.pool.connect | pool.getConnection() |

All spans are SpanKind.CLIENT.

Span Attributes

All spans include these base attributes:

| Attribute | Type | Description | | ---------------- | -------- | -------------------------------------- | | db.system.name | string | Always "mysql" | | server.address | string | Hostname or unix socket path | | server.port | number | Port number (omitted for unix sockets) | | db.namespace | string | Database name (omitted if empty) |

Query and execute spans add:

| Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------- | | db.query.text | string | The SQL statement (masked if maskStatement: true) |

Connect spans add:

| Attribute | Type | Description | | --------- | -------- | -------------------------------- | | db.user | string | Username used for the connection |

These follow the stable OpenTelemetry database semantic conventions (v1.33.0+).

Error Handling

When a query, execute, or connection operation fails:

  1. The span status is set to ERROR with the error message
  2. span.recordException(error) is called (unless recordExceptions: false), adding an exception event with the error message and stack trace
  3. The span is ended normally

Metrics

Two histogram metrics are emitted automatically:

| Metric | Unit | Description | | ---------------------------------- | ---- | --------------------------------------------------------------- | | db.client.operation.duration | s | Duration of query and execute operations | | db.client.connection.create_time | s | Duration of connection establishment (connect and pool connect) |

Both use bucket boundaries: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10] seconds.

Metrics carry the same base attributes as spans (db.system.name, server.address, server.port, db.namespace).

To disable metrics entirely (for trace-only setups or when no MeterProvider is configured), pass metrics: false:

new MySQL2Instrumentation({ metrics: false });

When disabled, no histogram instruments are created and no record calls are made. The flag is read once at enable() time; to change it at runtime, call disable() then re-create the instrumentation.

SQL Masking

By default, db.query.text contains the SQL as mysql2 emits it -- typically the parameterized query template:

SELECT * FROM users WHERE id = ?

Values are never interpolated into the attribute. To further mask the SQL:

new MySQL2Instrumentation({ maskStatement: true });
// "SELECT * FROM users WHERE id = 42" becomes "SELECT * FROM users WHERE id = ?"

The default masking hook replaces integer literals and quoted strings with ?. Provide a custom hook for different behavior:

new MySQL2Instrumentation({
  maskStatement: true,
  maskStatementHook(query) {
    return query.replace(/\b(password|secret|token)\s*=\s*\S+/gi, "$1=***");
  },
});

Examples

Adding custom attributes from results

new MySQL2Instrumentation({
  responseHook(span, { queryResults }) {
    if (Array.isArray(queryResults)) {
      span.setAttribute("db.response.rowCount", queryResults.length);
    }
  },
});

Adding query parameter count

new MySQL2Instrumentation({
  requestHook(span, { values }) {
    if (Array.isArray(values)) {
      span.setAttribute("db.query.paramCount", values.length);
    }
  },
});

Disabling exception recording

// Only set ERROR status, don't add exception events
new MySQL2Instrumentation({ recordExceptions: false });

API

Exports

import {
  MySQL2Instrumentation, // Main instrumentation class
  INSTRUMENTATION_NAME, // "mysql2-otel-instrumentation"
} from "mysql2-otel-instrumentation";

import type {
  MySQL2InstrumentationConfig, // Configuration options
  QueryRequestInfo, // requestHook info parameter
  QueryResponseInfo, // responseHook info parameter
} from "mysql2-otel-instrumentation";

MySQL2Instrumentation

Extends InstrumentationBase from @opentelemetry/instrumentation.

const instrumentation = new MySQL2Instrumentation(config?);

instrumentation.enable();   // Subscribe to diagnostic channels (called automatically on construction)
instrumentation.disable();  // Unsubscribe from diagnostic channels

Useful Links

License

Apache-2.0. See LICENSE for the full text.

Copyright 2026 Vladimir Adamić.