diff --git a/examples/AspNetCore/Startup.cs b/examples/AspNetCore/Startup.cs
index 625c7c16615..416f02de043 100644
--- a/examples/AspNetCore/Startup.cs
+++ b/examples/AspNetCore/Startup.cs
@@ -23,8 +23,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
+using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Instrumentation.AspNetCore;
+using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
@@ -32,6 +34,8 @@ namespace Examples.AspNetCore
{
public class Startup
{
+ private MeterProvider meterProvider;
+
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
@@ -111,6 +115,16 @@ public void ConfigureServices(IServiceCollection services)
break;
}
+
+ // TODO: Add IServiceCollection.AddOpenTelemetryMetrics extension method
+ var providerBuilder = Sdk.CreateMeterProviderBuilder()
+ .AddAspNetCoreInstrumentation();
+
+ // TODO: Add configuration switch for Prometheus and OTLP export
+ providerBuilder
+ .AddConsoleExporter();
+
+ this.meterProvider = providerBuilder.Build();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
diff --git a/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs b/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs
index d47be1c1ff9..f5bb6dc9ff2 100644
--- a/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs
+++ b/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs
@@ -29,6 +29,16 @@ protected MeterProviderBuilder()
{
}
+ ///
+ /// Adds instrumentation to the provider.
+ ///
+ /// Type of instrumentation class.
+ /// Function that builds instrumentation.
+ /// Returns for chaining.
+ public abstract MeterProviderBuilder AddInstrumentation(
+ Func instrumentationFactory)
+ where TInstrumentation : class;
+
///
/// Adds given Meter names to the list of subscribed sources.
///
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs
new file mode 100644
index 00000000000..7f4c2292091
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs
@@ -0,0 +1,53 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Diagnostics.Metrics;
+using System.Reflection;
+using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
+
+namespace OpenTelemetry.Instrumentation.AspNetCore
+{
+ ///
+ /// Asp.Net Core Requests instrumentation.
+ ///
+ internal class AspNetCoreMetrics : IDisposable
+ {
+ internal static readonly AssemblyName AssemblyName = typeof(HttpInListener).Assembly.GetName();
+ internal static readonly string InstrumentationName = AssemblyName.Name;
+ internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString();
+
+ private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
+ private readonly Meter meter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AspNetCoreMetrics()
+ {
+ this.meter = new Meter(InstrumentationName, InstrumentationVersion);
+ this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpInMetricsListener("Microsoft.AspNetCore", this.meter), null);
+ this.diagnosticSourceSubscriber.Subscribe();
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.diagnosticSourceSubscriber?.Dispose();
+ this.meter?.Dispose();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs
new file mode 100644
index 00000000000..3856299dd2a
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs
@@ -0,0 +1,78 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using Microsoft.AspNetCore.Http;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
+{
+ internal class HttpInMetricsListener : ListenerHandler
+ {
+ private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext");
+ private readonly Meter meter;
+
+ private Counter httpServerRequestCount;
+
+ public HttpInMetricsListener(string name, Meter meter)
+ : base(name)
+ {
+ this.meter = meter;
+
+ // TODO:
+ // In the future, this instrumentation should produce the http.server.duration metric which will likely be represented as a histogram.
+ // See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server
+ //
+ // Histograms are not yet supported by the SDK.
+ //
+ // For now we produce a count metric called http.server.request_count just for demonstration purposes.
+ // This metric is not defined by the in the semantic conventions.
+ this.httpServerRequestCount = meter.CreateCounter("http.server.request_count", null, "The number of HTTP requests processed.");
+ }
+
+ public override void OnStopActivity(Activity activity, object payload)
+ {
+ HttpContext context = this.stopContextFetcher.Fetch(payload);
+ if (context == null)
+ {
+ AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), nameof(this.OnStopActivity));
+ return;
+ }
+
+ // TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
+ // Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
+ // If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
+ if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
+ {
+ return;
+ }
+
+ // TODO: This is just a minimal set of attributes. See the spec for additional attributes:
+ // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server
+ var tags = new KeyValuePair[]
+ {
+ new KeyValuePair(SemanticConventions.AttributeHttpMethod, context.Request.Method),
+ new KeyValuePair(SemanticConventions.AttributeHttpScheme, context.Request.Scheme),
+ new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, context.Response.StatusCode),
+ new KeyValuePair(SemanticConventions.AttributeHttpFlavor, context.Request.Protocol),
+ };
+
+ this.httpServerRequestCount.Add(1, tags);
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs
new file mode 100644
index 00000000000..8f32fea696b
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs
@@ -0,0 +1,53 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using OpenTelemetry.Instrumentation.AspNetCore;
+
+namespace OpenTelemetry.Metrics
+{
+ ///
+ /// Extension methods to simplify registering of ASP.NET Core request instrumentation.
+ ///
+ public static class MeterProviderBuilderExtensions
+ {
+ ///
+ /// Enables the incoming requests automatic data collection for ASP.NET Core.
+ ///
+ /// being configured.
+ /// The instance of to chain the calls.
+ public static MeterProviderBuilder AddAspNetCoreInstrumentation(
+ this MeterProviderBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ // TODO: Implement an IDeferredMeterProviderBuilder
+
+ // TODO: Handle AspNetCoreInstrumentationOptions
+ // Filter - makes sense for metric instrumentation
+ // Enrich - do we want a similar kind of functionality for metrics?
+ // RecordException - probably doesn't make sense for metric instrumentation
+ // EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests
+
+ var instrumentation = new AspNetCoreMetrics();
+ builder.AddSource(AspNetCoreMetrics.InstrumentationName);
+ return builder.AddInstrumentation(() => instrumentation);
+ }
+ }
+}
diff --git a/src/OpenTelemetry/Metrics/MeterProviderBuilderSdk.cs b/src/OpenTelemetry/Metrics/MeterProviderBuilderSdk.cs
index d6725972101..eb35983fed3 100644
--- a/src/OpenTelemetry/Metrics/MeterProviderBuilderSdk.cs
+++ b/src/OpenTelemetry/Metrics/MeterProviderBuilderSdk.cs
@@ -22,6 +22,7 @@ namespace OpenTelemetry.Metrics
{
internal class MeterProviderBuilderSdk : MeterProviderBuilder
{
+ private readonly List instrumentationFactories = new List();
private readonly List meterSources = new List();
private ResourceBuilder resourceBuilder = ResourceBuilder.CreateDefault();
@@ -33,6 +34,22 @@ internal MeterProviderBuilderSdk()
internal List MetricProcessors { get; } = new List();
+ public override MeterProviderBuilder AddInstrumentation(Func instrumentationFactory)
+ {
+ if (instrumentationFactory == null)
+ {
+ throw new ArgumentNullException(nameof(instrumentationFactory));
+ }
+
+ this.instrumentationFactories.Add(
+ new InstrumentationFactory(
+ typeof(TInstrumentation).Name,
+ "semver:" + typeof(TInstrumentation).Assembly.GetName().Version,
+ instrumentationFactory));
+
+ return this;
+ }
+
public override MeterProviderBuilder AddSource(params string[] names)
{
if (names == null)
@@ -76,8 +93,24 @@ internal MeterProvider Build()
return new MeterProviderSdk(
this.resourceBuilder.Build(),
this.meterSources,
+ this.instrumentationFactories,
this.MeasurementProcessors.ToArray(),
this.MetricProcessors.ToArray());
}
+
+ // TODO: This is copied from TracerProviderBuilderSdk. Move to common location.
+ internal readonly struct InstrumentationFactory
+ {
+ public readonly string Name;
+ public readonly string Version;
+ public readonly Func