@instana/core
Version:
Core library for Instana's Node.js packages
254 lines (213 loc) • 8.31 kB
JavaScript
/*
* (c) Copyright IBM Corp. 2025
*/
;
const { DISABLABLE_INSTRUMENTATION_GROUPS } = require('../../tracing/constants');
const { CONFIG_SOURCES } = require('../../util/constants');
/** @type {import('../../core').GenericLogger} */
let logger;
/**
* @param {import('../../config').InstanaConfig} _config
*/
exports.init = function init(_config) {
logger = _config.logger;
};
/**
* Handles environment variables, and array inputs.
*
* Precedence order (highest to lowest):
* 1. Environment variables (`INSTANA_TRACING_DISABLE*`)
* 2. In-code tracing.disable
*
* @param {import('../../config').InstanaConfig} config
*/
exports.normalize = function normalize(config) {
if (!config?.tracing) config.tracing = {};
try {
const envDisableConfig = getDisableFromEnv();
if (envDisableConfig !== null) {
if (envDisableConfig === true) {
return { value: true, source: CONFIG_SOURCES.ENV };
}
if (envDisableConfig === false) {
return { value: {}, source: CONFIG_SOURCES.ENV };
}
if (envDisableConfig.instrumentations?.length || envDisableConfig.groups?.length) {
logger?.debug(`[config] env:INSTANA_TRACING_DISABLE* = ${JSON.stringify(envDisableConfig)}`);
if (envDisableConfig.instrumentations) {
envDisableConfig.instrumentations = normalizeArray(envDisableConfig.instrumentations);
}
if (envDisableConfig.groups) {
envDisableConfig.groups = normalizeArray(envDisableConfig.groups);
}
return { value: envDisableConfig, source: CONFIG_SOURCES.ENV };
}
}
if (config.tracing.disable === true) {
logger?.debug('[config] incode:tracing.disable = true');
return { value: true, source: CONFIG_SOURCES.INCODE };
}
const hasDisableConfig = isDisableConfigNonEmpty(config);
if (hasDisableConfig) {
logger?.debug(`[config] incode:tracing.disable = ${JSON.stringify(config.tracing.disable)}`);
}
const disableConfig = isDisableConfigNonEmpty(config) ? config.tracing.disable : null;
if (!disableConfig) return { value: {}, source: CONFIG_SOURCES.DEFAULT };
// Normalize instrumentations and groups
if (disableConfig?.instrumentations) {
disableConfig.instrumentations = normalizeArray(disableConfig.instrumentations);
}
if (disableConfig?.groups) {
disableConfig.groups = normalizeArray(disableConfig.groups);
}
// Handle if tracing.disable is an array
if (Array.isArray(disableConfig)) {
return { value: categorizeDisableEntries(disableConfig), source: CONFIG_SOURCES.INCODE };
}
return { value: disableConfig || {}, source: CONFIG_SOURCES.INCODE };
} catch (error) {
// Fallback to an empty disable config on error
logger?.debug(`Error while normalizing tracing.disable config: ${error?.message} ${error?.stack}`);
return { value: {}, source: CONFIG_SOURCES.DEFAULT };
}
};
/**
* Handles config from agent.
* @param {import('../../config').InstanaConfig} config
*/
exports.normalizeExternalConfig = function normalizeExternalConfig(config) {
try {
if (isNonEmptyObject(config.tracing.disable)) {
const flattenedEntries = flattenDisableConfigs(config.tracing.disable);
return { value: categorizeDisableEntries(flattenedEntries), source: CONFIG_SOURCES.AGENT };
}
} catch (error) {
logger?.debug(`Error while normalizing external tracing.disable config: ${error?.message} ${error?.stack}`);
}
return { value: {}, source: CONFIG_SOURCES.DEFAULT };
};
/**
* Precedence (highest to lowest):
* 1. INSTANA_TRACING_DISABLE=true/false
* 2. INSTANA_TRACING_DISABLE_INSTRUMENTATIONS / INSTANA_TRACING_DISABLE_GROUPS
* 3. INSTANA_TRACING_DISABLE=list
*
* @returns {import('../../config/types').Disable | boolean | null}
*/
function getDisableFromEnv() {
const disable = {};
// This env var may contains true/false and also both groups and instrumentations
if (process.env.INSTANA_TRACING_DISABLE) {
const envVarValue = process.env.INSTANA_TRACING_DISABLE;
if (envVarValue === 'true') {
logger?.debug('[config] env:INSTANA_TRACING_DISABLE = true');
return true;
}
if (envVarValue === 'false') {
logger?.debug('[config] env:INSTANA_TRACING_DISABLE = false');
return false;
}
if (envVarValue !== '') {
const categorized = categorizeDisableEntries(parseEnvVar(envVarValue));
if (categorized?.instrumentations?.length) {
disable.instrumentations = categorized.instrumentations;
}
if (categorized?.groups?.length) {
disable.groups = categorized.groups;
}
logger?.debug(`[config] env:INSTANA_TRACING_DISABLE = ${envVarValue}`);
}
}
if (process.env.INSTANA_TRACING_DISABLE_INSTRUMENTATIONS) {
disable.instrumentations = parseEnvVar(process.env.INSTANA_TRACING_DISABLE_INSTRUMENTATIONS);
logger?.debug(
`[config] env:INSTANA_TRACING_DISABLE_INSTRUMENTATIONS = ${process.env.INSTANA_TRACING_DISABLE_INSTRUMENTATIONS}`
);
}
if (process.env.INSTANA_TRACING_DISABLE_GROUPS) {
disable.groups = parseEnvVar(process.env.INSTANA_TRACING_DISABLE_GROUPS);
logger?.debug(`[config] env:INSTANA_TRACING_DISABLE_GROUPS = ${process.env.INSTANA_TRACING_DISABLE_GROUPS}`);
}
return Object.keys(disable).length > 0 ? disable : null;
}
/**
* @param {string} envVarValue
* @returns {string[]}
*/
function parseEnvVar(envVarValue) {
return envVarValue
.split(/[;,]/)
.map(item => item.trim().toLowerCase())
.filter(item => item !== '');
}
/**
* @param {any[]} arr
* @returns {string[]}
*/
function normalizeArray(arr) {
if (!Array.isArray(arr)) return [];
return arr.map(s => (typeof s === 'string' ? s.toLowerCase().trim() : null)).filter(Boolean);
}
/**
* Categorizes a flat array of strings into instrumentations and groups.
* Supports negated entries (e.g., '!console').
*
* @param {string[]} rawEntries
* @returns {{ instrumentations?: string[], groups?: string[] }}
*/
function categorizeDisableEntries(rawEntries) {
/** @type {string[]} */
const instrumentations = [];
/** @type {string[]} */
const groups = [];
rawEntries.forEach(entry => {
if (typeof entry !== 'string') return;
const normalizedEntry = entry?.toLowerCase().trim();
if (!normalizedEntry) return;
// This allows configurations like { console: false } to be interpreted as '!console',
// which means "do not disable console" — useful when overriding group disables.
const isNegated = normalizedEntry.startsWith('!');
const actualValue = isNegated ? normalizedEntry.slice(1) : normalizedEntry;
const normalized = isNegated ? `!${actualValue}` : actualValue;
// The supported groups are predefined in DISABLABLE_INSTRUMENTATION_GROUPS.
// If the entry matches one of these, we classify it as a group, otherwise, considered as an instrumentation.
if (DISABLABLE_INSTRUMENTATION_GROUPS.has(actualValue)) {
groups.push(normalized);
} else {
instrumentations.push(normalized);
}
});
const categorized = {};
if (instrumentations.length > 0) categorized.instrumentations = instrumentations;
if (groups.length > 0) categorized.groups = groups;
return categorized;
}
/**
* @param {*} disableConfig
* @returns {string[]}
*/
function flattenDisableConfigs(disableConfig) {
// Converts a config with boolean values into a flat list of strings.
// Each key is a instrumentation or group.
// - true - disable
// - false - enable, we internally formated by adding ! in prefix
// Example: { logging: true, console: false } => ['logging', '!console']
return Object.entries(disableConfig).flatMap(([entryName, shouldDisable]) => {
if (shouldDisable === true) return [entryName];
if (shouldDisable === false) return [`!${entryName}`];
return [];
});
}
/**
* @param {{}} obj
*/
function isNonEmptyObject(obj) {
return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length > 0;
}
/**
* @param {import('../../config').InstanaConfig} config
*/
function isDisableConfigNonEmpty(config) {
const disableConfig = config.tracing?.disable;
return isNonEmptyObject(disableConfig) || (Array.isArray(disableConfig) && disableConfig.length > 0);
}