UNPKG

applicationinsights

Version:
332 lines 17.2 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentLoader = void 0; const identity_1 = require("@azure/identity"); const util_1 = require("../shared/util"); const consoleWriter_1 = require("./diagnostics/writers/consoleWriter"); const diagnosticLogger_1 = require("./diagnostics/diagnosticLogger"); const statusLogger_1 = require("./diagnostics/statusLogger"); const types_1 = require("./types"); const main_1 = require("../main"); const forceStart = process.env.APPLICATIONINSIGHTS_FORCE_START === "true"; const OTEL_DETECTION_DELAY_MS = 60000; // 1 minute // Azure Connection String const ENV_connectionString = "APPLICATIONINSIGHTS_CONNECTION_STRING"; const ENV_AZURE_PREFIX = "APPSETTING_"; // Azure adds this prefix to all environment variables const ENV_IKEY = "APPINSIGHTS_INSTRUMENTATIONKEY"; // This key is provided in the readme const LEGACY_ENV_IKEY = "APPINSIGHTS_INSTRUMENTATION_KEY"; const LINUX_USER_APPLICATION_INSIGHTS_PATH = "/node_modules/applicationinsights/out/applicationinsights.js"; class AgentLoader { constructor() { // Open Telemetry and AAD packages unsusable in older versions of Node.js runtime // https://github.com/open-telemetry/opentelemetry-js?tab=readme-ov-file#supported-runtimes if (types_1.NODE_JS_RUNTIME_MAJOR_VERSION < 14) { this._canLoad = false; } else { this._canLoad = true; this._aadCredential = this._getAuthenticationCredential(); // Default options this._options = { azureMonitorExporterOptions: { disableOfflineStorage: false, }, enableAutoCollectExceptions: true, enableAutoCollectPerformance: true, samplingRatio: 1, enableLiveMetrics: true, instrumentationOptions: { azureSdk: { enabled: true }, http: { enabled: true }, mongoDb: { enabled: true }, mySql: { enabled: true }, postgreSql: { enabled: true }, redis4: { enabled: true }, redis: { enabled: true }, } }; const connectionString = process.env[ENV_connectionString]; if (connectionString) { this._instrumentationKey = this._getInstrumentationKey(connectionString); } else { const instrumentationKey = process.env[ENV_IKEY] || process.env[ENV_AZURE_PREFIX + ENV_IKEY] || process.env[LEGACY_ENV_IKEY] || process.env[ENV_AZURE_PREFIX + LEGACY_ENV_IKEY]; this._instrumentationKey = instrumentationKey || "unknown"; } // Default diagnostic using console this._diagnosticLogger = new diagnosticLogger_1.DiagnosticLogger(this._instrumentationKey, new consoleWriter_1.ConsoleWriter()); this._statusLogger = new statusLogger_1.StatusLogger(this._instrumentationKey, new consoleWriter_1.ConsoleWriter()); this._isWindows = process.platform === 'win32'; this._isLinux = process.platform === 'linux'; } } _getInstrumentationKey(connectionString) { if (connectionString) { const kvPairs = connectionString.split(";"); for (let i = 0; i < kvPairs.length; i++) { const kvParts = kvPairs[i].split("="); if (kvParts.length === 2 && kvParts[0].toLowerCase() === "instrumentationkey") { return kvParts[1]; } } } return ""; } // Exposed so ETW logger could be provider in IPA code setLogger(logger) { this._diagnosticLogger = logger; } initialize() { if (!this._canLoad) { const msg = `Cannot load Azure Monitor Application Insights Distro because of unsupported Node.js runtime, currently running in version ${types_1.NODE_JS_RUNTIME_MAJOR_VERSION}`; console.log(msg); return; } // Schedule detection of OpenTelemetry globals after a delay to allow customer application to initialize const otelDetectionTimeout = setTimeout(() => { this._detectOpenTelemetryGlobals(); }, OTEL_DETECTION_DELAY_MS); // Ensure the timeout doesn't prevent the process from exiting otelDetectionTimeout.unref(); if (this._validate()) { try { // Set environment variable to auto attach so the distro is aware of the attach state process.env[types_1.AZURE_MONITOR_AUTO_ATTACH] = "true"; // Initialize Distro this._options.azureMonitorExporterOptions.credential = this._aadCredential; (0, main_1.useAzureMonitor)(this._options); // Agent successfully initialized const diagnosticLog = { message: "Azure Monitor Application Insights Distro was started succesfully.", messageId: types_1.DiagnosticMessageId.attachSuccessful }; this._diagnosticLogger.logMessage(diagnosticLog); this._statusLogger.logStatus({ AgentInitializedSuccessfully: true }); } catch (error) { const msg = `Error initializaing Azure Monitor Application Insights Distro.${util_1.Util.getInstance().dumpObj(error)}`; const diagnosticLog = { message: msg, messageId: types_1.DiagnosticMessageId.unknownError }; this._diagnosticLogger.logMessage(diagnosticLog); this._statusLogger.logStatus({ AgentInitializedSuccessfully: false, Reason: msg }); } } } _validate() { try { if (!forceStart && this._sdkAlreadyExists()) { this._statusLogger.logStatus({ AgentInitializedSuccessfully: false, SDKPresent: true, Reason: "Azure Monitor Application Insights Distro already available." }); return false; } if (this._instrumentationKey === "unknown") { const diagnosticLog = { message: "Azure Monitor Application Insights Distro wanted to be started, but no Connection String was provided", messageId: types_1.DiagnosticMessageId.missingIkey }; this._diagnosticLogger.logMessage(diagnosticLog); this._statusLogger.logStatus({ AgentInitializedSuccessfully: false, Reason: diagnosticLog.message }); return false; } return true; } catch (err) { const msg = `Failed to validate Azure Monitor Application Insights Distro initialization.${util_1.Util.getInstance().dumpObj(err)}`; console.log(msg); if (this._diagnosticLogger) { const diagnosticLog = { message: msg, messageId: types_1.DiagnosticMessageId.unknownError }; this._diagnosticLogger.logMessage(diagnosticLog); } if (this._statusLogger) { this._statusLogger.logStatus({ AgentInitializedSuccessfully: false, Reason: msg }); } } } _detectOpenTelemetryGlobals() { var _a, _b, _c, _d, _e, _f, _g, _h, _j; try { const detectedProviders = []; // Check for OpenTelemetry globals directly on the global object // The OpenTelemetry API stores globals using Symbol.for('opentelemetry.js.api.<major>') // This avoids calling the API methods which could have side effects // Try v1 first, then fallback to v2 for future compatibility const otelSymbolV1 = Symbol.for('opentelemetry.js.api.1'); const otelSymbolV2 = Symbol.for('opentelemetry.js.api.2'); const otelGlobal = global[otelSymbolV1] || global[otelSymbolV2]; if (otelGlobal) { // Check for registered TracerProvider if (otelGlobal["trace"]) { const traceProviderName = (_b = (_a = otelGlobal["trace"]) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name; // ProxyTracerProvider wraps the real provider - check the delegate if (traceProviderName === 'ProxyTracerProvider') { const delegateName = (_e = (_d = (_c = otelGlobal["trace"]) === null || _c === void 0 ? void 0 : _c._delegate) === null || _d === void 0 ? void 0 : _d.constructor) === null || _e === void 0 ? void 0 : _e.name; if (delegateName && delegateName !== 'NoopTracerProvider') { detectedProviders.push('TracerProvider'); } } else if (traceProviderName && traceProviderName !== 'NoopTracerProvider') { detectedProviders.push('TracerProvider'); } } // Check for registered MeterProvider if (otelGlobal["metrics"] && ((_g = (_f = otelGlobal["metrics"]) === null || _f === void 0 ? void 0 : _f.constructor) === null || _g === void 0 ? void 0 : _g.name) !== 'ProxyMeterProvider' && otelGlobal["metrics"].constructor.name !== 'NoopMeterProvider') { detectedProviders.push('MeterProvider'); } } // Check for registered LoggerProvider - uses a different symbol and stores a getter function const logsSymbol = Symbol.for('io.opentelemetry.js.api.logs'); const logsGlobal = global[logsSymbol]; if (typeof logsGlobal === 'function') { // logsGlobal is a getter function that takes a version number and returns the provider // Try both API compatibility versions (1 and 2) to support different @opentelemetry/api-logs versions let logsProvider = logsGlobal(1); // Try v1 first if (!logsProvider || ((_h = logsProvider.constructor) === null || _h === void 0 ? void 0 : _h.name) === 'NoopLoggerProvider') { logsProvider = logsGlobal(2); // Try v2 if v1 returns NOOP } const loggerProviderName = (_j = logsProvider === null || logsProvider === void 0 ? void 0 : logsProvider.constructor) === null || _j === void 0 ? void 0 : _j.name; if (loggerProviderName && loggerProviderName !== 'ProxyLoggerProvider' && loggerProviderName !== 'NoopLoggerProvider') { detectedProviders.push('LoggerProvider'); } } if (detectedProviders.length > 0 && this._diagnosticLogger) { const msg = `OpenTelemetry global providers detected while using Application Insights auto-attach: ${detectedProviders.join(', ')}. `; const diagnosticLog = { message: msg, messageId: types_1.DiagnosticMessageId.openTelemetryConflict }; this._diagnosticLogger.logMessage(diagnosticLog); } } catch (err) { console.log("Error detecting OpenTelemetry globals: " + err); } } _getAuthenticationCredential() { let credential = undefined; // Try to add AAD Token Credential try { const authenticationString = process.env["APPLICATIONINSIGHTS_AUTHENTICATION_STRING"]; if (authenticationString) { const kvPairs = authenticationString.split(";"); const result = kvPairs.reduce((fields, kv) => { const kvParts = kv.split("="); if (kvParts.length === 2) { // only save fields with valid formats const key = kvParts[0].toLowerCase(); const value = kvParts[1]; fields[key] = value; } return fields; }, {}); if (result["authorization"] && result["authorization"] === "AAD") { const clientId = result["clientid"]; if (clientId) { console.log('AppInsightsAgent: ClientId found, trying to authenticate using Managed Identity.'); credential = new identity_1.ManagedIdentityCredential(clientId); } else { console.log('AppInsightsAgent: Trying to authenticate using System assigned Managed Identity.'); credential = new identity_1.ManagedIdentityCredential(); // System assigned identity } } } } catch (authError) { const msg = `Failed to get authentication credential and enable AAD.${util_1.Util.getInstance().dumpObj(authError)}`; console.log(msg); if (this._diagnosticLogger) { const diagnosticLog = { message: msg, messageId: types_1.DiagnosticMessageId.aadEnabled }; this._diagnosticLogger.logMessage(diagnosticLog); } } return credential; } _sdkAlreadyExists() { try { // appInstance should either resolve to user SDK or crash. If it resolves to attach SDK, user probably modified their NODE_PATH let shimInstance; let exporterInstance; try { // Node 8.9+ Windows if (this._isWindows) { shimInstance = require.resolve("applicationinsights", { paths: [process.cwd()] }); exporterInstance = require.resolve("@azure/monitor-opentelemetry-exporter", { paths: [process.cwd()] }); } // Node 8.9+ Linux else if (this._isLinux) { shimInstance = `${process.cwd()}${require.resolve("applicationinsights", { paths: [process.cwd()] })}`; exporterInstance = `${process.cwd()}${require.resolve("@azure/monitor-opentelemetry-exporter", { paths: [process.cwd()] })}`; } } catch (e) { // Node <8.9 shimInstance = require.resolve(`${process.cwd()}/node_modules/applicationinsights`); exporterInstance = require.resolve(`${process.cwd()}/node_modules/@azure/monitor-opentelemetry-exporter`); } /** * If loaded instance is in Azure machine home path do not attach the SDK, this means customer already instrumented their app. * Linux App Service doesn't append the full cwd to the require.resolve, so we need to check for the relative path we expect * if application insights is being imported in the user app code. */ if (shimInstance.indexOf("home") > -1 || exporterInstance.indexOf("home") > -1 || (shimInstance === LINUX_USER_APPLICATION_INSIGHTS_PATH && this._isLinux)) { const diagnosticLog = { message: `Azure Monitor Distro, Exporter, or Application Insights already exists. Module is already installed in this application; not re-attaching. Location: ${shimInstance}`, messageId: types_1.DiagnosticMessageId.sdkExists }; this._diagnosticLogger.logMessage(diagnosticLog); return true; } // ApplicationInsights or Azure Monitor Distro could be loaded outside of customer application, attach in this case return false; } catch (e) { // crashed while trying to resolve "applicationinsights", so SDK does not exist. Attach appinsights return false; } } } exports.AgentLoader = AgentLoader; //# sourceMappingURL=agentLoader.js.map