src/Elastic.Apm/AgentComponents.cs (223 lines of code) (raw):
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Elastic.Apm.Api;
using Elastic.Apm.BackendComm.CentralConfig;
using Elastic.Apm.Config;
using Elastic.Apm.DiagnosticListeners;
using Elastic.Apm.Features;
using Elastic.Apm.Helpers;
using Elastic.Apm.Logging;
using Elastic.Apm.Metrics;
using Elastic.Apm.Metrics.MetricsProvider;
using Elastic.Apm.Report;
using Elastic.Apm.ServerInfo;
#if NET8_0_OR_GREATER
using Elastic.Apm.OpenTelemetry;
#endif
#if NETFRAMEWORK
using Elastic.Apm.Config.Net4FullFramework;
#endif
namespace Elastic.Apm
{
public class AgentComponents : IApmAgent, IDisposable
{
public AgentComponents(
IApmLogger logger = null,
IConfigurationReader configurationReader = null,
IPayloadSender payloadSender = null
) : this(logger, configurationReader, payloadSender, null, null, null, null) { }
internal AgentComponents(
IApmLogger logger,
IConfigurationReader configurationReader,
IPayloadSender payloadSender,
IMetricsCollector metricsCollector,
ICurrentExecutionSegmentsContainer currentExecutionSegmentsContainer,
ICentralConfigurationFetcher centralConfigurationFetcher,
IApmServerInfo apmServerInfo,
BreakdownMetricsProvider breakdownMetricsProvider = null,
IHostNameDetector hostNameDetector = null
)
{
try
{
var config = CreateConfiguration(logger, configurationReader);
hostNameDetector ??= new HostNameDetector();
Logger = logger ?? GetGlobalLogger(DefaultLogger(null, configurationReader), config.LogLevel);
ConfigurationStore = new ConfigurationStore(new RuntimeConfigurationSnapshot(config), Logger);
Service = Service.GetDefaultService(config, Logger);
ApmServerInfo = apmServerInfo ?? new ApmServerInfo();
HttpTraceConfiguration = new HttpTraceConfiguration();
#if NET8_0_OR_GREATER
// Initialize early because ServerInfoCallback requires it and might execute
// before EnsureElasticActivityStarted runs
ElasticActivityListener = new ElasticActivityListener(this, HttpTraceConfiguration);
#endif
var systemInfoHelper = new SystemInfoHelper(Logger);
var system = systemInfoHelper.GetSystemInfo(Configuration.HostName, hostNameDetector);
PayloadSender = payloadSender
?? new PayloadSenderV2(Logger, ConfigurationStore.CurrentSnapshot, Service, system,
ApmServerInfo,
isEnabled: Configuration.Enabled, serverInfoCallback: ServerInfoCallback);
if (Configuration.Enabled)
breakdownMetricsProvider ??= new BreakdownMetricsProvider(Logger);
// initialize the tracer before central configuration or metric collection is started
TracerInternal = new Tracer(Logger, Service, PayloadSender, ConfigurationStore,
currentExecutionSegmentsContainer ?? new CurrentExecutionSegmentsContainer(), ApmServerInfo,
breakdownMetricsProvider);
#if NET8_0_OR_GREATER
EnsureElasticActivityStarted();
#endif
if (Configuration.Enabled)
{
var agentFeatures = AgentFeaturesProvider.Get(Logger);
//
// Central configuration
//
if (centralConfigurationFetcher != null)
CentralConfigurationFetcher = centralConfigurationFetcher;
else if (agentFeatures.Check(AgentFeature.RemoteConfiguration))
CentralConfigurationFetcher = new CentralConfigurationFetcher(Logger, ConfigurationStore, Service);
//
// Metrics collection
//
if (metricsCollector != null)
MetricsCollector = metricsCollector;
else if (agentFeatures.Check(AgentFeature.MetricsCollection))
MetricsCollector = new MetricsCollector(Logger, PayloadSender, ConfigurationStore, breakdownMetricsProvider);
MetricsCollector?.StartCollecting();
}
else
{
Logger.Info()
?.Log("The Elastic APM .NET Agent is disabled - the agent won't capture traces and metrics.");
}
}
catch (Exception e)
{
Logger.Error()?.LogException(e, "Failed initializing agent.");
}
}
private void EnsureElasticActivityStarted()
{
#if !NET8_0_OR_GREATER
return;
#else
if (!Configuration.OpenTelemetryBridgeEnabled) return;
// If the server version is not known yet, we enable the listener - and then the callback will do the version check once we have the version
if (ApmServerInfo.Version == null || ApmServerInfo.Version == new ElasticVersion(0, 0, 0, null))
ElasticActivityListener?.Start(TracerInternal);
// Otherwise do a version check
else if (ApmServerInfo.Version >= new ElasticVersion(7, 16, 0, string.Empty))
{
Logger.Info()?.Log("Starting OpenTelemetry (Activity) bridge");
ElasticActivityListener?.Start(TracerInternal);
}
else
{
Logger.Warning()
?.Log(
"OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer - bridge won't be enabled. Current Server version: {version}",
ApmServerInfo.Version.ToString());
ElasticActivityListener?.Dispose();
}
#endif
}
private void ServerInfoCallback(bool success, IApmServerInfo serverInfo)
{
#if !NET8_0_OR_GREATER
return;
#else
if (!Configuration.OpenTelemetryBridgeEnabled) return;
if (success)
{
if (serverInfo.Version >= new ElasticVersion(7, 16, 0, string.Empty))
{
Logger.Info()
?.Log("APM Server version ready - OpenTelemetry (Activity) bridge is active. Current Server version: {version}",
serverInfo.Version.ToString());
}
else
{
Logger.Warning()
?.Log(
"OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer - bridge won't be enabled. Current Server version: {version}",
serverInfo.Version.ToString());
ElasticActivityListener?.Dispose();
}
}
else
{
Logger.Warning()
?.Log(
"Unable to read server version - OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer. "
+ "The bridge remains active, but due to unknown server version it may not work as expected.");
}
#endif
}
#pragma warning disable IDE0022
private static IApmLogger DefaultLogger(IApmLogger logger, IConfigurationReader configurationReader)
{
#if NETFRAMEWORK
return logger ?? FullFrameworkDefaultImplementations.CreateDefaultLogger(configurationReader?.LogLevel);
#else
return logger ?? ConsoleLogger.LoggerOrDefault(configurationReader?.LogLevel);
#endif
}
private static IConfigurationReader CreateConfiguration(IApmLogger logger, IConfigurationReader configurationReader)
{
var configurationLogger = DefaultLogger(logger, configurationReader);
#if NETFRAMEWORK
return configurationReader
?? FullFrameworkDefaultImplementations.CreateConfigurationReaderFromConfiguredType(configurationLogger)
?? new AppSettingsConfiguration(configurationLogger);
#else
return configurationReader ?? new EnvironmentConfiguration(configurationLogger);
#endif
}
#pragma warning restore IDE0022
/// <summary>
/// This ensures agents will respect externally provided loggers.
/// <para>If the agent is started as part of profiling it should adhere to profiling configuration</para>
/// <para>If file logging environment variables are set we should always log to that location</para>
/// </summary>
/// <param name="fallbackLogger"></param>
/// <param name="agentLogLevel"></param>
/// <param name="environmentVariables"></param>
/// <returns></returns>
internal static IApmLogger GetGlobalLogger(IApmLogger fallbackLogger, LogLevel agentLogLevel, IDictionary environmentVariables = null)
{
try
{
var fileLogConfig = GlobalLogConfiguration.FromEnvironment(environmentVariables);
if (!fileLogConfig.IsActive)
{
fallbackLogger.Info()?.Log("No system wide logging configured, defaulting to fallback logger");
return fallbackLogger;
}
var effectiveLogLevel = LogLevelUtils.GetFinest(agentLogLevel, fileLogConfig.LogLevel);
if ((fileLogConfig.LogTargets & GlobalLogTarget.File) == GlobalLogTarget.File)
TraceLogger.TraceSource.Listeners.Add(new TextWriterTraceListener(fileLogConfig.AgentLogFilePath));
if ((fileLogConfig.LogTargets & GlobalLogTarget.StdOut) == GlobalLogTarget.StdOut)
TraceLogger.TraceSource.Listeners.Add(new TextWriterTraceListener(Console.Out));
var logger = new TraceLogger(effectiveLogLevel);
logger.Info()?.Log($"{nameof(fileLogConfig)} - {fileLogConfig}");
return logger;
}
catch (Exception e)
{
fallbackLogger.Warning()?.LogException(e, "Error in GetGlobalLogger");
}
return fallbackLogger;
}
#if NET8_0_OR_GREATER
private ElasticActivityListener ElasticActivityListener { get; }
#endif
internal ICentralConfigurationFetcher CentralConfigurationFetcher { get; }
internal IConfigurationStore ConfigurationStore { get; }
internal IMetricsCollector MetricsCollector { get; }
internal IApmServerInfo ApmServerInfo { get; }
internal HttpTraceConfiguration HttpTraceConfiguration { get; }
HashSet<Type> IApmAgentComponents.SubscribedListeners { get; } = new();
internal Tracer TracerInternal { get; }
[Obsolete("Please use Configuration property instead")]
public IConfigurationReader ConfigurationReader => Configuration;
public IConfigurationReader Configuration => ConfigurationStore.CurrentSnapshot;
public IApmLogger Logger { get; }
public IPayloadSender PayloadSender { get; }
/// <summary>
/// Identifies the monitored service. If this remains unset the agent
/// automatically populates it based on the entry assembly.
/// </summary>
/// <value>The service.</value>
public Service Service { get; }
public ITracer Tracer => TracerInternal;
public void Dispose()
{
if (MetricsCollector is IDisposable disposableMetricsCollector)
disposableMetricsCollector.Dispose();
if (PayloadSender is IDisposable disposablePayloadSender)
disposablePayloadSender.Dispose();
CentralConfigurationFetcher?.Dispose();
#if NET8_0_OR_GREATER
ElasticActivityListener?.Dispose();
#endif
}
}
}