@sap/hana-client
Version:
Official SAP HANA Node.js Driver
187 lines (172 loc) • 6.46 kB
JavaScript
const dynatrace = {};
try {
// @dynatrace/oneagent-sdk must be installed by the application in order for
// the client to use it.
dynatrace.sdk = require('@dynatrace/oneagent-sdk');
dynatrace.api = dynatrace.sdk.createInstance();
} catch (err) {
// If module was not found, do not do anything
}
function isDynatraceEnabled() {
if(dynatrace.api === undefined) {
return false;
}
const envVar = process.env.HDB_NODEJS_SKIP_DYNATRACE;
if(envVar && envVar != '0' && envVar.toLowerCase() != 'false') {
return false;
}
return true;
}
function _dynatraceResultCallback(tracer, cb) {
return function (err, ...args) {
const results = args[0];
if (err) {
tracer.error(err);
} else if(results !== undefined) {
tracer.setResultData({
rowsReturned: (results && results.length) || results
});
}
tracer.end(cb, err, ...args);
};
}
function _dynatraceResultSetCallback(tracer, cb) {
return function (err, ...args) {
const resultSet = args[0];
if (err) {
tracer.error(err);
} else if(resultSet) {
const rowCount = resultSet.getRowCount();
// A negative rowCount means the number of rows is unknown.
// This happens if the client hasn't received the last fetch chunk yet (with default server configuration,
// this happens if the result set is larger than 32 rows)
if(rowCount >= 0) {
tracer.setResultData({rowsReturned: rowCount});
}
}
tracer.end(cb, err, ...args);
};
}
function _ExecuteWrapperFn(stmtOrConn, conn, execFn, resultCB, sql) {
// connection exec args = [sql, options, callback] --> options and callback is optional
// stmt exec args = [options, callback] --> options and callback is optional
return function (...args) {
if(stmtOrConn === conn && args.length > 0) {
sql = args[0];
}
if(typeof(sql) !== 'string') {
sql = ''; // execute will fail, but need sql for when the error is traced
}
// get dbInfo from the conn in case it changes since the first time dynatraceConnection was called
const tracer = dynatrace.api.traceSQLDatabaseRequest(conn._dbInfo, {statement: sql});
var cb = null;
if (args.length > 0 && typeof args[args.length - 1] === 'function') {
cb = args[args.length - 1];
}
if(cb) {
// async execute
tracer.startWithContext(execFn, stmtOrConn, ...args.slice(0, args.length - 1), resultCB(tracer, cb));
} else {
// sync execute
var result;
tracer.start(function sync_execute_wrapper() {
try {
result = execFn.call(stmtOrConn, ...args);
resultCB(tracer, function noop(){})(null, result);
} catch (err) {
tracer.error(err);
tracer.end();
throw err;
}
});
return result;
}
}
}
// modify stmt for Dynatrace after a successful prepare
function _DynatraceStmt(stmt, conn, sql) {
// same here. hana-client does not like decorating
const originalExecFn = stmt.exec;
stmt.exec = _ExecuteWrapperFn(stmt, conn, originalExecFn, _dynatraceResultCallback, sql);
stmt.execute = stmt.exec;
const originalExecBatchFn = stmt.execBatch;
stmt.execBatch = _ExecuteWrapperFn(stmt, conn, originalExecBatchFn, _dynatraceResultCallback, sql);
stmt.executeBatch = stmt.execBatch;
const originalExecQueryFn = stmt.execQuery;
stmt.execQuery = _ExecuteWrapperFn(stmt, conn, originalExecQueryFn, _dynatraceResultSetCallback, sql);
stmt.executeQuery = stmt.execQuery;
}
function _prepareStmtUsingDynatrace(conn, prepareFn) {
// args = [sql, options, callback] --> options is optional
return function (...args) {
var cb;
if(args.length > 0 && typeof args[args.length - 1] === 'function') {
cb = args[args.length - 1];
}
var sql = args[0];
if(typeof(sql) !== 'string') {
sql = ''; // prepare will fail, but need sql for when the error is traced
}
if(typeof(cb) !== 'function') {
// sync prepare
var stmt;
try {
stmt = prepareFn.call(conn, ...args);
_DynatraceStmt(stmt, conn, sql);
return stmt;
} catch (err) {
const tracer = dynatrace.api.traceSQLDatabaseRequest(conn._dbInfo, {statement: sql});
tracer.start(function prepare_error_handler() {
tracer.error(err);
tracer.end();
});
throw err;
}
}
prepareFn.call(conn, ...args.slice(0, args.length - 1), dynatrace.api.passContext(function prepare_handler(err, stmt) {
if (err) {
// The prepare failed, so trace the SQL and the error
// We didn't start the tracer yet, so the trace start time will be inaccurate.
const tracer = dynatrace.api.traceSQLDatabaseRequest(conn._dbInfo, {statement: sql});
tracer.start(function prepare_error_handler() {
tracer.error(err);
tracer.end(cb, err);
});
} else {
_DynatraceStmt(stmt, conn, sql);
cb(err, stmt);
}
}));
}
}
function _createDbInfo(destinationInfo) {
const dbInfo = {
name: `SAPHANA${destinationInfo.tenant ? `-${destinationInfo.tenant}` : ''}`,
vendor: dynatrace.sdk.DatabaseVendor.HANADB,
host: destinationInfo.host,
port: Number(destinationInfo.port)
};
return dbInfo;
}
function dynatraceConnection(conn, destinationInfo) {
if(dynatrace.api === undefined) {
return conn;
}
const dbInfo = _createDbInfo(destinationInfo);
if(conn._dbInfo) {
// dynatraceConnection has already been called on conn, use new destinationInfo
// in case it changed, but don't wrap conn again
conn._dbInfo = dbInfo;
return conn;
}
conn._dbInfo = dbInfo;
// hana-client does not like decorating.
// because of that, we need to override the fn and pass the original fn for execution
const originalExecFn = conn.exec;
conn.exec = _ExecuteWrapperFn(conn, conn, originalExecFn, _dynatraceResultCallback);
conn.execute = conn.exec;
const originalPrepareFn = conn.prepare;
Object.defineProperty(conn, 'prepare', {value: _prepareStmtUsingDynatrace(conn, originalPrepareFn)});
return conn;
}
module.exports = { dynatraceConnection, isDynatraceEnabled };