From 007a2ae9402ebda1a1c6e9ae53e08cb893e20b56 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 12:12:43 -0700 Subject: [PATCH 1/7] Add very basic prometheus support --- OpenTelemetry.sln | 8 +- docs/metrics/getting-started/Program.cs | 5 +- .../getting-started/getting-started.csproj | 1 + .../CHANGELOG.md | 27 ++ .../PrometheusExporterEventSource.cs | 61 ++++ .../Implementation/PrometheusMetricBuilder.cs | 276 ++++++++++++++++++ .../MeterProviderBuilderExtensions.cs | 49 ++++ .../OpenTelemetry.Exporter.Prometheus.csproj | 26 ++ .../PrometheusExporter.cs | 50 ++++ .../PrometheusExporterExtensions.cs | 146 +++++++++ .../PrometheusExporterMetricsHttpServer.cs | 145 +++++++++ .../PrometheusExporterMiddleware.cs | 60 ++++ .../PrometheusExporterOptions.cs | 29 ++ .../PrometheusRouteBuilderExtensions.cs | 46 +++ .../README.md | 29 ++ 15 files changed, 955 insertions(+), 3 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md create mode 100644 src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj create mode 100644 src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus/README.md diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 84d80c5c51c..d1761243800 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -208,7 +208,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "exception-reporting", "docs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs\trace\customizing-the-sdk\customizing-the-sdk.csproj", "{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "getting-started", "docs\metrics\getting-started\getting-started.csproj", "{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started", "docs\metrics\getting-started\getting-started.csproj", "{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Prometheus", "src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj", "{52158A12-E7EF-45A1-859F-06F9B17410CB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -412,6 +414,10 @@ Global {DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Debug|Any CPU.Build.0 = Debug|Any CPU {DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Release|Any CPU.Build.0 = Release|Any CPU + {52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/docs/metrics/getting-started/Program.cs b/docs/metrics/getting-started/Program.cs index e8aa9c63267..d7a62fde903 100644 --- a/docs/metrics/getting-started/Program.cs +++ b/docs/metrics/getting-started/Program.cs @@ -31,7 +31,8 @@ public static async Task Main(string[] args) { using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddSource("TestMeter") - .AddConsoleExporter() + // .AddConsoleExporter() + .AddPrometheusExporter() .Build(); using var token = new CancellationTokenSource(); @@ -48,7 +49,7 @@ public static async Task Main(string[] args) }); writeMetricTask.Start(); - token.CancelAfter(10000); + token.CancelAfter(1000000); await writeMetricTask; } } diff --git a/docs/metrics/getting-started/getting-started.csproj b/docs/metrics/getting-started/getting-started.csproj index 9f5b6b79bc3..236ed576ec3 100644 --- a/docs/metrics/getting-started/getting-started.csproj +++ b/docs/metrics/getting-started/getting-started.csproj @@ -2,5 +2,6 @@ + diff --git a/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md new file mode 100644 index 00000000000..5084c833ad1 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +## Unreleased + +## 0.7.0-beta.1 + +Released 2020-Oct-16 + +## 0.6.0-beta.1 + +Released 2020-Sep-15 + +## 0.5.0-beta.2 + +Released 2020-08-28 + +## 0.4.0-beta.2 + +Released 2020-07-24 + +* First beta release + +## 0.3.0-beta + +Released 2020-07-23 + +* Initial release diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs new file mode 100644 index 00000000000..4361f024e5c --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs @@ -0,0 +1,61 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Diagnostics.Tracing; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Prometheus.Implementation +{ + /// + /// EventSource events emitted from the project. + /// + [EventSource(Name = "OpenTelemetry-Exporter-Prometheus")] + internal class PrometheusExporterEventSource : EventSource + { + public static PrometheusExporterEventSource Log = new PrometheusExporterEventSource(); + + [NonEvent] + public void FailedExport(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + this.FailedExport(ex.ToInvariantString()); + } + } + + [NonEvent] + public void CanceledExport(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + this.CanceledExport(ex.ToInvariantString()); + } + } + + [Event(1, Message = "Failed to export activities: '{0}'", Level = EventLevel.Error)] + public void FailedExport(string exception) + { + this.WriteEvent(1, exception); + } + + [Event(2, Message = "Canceled to export activities: '{0}'", Level = EventLevel.Error)] + public void CanceledExport(string exception) + { + this.WriteEvent(2, exception); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs new file mode 100644 index 00000000000..a11c8e5131d --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs @@ -0,0 +1,276 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +#if NET452 +using OpenTelemetry.Internal; +#endif + +namespace OpenTelemetry.Exporter.Prometheus.Implementation +{ + internal class PrometheusMetricBuilder + { + public const string ContentType = "text/plain; version = 0.0.4"; + + private static readonly char[] FirstCharacterNameCharset = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', ':', + }; + + private static readonly char[] NameCharset = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '_', ':', + }; + + private static readonly char[] FirstCharacterLabelCharset = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', + }; + + private static readonly char[] LabelCharset = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '_', + }; + + private readonly ICollection values = new List(); + + private string name; + private string description; + private string type; + + public PrometheusMetricBuilder WithName(string name) + { + this.name = name; + return this; + } + + public PrometheusMetricBuilder WithDescription(string description) + { + this.description = description; + return this; + } + + public PrometheusMetricBuilder WithType(string type) + { + this.type = type; + return this; + } + + public PrometheusMetricValueBuilder AddValue() + { + var val = new PrometheusMetricValueBuilder(); + + this.values.Add(val); + + return val; + } + + public void Write(StreamWriter writer) + { + // https://prometheus.io/docs/instrumenting/exposition_formats/ + + if (string.IsNullOrEmpty(this.name)) + { + throw new InvalidOperationException("Metric name should not be empty"); + } + + this.name = GetSafeMetricName(this.name); + + if (!string.IsNullOrEmpty(this.description)) + { + // Lines with a # as the first non-whitespace character are comments. + // They are ignored unless the first token after # is either HELP or TYPE. + // Those lines are treated as follows: If the token is HELP, at least one + // more token is expected, which is the metric name. All remaining tokens + // are considered the docstring for that metric name. HELP lines may contain + // any sequence of UTF-8 characters (after the metric name), but the backslash + // 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"); + } + + if (!string.IsNullOrEmpty(this.type)) + { + // If the token is TYPE, exactly two more tokens are expected. The first is the + // metric name, and the second is either counter, gauge, histogram, summary, or + // untyped, defining the type for the metric of that name. Only one TYPE line + // may exist for a given metric name. The TYPE line for a metric name must appear + // 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"); + } + + // The remaining lines describe samples (one per line) using the following syntax (EBNF): + // metric_name [ + // "{" label_name "=" `"` label_value `"` { "," label_name "=" `"` label_value `"` } [ "," ] "}" + // ] value [ timestamp ] + // In the sample syntax: + + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture); + + 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); + + // label_value can be any sequence of UTF-8 characters, but the backslash + // (\, double-quote ("}, and line feed (\n) characters have to be escaped + // as \\, \", and \n, respectively. + + if (m.Labels.Count > 0) + { + writer.Write(@"{"); + writer.Write(string.Join(",", m.Labels.Select(x => GetLabelAndValue(x.Item1, x.Item2)))); + writer.Write(@"}"); + } + + // 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(" "); + + // 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); + + // 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"); + } + + static string GetLabelAndValue(string label, string value) + { + var safeKey = GetSafeLabelName(label); + var safeValue = GetSafeLabelValue(value); + return $"{safeKey}=\"{safeValue}\""; + } + } + + private static string GetSafeName(string name, char[] firstCharNameCharset, char[] charNameCharset) + { + // https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels + // + // Metric names and labels + // Every time series is uniquely identified by its metric name and a set of key-value pairs, also known as labels. + // The metric name specifies the general feature of a system that is measured (e.g. http_requests_total - the total number of HTTP requests received). It may contain ASCII letters and digits, as well as underscores and colons. It must match the regex [a-zA-Z_:][a-zA-Z0-9_:]*. + // Note: The colons are reserved for user defined recording rules. They should not be used by exporters or direct instrumentation. + // Labels enable Prometheus's dimensional data model: any given combination of labels for the same metric name identifies a particular dimensional instantiation of that metric (for example: all HTTP requests that used the method POST to the /api/tracks handler). The query language allows filtering and aggregation based on these dimensions. Changing any label value, including adding or removing a label, will create a new time series. + // Label names may contain ASCII letters, numbers, as well as underscores. They must match the regex [a-zA-Z_][a-zA-Z0-9_]*. Label names beginning with __ are reserved for internal use. + // Label values may contain any Unicode characters. + + var sb = new StringBuilder(); + var firstChar = name[0]; + + sb.Append(firstCharNameCharset.Contains(firstChar) + ? firstChar + : GetSafeChar(char.ToLowerInvariant(firstChar), firstCharNameCharset)); + + for (var i = 1; i < name.Length; ++i) + { + sb.Append(GetSafeChar(name[i], charNameCharset)); + } + + return sb.ToString(); + + static char GetSafeChar(char c, char[] charset) => charset.Contains(c) ? c : '_'; + } + + private static string GetSafeMetricName(string name) => GetSafeName(name, FirstCharacterNameCharset, NameCharset); + + private static string GetSafeLabelName(string name) => GetSafeName(name, FirstCharacterLabelCharset, LabelCharset); + + private static string GetSafeLabelValue(string value) + { + // label_value can be any sequence of UTF-8 characters, but the backslash + // (\), double-quote ("), and line feed (\n) characters have to be escaped + // as \\, \", and \n, respectively. + + var result = value.Replace("\\", "\\\\"); + result = result.Replace("\n", "\\n"); + result = result.Replace("\"", "\\\""); + + return result; + } + + private static string GetSafeMetricDescription(string description) + { + // HELP lines may contain any sequence of UTF-8 characters(after the metric name), but the backslash + // and the line feed characters have to be escaped as \\ and \n, respectively.Only one HELP line may + // exist for any given metric name. + var result = description.Replace(@"\", @"\\"); + result = result.Replace("\n", @"\n"); + + return result; + } + + internal class PrometheusMetricValueBuilder + { + public readonly ICollection> Labels = new List>(); + public double Value; + public string Name; + + public PrometheusMetricValueBuilder WithLabel(string name, string value) + { + this.Labels.Add(new Tuple(name, value)); + return this; + } + + public PrometheusMetricValueBuilder WithValue(long metricValue) + { + this.Value = metricValue; + return this; + } + + public PrometheusMetricValueBuilder WithValue(double metricValue) + { + this.Value = metricValue; + return this; + } + + public PrometheusMetricValueBuilder WithName(string name) + { + this.Name = name; + return this; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs new file mode 100644 index 00000000000..04bd0452078 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs @@ -0,0 +1,49 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Exporter; + +namespace OpenTelemetry.Metrics +{ + public static class MeterProviderBuilderExtensions + { + /// + /// Adds Console exporter to the TracerProvider. + /// + /// 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) + { + throw new ArgumentNullException(nameof(builder)); + } + + var options = new PrometheusExporterOptions(); + configure?.Invoke(options); + var exporter = new PrometheusExporter(options); + var pullMetricProcessor = new PullMetricProcessor(exporter); + exporter.MakePullRequest = pullMetricProcessor.PullRequest; + + var metricsHttpServer = new PrometheusExporterMetricsHttpServer(exporter); + metricsHttpServer.Start(); + return builder.AddMetricProcessor(pullMetricProcessor); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj b/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj new file mode 100644 index 00000000000..6ee2ac3fa22 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0;net461 + Console exporter for OpenTelemetry .NET + $(PackageTags);prometheus;metrics + + + + $(NoWarn),1591 + + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs new file mode 100644 index 00000000000..ea51da4cf56 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs @@ -0,0 +1,50 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; + +namespace OpenTelemetry.Exporter +{ + /// + /// Exporter of OpenTelemetry metrics to Prometheus. + /// + public class PrometheusExporter : BaseExporter + { + internal readonly PrometheusExporterOptions Options; + internal Batch Batch; + + /// + /// Initializes a new instance of the class. + /// + /// Options for the exporter. + public PrometheusExporter(PrometheusExporterOptions options) + { + this.Options = options; + } + + internal Action MakePullRequest { get; set; } + + public override ExportResult Export(in Batch batch) + { + this.Batch = batch; + return ExportResult.Success; + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs new file mode 100644 index 00000000000..79c4f960bc2 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs @@ -0,0 +1,146 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.IO; +using System.Text; +using OpenTelemetry.Exporter.Prometheus.Implementation; +using OpenTelemetry.Metrics; + +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 PrometheusSummaryType = "summary"; + private const string PrometheusSummarySumPostFix = "_sum"; + private const string PrometheusSummaryCountPostFix = "_count"; + private const string PrometheusSummaryQuantileLabelName = "quantile"; + private const string PrometheusSummaryQuantileLabelValueForMin = "0"; + private const string PrometheusSummaryQuantileLabelValueForMax = "1"; + + /// + /// Serialize to Prometheus Format. + /// + /// Prometheus Exporter. + /// StreamWriter to write to. + public static void WriteMetricsCollection(this PrometheusExporter exporter, StreamWriter writer) + { + foreach (var metricItem in exporter.Batch) + { + foreach (var metric in metricItem.Metrics) + { + var builder = new PrometheusMetricBuilder() + .WithName(metric.Name) + .WithDescription(metric.Name); + + if (metric is ISumMetric sumMetric) + { + if (sumMetric.Sum.Value is double doubleSum) + { + WriteSum(writer, builder, metric.Attributes, doubleSum); + } + else if (sumMetric.Sum.Value is long longSum) + { + WriteSum(writer, builder, metric.Attributes, longSum); + } + + } + } + } + } + + /// + /// 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 WriteSum(StreamWriter writer, PrometheusMetricBuilder builder, IEnumerable> labels, double doubleValue) + { + builder = builder.WithType(PrometheusCounterType); + + var metricValueBuilder = builder.AddValue(); + metricValueBuilder = metricValueBuilder.WithValue(doubleValue); + + foreach (var label in labels) + { + metricValueBuilder.WithLabel(label.Key, label.Value.ToString()); + } + + builder.Write(writer); + } + + private static void WriteSummary( + StreamWriter writer, + PrometheusMetricBuilder builder, + IEnumerable> labels, + string metricName, + double sum, + long count, + double min, + double max) + { + builder = builder.WithType(PrometheusSummaryType); + + foreach (var label in labels) + { + /* For Summary we emit one row for Sum, Count, Min, Max. + Min,Max exports as quantile 0 and 1. + In future, when OpenTelemetry implements more aggregation + algorithms, this section will need to be revisited. + Sample output: + MyMeasure_sum{dim1="value1"} 750 1587013352982 + MyMeasure_count{dim1="value1"} 5 1587013352982 + MyMeasure{dim1="value2",quantile="0"} 150 1587013352982 + MyMeasure{dim1="value2",quantile="1"} 150 1587013352982 + */ + builder.AddValue() + .WithName(metricName + PrometheusSummarySumPostFix) + .WithLabel(label.Key, label.Value) + .WithValue(sum); + builder.AddValue() + .WithName(metricName + PrometheusSummaryCountPostFix) + .WithLabel(label.Key, label.Value) + .WithValue(count); + builder.AddValue() + .WithName(metricName) + .WithLabel(label.Key, label.Value) + .WithLabel(PrometheusSummaryQuantileLabelName, PrometheusSummaryQuantileLabelValueForMin) + .WithValue(min); + builder.AddValue() + .WithName(metricName) + .WithLabel(label.Key, label.Value) + .WithLabel(PrometheusSummaryQuantileLabelName, PrometheusSummaryQuantileLabelValueForMax) + .WithValue(max); + } + + builder.Write(writer); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs new file mode 100644 index 00000000000..78a907111e7 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs @@ -0,0 +1,145 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Exporter.Prometheus.Implementation; + +namespace OpenTelemetry.Exporter +{ + /// + /// A HTTP listener used to expose Prometheus metrics. + /// + public class PrometheusExporterMetricsHttpServer : IDisposable + { + private readonly PrometheusExporter exporter; + private readonly HttpListener httpListener = new HttpListener(); + private readonly object syncObject = new object(); + + private CancellationTokenSource tokenSource; + private Task workerThread; + + /// + /// Initializes a new instance of the class. + /// + /// The instance. + public PrometheusExporterMetricsHttpServer(PrometheusExporter exporter) + { + this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter)); + this.httpListener.Prefixes.Add(exporter.Options.Url); + } + + /// + /// Start exporter. + /// + /// An optional that can be used to stop the htto server. + public void Start(CancellationToken token = default) + { + lock (this.syncObject) + { + if (this.tokenSource != null) + { + return; + } + + // link the passed in token if not null + this.tokenSource = token == default ? + new CancellationTokenSource() : + CancellationTokenSource.CreateLinkedTokenSource(token); + + this.workerThread = Task.Factory.StartNew(this.WorkerThread, default, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + } + + /// + /// Stop exporter. + /// + public void Stop() + { + lock (this.syncObject) + { + if (this.tokenSource == null) + { + return; + } + + this.tokenSource.Cancel(); + this.workerThread.Wait(); + this.tokenSource = null; + } + } + + /// + 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) + { + this.Stop(); + this.httpListener.Close(); + } + } + + private void WorkerThread() + { + this.httpListener.Start(); + + try + { + while (!this.tokenSource.IsCancellationRequested) + { + var ctxTask = this.httpListener.GetContextAsync(); + ctxTask.Wait(this.tokenSource.Token); + + var ctx = ctxTask.Result; + + ctx.Response.StatusCode = 200; + ctx.Response.ContentType = PrometheusMetricBuilder.ContentType; + + using var output = ctx.Response.OutputStream; + using var writer = new StreamWriter(output); + this.exporter.MakePullRequest(); + this.exporter.WriteMetricsCollection(writer); + } + } + catch (OperationCanceledException ex) + { + PrometheusExporterEventSource.Log.CanceledExport(ex); + } + catch (Exception ex) + { + PrometheusExporterEventSource.Log.FailedExport(ex); + } + finally + { + this.httpListener.Stop(); + this.httpListener.Close(); + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs new file mode 100644 index 00000000000..10cb608ee31 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMiddleware.cs @@ -0,0 +1,60 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Threading.Tasks; + +#if NETSTANDARD2_0 +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 new file mode 100644 index 00000000000..ce4aae423cf --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs @@ -0,0 +1,29 @@ +// +// 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. +// + +namespace OpenTelemetry.Exporter +{ + /// + /// Options to run prometheus exporter. + /// + public class PrometheusExporterOptions + { + /// + /// Gets or sets the port to listen to. Typically it ends with /metrics like http://localhost:9184/metrics/. + /// + public string Url { get; set; } = "http://localhost:9184/metrics/"; + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs new file mode 100644 index 00000000000..0b38d473419 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusRouteBuilderExtensions.cs @@ -0,0 +1,46 @@ +// +// 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 new file mode 100644 index 00000000000..ed79f8d2fa8 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus/README.md @@ -0,0 +1,29 @@ +# Prometheus Exporter for OpenTelemetry .NET + +[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Exporter.Prometheus.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus) +[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Exporter.Prometheus.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus) + +## Prerequisite + +* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/) + +## Installation + +```shell +dotnet add package OpenTelemetry.Exporter.Prometheus +``` + +## Configuration + +You can configure the `PrometheusExporter` by following the directions below: + +* `Url`: The url to listen to. Typically it ends with `/metrics` like `http://localhost:9184/metrics/`. + +See +[`TestPrometheusExporter.cs`](../../examples/Console/TestPrometheusExporter.cs) +for example use. + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) +* [Prometheus](https://prometheus.io) From 7bd5e45ee8010349fe82a33a2424b6133f627fb9 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 12:32:59 -0700 Subject: [PATCH 2/7] Add prometheus example --- docs/metrics/getting-started/Program.cs | 5 +-- examples/Console/Examples.Console.csproj | 1 + examples/Console/Program.cs | 7 +--- examples/Console/TestPrometheusExporter.cs | 42 ++++++++++++++++--- .../MeterProviderBuilderExtensions.cs | 2 +- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/docs/metrics/getting-started/Program.cs b/docs/metrics/getting-started/Program.cs index d7a62fde903..e8aa9c63267 100644 --- a/docs/metrics/getting-started/Program.cs +++ b/docs/metrics/getting-started/Program.cs @@ -31,8 +31,7 @@ public static async Task Main(string[] args) { using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddSource("TestMeter") - // .AddConsoleExporter() - .AddPrometheusExporter() + .AddConsoleExporter() .Build(); using var token = new CancellationTokenSource(); @@ -49,7 +48,7 @@ public static async Task Main(string[] args) }); writeMetricTask.Start(); - token.CancelAfter(1000000); + token.CancelAfter(10000); await writeMetricTask; } } diff --git a/examples/Console/Examples.Console.csproj b/examples/Console/Examples.Console.csproj index 33a1f6a4191..05aea2c95a6 100644 --- a/examples/Console/Examples.Console.csproj +++ b/examples/Console/Examples.Console.csproj @@ -36,5 +36,6 @@ + diff --git a/examples/Console/Program.cs b/examples/Console/Program.cs index 3b3b061869f..324e25aa940 100644 --- a/examples/Console/Program.cs +++ b/examples/Console/Program.cs @@ -46,7 +46,7 @@ public static void Main(string[] args) .MapResult( (JaegerOptions options) => TestJaegerExporter.Run(options.Host, options.Port), (ZipkinOptions options) => TestZipkinExporter.Run(options.Uri), - (PrometheusOptions options) => TestPrometheusExporter.Run(options.Port, options.PushIntervalInSecs, options.DurationInMins), + (PrometheusOptions options) => TestPrometheusExporter.Run(options.Port, options.DurationInMins), (MetricsOptions options) => TestMetrics.Run(options), (GrpcNetClientOptions options) => TestGrpcNetClient.Run(), (HttpClientOptions options) => TestHttpClient.Run(), @@ -83,13 +83,10 @@ internal class ZipkinOptions [Verb("prometheus", HelpText = "Specify the options required to test Prometheus")] internal class PrometheusOptions { - [Option('i', "pushIntervalInSecs", Default = 15, HelpText = "The interval at which Push controller pushes metrics.", Required = false)] - public int PushIntervalInSecs { get; set; } - [Option('p', "port", Default = 9184, HelpText = "The port to expose metrics. The endpoint will be http://localhost:port/metrics (This is the port from which your Prometheus server scraps metrics from.)", Required = false)] public int Port { get; set; } - [Option('d', "duration", Default = 2, HelpText = "Total duration in minutes to run the demo. Run atleast for a min to see metrics flowing.", Required = false)] + [Option('d', "duration", Default = 2, HelpText = "Total duration in minutes to run the demo.", Required = false)] public int DurationInMins { get; set; } } diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs index 098846b4793..34ef6efcf08 100644 --- a/examples/Console/TestPrometheusExporter.cs +++ b/examples/Console/TestPrometheusExporter.cs @@ -17,18 +17,22 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Metrics; +using System.Threading; using System.Threading.Tasks; using OpenTelemetry; +using OpenTelemetry.Metrics; using OpenTelemetry.Trace; namespace Examples.Console { internal class TestPrometheusExporter { - internal static object Run(int port, int pushIntervalInSecs, int totalDurationInMins) - { - System.Console.WriteLine($"OpenTelemetry Prometheus Exporter is making metrics available at http://localhost:{port}/metrics/"); + private static readonly Meter MyMeter = new Meter("TestMeter", "0.0.1"); + private static readonly Counter Counter = MyMeter.CreateCounter("counter"); + internal static object Run(int port, int totalDurationInMins) + { /* Following is sample prometheus.yml config. Adjust port,interval as needed. @@ -42,9 +46,37 @@ internal static object Run(int port, int pushIntervalInSecs, int totalDurationIn static_configs: - targets: ['localhost:9184'] */ - System.Console.WriteLine("Press Enter key to exit."); - System.Console.ReadLine(); + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddSource("TestMeter") + .AddPrometheusExporter(opt => opt.Url = $"http://localhost:{port}/metrics/") + .Build(); + + using var token = new CancellationTokenSource(); + Task writeMetricTask = new Task(() => + { + while (!token.IsCancellationRequested) + { + Counter.Add( + 10, + new KeyValuePair("tag1", "value1"), + new KeyValuePair("tag2", "value2")); + Counter.Add( + 100, + new KeyValuePair("tag1", "anothervalue"), + new KeyValuePair("tag2", "somethingelse")); + Task.Delay(10).Wait(); + } + }); + writeMetricTask.Start(); + + token.CancelAfter(totalDurationInMins * 60 * 1000); + + System.Console.WriteLine($"OpenTelemetry Prometheus Exporter is making metrics available at http://localhost:{port}/metrics/"); + System.Console.WriteLine($"Press Enter key to exit now or will exit automatically after {totalDurationInMins} minutes."); + System.Console.ReadLine(); + token.Cancel(); + System.Console.WriteLine("Exiting..."); return null; } } diff --git a/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs index 04bd0452078..b7e307ada2b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/MeterProviderBuilderExtensions.cs @@ -38,7 +38,7 @@ public static MeterProviderBuilder AddPrometheusExporter(this MeterProviderBuild var options = new PrometheusExporterOptions(); configure?.Invoke(options); var exporter = new PrometheusExporter(options); - var pullMetricProcessor = new PullMetricProcessor(exporter); + var pullMetricProcessor = new PullMetricProcessor(exporter, false); exporter.MakePullRequest = pullMetricProcessor.PullRequest; var metricsHttpServer = new PrometheusExporterMetricsHttpServer(exporter); From 946fa7f1c9f688d452c925f1d34b1b60a862026b Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 12:36:51 -0700 Subject: [PATCH 3/7] remove unwanted file --- .../CHANGELOG.md | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md diff --git a/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md deleted file mode 100644 index 5084c833ad1..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ -# Changelog - -## Unreleased - -## 0.7.0-beta.1 - -Released 2020-Oct-16 - -## 0.6.0-beta.1 - -Released 2020-Sep-15 - -## 0.5.0-beta.2 - -Released 2020-08-28 - -## 0.4.0-beta.2 - -Released 2020-07-24 - -* First beta release - -## 0.3.0-beta - -Released 2020-07-23 - -* Initial release From 3a8267d7e0e555520026393f11ed9aee593d4bf9 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 14:19:48 -0700 Subject: [PATCH 4/7] pr comment --- docs/metrics/getting-started/getting-started.csproj | 1 - examples/Console/Examples.Console.csproj | 2 +- .../Implementation/PrometheusExporterEventSource.cs | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/metrics/getting-started/getting-started.csproj b/docs/metrics/getting-started/getting-started.csproj index 236ed576ec3..9f5b6b79bc3 100644 --- a/docs/metrics/getting-started/getting-started.csproj +++ b/docs/metrics/getting-started/getting-started.csproj @@ -2,6 +2,5 @@ - diff --git a/examples/Console/Examples.Console.csproj b/examples/Console/Examples.Console.csproj index 05aea2c95a6..84ecf4f5b71 100644 --- a/examples/Console/Examples.Console.csproj +++ b/examples/Console/Examples.Console.csproj @@ -36,6 +36,6 @@ - + diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs index 4361f024e5c..762a69e1b38 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs @@ -46,13 +46,13 @@ public void CanceledExport(Exception ex) } } - [Event(1, Message = "Failed to export activities: '{0}'", Level = EventLevel.Error)] + [Event(1, Message = "Failed to export metrics: '{0}'", Level = EventLevel.Error)] public void FailedExport(string exception) { this.WriteEvent(1, exception); } - [Event(2, Message = "Canceled to export activities: '{0}'", Level = EventLevel.Error)] + [Event(2, Message = "Canceled to export metrics: '{0}'", Level = EventLevel.Error)] public void CanceledExport(string exception) { this.WriteEvent(2, exception); From fbcb7d8b0cf5c09eb67cf0bc0eb02ffbd0a60151 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 14:30:54 -0700 Subject: [PATCH 5/7] supress promtheus --- .../PrometheusExporterMetricsHttpServer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs index 78a907111e7..68967940ab4 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs @@ -111,6 +111,7 @@ private void WorkerThread() try { + using var scope = SuppressInstrumentationScope.Begin(); while (!this.tokenSource.IsCancellationRequested) { var ctxTask = this.httpListener.GetContextAsync(); From c31273b4964283149907a5cc6cd9d630fe59bdfb Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 14:32:54 -0700 Subject: [PATCH 6/7] try catch --- .../PrometheusExporterEventSource.cs | 15 +++++++++++++++ .../PrometheusExporterMetricsHttpServer.cs | 11 +++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs index 762a69e1b38..f533884005d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs @@ -37,6 +37,15 @@ public void FailedExport(Exception ex) } } + [NonEvent] + public void FailedShutdown(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + this.FailedShutdown(ex.ToInvariantString()); + } + } + [NonEvent] public void CanceledExport(Exception ex) { @@ -57,5 +66,11 @@ public void CanceledExport(string exception) { this.WriteEvent(2, exception); } + + [Event(3, Message = "Failed to shutdown Metrics server '{0}'", Level = EventLevel.Error)] + public void FailedShutdown(string exception) + { + this.WriteEvent(3, exception); + } } } diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs index 68967940ab4..73266aba29a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMetricsHttpServer.cs @@ -138,8 +138,15 @@ private void WorkerThread() } finally { - this.httpListener.Stop(); - this.httpListener.Close(); + try + { + this.httpListener.Stop(); + this.httpListener.Close(); + } + catch (Exception exFromFinally) + { + PrometheusExporterEventSource.Log.FailedShutdown(exFromFinally); + } } } } From 7eef7e762a7b9c8f644e2b0df78970b2d5fcc729 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 16 Jul 2021 14:36:45 -0700 Subject: [PATCH 7/7] remove space --- .../PrometheusExporterExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs index 79c4f960bc2..675acb6c55e 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs @@ -60,7 +60,6 @@ public static void WriteMetricsCollection(this PrometheusExporter exporter, Stre { WriteSum(writer, builder, metric.Attributes, longSum); } - } } }