Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure functions overwite discover default service name #2321

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,29 @@ The service must then be restarted for the change to take effect
Restart-Service <service-name>
----

[[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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<WildcardMatcher> SanitizeFieldNames => _dynamicConfiguration?.SanitizeFieldNames ?? _mainConfiguration.SanitizeFieldNames;
Expand Down
1 change: 1 addition & 0 deletions src/Elastic.Apm/Config/ConfigConsts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/Elastic.Apm/Config/ConfigurationOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public enum ConfigurationOption
MaxQueueEventCount,
/// <inheritdoc cref="IConfigurationReader.MetricsIntervalInMilliseconds"/>
MetricsInterval,
/// <inheritdoc cref="IConfigurationReader.OverwriteDiscoverDefaultServiceName"/>
OverwriteDiscoverDefaultServiceName,
/// <inheritdoc cref="IConfigurationReader.Recording"/>
Recording,
/// <inheritdoc cref="IConfigurationReader.SanitizeFieldNames"/>
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -195,6 +196,8 @@ public ConfigurationKeyValue Lookup(ConfigurationOption option) =>

public double MetricsIntervalInMilliseconds { get; }

public bool OverwriteDiscoverDefaultServiceName { get; }

public bool Recording { get; }

public IReadOnlyList<WildcardMatcher> SanitizeFieldNames { get; }
Expand Down
8 changes: 8 additions & 0 deletions src/Elastic.Apm/Config/IConfigurationReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ public interface IConfigurationReader : IConfigurationDescription, IConfiguratio

double MetricsIntervalInMilliseconds { get; }

/// <summary>
/// Overwrite the discover default service name by the Azure Functions name.
/// </summary>
/// <remarks>
/// This option is only used during the setup of Azure Functions agent
/// </remarks>
bool OverwriteDiscoverDefaultServiceName { get; }

/// <summary>
/// Whether the agent is recording.
/// When set to <c>true</c>. the agent instruments and capture requests, tracks errors, and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -32,15 +32,15 @@ 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)
{
Logger.Warning()?.Log($"{nameof(UpdateServiceInformation)}: service is null");
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;
Expand Down
4 changes: 3 additions & 1 deletion test/Elastic.Apm.Tests.Utilities/MockConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down Expand Up @@ -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,
Expand Down
37 changes: 37 additions & 0 deletions test/Elastic.Apm.Tests/Config/ConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,43 @@ public void DefaultApplicationNamespaceConfig()
excludedNamespaces.Should().BeEquivalentTo(DefaultValues.DefaultExcludedNamespaces);
}

/// <summary>
/// Makes sure that in case OverwriteDiscoverDefaultServiceName is not set, the agent uses true as default value
/// </summary>
[Fact]
public void OverwriteDiscoverDefaultServiceNameTestWithNoValue()
{
using var agent =
new ApmAgent(new TestAgentComponents(
configuration: new MockConfiguration()));
agent.Configuration.OverwriteDiscoverDefaultServiceName.Should().BeTrue();
}

/// <summary>
/// Makes sure that in case OverwriteDiscoverDefaultServiceName is set to invalid value, the agent uses true as default value
/// </summary>
[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);
Expand Down
1 change: 1 addition & 0 deletions test/Elastic.Apm.Tests/ConstructorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IsolatedContextNotOverwite>
{
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,22 @@ 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");
// TODO - temporarily removing this assertion as we can no longer seem to set this value without causing a host error
//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");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }
}
Expand All @@ -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; }

Expand Down Expand Up @@ -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
}
Expand Down