opentelemetry-dotnet/src/OpenTelemetry.Exporter.Prom.../PrometheusExporterMetricsHt...

154 lines
5.2 KiB
C#

// <copyright file="PrometheusExporterMetricsHttpServer.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;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using OpenTelemetry.Exporter.Prometheus.Implementation;
namespace OpenTelemetry.Exporter
{
/// <summary>
/// A HTTP listener used to expose Prometheus metrics.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="PrometheusExporterMetricsHttpServer"/> class.
/// </summary>
/// <param name="exporter">The <see cref="PrometheusExporter"/> instance.</param>
public PrometheusExporterMetricsHttpServer(PrometheusExporter exporter)
{
this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
this.httpListener.Prefixes.Add(exporter.Options.Url);
}
/// <summary>
/// Start exporter.
/// </summary>
/// <param name="token">An optional <see cref="CancellationToken"/> that can be used to stop the htto server.</param>
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);
}
}
/// <summary>
/// Stop exporter.
/// </summary>
public void Stop()
{
lock (this.syncObject)
{
if (this.tokenSource == null)
{
return;
}
this.tokenSource.Cancel();
this.workerThread.Wait();
this.tokenSource = null;
}
}
/// <inheritdoc/>
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)
{
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);
}
}
}
}
}