UNPKG

@sap/hana-client

Version:

Official SAP HANA Node.js Driver

187 lines (172 loc) 6.46 kB
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 };