polartrace 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/diagnostics.d.ts +20 -0
- package/dist/diagnostics.js +105 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +1391 -0
- package/dist/instrumentations/instrumentation-manager.d.ts +12 -0
- package/dist/instrumentations/instrumentation-manager.js +50 -0
- package/dist/instrumentations/instrumentation.d.ts +4 -0
- package/dist/instrumentations/instrumentation.js +2 -0
- package/dist/instrumentations/mongo.instrumentation.d.ts +32 -0
- package/dist/instrumentations/mongo.instrumentation.js +131 -0
- package/dist/instrumentations/postgres.instrumentation.d.ts +29 -0
- package/dist/instrumentations/postgres.instrumentation.js +142 -0
- package/dist/instrumentations/redis.instrumentation.d.ts +31 -0
- package/dist/instrumentations/redis.instrumentation.js +183 -0
- package/dist/metrics/host-metrics-monitor.d.ts +57 -0
- package/dist/metrics/host-metrics-monitor.js +189 -0
- package/dist/module-hook.d.ts +3 -0
- package/dist/module-hook.js +82 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +16 -0
- package/dist/trace/index.d.ts +1 -0
- package/dist/trace/index.js +5 -0
- package/dist/trace/trace-monitor.d.ts +42 -0
- package/dist/trace/trace-monitor.js +376 -0
- package/dist/ts-loader.d.ts +1 -0
- package/dist/ts-loader.js +75 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +12 -0
- package/package.json +118 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Instrumentation } from "./instrumentation";
|
|
2
|
+
declare class InstrumentationManager {
|
|
3
|
+
private static instance;
|
|
4
|
+
private instrumentations;
|
|
5
|
+
static getInstance(): InstrumentationManager;
|
|
6
|
+
register(inst: Instrumentation): void;
|
|
7
|
+
updateMongoCallback(callback: (span: any) => void): boolean;
|
|
8
|
+
updateRedisCallback(callback: (span: any) => void): boolean;
|
|
9
|
+
updatePostgresCallback(callback: (span: any) => void): boolean;
|
|
10
|
+
enableAll(): void;
|
|
11
|
+
}
|
|
12
|
+
export { InstrumentationManager };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InstrumentationManager = void 0;
|
|
4
|
+
const mongo_instrumentation_1 = require("./mongo.instrumentation");
|
|
5
|
+
const redis_instrumentation_1 = require("./redis.instrumentation");
|
|
6
|
+
const postgres_instrumentation_1 = require("./postgres.instrumentation");
|
|
7
|
+
class InstrumentationManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.instrumentations = [];
|
|
10
|
+
}
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!this.instance) {
|
|
13
|
+
this.instance = new InstrumentationManager();
|
|
14
|
+
}
|
|
15
|
+
return this.instance;
|
|
16
|
+
}
|
|
17
|
+
register(inst) {
|
|
18
|
+
this.instrumentations.push(inst);
|
|
19
|
+
}
|
|
20
|
+
updateMongoCallback(callback) {
|
|
21
|
+
const mongoInst = this.instrumentations.find((inst) => inst instanceof mongo_instrumentation_1.MongoInstrumentation);
|
|
22
|
+
if (mongoInst) {
|
|
23
|
+
mongoInst.setOnSpan(callback);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
updateRedisCallback(callback) {
|
|
29
|
+
const redisInst = this.instrumentations.find((inst) => inst instanceof redis_instrumentation_1.RedisInstrumentation);
|
|
30
|
+
if (redisInst) {
|
|
31
|
+
redisInst.setOnSpan(callback);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
updatePostgresCallback(callback) {
|
|
37
|
+
const pgInst = this.instrumentations.find((inst) => inst instanceof postgres_instrumentation_1.PostgresInstrumentation);
|
|
38
|
+
if (pgInst) {
|
|
39
|
+
pgInst.setOnSpan(callback);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
enableAll() {
|
|
45
|
+
for (const inst of this.instrumentations) {
|
|
46
|
+
inst.enable();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.InstrumentationManager = InstrumentationManager;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Instrumentation } from "./instrumentation";
|
|
2
|
+
export interface MongoSdkSpan {
|
|
3
|
+
name: string;
|
|
4
|
+
startTime: number;
|
|
5
|
+
endTime?: number;
|
|
6
|
+
durationMs?: number;
|
|
7
|
+
attributes: {
|
|
8
|
+
db?: string;
|
|
9
|
+
command?: string;
|
|
10
|
+
collection?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
error?: any;
|
|
14
|
+
parentSpanId?: string;
|
|
15
|
+
traceId?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class MongoInstrumentation implements Instrumentation {
|
|
18
|
+
private readonly mongoose?;
|
|
19
|
+
name: string;
|
|
20
|
+
private trackedClients;
|
|
21
|
+
private spans;
|
|
22
|
+
private requireHookInstalled;
|
|
23
|
+
private onSpanCallback;
|
|
24
|
+
constructor(onSpan: (span: MongoSdkSpan) => void, mongoose?: any | undefined);
|
|
25
|
+
setOnSpan(callback: (span: MongoSdkSpan) => void): void;
|
|
26
|
+
enable(): void;
|
|
27
|
+
private patchExistingDriver;
|
|
28
|
+
private installRequireHook;
|
|
29
|
+
private patchMongoDriver;
|
|
30
|
+
private attach;
|
|
31
|
+
private end;
|
|
32
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongoInstrumentation = void 0;
|
|
4
|
+
const module_hook_1 = require("../module-hook");
|
|
5
|
+
const api_1 = require("@opentelemetry/api");
|
|
6
|
+
class MongoInstrumentation {
|
|
7
|
+
constructor(onSpan, mongoose) {
|
|
8
|
+
this.mongoose = mongoose;
|
|
9
|
+
this.name = "mongodb";
|
|
10
|
+
this.trackedClients = new WeakSet();
|
|
11
|
+
this.spans = new Map();
|
|
12
|
+
this.requireHookInstalled = false;
|
|
13
|
+
this.onSpanCallback = onSpan;
|
|
14
|
+
}
|
|
15
|
+
setOnSpan(callback) {
|
|
16
|
+
this.onSpanCallback = callback;
|
|
17
|
+
}
|
|
18
|
+
enable() {
|
|
19
|
+
this.patchExistingDriver();
|
|
20
|
+
this.installRequireHook();
|
|
21
|
+
if (this.mongoose?.connection) {
|
|
22
|
+
this.mongoose.connection.once("open", () => {
|
|
23
|
+
const client = this.mongoose.connection.client;
|
|
24
|
+
if (client) {
|
|
25
|
+
this.attach(client);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
patchExistingDriver() {
|
|
31
|
+
try {
|
|
32
|
+
if (this.mongoose?.mongo) {
|
|
33
|
+
this.patchMongoDriver(this.mongoose.mongo);
|
|
34
|
+
}
|
|
35
|
+
const cached = require.cache;
|
|
36
|
+
for (const key of Object.keys(cached)) {
|
|
37
|
+
if (key.includes("mongodb")) {
|
|
38
|
+
const exports = cached[key]?.exports;
|
|
39
|
+
if (exports?.MongoClient) {
|
|
40
|
+
this.patchMongoDriver(exports);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
installRequireHook() {
|
|
49
|
+
if (this.requireHookInstalled)
|
|
50
|
+
return;
|
|
51
|
+
this.requireHookInstalled = true;
|
|
52
|
+
const self = this;
|
|
53
|
+
(0, module_hook_1.onModuleLoad)("mongodb", (exports) => self.patchMongoDriver(exports));
|
|
54
|
+
}
|
|
55
|
+
patchMongoDriver(mongodb) {
|
|
56
|
+
const MongoClient = mongodb?.MongoClient;
|
|
57
|
+
if (!MongoClient)
|
|
58
|
+
return;
|
|
59
|
+
if (MongoClient.__polartrace_patched)
|
|
60
|
+
return;
|
|
61
|
+
MongoClient.__polartrace_patched = true;
|
|
62
|
+
const self = this;
|
|
63
|
+
const originalConnect = MongoClient.prototype.connect;
|
|
64
|
+
MongoClient.prototype.connect = async function (...args) {
|
|
65
|
+
try {
|
|
66
|
+
const opts = this.options || this.s?.options;
|
|
67
|
+
if (opts)
|
|
68
|
+
opts.monitorCommands = true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
}
|
|
72
|
+
const result = await originalConnect.apply(this, args);
|
|
73
|
+
self.attach(this);
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
attach(client) {
|
|
78
|
+
if (this.trackedClients.has(client))
|
|
79
|
+
return;
|
|
80
|
+
this.trackedClients.add(client);
|
|
81
|
+
const emitter = typeof client?.on === "function"
|
|
82
|
+
? client
|
|
83
|
+
: client.topology || client?.s?.topology;
|
|
84
|
+
if (!emitter?.on)
|
|
85
|
+
return;
|
|
86
|
+
emitter.on("commandStarted", (ev) => {
|
|
87
|
+
const activeSpan = api_1.trace.getActiveSpan();
|
|
88
|
+
let parentSpanId;
|
|
89
|
+
let traceId;
|
|
90
|
+
if (activeSpan) {
|
|
91
|
+
const spanContext = activeSpan.spanContext();
|
|
92
|
+
traceId = spanContext.traceId;
|
|
93
|
+
parentSpanId = spanContext.spanId;
|
|
94
|
+
}
|
|
95
|
+
this.spans.set(ev.requestId, {
|
|
96
|
+
name: `mongodb.${ev.commandName}`,
|
|
97
|
+
startTime: Date.now(),
|
|
98
|
+
attributes: {
|
|
99
|
+
db: ev.databaseName,
|
|
100
|
+
command: ev.commandName,
|
|
101
|
+
collection: extractCollection(ev.command),
|
|
102
|
+
},
|
|
103
|
+
parentSpanId,
|
|
104
|
+
traceId,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
emitter.on("commandSucceeded", (ev) => {
|
|
108
|
+
this.end(ev.requestId);
|
|
109
|
+
});
|
|
110
|
+
emitter.on("commandFailed", (ev) => {
|
|
111
|
+
this.end(ev.requestId, ev.failure);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
end(id, error) {
|
|
115
|
+
const span = this.spans.get(id);
|
|
116
|
+
if (!span)
|
|
117
|
+
return;
|
|
118
|
+
const end = Date.now();
|
|
119
|
+
span.endTime = end;
|
|
120
|
+
span.durationMs = end - span.startTime;
|
|
121
|
+
if (error)
|
|
122
|
+
span.error = error;
|
|
123
|
+
this.onSpanCallback(span);
|
|
124
|
+
this.spans.delete(id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.MongoInstrumentation = MongoInstrumentation;
|
|
128
|
+
function extractCollection(cmd) {
|
|
129
|
+
const key = Object.keys(cmd || {})[0];
|
|
130
|
+
return typeof cmd?.[key] === "string" ? cmd[key] : undefined;
|
|
131
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Instrumentation } from "./instrumentation";
|
|
2
|
+
export interface PostgresSdkSpan {
|
|
3
|
+
name: string;
|
|
4
|
+
startTime: number;
|
|
5
|
+
endTime?: number;
|
|
6
|
+
durationMs?: number;
|
|
7
|
+
attributes: {
|
|
8
|
+
operation?: string;
|
|
9
|
+
table?: string;
|
|
10
|
+
statement?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
error?: any;
|
|
14
|
+
parentSpanId?: string;
|
|
15
|
+
traceId?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class PostgresInstrumentation implements Instrumentation {
|
|
18
|
+
name: string;
|
|
19
|
+
private requireHookInstalled;
|
|
20
|
+
private onSpanCallback;
|
|
21
|
+
constructor(onSpan: (span: PostgresSdkSpan) => void);
|
|
22
|
+
setOnSpan(callback: (span: PostgresSdkSpan) => void): void;
|
|
23
|
+
enable(): void;
|
|
24
|
+
private patchExistingDrivers;
|
|
25
|
+
private installRequireHook;
|
|
26
|
+
private patchPg;
|
|
27
|
+
private beginSpan;
|
|
28
|
+
private end;
|
|
29
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresInstrumentation = void 0;
|
|
4
|
+
const module_hook_1 = require("../module-hook");
|
|
5
|
+
const api_1 = require("@opentelemetry/api");
|
|
6
|
+
const MAX_STATEMENT_LENGTH = 500;
|
|
7
|
+
class PostgresInstrumentation {
|
|
8
|
+
constructor(onSpan) {
|
|
9
|
+
this.name = "postgres";
|
|
10
|
+
this.requireHookInstalled = false;
|
|
11
|
+
this.onSpanCallback = onSpan;
|
|
12
|
+
}
|
|
13
|
+
setOnSpan(callback) {
|
|
14
|
+
this.onSpanCallback = callback;
|
|
15
|
+
}
|
|
16
|
+
enable() {
|
|
17
|
+
this.patchExistingDrivers();
|
|
18
|
+
this.installRequireHook();
|
|
19
|
+
}
|
|
20
|
+
patchExistingDrivers() {
|
|
21
|
+
try {
|
|
22
|
+
const cached = require.cache;
|
|
23
|
+
for (const key of Object.keys(cached)) {
|
|
24
|
+
const exports = cached[key]?.exports;
|
|
25
|
+
if (!exports)
|
|
26
|
+
continue;
|
|
27
|
+
if (key.includes("node_modules/pg/")) {
|
|
28
|
+
this.patchPg(exports);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
installRequireHook() {
|
|
36
|
+
if (this.requireHookInstalled)
|
|
37
|
+
return;
|
|
38
|
+
this.requireHookInstalled = true;
|
|
39
|
+
const self = this;
|
|
40
|
+
(0, module_hook_1.onModuleLoad)("pg", (exports) => self.patchPg(exports));
|
|
41
|
+
}
|
|
42
|
+
patchPg(mod) {
|
|
43
|
+
const pg = mod?.default || mod;
|
|
44
|
+
const Client = pg?.Client;
|
|
45
|
+
if (!Client?.prototype?.query)
|
|
46
|
+
return;
|
|
47
|
+
if (Client.__polartrace_patched)
|
|
48
|
+
return;
|
|
49
|
+
Client.__polartrace_patched = true;
|
|
50
|
+
const self = this;
|
|
51
|
+
const originalQuery = Client.prototype.query;
|
|
52
|
+
Client.prototype.query = function (config, values, callback) {
|
|
53
|
+
const text = extractQueryText(config);
|
|
54
|
+
if (config && typeof config.submit === "function") {
|
|
55
|
+
return originalQuery.call(this, config, values, callback);
|
|
56
|
+
}
|
|
57
|
+
const span = self.beginSpan(text);
|
|
58
|
+
let cb = callback;
|
|
59
|
+
let vals = values;
|
|
60
|
+
if (typeof values === "function") {
|
|
61
|
+
cb = values;
|
|
62
|
+
vals = undefined;
|
|
63
|
+
}
|
|
64
|
+
if (typeof cb === "function") {
|
|
65
|
+
const wrappedCb = function (err, res) {
|
|
66
|
+
self.end(span, err);
|
|
67
|
+
return cb(err, res);
|
|
68
|
+
};
|
|
69
|
+
return originalQuery.call(this, config, vals, wrappedCb);
|
|
70
|
+
}
|
|
71
|
+
const result = originalQuery.call(this, config, vals);
|
|
72
|
+
if (result && typeof result.then === "function") {
|
|
73
|
+
result.then(() => self.end(span), (err) => self.end(span, err));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
self.end(span);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
beginSpan(text) {
|
|
82
|
+
const activeSpan = api_1.trace.getActiveSpan();
|
|
83
|
+
let parentSpanId;
|
|
84
|
+
let traceId;
|
|
85
|
+
if (activeSpan) {
|
|
86
|
+
const spanContext = activeSpan.spanContext();
|
|
87
|
+
traceId = spanContext.traceId;
|
|
88
|
+
parentSpanId = spanContext.spanId;
|
|
89
|
+
}
|
|
90
|
+
const operation = extractOperation(text);
|
|
91
|
+
return {
|
|
92
|
+
name: `postgres.${operation ? operation.toLowerCase() : "query"}`,
|
|
93
|
+
startTime: Date.now(),
|
|
94
|
+
attributes: {
|
|
95
|
+
operation,
|
|
96
|
+
table: extractTable(text),
|
|
97
|
+
statement: truncateStatement(text),
|
|
98
|
+
},
|
|
99
|
+
parentSpanId,
|
|
100
|
+
traceId,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
end(span, error) {
|
|
104
|
+
const end = Date.now();
|
|
105
|
+
span.endTime = end;
|
|
106
|
+
span.durationMs = end - span.startTime;
|
|
107
|
+
if (error)
|
|
108
|
+
span.error = error;
|
|
109
|
+
this.onSpanCallback(span);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.PostgresInstrumentation = PostgresInstrumentation;
|
|
113
|
+
function extractQueryText(config) {
|
|
114
|
+
if (typeof config === "string")
|
|
115
|
+
return config;
|
|
116
|
+
if (config && typeof config.text === "string")
|
|
117
|
+
return config.text;
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function redactStatementLiterals(text) {
|
|
121
|
+
return text.replace(/'(?:[^']|'')*'/g, "'?'");
|
|
122
|
+
}
|
|
123
|
+
function extractOperation(text) {
|
|
124
|
+
if (!text)
|
|
125
|
+
return undefined;
|
|
126
|
+
const match = text.match(/^\s*([a-zA-Z]+)/);
|
|
127
|
+
return match ? match[1].toUpperCase() : undefined;
|
|
128
|
+
}
|
|
129
|
+
function extractTable(text) {
|
|
130
|
+
if (!text)
|
|
131
|
+
return undefined;
|
|
132
|
+
const match = text.match(/\b(?:from|into|update|join)\s+"?([a-zA-Z0-9_.]+)"?/i);
|
|
133
|
+
return match ? match[1] : undefined;
|
|
134
|
+
}
|
|
135
|
+
function truncateStatement(text) {
|
|
136
|
+
text = text === undefined ? text : redactStatementLiterals(text);
|
|
137
|
+
if (!text)
|
|
138
|
+
return undefined;
|
|
139
|
+
return text.length > MAX_STATEMENT_LENGTH
|
|
140
|
+
? text.slice(0, MAX_STATEMENT_LENGTH)
|
|
141
|
+
: text;
|
|
142
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Instrumentation } from "./instrumentation";
|
|
2
|
+
export interface RedisSdkSpan {
|
|
3
|
+
name: string;
|
|
4
|
+
startTime: number;
|
|
5
|
+
endTime?: number;
|
|
6
|
+
durationMs?: number;
|
|
7
|
+
attributes: {
|
|
8
|
+
library?: string;
|
|
9
|
+
command?: string;
|
|
10
|
+
key?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
error?: any;
|
|
14
|
+
parentSpanId?: string;
|
|
15
|
+
traceId?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class RedisInstrumentation implements Instrumentation {
|
|
18
|
+
name: string;
|
|
19
|
+
private requireHookInstalled;
|
|
20
|
+
private onSpanCallback;
|
|
21
|
+
constructor(onSpan: (span: RedisSdkSpan) => void);
|
|
22
|
+
setOnSpan(callback: (span: RedisSdkSpan) => void): void;
|
|
23
|
+
enable(): void;
|
|
24
|
+
private patchExistingDrivers;
|
|
25
|
+
private installRequireHook;
|
|
26
|
+
private patchIoredis;
|
|
27
|
+
private patchNodeRedis;
|
|
28
|
+
private patchNodeRedisClient;
|
|
29
|
+
private beginSpan;
|
|
30
|
+
private end;
|
|
31
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisInstrumentation = void 0;
|
|
4
|
+
const module_hook_1 = require("../module-hook");
|
|
5
|
+
const api_1 = require("@opentelemetry/api");
|
|
6
|
+
class RedisInstrumentation {
|
|
7
|
+
constructor(onSpan) {
|
|
8
|
+
this.name = "redis";
|
|
9
|
+
this.requireHookInstalled = false;
|
|
10
|
+
this.onSpanCallback = onSpan;
|
|
11
|
+
}
|
|
12
|
+
setOnSpan(callback) {
|
|
13
|
+
this.onSpanCallback = callback;
|
|
14
|
+
}
|
|
15
|
+
enable() {
|
|
16
|
+
this.patchExistingDrivers();
|
|
17
|
+
this.installRequireHook();
|
|
18
|
+
}
|
|
19
|
+
patchExistingDrivers() {
|
|
20
|
+
try {
|
|
21
|
+
const cached = require.cache;
|
|
22
|
+
for (const key of Object.keys(cached)) {
|
|
23
|
+
const exports = cached[key]?.exports;
|
|
24
|
+
if (!exports)
|
|
25
|
+
continue;
|
|
26
|
+
if (key.includes("node_modules/ioredis")) {
|
|
27
|
+
this.patchIoredis(exports);
|
|
28
|
+
}
|
|
29
|
+
else if (key.includes("node_modules/redis/") ||
|
|
30
|
+
key.endsWith("node_modules/redis/dist/index.js")) {
|
|
31
|
+
this.patchNodeRedis(exports);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
installRequireHook() {
|
|
39
|
+
if (this.requireHookInstalled)
|
|
40
|
+
return;
|
|
41
|
+
this.requireHookInstalled = true;
|
|
42
|
+
const self = this;
|
|
43
|
+
(0, module_hook_1.onModuleLoad)("ioredis", (exports) => self.patchIoredis(exports));
|
|
44
|
+
(0, module_hook_1.onModuleLoad)("redis", (exports) => self.patchNodeRedis(exports));
|
|
45
|
+
}
|
|
46
|
+
patchIoredis(mod) {
|
|
47
|
+
const Redis = mod?.default || mod;
|
|
48
|
+
if (!Redis?.prototype?.sendCommand)
|
|
49
|
+
return;
|
|
50
|
+
if (Redis.__polartrace_patched)
|
|
51
|
+
return;
|
|
52
|
+
Redis.__polartrace_patched = true;
|
|
53
|
+
const self = this;
|
|
54
|
+
const originalSendCommand = Redis.prototype.sendCommand;
|
|
55
|
+
Redis.prototype.sendCommand = function (command, stream) {
|
|
56
|
+
const span = self.beginSpan("ioredis", command?.name, command?.args);
|
|
57
|
+
const result = originalSendCommand.call(this, command, stream);
|
|
58
|
+
const promise = command?.promise;
|
|
59
|
+
if (promise && typeof promise.then === "function") {
|
|
60
|
+
promise.then(() => self.end(span), (err) => self.end(span, err));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
self.end(span);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
patchNodeRedis(mod) {
|
|
69
|
+
if (!mod || typeof mod.createClient !== "function")
|
|
70
|
+
return;
|
|
71
|
+
if (mod.__polartrace_patched)
|
|
72
|
+
return;
|
|
73
|
+
mod.__polartrace_patched = true;
|
|
74
|
+
const self = this;
|
|
75
|
+
const originalCreateClient = mod.createClient;
|
|
76
|
+
mod.createClient = function (...args) {
|
|
77
|
+
const client = originalCreateClient.apply(this, args);
|
|
78
|
+
self.patchNodeRedisClient(client);
|
|
79
|
+
return client;
|
|
80
|
+
};
|
|
81
|
+
if (typeof mod.createCluster === "function") {
|
|
82
|
+
const originalCreateCluster = mod.createCluster;
|
|
83
|
+
mod.createCluster = function (...args) {
|
|
84
|
+
const cluster = originalCreateCluster.apply(this, args);
|
|
85
|
+
self.patchNodeRedisClient(cluster);
|
|
86
|
+
return cluster;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
patchNodeRedisClient(client) {
|
|
91
|
+
if (!client)
|
|
92
|
+
return;
|
|
93
|
+
let proto = Object.getPrototypeOf(client);
|
|
94
|
+
while (proto && !Object.prototype.hasOwnProperty.call(proto, "sendCommand")) {
|
|
95
|
+
proto = Object.getPrototypeOf(proto);
|
|
96
|
+
}
|
|
97
|
+
if (!proto || proto.__polartrace_patched)
|
|
98
|
+
return;
|
|
99
|
+
proto.__polartrace_patched = true;
|
|
100
|
+
const self = this;
|
|
101
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
102
|
+
let original;
|
|
103
|
+
try {
|
|
104
|
+
original = proto[name];
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (typeof original !== "function")
|
|
110
|
+
continue;
|
|
111
|
+
let isGeneratedCommand = false;
|
|
112
|
+
try {
|
|
113
|
+
isGeneratedCommand = /executor\.call\(/.test(Function.prototype.toString.call(original));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!isGeneratedCommand)
|
|
119
|
+
continue;
|
|
120
|
+
proto[name] = function (...args) {
|
|
121
|
+
const span = self.beginSpan("redis", name.toLowerCase(), args);
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
result = original.apply(this, args);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
self.end(span, err);
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
if (result && typeof result.then === "function") {
|
|
131
|
+
return result.then((value) => {
|
|
132
|
+
self.end(span);
|
|
133
|
+
return value;
|
|
134
|
+
}, (err) => {
|
|
135
|
+
self.end(span, err);
|
|
136
|
+
throw err;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
self.end(span);
|
|
140
|
+
return result;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
beginSpan(library, command, args) {
|
|
145
|
+
const activeSpan = api_1.trace.getActiveSpan();
|
|
146
|
+
let parentSpanId;
|
|
147
|
+
let traceId;
|
|
148
|
+
if (activeSpan) {
|
|
149
|
+
const spanContext = activeSpan.spanContext();
|
|
150
|
+
traceId = spanContext.traceId;
|
|
151
|
+
parentSpanId = spanContext.spanId;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
name: `redis.${command || "unknown"}`,
|
|
155
|
+
startTime: Date.now(),
|
|
156
|
+
attributes: {
|
|
157
|
+
library,
|
|
158
|
+
command,
|
|
159
|
+
key: extractKey(args),
|
|
160
|
+
},
|
|
161
|
+
parentSpanId,
|
|
162
|
+
traceId,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
end(span, error) {
|
|
166
|
+
const end = Date.now();
|
|
167
|
+
span.endTime = end;
|
|
168
|
+
span.durationMs = end - span.startTime;
|
|
169
|
+
if (error)
|
|
170
|
+
span.error = error;
|
|
171
|
+
this.onSpanCallback(span);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.RedisInstrumentation = RedisInstrumentation;
|
|
175
|
+
function extractKey(args) {
|
|
176
|
+
if (!Array.isArray(args) || args.length === 0)
|
|
177
|
+
return undefined;
|
|
178
|
+
const first = args[0];
|
|
179
|
+
if (first === null || first === undefined)
|
|
180
|
+
return undefined;
|
|
181
|
+
const str = typeof first === "string" ? first : String(first);
|
|
182
|
+
return str.length > 128 ? str.slice(0, 128) : str;
|
|
183
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface HostMetricSample {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
process: {
|
|
4
|
+
pid: number;
|
|
5
|
+
cpuPercent: number;
|
|
6
|
+
cpuUserMicros: number;
|
|
7
|
+
cpuSystemMicros: number;
|
|
8
|
+
rssBytes: number;
|
|
9
|
+
heapUsedBytes: number;
|
|
10
|
+
heapTotalBytes: number;
|
|
11
|
+
externalBytes: number;
|
|
12
|
+
arrayBuffersBytes: number;
|
|
13
|
+
uptimeSeconds: number;
|
|
14
|
+
};
|
|
15
|
+
eventLoop: {
|
|
16
|
+
meanMs: number;
|
|
17
|
+
p50Ms: number;
|
|
18
|
+
p95Ms: number;
|
|
19
|
+
p99Ms: number;
|
|
20
|
+
maxMs: number;
|
|
21
|
+
};
|
|
22
|
+
host: {
|
|
23
|
+
hostname: string;
|
|
24
|
+
platform: string;
|
|
25
|
+
cpuCount: number;
|
|
26
|
+
loadAvg1: number;
|
|
27
|
+
loadAvg5: number;
|
|
28
|
+
loadAvg15: number;
|
|
29
|
+
freeMemBytes: number;
|
|
30
|
+
totalMemBytes: number;
|
|
31
|
+
uptimeSeconds: number;
|
|
32
|
+
container?: {
|
|
33
|
+
memoryLimitBytes: number | null;
|
|
34
|
+
memoryUsageBytes: number | null;
|
|
35
|
+
cpuLimitCores: number | null;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface HostMetricsMonitorConfig {
|
|
40
|
+
serviceName: string;
|
|
41
|
+
sampleIntervalMs?: number;
|
|
42
|
+
eventLoopResolutionMs?: number;
|
|
43
|
+
onSample: (sample: HostMetricSample) => void;
|
|
44
|
+
onError?: (err: Error) => void;
|
|
45
|
+
}
|
|
46
|
+
export declare class HostMetricsMonitor {
|
|
47
|
+
private readonly config;
|
|
48
|
+
private timer?;
|
|
49
|
+
private histogram?;
|
|
50
|
+
private prevCpu;
|
|
51
|
+
private prevWallMs;
|
|
52
|
+
initialized: boolean;
|
|
53
|
+
constructor(config: HostMetricsMonitorConfig);
|
|
54
|
+
initialize(): void;
|
|
55
|
+
destroy(): void;
|
|
56
|
+
private sample;
|
|
57
|
+
}
|