Issue/2292 otlp http binary protobuf trace exporter (#2316)
This commit is contained in:
parent
2512aa1709
commit
1aa4da2098
|
|
@ -15,6 +15,7 @@
|
|||
// </copyright>
|
||||
|
||||
using CommandLine;
|
||||
using OpenTelemetry.Exporter;
|
||||
|
||||
namespace Examples.Console
|
||||
{
|
||||
|
|
@ -32,7 +33,7 @@ namespace Examples.Console
|
|||
/// dotnet run -p Examples.Console.csproj zipkin -u http://localhost:9411/api/v2/spans
|
||||
/// dotnet run -p Examples.Console.csproj jaeger -h localhost -p 6831
|
||||
/// dotnet run -p Examples.Console.csproj prometheus -p 9184 -d 2
|
||||
/// dotnet run -p Examples.Console.csproj otlp -e "http://localhost:4317"
|
||||
/// dotnet run -p Examples.Console.csproj otlp -e "http://localhost:4317" -p "grpc"
|
||||
/// dotnet run -p Examples.Console.csproj zpages
|
||||
/// dotnet run -p Examples.Console.csproj metrics --help
|
||||
///
|
||||
|
|
@ -55,7 +56,7 @@ namespace Examples.Console
|
|||
(ConsoleOptions options) => TestConsoleExporter.Run(options),
|
||||
(OpenTelemetryShimOptions options) => TestOTelShimWithConsoleExporter.Run(options),
|
||||
(OpenTracingShimOptions options) => TestOpenTracingShim.Run(options),
|
||||
(OtlpOptions options) => TestOtlpExporter.Run(options.Endpoint),
|
||||
(OtlpOptions options) => TestOtlpExporter.Run(options.Endpoint, options.Protocol),
|
||||
(InMemoryOptions options) => TestInMemoryExporter.Run(options),
|
||||
errs => 1);
|
||||
}
|
||||
|
|
@ -163,6 +164,9 @@ namespace Examples.Console
|
|||
{
|
||||
[Option('e', "endpoint", HelpText = "Target to which the exporter is going to send traces or metrics", Default = "http://localhost:4317")]
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
[Option('p', "protocol", HelpText = "Transport protocol used by exporter. Supported values: grpc and http/protobuf.", Default = "grpc")]
|
||||
public string Protocol { get; set; }
|
||||
}
|
||||
|
||||
[Verb("inmemory", HelpText = "Specify the options required to test InMemory Exporter")]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
using System;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ namespace Examples.Console
|
|||
{
|
||||
internal static class TestOtlpExporter
|
||||
{
|
||||
internal static object Run(string endpoint)
|
||||
internal static object Run(string endpoint, string protocol)
|
||||
{
|
||||
/*
|
||||
* Prerequisite to run this example:
|
||||
|
|
@ -49,22 +50,33 @@ namespace Examples.Console
|
|||
* For more information about the OpenTelemetry Collector go to https://github.com/open-telemetry/opentelemetry-collector
|
||||
*
|
||||
*/
|
||||
return RunWithActivitySource(endpoint);
|
||||
return RunWithActivitySource(endpoint, protocol);
|
||||
}
|
||||
|
||||
private static object RunWithActivitySource(string endpoint)
|
||||
private static object RunWithActivitySource(string endpoint, string protocol)
|
||||
{
|
||||
// Adding the OtlpExporter creates a GrpcChannel.
|
||||
// This switch must be set before creating a GrpcChannel/HttpClient when calling an insecure gRPC service.
|
||||
// See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client
|
||||
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
|
||||
|
||||
var otlpExportProtocol = ToOtlpExportProtocol(protocol);
|
||||
if (!otlpExportProtocol.HasValue)
|
||||
{
|
||||
System.Console.WriteLine($"Export protocol {protocol} is not supported. Default protocol 'grpc' will be used.");
|
||||
otlpExportProtocol = OtlpExportProtocol.Grpc;
|
||||
}
|
||||
|
||||
// Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient"
|
||||
// and use OTLP exporter.
|
||||
using var openTelemetry = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource("Samples.SampleClient", "Samples.SampleServer")
|
||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("otlp-test"))
|
||||
.AddOtlpExporter(opt => opt.Endpoint = new Uri(endpoint))
|
||||
.AddOtlpExporter(opt =>
|
||||
{
|
||||
opt.Endpoint = new Uri(endpoint);
|
||||
opt.Protocol = otlpExportProtocol.Value;
|
||||
})
|
||||
.Build();
|
||||
|
||||
// The above line is required only in Applications
|
||||
|
|
@ -81,5 +93,13 @@ namespace Examples.Console
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static OtlpExportProtocol? ToOtlpExportProtocol(string protocol) =>
|
||||
protocol.Trim().ToLower() switch
|
||||
{
|
||||
"grpc" => OtlpExportProtocol.Grpc,
|
||||
"http/protobuf" => OtlpExportProtocol.HttpProtobuf,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@ OpenTelemetry.Exporter.OtlpExporterOptions.Headers.set -> void
|
|||
OpenTelemetry.Exporter.OtlpExporterOptions.OtlpExporterOptions() -> void
|
||||
OpenTelemetry.Exporter.OtlpExporterOptions.TimeoutMilliseconds.get -> int
|
||||
OpenTelemetry.Exporter.OtlpExporterOptions.TimeoutMilliseconds.set -> void
|
||||
OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.get -> OpenTelemetry.Exporter.ExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.set -> void
|
||||
OpenTelemetry.Exporter.OtlpTraceExporter
|
||||
OpenTelemetry.Exporter.OtlpTraceExporter.OtlpTraceExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void
|
||||
OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions
|
||||
override OpenTelemetry.Exporter.OtlpTraceExporter.Export(in OpenTelemetry.Batch<System.Diagnostics.Activity> activityBatch) -> OpenTelemetry.ExportResult
|
||||
override OpenTelemetry.Exporter.OtlpTraceExporter.OnShutdown(int timeoutMilliseconds) -> bool
|
||||
static OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<OpenTelemetry.Exporter.OtlpExporterOptions> configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol.Grpc = 0 -> OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf = 1 -> OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.get -> OpenTelemetry.Exporter.ExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.set -> void
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol.Grpc = 0 -> OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf = 1 -> OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.get -> OpenTelemetry.Exporter.ExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExporterOptions.Protocol.set -> void
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol.Grpc = 0 -> OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf = 1 -> OpenTelemetry.Exporter.OtlpExportProtocol
|
||||
|
|
@ -5,6 +5,10 @@
|
|||
* `MeterProviderBuilder` extension methods now support `OtlpExporterOptions`
|
||||
bound to `IConfiguration` when using OpenTelemetry.Extensions.Hosting
|
||||
([#2413](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2413))
|
||||
* Extended `OtlpExporterOptions` by `Protocol` property. The property can be
|
||||
overridden by `OTEL_EXPORTER_OTLP_PROTOCOL` environmental variable (grpc or http/protobuf).
|
||||
Implemented OTLP over HTTP binary protobuf trace exporter.
|
||||
([#2292](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2292))
|
||||
|
||||
## 1.2.0-alpha4
|
||||
|
||||
|
|
@ -18,7 +22,7 @@ Released 2021-Sep-13
|
|||
`BatchExportActivityProcessorOptions` which supports field value overriding
|
||||
using `OTEL_BSP_SCHEDULE_DELAY`, `OTEL_BSP_EXPORT_TIMEOUT`,
|
||||
`OTEL_BSP_MAX_QUEUE_SIZE`, `OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
|
||||
envionmental variables as defined in the
|
||||
environmental variables as defined in the
|
||||
[specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.5.0/specification/sdk-environment-variables.md#batch-span-processor).
|
||||
([#2219](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2219))
|
||||
|
||||
|
|
@ -28,7 +32,7 @@ Released 2021-Aug-24
|
|||
|
||||
* The `OtlpExporterOptions` defaults can be overridden using
|
||||
`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS` and `OTEL_EXPORTER_OTLP_TIMEOUT`
|
||||
envionmental variables as defined in the
|
||||
environmental variables as defined in the
|
||||
[specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md).
|
||||
([#2188](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2188))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// <copyright file="BaseOtlpExporter.cs" company="OpenTelemetry Authors">
|
||||
// <copyright file="BaseOtlpGrpcExportClient.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
@ -15,54 +15,46 @@
|
|||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol;
|
||||
#if NETSTANDARD2_1
|
||||
#if NETSTANDARD2_1 || NET5_0_OR_GREATER
|
||||
using Grpc.Net.Client;
|
||||
#endif
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OtlpResource = Opentelemetry.Proto.Resource.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements exporter that exports telemetry objects over OTLP/gRPC.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of telemetry object to be exported.</typeparam>
|
||||
public abstract class BaseOtlpExporter<T> : BaseExporter<T>
|
||||
where T : class
|
||||
/// <summary>Base class for sending OTLP export request over gRPC.</summary>
|
||||
/// <typeparam name="TRequest">Type of export request.</typeparam>
|
||||
internal abstract class BaseOtlpGrpcExportClient<TRequest> : IExportClient<TRequest>
|
||||
{
|
||||
private OtlpResource.Resource processResource;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseOtlpExporter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="OtlpExporterOptions"/> for configuring the exporter.</param>
|
||||
protected BaseOtlpExporter(OtlpExporterOptions options)
|
||||
protected BaseOtlpGrpcExportClient(OtlpExporterOptions options)
|
||||
{
|
||||
this.Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
this.Headers = options.GetMetadataFromHeaders();
|
||||
if (this.Options.TimeoutMilliseconds <= 0)
|
||||
|
||||
if (options.TimeoutMilliseconds <= 0)
|
||||
{
|
||||
throw new ArgumentException("Timeout value provided is not a positive number.", nameof(this.Options.TimeoutMilliseconds));
|
||||
throw new ArgumentException("Timeout value provided is not a positive number.", nameof(options.TimeoutMilliseconds));
|
||||
}
|
||||
|
||||
this.Headers = options.GetMetadataFromHeaders();
|
||||
}
|
||||
|
||||
internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource();
|
||||
internal OtlpExporterOptions Options { get; }
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if NETSTANDARD2_1 || NET5_0_OR_GREATER
|
||||
internal GrpcChannel Channel { get; set; }
|
||||
#else
|
||||
internal Channel Channel { get; set; }
|
||||
#endif
|
||||
|
||||
internal OtlpExporterOptions Options { get; }
|
||||
|
||||
internal Metadata Headers { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnShutdown(int timeoutMilliseconds)
|
||||
public abstract bool SendExportRequest(TRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool Shutdown(int timeoutMilliseconds)
|
||||
{
|
||||
if (this.Channel == null)
|
||||
{
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
// <copyright file="BaseOtlpHttpExportClient.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.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
|
||||
{
|
||||
/// <summary>Base class for sending OTLP export request over HTTP.</summary>
|
||||
/// <typeparam name="TRequest">Type of export request.</typeparam>
|
||||
internal abstract class BaseOtlpHttpExportClient<TRequest> : IExportClient<TRequest>
|
||||
{
|
||||
protected BaseOtlpHttpExportClient(OtlpExporterOptions options, HttpClient httpClient = null)
|
||||
{
|
||||
this.Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (this.Options.TimeoutMilliseconds <= 0)
|
||||
{
|
||||
throw new ArgumentException("Timeout value provided is not a positive number.", nameof(this.Options.TimeoutMilliseconds));
|
||||
}
|
||||
|
||||
this.Headers = options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v));
|
||||
|
||||
this.HttpClient = httpClient ?? new HttpClient { Timeout = TimeSpan.FromMilliseconds(this.Options.TimeoutMilliseconds) };
|
||||
}
|
||||
|
||||
internal OtlpExporterOptions Options { get; }
|
||||
|
||||
internal HttpClient HttpClient { get; }
|
||||
|
||||
internal IReadOnlyDictionary<string, string> Headers { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SendExportRequest(TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var httpRequest = this.CreateHttpRequest(request);
|
||||
|
||||
using var httpResponse = this.SendHttpRequest(httpRequest, cancellationToken);
|
||||
|
||||
httpResponse?.EnsureSuccessStatusCode();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(ex);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Shutdown(int timeoutMilliseconds)
|
||||
{
|
||||
this.HttpClient.CancelPendingRequests();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract HttpRequestMessage CreateHttpRequest(TRequest request);
|
||||
|
||||
protected HttpResponseMessage SendHttpRequest(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
return this.HttpClient.Send(request, cancellationToken);
|
||||
#else
|
||||
return this.HttpClient.SendAsync(request, cancellationToken).GetAwaiter().GetResult();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// <copyright file="IExportClient.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.Threading;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
|
||||
{
|
||||
/// <summary>Export client interface.</summary>
|
||||
/// <typeparam name="TRequest">Type of export request.</typeparam>
|
||||
internal interface IExportClient<in TRequest>
|
||||
{
|
||||
/// <summary>
|
||||
/// Method for sending export request to the server.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to send to the server.</param>
|
||||
/// <param name="cancellationToken">An optional token for canceling the call.</param>
|
||||
/// <returns>True if the request has been sent successfully, otherwise false.</returns>
|
||||
bool SendExportRequest(TRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Method for shutting down the export client.
|
||||
/// </summary>
|
||||
/// <param name="timeoutMilliseconds">
|
||||
/// The number of milliseconds to wait, or <c>Timeout.Infinite</c> to
|
||||
/// wait indefinitely.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns <c>true</c> if shutdown succeeded; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
bool Shutdown(int timeoutMilliseconds);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// <copyright file="OtlpGrpcMetricsExportClient.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.Threading;
|
||||
using Grpc.Core;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
|
||||
{
|
||||
/// <summary>Class for sending OTLP metrics export request over gRPC.</summary>
|
||||
internal sealed class OtlpGrpcMetricsExportClient : BaseOtlpGrpcExportClient<OtlpCollector.ExportMetricsServiceRequest>
|
||||
{
|
||||
private readonly OtlpCollector.MetricsService.IMetricsServiceClient metricsClient;
|
||||
|
||||
public OtlpGrpcMetricsExportClient(OtlpExporterOptions options, OtlpCollector.MetricsService.IMetricsServiceClient metricsServiceClient = null)
|
||||
: base(options)
|
||||
{
|
||||
if (metricsServiceClient != null)
|
||||
{
|
||||
this.metricsClient = metricsServiceClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Channel = options.CreateChannel();
|
||||
this.metricsClient = new OtlpCollector.MetricsService.MetricsServiceClient(this.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool SendExportRequest(OtlpCollector.ExportMetricsServiceRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var deadline = DateTime.UtcNow.AddMilliseconds(this.Options.TimeoutMilliseconds);
|
||||
|
||||
try
|
||||
{
|
||||
this.metricsClient.Export(request, headers: this.Headers, deadline: deadline);
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(ex);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// <copyright file="OtlpGrpcTraceExportClient.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.Threading;
|
||||
using Grpc.Core;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
|
||||
{
|
||||
/// <summary>Class for sending OTLP trace export request over gRPC.</summary>
|
||||
internal sealed class OtlpGrpcTraceExportClient : BaseOtlpGrpcExportClient<OtlpCollector.ExportTraceServiceRequest>
|
||||
{
|
||||
private readonly OtlpCollector.TraceService.ITraceServiceClient traceClient;
|
||||
|
||||
public OtlpGrpcTraceExportClient(OtlpExporterOptions options, OtlpCollector.TraceService.ITraceServiceClient traceServiceClient = null)
|
||||
: base(options)
|
||||
{
|
||||
if (traceServiceClient != null)
|
||||
{
|
||||
this.traceClient = traceServiceClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Channel = options.CreateChannel();
|
||||
this.traceClient = new OtlpCollector.TraceService.TraceServiceClient(this.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool SendExportRequest(OtlpCollector.ExportTraceServiceRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var deadline = DateTime.UtcNow.AddMilliseconds(this.Options.TimeoutMilliseconds);
|
||||
|
||||
try
|
||||
{
|
||||
this.traceClient.Export(request, headers: this.Headers, deadline: deadline);
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(ex);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// <copyright file="OtlpHttpTraceExportClient.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.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using Google.Protobuf;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient
|
||||
{
|
||||
/// <summary>Class for sending OTLP trace export request over HTTP.</summary>
|
||||
internal sealed class OtlpHttpTraceExportClient : BaseOtlpHttpExportClient<OtlpCollector.ExportTraceServiceRequest>
|
||||
{
|
||||
internal const string MediaContentType = "application/x-protobuf";
|
||||
private readonly Uri exportTracesUri;
|
||||
|
||||
public OtlpHttpTraceExportClient(OtlpExporterOptions options, HttpClient httpClient = null)
|
||||
: base(options, httpClient)
|
||||
{
|
||||
this.exportTracesUri = this.Options.Endpoint.AppendPathIfNotPresent(OtlpExporterOptions.TracesExportPath);
|
||||
}
|
||||
|
||||
protected override HttpRequestMessage CreateHttpRequest(OtlpCollector.ExportTraceServiceRequest exportRequest)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, this.exportTracesUri);
|
||||
foreach (var header in this.Headers)
|
||||
{
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
var content = Array.Empty<byte>();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
exportRequest.WriteTo(stream);
|
||||
content = stream.ToArray();
|
||||
}
|
||||
|
||||
var binaryContent = new ByteArrayContent(content);
|
||||
binaryContent.Headers.ContentType = new MediaTypeHeaderValue(MediaContentType);
|
||||
request.Content = binaryContent;
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,5 +103,11 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
|
|||
{
|
||||
this.WriteEvent(7, exception);
|
||||
}
|
||||
|
||||
[Event(8, Message = "Unsupported value for protocol '{0}' is configured, default protocol 'grpc' will be used.", Level = EventLevel.Warning)]
|
||||
public void UnsupportedProtocol(string protocol)
|
||||
{
|
||||
this.WriteEvent(8, protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5.0</TargetFrameworks>
|
||||
<Description>OpenTelemetry protocol exporter for OpenTelemetry .NET</Description>
|
||||
<PackageTags>$(PackageTags);OTLP</PackageTags>
|
||||
<MinVerTagPrefix>core-</MinVerTagPrefix>
|
||||
<!-- TODO: Remove this once a version targeting net5.0 is released -->
|
||||
<RunApiCompat Condition="$(TargetFramework) == 'net5.0'">false</RunApiCompat>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--Do not run ApiCompat for net461 as this is newly added. There is no existing contract for net461 against which we could compare the implementation.
|
||||
|
|
@ -16,7 +18,15 @@
|
|||
<PackageReference Include="Grpc.Net.Client" Version="$(GrpcNetClientPkgVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.1'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
<PackageReference Include="Grpc.Net.Client" Version="$(GrpcNetClientPkgVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="Grpc" Version="$(GrpcPkgVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
|
||||
<PackageReference Include="Grpc" Version="$(GrpcPkgVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
// <copyright file="OtlpExportProtocol.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>
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported by OTLP exporter protocol types according to the specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md.
|
||||
/// </summary>
|
||||
public enum OtlpExportProtocol : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// OTLP over gRPC (corresponds to 'grpc' Protocol configuration option). Used as default.
|
||||
/// </summary>
|
||||
Grpc = 0,
|
||||
|
||||
/// <summary>
|
||||
/// OTLP over HTTP with protobuf payloads (corresponds to 'http/protobuf' Protocol configuration option).
|
||||
/// </summary>
|
||||
HttpProtobuf = 1,
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,10 @@ namespace OpenTelemetry.Exporter
|
|||
internal const string EndpointEnvVarName = "OTEL_EXPORTER_OTLP_ENDPOINT";
|
||||
internal const string HeadersEnvVarName = "OTEL_EXPORTER_OTLP_HEADERS";
|
||||
internal const string TimeoutEnvVarName = "OTEL_EXPORTER_OTLP_TIMEOUT";
|
||||
internal const string ProtocolEnvVarName = "OTEL_EXPORTER_OTLP_PROTOCOL";
|
||||
|
||||
internal const string TracesExportPath = "v1/traces";
|
||||
internal const string MetricsExportPath = "v1/metrics";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpExporterOptions"/> class.
|
||||
|
|
@ -70,6 +74,20 @@ namespace OpenTelemetry.Exporter
|
|||
OpenTelemetryProtocolExporterEventSource.Log.FailedToParseEnvironmentVariable(TimeoutEnvVarName, timeoutEnvVar);
|
||||
}
|
||||
}
|
||||
|
||||
string protocolEnvVar = Environment.GetEnvironmentVariable(ProtocolEnvVarName);
|
||||
if (!string.IsNullOrEmpty(protocolEnvVar))
|
||||
{
|
||||
var protocol = protocolEnvVar.ToOtlpExportProtocol();
|
||||
if (protocol.HasValue)
|
||||
{
|
||||
this.Protocol = protocol.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.UnsupportedProtocol(protocolEnvVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
|
|
@ -98,6 +116,11 @@ namespace OpenTelemetry.Exporter
|
|||
/// </summary>
|
||||
public int TimeoutMilliseconds { get; set; } = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the the OTLP transport protocol. Supported values: Grpc and HttpProtobuf.
|
||||
/// </summary>
|
||||
public OtlpExportProtocol Protocol { get; set; } = OtlpExportProtocol.Grpc;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the export processor type to be used with the OpenTelemetry Protocol Exporter. The default value is <see cref="ExportProcessorType.Batch"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
// <copyright file="OtlpExporterOptionsExtensions.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 Grpc.Core;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
#if NETSTANDARD2_1 || NET5_0_OR_GREATER
|
||||
using Grpc.Net.Client;
|
||||
#endif
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
internal static class OtlpExporterOptionsExtensions
|
||||
{
|
||||
#if NETSTANDARD2_1 || NET5_0_OR_GREATER
|
||||
public static GrpcChannel CreateChannel(this OtlpExporterOptions options)
|
||||
#else
|
||||
public static Channel CreateChannel(this OtlpExporterOptions options)
|
||||
#endif
|
||||
{
|
||||
if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps)
|
||||
{
|
||||
throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported.");
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1 || NET5_0_OR_GREATER
|
||||
return GrpcChannel.ForAddress(options.Endpoint);
|
||||
#else
|
||||
ChannelCredentials channelCredentials;
|
||||
if (options.Endpoint.Scheme == Uri.UriSchemeHttps)
|
||||
{
|
||||
channelCredentials = new SslCredentials();
|
||||
}
|
||||
else
|
||||
{
|
||||
channelCredentials = ChannelCredentials.Insecure;
|
||||
}
|
||||
|
||||
return new Channel(options.Endpoint.Authority, channelCredentials);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Metadata GetMetadataFromHeaders(this OtlpExporterOptions options)
|
||||
{
|
||||
return options.GetHeaders<Metadata>((m, k, v) => m.Add(k, v));
|
||||
}
|
||||
|
||||
public static THeaders GetHeaders<THeaders>(this OtlpExporterOptions options, Action<THeaders, string, string> addHeader)
|
||||
where THeaders : new()
|
||||
{
|
||||
var optionHeaders = options.Headers;
|
||||
var headers = new THeaders();
|
||||
if (!string.IsNullOrEmpty(optionHeaders))
|
||||
{
|
||||
Array.ForEach(
|
||||
optionHeaders.Split(','),
|
||||
(pair) =>
|
||||
{
|
||||
// Specify the maximum number of substrings to return to 2
|
||||
// This treats everything that follows the first `=` in the string as the value to be added for the metadata key
|
||||
var keyValueData = pair.Split(new char[] { '=' }, 2);
|
||||
if (keyValueData.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Headers provided in an invalid format.");
|
||||
}
|
||||
|
||||
var key = keyValueData[0].Trim();
|
||||
var value = keyValueData[1].Trim();
|
||||
addHeader(headers, key, value);
|
||||
});
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
public static IExportClient<OtlpCollector.ExportTraceServiceRequest> GetTraceExportClient(this OtlpExporterOptions options) =>
|
||||
options.Protocol switch
|
||||
{
|
||||
OtlpExportProtocol.Grpc => new OtlpGrpcTraceExportClient(options),
|
||||
OtlpExportProtocol.HttpProtobuf => new OtlpHttpTraceExportClient(options),
|
||||
_ => throw new NotSupportedException($"Protocol {options.Protocol} is not supported.")
|
||||
};
|
||||
|
||||
public static OtlpExportProtocol? ToOtlpExportProtocol(this string protocol) =>
|
||||
protocol.Trim() switch
|
||||
{
|
||||
"grpc" => OtlpExportProtocol.Grpc,
|
||||
"http/protobuf" => OtlpExportProtocol.HttpProtobuf,
|
||||
_ => null
|
||||
};
|
||||
|
||||
public static Uri AppendPathIfNotPresent(this Uri uri, string path)
|
||||
{
|
||||
var absoluteUri = uri.AbsoluteUri;
|
||||
var separator = string.Empty;
|
||||
|
||||
if (absoluteUri.EndsWith("/"))
|
||||
{
|
||||
// Endpoint already ends with 'path/'
|
||||
if (absoluteUri.EndsWith(string.Concat(path, "/"), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Endpoint already ends with 'path'
|
||||
if (absoluteUri.EndsWith(path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
separator = "/";
|
||||
}
|
||||
|
||||
return new Uri(string.Concat(uri.AbsoluteUri, separator, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// <copyright file="OtlpExporterOptionsGrpcExtensions.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 Grpc.Core;
|
||||
#if NETSTANDARD2_1
|
||||
using Grpc.Net.Client;
|
||||
#endif
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol
|
||||
{
|
||||
internal static class OtlpExporterOptionsGrpcExtensions
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
public static GrpcChannel CreateChannel(this OtlpExporterOptions options)
|
||||
#else
|
||||
public static Channel CreateChannel(this OtlpExporterOptions options)
|
||||
#endif
|
||||
{
|
||||
if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps)
|
||||
{
|
||||
throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported.");
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
return GrpcChannel.ForAddress(options.Endpoint);
|
||||
#else
|
||||
ChannelCredentials channelCredentials;
|
||||
if (options.Endpoint.Scheme == Uri.UriSchemeHttps)
|
||||
{
|
||||
channelCredentials = new SslCredentials();
|
||||
}
|
||||
else
|
||||
{
|
||||
channelCredentials = ChannelCredentials.Insecure;
|
||||
}
|
||||
|
||||
return new Channel(options.Endpoint.Authority, channelCredentials);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Metadata GetMetadataFromHeaders(this OtlpExporterOptions options)
|
||||
{
|
||||
var headers = options.Headers;
|
||||
var metadata = new Metadata();
|
||||
if (!string.IsNullOrEmpty(headers))
|
||||
{
|
||||
Array.ForEach(
|
||||
headers.Split(','),
|
||||
(pair) =>
|
||||
{
|
||||
// Specify the maximum number of substrings to return to 2
|
||||
// This treats everything that follows the first `=` in the string as the value to be added for the metadata key
|
||||
var keyValueData = pair.Split(new char[] { '=' }, 2);
|
||||
if (keyValueData.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Headers provided in an invalid format.");
|
||||
}
|
||||
|
||||
var key = keyValueData[0].Trim();
|
||||
var value = keyValueData[1].Trim();
|
||||
metadata.Add(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,12 +15,11 @@
|
|||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1;
|
||||
using OtlpResource = Opentelemetry.Proto.Resource.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
|
|
@ -29,9 +28,11 @@ namespace OpenTelemetry.Exporter
|
|||
/// the OpenTelemetry protocol (OTLP).
|
||||
/// </summary>
|
||||
[AggregationTemporality(AggregationTemporality.Cumulative | AggregationTemporality.Delta, AggregationTemporality.Cumulative)]
|
||||
public class OtlpMetricExporter : BaseOtlpExporter<Metric>
|
||||
public class OtlpMetricExporter : BaseExporter<Metric>
|
||||
{
|
||||
private readonly OtlpCollector.MetricsService.IMetricsServiceClient metricsClient;
|
||||
private readonly IExportClient<OtlpCollector.ExportMetricsServiceRequest> exportClient;
|
||||
|
||||
private OtlpResource.Resource processResource;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpMetricExporter"/> class.
|
||||
|
|
@ -45,22 +46,23 @@ namespace OpenTelemetry.Exporter
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpMetricExporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Configuration options for the exporter.</param>
|
||||
/// <param name="metricsServiceClient"><see cref="OtlpCollector.MetricsService.IMetricsServiceClient"/>.</param>
|
||||
internal OtlpMetricExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.IMetricsServiceClient metricsServiceClient = null)
|
||||
: base(options)
|
||||
/// <param name="options">Configuration options for the export.</param>
|
||||
/// <param name="exportClient">Client used for sending export request.</param>
|
||||
internal OtlpMetricExporter(OtlpExporterOptions options, IExportClient<OtlpCollector.ExportMetricsServiceRequest> exportClient = null)
|
||||
{
|
||||
if (metricsServiceClient != null)
|
||||
if (exportClient != null)
|
||||
{
|
||||
this.metricsClient = metricsServiceClient;
|
||||
this.exportClient = exportClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Channel = options.CreateChannel();
|
||||
this.metricsClient = new OtlpCollector.MetricsService.MetricsServiceClient(this.Channel);
|
||||
// TODO: this instantiation should be aligned with the protocol option (grpc or http/protobuf) when OtlpHttpMetricsExportClient will be implemented.
|
||||
this.exportClient = new OtlpGrpcMetricsExportClient(options);
|
||||
}
|
||||
}
|
||||
|
||||
internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ExportResult Export(in Batch<Metric> metrics)
|
||||
{
|
||||
|
|
@ -70,16 +72,13 @@ namespace OpenTelemetry.Exporter
|
|||
var request = new OtlpCollector.ExportMetricsServiceRequest();
|
||||
|
||||
request.AddMetrics(this.ProcessResource, metrics);
|
||||
var deadline = DateTime.UtcNow.AddMilliseconds(this.Options.TimeoutMilliseconds);
|
||||
|
||||
try
|
||||
{
|
||||
this.metricsClient.Export(request, headers: this.Headers, deadline: deadline);
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(ex);
|
||||
return ExportResult.Failure;
|
||||
if (!this.exportClient.SendExportRequest(request))
|
||||
{
|
||||
return ExportResult.Failure;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -97,20 +96,7 @@ namespace OpenTelemetry.Exporter
|
|||
/// <inheritdoc />
|
||||
protected override bool OnShutdown(int timeoutMilliseconds)
|
||||
{
|
||||
if (this.Channel == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (timeoutMilliseconds == -1)
|
||||
{
|
||||
this.Channel.ShutdownAsync().Wait();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.WaitAny(new Task[] { this.Channel.ShutdownAsync(), Task.Delay(timeoutMilliseconds) }) == 0;
|
||||
}
|
||||
return this.exportClient?.Shutdown(timeoutMilliseconds) ?? true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Grpc.Core;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1;
|
||||
using OtlpResource = Opentelemetry.Proto.Resource.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
|
|
@ -27,14 +27,16 @@ namespace OpenTelemetry.Exporter
|
|||
/// Exporter consuming <see cref="Activity"/> and exporting the data using
|
||||
/// the OpenTelemetry protocol (OTLP).
|
||||
/// </summary>
|
||||
public class OtlpTraceExporter : BaseOtlpExporter<Activity>
|
||||
public class OtlpTraceExporter : BaseExporter<Activity>
|
||||
{
|
||||
private readonly OtlpCollector.TraceService.ITraceServiceClient traceClient;
|
||||
private readonly IExportClient<OtlpCollector.ExportTraceServiceRequest> exportClient;
|
||||
|
||||
private OtlpResource.Resource processResource;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpTraceExporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Configuration options for the exporter.</param>
|
||||
/// <param name="options">Configuration options for the export.</param>
|
||||
public OtlpTraceExporter(OtlpExporterOptions options)
|
||||
: this(options, null)
|
||||
{
|
||||
|
|
@ -43,42 +45,38 @@ namespace OpenTelemetry.Exporter
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpTraceExporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Configuration options for the exporter.</param>
|
||||
/// <param name="traceServiceClient"><see cref="OtlpCollector.TraceService.TraceServiceClient"/>.</param>
|
||||
internal OtlpTraceExporter(OtlpExporterOptions options, OtlpCollector.TraceService.ITraceServiceClient traceServiceClient = null)
|
||||
: base(options)
|
||||
/// <param name="options">Configuration options for the export.</param>
|
||||
/// <param name="exportClient">Client used for sending export request.</param>
|
||||
internal OtlpTraceExporter(OtlpExporterOptions options, IExportClient<OtlpCollector.ExportTraceServiceRequest> exportClient = null)
|
||||
{
|
||||
if (traceServiceClient != null)
|
||||
if (exportClient != null)
|
||||
{
|
||||
this.traceClient = traceServiceClient;
|
||||
this.exportClient = exportClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Channel = options.CreateChannel();
|
||||
this.traceClient = new OtlpCollector.TraceService.TraceServiceClient(this.Channel);
|
||||
this.exportClient = options.GetTraceExportClient();
|
||||
}
|
||||
}
|
||||
|
||||
internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ExportResult Export(in Batch<Activity> activityBatch)
|
||||
{
|
||||
// Prevents the exporter's gRPC and HTTP operations from being instrumented.
|
||||
using var scope = SuppressInstrumentationScope.Begin();
|
||||
|
||||
OtlpCollector.ExportTraceServiceRequest request = new OtlpCollector.ExportTraceServiceRequest();
|
||||
var request = new OtlpCollector.ExportTraceServiceRequest();
|
||||
|
||||
request.AddBatch(this.ProcessResource, activityBatch);
|
||||
var deadline = DateTime.UtcNow.AddMilliseconds(this.Options.TimeoutMilliseconds);
|
||||
|
||||
try
|
||||
{
|
||||
this.traceClient.Export(request, headers: this.Headers, deadline: deadline);
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(ex);
|
||||
|
||||
return ExportResult.Failure;
|
||||
if (!this.exportClient.SendExportRequest(request))
|
||||
{
|
||||
return ExportResult.Failure;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -93,5 +91,11 @@ namespace OpenTelemetry.Exporter
|
|||
|
||||
return ExportResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool OnShutdown(int timeoutMilliseconds)
|
||||
{
|
||||
return this.exportClient?.Shutdown(timeoutMilliseconds) ?? true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ setters take precedence over the environment variables.
|
|||
contain a port and path.
|
||||
* `Headers`: Optional headers for the connection.
|
||||
* `TimeoutMilliseconds` : Max waiting time for the backend to process a batch.
|
||||
* `Protocol`: OTLP transport protocol. Supported values: Grpc and HttpProtobuf.
|
||||
* `ExportProcessorType`: Whether the exporter should use [Batch or Simple
|
||||
exporting
|
||||
processor](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#built-in-span-processors)
|
||||
|
|
@ -45,11 +46,12 @@ The following environment variables can be used to override the default
|
|||
values of the `OtlpExporterOptions`
|
||||
(following the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md)).
|
||||
|
||||
| Environment variable | `OtlpExporterOptions` property |
|
||||
| ------------------------------| -------------------------------|
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `Endpoint` |
|
||||
| `OTEL_EXPORTER_OTLP_HEADERS` | `Headers` |
|
||||
| `OTEL_EXPORTER_OTLP_TIMEOUT` | `TimeoutMilliseconds` |
|
||||
| Environment variable | `OtlpExporterOptions` property |
|
||||
| ------------------------------| ----------------------------------|
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `Endpoint` |
|
||||
| `OTEL_EXPORTER_OTLP_HEADERS` | `Headers` |
|
||||
| `OTEL_EXPORTER_OTLP_TIMEOUT` | `TimeoutMilliseconds` |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `Protocol` (grpc or http/protobuf)|
|
||||
|
||||
## Special case when using insecure channel
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ using Benchmarks.Helper;
|
|||
using Grpc.Core;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using OpenTelemetry.Internal;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1;
|
||||
|
||||
|
|
@ -43,9 +44,11 @@ namespace Benchmarks.Exporter
|
|||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
var options = new OtlpExporterOptions();
|
||||
this.exporter = new OtlpTraceExporter(
|
||||
new OtlpExporterOptions(),
|
||||
new NoopTraceServiceClient());
|
||||
options,
|
||||
new OtlpGrpcTraceExportClient(options, new NoopTraceServiceClient()));
|
||||
|
||||
this.activity = ActivityHelper.CreateTestActivity();
|
||||
this.activityBatch = new CircularBuffer<Activity>(this.NumberOfSpans);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
// <copyright file="OtlpHttpTraceExportClientTests.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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Tests;
|
||||
using OpenTelemetry.Trace;
|
||||
using Xunit;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.ExportClient
|
||||
{
|
||||
public class OtlpHttpTraceExportClientTests
|
||||
{
|
||||
static OtlpHttpTraceExportClientTests()
|
||||
{
|
||||
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
|
||||
Activity.ForceDefaultIdFormat = true;
|
||||
|
||||
var listener = new ActivityListener
|
||||
{
|
||||
ShouldListenTo = _ => true,
|
||||
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
|
||||
};
|
||||
|
||||
ActivitySource.AddActivityListener(listener);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewOtlpHttpTraceExportClient_OtlpExporterOptions_ExporterHasCorrectProperties()
|
||||
{
|
||||
var header1 = new { Name = "hdr1", Value = "val1" };
|
||||
var header2 = new { Name = "hdr2", Value = "val2" };
|
||||
|
||||
var options = new OtlpExporterOptions
|
||||
{
|
||||
Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}",
|
||||
};
|
||||
|
||||
var client = new OtlpHttpTraceExportClient(options);
|
||||
|
||||
Assert.NotNull(client.HttpClient);
|
||||
|
||||
Assert.Equal(2, client.Headers.Count);
|
||||
Assert.Contains(client.Headers, kvp => kvp.Key == header1.Name && kvp.Value == header1.Value);
|
||||
Assert.Contains(client.Headers, kvp => kvp.Key == header2.Name && kvp.Value == header2.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest(bool includeServiceNameInResource)
|
||||
{
|
||||
// Arrange
|
||||
var evenTags = new[] { new KeyValuePair<string, object>("k0", "v0") };
|
||||
var oddTags = new[] { new KeyValuePair<string, object>("k1", "v1") };
|
||||
var sources = new[]
|
||||
{
|
||||
new ActivitySource("even", "2.4.6"),
|
||||
new ActivitySource("odd", "1.3.5"),
|
||||
};
|
||||
var header1 = new { Name = "hdr1", Value = "val1" };
|
||||
var header2 = new { Name = "hdr2", Value = "val2" };
|
||||
|
||||
var options = new OtlpExporterOptions
|
||||
{
|
||||
Endpoint = new Uri("http://localhost:4317"),
|
||||
Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}",
|
||||
};
|
||||
|
||||
var httpHandlerMock = new Mock<HttpMessageHandler>();
|
||||
|
||||
HttpRequestMessage httpRequest = null;
|
||||
var httpRequestContent = Array.Empty<byte>();
|
||||
|
||||
httpHandlerMock.Protected()
|
||||
#if NET5_0_OR_GREATER
|
||||
.Setup<HttpResponseMessage>("Send", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns((HttpRequestMessage request, CancellationToken token) =>
|
||||
{
|
||||
return new HttpResponseMessage();
|
||||
})
|
||||
.Callback<HttpRequestMessage, CancellationToken>((r, ct) =>
|
||||
{
|
||||
httpRequest = r;
|
||||
|
||||
// We have to capture content as it can't be accessed after request is disposed inside of SendExportRequest method
|
||||
httpRequestContent = r.Content.ReadAsByteArrayAsync()?.Result;
|
||||
})
|
||||
#else
|
||||
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
|
||||
{
|
||||
return new HttpResponseMessage();
|
||||
})
|
||||
.Callback<HttpRequestMessage, CancellationToken>(async (r, ct) =>
|
||||
{
|
||||
httpRequest = r;
|
||||
|
||||
// We have to capture content as it can't be accessed after request is disposed inside of SendExportRequest method
|
||||
httpRequestContent = await r.Content.ReadAsByteArrayAsync();
|
||||
})
|
||||
#endif
|
||||
.Verifiable();
|
||||
|
||||
var exportClient = new OtlpHttpTraceExportClient(options, new HttpClient(httpHandlerMock.Object));
|
||||
|
||||
var resourceBuilder = ResourceBuilder.CreateEmpty();
|
||||
if (includeServiceNameInResource)
|
||||
{
|
||||
resourceBuilder.AddAttributes(
|
||||
new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "service_name"),
|
||||
new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceNamespace, "ns_1"),
|
||||
});
|
||||
}
|
||||
|
||||
var builder = Sdk.CreateTracerProviderBuilder()
|
||||
.SetResourceBuilder(resourceBuilder)
|
||||
.AddSource(sources[0].Name)
|
||||
.AddSource(sources[1].Name);
|
||||
|
||||
using var openTelemetrySdk = builder.Build();
|
||||
|
||||
var processor = new BatchActivityExportProcessor(new TestExporter<Activity>(RunTest));
|
||||
const int numOfSpans = 10;
|
||||
bool isEven;
|
||||
for (var i = 0; i < numOfSpans; i++)
|
||||
{
|
||||
isEven = i % 2 == 0;
|
||||
var source = sources[i % 2];
|
||||
var activityKind = isEven ? ActivityKind.Client : ActivityKind.Server;
|
||||
var activityTags = isEven ? evenTags : oddTags;
|
||||
|
||||
using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags);
|
||||
processor.OnEnd(activity);
|
||||
}
|
||||
|
||||
processor.Shutdown();
|
||||
|
||||
void RunTest(Batch<Activity> batch)
|
||||
{
|
||||
var request = new OtlpCollector.ExportTraceServiceRequest();
|
||||
|
||||
request.AddBatch(resourceBuilder.Build().ToOtlpResource(), batch);
|
||||
|
||||
// Act
|
||||
var result = exportClient.SendExportRequest(request);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.NotNull(httpRequest);
|
||||
Assert.Equal(HttpMethod.Post, httpRequest.Method);
|
||||
Assert.Equal("http://localhost:4317/v1/traces", httpRequest.RequestUri.AbsoluteUri);
|
||||
Assert.Equal(2, httpRequest.Headers.Count());
|
||||
Assert.Contains(httpRequest.Headers, h => h.Key == header1.Name && h.Value.First() == header1.Value);
|
||||
Assert.Contains(httpRequest.Headers, h => h.Key == header2.Name && h.Value.First() == header2.Value);
|
||||
|
||||
Assert.NotNull(httpRequest.Content);
|
||||
Assert.IsType<ByteArrayContent>(httpRequest.Content);
|
||||
Assert.Contains(httpRequest.Content.Headers, h => h.Key == "Content-Type" && h.Value.First() == OtlpHttpTraceExportClient.MediaContentType);
|
||||
|
||||
var exportTraceRequest = OtlpCollector.ExportTraceServiceRequest.Parser.ParseFrom(httpRequestContent);
|
||||
Assert.NotNull(exportTraceRequest);
|
||||
Assert.Single(exportTraceRequest.ResourceSpans);
|
||||
|
||||
var resourceSpan = exportTraceRequest.ResourceSpans.First();
|
||||
if (includeServiceNameInResource)
|
||||
{
|
||||
Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service_name");
|
||||
Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns_1");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
// <copyright file="OtlpExporterOptionsExtensionsTests.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.Collections.Generic;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
||||
{
|
||||
public class OtlpExporterOptionsExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("key=value", new string[] { "key" }, new string[] { "value" })]
|
||||
[InlineData("key1=value1,key2=value2", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })]
|
||||
[InlineData("key1 = value1, key2=value2 ", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })]
|
||||
[InlineData("key==value", new string[] { "key" }, new string[] { "=value" })]
|
||||
[InlineData("access-token=abc=/123,timeout=1234", new string[] { "access-token", "timeout" }, new string[] { "abc=/123", "1234" })]
|
||||
[InlineData("key1=value1;key2=value2", new string[] { "key1" }, new string[] { "value1;key2=value2" })] // semicolon is not treated as a delimeter (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables)
|
||||
public void GetMetadataFromHeadersWorksCorrectFormat(string headers, string[] keys, string[] values)
|
||||
{
|
||||
var options = new OtlpExporterOptions();
|
||||
options.Headers = headers;
|
||||
var metadata = options.GetMetadataFromHeaders();
|
||||
|
||||
Assert.Equal(keys.Length, metadata.Count);
|
||||
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
Assert.Contains(metadata, entry => entry.Key == keys[i] && entry.Value == values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("headers")]
|
||||
[InlineData("key,value")]
|
||||
public void GetMetadataFromHeadersThrowsExceptionOnInvalidFormat(string headers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new OtlpExporterOptions();
|
||||
options.Headers = headers;
|
||||
var metadata = options.GetMetadataFromHeaders();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.IsType<ArgumentException>(ex);
|
||||
Assert.Equal("Headers provided in an invalid format.", ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new XunitException("GetMetadataFromHeaders did not throw an exception for invalid input headers");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void GetHeaders_NoOptionHeaders_ReturnsEmptyHeadres(string optionHeaders)
|
||||
{
|
||||
var options = new OtlpExporterOptions
|
||||
{
|
||||
Headers = optionHeaders,
|
||||
};
|
||||
|
||||
var headers = options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v));
|
||||
|
||||
Assert.Empty(headers);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcTraceExportClient))]
|
||||
[InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpTraceExportClient))]
|
||||
public void GetTraceExportClient_SupportedProtocol_ReturnsCorrectExportClient(OtlpExportProtocol protocol, Type expectedExportClientType)
|
||||
{
|
||||
var options = new OtlpExporterOptions
|
||||
{
|
||||
Protocol = protocol,
|
||||
};
|
||||
|
||||
var exportClient = options.GetTraceExportClient();
|
||||
|
||||
Assert.Equal(expectedExportClientType, exportClient.GetType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTraceExportClient_UnsupportedProtocol_Throws()
|
||||
{
|
||||
var options = new OtlpExporterOptions
|
||||
{
|
||||
Protocol = (OtlpExportProtocol)123,
|
||||
};
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => options.GetTraceExportClient());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("grpc", OtlpExportProtocol.Grpc)]
|
||||
[InlineData("http/protobuf", OtlpExportProtocol.HttpProtobuf)]
|
||||
[InlineData("unsupported", null)]
|
||||
public void ToOtlpExportProtocol_Protocol_MapsToCorrectValue(string protocol, OtlpExportProtocol? expectedExportProtocol)
|
||||
{
|
||||
var exportProtocol = protocol.ToOtlpExportProtocol();
|
||||
|
||||
Assert.Equal(expectedExportProtocol, exportProtocol);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://test:8888", "http://test:8888/v1/traces")]
|
||||
[InlineData("http://test:8888/", "http://test:8888/v1/traces")]
|
||||
[InlineData("http://test:8888/v1/traces", "http://test:8888/v1/traces")]
|
||||
[InlineData("http://test:8888/v1/traces/", "http://test:8888/v1/traces/")]
|
||||
public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string inputUri, string expectedUri)
|
||||
{
|
||||
var uri = new Uri(inputUri, UriKind.Absolute);
|
||||
|
||||
var resultUri = uri.AppendPathIfNotPresent(OtlpExporterOptions.TracesExportPath);
|
||||
|
||||
Assert.Equal(expectedUri, resultUri.AbsoluteUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
// <copyright file="OtlpExporterOptionsGrpcExtensionsTests.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 Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
||||
{
|
||||
public class OtlpExporterOptionsGrpcExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("key=value", new string[] { "key" }, new string[] { "value" })]
|
||||
[InlineData("key1=value1,key2=value2", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })]
|
||||
[InlineData("key1 = value1, key2=value2 ", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })]
|
||||
[InlineData("key==value", new string[] { "key" }, new string[] { "=value" })]
|
||||
[InlineData("access-token=abc=/123,timeout=1234", new string[] { "access-token", "timeout" }, new string[] { "abc=/123", "1234" })]
|
||||
[InlineData("key1=value1;key2=value2", new string[] { "key1" }, new string[] { "value1;key2=value2" })] // semicolon is not treated as a delimeter (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables)
|
||||
public void GetMetadataFromHeadersWorksCorrectFormat(string headers, string[] keys, string[] values)
|
||||
{
|
||||
var options = new OtlpExporterOptions();
|
||||
options.Headers = headers;
|
||||
var metadata = options.GetMetadataFromHeaders();
|
||||
|
||||
Assert.Equal(keys.Length, metadata.Count);
|
||||
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
Assert.Contains(metadata, entry => entry.Key == keys[i] && entry.Value == values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("headers")]
|
||||
[InlineData("key,value")]
|
||||
public void GetMetadataFromHeadersThrowsExceptionOnOnvalidFormat(string headers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new OtlpExporterOptions();
|
||||
options.Headers = headers;
|
||||
var metadata = options.GetMetadataFromHeaders();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.IsType<ArgumentException>(ex);
|
||||
Assert.Equal("Headers provided in an invalid format.", ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new XunitException("GetMetadataFromHeaders did not throw an exception for invalid input headers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Assert.Equal(new Uri("http://localhost:4317"), options.Endpoint);
|
||||
Assert.Null(options.Headers);
|
||||
Assert.Equal(10000, options.TimeoutMilliseconds);
|
||||
Assert.Equal(OtlpExportProtocol.Grpc, options.Protocol);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -47,12 +48,14 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "http://test:8888");
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3");
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000");
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.ProtocolEnvVarName, "http/protobuf");
|
||||
|
||||
var options = new OtlpExporterOptions();
|
||||
|
||||
Assert.Equal(new Uri("http://test:8888"), options.Endpoint);
|
||||
Assert.Equal("A=2,B=3", options.Headers);
|
||||
Assert.Equal(2000, options.TimeoutMilliseconds);
|
||||
Assert.Equal(OtlpExportProtocol.HttpProtobuf, options.Protocol);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -75,23 +78,36 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Assert.Equal(10000, options.TimeoutMilliseconds); // use default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OtlpExporterOptions_InvalidProtocolVariableOverride()
|
||||
{
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.ProtocolEnvVarName, "invalid");
|
||||
|
||||
var options = new OtlpExporterOptions();
|
||||
|
||||
Assert.Equal(OtlpExportProtocol.Grpc, options.Protocol); // use default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OtlpExporterOptions_SetterOverridesEnvironmentVariable()
|
||||
{
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "http://test:8888");
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3");
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000");
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.ProtocolEnvVarName, "grpc");
|
||||
|
||||
var options = new OtlpExporterOptions
|
||||
{
|
||||
Endpoint = new Uri("http://localhost:200"),
|
||||
Headers = "C=3",
|
||||
TimeoutMilliseconds = 40000,
|
||||
Protocol = OtlpExportProtocol.HttpProtobuf,
|
||||
};
|
||||
|
||||
Assert.Equal(new Uri("http://localhost:200"), options.Endpoint);
|
||||
Assert.Equal("C=3", options.Headers);
|
||||
Assert.Equal(40000, options.TimeoutMilliseconds);
|
||||
Assert.Equal(OtlpExportProtocol.HttpProtobuf, options.Protocol);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -100,6 +116,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Assert.Equal("OTEL_EXPORTER_OTLP_ENDPOINT", OtlpExporterOptions.EndpointEnvVarName);
|
||||
Assert.Equal("OTEL_EXPORTER_OTLP_HEADERS", OtlpExporterOptions.HeadersEnvVarName);
|
||||
Assert.Equal("OTEL_EXPORTER_OTLP_TIMEOUT", OtlpExporterOptions.TimeoutEnvVarName);
|
||||
Assert.Equal("OTEL_EXPORTER_OTLP_PROTOCOL", OtlpExporterOptions.ProtocolEnvVarName);
|
||||
}
|
||||
|
||||
private static void ClearEnvVars()
|
||||
|
|
@ -107,6 +124,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, null);
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, null);
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, null);
|
||||
Environment.SetEnvironmentVariable(OtlpExporterOptions.ProtocolEnvVarName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using System.Diagnostics.Metrics;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Tests;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Moq;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Tests;
|
||||
using OpenTelemetry.Trace;
|
||||
|
|
@ -112,12 +114,12 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
var oltpResource = request.ResourceSpans.First().Resource;
|
||||
if (includeServiceNameInResource)
|
||||
{
|
||||
Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name");
|
||||
Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1");
|
||||
Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name");
|
||||
Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:"));
|
||||
Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:"));
|
||||
}
|
||||
|
||||
foreach (var instrumentationLibrarySpans in request.ResourceSpans.First().InstrumentationLibrarySpans)
|
||||
|
|
@ -332,6 +334,18 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Assert.True(endCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shutdown_ClientShutdownIsCalled()
|
||||
{
|
||||
var exportClientMock = new Mock<IExportClient<OtlpCollector.ExportTraceServiceRequest>>();
|
||||
|
||||
var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), exportClientMock.Object);
|
||||
|
||||
var result = exporter.Shutdown();
|
||||
|
||||
exportClientMock.Verify(m => m.Shutdown(It.IsAny<int>()), Times.Once());
|
||||
}
|
||||
|
||||
private class NoopTraceServiceClient : OtlpCollector.TraceService.ITraceServiceClient
|
||||
{
|
||||
public OtlpCollector.ExportTraceServiceResponse Export(OtlpCollector.ExportTraceServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default)
|
||||
|
|
|
|||
Loading…
Reference in New Issue