diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj
index e067ca3eb..646c8b0d9 100644
--- a/examples/AspNetCore/Examples.AspNetCore.csproj
+++ b/examples/AspNetCore/Examples.AspNetCore.csproj
@@ -12,13 +12,14 @@
+
-
-
-
+
+
+
diff --git a/examples/AspNetCore/Startup.cs b/examples/AspNetCore/Startup.cs
index 6d43c6622..d9f4aa733 100644
--- a/examples/AspNetCore/Startup.cs
+++ b/examples/AspNetCore/Startup.cs
@@ -23,7 +23,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
-using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Metrics;
@@ -34,8 +33,6 @@ namespace Examples.AspNetCore
{
public class Startup
{
- private MeterProvider meterProvider;
-
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
@@ -59,9 +56,9 @@ namespace Examples.AspNetCore
}
});
- // Switch between Zipkin/Jaeger by setting UseExporter in appsettings.json.
- var exporter = this.Configuration.GetValue("UseExporter").ToLowerInvariant();
- switch (exporter)
+ // Switch between Zipkin/Jaeger/OTLP by setting UseExporter in appsettings.json.
+ var tracingExporter = this.Configuration.GetValue("UseTracingExporter").ToLowerInvariant();
+ switch (tracingExporter)
{
case "jaeger":
services.AddOpenTelemetryTracing((builder) => builder
@@ -117,15 +114,24 @@ namespace Examples.AspNetCore
break;
}
- // TODO: Add IServiceCollection.AddOpenTelemetryMetrics extension method
- var providerBuilder = Sdk.CreateMeterProviderBuilder()
- .AddAspNetCoreInstrumentation();
+ var metricsExporter = this.Configuration.GetValue("UseMetricsExporter").ToLowerInvariant();
+ services.AddOpenTelemetryMetrics(builder =>
+ {
+ builder.AddAspNetCoreInstrumentation();
- // TODO: Add configuration switch for Prometheus and OTLP export
- providerBuilder
- .AddConsoleExporter();
-
- this.meterProvider = providerBuilder.Build();
+ switch (metricsExporter)
+ {
+ case "prometheus":
+ builder.AddPrometheusExporter();
+ break;
+ case "otlp":
+ builder.AddOtlpExporter();
+ break;
+ default:
+ builder.AddConsoleExporter();
+ break;
+ }
+ });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
@@ -146,6 +152,12 @@ namespace Examples.AspNetCore
app.UseRouting();
+ var metricsExporter = this.Configuration.GetValue("UseMetricsExporter").ToLowerInvariant();
+ if (metricsExporter == "prometheus")
+ {
+ app.UseOpenTelemetryPrometheusScrapingEndpoint();
+ }
+
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json
index edd66d9db..bab9c301d 100644
--- a/examples/AspNetCore/appsettings.json
+++ b/examples/AspNetCore/appsettings.json
@@ -7,7 +7,8 @@
}
},
"AllowedHosts": "*",
- "UseExporter": "console",
+ "UseTracingExporter": "console",
+ "UseMetricsExporter": "console",
"UseLogging": true,
"Jaeger": {
"ServiceName": "jaeger-test",
diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs
index 3e717f250..a1f66fd0a 100644
--- a/examples/Console/TestPrometheusExporter.cs
+++ b/examples/Console/TestPrometheusExporter.cs
@@ -49,7 +49,11 @@ namespace Examples.Console
*/
using var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddSource("TestMeter")
- .AddPrometheusExporter(opt => opt.Url = $"http://localhost:{port}/metrics/")
+ .AddPrometheusExporter(opt =>
+ {
+ opt.StartHttpListener = true;
+ opt.HttpListenerPrefixes = new string[] { $"http://*:{port}/" };
+ })
.Build();
ObservableGauge gauge = MyMeter.CreateObservableGauge(
diff --git a/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs
new file mode 100644
index 000000000..36f813dca
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+//
+// 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.Runtime.CompilerServices;
+
+#if SIGNED
+[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")]
+#else
+[assembly: InternalsVisibleTo("Benchmarks")]
+#endif
diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs
index cd577f4f5..0523d120e 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs
@@ -18,13 +18,13 @@ using System;
using System.Diagnostics.Tracing;
using OpenTelemetry.Internal;
-namespace OpenTelemetry.Exporter.Prometheus.Implementation
+namespace OpenTelemetry.Exporter.Prometheus
{
///
/// EventSource events emitted from the project.
///
[EventSource(Name = "OpenTelemetry-Exporter-Prometheus")]
- internal class PrometheusExporterEventSource : EventSource
+ internal sealed class PrometheusExporterEventSource : EventSource
{
public static PrometheusExporterEventSource Log = new PrometheusExporterEventSource();
diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs
new file mode 100644
index 000000000..88e504fa3
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs
@@ -0,0 +1,194 @@
+//
+// 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.Globalization;
+using System.IO;
+using System.Threading.Tasks;
+using OpenTelemetry.Metrics;
+using static OpenTelemetry.Exporter.Prometheus.PrometheusMetricBuilder;
+
+namespace OpenTelemetry.Exporter.Prometheus
+{
+ ///
+ /// Helper to write metrics collection from exporter in Prometheus format.
+ ///
+ internal static class PrometheusExporterExtensions
+ {
+ private const string PrometheusCounterType = "counter";
+ private const string PrometheusGaugeType = "gauge";
+ private const string PrometheusHistogramType = "histogram";
+ private const string PrometheusHistogramSumPostFix = "_sum";
+ private const string PrometheusHistogramCountPostFix = "_count";
+ private const string PrometheusHistogramBucketPostFix = "_bucket";
+ private const string PrometheusHistogramBucketLabelPositiveInfinity = "+Inf";
+ private const string PrometheusHistogramBucketLabelLessThan = "le";
+
+ ///
+ /// Serialize metrics to prometheus format.
+ ///
+ /// .
+ /// StreamWriter to write to.
+ /// to await the operation.
+ public static async Task WriteMetricsCollection(this PrometheusExporter exporter, StreamWriter writer)
+ {
+ foreach (var metric in exporter.Metrics)
+ {
+ var builder = new PrometheusMetricBuilder()
+ .WithName(metric.Name)
+ .WithDescription(metric.Description);
+
+ switch (metric.MetricType)
+ {
+ case MetricType.LongSum:
+ {
+ builder = builder.WithType(PrometheusCounterType);
+ WriteLongSumMetrics(metric, builder);
+ break;
+ }
+
+ case MetricType.DoubleSum:
+ {
+ builder = builder.WithType(PrometheusCounterType);
+ WriteDoubleSumMetrics(metric, builder);
+ break;
+ }
+
+ case MetricType.LongGauge:
+ {
+ builder = builder.WithType(PrometheusGaugeType);
+ WriteLongGaugeMetrics(metric, builder);
+ break;
+ }
+
+ case MetricType.DoubleGauge:
+ {
+ builder = builder.WithType(PrometheusGaugeType);
+ WriteDoubleGaugeMetrics(metric, builder);
+ break;
+ }
+
+ case MetricType.Histogram:
+ {
+ builder = builder.WithType(PrometheusHistogramType);
+ WriteHistogramMetrics(metric, builder);
+ break;
+ }
+ }
+
+ await builder.Write(writer).ConfigureAwait(false);
+ }
+ }
+
+ private static void WriteLongSumMetrics(Metric metric, PrometheusMetricBuilder builder)
+ {
+ foreach (ref var metricPoint in metric.GetMetricPoints())
+ {
+ var metricValueBuilder = builder.AddValue();
+ metricValueBuilder = metricValueBuilder.WithValue(metricPoint.LongValue);
+ metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
+ }
+ }
+
+ private static void WriteDoubleSumMetrics(Metric metric, PrometheusMetricBuilder builder)
+ {
+ foreach (ref var metricPoint in metric.GetMetricPoints())
+ {
+ var metricValueBuilder = builder.AddValue();
+ metricValueBuilder = metricValueBuilder.WithValue(metricPoint.DoubleValue);
+ metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
+ }
+ }
+
+ private static void WriteLongGaugeMetrics(Metric metric, PrometheusMetricBuilder builder)
+ {
+ foreach (ref var metricPoint in metric.GetMetricPoints())
+ {
+ var metricValueBuilder = builder.AddValue();
+ metricValueBuilder = metricValueBuilder.WithValue(metricPoint.LongValue);
+ metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
+ }
+ }
+
+ private static void WriteDoubleGaugeMetrics(Metric metric, PrometheusMetricBuilder builder)
+ {
+ foreach (ref var metricPoint in metric.GetMetricPoints())
+ {
+ var metricValueBuilder = builder.AddValue();
+ metricValueBuilder = metricValueBuilder.WithValue(metricPoint.DoubleValue);
+ metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
+ }
+ }
+
+ private static void WriteHistogramMetrics(Metric metric, PrometheusMetricBuilder builder)
+ {
+ /*
+ * For Histogram we emit one row for Sum, Count and as
+ * many rows as number of buckets.
+ * myHistogram_sum{tag1="value1",tag2="value2"} 258330 1629860660991
+ * myHistogram_count{tag1="value1",tag2="value2"} 355 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="0"} 0 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="5"} 2 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="10"} 4 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="25"} 6 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="50"} 12 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="75"} 19 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="100"} 26 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="250"} 65 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="500"} 128 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="1000"} 241 1629860660991
+ * myHistogram_bucket{tag1="value1",tag2="value2",le="+Inf"} 355 1629860660991
+ */
+
+ foreach (ref var metricPoint in metric.GetMetricPoints())
+ {
+ var metricValueBuilderSum = builder.AddValue();
+ metricValueBuilderSum.WithName(metric.Name + PrometheusHistogramSumPostFix);
+ metricValueBuilderSum = metricValueBuilderSum.WithValue(metricPoint.DoubleValue);
+ metricValueBuilderSum.AddLabels(metricPoint.Keys, metricPoint.Values);
+
+ var metricValueBuilderCount = builder.AddValue();
+ metricValueBuilderCount.WithName(metric.Name + PrometheusHistogramCountPostFix);
+ metricValueBuilderCount = metricValueBuilderCount.WithValue(metricPoint.LongValue);
+ metricValueBuilderCount.AddLabels(metricPoint.Keys, metricPoint.Values);
+
+ long totalCount = 0;
+ for (int i = 0; i < metricPoint.ExplicitBounds.Length + 1; i++)
+ {
+ totalCount += metricPoint.BucketCounts[i];
+ var metricValueBuilderBuckets = builder.AddValue();
+ metricValueBuilderBuckets.WithName(metric.Name + PrometheusHistogramBucketPostFix);
+ metricValueBuilderBuckets = metricValueBuilderBuckets.WithValue(totalCount);
+ metricValueBuilderBuckets.AddLabels(metricPoint.Keys, metricPoint.Values);
+
+ var bucketName = i == metricPoint.ExplicitBounds.Length ?
+ PrometheusHistogramBucketLabelPositiveInfinity : metricPoint.ExplicitBounds[i].ToString(CultureInfo.InvariantCulture);
+ metricValueBuilderBuckets.WithLabel(PrometheusHistogramBucketLabelLessThan, bucketName);
+ }
+ }
+ }
+
+ private static void AddLabels(this PrometheusMetricValueBuilder valueBuilder, string[] keys, object[] values)
+ {
+ if (keys != null)
+ {
+ for (int i = 0; i < keys.Length; i++)
+ {
+ valueBuilder.WithLabel(keys[i], values[i].ToString());
+ }
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMetricsHttpServer.cs
similarity index 73%
rename from src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs
rename to src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMetricsHttpServer.cs
index 330764363..909f1ff2e 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMetricsHttpServer.cs
@@ -19,14 +19,13 @@ using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
-using OpenTelemetry.Exporter.Prometheus.Implementation;
-namespace OpenTelemetry.Exporter
+namespace OpenTelemetry.Exporter.Prometheus
{
///
/// A HTTP listener used to expose Prometheus metrics.
///
- public class PrometheusExporterMetricsHttpServer : IDisposable
+ internal sealed class PrometheusExporterMetricsHttpServer : IDisposable
{
private readonly PrometheusExporter exporter;
private readonly HttpListener httpListener = new HttpListener();
@@ -42,7 +41,22 @@ namespace OpenTelemetry.Exporter
public PrometheusExporterMetricsHttpServer(PrometheusExporter exporter)
{
this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
- this.httpListener.Prefixes.Add(exporter.Options.Url);
+
+ if ((exporter.Options.HttpListenerPrefixes?.Count ?? 0) <= 0)
+ {
+ throw new ArgumentException("No HttpListenerPrefixes were specified on PrometheusExporterOptions.");
+ }
+
+ string path = exporter.Options.ScrapeEndpointPath ?? PrometheusExporterOptions.DefaultScrapeEndpointPath;
+ if (!path.StartsWith("/"))
+ {
+ path = $"/{path}";
+ }
+
+ foreach (string prefix in exporter.Options.HttpListenerPrefixes)
+ {
+ this.httpListener.Prefixes.Add($"{prefix.TrimEnd('/')}{path}");
+ }
}
///
@@ -87,16 +101,6 @@ namespace OpenTelemetry.Exporter
///
public void Dispose()
- {
- this.Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Releases the unmanaged resources used by this class and optionally releases the managed resources.
- ///
- /// to release both managed and unmanaged resources; to release only unmanaged resources.
- protected virtual void Dispose(bool disposing)
{
if (this.httpListener != null && this.httpListener.IsListening)
{
@@ -116,26 +120,21 @@ namespace OpenTelemetry.Exporter
{
var ctxTask = this.httpListener.GetContextAsync();
ctxTask.Wait(this.tokenSource.Token);
-
var ctx = ctxTask.Result;
- ctx.Response.StatusCode = 200;
- ctx.Response.ContentType = PrometheusMetricBuilder.ContentType;
+ if (!this.exporter.TryEnterSemaphore())
+ {
+ ctx.Response.StatusCode = 429;
+ ctx.Response.Close();
+ }
- using var output = ctx.Response.OutputStream;
- using var writer = new StreamWriter(output);
- this.exporter.Collect(Timeout.Infinite);
- this.exporter.WriteMetricsCollection(writer);
+ Task.Run(() => this.ProcessExportRequest(ctx));
}
}
catch (OperationCanceledException ex)
{
PrometheusExporterEventSource.Log.CanceledExport(ex);
}
- catch (Exception ex)
- {
- PrometheusExporterEventSource.Log.FailedExport(ex);
- }
finally
{
try
@@ -149,5 +148,31 @@ namespace OpenTelemetry.Exporter
}
}
}
+
+ private async Task ProcessExportRequest(HttpListenerContext context)
+ {
+ try
+ {
+ using var writer = new StreamWriter(context.Response.OutputStream);
+ try
+ {
+ this.exporter.Collect(Timeout.Infinite);
+
+ await this.exporter.WriteMetricsCollection(writer).ConfigureAwait(false);
+ }
+ finally
+ {
+ await writer.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ PrometheusExporterEventSource.Log.FailedExport(ex);
+ }
+ finally
+ {
+ this.exporter.ReleaseSemaphore();
+ }
+ }
}
}
diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMiddleware.cs
new file mode 100644
index 000000000..f8a584be4
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMiddleware.cs
@@ -0,0 +1,112 @@
+//
+// 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.
+//
+
+#if NETCOREAPP3_1_OR_GREATER
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using OpenTelemetry.Metrics;
+
+namespace OpenTelemetry.Exporter.Prometheus
+{
+ ///
+ /// ASP.NET Core middleware for exposing a Prometheus metrics scraping endpoint.
+ ///
+ internal sealed class PrometheusExporterMiddleware
+ {
+ private readonly PrometheusExporter exporter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// .
+ /// .
+ public PrometheusExporterMiddleware(MeterProvider meterProvider, RequestDelegate next)
+ {
+ if (meterProvider == null)
+ {
+ throw new ArgumentNullException(nameof(meterProvider));
+ }
+
+ if (!meterProvider.TryFindExporter(out PrometheusExporter exporter))
+ {
+ throw new ArgumentException("A PrometheusExporter could not be found configured on the provided MeterProvider.");
+ }
+
+ this.exporter = exporter;
+ }
+
+ ///
+ /// Invoke.
+ ///
+ /// context.
+ /// Task.
+ public async Task InvokeAsync(HttpContext httpContext)
+ {
+ Debug.Assert(httpContext != null, "httpContext should not be null");
+
+ var response = httpContext.Response;
+
+ if (!this.exporter.TryEnterSemaphore())
+ {
+ response.StatusCode = 429;
+ return;
+ }
+
+ try
+ {
+ this.exporter.Collect(Timeout.Infinite);
+
+ await WriteMetricsToResponse(this.exporter, response).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ if (!response.HasStarted)
+ {
+ response.StatusCode = 500;
+ }
+
+ PrometheusExporterEventSource.Log.FailedExport(ex);
+ }
+ finally
+ {
+ this.exporter.ReleaseSemaphore();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static async Task WriteMetricsToResponse(PrometheusExporter exporter, HttpResponse response)
+ {
+ response.StatusCode = 200;
+ response.ContentType = PrometheusMetricBuilder.ContentType;
+
+ using var writer = new StreamWriter(response.Body);
+ try
+ {
+ await exporter.WriteMetricsCollection(writer).ConfigureAwait(false);
+ }
+ finally
+ {
+ await writer.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ }
+}
+#endif
diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs
index 28e78e59c..751a7be50 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs
@@ -13,16 +13,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
+
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
-namespace OpenTelemetry.Exporter.Prometheus.Implementation
+namespace OpenTelemetry.Exporter.Prometheus
{
- internal class PrometheusMetricBuilder
+ internal sealed class PrometheusMetricBuilder
{
public const string ContentType = "text/plain; version = 0.0.4";
@@ -89,7 +91,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
return val;
}
- public void Write(StreamWriter writer)
+ public async Task Write(StreamWriter writer)
{
// https://prometheus.io/docs/instrumenting/exposition_formats/
@@ -111,10 +113,10 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
// and the line feed characters have to be escaped as \\ and \n, respectively.
// Only one HELP line may exist for any given metric name.
- writer.Write("# HELP ");
- writer.Write(this.name);
- writer.Write(GetSafeMetricDescription(this.description));
- writer.Write("\n");
+ await writer.WriteAsync("# HELP ").ConfigureAwait(false);
+ await writer.WriteAsync(this.name).ConfigureAwait(false);
+ await writer.WriteAsync(GetSafeMetricDescription(this.description)).ConfigureAwait(false);
+ await writer.WriteAsync("\n").ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(this.type))
@@ -126,11 +128,11 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
// before the first sample is reported for that metric name. If there is no TYPE
// line for a metric name, the type is set to untyped.
- writer.Write("# TYPE ");
- writer.Write(this.name);
- writer.Write(" ");
- writer.Write(this.type);
- writer.Write("\n");
+ await writer.WriteAsync("# TYPE ").ConfigureAwait(false);
+ await writer.WriteAsync(this.name).ConfigureAwait(false);
+ await writer.WriteAsync(" ").ConfigureAwait(false);
+ await writer.WriteAsync(this.type).ConfigureAwait(false);
+ await writer.WriteAsync("\n").ConfigureAwait(false);
}
// The remaining lines describe samples (one per line) using the following syntax (EBNF):
@@ -144,7 +146,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
foreach (var m in this.values)
{
// metric_name and label_name carry the usual Prometheus expression language restrictions.
- writer.Write(m.Name != null ? GetSafeMetricName(m.Name) : this.name);
+ await writer.WriteAsync(m.Name != null ? GetSafeMetricName(m.Name) : this.name).ConfigureAwait(false);
// label_value can be any sequence of UTF-8 characters, but the backslash
// (\, double-quote ("}, and line feed (\n) characters have to be escaped
@@ -152,26 +154,26 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
if (m.Labels.Count > 0)
{
- writer.Write(@"{");
- writer.Write(string.Join(",", m.Labels.Select(x => GetLabelAndValue(x.Item1, x.Item2))));
- writer.Write(@"}");
+ await writer.WriteAsync(@"{").ConfigureAwait(false);
+ await writer.WriteAsync(string.Join(",", m.Labels.Select(x => GetLabelAndValue(x.Item1, x.Item2)))).ConfigureAwait(false);
+ await writer.WriteAsync(@"}").ConfigureAwait(false);
}
// value is a float represented as required by Go's ParseFloat() function. In addition to
// standard numerical values, Nan, +Inf, and -Inf are valid values representing not a number,
// positive infinity, and negative infinity, respectively.
- writer.Write(" ");
- writer.Write(m.Value.ToString(CultureInfo.InvariantCulture));
- writer.Write(" ");
+ await writer.WriteAsync(" ").ConfigureAwait(false);
+ await writer.WriteAsync(m.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
+ await writer.WriteAsync(" ").ConfigureAwait(false);
// The timestamp is an int64 (milliseconds since epoch, i.e. 1970-01-01 00:00:00 UTC, excluding
// leap seconds), represented as required by Go's ParseInt() function.
- writer.Write(now);
+ await writer.WriteAsync(now).ConfigureAwait(false);
// Prometheus' text-based format is line oriented. Lines are separated
// by a line feed character (\n). The last line must end with a line
// feed character. Empty lines are ignored.
- writer.Write("\n");
+ await writer.WriteAsync("\n").ConfigureAwait(false);
}
static string GetLabelAndValue(string label, string value)
@@ -239,7 +241,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
return result;
}
- internal class PrometheusMetricValueBuilder
+ internal sealed class PrometheusMetricValueBuilder
{
public readonly ICollection> Labels = new List>();
public double Value;
diff --git a/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj b/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj
index b7a7eba84..f69c2c2d9 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj
+++ b/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net461
+ netcoreapp3.1;net461
Prometheus exporter for OpenTelemetry .NET
$(PackageTags);prometheus;metrics
core-
@@ -26,8 +26,8 @@
-
-
+
+
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs
index d3b8c6256..66bd48411 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs
@@ -15,6 +15,8 @@
//
using System;
+using System.Threading;
+using OpenTelemetry.Exporter.Prometheus;
using OpenTelemetry.Metrics;
namespace OpenTelemetry.Exporter
@@ -28,7 +30,10 @@ namespace OpenTelemetry.Exporter
{
internal readonly PrometheusExporterOptions Options;
internal Batch Metrics;
+ private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
+ private readonly PrometheusExporterMetricsHttpServer metricsHttpServer;
private Func funcCollect;
+ private bool disposed;
///
/// Initializes a new instance of the class.
@@ -37,12 +42,25 @@ namespace OpenTelemetry.Exporter
public PrometheusExporter(PrometheusExporterOptions options)
{
this.Options = options;
+
+ if (options.StartHttpListener)
+ {
+ try
+ {
+ this.metricsHttpServer = new PrometheusExporterMetricsHttpServer(this);
+ this.metricsHttpServer.Start();
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException("PrometheusExporter http listener could not be started.", ex);
+ }
+ }
}
public Func Collect
{
get => this.funcCollect;
- set { this.funcCollect = value; }
+ set => this.funcCollect = value;
}
public override ExportResult Export(in Batch metrics)
@@ -50,5 +68,31 @@ namespace OpenTelemetry.Exporter
this.Metrics = metrics;
return ExportResult.Success;
}
+
+ internal bool TryEnterSemaphore()
+ {
+ return this.semaphore.Wait(0);
+ }
+
+ internal void ReleaseSemaphore()
+ {
+ this.semaphore.Release();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!this.disposed)
+ {
+ if (disposing)
+ {
+ this.metricsHttpServer?.Dispose();
+ this.semaphore.Dispose();
+ }
+
+ this.disposed = true;
+ }
+
+ base.Dispose(disposing);
+ }
}
}
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterApplicationBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterApplicationBuilderExtensions.cs
new file mode 100644
index 000000000..f88fc1b89
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterApplicationBuilderExtensions.cs
@@ -0,0 +1,59 @@
+//
+// 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.
+//
+
+#if NETCOREAPP3_1_OR_GREATER
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using OpenTelemetry.Exporter;
+using OpenTelemetry.Exporter.Prometheus;
+using OpenTelemetry.Metrics;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Provides extension methods for to add Prometheus Scraper Endpoint.
+ ///
+ public static class PrometheusExporterApplicationBuilderExtensions
+ {
+ ///
+ /// Adds OpenTelemetry Prometheus scraping endpoint middleware to an
+ /// instance.
+ ///
+ /// The to add
+ /// middleware to.
+ /// Optional
+ /// containing a otherwise the primary
+ /// SDK provider will be resolved using application services.
+ /// A reference to the instance after the operation has completed.
+ public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint(this IApplicationBuilder app, MeterProvider meterProvider = null)
+ {
+ var options = app.ApplicationServices.GetOptions();
+
+ string path = options.ScrapeEndpointPath ?? PrometheusExporterOptions.DefaultScrapeEndpointPath;
+ if (!path.StartsWith("/"))
+ {
+ path = $"/{path}";
+ }
+
+ return app.Map(
+ new PathString(path),
+ builder => builder.UseMiddleware(meterProvider ?? app.ApplicationServices.GetRequiredService()));
+ }
+ }
+}
+#endif
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs
deleted file mode 100644
index a0c7216b6..000000000
--- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-//
-// 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.Globalization;
-using System.IO;
-using System.Text;
-using OpenTelemetry.Exporter.Prometheus.Implementation;
-using OpenTelemetry.Metrics;
-using static OpenTelemetry.Exporter.Prometheus.Implementation.PrometheusMetricBuilder;
-
-namespace OpenTelemetry.Exporter
-{
- ///
- /// Helper to write metrics collection from exporter in Prometheus format.
- ///
- public static class PrometheusExporterExtensions
- {
- private const string PrometheusCounterType = "counter";
- private const string PrometheusGaugeType = "gauge";
- private const string PrometheusHistogramType = "histogram";
- private const string PrometheusHistogramSumPostFix = "_sum";
- private const string PrometheusHistogramCountPostFix = "_count";
- private const string PrometheusHistogramBucketPostFix = "_bucket";
- private const string PrometheusHistogramBucketLabelPositiveInfinity = "+Inf";
- private const string PrometheusHistogramBucketLabelLessThan = "le";
-
- ///
- /// Serialize to Prometheus Format.
- ///
- /// Prometheus Exporter.
- /// StreamWriter to write to.
- public static void WriteMetricsCollection(this PrometheusExporter exporter, StreamWriter writer)
- {
- foreach (var metric in exporter.Metrics)
- {
- var builder = new PrometheusMetricBuilder()
- .WithName(metric.Name)
- .WithDescription(metric.Description);
-
- switch (metric.MetricType)
- {
- case MetricType.LongSum:
- {
- builder = builder.WithType(PrometheusCounterType);
- foreach (ref var metricPoint in metric.GetMetricPoints())
- {
- var metricValueBuilder = builder.AddValue();
- metricValueBuilder = metricValueBuilder.WithValue(metricPoint.LongValue);
- metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
- }
-
- builder.Write(writer);
- break;
- }
-
- case MetricType.DoubleSum:
- {
- builder = builder.WithType(PrometheusCounterType);
- foreach (ref var metricPoint in metric.GetMetricPoints())
- {
- var metricValueBuilder = builder.AddValue();
- metricValueBuilder = metricValueBuilder.WithValue(metricPoint.DoubleValue);
- metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
- }
-
- builder.Write(writer);
- break;
- }
-
- case MetricType.LongGauge:
- {
- builder = builder.WithType(PrometheusGaugeType);
- foreach (ref var metricPoint in metric.GetMetricPoints())
- {
- var metricValueBuilder = builder.AddValue();
- metricValueBuilder = metricValueBuilder.WithValue(metricPoint.LongValue);
- metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
- }
-
- builder.Write(writer);
- break;
- }
-
- case MetricType.DoubleGauge:
- {
- builder = builder.WithType(PrometheusGaugeType);
- foreach (ref var metricPoint in metric.GetMetricPoints())
- {
- var metricValueBuilder = builder.AddValue();
- metricValueBuilder = metricValueBuilder.WithValue(metricPoint.DoubleValue);
- metricValueBuilder.AddLabels(metricPoint.Keys, metricPoint.Values);
- }
-
- builder.Write(writer);
- break;
- }
-
- case MetricType.Histogram:
- {
- /*
- * For Histogram we emit one row for Sum, Count and as
- * many rows as number of buckets.
- * myHistogram_sum{tag1="value1",tag2="value2"} 258330 1629860660991
- * myHistogram_count{tag1="value1",tag2="value2"} 355 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="0"} 0 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="5"} 2 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="10"} 4 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="25"} 6 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="50"} 12 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="75"} 19 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="100"} 26 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="250"} 65 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="500"} 128 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="1000"} 241 1629860660991
- * myHistogram_bucket{tag1="value1",tag2="value2",le="+Inf"} 355 1629860660991
- */
- builder = builder.WithType(PrometheusHistogramType);
- foreach (ref var metricPoint in metric.GetMetricPoints())
- {
- var metricValueBuilderSum = builder.AddValue();
- metricValueBuilderSum.WithName(metric.Name + PrometheusHistogramSumPostFix);
- metricValueBuilderSum = metricValueBuilderSum.WithValue(metricPoint.DoubleValue);
- metricValueBuilderSum.AddLabels(metricPoint.Keys, metricPoint.Values);
-
- var metricValueBuilderCount = builder.AddValue();
- metricValueBuilderCount.WithName(metric.Name + PrometheusHistogramCountPostFix);
- metricValueBuilderCount = metricValueBuilderCount.WithValue(metricPoint.LongValue);
- metricValueBuilderCount.AddLabels(metricPoint.Keys, metricPoint.Values);
-
- long totalCount = 0;
- for (int i = 0; i < metricPoint.ExplicitBounds.Length + 1; i++)
- {
- totalCount += metricPoint.BucketCounts[i];
- var metricValueBuilderBuckets = builder.AddValue();
- metricValueBuilderBuckets.WithName(metric.Name + PrometheusHistogramBucketPostFix);
- metricValueBuilderBuckets = metricValueBuilderBuckets.WithValue(totalCount);
- metricValueBuilderBuckets.AddLabels(metricPoint.Keys, metricPoint.Values);
-
- var bucketName = i == metricPoint.ExplicitBounds.Length ?
- PrometheusHistogramBucketLabelPositiveInfinity : metricPoint.ExplicitBounds[i].ToString(CultureInfo.InvariantCulture);
- metricValueBuilderBuckets.WithLabel(PrometheusHistogramBucketLabelLessThan, bucketName);
- }
- }
-
- builder.Write(writer);
-
- break;
- }
- }
- }
- }
-
- ///
- /// Get Metrics Collection as a string.
- ///
- /// Prometheus Exporter.
- /// Metrics serialized to string in Prometheus format.
- public static string GetMetricsCollection(this PrometheusExporter exporter)
- {
- using var stream = new MemoryStream();
- using var writer = new StreamWriter(stream);
- WriteMetricsCollection(exporter, writer);
- writer.Flush();
-
- return Encoding.UTF8.GetString(stream.ToArray(), 0, (int)stream.Length);
- }
-
- private static void AddLabels(this PrometheusMetricValueBuilder valueBuilder, string[] keys, object[] values)
- {
- if (keys != null)
- {
- for (int i = 0; i < keys.Length; i++)
- {
- valueBuilder.WithLabel(keys[i], values[i].ToString());
- }
- }
- }
- }
-}
diff --git a/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMeterProviderBuilderExtensions.cs
similarity index 62%
rename from src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs
rename to src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMeterProviderBuilderExtensions.cs
index f7bfb6c33..3be9b5ead 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMeterProviderBuilderExtensions.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,15 +19,14 @@ using OpenTelemetry.Exporter;
namespace OpenTelemetry.Metrics
{
- public static class MeterProviderBuilderExtensions
+ public static class PrometheusExporterMeterProviderBuilderExtensions
{
///
- /// Adds Console exporter to the TracerProvider.
+ /// Adds to the .
///
/// builder to use.
/// Exporter configuration options.
/// The instance of to chain the calls.
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public static MeterProviderBuilder AddPrometheusExporter(this MeterProviderBuilder builder, Action configure = null)
{
if (builder == null)
@@ -35,14 +34,24 @@ namespace OpenTelemetry.Metrics
throw new ArgumentNullException(nameof(builder));
}
- var options = new PrometheusExporterOptions();
+ if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
+ {
+ return deferredMeterProviderBuilder.Configure((sp, builder) =>
+ {
+ AddPrometheusExporter(builder, sp.GetOptions(), configure);
+ });
+ }
+
+ return AddPrometheusExporter(builder, new PrometheusExporterOptions(), configure);
+ }
+
+ private static MeterProviderBuilder AddPrometheusExporter(MeterProviderBuilder builder, PrometheusExporterOptions options, Action configure = null)
+ {
configure?.Invoke(options);
var exporter = new PrometheusExporter(options);
var reader = new BaseExportingMetricReader(exporter);
- var metricsHttpServer = new PrometheusExporterMetricsHttpServer(exporter);
- metricsHttpServer.Start();
return builder.AddReader(reader);
}
}
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs
deleted file mode 100644
index c692bc850..000000000
--- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// 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.
-//
-
-#if NETSTANDARD2_0
-
-using System;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-
-namespace OpenTelemetry.Exporter
-{
- ///
- /// A middleware used to expose Prometheus metrics.
- ///
- public class PrometheusExporterMiddleware
- {
- private readonly PrometheusExporter exporter;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The next middleware in the pipeline.
- /// The instance.
- public PrometheusExporterMiddleware(RequestDelegate next, PrometheusExporter exporter)
- {
- this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
- }
-
- ///
- /// Invoke.
- ///
- /// context.
- /// Task.
- public Task InvokeAsync(HttpContext httpContext)
- {
- if (httpContext is null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var result = this.exporter.GetMetricsCollection();
-
- return httpContext.Response.WriteAsync(result);
- }
- }
-}
-#endif
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs
index ce4aae423..f12906374 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs
@@ -14,16 +14,40 @@
// limitations under the License.
//
+using System.Collections.Generic;
+
namespace OpenTelemetry.Exporter
{
///
- /// Options to run prometheus exporter.
+ /// options.
///
public class PrometheusExporterOptions
{
+ internal const string DefaultScrapeEndpointPath = "/metrics";
+
+#if NETCOREAPP3_1_OR_GREATER
///
- /// Gets or sets the port to listen to. Typically it ends with /metrics like http://localhost:9184/metrics/.
+ /// Gets or sets a value indicating whether or not an http listener
+ /// should be started. Default value: False.
///
- public string Url { get; set; } = "http://localhost:9184/metrics/";
+ public bool StartHttpListener { get; set; }
+#else
+ ///
+ /// Gets or sets a value indicating whether or not an http listener
+ /// should be started. Default value: True.
+ ///
+ public bool StartHttpListener { get; set; } = true;
+#endif
+
+ ///
+ /// Gets or sets the prefixes to use for the http listener. Default
+ /// value: http://*:80/.
+ ///
+ public IReadOnlyCollection HttpListenerPrefixes { get; set; } = new string[] { "http://*:80/" };
+
+ ///
+ /// Gets or sets the path to use for the scraping endpoint. Default value: /metrics.
+ ///
+ public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath;
}
}
diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs
deleted file mode 100644
index 0b38d4734..000000000
--- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// 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.
-//
-
-#if NETSTANDARD2_0
-
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-
-namespace OpenTelemetry.Exporter.Prometheus
-{
- ///
- /// Provides extension methods for to add Prometheus Scraper Endpoint.
- ///
- public static class PrometheusRouteBuilderExtensions
- {
- private const string DefaultPath = "/metrics";
-
- ///
- /// Use prometheus extension.
- ///
- /// The to add middleware to.
- /// A reference to the instance after the operation has completed.
- public static IApplicationBuilder UsePrometheus(this IApplicationBuilder app)
- {
- var options = app.ApplicationServices.GetService(typeof(PrometheusExporterOptions)) as PrometheusExporterOptions;
- var path = new PathString(options?.Url ?? DefaultPath);
- return app.Map(
- new PathString(path),
- builder => builder.UseMiddleware());
- }
- }
-}
-#endif
diff --git a/src/OpenTelemetry.Exporter.Prometheus/README.md b/src/OpenTelemetry.Exporter.Prometheus/README.md
index ed79f8d2f..29f1468e7 100644
--- a/src/OpenTelemetry.Exporter.Prometheus/README.md
+++ b/src/OpenTelemetry.Exporter.Prometheus/README.md
@@ -7,17 +7,90 @@
* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/)
-## Installation
+## Steps to enable OpenTelemetry.Exporter.Prometheus
+
+### Step 1: Install Package
```shell
dotnet add package OpenTelemetry.Exporter.Prometheus
```
+### Step 2: Configure OpenTelemetry MeterProvider
+
+* When using OpenTelemetry.Extensions.Hosting package on .NET Core 3.1+:
+
+ ```csharp
+ services.AddOpenTelemetryMetrics(builder =>
+ {
+ builder.AddPrometheusExporter();
+ });
+ ```
+
+* Or configure directly:
+
+ Call the `AddPrometheusExporter` `MeterProviderBuilder` extension to
+ register the Prometheus exporter.
+
+ ```csharp
+ using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddPrometheusExporter()
+ .Build();
+ ```
+
+### Step 3: Configure Prometheus Scraping Endpoint
+
+* On .NET Core 3.1+ register Prometheus scraping middleware using the
+ `UseOpenTelemetryPrometheusScrapingEndpoint` extension:
+
+ ```csharp
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ app.UseRouting();
+ app.UseOpenTelemetryPrometheusScrapingEndpoint();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ }
+ ```
+
+* On .NET Framework an http listener is automatically started which will respond
+ to scraping requests. See the [Options Properties](#options-properties)
+ section for details on the settings available. This may also be turned on in
+ .NET Core (it is OFF by default) when the ASP.NET Core pipeline is not
+ available for middleware registration.
+
## Configuration
-You can configure the `PrometheusExporter` by following the directions below:
+You can configure the `PrometheusExporter` through `PrometheusExporterOptions`.
-* `Url`: The url to listen to. Typically it ends with `/metrics` like `http://localhost:9184/metrics/`.
+## Options Properties
+
+The `PrometheusExporter` can be configured using the `PrometheusExporterOptions`
+properties:
+
+* `StartHttpListener`: Set to `true` to start an http listener which will
+ respond to Prometheus scrape requests using the `HttpListenerPrefixes` and
+ `ScrapeEndpointPath` options.
+
+ Defaults:
+
+ * On .NET Framework this is `true` by default.
+
+ * On .NET Core 3.1+ this is `false` by default. Users running ASP.NET Core
+ should use the `UseOpenTelemetryPrometheusScrapingEndpoint` extension to
+ register the scraping middleware instead of using the listener.
+
+* `HttpListenerPrefixes`: Defines the prefixes which will be used by the
+ listener when `StartHttpListener` is `true`. The default value is an array of
+ one element: `http://*:80/`. You may specify multiple endpoints.
+
+ For details see:
+ [HttpListenerPrefixCollection.Add(String)](https://docs.microsoft.com/dotnet/api/system.net.httplistenerprefixcollection.add)
+
+* `ScrapeEndpointPath`: Defines the path for the Prometheus scrape endpoint for
+ either the http listener or the middleware registered by
+ `UseOpenTelemetryPrometheusScrapingEndpoint`. Default value: `/metrics`.
See
[`TestPrometheusExporter.cs`](../../examples/Console/TestPrometheusExporter.cs)
diff --git a/src/OpenTelemetry/Metrics/BaseExportingMetricReader.cs b/src/OpenTelemetry/Metrics/BaseExportingMetricReader.cs
index 058d590be..aae8da125 100644
--- a/src/OpenTelemetry/Metrics/BaseExportingMetricReader.cs
+++ b/src/OpenTelemetry/Metrics/BaseExportingMetricReader.cs
@@ -65,6 +65,8 @@ namespace OpenTelemetry.Metrics
}
}
+ internal BaseExporter Exporter => this.exporter;
+
protected ExportModes SupportedExportModes => this.supportedExportModes;
internal override void SetParentProvider(BaseProvider parentProvider)
diff --git a/src/OpenTelemetry/Metrics/CompositeMetricReader.cs b/src/OpenTelemetry/Metrics/CompositeMetricReader.cs
index c889bdec0..6720341a1 100644
--- a/src/OpenTelemetry/Metrics/CompositeMetricReader.cs
+++ b/src/OpenTelemetry/Metrics/CompositeMetricReader.cs
@@ -67,6 +67,8 @@ namespace OpenTelemetry.Metrics
return this;
}
+ public Enumerator GetEnumerator() => new Enumerator(this.head);
+
///
protected override bool ProcessMetrics(Batch metrics, int timeoutMilliseconds)
{
@@ -159,7 +161,32 @@ namespace OpenTelemetry.Metrics
this.disposed = true;
}
- private class DoublyLinkedListNode
+ public struct Enumerator
+ {
+ private DoublyLinkedListNode node;
+
+ internal Enumerator(DoublyLinkedListNode node)
+ {
+ this.node = node;
+ this.Current = null;
+ }
+
+ public MetricReader Current { get; private set; }
+
+ public bool MoveNext()
+ {
+ if (this.node != null)
+ {
+ this.Current = this.node.Value;
+ this.node = this.node.Next;
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ internal class DoublyLinkedListNode
{
public readonly MetricReader Value;
diff --git a/src/OpenTelemetry/Metrics/MeterProviderExtensions.cs b/src/OpenTelemetry/Metrics/MeterProviderExtensions.cs
index 416c61197..004cafeb4 100644
--- a/src/OpenTelemetry/Metrics/MeterProviderExtensions.cs
+++ b/src/OpenTelemetry/Metrics/MeterProviderExtensions.cs
@@ -120,5 +120,40 @@ namespace OpenTelemetry.Metrics
return true;
}
+
+ public static bool TryFindExporter(this MeterProvider provider, out T exporter)
+ where T : BaseExporter
+ {
+ if (provider is MeterProviderSdk meterProviderSdk)
+ {
+ return TryFindExporter(meterProviderSdk.Reader, out exporter);
+ }
+
+ exporter = null;
+ return false;
+
+ static bool TryFindExporter(MetricReader reader, out T exporter)
+ {
+ if (reader is BaseExportingMetricReader exportingMetricReader)
+ {
+ exporter = exportingMetricReader.Exporter as T;
+ return exporter != null;
+ }
+
+ if (reader is CompositeMetricReader compositeMetricReader)
+ {
+ foreach (MetricReader childReader in compositeMetricReader)
+ {
+ if (TryFindExporter(childReader, out exporter))
+ {
+ return true;
+ }
+ }
+ }
+
+ exporter = null;
+ return false;
+ }
+ }
}
}