apminsight
Version:
monitor nodejs applications
398 lines (360 loc) • 12.3 kB
JavaScript
var fs = require("fs");
var path = require("path");
var constants = require("../constants");
var crypto = require("crypto");
const ini = require('ini');
var algorithm = "aes-256-cbc"; //For encryption and decryption
const os = require("os");
function isEmpty(string) {
return string === undefined || string === null || string === "";
}
function isNonEmptyString(str) {
return typeof str === "string" && !isEmpty(str) && /\S/.test(str);
}
function isPositiveNumber(num) {
// Use the existing isNumber function to check validity
if (!isNumber(num)) {
return false;
}
// If it's a valid number, just check if it's >= 0
return num >= 0;
}
function isNumber(value) {
if (value === null || value === undefined || value === '') {
return false;
}
// If it's already a number, just check for NaN
if (typeof value === 'number') {
return !isNaN(value);
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (trimmed === '') {
return false;
}
// Match valid integer or float, positive/negative
return /^-?\d+(\.\d+)?$/.test(trimmed);
}
return false;
}
function isTypeNumber(num) {
if (typeof num === "number") {
return true;
}
return false;
}
function isLetter(str) {
return str.length === 1 && str.match(/[a-z]/i) != null;
}
function isFunction(fn) {
return typeof fn === "function";
}
function isObject(obj) {
return typeof obj === "object";
}
function isBoolean(boolean) {
return typeof boolean === "boolean";
}
function isEmptyObject(obj) {
return Object.keys(obj).length === 0;
}
function convertToString(value) {
if (value && typeof value !== "string") {
return String(value);
}
return value;
}
/*eslint no-empty: "error"*/
function loadInfoFileSync(configInstance) {
if (isEmpty(configInstance.getBaseDir())) {
return {};
}
try {
var baseDirectory = configInstance.getBaseDir();
var appDirectoryName = configInstance.getApplicationName() + "_" + configInstance.getApplicationPort();
var appDirectoryPath = path.join(baseDirectory, appDirectoryName);
var newFilePath = path.join(appDirectoryPath, "apminsight.json");
if (!fs.existsSync(newFilePath)) {
return {};
}
return JSON.parse(fs.readFileSync(newFilePath));
} catch (e) {
console.error("[APM] Error while reading apminsight.json file: ", e);
}
return {};
}
function createInfoFileFolder() {
try {
var configDetails = apmInsightAgentInstance.getConfig();
var baseDirectory = configDetails.getBaseDir();
if (isEmpty(baseDirectory)) {
console.log("[APM] Base directory is empty, cannot create info file folder");
return;
}
var appDirectoryName = configDetails.getApplicationName() + "_" + configDetails.getApplicationPort();
var appDirectoryPath = path.join(baseDirectory, appDirectoryName);
if (!fs.existsSync(appDirectoryPath)) {
fs.mkdir(appDirectoryPath, { recursive: true }, (err) => {
if (err) {
console.log('[APM] Error creating app directory:', err);
return;
}
})
}
}
catch (err) {
console.error("[APM] Error while creating app directory : ", err);
}
}
function writeStream() {
return process.stdout;
}
function checkAndCreateBaseDir(options) {
var agentDataPath;
if (
isNonEmptyString(options.logsDir) &&
isNonEmptyString(options.appName)
) {
var dirName = "apminsightdata_" + options.appName + "_" + options.port;
agentDataPath = path.join(options.logsDir, dirName);
}
else if (isNonEmptyString(process.env.APMINSIGHT_AGENT_HOMEPATH)) {
// One agent requirement
agentDataPath = path.join(process.env.APMINSIGHT_AGENT_HOMEPATH, constants.oneAgentNodeSpecificDir);
}
else {
agentDataPath = path.join(process.cwd(), "apminsightdata");
}
try {
if (!fs.existsSync(agentDataPath)) {
fs.mkdirSync(agentDataPath, { recursive: true });
}
} catch (err) {
console.error(
`[APM] Failed to create logs directory at ${agentDataPath}: ${err.message || err}`,
err
);
// Fallback: If creation fails, try temporary directory as last resort
try {
agentDataPath = path.join(os.tmpdir(), "apminsightdata");
if (!fs.existsSync(agentDataPath)) {
fs.mkdirSync(agentDataPath, { recursive: true });
}
console.log(`[APM] Using temporary directory for agent data: ${agentDataPath}`);
} catch (fallbackErr) {
console.error(`[APM] Failed to create fallback directory: ${fallbackErr.message || fallbackErr}`, fallbackErr);
return;
}
}
return agentDataPath;
}
function getGenericThreshold(txnName) {
var threshold;
try {
if (isNonEmptyString(txnName)) {
const genericThreshold = apmInsightAgentInstance.getThreshold();
if (genericThreshold) {
const keyTxnList = genericThreshold.getKeyTxnList();
if (keyTxnList && keyTxnList.includes(txnName)) {
threshold = apmInsightAgentInstance.getKeyTxnThreshold()[txnName];
if (isEmpty(threshold)) {
console.warn(`[APM] threshold object was empty for txnName ${txnName} and assigned with getThreshold`);
threshold = apmInsightAgentInstance.getThreshold();
}
} else {
threshold = apmInsightAgentInstance.getThreshold();
}
}
}
} catch (err) {
console.error(`[APM] error while getting genericThreshold ${err}`, err);
threshold = apmInsightAgentInstance.getThreshold();
}
if (isEmpty(threshold)) {
threshold = apmInsightAgentInstance.getThreshold();
}
return threshold;
}
function checkpm2(pm2Id, options) {
var agentDataPath = path.join(options.agentBaseDir, String(pm2Id));
try {
if (!fs.existsSync(agentDataPath)) {
fs.mkdirSync(agentDataPath, { recursive: true });
}
} catch (e) {
console.log(
"[APM] Error while creating apminsight agent logs in " +
agentDataPath
);
return;
}
return agentDataPath;
}
function isValidLicenseKey(lKey) {
if (isEmpty(lKey)) {
return false;
} else {
var licenseKeySplit = lKey.split("_").pop();
if (
licenseKeySplit.length == 64 &&
lKey.match(constants.appManagerLicenseRegex)
) {
return true;
}
if (
licenseKeySplit.length <= 40 &&
lKey.match(constants.site24x7LicenseRegex)
) {
return true;
}
return false;
}
}
function toEncryptKey(lKey) {
try {
// the cipher function
var cipher = crypto.createCipheriv(
algorithm,
constants.securitykey,
constants.initVector
);
// encrypt the message
var encryptedData = cipher.update(lKey, "utf-8", "base64");
encryptedData += cipher.final("base64");
return encryptedData;
} catch (err) {
return false;
}
}
function toDecryptKey(lKey, securityKey, iV) {
try {
// the decipher function
var decipher = crypto.createDecipheriv(
algorithm,
securityKey || constants.securitykey,
iV || constants.initVector
);
// decrypt the message
let decryptedData = decipher.update(lKey, "base64", "utf-8");
decryptedData += decipher.final("utf8");
return decryptedData;
} catch (err) {
return false;
}
}
function fetchAppname(configInstance) {
try {
var filePath = path.join(process.cwd(), "package.json");
var packageJson = JSON.parse(fs.readFileSync(filePath));
if (!configInstance.getApplicationName()) {
if (packageJson && packageJson.name) {
configInstance.setApplicationName(packageJson.name);
} else {
configInstance.setApplicationName(constants.defaultAppName);
}
}
}
catch (e) {
console.error("[APM] Error while fetching app name from package.json, setting default name:", e);
if (!configInstance.getApplicationName()) {
configInstance.setApplicationName(constants.defaultAppName);
}
}
}
function parseIniFile(filePath) {
if (isEmpty(filePath)) {
return null;
}
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const parsedData = ini.parse(fileContent);
return parsedData;
} catch (err) {
console.error(`[APM] error parsing INI file at ${filePath}: ${err}`, err);
return null;
}
}
function isValidURL(url) {
try {
new URL(url); // Attempt to parse the URL
return true; // If no error, it's valid
} catch (err) {
return false; // Invalid URL
}
}
function randomIdGenerator(idBytes){
const SHARED_BUFFER = Buffer.allocUnsafe(idBytes);
return function generateId() {
for (let i = 0; i < idBytes / 4; i++) {
// unsigned right shift drops decimal part of the number
// it is required because if a number between 2**32 and 2**32 - 1 is generated, an out of range error is thrown by writeUInt32BE
SHARED_BUFFER.writeUInt32BE((Math.random() * Math.pow(2, 32)) >>> 0, i * 4);
}
// If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated
for (let i = 0; i < idBytes; i++) {
if (SHARED_BUFFER[i] > 0) {
break;
} else if (i === idBytes - 1) {
SHARED_BUFFER[idBytes - 1] = 1;
}
}
return SHARED_BUFFER.toString('hex', 0, idBytes);
};
}
// Common utility functions used by both WebTxn and BgTxn transactions
function getExporterFmtMethodInfo(baseChildTracker, txnObj) {
if (!baseChildTracker) {
return;
}
if (txnObj._spanInfo && txnObj._spanInfo.length < constants.maxTrackers) {
const spanInfo = baseChildTracker.getExporterFmtInfo(txnObj);
spanInfo && txnObj._spanInfo.push(spanInfo);
}
// Recursively process child trackers
const allChilds = baseChildTracker.getChildTrackers();
for (let index = 0; index < allChilds.length; index++) {
const currChild = allChilds[index];
getExporterFmtMethodInfo(currChild, txnObj);
}
}
function traverseChildTrackers(baseChildTracker, txnObj) {
if (!baseChildTracker) {
return;
}
var allChilds = baseChildTracker.getChildTrackers();
for (var index = 0; index < allChilds.length; index++) {
var currChild = allChilds[index];
traverseChildTrackers(currChild, txnObj);
}
if (!baseChildTracker.isCompleted()) {
apmInsightAgentInstance.endTracker(baseChildTracker, txnObj);
}
}
module.exports = {
isEmpty: isEmpty,
isNumber: isNumber,
isTypeNumber: isTypeNumber,
isPositiveNumber: isPositiveNumber,
isNonEmptyString: isNonEmptyString,
isLetter: isLetter,
isFunction: isFunction,
isObject: isObject,
isBoolean: isBoolean,
loadInfoFileSync: loadInfoFileSync,
checkAndCreateBaseDir: checkAndCreateBaseDir,
writeStream: writeStream,
getGenericThreshold: getGenericThreshold,
checkpm2: checkpm2,
isValidLicenseKey: isValidLicenseKey,
isEmptyObject: isEmptyObject,
toEncryptKey: toEncryptKey,
toDecryptKey: toDecryptKey,
convertToString: convertToString,
fetchAppname: fetchAppname,
createInfoFileFolder: createInfoFileFolder,
parseIniFile: parseIniFile,
isValidURL: isValidURL,
randomIdGenerator: randomIdGenerator,
getExporterFmtMethodInfo: getExporterFmtMethodInfo,
traverseChildTrackers: traverseChildTrackers
};