Skip to content
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
21 changes: 21 additions & 0 deletions src/libraries/Common/src/System/ExceptionPolyfills.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ private static void ThrowArgumentNullException(string? paramName) =>
private static void ThrowArgumentOutOfRangeException(string? paramName) =>
throw new ArgumentOutOfRangeException(paramName);

extension(ArgumentException)
{
public static void ThrowIfNullOrEmpty([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
{
if (string.IsNullOrEmpty(argument))
{
ThrowNullOrEmptyException(argument, paramName);
}
}
}

[DoesNotReturn]
private static void ThrowNullOrEmptyException(string? argument, string? paramName)
{
if (argument is null)
{
ThrowArgumentNullException(paramName);
}
throw new ArgumentException("The value cannot be an empty string.", paramName);
}

extension(ObjectDisposedException)
{
public static void ThrowIf([DoesNotReturnIf(true)] bool condition, object instance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,52 @@ public class MetricsOptions
public IList<InstrumentRule> Rules { get; } = null!;
}
}
namespace Microsoft.Extensions.Diagnostics.Tracing
{
public interface ITracingBuilder
{
Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; }
}
public class TracingRule
{
public TracingRule(string? sourceName, string? operationName, string? listenerName, ActivitySourceScopes scopes, bool enable) { }
public string? SourceName { get; }
public string? OperationName { get; }
public string? ListenerName { get; }
public ActivitySourceScopes Scopes { get; }
public bool Enable { get; }
}
[Flags]
public enum ActivitySourceScopes
{
None = 0,
Global = 1,
Local = 2
}
public sealed class ActivityListenerBuilder
{
internal ActivityListenerBuilder(string name) { }
public string Name { get; }
public System.Diagnostics.SampleActivity<System.Diagnostics.ActivityContext>? Sample { get; set; }
public System.Diagnostics.SampleActivity<string>? SampleUsingParentId { get; set; }
public Action<System.Diagnostics.Activity>? ActivityStarted { get; set; }
public Action<System.Diagnostics.Activity>? ActivityStopped { get; set; }
public System.Diagnostics.ExceptionRecorder? ExceptionRecorder { get; set; }
}
public static partial class TracingBuilderExtensions
{
public static ITracingBuilder AddListener(this ITracingBuilder builder, string name, Action<ActivityListenerBuilder> configure) { throw null!; }
public static ITracingBuilder AddListener(this ITracingBuilder builder, string name, Action<IServiceProvider, ActivityListenerBuilder> configure) { throw null!; }
public static ITracingBuilder ClearListeners(this ITracingBuilder builder) { throw null!; }

public static ITracingBuilder EnableTracing(this ITracingBuilder builder, string? sourceName = null, string? operationName = null, string? listenerName = null, ActivitySourceScopes scopes = ActivitySourceScopes.Global | ActivitySourceScopes.Local) => throw null!;
public static TracingOptions EnableTracing(this TracingOptions options, string? sourceName = null, string? operationName = null, string? listenerName = null, ActivitySourceScopes scopes = ActivitySourceScopes.Global | ActivitySourceScopes.Local) => throw null!;

public static ITracingBuilder DisableTracing(this ITracingBuilder builder, string? sourceName = null, string? operationName = null, string? listenerName = null, ActivitySourceScopes scopes = ActivitySourceScopes.Global | ActivitySourceScopes.Local) => throw null!;
public static TracingOptions DisableTracing(this TracingOptions options, string? sourceName = null, string? operationName = null, string? listenerName = null, ActivitySourceScopes scopes = ActivitySourceScopes.Global | ActivitySourceScopes.Local) => throw null!;
}
public class TracingOptions
{
public List<TracingRule> Rules { get; } = null!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
<ProjectReference Include="$(LibrariesProjectRoot)System.Collections\ref\System.Collections.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.ComponentModel\ref\System.ComponentModel.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\ref\System.Runtime.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Diagnostics.DiagnosticSource\ref\System.Diagnostics.DiagnosticSource.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;

namespace Microsoft.Extensions.Diagnostics.Tracing
{
/// <summary>
/// Describes the user-configurable surface of an <see cref="ActivityListener"/> registered with an
/// <see cref="ITracingBuilder"/>. The tracing infrastructure consumes the values set on the builder and
/// constructs the underlying <see cref="ActivityListener"/>; subscription to <see cref="ActivitySource"/>
/// instances is controlled entirely by the configuration-driven <see cref="TracingRule"/> set.
/// </summary>
/// <remarks>
/// The builder intentionally does not expose <c>ShouldListenTo</c>, <see cref="ActivityListener.RefreshSources"/>
/// or <see cref="IDisposable.Dispose"/>: subscription filtering is owned by the rule set, and the lifetime of the
/// underlying listener is owned by the tracing builder. The delegate properties are snapshotted by the
/// infrastructure at registration time; mutating the builder afterwards has no effect on the registered listener.
/// Instances are constructed by the tracing infrastructure when an <c>AddListener</c> overload runs; user code
/// configures an instance through the <c>configure</c> callback passed to <c>AddListener</c>.
/// </remarks>
public sealed class ActivityListenerBuilder
{
internal ActivityListenerBuilder(string name)
{
Debug.Assert(name is not null);
Name = name;
}

/// <summary>
/// Gets the name used by configuration-based filtering to target rules at this listener.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets or sets the callback invoked when an <see cref="Activity"/> is sampled from an <see cref="ActivityContext"/>.
/// </summary>
public SampleActivity<ActivityContext>? Sample { get; set; }

/// <summary>
/// Gets or sets the callback invoked when an <see cref="Activity"/> is sampled from a parent identifier string.
/// </summary>
public SampleActivity<string>? SampleUsingParentId { get; set; }

/// <summary>
/// Gets or sets the callback invoked when a sampled <see cref="Activity"/> starts.
/// </summary>
public Action<Activity>? ActivityStarted { get; set; }

/// <summary>
/// Gets or sets the callback invoked when a sampled <see cref="Activity"/> stops.
/// </summary>
public Action<Activity>? ActivityStopped { get; set; }

/// <summary>
/// Gets or sets the callback invoked when an exception is recorded on a sampled <see cref="Activity"/>.
/// </summary>
public ExceptionRecorder? ExceptionRecorder { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Extensions.Diagnostics.Tracing
{
/// <summary>
/// Represents scopes used by <see cref="TracingRule"/> to distinguish between activity sources created directly
/// via <see cref="System.Diagnostics.ActivitySource"/> constructors (<see cref="Global"/>) and those created via
/// dependency injection with <see cref="System.Diagnostics.ActivitySourceFactory.Create(System.Diagnostics.ActivitySourceOptions)"/> (<see cref="Local"/>).
/// </summary>
[Flags]
public enum ActivitySourceScopes
{
/// <summary>
/// No scope is specified. This field should not be used.
/// </summary>
None = 0,

/// <summary>
/// Indicates <see cref="System.Diagnostics.ActivitySource"/> instances created via constructors.
/// </summary>
Global = 1,

/// <summary>
/// Indicates <see cref="System.Diagnostics.ActivitySource"/> instances created via <see cref="System.Diagnostics.ActivitySourceFactory"/>.
/// </summary>
Local = 2
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Diagnostics.Tracing
{
/// <summary>
/// Configures the tracing system by registering <see cref="System.Diagnostics.ActivityListener"/> instances and using
/// rules to determine which <see cref="System.Diagnostics.ActivitySource"/> and <see cref="System.Diagnostics.Activity"/>
/// instances are enabled.
/// </summary>
public interface ITracingBuilder
{
/// <summary>
/// Gets the application service collection that's used by extension methods to register services.
/// </summary>
IServiceCollection Services { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.Diagnostics.Tracing
{
/// <summary>
/// Extension methods for <see cref="ITracingBuilder"/> to add or clear <see cref="ActivityListener"/> registrations.
/// </summary>
public static partial class TracingBuilderExtensions
{
/// <summary>
/// Registers a new <see cref="ActivityListener"/> identified by <paramref name="name"/> and described by
/// the supplied <paramref name="configure"/> callback.
/// </summary>
/// <param name="builder">The <see cref="ITracingBuilder"/>.</param>
/// <param name="name">A name used by configuration-based filtering to identify this listener for rule matching.</param>
/// <param name="configure">A callback that configures the delegate properties of the supplied <see cref="ActivityListenerBuilder"/>.</param>
/// <returns>Returns the original <see cref="ITracingBuilder"/> for chaining.</returns>
/// <remarks>
/// The tracing infrastructure invokes <paramref name="configure"/> once when the underlying
/// <see cref="ActivitySourceFactory"/> is first resolved, snapshots the delegate properties from the supplied
/// <see cref="ActivityListenerBuilder"/>, and constructs the registered <see cref="ActivityListener"/> itself.
/// Subscription to <see cref="ActivitySource"/> instances is driven entirely by the configuration-based
/// <see cref="TracingRule"/> set; the builder re-evaluates listener subscriptions automatically when the bound
/// <see cref="TracingOptions"/> change.
/// </remarks>
public static ITracingBuilder AddListener(this ITracingBuilder builder, string name, Action<ActivityListenerBuilder> configure)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);
ArgumentNullException.ThrowIfNull(configure);

builder.Services.AddSingleton(_ =>
{
ActivityListenerBuilder listenerBuilder = new ActivityListenerBuilder(name);
configure(listenerBuilder);
return listenerBuilder;
});
return builder;
}

/// <summary>
/// Registers a new <see cref="ActivityListener"/> identified by <paramref name="name"/> and described by
/// the supplied <paramref name="configure"/> callback, which also receives the resolved <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="builder">The <see cref="ITracingBuilder"/>.</param>
/// <param name="name">A name used by configuration-based filtering to identify this listener for rule matching.</param>
/// <param name="configure">A callback that configures the supplied <see cref="ActivityListenerBuilder"/>, with access to the resolved <see cref="IServiceProvider"/>.</param>
/// <returns>Returns the original <see cref="ITracingBuilder"/> for chaining.</returns>
/// <remarks>
/// The tracing infrastructure invokes <paramref name="configure"/> once when the underlying
/// <see cref="ActivitySourceFactory"/> is first resolved, snapshots the delegate properties from the supplied
/// <see cref="ActivityListenerBuilder"/>, and constructs the registered <see cref="ActivityListener"/> itself.
/// Subscription to <see cref="ActivitySource"/> instances is driven entirely by the configuration-based
/// <see cref="TracingRule"/> set; the builder re-evaluates listener subscriptions automatically when the bound
/// <see cref="TracingOptions"/> change.
/// </remarks>
public static ITracingBuilder AddListener(this ITracingBuilder builder, string name, Action<IServiceProvider, ActivityListenerBuilder> configure)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);
ArgumentNullException.ThrowIfNull(configure);

builder.Services.AddSingleton(serviceProvider =>
{
ActivityListenerBuilder listenerBuilder = new ActivityListenerBuilder(name);
configure(serviceProvider, listenerBuilder);
return listenerBuilder;
});
return builder;
}

/// <summary>
/// Removes all <see cref="ActivityListenerBuilder"/> registrations from the dependency injection container.
/// </summary>
/// <param name="builder">The <see cref="ITracingBuilder"/>.</param>
/// <returns>Returns the original <see cref="ITracingBuilder"/> for chaining.</returns>
public static ITracingBuilder ClearListeners(this ITracingBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);
builder.Services.RemoveAll<ActivityListenerBuilder>();
return builder;
}
}
}
Loading
Loading