@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
JavaScript
;
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