UNPKG

@splunk/otel

Version:

The Splunk distribution of OpenTelemetry Node Instrumentation provides a Node agent that automatically instruments your Node application to capture and report distributed traces to Splunk APM.

178 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SequelizeInstrumentation = void 0; /* * Copyright Splunk Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const api_1 = require("@opentelemetry/api"); const core_1 = require("@opentelemetry/core"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const version_1 = require("../../../version"); const utils_1 = require("./utils"); const instrumentation_1 = require("@opentelemetry/instrumentation"); class SequelizeInstrumentation extends instrumentation_1.InstrumentationBase { constructor(config = {}) { super('splunk-opentelemetry-instrumentation-sequelize', version_1.VERSION, config); } init() { // eslint-disable-next-line @typescript-eslint/no-explicit-any const unpatchConnectionManager = (moduleExports) => { var _a, _b; if ((0, instrumentation_1.isWrapped)((_b = (_a = moduleExports === null || moduleExports === void 0 ? void 0 : moduleExports.ConnectionManager) === null || _a === void 0 ? void 0 : _a.prototype) === null || _b === void 0 ? void 0 : _b.getConnection)) { this._unwrap(moduleExports.ConnectionManager.prototype, 'getConnection'); } return moduleExports; }; const connectionManagerInstrumentation = new instrumentation_1.InstrumentationNodeModuleFile('sequelize/lib/dialects/abstract/connection-manager.js', ['*'], (moduleExports) => { if (moduleExports === undefined || moduleExports === null) { return moduleExports; } api_1.diag.debug(`sequelize instrumentation: applying patch to sequelize ConnectionManager`); unpatchConnectionManager(moduleExports); this._wrap(moduleExports.ConnectionManager.prototype, 'getConnection', this._getConnectionPatch()); return moduleExports; }, unpatchConnectionManager); const unpatch = (moduleExports) => { if ((0, instrumentation_1.isWrapped)(moduleExports.Sequelize.prototype.query)) { this._unwrap(moduleExports.Sequelize.prototype, 'query'); } }; const module = new instrumentation_1.InstrumentationNodeModuleDefinition(SequelizeInstrumentation.component, ['*'], (moduleExports, moduleVersion) => { this.moduleVersion = moduleVersion; if (moduleExports === undefined || moduleExports === null) { return moduleExports; } api_1.diag.debug(`sequelize instrumentation: applying patch to sequelize`); unpatch(moduleExports); this._wrap(moduleExports.Sequelize.prototype, 'query', this._createQueryPatch()); return moduleExports; }, unpatch, [connectionManagerInstrumentation]); return module; } // run getConnection with suppressTracing, as it might call internally to `databaseVersion` function // which calls `query` and create internal span which we don't need to instrument _getConnectionPatch() { return (original) => { return function (...args) { return api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), () => original.apply(this, args)); }; }; } _createQueryPatch() { const self = this; return (original) => { return function query(...args) { var _a, _b, _c, _d; if (((_a = self._config) === null || _a === void 0 ? void 0 : _a.ignoreOrphanedSpans) && !api_1.trace.getSpan(api_1.context.active())) { return original.apply(this, args); } const sqlOrQuery = args[0]; const extractStatement = (sql) => { if (typeof sql === 'string') return sql; return (sql === null || sql === void 0 ? void 0 : sql.query) || ''; }; const statement = extractStatement(args[0]).trim(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const option = args[1]; let operation = option === null || option === void 0 ? void 0 : option.type; if (!operation) operation = statement.split(' ')[0]; const sequelizeInstance = this; const config = sequelizeInstance === null || sequelizeInstance === void 0 ? void 0 : sequelizeInstance.config; let tableName = (_c = (_b = option === null || option === void 0 ? void 0 : option.instance) === null || _b === void 0 ? void 0 : _b.constructor) === null || _c === void 0 ? void 0 : _c.tableName; if (!tableName) { if (Array.isArray(option === null || option === void 0 ? void 0 : option.tableNames) && option.tableNames.length > 0) tableName = option === null || option === void 0 ? void 0 : option.tableNames.sort().join(','); else tableName = (0, utils_1.extractTableFromQuery)(statement); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const attributes = { [semantic_conventions_1.SemanticAttributes.DB_SYSTEM]: sequelizeInstance.getDialect(), [semantic_conventions_1.SemanticAttributes.DB_USER]: config === null || config === void 0 ? void 0 : config.username, [semantic_conventions_1.SemanticAttributes.NET_PEER_NAME]: config === null || config === void 0 ? void 0 : config.host, [semantic_conventions_1.SemanticAttributes.NET_PEER_PORT]: (config === null || config === void 0 ? void 0 : config.port) ? Number(config === null || config === void 0 ? void 0 : config.port) : undefined, [semantic_conventions_1.SemanticAttributes.NET_TRANSPORT]: self._getNetTransport(config === null || config === void 0 ? void 0 : config.protocol), [semantic_conventions_1.SemanticAttributes.DB_NAME]: config === null || config === void 0 ? void 0 : config.database, [semantic_conventions_1.SemanticAttributes.DB_OPERATION]: operation, [semantic_conventions_1.SemanticAttributes.DB_STATEMENT]: statement, [semantic_conventions_1.SemanticAttributes.DB_SQL_TABLE]: tableName, // [SemanticAttributes.NET_PEER_IP]: '?', // Part of protocol }; if (self._config.moduleVersionAttributeName) { attributes[self._config.moduleVersionAttributeName] = self.moduleVersion; } Object.entries(attributes).forEach(([key, value]) => { if (value === undefined) delete attributes[key]; }); const newSpan = self.tracer.startSpan(`Sequelize ${operation}`, { kind: api_1.SpanKind.CLIENT, attributes, }); const activeContextWithSpan = api_1.trace.setSpan(api_1.context.active(), newSpan); const hook = (_d = self._config) === null || _d === void 0 ? void 0 : _d.queryHook; if (hook !== undefined && sqlOrQuery !== undefined) { (0, instrumentation_1.safeExecuteInTheMiddle)(() => hook(newSpan, { sql: sqlOrQuery, option }), (e) => { if (e) api_1.diag.error('sequelize instrumentation: queryHook error', e); }, true); } return (api_1.context .with(self._config.suppressInternalInstrumentation ? (0, core_1.suppressTracing)(activeContextWithSpan) : activeContextWithSpan, () => original.apply(this, args)) // eslint-disable-next-line @typescript-eslint/no-explicit-any .then((response) => { var _a; const responseHook = (_a = self._config) === null || _a === void 0 ? void 0 : _a.responseHook; if (responseHook !== undefined) { (0, instrumentation_1.safeExecuteInTheMiddle)(() => responseHook(newSpan, response), (e) => { if (e) api_1.diag.error('sequelize instrumentation: responseHook error', e); }, true); } return response; }) .catch((err) => { newSpan.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err.message, }); throw err; }) .finally(() => { newSpan.end(); })); }; }; } _getNetTransport(protocol) { switch (protocol) { case 'tcp': return semantic_conventions_1.NetTransportValues.IP_TCP; default: return undefined; } } } exports.SequelizeInstrumentation = SequelizeInstrumentation; SequelizeInstrumentation.component = 'sequelize'; //# sourceMappingURL=sequelize.js.map