UNPKG

ca-apm-probe

Version:

CA APM Node.js Agent monitors real-time health and performance of Node.js applications

606 lines (518 loc) 22 kB
var configFile = '../config.json'; var fs = require('fs'); var path = require('path'); var probeUtil = require('./utils/common-utils'); var probeNameResolver = require('./probename-resolver'); var configPath = path.resolve(__dirname, configFile); var config = require(configFile); var logger = null; var lastConfigRead = new Date(0); //These global variables var configData = config; var probeOptions = null; var deprecatedEnvMappings = null; const ENV_PREFIX = "apm_env_"; function startConfiguration(options) { //var prevConfig = JSON.parse(JSON.stringify(config)); deprecatedEnvMappings = getDeprecatedEnvMappings(); try { updateConfigWithBwCompatability(options); resolveConfigPropertiesFromEnvVars(config); options = resolveFromOptionsToConfig(options); //One time at start before PostConfig postConfig(options); probeOptions = options; logger = require("./logger.js"); //logger.info("[CA APM PROBE] Original Configuration: " + JSON.stringify(prevConfig)); logger.info("[CA APM PROBE] Updated Configuration: " + JSON.stringify(config)); logger.info("[CA APM PROBE] Probe will use Host Name: [%s], Probe Name: [%s], App Name: [%s], Protocol : [%s]", config.hostName, config.probeName, config.appName, config.protocol); if (logger && logger.isDebug()) { logger.debug('final resolved options:\n %s', require('util').inspect(options)); } deprecatedEnvMappings = null; updateLastConfigReadTime(); //Update Initial Time //var configReloadInterval = config.configReloadInterval; //setInterval(reloadConfiguration, configReloadInterval); fs.watchFile(configPath, configListener); } catch (e) { console.log("Config.startConfiguration Error : " + e); } } function updateConfigWithBwCompatability(options) { //1 argument = New Property, 2 argument = Old Property bwCompatibleProperty('http.browseragent.response.decoration.enabled', 'http.browseragent.responseDecEnabled'); bwCompatibleProperty('http.browseragent.response.decoration.cookie.expirationTime', 'http.browseragent.responseDecoration.cookieExpTime'); bwCompatibleProperty('http.browseragent.response.decoration.cookie.contentType', 'http.browseragent.responseDecoration.cookieContentType'); bwCompatibleProperty('http.browseragent.response.decoration.includeURLsRegex', 'http.browseragent.responseDecoration.includeURLsRegex'); bwCompatibleProperty('http.browseragent.response.decoration.apmDataEnabled', 'http.browseragent.responseDecoration.apmDataEnabled'); bwCompatibleProperty('http.browseragent.sustainabilityMetrics.enabled', 'http.browseragent.sustainabilityMetricsEnabled'); bwCompatibleProperty('http.browseragent.autoInjection.snippetLocation', 'http.browseragent.autoinjection.snippetLocation'); bwCompatibleProperty('http.browseragent.autoInjection.includeURLsRegex', 'http.browseragent.autoinjection.includeURLsRegex'); //For Attribute Decoration if (config.hasOwnProperty("attribute.decoration")) { var adObj = config["attribute.decoration"]; if (adObj.hasOwnProperty("enable.extension")) { bwCompatiblePropertyObject('attribute.decoration.enableExtension', adObj["enable.extension"], 'attribute.decoration.enable.extension'); } if (adObj.hasOwnProperty("reporting.interval")) { bwCompatiblePropertyObject('attribute.decoration.reportingInterval', adObj["reporting.interval"], 'attribute.decoration.reporting.interval'); } if (adObj.hasOwnProperty("env")) { bwCompatiblePropertyObject('attribute.decoration.environment.properties', adObj["env"], 'attribute.decoration.env'); } if (adObj.hasOwnProperty("static")) { bwCompatiblePropertyObject('attribute.decoration.static.attributes', adObj["static"], 'attribute.decoration.static'); } if (adObj.hasOwnProperty("ext") && adObj['ext'].hasOwnProperty("filepath")) { bwCompatiblePropertyObject('attribute.decoration.externalfile.fileName', adObj["ext"]["filepath"], 'attribute.decoration.ext.filepath'); } } //Depreicated Env Variables readProperty(['PROTOCOL'], 'protocol', "arf"); readProperty(['CA_APM_HOSTNAME'], 'hostName', resolveHostName()); readProperty(['CA_APM_APPNAME'], 'appName', resolveAppName()); readProperty(['CA_APM_PROBENAME'], 'probeName', options.probeName); if (config.infrastructureAgent) { readProperty(['INFRASTRUCTURE_AGENT_HOST', 'COLLECTOR_AGENT_HOST'], 'infrastructureAgent.host', "localhost"); readProperty(['INFRASTRUCTURE_AGENT_PORT', 'COLLECTOR_AGENT_PORT'], 'infrastructureAgent.port', null); } readProperty(['SPEAK_INTERVAL'], 'speakInterval', 5000); readProperty(['MAX_PING_DELAY'], 'maxPingDelay', 15000); readProperty(['COKIE_ENABLED'], 'cookieEnabled', false); if (config.security) { readProperty(['SECURITY_ENABLED'], 'security.enabled', false); readProperty(['CERTPATH'], 'security.certPath', ""); } readProperty(['REACT_HTMLPATH','react.htmlpath','public/index.html']); readProperty(['REACT_ENABLED','react.enabled',false]); //For Browser Agent if (config.http.browseragent) { readProperty(['BA_RESPONSE_DECORATION_ENABLED'], 'http.browseragent.response.decoration.enabled', false); readProperty(['BA_COOKIE_EXPIRY_TIME'], 'http.browseragent.response.decoration.cookie.expirationTime', null); readProperty(['BA_CORID_ENABLED'], 'http.browseragent.corGuidEnabled', null); readProperty(['BA_AUTO_INJECTION_ENABLED'], 'http.browseragent.autoInjectionEnabled', false); readProperty(['BA_DEFAULT_SNIPPET'], 'http.browseragent.snippetString', null); } if (config.logging) { readProperty(['LOG_ENABLE_FILE_MODE'], 'logging.fileModeLog', "enabled"); readProperty(['LOG_ENABLE_CONSOLE_MODE'], 'logging.consoleModeLog', "disabled"); readProperty(['LOG_FILE_MAX_SIZE'], 'logging.maxLogFileSize', 2); //2 MB readProperty(['LOG_FILE_MAX_COUNT'], 'logging.maxLogFileCount', 5); readProperty(['LOG_LEVEL'], 'logging.logLevel', null); } readProperty(['apmenv_introscope_agent_pod_namespace'], 'k8s.podNamespace', null); readProperty(['apmenv_introscope_agent_pod_name'], 'k8s.podName', null); readProperty(['apmenv_introscope_agent_pod_container_name'], 'k8s.containerName', null); readProperty(['apmenv_introscope_agent_pod_ipaddress'], 'k8s.podIpAddress', null); } function postConfig(options) { setLogFile(); //Special Handling for these properties, in this case value should be in JSON format if (config.attribute) { readPropertyInObjForm(['apm_env_attribute_decoration_environment_properties', 'APM_ENV_ATTRIBUTE_DECORATION_ENVIRONMENT_PROPERTIES'], 'attribute.decoration.environment.properties'); readPropertyInObjForm(['apm_env_attribute_decoration_static_attributes', 'APM_ENV_ATTRIBUTE_DECORATION_STATIC_ATTRIBUTES'], 'attribute.decoration.system.properties'); } //Setting Default Value for port based on protocol if (!config.infrastructureAgent.port) { if (config.protocol == 'arf') { config.infrastructureAgent.port = 5005; } else if (config.protocol == 'http') { config.infrastructureAgent.port = 8085; } } var probeNameDefault = 'NodeApplication'; if (!config.probeName) { config.probeName = probeNameDefault; } if (config.http.browseragent.hasOwnProperty("snippetString") && config.http.browseragent.snippetString) { config.http.browseragent.snippetString = transformSnippet(config.http.browseragent.snippetString); } options.collectorAgentHost = config.infrastructureAgent.host; options.collectorAgentPort = config.infrastructureAgent.port; options.protocol = config.protocol; options.interval = config.interval; //Set Update config configData = config; } function updateLastConfigReadTime() { var resolved = require.resolve(configFile); var stats = fs.statSync(resolved); lastConfigRead = stats.ctime; } function updateConfiguration() { try { delete require.cache[require.resolve(configFile)]; config = require(configFile); updateConfigWithBwCompatability(probeOptions); resolveConfigPropertiesFromEnvVars(config); postConfig(probeOptions); if (logger) { logger.logConfiguration(); } } catch (e) { console.log("updateConfiguration - Error : " + e); } } function configListener(curr, prev) { if (logger) { logger.debug("config file modified, triggering realoding of config file"); } reloadConfiguration() } function reloadConfiguration() { var resolved = require.resolve(configFile); var stats = fs.statSync(resolved); if (!stats || stats.ctime.getTime() <= lastConfigRead.getTime()) { return; } if (logger) { logger.info('Reloading log config from %s.', resolved); } lastConfigRead = stats.ctime; updateConfiguration(); //This it to call attribute decoration attributes if (config.protocol == 'arf') { var arfClient = require("./arf-client"); setTimeout(arfClient.reportConfigAttributes, 2000); } else if (config.protocol == 'http') { var httpClient = require("./http-client"); setTimeout(httpClient.reportConfigAttributes, 2000); } } function setLogFile() { if (!config.logging) { return; } var logFile = null; if (config.logging.logFile != "") { logFile = config.logging.logFile; if (isAbsolute(config.logging.logFile)) { isPathAbsolute = true; } else { logFile = __dirname + "/" + config.logging.logFile; } if (logFile.match("\\$\\{.*\\}")) { if (probeNameResolver.getProbeName()) { logFile = logFile.replace(new RegExp("\\$\\{.*\\}"), probeNameResolver.getProbeName()); } else { logFile = logFile.replace(new RegExp("\\$\\{.*\\}"), "NodeApplication"); } } } else { if (probeNameResolver.getProbeName()) { logFile = __dirname + "/../logs/Probe-" + probeNameResolver.getProbeName() + ".log"; } else { logFile = __dirname + "/../logs/Probe-" + "NodeApplication.log"; } } if (logFile) { config.logging.logFile = logFile; } } function isAbsolute(p) { return path.normalize(p + '/') === path.normalize(path.resolve(p) + '/'); } /** * It reads the value from given environment variables, * If it exists, it will update into the config property. */ function readProperty(envVarNames, configPropName, defaultValue) { if (envVarNames == undefined || envVarNames.length == 0) { return; } if (configPropName == undefined) { return; } var envVarValue; var propTokens = configPropName.split('.'); var configPropObj = config; var token = propTokens[0]; for (var idx = 0; idx < propTokens.length - 1; idx++) { configPropObj = configPropObj[propTokens[idx]]; token = propTokens[idx + 1]; } for (var idx = 0; idx < envVarNames.length; idx++) { var envVarName = envVarNames[idx]; envVarValue = process.env[envVarName]; if (envVarValue) { configPropObj[token] = envVarValue; if (deprecatedEnvMappings && deprecatedEnvMappings.has(envVarName)) { console.log("[CA APM PROBE] Deprication alert! Depricated env variable ['" + envVarName + "'] is used. Replace it with env variable ['" + deprecatedEnvMappings.get(envVarName) + "']"); } } } if (!configPropObj[token] && defaultValue) { configPropObj[token] = defaultValue; } } function readPropertyInObjForm(envVarNames, configPropName) { if (envVarNames == undefined || envVarNames.length == 0) { return; } if (configPropName == undefined) { return; } var envVarValue; var propTokens = configPropName.split('.'); var configPropObj = config; var token = propTokens[0]; for (var idx = 0; idx < propTokens.length - 1; idx++) { configPropObj = configPropObj[propTokens[idx]]; token = propTokens[idx + 1]; } for (var idx = 0; idx < envVarNames.length; idx++) { var envVarName = envVarNames[idx]; envVarValue = process.env[envVarName]; if (envVarValue) { try { configPropObj[token] = JSON.parse(envVarValue); } catch (e) { if (logger) { logger.debug("Enable to resolve env %s due to error %s", envVarName, e); } } } } } /** * It reads the config property value from 2 argument (old one), * If it exists, it will update into new config property. * 1st argument: New Property (name in string with dots) * 2nd argument: Old Property (name in string with dots) */ function bwCompatibleProperty(configPropName, configPropNameOld) { if (!configPropName || !configPropNameOld) { return; } var configPropOldObj = config; var oldPropFields = configPropNameOld.split('.'); for (var idx = 0; idx < oldPropFields.length; idx++) { if (!configPropOldObj[oldPropFields[idx]]) { return; } configPropOldObj = configPropOldObj[oldPropFields[idx]]; } var configPropObj = config; var propTokens = configPropName.split('.'); var token = propTokens[0]; for (var idx = 0; idx < propTokens.length - 1; idx++) { token = propTokens[idx]; if (!configPropObj[token]) { configPropObj[token] = {}; } configPropObj = configPropObj[token]; token = propTokens[idx + 1]; } if (!configPropObj[token]) { //Don't need to override if is already exists configPropObj[token] = configPropOldObj; console.log("[CA APM PROBE] Deprication alert! Depricated config ['" + configPropNameOld + "'] is used. Replace it with config ['" + configPropName + "'] in config.json"); } } /** * It reads the config property value from 2 argument (old one), * If it exists, it will update into new config property. * 1st argument: New Property (name in string with dots) * 2nd argument: Old Property value (it is direct value) * 3rd argument: Old Property value (it is direct value) */ function bwCompatiblePropertyObject(configPropName, configPropValueOld, configPropNameOld) { if (!configPropName) { return; } var configPropObj = config; var propTokens = configPropName.split('.'); var token = propTokens[0]; for (var idx = 0; idx < propTokens.length - 1; idx++) { token = propTokens[idx]; if (!configPropObj[token]) { configPropObj[token] = {}; } configPropObj = configPropObj[token]; token = propTokens[idx + 1]; } if (!configPropObj[token]) { //Don't need to override if is already exists configPropObj[token] = configPropValueOld; console.log("[CA APM PROBE] Deprication alert! Depricated config ['" + configPropNameOld + "'] is used. Replace it with config ['" + configPropName + "'] in config.json"); } } /** * It reads the config whole structure recursively, * and check environment variable for each property by prefixing with 'apm_env_'. * It will update config back if environment variable exists. * It check first case-sensitive env name and then with all UPPER Case env name. */ function resolveConfigPropertiesFromEnvVars(configObj) { if (!configObj) { return; } var stack = []; var path = ""; while (configObj && isObject(configObj)) { var keys = Object.keys(configObj); keys.forEach(key => { var value = configObj[key]; var pathKey = (path.length == 0) ? key : path + "_" + key; if (isObject(value)) { stack.push({ "path": pathKey, "value": value }); } else { //get value from environment variable var envVarValue = getEnvVariableValue(pathKey); if (envVarValue) { configObj[key] = envVarValue; //console.log("Env Variable: apm_env_" + pathKey + "=" + configObj[key]); } } }); if (stack.length == 0) { break; } var stackObj = stack.pop(); configObj = stackObj.value; path = stackObj.path; } } function getEnvVariableValue(key) { var envVarName = ENV_PREFIX + key; var envVarValue = process.env[envVarName]; if (envVarValue) { if(envVarValue === 'true' || envVarValue === 'false') { envVarValue = envVarValue === 'true'; } return envVarValue; } envVarValue = process.env[envVarName.toUpperCase()]; if (envVarValue) { if(envVarValue === 'true' || envVarValue === 'false') { envVarValue = envVarValue === 'true'; } return envVarValue; } return null; } const isObject = (value) => { return !!(value && typeof value === "object" && !Array.isArray(value)); }; function getConfigData() { return configData; } function getProbeOptions() { return probeOptions; } function resolveFromOptionsToConfig(options) { if (!options) { var options = {}; } if (options.hasOwnProperty("protocol") && options.protocol) { config.protocol = options.protocol; } if (options.hasOwnProperty("collectorAgentHost") && options.collectorAgentHost) { config.infrastructureAgent.host = options.collectorAgentHost; } if (options.hasOwnProperty("collectorAgentPort") && options.collectorAgentPort) { config.infrastructureAgent.port = options.collectorAgentPort; } if (options.hasOwnProperty("interval") && options.interval) { config.interval = options.interval; } return options; } function resolveAppName() { var appNames = []; // app name from env variable appNames[0] = process.env.CA_APM_APPNAME || process.env.APM_ENV_APPNAME; // from config file appNames[1] = config.appName ? probeUtil.resolveEnv(config.appName) : null; // app name from application appNames[2] = probeUtil.findAppName(); // default app name appNames[3] = 'NodeApplication'; for (var i = 0; i < appNames.length; i++) { if (appNames[i]) { if (logger) { logger.debug("App Name Detection choosing the name: " + appNames[i]); } return appNames[i]; } } return null; } function resolveHostName() { var hostNames = []; // host name from env variable hostNames[0] = process.env.CA_APM_HOSTNAME || process.env.APM_ENV_HOSTNAME; // from config file hostNames[1] = config.hostName ? probeUtil.resolveEnv(config.hostName) : null; for (var i = 0; i < hostNames.length; i++) { if (hostNames[i]) { if (logger) { logger.debug("Host Name Detection choosing the name: " + hostNames[i]); } return hostNames[i]; } } return null; } function transformSnippet(snippet) { if (snippet == undefined || snippet == '') { return null; } if (snippet.includes('type="text/')) { return snippet; } else { var snippetArr = snippet.split('<script '); var updatedSnippet = snippetArr[1].toString(); updatedSnippet = updatedSnippet.replace('async >', 'async>'); updatedSnippet = updatedSnippet.replace(/=/g, '="'); updatedSnippet = updatedSnippet.replace(/ /g, '" '); updatedSnippet = updatedSnippet.replace(/agent="browser"/g, 'agent=browser"'); updatedSnippet = '<script ' + updatedSnippet; return updatedSnippet; } } function getDeprecatedEnvMappings() { var depEnvMappings = new Map(); depEnvMappings.set('PROTOCOL', 'APM_ENV_PROTOCOL'); depEnvMappings.set('CA_APM_HOSTNAME', 'APM_ENV_HOSTNAME'); depEnvMappings.set('CA_APM_APPNAME', 'APM_ENV_APPNAME'); depEnvMappings.set('CA_APM_PROBENAME', 'APM_ENV_PROBENAME'); depEnvMappings.set('INFRASTRUCTURE_AGENT_HOST', 'APM_ENV_INFRASTRUCTUREAGENT_HOST'); depEnvMappings.set('COLLECTOR_AGENT_HOST', 'APM_ENV_INFRASTRUCTUREAGENT_HOST'); depEnvMappings.set('INFRASTRUCTURE_AGENT_PORT', 'APM_ENV_INFRASTRUCTUREAGENT_PORT'); depEnvMappings.set('COLLECTOR_AGENT_PORT', 'APM_ENV_INFRASTRUCTUREAGENT_PORT'); depEnvMappings.set('SPEAK_INTERVAL', 'APM_ENV_SPEAKINTERVAL'); depEnvMappings.set('MAX_PING_DELAY', 'APM_ENV_MAXPINGDELAY'); depEnvMappings.set('COKIE_ENABLED', 'APM_ENV_COKIEENABLED'); depEnvMappings.set('SECURITY_ENABLED', 'APM_ENV_SECURITY_ENABLED'); depEnvMappings.set('CERTPATH', 'APM_ENV_CERTPATH'); depEnvMappings.set('LOG_ENABLE_FILE_MODE', 'APM_ENV_LOGGING_FILEMODELOG'); depEnvMappings.set('LOG_ENABLE_CONSOLE_MODE', 'APM_ENV_LOGGING_CONSOLEMODELOG'); depEnvMappings.set('LOG_FILE_MAX_SIZE', 'APM_ENV_LOGGING_MAXLOGFILESIZE'); depEnvMappings.set('LOG_FILE_MAX_COUNT', 'APM_ENV_LOGGING_MAXLOGFILECOUNT'); depEnvMappings.set('LOG_LEVEL', 'APM_ENV_LOGGING_LOGLEVEL'); return depEnvMappings; } module.exports.startConfiguration = startConfiguration; module.exports.updateConfiguration = updateConfiguration; module.exports.getConfigData = getConfigData; module.exports.getProbeOptions = getProbeOptions;