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