opentelemetry-instrumentation-typeorm
Version:
open telemetry instrumentation for the `typeorm` module
341 lines • 17.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeormInstrumentation = void 0;
const api_1 = require("@opentelemetry/api");
const core_1 = require("@opentelemetry/core");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const types_1 = require("./types");
const utils_1 = require("./utils");
const version_1 = require("./version");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const is_promise_1 = __importDefault(require("is-promise"));
const selectQueryBuilderExecuteMethods = [
'getRawOne',
'getCount',
'getManyAndCount',
'stream',
'getMany',
'getOneOrFail',
'getOne',
'getRawAndEntities',
'getRawMany',
];
const rawQueryFuncName = 'query';
const functionsUsingEntityPersistExecutor = ['save', 'remove', 'softRemove', 'recover'];
const functionsUsingQueryBuilder = [
'insert',
'update',
'delete',
'softDelete',
'restore',
'count',
'find',
'findAndCount',
'findByIds',
'findOne',
'increment',
'decrement',
];
const entityManagerMethods = [
...functionsUsingEntityPersistExecutor,
...functionsUsingQueryBuilder,
];
class TypeormInstrumentation extends instrumentation_1.InstrumentationBase {
constructor(config = {}) {
super('opentelemetry-instrumentation-typeorm', version_1.VERSION, Object.assign({}, config));
}
init() {
const selectQueryBuilder = new instrumentation_1.InstrumentationNodeModuleFile('typeorm/query-builder/SelectQueryBuilder.js', ['>0.2.28'], (moduleExports, moduleVersion) => {
selectQueryBuilderExecuteMethods.map((method) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.SelectQueryBuilder.prototype) === null || _a === void 0 ? void 0 : _a[method])) {
this._unwrap(moduleExports.SelectQueryBuilder.prototype, method);
}
this._wrap(moduleExports.SelectQueryBuilder.prototype, method, this._patchQueryBuilder(moduleVersion));
});
return moduleExports;
}, (moduleExports) => {
selectQueryBuilderExecuteMethods.map((method) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.SelectQueryBuilder.prototype) === null || _a === void 0 ? void 0 : _a[method])) {
this._unwrap(moduleExports.SelectQueryBuilder.prototype, method);
}
});
return moduleExports;
});
const connection = new instrumentation_1.InstrumentationNodeModuleFile('typeorm/connection/Connection.js', ['>0.2.28 <0.3.0'], (moduleExports, moduleVersion) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.Connection.prototype) === null || _a === void 0 ? void 0 : _a[rawQueryFuncName])) {
this._unwrap(moduleExports.Connection.prototype, rawQueryFuncName);
}
this._wrap(moduleExports.Connection.prototype, rawQueryFuncName, this._patchRawQuery(moduleVersion));
return moduleExports;
}, (moduleExports) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.Connection.prototype) === null || _a === void 0 ? void 0 : _a[rawQueryFuncName])) {
this._unwrap(moduleExports.Connection.prototype, rawQueryFuncName);
}
return moduleExports;
});
const dataSource = new instrumentation_1.InstrumentationNodeModuleFile('typeorm/data-source/DataSource.js', ['>=0.3.0'], (moduleExports, moduleVersion) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.DataSource.prototype) === null || _a === void 0 ? void 0 : _a[rawQueryFuncName])) {
this._unwrap(moduleExports.DataSource.prototype, rawQueryFuncName);
}
this._wrap(moduleExports.DataSource.prototype, rawQueryFuncName, this._patchRawQuery(moduleVersion));
return moduleExports;
}, (moduleExports) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.DataSource.prototype) === null || _a === void 0 ? void 0 : _a[rawQueryFuncName])) {
this._unwrap(moduleExports.DataSource.prototype, rawQueryFuncName);
}
return moduleExports;
});
const entityManager = new instrumentation_1.InstrumentationNodeModuleFile('typeorm/entity-manager/EntityManager.js', ['>0.2.28'], (moduleExports, moduleVersion) => {
entityManagerMethods.map((method) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.EntityManager.prototype) === null || _a === void 0 ? void 0 : _a[method])) {
this._unwrap(moduleExports.EntityManager.prototype, method);
}
this._wrap(moduleExports.EntityManager.prototype, method, this._patchEntityManagerFunction(method, moduleVersion));
});
return moduleExports;
}, (moduleExports) => {
entityManagerMethods.map((method) => {
var _a;
if ((0, instrumentation_1.isWrapped)((_a = moduleExports.EntityManager.prototype) === null || _a === void 0 ? void 0 : _a[method])) {
this._unwrap(moduleExports.EntityManager.prototype, method);
}
});
return moduleExports;
});
const module = new instrumentation_1.InstrumentationNodeModuleDefinition('typeorm', ['>0.2.28'], null, null, [
selectQueryBuilder,
entityManager,
connection,
dataSource,
]);
return module;
}
_patchEntityManagerFunction(opName, moduleVersion) {
const self = this;
api_1.diag.debug(`typeorm instrumentation: patched EntityManager ${opName} prototype`);
return (original) => {
return function (...args) {
var _a, _b, _c;
if ((0, utils_1.isTypeormInternalTracingSuppressed)(api_1.context.active())) {
return original.apply(this, arguments);
}
const connectionOptions = (_b = (_a = this === null || this === void 0 ? void 0 : this.connection) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : {};
const attributes = {
[semantic_conventions_1.SEMATTRS_DB_SYSTEM]: connectionOptions.type,
[semantic_conventions_1.SEMATTRS_DB_USER]: connectionOptions.username,
[semantic_conventions_1.SEMATTRS_NET_PEER_NAME]: connectionOptions.host,
[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]: connectionOptions.port,
[semantic_conventions_1.SEMATTRS_DB_NAME]: connectionOptions.database,
[semantic_conventions_1.SEMATTRS_DB_OPERATION]: opName,
[semantic_conventions_1.SEMATTRS_DB_STATEMENT]: JSON.stringify(buildStatement(original, args)),
};
if (self._config.moduleVersionAttributeName && moduleVersion) {
attributes[self._config.moduleVersionAttributeName] = moduleVersion;
}
//ignore EntityMetadataNotFoundError
try {
if (this.metadata) {
attributes[semantic_conventions_1.SEMATTRS_DB_SQL_TABLE] = this.metadata.tableName;
}
else {
const entity = args[0];
const name = typeof entity === 'object' ? (_c = entity === null || entity === void 0 ? void 0 : entity.constructor) === null || _c === void 0 ? void 0 : _c.name : entity;
const metadata = this.connection.getMetadata(name);
if (metadata === null || metadata === void 0 ? void 0 : metadata.tableName) {
attributes[semantic_conventions_1.SEMATTRS_DB_SQL_TABLE] = metadata.tableName;
}
}
}
catch (_d) { }
Object.entries(attributes).forEach(([key, value]) => {
if (value === undefined)
delete attributes[key];
});
const span = self.tracer.startSpan(`TypeORM ${opName}`, {
kind: api_1.SpanKind.CLIENT,
attributes,
});
const contextWithSpan = api_1.trace.setSpan(api_1.context.active(), span);
const traceContext = self._config.enableInternalInstrumentation
? contextWithSpan
: (0, utils_1.suppressTypeormInternalTracing)(contextWithSpan);
const contextWithSuppressTracing = self._config.suppressInternalInstrumentation
? (0, core_1.suppressTracing)(traceContext)
: traceContext;
return api_1.context.with(contextWithSuppressTracing, () => self._endSpan(() => original.apply(this, arguments), span));
};
};
}
_patchQueryBuilder(moduleVersion) {
const self = this;
return (original) => {
return function () {
var _a, _b;
if ((0, utils_1.isTypeormInternalTracingSuppressed)(api_1.context.active())) {
return original.apply(this, arguments);
}
const queryBuilder = this;
const sql = queryBuilder.getQuery();
const parameters = queryBuilder.getParameters();
const mainTableName = this.getMainTableName();
const operation = queryBuilder.expressionMap.queryType;
const connectionOptions = (_a = queryBuilder === null || queryBuilder === void 0 ? void 0 : queryBuilder.connection) === null || _a === void 0 ? void 0 : _a.options;
const attributes = {
[semantic_conventions_1.SEMATTRS_DB_SYSTEM]: connectionOptions.type,
[semantic_conventions_1.SEMATTRS_DB_USER]: connectionOptions.username,
[semantic_conventions_1.SEMATTRS_NET_PEER_NAME]: connectionOptions.host,
[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]: connectionOptions.port,
[semantic_conventions_1.SEMATTRS_DB_NAME]: connectionOptions.database,
[semantic_conventions_1.SEMATTRS_DB_OPERATION]: operation,
[semantic_conventions_1.SEMATTRS_DB_STATEMENT]: sql,
[semantic_conventions_1.SEMATTRS_DB_SQL_TABLE]: mainTableName,
};
if (self._config.collectParameters) {
try {
attributes[types_1.ExtendedDatabaseAttribute.DB_STATEMENT_PARAMETERS] = JSON.stringify(parameters);
}
catch (err) { }
}
const span = self.tracer.startSpan(`TypeORM ${operation} ${mainTableName}`, {
kind: api_1.SpanKind.CLIENT,
attributes,
});
const contextWithSpan = api_1.trace.setSpan(api_1.context.active(), span);
const traceContext = self._config.enableInternalInstrumentation
? contextWithSpan
: (0, utils_1.suppressTypeormInternalTracing)(contextWithSpan);
const contextWithSuppressTracing = ((_b = self._config) === null || _b === void 0 ? void 0 : _b.suppressInternalInstrumentation)
? (0, core_1.suppressTracing)(traceContext)
: traceContext;
return api_1.context.with(contextWithSuppressTracing, () => self._endSpan(() => original.apply(this, arguments), span));
};
};
}
getOperationName(statement) {
let operation = 'raw query';
if (typeof statement === 'string') {
statement = statement.trim();
try {
operation = statement.split(' ')[0].toUpperCase();
}
catch (e) { }
}
return operation;
}
_patchRawQuery(moduleVersion) {
const self = this;
return (original) => {
return function () {
var _a;
if ((0, utils_1.isTypeormInternalTracingSuppressed)(api_1.context.active())) {
return original.apply(this, arguments);
}
const conn = this;
const sql = arguments[0];
const operation = self.getOperationName(sql);
const connectionOptions = conn.options;
const attributes = {
[semantic_conventions_1.SEMATTRS_DB_SYSTEM]: connectionOptions.type,
[semantic_conventions_1.SEMATTRS_DB_USER]: connectionOptions.username,
[semantic_conventions_1.SEMATTRS_NET_PEER_NAME]: connectionOptions.host,
[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]: connectionOptions.port,
[semantic_conventions_1.SEMATTRS_DB_NAME]: connectionOptions.database,
[semantic_conventions_1.SEMATTRS_DB_OPERATION]: operation,
[semantic_conventions_1.SEMATTRS_DB_STATEMENT]: sql,
};
const span = self.tracer.startSpan(`TypeORM ${operation}`, {
kind: api_1.SpanKind.CLIENT,
attributes,
});
const contextWithSpan = api_1.trace.setSpan(api_1.context.active(), span);
const traceContext = self._config.enableInternalInstrumentation
? contextWithSpan
: (0, utils_1.suppressTypeormInternalTracing)(contextWithSpan);
const contextWithSuppressTracing = ((_a = self._config) === null || _a === void 0 ? void 0 : _a.suppressInternalInstrumentation)
? (0, core_1.suppressTracing)(traceContext)
: traceContext;
return api_1.context.with(contextWithSuppressTracing, () => self._endSpan(() => original.apply(this, arguments), span));
};
};
}
_endSpan(traced, span) {
const executeResponseHook = (response) => {
var _a;
if ((_a = this._config) === null || _a === void 0 ? void 0 : _a.responseHook) {
(0, instrumentation_1.safeExecuteInTheMiddle)(() => this._config.responseHook(span, response), (e) => {
if (e)
api_1.diag.error('typeorm instrumentation: responseHook error', e);
}, true);
}
return response;
};
try {
const response = traced();
if ((0, is_promise_1.default)(response)) {
return Promise.resolve(response)
.then((response) => executeResponseHook(response))
.catch((err) => {
if (err) {
if (typeof err === 'string') {
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err });
}
else {
span.recordException(err);
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err === null || err === void 0 ? void 0 : err.message });
}
}
throw err;
})
.finally(() => span.end());
}
else {
span.end();
return executeResponseHook(response);
}
}
catch (error) {
span.recordException(error);
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error === null || error === void 0 ? void 0 : error.message });
span.end();
throw error;
}
}
}
exports.TypeormInstrumentation = TypeormInstrumentation;
const buildStatement = (func, args) => {
const paramNames = (0, utils_1.getParamNames)(func);
const statement = {};
paramNames.forEach((pName, i) => {
var _a;
const value = args[i];
if (!value)
return;
try {
const stringified = JSON.stringify(value);
if (stringified) {
statement[pName] = args[i];
return;
}
}
catch (err) { }
if (value === null || value === void 0 ? void 0 : value.name) {
statement[pName] = value.name;
return;
}
if ((_a = value === null || value === void 0 ? void 0 : value.constructor) === null || _a === void 0 ? void 0 : _a.name) {
statement[pName] = value.constructor.name;
}
});
return statement;
};
//# sourceMappingURL=typeorm.js.map