Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net10.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ protected internal DiagnosticsOptions(Microsoft.Extensions.Configuration.IConfig
public int LoggedContentSizeLimit { get { throw null; } set { } }
public System.Collections.Generic.IList<string> LoggedHeaderNames { get { throw null; } }
public System.Collections.Generic.IList<string> LoggedQueryParameters { get { throw null; } }
public int MaxApplicationIdLength { get { throw null; } set { } }
Comment thread
m-redding marked this conversation as resolved.
Outdated
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct HttpHeader : System.IEquatable<Azure.Core.HttpHeader>
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net462.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ protected internal DiagnosticsOptions(Microsoft.Extensions.Configuration.IConfig
public int LoggedContentSizeLimit { get { throw null; } set { } }
public System.Collections.Generic.IList<string> LoggedHeaderNames { get { throw null; } }
public System.Collections.Generic.IList<string> LoggedQueryParameters { get { throw null; } }
public int MaxApplicationIdLength { get { throw null; } set { } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct HttpHeader : System.IEquatable<Azure.Core.HttpHeader>
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net472.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ protected internal DiagnosticsOptions(Microsoft.Extensions.Configuration.IConfig
public int LoggedContentSizeLimit { get { throw null; } set { } }
public System.Collections.Generic.IList<string> LoggedHeaderNames { get { throw null; } }
public System.Collections.Generic.IList<string> LoggedQueryParameters { get { throw null; } }
public int MaxApplicationIdLength { get { throw null; } set { } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct HttpHeader : System.IEquatable<Azure.Core.HttpHeader>
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net8.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ protected internal DiagnosticsOptions(Microsoft.Extensions.Configuration.IConfig
public int LoggedContentSizeLimit { get { throw null; } set { } }
public System.Collections.Generic.IList<string> LoggedHeaderNames { get { throw null; } }
public System.Collections.Generic.IList<string> LoggedQueryParameters { get { throw null; } }
public int MaxApplicationIdLength { get { throw null; } set { } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct HttpHeader : System.IEquatable<Azure.Core.HttpHeader>
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ protected internal DiagnosticsOptions(Microsoft.Extensions.Configuration.IConfig
public int LoggedContentSizeLimit { get { throw null; } set { } }
public System.Collections.Generic.IList<string> LoggedHeaderNames { get { throw null; } }
public System.Collections.Generic.IList<string> LoggedQueryParameters { get { throw null; } }
public int MaxApplicationIdLength { get { throw null; } set { } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct HttpHeader : System.IEquatable<Azure.Core.HttpHeader>
Expand Down
36 changes: 34 additions & 2 deletions sdk/core/Azure.Core/src/DiagnosticsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ namespace Azure.Core
/// </summary>
public class DiagnosticsOptions
{
private const int MaxApplicationIdLength = 24;

private string? _applicationId;
private int _maxApplicationIdLength = DefaultMaxApplicationIdLength;
private const int DefaultMaxApplicationIdLength = 24;
private const int AbsoluteMaxApplicationIdLength = 300;
Comment thread
m-redding marked this conversation as resolved.
Outdated

/// <summary>
/// Creates a new instance of <see cref="DiagnosticsOptions"/> with default values.
Expand All @@ -37,6 +38,10 @@ protected internal DiagnosticsOptions(IConfigurationSection section)
return;
}

if (int.TryParse(section["MaxApplicationIdLength"], out var maxApplicationIdLength))
{
MaxApplicationIdLength = maxApplicationIdLength;
}
Comment thread
m-redding marked this conversation as resolved.
Outdated
ApplicationId = section["ApplicationId"];
if (bool.TryParse(section["IsLoggingEnabled"], out var isLoggingEnabled))
{
Expand Down Expand Up @@ -102,6 +107,7 @@ internal DiagnosticsOptions(DiagnosticsOptions? diagnosticsOptions)
{
if (diagnosticsOptions != null)
{
_maxApplicationIdLength = diagnosticsOptions.MaxApplicationIdLength;
ApplicationId = diagnosticsOptions.ApplicationId;
IsLoggingEnabled = diagnosticsOptions.IsLoggingEnabled;
IsTelemetryEnabled = diagnosticsOptions.IsTelemetryEnabled;
Expand Down Expand Up @@ -197,6 +203,32 @@ private static IList<string> GetDefaultLoggedHeaders()
/// </summary>
public IList<string> LoggedQueryParameters { get; internal set; }

/// <summary>
/// Gets or sets the maximum allowed length for <see cref="ApplicationId"/>.
/// </summary>
/// <remarks>
/// The default value is 24 characters. This can be increased up to 300 characters
/// to accommodate longer application identifiers. Values less than 24 or greater
/// than 300 are not permitted.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the value is less than 24 or greater than 300.</exception>
public int MaxApplicationIdLength
{
get => _maxApplicationIdLength;
set
{
if (value < DefaultMaxApplicationIdLength || value > AbsoluteMaxApplicationIdLength)
Comment thread
m-redding marked this conversation as resolved.
Outdated
{
throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(MaxApplicationIdLength)} must be between {DefaultMaxApplicationIdLength} and {AbsoluteMaxApplicationIdLength}.");
}
if (_applicationId != null && _applicationId.Length > value)
{
throw new InvalidOperationException($"Cannot set {nameof(MaxApplicationIdLength)} to {value} because the current {nameof(ApplicationId)} has a length of {_applicationId.Length}.");
}
_maxApplicationIdLength = value;
}
}

/// <summary>
/// Gets or sets the value sent as the first part of "User-Agent" headers for all requests issues by this client. Defaults to <see cref="DefaultApplicationId"/>.
/// </summary>
Expand Down
6 changes: 5 additions & 1 deletion sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,11 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd)
internal static TelemetryPolicy CreateTelemetryPolicy(ClientOptions options)
{
var type = options.GetType();
var userAgentValue = new TelemetryDetails(type.Assembly, options.Diagnostics.ApplicationId);
var userAgentValue = new TelemetryDetails(
type.Assembly,
options.Diagnostics.ApplicationId,
runtimeInformation: null,
maxApplicationIdLength: options.Diagnostics.MaxApplicationIdLength);
return new TelemetryPolicy(userAgentValue);
Comment thread
m-redding marked this conversation as resolved.
}
}
Expand Down
10 changes: 5 additions & 5 deletions sdk/core/Azure.Core/src/TelemetryDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Azure.Core
/// </summary>
public class TelemetryDetails
{
private const int MaxApplicationIdLength = 24;
private const int DefaultMaxApplicationIdLength = 24;
private readonly string _userAgent;

/// <summary>
Expand All @@ -35,15 +35,15 @@ public class TelemetryDetails
/// <param name="applicationId">An optional value to be prepended to the <see cref="TelemetryDetails"/>.
/// This value overrides the behavior of the <see cref="DiagnosticsOptions.ApplicationId"/> property for the <see cref="HttpMessage"/> it is applied to.</param>
public TelemetryDetails(Assembly assembly, string? applicationId = null)
: this(assembly, applicationId, new RuntimeInformationWrapper())
: this(assembly, applicationId, new RuntimeInformationWrapper(), maxApplicationIdLength: DefaultMaxApplicationIdLength)
{ }

internal TelemetryDetails(Assembly assembly, string? applicationId = null, RuntimeInformationWrapper? runtimeInformation = default)
internal TelemetryDetails(Assembly assembly, string? applicationId = null, RuntimeInformationWrapper? runtimeInformation = default, int maxApplicationIdLength = DefaultMaxApplicationIdLength)
{
Argument.AssertNotNull(assembly, nameof(assembly));
if (applicationId?.Length > MaxApplicationIdLength)
if (applicationId?.Length > maxApplicationIdLength)
{
throw new ArgumentOutOfRangeException(nameof(applicationId), $"{nameof(applicationId)} must be shorter than {MaxApplicationIdLength + 1} characters");
throw new ArgumentOutOfRangeException(nameof(applicationId), $"{nameof(applicationId)} must be shorter than {maxApplicationIdLength + 1} characters");
}

Assembly = assembly;
Expand Down
67 changes: 67 additions & 0 deletions sdk/core/Azure.Core/tests/ClientOptionsConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,5 +601,72 @@ public TestDiagnosticsOptions() : base()
{
}
}

[Test]
public void ConfigurationCanSetMaxApplicationIdLength()
{
var configData = new Dictionary<string, string>
{
{ "TestClient:Diagnostics:MaxApplicationIdLength", "50" },
{ "TestClient:Diagnostics:ApplicationId", new string('a', 50) }
};

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configData)
.Build();

var options = new TestClientOptions(configuration.GetSection("TestClient"), null);

Assert.AreEqual(50, options.Diagnostics.MaxApplicationIdLength);
Assert.AreEqual(new string('a', 50), options.Diagnostics.ApplicationId);
}

[Test]
public void ConfigurationWithLongApplicationIdButNoMaxLengthThrows()
{
var configData = new Dictionary<string, string>
{
{ "TestClient:Diagnostics:ApplicationId", new string('a', 25) }
};

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configData)
.Build();

Assert.Throws<ArgumentOutOfRangeException>(() => new TestClientOptions(configuration.GetSection("TestClient"), null));
}

[Test]
public void ConfigurationWithInvalidMaxApplicationIdLengthThrows()
{
var configData = new Dictionary<string, string>
{
{ "TestClient:Diagnostics:MaxApplicationIdLength", "10" }
};

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configData)
.Build();

Assert.Throws<ArgumentOutOfRangeException>(() => new TestClientOptions(configuration.GetSection("TestClient"), null));
}

[Test]
public void ConfigurationWithoutMaxApplicationIdLengthDefaultsTo24()
{
var configData = new Dictionary<string, string>
{
{ "TestClient:Diagnostics:ApplicationId", "MyApp" }
};

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configData)
.Build();

var options = new TestClientOptions(configuration.GetSection("TestClient"), null);

Assert.AreEqual(24, options.Diagnostics.MaxApplicationIdLength);
Assert.AreEqual("MyApp", options.Diagnostics.ApplicationId);
}
}
}
124 changes: 124 additions & 0 deletions sdk/core/Azure.Core/tests/ClientOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,130 @@ public void AcceptsCustomDiagnosticsOptions([Values(true, false)] bool useCustom
}
}

[Test]
public void DefaultMaxApplicationIdLengthIs24()
{
var options = new TestClientOptions();
Assert.AreEqual(24, options.Diagnostics.MaxApplicationIdLength);
}
Comment thread
m-redding marked this conversation as resolved.

[Test]
public void ApplicationIdExceedingDefaultMaxLengthThrows()
{
var options = new TestClientOptions();
var longApplicationId = new string('a', 25);

var ex = Assert.Throws<ArgumentOutOfRangeException>(() => options.Diagnostics.ApplicationId = longApplicationId);
Assert.That(ex.Message, Does.Contain("ApplicationId must be shorter than 25 characters"));
}

[Test]
public void ApplicationIdAtDefaultMaxLengthSucceeds()
{
var options = new TestClientOptions();
var applicationId = new string('a', 24);

options.Diagnostics.ApplicationId = applicationId;

Assert.AreEqual(applicationId, options.Diagnostics.ApplicationId);
}

[Test]
public void CanSetCustomMaxApplicationIdLength()
{
var options = new TestClientOptions();
options.Diagnostics.MaxApplicationIdLength = 50;

Assert.AreEqual(50, options.Diagnostics.MaxApplicationIdLength);
}

[Test]
public void CustomMaxApplicationIdLengthAllowsLongerApplicationId()
{
var options = new TestClientOptions();
options.Diagnostics.MaxApplicationIdLength = 50;
var longApplicationId = new string('a', 50);

options.Diagnostics.ApplicationId = longApplicationId;

Assert.AreEqual(longApplicationId, options.Diagnostics.ApplicationId);
}

[Test]
public void CustomMaxApplicationIdLengthStillEnforcesLimit()
{
var options = new TestClientOptions();
options.Diagnostics.MaxApplicationIdLength = 50;
var tooLongApplicationId = new string('a', 51);

var ex = Assert.Throws<ArgumentOutOfRangeException>(() => options.Diagnostics.ApplicationId = tooLongApplicationId);
Assert.That(ex.Message, Does.Contain("ApplicationId must be shorter than 51 characters"));
}

[Test]
public void LoweringMaxApplicationIdLengthBelowCurrentApplicationIdThrows()
{
var options = new TestClientOptions();
options.Diagnostics.MaxApplicationIdLength = 50;
options.Diagnostics.ApplicationId = new string('a', 49);

var ex = Assert.Throws<InvalidOperationException>(() => options.Diagnostics.MaxApplicationIdLength = 34);
Assert.That(ex.Message, Does.Contain("Cannot set MaxApplicationIdLength to 34 because the current ApplicationId has a length of 49"));
}

[TestCase(23)]
[TestCase(0)]
[TestCase(-1)]
public void SettingMaxApplicationIdLengthBelowDefaultThrows(int maxLength)
{
var options = new TestClientOptions();

var ex = Assert.Throws<ArgumentOutOfRangeException>(() => options.Diagnostics.MaxApplicationIdLength = maxLength);
Assert.That(ex.Message, Does.Contain("MaxApplicationIdLength must be between 24 and 300"));
}

[Test]
public void SettingMaxApplicationIdLengthAboveAbsoluteMaxThrows()
{
var options = new TestClientOptions();

var ex = Assert.Throws<ArgumentOutOfRangeException>(() => options.Diagnostics.MaxApplicationIdLength = 301);
Assert.That(ex.Message, Does.Contain("MaxApplicationIdLength must be between 24 and 300"));
}

[Test]
public void SettingMaxApplicationIdLengthToAbsoluteMaxSucceeds()
{
var options = new TestClientOptions();
options.Diagnostics.MaxApplicationIdLength = 300;

Assert.AreEqual(300, options.Diagnostics.MaxApplicationIdLength);
}

[TestCase(null)]
[TestCase("")]
public void NullOrEmptyApplicationIdIsAccepted(string applicationId)
{
var options = new TestClientOptions();

options.Diagnostics.ApplicationId = applicationId;

Assert.AreEqual(applicationId, options.Diagnostics.ApplicationId);
}

[Test]
public void MaxApplicationIdLengthIsPreservedThroughCopyConstructor()
{
var original = new TestClientOptions();
original.Diagnostics.MaxApplicationIdLength = 50;
var longApplicationId = new string('a', 50);
original.Diagnostics.ApplicationId = longApplicationId;

var copy = new TestClientOptions();
// The copy should get the default (24) since it's a new instance, not copied from original
Assert.AreEqual(24, copy.Diagnostics.MaxApplicationIdLength);
}

private class TestClientOptions : ClientOptions
{
}
Expand Down
17 changes: 17 additions & 0 deletions sdk/core/Azure.Core/tests/HttpPipelineBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@ public async Task VersionDoesntHaveCommitHash()
Regex.Escape($" ({RuntimeInformation.FrameworkDescription}; {RuntimeInformation.OSDescription})"), userAgent);
}

[Test]
public async Task CreateTelemetryPolicyHonorsCustomMaxApplicationIdLength()
{
var longApplicationId = new string('a', 50);
var options = new TestOptions();
options.Diagnostics.MaxApplicationIdLength = 50;
options.Diagnostics.ApplicationId = longApplicationId;

var transport = new MockTransport(new MockResponse(200));
var telemetryPolicy = HttpPipelineBuilder.CreateTelemetryPolicy(options);

await SendGetRequest(transport, telemetryPolicy);

Assert.True(transport.SingleRequest.TryGetHeader("User-Agent", out var userAgent));
StringAssert.StartsWith(longApplicationId + " ", userAgent);
}

[Test]
public async Task CustomClientRequestIdAvailableInPerCallPolicies()
{
Expand Down
Loading
Loading