@myrotvorets/opentelemetry-plugin-knex
Version:
OpenTelemetry knex automatic instrumentation package
88 lines (87 loc) • 4.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.KnexInstrumentation = void 0;
const api_1 = require("@opentelemetry/api");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const connectionattributes_1 = require("./connectionattributes");
const supportedVersions = ['^1.0.0', '^2.0.0', '^3.0.0'];
const _STORED_PARENT_SPAN = Symbol.for('opentelemetry.stored-parent-span');
class KnexInstrumentation extends instrumentation_1.InstrumentationBase {
static COMPONENT = 'knex';
constructor(config) {
super('@myrotvorets/opentelemetry-plugin-knex', '1.0.0', config ?? {});
}
init() {
const { patch, unpatch } = this.getClientPatches();
return [
new instrumentation_1.InstrumentationNodeModuleDefinition('knex', supportedVersions, undefined, undefined, [
new instrumentation_1.InstrumentationNodeModuleFile('knex/lib/client.js', supportedVersions, patch, unpatch),
]),
];
}
getClientPatches() {
return {
patch: (moduleExports, moduleVersion) => {
api_1.diag.debug(`Applying patch for knex@${moduleVersion}`);
// istanbul ignore else
// eslint-disable-next-line @typescript-eslint/unbound-method
if (!(0, instrumentation_1.isWrapped)(moduleExports.prototype.queryBuilder)) {
this._massWrap([moduleExports.prototype], ['queryBuilder', 'raw'], this.patchAddParentSpan);
this._wrap(moduleExports.prototype, 'query', this.patchQuery);
}
return moduleExports;
},
unpatch: (moduleExports, moduleVersion) => {
// istanbul ignore else
if (moduleExports !== undefined) {
api_1.diag.debug(`Removing patch for knex@${moduleVersion}`);
this._massUnwrap([moduleExports.prototype], ['query', 'queryBuilder', 'raw']);
}
},
};
}
static ensureParentSpan(fallback) {
const where = fallback;
const span = api_1.trace.getSpan(api_1.context.active()) ?? where[_STORED_PARENT_SPAN];
if (span) {
where[_STORED_PARENT_SPAN] = span;
}
return span;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/class-methods-use-this
patchAddParentSpan = (original) => {
return function (...params) {
KnexInstrumentation.ensureParentSpan(this);
return original.apply(this, params);
};
};
patchQuery = (original) => {
const self = this;
return function (connection, query) {
const span = self.createSpan(this, query);
return original.call(this, connection, query).then((result) => {
span.setStatus({ code: api_1.SpanStatusCode.OK }).end();
return result;
}, (e) => {
const err = e instanceof Error ? e : new Error(String(e), { cause: e });
span.recordException(err);
span.setStatus({ code: api_1.SpanStatusCode.ERROR }).end();
throw err;
});
};
};
createSpan(client, query) {
const q = typeof query === 'string' ? { sql: query } : query;
const parentSpan = KnexInstrumentation.ensureParentSpan(client);
return this.tracer.startSpan(q.method ?? q.sql, {
kind: api_1.SpanKind.CLIENT,
attributes: {
[semantic_conventions_1.ATTR_DB_SYSTEM_NAME]: client.driverName,
...new connectionattributes_1.ConnectionAttributes(client.connectionSettings).getAttributes(),
[semantic_conventions_1.ATTR_DB_QUERY_TEXT]: q.bindings?.length ? `${q.sql}\nwith [${q.bindings}]` : q.sql,
},
}, parentSpan ? api_1.trace.setSpan(api_1.context.active(), parentSpan) : undefined);
}
}
exports.KnexInstrumentation = KnexInstrumentation;