PrometheusExporter: Project cleanup and middleware fixes (#2414)
This commit is contained in:
parent
f47ea55a20
commit
dc24fe1485
|
|
@ -12,13 +12,14 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj" />
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj" />
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.AspNetCore\OpenTelemetry.Instrumentation.AspNetCore.csproj" />
|
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
|
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.Http\OpenTelemetry.Instrumentation.Http.csproj" />
|
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
|
||||||
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj" />
|
||||||
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.AspNetCore\OpenTelemetry.Instrumentation.AspNetCore.csproj" />
|
||||||
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.Http\OpenTelemetry.Instrumentation.Http.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using OpenTelemetry;
|
|
||||||
using OpenTelemetry.Exporter;
|
using OpenTelemetry.Exporter;
|
||||||
using OpenTelemetry.Instrumentation.AspNetCore;
|
using OpenTelemetry.Instrumentation.AspNetCore;
|
||||||
using OpenTelemetry.Metrics;
|
using OpenTelemetry.Metrics;
|
||||||
|
|
@ -34,8 +33,6 @@ namespace Examples.AspNetCore
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
private MeterProvider meterProvider;
|
|
||||||
|
|
||||||
public Startup(IConfiguration configuration)
|
public Startup(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
this.Configuration = configuration;
|
this.Configuration = configuration;
|
||||||
|
|
@ -59,9 +56,9 @@ namespace Examples.AspNetCore
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Switch between Zipkin/Jaeger by setting UseExporter in appsettings.json.
|
// Switch between Zipkin/Jaeger/OTLP by setting UseExporter in appsettings.json.
|
||||||
var exporter = this.Configuration.GetValue<string>("UseExporter").ToLowerInvariant();
|
var tracingExporter = this.Configuration.GetValue<string>("UseTracingExporter").ToLowerInvariant();
|
||||||
switch (exporter)
|
switch (tracingExporter)
|
||||||
{
|
{
|
||||||
case "jaeger":
|
case "jaeger":
|
||||||
services.AddOpenTelemetryTracing((builder) => builder
|
services.AddOpenTelemetryTracing((builder) => builder
|
||||||
|
|
@ -117,15 +114,24 @@ namespace Examples.AspNetCore
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add IServiceCollection.AddOpenTelemetryMetrics extension method
|
var metricsExporter = this.Configuration.GetValue<string>("UseMetricsExporter").ToLowerInvariant();
|
||||||
var providerBuilder = Sdk.CreateMeterProviderBuilder()
|
services.AddOpenTelemetryMetrics(builder =>
|
||||||
.AddAspNetCoreInstrumentation();
|
{
|
||||||
|
builder.AddAspNetCoreInstrumentation();
|
||||||
|
|
||||||
// TODO: Add configuration switch for Prometheus and OTLP export
|
switch (metricsExporter)
|
||||||
providerBuilder
|
{
|
||||||
.AddConsoleExporter();
|
case "prometheus":
|
||||||
|
builder.AddPrometheusExporter();
|
||||||
this.meterProvider = providerBuilder.Build();
|
break;
|
||||||
|
case "otlp":
|
||||||
|
builder.AddOtlpExporter();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
builder.AddConsoleExporter();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
|
@ -146,6 +152,12 @@ namespace Examples.AspNetCore
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
|
var metricsExporter = this.Configuration.GetValue<string>("UseMetricsExporter").ToLowerInvariant();
|
||||||
|
if (metricsExporter == "prometheus")
|
||||||
|
{
|
||||||
|
app.UseOpenTelemetryPrometheusScrapingEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"UseExporter": "console",
|
"UseTracingExporter": "console",
|
||||||
|
"UseMetricsExporter": "console",
|
||||||
"UseLogging": true,
|
"UseLogging": true,
|
||||||
"Jaeger": {
|
"Jaeger": {
|
||||||
"ServiceName": "jaeger-test",
|
"ServiceName": "jaeger-test",
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,11 @@ namespace Examples.Console
|
||||||
*/
|
*/
|
||||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||||
.AddSource("TestMeter")
|
.AddSource("TestMeter")
|
||||||
.AddPrometheusExporter(opt => opt.Url = $"http://localhost:{port}/metrics/")
|
.AddPrometheusExporter(opt =>
|
||||||
|
{
|
||||||
|
opt.StartHttpListener = true;
|
||||||
|
opt.HttpListenerPrefixes = new string[] { $"http://*:{port}/" };
|
||||||
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
ObservableGauge<long> gauge = MyMeter.CreateObservableGauge<long>(
|
ObservableGauge<long> gauge = MyMeter.CreateObservableGauge<long>(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// <copyright file="AssemblyInfo.cs" company="OpenTelemetry Authors">
|
||||||
|
// 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.
|
||||||
|
// </copyright>
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#if SIGNED
|
||||||
|
[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")]
|
||||||
|
#else
|
||||||
|
[assembly: InternalsVisibleTo("Benchmarks")]
|
||||||
|
#endif
|
||||||
|
|
@ -18,13 +18,13 @@ using System;
|
||||||
using System.Diagnostics.Tracing;
|
using System.Diagnostics.Tracing;
|
||||||
using OpenTelemetry.Internal;
|
using OpenTelemetry.Internal;
|
||||||
|
|
||||||
namespace OpenTelemetry.Exporter.Prometheus.Implementation
|
namespace OpenTelemetry.Exporter.Prometheus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventSource events emitted from the project.
|
/// EventSource events emitted from the project.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EventSource(Name = "OpenTelemetry-Exporter-Prometheus")]
|
[EventSource(Name = "OpenTelemetry-Exporter-Prometheus")]
|
||||||
internal class PrometheusExporterEventSource : EventSource
|
internal sealed class PrometheusExporterEventSource : EventSource
|
||||||
{
|
{
|
||||||
public static PrometheusExporterEventSource Log = new PrometheusExporterEventSource();
|
public static PrometheusExporterEventSource Log = new PrometheusExporterEventSource();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
// <copyright file="PrometheusExporterExtensions.cs" company="OpenTelemetry Authors">
|
||||||
|
// 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.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using OpenTelemetry.Metrics;
|
||||||
|
using static OpenTelemetry.Exporter.Prometheus.PrometheusMetricBuilder;
|
||||||
|
|
||||||
|
namespace OpenTelemetry.Exporter.Prometheus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to write metrics collection from exporter in Prometheus format.
|
||||||
|
/// </summary>
|
||||||
|
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";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize metrics to prometheus format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exporter"><see cref="PrometheusExporter"/>.</param>
|
||||||
|
/// <param name="writer">StreamWriter to write to.</param>
|
||||||
|
/// <returns><see cref="Task"/> to await the operation.</returns>
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,14 +19,13 @@ using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenTelemetry.Exporter.Prometheus.Implementation;
|
|
||||||
|
|
||||||
namespace OpenTelemetry.Exporter
|
namespace OpenTelemetry.Exporter.Prometheus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A HTTP listener used to expose Prometheus metrics.
|
/// A HTTP listener used to expose Prometheus metrics.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PrometheusExporterMetricsHttpServer : IDisposable
|
internal sealed class PrometheusExporterMetricsHttpServer : IDisposable
|
||||||
{
|
{
|
||||||
private readonly PrometheusExporter exporter;
|
private readonly PrometheusExporter exporter;
|
||||||
private readonly HttpListener httpListener = new HttpListener();
|
private readonly HttpListener httpListener = new HttpListener();
|
||||||
|
|
@ -42,7 +41,22 @@ namespace OpenTelemetry.Exporter
|
||||||
public PrometheusExporterMetricsHttpServer(PrometheusExporter exporter)
|
public PrometheusExporterMetricsHttpServer(PrometheusExporter exporter)
|
||||||
{
|
{
|
||||||
this.exporter = exporter ?? throw new ArgumentNullException(nameof(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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -87,16 +101,6 @@ namespace OpenTelemetry.Exporter
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
|
||||||
this.Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases the unmanaged resources used by this class and optionally releases the managed resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
{
|
||||||
if (this.httpListener != null && this.httpListener.IsListening)
|
if (this.httpListener != null && this.httpListener.IsListening)
|
||||||
{
|
{
|
||||||
|
|
@ -116,26 +120,21 @@ namespace OpenTelemetry.Exporter
|
||||||
{
|
{
|
||||||
var ctxTask = this.httpListener.GetContextAsync();
|
var ctxTask = this.httpListener.GetContextAsync();
|
||||||
ctxTask.Wait(this.tokenSource.Token);
|
ctxTask.Wait(this.tokenSource.Token);
|
||||||
|
|
||||||
var ctx = ctxTask.Result;
|
var ctx = ctxTask.Result;
|
||||||
|
|
||||||
ctx.Response.StatusCode = 200;
|
if (!this.exporter.TryEnterSemaphore())
|
||||||
ctx.Response.ContentType = PrometheusMetricBuilder.ContentType;
|
{
|
||||||
|
ctx.Response.StatusCode = 429;
|
||||||
|
ctx.Response.Close();
|
||||||
|
}
|
||||||
|
|
||||||
using var output = ctx.Response.OutputStream;
|
Task.Run(() => this.ProcessExportRequest(ctx));
|
||||||
using var writer = new StreamWriter(output);
|
|
||||||
this.exporter.Collect(Timeout.Infinite);
|
|
||||||
this.exporter.WriteMetricsCollection(writer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
PrometheusExporterEventSource.Log.CanceledExport(ex);
|
PrometheusExporterEventSource.Log.CanceledExport(ex);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PrometheusExporterEventSource.Log.FailedExport(ex);
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
try
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
// <copyright file="PrometheusExporterMiddleware.cs" company="OpenTelemetry Authors">
|
||||||
|
// 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.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ASP.NET Core middleware for exposing a Prometheus metrics scraping endpoint.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class PrometheusExporterMiddleware
|
||||||
|
{
|
||||||
|
private readonly PrometheusExporter exporter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PrometheusExporterMiddleware"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="meterProvider"><see cref="MeterProvider"/>.</param>
|
||||||
|
/// <param name="next"><see cref="RequestDelegate"/>.</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext"> context. </param>
|
||||||
|
/// <returns>Task. </returns>
|
||||||
|
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
|
||||||
|
|
@ -13,16 +13,18 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
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";
|
public const string ContentType = "text/plain; version = 0.0.4";
|
||||||
|
|
||||||
|
|
@ -89,7 +91,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(StreamWriter writer)
|
public async Task Write(StreamWriter writer)
|
||||||
{
|
{
|
||||||
// https://prometheus.io/docs/instrumenting/exposition_formats/
|
// 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.
|
// and the line feed characters have to be escaped as \\ and \n, respectively.
|
||||||
// Only one HELP line may exist for any given metric name.
|
// Only one HELP line may exist for any given metric name.
|
||||||
|
|
||||||
writer.Write("# HELP ");
|
await writer.WriteAsync("# HELP ").ConfigureAwait(false);
|
||||||
writer.Write(this.name);
|
await writer.WriteAsync(this.name).ConfigureAwait(false);
|
||||||
writer.Write(GetSafeMetricDescription(this.description));
|
await writer.WriteAsync(GetSafeMetricDescription(this.description)).ConfigureAwait(false);
|
||||||
writer.Write("\n");
|
await writer.WriteAsync("\n").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.type))
|
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
|
// 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.
|
// line for a metric name, the type is set to untyped.
|
||||||
|
|
||||||
writer.Write("# TYPE ");
|
await writer.WriteAsync("# TYPE ").ConfigureAwait(false);
|
||||||
writer.Write(this.name);
|
await writer.WriteAsync(this.name).ConfigureAwait(false);
|
||||||
writer.Write(" ");
|
await writer.WriteAsync(" ").ConfigureAwait(false);
|
||||||
writer.Write(this.type);
|
await writer.WriteAsync(this.type).ConfigureAwait(false);
|
||||||
writer.Write("\n");
|
await writer.WriteAsync("\n").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The remaining lines describe samples (one per line) using the following syntax (EBNF):
|
// 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)
|
foreach (var m in this.values)
|
||||||
{
|
{
|
||||||
// metric_name and label_name carry the usual Prometheus expression language restrictions.
|
// 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
|
// label_value can be any sequence of UTF-8 characters, but the backslash
|
||||||
// (\, double-quote ("}, and line feed (\n) characters have to be escaped
|
// (\, 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)
|
if (m.Labels.Count > 0)
|
||||||
{
|
{
|
||||||
writer.Write(@"{");
|
await writer.WriteAsync(@"{").ConfigureAwait(false);
|
||||||
writer.Write(string.Join(",", m.Labels.Select(x => GetLabelAndValue(x.Item1, x.Item2))));
|
await writer.WriteAsync(string.Join(",", m.Labels.Select(x => GetLabelAndValue(x.Item1, x.Item2)))).ConfigureAwait(false);
|
||||||
writer.Write(@"}");
|
await writer.WriteAsync(@"}").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// value is a float represented as required by Go's ParseFloat() function. In addition to
|
// 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,
|
// standard numerical values, Nan, +Inf, and -Inf are valid values representing not a number,
|
||||||
// positive infinity, and negative infinity, respectively.
|
// positive infinity, and negative infinity, respectively.
|
||||||
writer.Write(" ");
|
await writer.WriteAsync(" ").ConfigureAwait(false);
|
||||||
writer.Write(m.Value.ToString(CultureInfo.InvariantCulture));
|
await writer.WriteAsync(m.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||||
writer.Write(" ");
|
await writer.WriteAsync(" ").ConfigureAwait(false);
|
||||||
|
|
||||||
// The timestamp is an int64 (milliseconds since epoch, i.e. 1970-01-01 00:00:00 UTC, excluding
|
// 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.
|
// 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
|
// Prometheus' text-based format is line oriented. Lines are separated
|
||||||
// by a line feed character (\n). The last line must end with a line
|
// by a line feed character (\n). The last line must end with a line
|
||||||
// feed character. Empty lines are ignored.
|
// feed character. Empty lines are ignored.
|
||||||
writer.Write("\n");
|
await writer.WriteAsync("\n").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetLabelAndValue(string label, string value)
|
static string GetLabelAndValue(string label, string value)
|
||||||
|
|
@ -239,7 +241,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Implementation
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class PrometheusMetricValueBuilder
|
internal sealed class PrometheusMetricValueBuilder
|
||||||
{
|
{
|
||||||
public readonly ICollection<Tuple<string, string>> Labels = new List<Tuple<string, string>>();
|
public readonly ICollection<Tuple<string, string>> Labels = new List<Tuple<string, string>>();
|
||||||
public double Value;
|
public double Value;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
|
<TargetFrameworks>netcoreapp3.1;net461</TargetFrameworks>
|
||||||
<Description>Prometheus exporter for OpenTelemetry .NET</Description>
|
<Description>Prometheus exporter for OpenTelemetry .NET</Description>
|
||||||
<PackageTags>$(PackageTags);prometheus;metrics</PackageTags>
|
<PackageTags>$(PackageTags);prometheus;metrics</PackageTags>
|
||||||
<MinVerTagPrefix>core-</MinVerTagPrefix>
|
<MinVerTagPrefix>core-</MinVerTagPrefix>
|
||||||
|
|
@ -26,8 +26,8 @@
|
||||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
|
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPkgVer)" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using OpenTelemetry.Exporter.Prometheus;
|
||||||
using OpenTelemetry.Metrics;
|
using OpenTelemetry.Metrics;
|
||||||
|
|
||||||
namespace OpenTelemetry.Exporter
|
namespace OpenTelemetry.Exporter
|
||||||
|
|
@ -28,7 +30,10 @@ namespace OpenTelemetry.Exporter
|
||||||
{
|
{
|
||||||
internal readonly PrometheusExporterOptions Options;
|
internal readonly PrometheusExporterOptions Options;
|
||||||
internal Batch<Metric> Metrics;
|
internal Batch<Metric> Metrics;
|
||||||
|
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
|
||||||
|
private readonly PrometheusExporterMetricsHttpServer metricsHttpServer;
|
||||||
private Func<int, bool> funcCollect;
|
private Func<int, bool> funcCollect;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PrometheusExporter"/> class.
|
/// Initializes a new instance of the <see cref="PrometheusExporter"/> class.
|
||||||
|
|
@ -37,12 +42,25 @@ namespace OpenTelemetry.Exporter
|
||||||
public PrometheusExporter(PrometheusExporterOptions options)
|
public PrometheusExporter(PrometheusExporterOptions options)
|
||||||
{
|
{
|
||||||
this.Options = 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<int, bool> Collect
|
public Func<int, bool> Collect
|
||||||
{
|
{
|
||||||
get => this.funcCollect;
|
get => this.funcCollect;
|
||||||
set { this.funcCollect = value; }
|
set => this.funcCollect = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ExportResult Export(in Batch<Metric> metrics)
|
public override ExportResult Export(in Batch<Metric> metrics)
|
||||||
|
|
@ -50,5 +68,31 @@ namespace OpenTelemetry.Exporter
|
||||||
this.Metrics = metrics;
|
this.Metrics = metrics;
|
||||||
return ExportResult.Success;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// <copyright file="PrometheusExporterApplicationBuilderExtensions.cs" company="OpenTelemetry Authors">
|
||||||
|
// 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.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for <see cref="IApplicationBuilder"/> to add Prometheus Scraper Endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public static class PrometheusExporterApplicationBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds OpenTelemetry Prometheus scraping endpoint middleware to an
|
||||||
|
/// <see cref="IApplicationBuilder"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/> to add
|
||||||
|
/// middleware to.</param>
|
||||||
|
/// <param name="meterProvider">Optional <see cref="MeterProvider"/>
|
||||||
|
/// containing a <see cref="PrometheusExporter"/> otherwise the primary
|
||||||
|
/// SDK provider will be resolved using application services.</param>
|
||||||
|
/// <returns>A reference to the <see cref="IApplicationBuilder"/> instance after the operation has completed.</returns>
|
||||||
|
public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint(this IApplicationBuilder app, MeterProvider meterProvider = null)
|
||||||
|
{
|
||||||
|
var options = app.ApplicationServices.GetOptions<PrometheusExporterOptions>();
|
||||||
|
|
||||||
|
string path = options.ScrapeEndpointPath ?? PrometheusExporterOptions.DefaultScrapeEndpointPath;
|
||||||
|
if (!path.StartsWith("/"))
|
||||||
|
{
|
||||||
|
path = $"/{path}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Map(
|
||||||
|
new PathString(path),
|
||||||
|
builder => builder.UseMiddleware<PrometheusExporterMiddleware>(meterProvider ?? app.ApplicationServices.GetRequiredService<MeterProvider>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
// <copyright file="PrometheusExporterExtensions.cs" company="OpenTelemetry Authors">
|
|
||||||
// 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.
|
|
||||||
// </copyright>
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to write metrics collection from exporter in Prometheus format.
|
|
||||||
/// </summary>
|
|
||||||
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";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize to Prometheus Format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exporter">Prometheus Exporter.</param>
|
|
||||||
/// <param name="writer">StreamWriter to write to.</param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get Metrics Collection as a string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exporter"> Prometheus Exporter. </param>
|
|
||||||
/// <returns>Metrics serialized to string in Prometheus format.</returns>
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// <copyright file="MeterProviderBuilderExtensions.cs" company="OpenTelemetry Authors">
|
// <copyright file="PrometheusExporterMeterProviderBuilderExtensions.cs" company="OpenTelemetry Authors">
|
||||||
// Copyright The OpenTelemetry Authors
|
// Copyright The OpenTelemetry Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
@ -19,15 +19,14 @@ using OpenTelemetry.Exporter;
|
||||||
|
|
||||||
namespace OpenTelemetry.Metrics
|
namespace OpenTelemetry.Metrics
|
||||||
{
|
{
|
||||||
public static class MeterProviderBuilderExtensions
|
public static class PrometheusExporterMeterProviderBuilderExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds Console exporter to the TracerProvider.
|
/// Adds <see cref="PrometheusExporter"/> to the <see cref="MeterProviderBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder"><see cref="MeterProviderBuilder"/> builder to use.</param>
|
/// <param name="builder"><see cref="MeterProviderBuilder"/> builder to use.</param>
|
||||||
/// <param name="configure">Exporter configuration options.</param>
|
/// <param name="configure">Exporter configuration options.</param>
|
||||||
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
|
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
|
||||||
[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<PrometheusExporterOptions> configure = null)
|
public static MeterProviderBuilder AddPrometheusExporter(this MeterProviderBuilder builder, Action<PrometheusExporterOptions> configure = null)
|
||||||
{
|
{
|
||||||
if (builder == null)
|
if (builder == null)
|
||||||
|
|
@ -35,14 +34,24 @@ namespace OpenTelemetry.Metrics
|
||||||
throw new ArgumentNullException(nameof(builder));
|
throw new ArgumentNullException(nameof(builder));
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new PrometheusExporterOptions();
|
if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
|
||||||
|
{
|
||||||
|
return deferredMeterProviderBuilder.Configure((sp, builder) =>
|
||||||
|
{
|
||||||
|
AddPrometheusExporter(builder, sp.GetOptions<PrometheusExporterOptions>(), configure);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddPrometheusExporter(builder, new PrometheusExporterOptions(), configure);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MeterProviderBuilder AddPrometheusExporter(MeterProviderBuilder builder, PrometheusExporterOptions options, Action<PrometheusExporterOptions> configure = null)
|
||||||
|
{
|
||||||
configure?.Invoke(options);
|
configure?.Invoke(options);
|
||||||
|
|
||||||
var exporter = new PrometheusExporter(options);
|
var exporter = new PrometheusExporter(options);
|
||||||
var reader = new BaseExportingMetricReader(exporter);
|
var reader = new BaseExportingMetricReader(exporter);
|
||||||
|
|
||||||
var metricsHttpServer = new PrometheusExporterMetricsHttpServer(exporter);
|
|
||||||
metricsHttpServer.Start();
|
|
||||||
return builder.AddReader(reader);
|
return builder.AddReader(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// <copyright file="PrometheusExporterMiddleware.cs" company="OpenTelemetry Authors">
|
|
||||||
// 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.
|
|
||||||
// </copyright>
|
|
||||||
|
|
||||||
#if NETSTANDARD2_0
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace OpenTelemetry.Exporter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A middleware used to expose Prometheus metrics.
|
|
||||||
/// </summary>
|
|
||||||
public class PrometheusExporterMiddleware
|
|
||||||
{
|
|
||||||
private readonly PrometheusExporter exporter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="PrometheusExporterMiddleware"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="next">The next middleware in the pipeline.</param>
|
|
||||||
/// <param name="exporter">The <see cref="PrometheusExporter"/> instance.</param>
|
|
||||||
public PrometheusExporterMiddleware(RequestDelegate next, PrometheusExporter exporter)
|
|
||||||
{
|
|
||||||
this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="httpContext"> context. </param>
|
|
||||||
/// <returns>Task. </returns>
|
|
||||||
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
|
|
||||||
|
|
@ -14,16 +14,40 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace OpenTelemetry.Exporter
|
namespace OpenTelemetry.Exporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options to run prometheus exporter.
|
/// <see cref="PrometheusExporter"/> options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PrometheusExporterOptions
|
public class PrometheusExporterOptions
|
||||||
{
|
{
|
||||||
|
internal const string DefaultScrapeEndpointPath = "/metrics";
|
||||||
|
|
||||||
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Url { get; set; } = "http://localhost:9184/metrics/";
|
public bool StartHttpListener { get; set; }
|
||||||
|
#else
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not an http listener
|
||||||
|
/// should be started. Default value: True.
|
||||||
|
/// </summary>
|
||||||
|
public bool StartHttpListener { get; set; } = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefixes to use for the http listener. Default
|
||||||
|
/// value: http://*:80/.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<string> HttpListenerPrefixes { get; set; } = new string[] { "http://*:80/" };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path to use for the scraping endpoint. Default value: /metrics.
|
||||||
|
/// </summary>
|
||||||
|
public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
// <copyright file="PrometheusRouteBuilderExtensions.cs" company="OpenTelemetry Authors">
|
|
||||||
// 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.
|
|
||||||
// </copyright>
|
|
||||||
|
|
||||||
#if NETSTANDARD2_0
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace OpenTelemetry.Exporter.Prometheus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides extension methods for <see cref="IApplicationBuilder"/> to add Prometheus Scraper Endpoint.
|
|
||||||
/// </summary>
|
|
||||||
public static class PrometheusRouteBuilderExtensions
|
|
||||||
{
|
|
||||||
private const string DefaultPath = "/metrics";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Use prometheus extension.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="app">The <see cref="IApplicationBuilder"/> to add middleware to.</param>
|
|
||||||
/// <returns>A reference to the <see cref="IApplicationBuilder"/> instance after the operation has completed.</returns>
|
|
||||||
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<PrometheusExporterMiddleware>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -7,17 +7,90 @@
|
||||||
|
|
||||||
* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/)
|
* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/)
|
||||||
|
|
||||||
## Installation
|
## Steps to enable OpenTelemetry.Exporter.Prometheus
|
||||||
|
|
||||||
|
### Step 1: Install Package
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
dotnet add package OpenTelemetry.Exporter.Prometheus
|
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
|
## 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
|
See
|
||||||
[`TestPrometheusExporter.cs`](../../examples/Console/TestPrometheusExporter.cs)
|
[`TestPrometheusExporter.cs`](../../examples/Console/TestPrometheusExporter.cs)
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ namespace OpenTelemetry.Metrics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal BaseExporter<Metric> Exporter => this.exporter;
|
||||||
|
|
||||||
protected ExportModes SupportedExportModes => this.supportedExportModes;
|
protected ExportModes SupportedExportModes => this.supportedExportModes;
|
||||||
|
|
||||||
internal override void SetParentProvider(BaseProvider parentProvider)
|
internal override void SetParentProvider(BaseProvider parentProvider)
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ namespace OpenTelemetry.Metrics
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Enumerator GetEnumerator() => new Enumerator(this.head);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override bool ProcessMetrics(Batch<Metric> metrics, int timeoutMilliseconds)
|
protected override bool ProcessMetrics(Batch<Metric> metrics, int timeoutMilliseconds)
|
||||||
{
|
{
|
||||||
|
|
@ -159,7 +161,32 @@ namespace OpenTelemetry.Metrics
|
||||||
this.disposed = true;
|
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;
|
public readonly MetricReader Value;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,5 +120,40 @@ namespace OpenTelemetry.Metrics
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryFindExporter<T>(this MeterProvider provider, out T exporter)
|
||||||
|
where T : BaseExporter<Metric>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue