diff --git a/docs/metrics/getting-started-prometheus-grafana/README.md b/docs/metrics/getting-started-prometheus-grafana/README.md index 62953f4f13..1e2f2e35e9 100644 --- a/docs/metrics/getting-started-prometheus-grafana/README.md +++ b/docs/metrics/getting-started-prometheus-grafana/README.md @@ -49,10 +49,10 @@ with .AddPrometheusHttpListener() ``` -`PrometheusHttpListener` is a wrapper that contains `PrometheusExporter`. -With `AddPrometheusHttpListener()`, OpenTelemetry `PrometheusExporter` will export +`PrometheusHttpListener` is a wrapper that contains `PrometheusExporter`. With +`AddPrometheusHttpListener()`, OpenTelemetry `PrometheusExporter` will export data via the endpoint defined by -[PrometheusHttpListenerOptions.Prefixes](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md#prefixes), +[PrometheusHttpListenerOptions.UriPrefixes](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md#uriprefixes), which is `http://localhost:9464/` by default. ```mermaid diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs index 0b1284c4ae..befb0e5d4e 100644 --- a/examples/Console/TestPrometheusExporter.cs +++ b/examples/Console/TestPrometheusExporter.cs @@ -52,7 +52,7 @@ internal static object Run(int port) .AddMeter(MyMeter.Name) .AddMeter(MyMeter2.Name) .AddPrometheusHttpListener( - options => options.Prefixes = new string[] { $"http://localhost:{port}/" }) + options => options.UriPrefixes = new string[] { $"http://localhost:{port}/" }) .Build(); var process = Process.GetCurrentProcess(); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt index 7b61e63513..e05aa673ed 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,6 +1,6 @@ OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions -OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.Prefixes.get -> System.Collections.Generic.IReadOnlyCollection -OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.Prefixes.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.set -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 7b61e63513..e05aa673ed 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,6 +1,6 @@ OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions -OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.Prefixes.get -> System.Collections.Generic.IReadOnlyCollection -OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.Prefixes.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.set -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index 324932a426..9d51615280 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -8,6 +8,10 @@ ([#3430](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3430) [#3503](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3503) [#3507](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3507)) +* Fixed bug + [#2840](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2840) by + allowing `+` and `*` to be used in the URI prefixes (e.g. `"http://*:9184"`). + ([#3521](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3521)) ## 1.3.0-rc.2 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index ce3a5a48ed..3bd122a773 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -39,14 +39,12 @@ internal sealed class PrometheusHttpListener : IDisposable public PrometheusHttpListener(PrometheusExporter exporter, PrometheusHttpListenerOptions options) { Guard.ThrowIfNull(exporter); - - if ((options.Prefixes?.Count ?? 0) <= 0) - { - throw new ArgumentException("No Prefixes were specified on PrometheusHttpListenerOptions."); - } + Guard.ThrowIfNull(options); this.exporter = exporter; + string path = this.exporter.ScrapeEndpointPath; + if (!path.StartsWith("/")) { path = $"/{path}"; @@ -57,9 +55,9 @@ public PrometheusHttpListener(PrometheusExporter exporter, PrometheusHttpListene path = $"{path}/"; } - foreach (string prefix in options.Prefixes) + foreach (string uriPrefix in options.UriPrefixes) { - this.httpListener.Prefixes.Add($"{prefix.TrimEnd('/')}{path}"); + this.httpListener.Prefixes.Add($"{uriPrefix.TrimEnd('/')}{path}"); } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs index dc1e8e5d9e..68f88c1642 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -25,7 +25,7 @@ namespace OpenTelemetry.Exporter.Prometheus /// public class PrometheusHttpListenerOptions { - private IReadOnlyCollection prefixes = new string[] { "http://localhost:9464/" }; + private IReadOnlyCollection uriPrefixes = new string[] { "http://localhost:9464/" }; /// /// Gets or sets the path to use for the scraping endpoint. Default value: "/metrics". @@ -33,28 +33,22 @@ public class PrometheusHttpListenerOptions public string ScrapeEndpointPath { get; set; } = "/metrics"; /// - /// Gets or sets the prefixes to use for the http listener. + /// Gets or sets the URI (Uniform Resource Identifier) prefixes to use for the http listener. /// Default value: ["http://localhost:9464/"]. /// - public IReadOnlyCollection Prefixes + public IReadOnlyCollection UriPrefixes { - get => this.prefixes; + get => this.uriPrefixes; set { Guard.ThrowIfNull(value); - foreach (string inputUri in value) + if (value.Count == 0) { - if (!(Uri.TryCreate(inputUri, UriKind.Absolute, out var uri) && - (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))) - { - throw new ArgumentException( - "Prometheus HttpListener prefix path should be a valid URI with http/https scheme.", - nameof(this.prefixes)); - } + throw new ArgumentException("Empty list provided.", nameof(this.UriPrefixes)); } - this.prefixes = value; + this.uriPrefixes = value; } } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md index b997b5adec..b97cb0bd25 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md @@ -15,38 +15,32 @@ instance for Prometheus to scrape. ### Step 1: Install Package -Install - ```shell dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener ``` ### Step 2: Add PrometheusHttpListener -Add and configure `PrometheusHttpListener` with `PrometheusHttpListenerOptions`. - -For example: - ```csharp -using var meterProvider = Sdk.CreateMeterProviderBuilder() +var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(MyMeter.Name) .AddPrometheusHttpListener( - options => options.Prefixes = new string[] { "http://localhost:9464/" }) + options => options.UriPrefixes = new string[] { "http://localhost:9464/" }) .Build(); ``` -### Prefixes +### UriPrefixes -Defines the prefixes which will be used by the listener. The default value is `["http://localhost:9464/"]`. -You may specify multiple endpoints. +Defines one or more URI (Uniform Resource Identifier) prefixes which will be +used by the HTTP listener. The default value is `["http://localhost:9464/"]`. -For details see: +Refer to [HttpListenerPrefixCollection.Add(String)](https://docs.microsoft.com/dotnet/api/system.net.httplistenerprefixcollection.add) +for more details. ### ScrapeEndpointPath -Defines the path for the Prometheus scrape endpoint for by -`UseOpenTelemetryPrometheusScrapingEndpoint`. Default value: `"/metrics"`. +Defines the Prometheus scrape endpoint path. Default value: `"/metrics"`. ## Troubleshooting diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index bfaa2f06f5..fb281a3563 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -31,42 +31,52 @@ public class PrometheusHttpListenerTests private readonly string meterName = Utils.GetCurrentMethodName(); [Theory] - [InlineData("http://example.com")] + [InlineData("http://+:9184")] + [InlineData("http://*:9184")] + [InlineData("http://+:9184/")] + [InlineData("http://*:9184/")] [InlineData("https://example.com")] [InlineData("http://127.0.0.1")] [InlineData("http://example.com", "https://example.com", "http://127.0.0.1")] - public void ServerEndpointSanityCheckPositiveTest(params string[] uris) + [InlineData("http://example.com")] + public void UriPrefixesPositiveTest(params string[] uriPrefixes) { using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() - .AddPrometheusHttpListener(options => options.Prefixes = uris) + .AddPrometheusHttpListener(options => options.UriPrefixes = uriPrefixes) .Build(); } - [Theory] - [InlineData("")] - [InlineData(null)] - [InlineData("ftp://example.com")] - [InlineData("http://example.com", "https://example.com", "ftp://example.com")] - public void ServerEndpointSanityCheckNegativeTest(params string[] uris) + [Fact] + public void UriPrefixesNull() { - try + Assert.Throws(() => { using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() - .AddPrometheusHttpListener(options => options.Prefixes = uris) + .AddPrometheusHttpListener(options => options.UriPrefixes = null) .Build(); - } - catch (Exception ex) + }); + } + + [Fact] + public void UriPrefixesEmptyList() + { + Assert.Throws(() => { - if (ex is not ArgumentNullException) - { - Assert.Equal("System.ArgumentException", ex.GetType().ToString()); -#if NETFRAMEWORK - Assert.Equal("Prometheus HttpListener prefix path should be a valid URI with http/https scheme.\r\nParameter name: prefixes", ex.Message); -#else - Assert.Equal("Prometheus HttpListener prefix path should be a valid URI with http/https scheme. (Parameter 'prefixes')", ex.Message); -#endif - } - } + using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { }) + .Build(); + }); + } + + [Fact] + public void UriPrefixesInvalid() + { + Assert.Throws(() => + { + using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "ftp://example.com" }) + .Build(); + }); } [Fact] @@ -98,7 +108,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .AddPrometheusHttpListener(options => options.Prefixes = new string[] { address }) + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { address }) .Build(); } diff --git a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs index 91d5e64a26..da3215aee7 100644 --- a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs @@ -47,7 +47,7 @@ public static void Main() using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(TestMeter.Name) .AddPrometheusHttpListener( - options => options.Prefixes = new string[] { $"http://localhost:9185/" }) + options => options.UriPrefixes = new string[] { $"http://localhost:9185/" }) .Build(); Stress(prometheusPort: 9184); diff --git a/test/OpenTelemetry.Tests.Stress/Skeleton.cs b/test/OpenTelemetry.Tests.Stress/Skeleton.cs index 967bcd53a4..c69e2543a1 100644 --- a/test/OpenTelemetry.Tests.Stress/Skeleton.cs +++ b/test/OpenTelemetry.Tests.Stress/Skeleton.cs @@ -76,7 +76,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0) .AddMeter(meter.Name) .AddRuntimeInstrumentation() .AddPrometheusHttpListener( - options => options.Prefixes = new string[] { $"http://localhost:{prometheusPort}/" }) + options => options.UriPrefixes = new string[] { $"http://localhost:{prometheusPort}/" }) .Build() : null; var statistics = new long[concurrency];