// // 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 { using var scope = SuppressInstrumentationScope.Begin(); 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 { try { this.httpListener.Stop(); this.httpListener.Close(); } catch (Exception exFromFinally) { PrometheusExporterEventSource.Log.FailedShutdown(exFromFinally); } } } } }