diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index e4604ddfc..23c181260 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -135,6 +135,29 @@ The service must then be restarted for the change to take effect Restart-Service ---- +[[configuration-for-azure-functions]] +=== Configuration for Azure Functions +Configuration for Azure Functions can be provided by setting environment variables for +the specific Azure Functions using https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#settings[application settings in the Azure Portal]. + +[float] +[[config-overwritediscoverdefaultservicename]] +==== `Enabled` (added[1.27.0]) + +Setting this to `false` keeps the discover default service name. Else, the discover default service name is replaced by the Azure Functions name. + +[options="header"] +|============ +| Environment variable name | IConfiguration or Web.config key +| `ELASTIC_APM_OVERWRITE_DISCOVER_DEFAULT_SERVICE_NAME` | `ElasticApm:OverwriteDiscoverDefaultServiceName` +|============ + +[options="header"] +|============ +| Default | Type +| `true` | Boolean +|============ + [[configuration-on-asp-net]] === Configuration on ASP.NET diff --git a/src/Elastic.Apm/BackendComm/CentralConfig/RuntimeConfigurationSnapshot.cs b/src/Elastic.Apm/BackendComm/CentralConfig/RuntimeConfigurationSnapshot.cs index 8640058e3..0d5a11b0b 100644 --- a/src/Elastic.Apm/BackendComm/CentralConfig/RuntimeConfigurationSnapshot.cs +++ b/src/Elastic.Apm/BackendComm/CentralConfig/RuntimeConfigurationSnapshot.cs @@ -73,6 +73,9 @@ public ConfigurationKeyValue Lookup(ConfigurationOption option) => public int MaxQueueEventCount => _mainConfiguration.MaxQueueEventCount; public double MetricsIntervalInMilliseconds => _mainConfiguration.MetricsIntervalInMilliseconds; + + public bool OverwriteDiscoverDefaultServiceName => _mainConfiguration.OverwriteDiscoverDefaultServiceName; + public bool Recording => _dynamicConfiguration?.Recording ?? _mainConfiguration.Recording; public IReadOnlyList SanitizeFieldNames => _dynamicConfiguration?.SanitizeFieldNames ?? _mainConfiguration.SanitizeFieldNames; diff --git a/src/Elastic.Apm/Config/ConfigConsts.cs b/src/Elastic.Apm/Config/ConfigConsts.cs index 36671af72..c2795e89d 100644 --- a/src/Elastic.Apm/Config/ConfigConsts.cs +++ b/src/Elastic.Apm/Config/ConfigConsts.cs @@ -36,6 +36,7 @@ public static class DefaultValues public const int MaxQueueEventCount = 1000; public const string MetricsInterval = "30s"; public const double MetricsIntervalInMilliseconds = 30 * 1000; + public const bool OverwriteDiscoverDefaultServiceName = true; public const bool SpanCompressionEnabled = true; public const string SpanCompressionExactMatchMaxDuration = "50ms"; public const double SpanCompressionExactMatchMaxDurationInMilliseconds = 50; diff --git a/src/Elastic.Apm/Config/ConfigurationOption.cs b/src/Elastic.Apm/Config/ConfigurationOption.cs index 37acf2693..4415979e8 100644 --- a/src/Elastic.Apm/Config/ConfigurationOption.cs +++ b/src/Elastic.Apm/Config/ConfigurationOption.cs @@ -56,6 +56,8 @@ public enum ConfigurationOption MaxQueueEventCount, /// MetricsInterval, + /// + OverwriteDiscoverDefaultServiceName, /// Recording, /// @@ -160,6 +162,7 @@ public static string ToEnvironmentVariable(this ConfigurationOption option) => MaxBatchEventCount => EnvPrefix + "MAX_BATCH_EVENT_COUNT", MaxQueueEventCount => EnvPrefix + "MAX_QUEUE_EVENT_COUNT", MetricsInterval => EnvPrefix + "METRICS_INTERVAL", + OverwriteDiscoverDefaultServiceName => EnvPrefix + "OVERWRITE_DISCOVER_DEFAULT_SERVICE_NAME", Recording => EnvPrefix + "RECORDING", SanitizeFieldNames => EnvPrefix + "SANITIZE_FIELD_NAMES", SecretToken => EnvPrefix + "SECRET_TOKEN", @@ -212,6 +215,7 @@ public static string ToConfigKey(this ConfigurationOption option) => MaxBatchEventCount => KeyPrefix + nameof(MaxBatchEventCount), MaxQueueEventCount => KeyPrefix + nameof(MaxQueueEventCount), MetricsInterval => KeyPrefix + nameof(MetricsInterval), + OverwriteDiscoverDefaultServiceName => KeyPrefix + nameof(OverwriteDiscoverDefaultServiceName), Recording => KeyPrefix + nameof(Recording), SanitizeFieldNames => KeyPrefix + nameof(SanitizeFieldNames), SecretToken => KeyPrefix + nameof(SecretToken), diff --git a/src/Elastic.Apm/Config/FallbackToEnvironmentConfigurationBase.cs b/src/Elastic.Apm/Config/FallbackToEnvironmentConfigurationBase.cs index 65dc4a54a..5ea3b41b0 100644 --- a/src/Elastic.Apm/Config/FallbackToEnvironmentConfigurationBase.cs +++ b/src/Elastic.Apm/Config/FallbackToEnvironmentConfigurationBase.cs @@ -104,6 +104,7 @@ IConfigurationEnvironmentValueProvider environmentValueProvider MaxBatchEventCount = ParseMaxBatchEventCount(Lookup(ConfigurationOption.MaxBatchEventCount)); MaxQueueEventCount = ParseMaxQueueEventCount(Lookup(ConfigurationOption.MaxQueueEventCount)); MetricsIntervalInMilliseconds = ParseMetricsInterval(Lookup(MetricsInterval)); + OverwriteDiscoverDefaultServiceName = ParseRecording(Lookup(ConfigurationOption.OverwriteDiscoverDefaultServiceName)); Recording = ParseRecording(Lookup(ConfigurationOption.Recording)); SanitizeFieldNames = ParseSanitizeFieldNames(Lookup(ConfigurationOption.SanitizeFieldNames)); SecretToken = ParseSecretToken(Lookup(ConfigurationOption.SecretToken)); @@ -195,6 +196,8 @@ public ConfigurationKeyValue Lookup(ConfigurationOption option) => public double MetricsIntervalInMilliseconds { get; } + public bool OverwriteDiscoverDefaultServiceName { get; } + public bool Recording { get; } public IReadOnlyList SanitizeFieldNames { get; } diff --git a/src/Elastic.Apm/Config/IConfigurationReader.cs b/src/Elastic.Apm/Config/IConfigurationReader.cs index 75ef44879..06bb66c42 100644 --- a/src/Elastic.Apm/Config/IConfigurationReader.cs +++ b/src/Elastic.Apm/Config/IConfigurationReader.cs @@ -219,6 +219,14 @@ public interface IConfigurationReader : IConfigurationDescription, IConfiguratio double MetricsIntervalInMilliseconds { get; } + /// + /// Overwrite the discover default service name by the Azure Functions name. + /// + /// + /// This option is only used during the setup of Azure Functions agent + /// + bool OverwriteDiscoverDefaultServiceName { get; } + /// /// Whether the agent is recording. /// When set to true. the agent instruments and capture requests, tracks errors, and diff --git a/src/azure/Elastic.Apm.Azure.Functions/AzureFunctionsContext.cs b/src/azure/Elastic.Apm.Azure.Functions/AzureFunctionsContext.cs index 2ed9629f0..7fa71a424 100644 --- a/src/azure/Elastic.Apm.Azure.Functions/AzureFunctionsContext.cs +++ b/src/azure/Elastic.Apm.Azure.Functions/AzureFunctionsContext.cs @@ -18,7 +18,7 @@ internal AzureFunctionsContext(string loggerScopeName) { Logger = Agent.Instance.Logger.Scoped(loggerScopeName); MetaData = AzureFunctionsMetadataProvider.GetAzureFunctionsMetaData(Logger); - UpdateServiceInformation(Agent.Instance.Service); + UpdateServiceInformation(Agent.Instance.Service, Agent.Instance.Configuration.OverwriteDiscoverDefaultServiceName); FaasIdPrefix = $"/subscriptions/{MetaData.SubscriptionId}/resourceGroups/{MetaData.WebsiteResourceGroup}/providers/Microsoft.Web/sites/{MetaData.WebsiteSiteName}/functions/"; Logger.Trace()?.Log("FaasIdPrefix: {FaasIdPrefix}", FaasIdPrefix); @@ -32,7 +32,7 @@ internal AzureFunctionsContext(string loggerScopeName) internal static bool IsColdStart() => Interlocked.Exchange(ref ColdStart, 0) == 1; - private void UpdateServiceInformation(Service? service) + private void UpdateServiceInformation(Service? service, bool overwriteDiscoverDefaultServiceName) { if (service == null) { @@ -40,7 +40,7 @@ private void UpdateServiceInformation(Service? service) return; } - if (service.Name == AbstractConfigurationReader.AdaptServiceName(AbstractConfigurationReader.DiscoverDefaultServiceName())) + if (overwriteDiscoverDefaultServiceName && service.Name == AbstractConfigurationReader.AdaptServiceName(AbstractConfigurationReader.DiscoverDefaultServiceName())) { // Only override the service name if it was set to default. service.Name = MetaData.WebsiteSiteName; diff --git a/test/Elastic.Apm.Tests.Utilities/MockConfiguration.cs b/test/Elastic.Apm.Tests.Utilities/MockConfiguration.cs index 81fae3541..116ec11d3 100644 --- a/test/Elastic.Apm.Tests.Utilities/MockConfiguration.cs +++ b/test/Elastic.Apm.Tests.Utilities/MockConfiguration.cs @@ -68,7 +68,8 @@ public MockConfiguration(IApmLogger logger = null, string spanCompressionEnabled = null, string spanCompressionExactMatchMaxDuration = null, string spanCompressionSameKindMaxDuration = null, - string traceContinuationStrategy = null + string traceContinuationStrategy = null, + string overwritediscoverdefaultservicename = null ) : base( logger, new ConfigurationDefaults { DebugName = nameof(MockConfiguration) }, @@ -97,6 +98,7 @@ public MockConfiguration(IApmLogger logger = null, ConfigurationOption.MaxBatchEventCount => maxBatchEventCount, ConfigurationOption.MaxQueueEventCount => maxQueueEventCount, ConfigurationOption.MetricsInterval => metricsInterval, + ConfigurationOption.OverwriteDiscoverDefaultServiceName => overwritediscoverdefaultservicename, ConfigurationOption.Recording => recording, ConfigurationOption.SanitizeFieldNames => sanitizeFieldNames, ConfigurationOption.SecretToken => secretToken, diff --git a/test/Elastic.Apm.Tests/Config/ConfigTests.cs b/test/Elastic.Apm.Tests/Config/ConfigTests.cs index 7a7b829d3..13b7b15a8 100644 --- a/test/Elastic.Apm.Tests/Config/ConfigTests.cs +++ b/test/Elastic.Apm.Tests/Config/ConfigTests.cs @@ -1157,6 +1157,43 @@ public void DefaultApplicationNamespaceConfig() excludedNamespaces.Should().BeEquivalentTo(DefaultValues.DefaultExcludedNamespaces); } + /// + /// Makes sure that in case OverwriteDiscoverDefaultServiceName is not set, the agent uses true as default value + /// + [Fact] + public void OverwriteDiscoverDefaultServiceNameTestWithNoValue() + { + using var agent = + new ApmAgent(new TestAgentComponents( + configuration: new MockConfiguration())); + agent.Configuration.OverwriteDiscoverDefaultServiceName.Should().BeTrue(); + } + + /// + /// Makes sure that in case OverwriteDiscoverDefaultServiceName is set to invalid value, the agent uses true as default value + /// + [Fact] + public void OverwriteDiscoverDefaultServiceNameTestWithInvalidValue() + { + using var agent = + new ApmAgent(new TestAgentComponents( + configuration: new MockConfiguration(overwritediscoverdefaultservicename: "foobar"))); + agent.Configuration.OverwriteDiscoverDefaultServiceName.Should().BeTrue(); + } + + [Theory] + [InlineData("true", true)] + [InlineData("false", false)] + [InlineData("True", true)] + [InlineData("False", false)] + [InlineData(" True ", true)] + [InlineData(" False ", false)] + public void OverwriteDiscoverDefaultServiceNameTestWithValidValue(string value, bool expected) + { + using var agent = new ApmAgent(new TestAgentComponents(configuration: new MockConfiguration(overwritediscoverdefaultservicename: value))); + agent.Configuration.OverwriteDiscoverDefaultServiceName.Should().Be(expected); + } + private static double MetricsIntervalTestCommon(string configValue) { Environment.SetEnvironmentVariable(MetricsInterval.ToEnvironmentVariable(), configValue); diff --git a/test/Elastic.Apm.Tests/ConstructorTests.cs b/test/Elastic.Apm.Tests/ConstructorTests.cs index a897569de..767ba33bc 100644 --- a/test/Elastic.Apm.Tests/ConstructorTests.cs +++ b/test/Elastic.Apm.Tests/ConstructorTests.cs @@ -104,6 +104,7 @@ private class LogConfiguration : IConfiguration, IConfigurationDescription public int MaxBatchEventCount => ConfigConsts.DefaultValues.MaxBatchEventCount; public int MaxQueueEventCount => ConfigConsts.DefaultValues.MaxQueueEventCount; public double MetricsIntervalInMilliseconds => ConfigConsts.DefaultValues.MetricsIntervalInMilliseconds; + public bool OverwriteDiscoverDefaultServiceName => ConfigConsts.DefaultValues.OverwriteDiscoverDefaultServiceName; public string SecretToken { get; } public string ServerCert { get; } public string ApiKey { get; } diff --git a/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsIsolatedNotOverwriteTests.cs b/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsIsolatedNotOverwriteTests.cs new file mode 100644 index 000000000..ff72c0c3d --- /dev/null +++ b/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsIsolatedNotOverwriteTests.cs @@ -0,0 +1,29 @@ +// 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.Threading.Tasks; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; +using static Elastic.Apm.Api.Outcome; +using static Elastic.Apm.AzureFunctionApp.Core.FunctionName; + +namespace Elastic.Apm.Azure.Functions.Tests; + +[Collection("AzureFunctions")] +public class AzureFunctionsIsolatedNotOverwriteTests : AzureFunctionsTestBase, IClassFixture +{ + public AzureFunctionsIsolatedNotOverwriteTests(ITestOutputHelper output, IsolatedContextNotOverwite context) + : base(output, context) { } + + [Fact] + public async Task OverwriteDiscoverDefaultServiceName_False() + { + var transaction = await InvokeAndAssertFunction(SampleHttpTrigger); + + transaction.Outcome.Should().Be(Success); + transaction.Result.Should().Be("HTTP 2xx"); + transaction.Context.Response.StatusCode.Should().Be(200); + } +} diff --git a/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestBase.cs b/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestBase.cs index 034d66a42..e5137e842 100644 --- a/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestBase.cs +++ b/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestBase.cs @@ -59,7 +59,7 @@ private void AssertMetaData(MetadataDto metaData) metaData.Service.Agent.ActivationMethod.Should().Be(Consts.ActivationMethodNuGet); metaData.Cloud.Provider.Should().Be("azure"); metaData.Cloud.Service.Name.Should().Be("functions"); - metaData.Service.Name.Should().Be(Context.WebsiteName); + AssertServiceName(metaData.Service.Name); metaData.Service.Runtime.Name.Should().Be(Context.RuntimeName); metaData.Service.Framework.Name.Should().Be("Azure Functions"); metaData.Service.Framework.Version.Should().Be("4"); @@ -67,6 +67,14 @@ private void AssertMetaData(MetadataDto metaData) //metaData.Service.Node.ConfiguredName.Should().Be("20367ea8-70b9-41b4-a552-b2a826b3aa0b"); } + private void AssertServiceName(string name) + { + if (Context.OverwriteDiscoverDefaultServiceName == null || (bool)Context.OverwriteDiscoverDefaultServiceName) + name.Should().Be(Context.WebsiteName); + else + name.Should().NotBe(Context.WebsiteName); + } + private static void AssertTracing(TransactionDto transaction) => transaction.TraceId.Should().Be("0af7651916cd43dd8448eb211c80319c"); diff --git a/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestFixture.cs b/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestFixture.cs index 8c8d247b3..fb165b937 100644 --- a/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestFixture.cs +++ b/test/azure/Elastic.Apm.Azure.Functions.Tests/AzureFunctionsTestFixture.cs @@ -25,15 +25,27 @@ public class IsolatedContext : AzureFunctionTestContextBase protected override Uri BaseUri { get; } = new("http://localhost:7071"); public override string WebsiteName { get; } = "testfaas"; public override string RuntimeName { get; } = "dotnet-isolated"; + public override bool? OverwriteDiscoverDefaultServiceName { get; } = null; public IsolatedContext() : base(FunctionType.Isolated) { } } +public class IsolatedContextNotOverwite : AzureFunctionTestContextBase +{ + protected override Uri BaseUri { get; } = new("http://localhost:7071"); + public override string WebsiteName { get; } = "testfaas"; + public override string RuntimeName { get; } = "dotnet-isolated"; + public override bool? OverwriteDiscoverDefaultServiceName { get; } = false; + + public IsolatedContextNotOverwite() : base(FunctionType.Isolated) { } +} + public class InProcessContext : AzureFunctionTestContextBase { protected override Uri BaseUri { get; } = new("http://localhost:17073"); public override string WebsiteName { get; } = "testfaas"; public override string RuntimeName { get; } = "dotnet"; + public override bool? OverwriteDiscoverDefaultServiceName { get; } = null; public InProcessContext() : base(FunctionType.InProcess) { } } @@ -46,6 +58,7 @@ public abstract class AzureFunctionTestContextBase : IDisposable protected abstract Uri BaseUri { get; } public abstract string WebsiteName { get; } public abstract string RuntimeName { get; } + public abstract bool? OverwriteDiscoverDefaultServiceName { get; } public bool IsFirst { get; internal set; } @@ -85,7 +98,8 @@ internal AzureFunctionTestContextBase(FunctionType functionType) EnvironmentVariables = { ["ELASTIC_APM_SERVER_URL"] = $"http://localhost:{port}", - ["ELASTIC_APM_FLUSH_INTERVAL"] = "0" + ["ELASTIC_APM_FLUSH_INTERVAL"] = "0", + ["ELASTIC_APM_OVERWRITE_DISCOVER_DEFAULT_SERVICE_NAME"] = $"{OverwriteDiscoverDefaultServiceName}" }, UseShellExecute = false }