-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathindex.ts
More file actions
157 lines (138 loc) · 5.29 KB
/
index.ts
File metadata and controls
157 lines (138 loc) · 5.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import type { Span } from '@opentelemetry/api';
import type { IntegrationFn } from '@sentry/core';
import {
defineIntegration,
SEMANTIC_ATTRIBUTE_CACHE_HIT,
SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE,
SEMANTIC_ATTRIBUTE_CACHE_KEY,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
spanToJSON,
truncate,
} from '@sentry/core';
import { generateInstrumentOnce } from '@sentry/node-core';
import type { IORedisCommandArgs } from '../../../utils/redisCache';
import {
calculateCacheItemSize,
GET_COMMANDS,
getCacheKeySafely,
getCacheOperation,
isInCommands,
shouldConsiderForCache,
} from '../../../utils/redisCache';
import type { IORedisInstrumentationConfig } from './vendored/types';
import { IORedisInstrumentation } from './vendored/ioredis-instrumentation';
import { RedisInstrumentation } from './vendored/redis-instrumentation';
import { subscribeRedisDiagnosticChannels } from './redis-dc-subscriber';
interface RedisOptions {
/**
* Define cache prefixes for cache keys that should be captured as a cache span.
*
* Setting this to, for example, `['user:']` will capture cache keys that start with `user:`.
*/
cachePrefixes?: string[];
/**
* Maximum length of the cache key added to the span description. If the key exceeds this length, it will be truncated.
*
* Passing `0` will use the full cache key without truncation.
*
* By default, the full cache key is used.
*/
maxCacheKeyLength?: number;
}
const INTEGRATION_NAME = 'Redis';
/* Only exported for testing purposes */
export let _redisOptions: RedisOptions = {};
/* Only exported for testing purposes */
export const cacheResponseHook: IORedisInstrumentationConfig['responseHook'] = (
span: Span,
redisCommand: string,
cmdArgs: IORedisCommandArgs,
response: unknown,
) => {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.redis');
const safeKey = getCacheKeySafely(redisCommand, cmdArgs);
const cacheOperation = getCacheOperation(redisCommand);
if (
!safeKey ||
!cacheOperation ||
!_redisOptions.cachePrefixes ||
!shouldConsiderForCache(redisCommand, safeKey, _redisOptions.cachePrefixes)
) {
// not relevant for cache
return;
}
// otel/ioredis seems to be using the old standard, as there was a change to those params: https://github.com/open-telemetry/opentelemetry-specification/issues/3199
// We are using params based on the docs: https://opentelemetry.io/docs/specs/semconv/attributes-registry/network/
const networkPeerAddress = spanToJSON(span).data['net.peer.name'];
const networkPeerPort = spanToJSON(span).data['net.peer.port'];
if (networkPeerPort && networkPeerAddress) {
span.setAttributes({ 'network.peer.address': networkPeerAddress, 'network.peer.port': networkPeerPort });
}
const cacheItemSize = calculateCacheItemSize(response);
if (cacheItemSize) {
span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE, cacheItemSize);
}
if (isInCommands(GET_COMMANDS, redisCommand) && cacheItemSize !== undefined) {
span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, cacheItemSize > 0);
}
span.setAttributes({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: cacheOperation,
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey,
});
// todo: change to string[] once EAP supports it
const spanDescription = safeKey.join(', ');
span.updateName(
_redisOptions.maxCacheKeyLength ? truncate(spanDescription, _redisOptions.maxCacheKeyLength) : spanDescription,
);
};
const instrumentIORedis = generateInstrumentOnce(`${INTEGRATION_NAME}.IORedis`, () => {
return new IORedisInstrumentation({
responseHook: cacheResponseHook,
});
});
const instrumentRedisModule = generateInstrumentOnce(`${INTEGRATION_NAME}.Redis`, () => {
return new RedisInstrumentation({
responseHook: cacheResponseHook,
});
});
/** To be able to preload all Redis OTel instrumentations with just one ID ("Redis"), all the instrumentations are generated in this one function */
export const instrumentRedis = Object.assign(
(): void => {
instrumentIORedis();
instrumentRedisModule();
// node-redis >= 5.12.0 publishes via diagnostics_channel. The subscriber uses
// `@sentry/opentelemetry/tracing-channel`, which needs the Sentry OTel context manager
// to be registered before it can `bindStore`. `initOpenTelemetry()` runs after integration
// `setupOnce`, so defer to the next tick.
void Promise.resolve().then(() => subscribeRedisDiagnosticChannels(cacheResponseHook));
// todo: implement them gradually
// new LegacyRedisInstrumentation({}),
},
{ id: INTEGRATION_NAME },
);
const _redisIntegration = ((options: RedisOptions = {}) => {
return {
name: INTEGRATION_NAME,
setupOnce() {
_redisOptions = options;
instrumentRedis();
},
};
}) satisfies IntegrationFn;
/**
* Adds Sentry tracing instrumentation for the [redis](https://www.npmjs.com/package/redis) and
* [ioredis](https://www.npmjs.com/package/ioredis) libraries.
*
* For more information, see the [`redisIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/redis/).
*
* @example
* ```javascript
* const Sentry = require('@sentry/node');
*
* Sentry.init({
* integrations: [Sentry.redisIntegration()],
* });
* ```
*/
export const redisIntegration = defineIntegration(_redisIntegration);