Skip to content

add OpenTelemetry metrics instrumentation#3110

Open
PavelPashov wants to merge 49 commits intoredis:masterfrom
PavelPashov:feat/add-opentelemetry-metrics
Open

add OpenTelemetry metrics instrumentation#3110
PavelPashov wants to merge 49 commits intoredis:masterfrom
PavelPashov:feat/add-opentelemetry-metrics

Conversation

@PavelPashov
Copy link
Contributor

@PavelPashov PavelPashov commented Oct 24, 2025

Description

TODO

  • Add docs
  • Add examples

Checklist

  • Does npm test pass with this change (including linting)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?

Note

Medium Risk
Touches core client lifecycle (queue/socket construction, command execution paths, pool/cluster wiring) to emit metrics and maintain per-client identity/registry; while mostly no-op unless OpenTelemetry.init is called, mistakes could still impact performance or shutdown/cleanup behavior.

Overview
Adds first-class OpenTelemetry metrics support via a new OpenTelemetry.init({ metrics: ... }) entrypoint (exported from @redis/client) plus a singleton OTelMetrics implementation and ClientRegistry used by observable gauges.

Instruments the client stack to emit metrics for command durations (including MULTI/PIPELINE), connection lifecycle/closure, pool wait time and pending requests, enterprise maintenance notifications/handoff, client-side cache hits/misses/evictions, pub/sub in+out message counts, and stream lag (via command onSuccess hooks). Includes new client identity IDs (standalone/cluster/pool roles) with registration/unregistration on close/destroy/quit, plus updated docs (docs/otel-metrics.md), an otel-metrics.js example, and extensive new test coverage.

Written by Cursor Bugbot for commit bc14732. This will update automatically on new commits. Configure here.

@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch 4 times, most recently from a11117e to 5fb1791 Compare October 31, 2025 11:39
@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch 2 times, most recently from 1be4565 to 03db1a2 Compare November 5, 2025 11:03
Copy link

@jit-ci jit-ci bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ The following Jit checks failed to run:

  • license-compliance-checker
  • secret-detection-trufflehog
  • software-component-analysis-js
  • static-code-analysis-semgrep-pro

#jit_bypass_commit in this PR to bypass, Jit Admin privileges required.

More info in the Jit platform.

@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch 3 times, most recently from 1106f8a to 242e98d Compare November 6, 2025 13:55
@elimelt
Copy link
Contributor

elimelt commented Dec 8, 2025

This is an exciting feature, I hope it makes it to main!

@jit-ci
Copy link

jit-ci bot commented Jan 19, 2026

❌ Security scan failed

Security scan failed: Branch feat/add-opentelemetry-metrics does not exist in the remote repository


💡 Need to bypass this check? Comment @sera bypass to override.

@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch from 364ff86 to 30b0e8c Compare January 19, 2026 08:35
@jit-ci
Copy link

jit-ci bot commented Jan 19, 2026

❌ Security scan failed

Security scan failed: Branch feat/add-opentelemetry-metrics does not exist in the remote repository


💡 Need to bypass this check? Comment @sera bypass to override.

1 similar comment
@jit-ci
Copy link

jit-ci bot commented Jan 19, 2026

❌ Security scan failed

Security scan failed: Branch feat/add-opentelemetry-metrics does not exist in the remote repository


💡 Need to bypass this check? Comment @sera bypass to override.

@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch 2 times, most recently from 0e91b34 to e2cc888 Compare February 20, 2026 15:32
@PavelPashov PavelPashov marked this pull request as ready for review February 25, 2026 11:28
@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch from af9f0fb to d0f42d3 Compare February 25, 2026 11:34
@PavelPashov PavelPashov changed the title wip: add OpenTelemetry metrics instrumentation add OpenTelemetry metrics instrumentation Feb 25, 2026
);
// Estimate bytes saved by avoiding network round-trip
// Note: JSON.stringify approximation; actual RESP wire size may differ (especially for Buffers)
const bytesEstimate = JSON.stringify(cacheEntry.value).length;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about wrapping it with Buffer.byteLength(JSON.stringify(cacheEntry.value), 'utf8');? If data to be stored isn't just ASCII it makes sense to measure UTF-8 bytes count

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good catch!

} from "./types";
import { noopFunction } from "./utils";

export class NoopCommandMetrics implements IOTelCommandMetrics {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this object? Just to ensure that no commands will be actually send to a collector?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we have multiple metric groups and don’t know what the user will opt into, I kept each group in its own class and defaulted them to noop. When the user initializes metrics, we swap in the real implementations based on config, so all checks happen once during OpenTelemetry.init(...). This keeps runtime code simple (no repeated conditional checks) and ensures disabled metrics have near-zero overhead

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is basically Null object pattern where in case if Otel is disabled empty methods are called?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly

TRANSFORM_LEGACY_REPLY?: boolean;
transformReply: TransformReply | Record<RespVersions, TransformReply>;
unstableResp3?: boolean;
onSuccess?: (args: ReadonlyArray<RedisArgument>, reply: unknown, clientId: string) => void;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkaradzhov we might want to rename onSuccess to something more metrics/otel related

Comment on lines +1127 to +1128
if (command.onSuccess) {
command.onSuccess(parser.redisArgs, finalReply, this._self._clientId);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkaradzhov there might be a better way to record specific command related metrics that require the server response

Comment on lines +65 to +73
// Build the appropriate function based on options
if (options.hasIncludeCommands || options.hasExcludeCommands) {
// Version with filtering
this.createRecordOperationDuration = this.#createWithFiltering.bind(this);
} else {
this.createRecordOperationDuration =
this.#createWithoutFiltering.bind(this);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have two fns?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkaradzhov to make sure we don't do unnecessary checks after initiating the metrics singleton if the user hasn't provided any included or excluded commands

this.clear(false);
// Record invalidations as server-initiated evictions
if (oldSize > 0) {
OTelMetrics.instance.clientSideCacheMetrics.recordCacheEviction(CSC_EVICTION_REASON.INVALIDATION, oldSize);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache eviction metrics missing clientId for attribute resolution

Low Severity

All recordCacheEviction calls omit clientId, so resolveClientAttributes returns undefined and the resulting eviction metrics lack server.address, server.port, and db.client.connection.pool.name attributes. In contrast, recordCacheRequest and recordNetworkBytesSaved in the same class do pass client._clientId, making CSC eviction metrics inconsistent with other CSC metrics and impossible to correlate by server.

Additional Locations (2)

Fix in Cursor Fix in Web

@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch from bf97457 to adbe066 Compare March 5, 2026 14:57
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

// because CSC stores transformed replies and does not retain raw byte size.
// Implement this at a lower protocol/parsing layer where response bytes are known.
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Network bytes saved metric is silently a no-op

Medium Severity

OTelClientSideCacheMetrics.recordNetworkBytesSaved has an empty body with a TODO comment, yet cache.ts calls it on every cache hit expecting the redis.client.csc.network_saved counter to be incremented. The metric instrument is registered and documented but will always report zero, silently providing incorrect observability data to users who enable client-side caching metrics.

Additional Locations (1)

Fix in Cursor Fix in Web

@PavelPashov PavelPashov force-pushed the feat/add-opentelemetry-metrics branch from adbe066 to bc14732 Compare March 5, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants