-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathknex.ts
120 lines (102 loc) · 4.36 KB
/
knex.ts
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
import { CanonicalCode, Span, SpanKind } from '@opentelemetry/api';
import { BasePlugin } from '@opentelemetry/core';
import { DatabaseAttribute } from '@opentelemetry/semantic-conventions';
import type knexTypes from 'knex';
import shimmer from 'shimmer';
import path from 'path';
import { ConnectionAttributes } from './connectionattributes';
interface KnexQuery {
method?: string;
options?: Record<string, unknown>;
timeout?: boolean;
cancelOnTimeout?: boolean;
bindings?: unknown[];
__knexQueryUid?: string;
sql: string;
}
interface IPackage {
name: string;
version: string;
}
const knexBaseDir = path.dirname(require.resolve('knex'));
// eslint-disable-next-line @typescript-eslint/no-var-requires
const knexVersion = (require(path.join(knexBaseDir, 'package.json')) as IPackage).version;
const _STORED_PARENT_SPAN = Symbol.for('opentelemetry.stored-parent-span');
export class KnexPlugin extends BasePlugin<knexTypes> {
public readonly supportedVersions = ['0.21.*'];
public static readonly COMPONENT = 'knex';
protected readonly _basedir = knexBaseDir;
protected _internalFilesList = {
'*': {
client: 'lib/client',
},
};
private enabled = false;
public constructor(public readonly moduleName: string, public readonly version: string) {
super('@myrotvorets/opentelemetry-plugin-knex', '1.0.0');
}
protected patch(): knexTypes {
// istanbul ignore else
if (!this.enabled && this._internalFilesExports.client) {
const proto = (this._internalFilesExports.client as ObjectConstructor).prototype as knexTypes.Client;
shimmer.massWrap([proto], ['queryBuilder', 'raw'], this.patchAddParentSpan);
shimmer.wrap(proto, 'query', this.patchQuery);
this.enabled = true;
}
return this._moduleExports;
}
protected unpatch(): void {
// istanbul ignore else
if (this.enabled && this._internalFilesExports.client) {
const proto = (this._internalFilesExports.client as ObjectConstructor).prototype as knexTypes.Client;
shimmer.massUnwrap([proto], ['query', 'queryBuilder', 'raw']);
this.enabled = false;
}
}
private ensureParentSpan(fallback: unknown): Span | undefined {
const where = fallback as Record<typeof _STORED_PARENT_SPAN, Span>;
const span = this._tracer.getCurrentSpan() || where[_STORED_PARENT_SPAN];
if (span) {
where[_STORED_PARENT_SPAN] = span;
}
return span;
}
private readonly patchAddParentSpan = (original: (...params: unknown[]) => unknown): typeof original => {
const self = this;
return function (this: unknown, ...params: unknown[]): unknown {
self.ensureParentSpan(this);
return original.apply(this, params);
};
};
private readonly patchQuery = (
original: (connection: unknown, obj: unknown) => Promise<unknown>,
): ((connection: unknown, obj: KnexQuery | string) => Promise<unknown>) => {
const self = this;
return function (this: knexTypes.Client, connection: unknown, query: KnexQuery | string): Promise<unknown> {
const span = self.createSpan(this, query);
return original.call(this, connection, query).then(
(result: unknown) => {
span.setStatus({ code: CanonicalCode.OK }).end();
return Promise.resolve(result);
},
(e: Error) => {
span.setStatus({ code: CanonicalCode.UNKNOWN, message: e.message }).end();
return Promise.reject(e);
},
);
};
};
private createSpan(client: knexTypes.Client, query: KnexQuery | string): Span {
const q = typeof query === 'string' ? { sql: query } : query;
return this._tracer.startSpan(q.method ?? q.sql, {
kind: SpanKind.CLIENT,
attributes: {
[DatabaseAttribute.DB_SYSTEM]: client.driverName,
...new ConnectionAttributes(client.connectionSettings).getAttributes(),
[DatabaseAttribute.DB_STATEMENT]: q.bindings?.length ? `${q.sql}\nwith [${q.bindings}]` : q.sql,
},
parent: this.ensureParentSpan(client),
});
}
}
export const plugin = new KnexPlugin('knex', knexVersion);