diff --git a/examples/Console/Program.cs b/examples/Console/Program.cs index 44bab047881..ae80904ba5e 100644 --- a/examples/Console/Program.cs +++ b/examples/Console/Program.cs @@ -169,6 +169,9 @@ internal class LogsOptions { [Option("useExporter", Default = "otlp", HelpText = "Options include otlp or console.", Required = false)] public string UseExporter { get; set; } + + [Option('p', "protocol", HelpText = "Transport protocol used by OTLP exporter. Supported values: grpc and http/protobuf. Only applicable if Exporter is OTLP", Default = "grpc")] + public string Protocol { get; set; } } [Verb("inmemory", HelpText = "Specify the options required to test InMemory Exporter")] diff --git a/examples/Console/TestLogs.cs b/examples/Console/TestLogs.cs index f508651bef6..76158593796 100644 --- a/examples/Console/TestLogs.cs +++ b/examples/Console/TestLogs.cs @@ -40,10 +40,10 @@ internal static object Run(LogsOptions options) * launch the OpenTelemetry Collector with an OTLP receiver, by running: * * - On Unix based systems use: - * docker run --rm -it -p 4317:4317 -v $(pwd):/cfg otel/opentelemetry-collector:0.33.0 --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:0.48.0 --config=/cfg/otlp-collector-example/config.yaml * * - On Windows use: - * docker run --rm -it -p 4317:4317 -v "%cd%":/cfg otel/opentelemetry-collector:0.33.0 --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:0.48.0 --config=/cfg/otlp-collector-example/config.yaml * * Open another terminal window at the examples/Console/ directory and * launch the OTLP example by running: @@ -59,7 +59,28 @@ internal static object Run(LogsOptions options) // 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); - opt.AddOtlpExporter(); + if (options.Protocol.Trim().ToLower().Equals("grpc")) + { + opt.AddOtlpExporter(otlpOptions => + { + otlpOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; + }); + } + else if (options.Protocol.Trim().ToLower().Equals("http/protobuf")) + { + opt.AddOtlpExporter(otlpOptions => + { + otlpOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; + }); + } + else + { + System.Console.WriteLine($"Export protocol {options.Protocol} is not supported. Default protocol 'grpc' will be used."); + opt.AddOtlpExporter(otlpOptions => + { + otlpOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; + }); + } } else { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 8bca96958d4..72c51b40b71 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -5,6 +5,10 @@ * LogExporter to support Logging Scopes. ([#3277](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3217)) +* Support `HttpProtobuf` protocol with logs & added `HttpClientFactory` +option +([#3224](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3224)) + ## 1.3.0-beta.1 Released 2022-Apr-15 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs index 1a87f48d5e3..2e5d03d6c16 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs @@ -21,7 +21,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient { - /// Class for sending OTLP metrics export request over gRPC. + /// Class for sending OTLP Logs export request over gRPC. internal sealed class OtlpGrpcLogExportClient : BaseOtlpGrpcExportClient { private readonly OtlpCollector.LogsService.LogsServiceClient logsClient; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpHttpLogExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpHttpLogExportClient.cs new file mode 100644 index 00000000000..b30c199ca4c --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpHttpLogExportClient.cs @@ -0,0 +1,86 @@ +// +// 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.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +#if NET5_0_OR_GREATER +using System.Threading; +#endif +using System.Threading.Tasks; +using Google.Protobuf; +using OtlpCollector = Opentelemetry.Proto.Collector.Logs.V1; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient +{ + /// Class for sending OTLP log export request over HTTP. + internal sealed class OtlpHttpLogExportClient : BaseOtlpHttpExportClient + { + internal const string MediaContentType = "application/x-protobuf"; + private const string LogsExportPath = "v1/logs"; + + public OtlpHttpLogExportClient(OtlpExporterOptions options, HttpClient httpClient) + : base(options, httpClient, LogsExportPath) + { + } + + protected override HttpContent CreateHttpContent(OtlpCollector.ExportLogsServiceRequest exportRequest) + { + return new ExportRequestContent(exportRequest); + } + + internal sealed class ExportRequestContent : HttpContent + { + private static readonly MediaTypeHeaderValue ProtobufMediaTypeHeader = new(MediaContentType); + + private readonly OtlpCollector.ExportLogsServiceRequest exportRequest; + + public ExportRequestContent(OtlpCollector.ExportLogsServiceRequest exportRequest) + { + this.exportRequest = exportRequest; + this.Headers.ContentType = ProtobufMediaTypeHeader; + } + +#if NET5_0_OR_GREATER + protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken) + { + this.SerializeToStreamInternal(stream); + } +#endif + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + this.SerializeToStreamInternal(stream); + return Task.CompletedTask; + } + + protected override bool TryComputeLength(out long length) + { + // We can't know the length of the content being pushed to the output stream. + length = -1; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SerializeToStreamInternal(Stream stream) + { + this.exportRequest.WriteTo(stream); + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs index 6c3005ccf40..7fee9f5a76f 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs @@ -22,6 +22,7 @@ #if NETSTANDARD2_1 || NET5_0_OR_GREATER using Grpc.Net.Client; #endif +using LogOtlpCollector = Opentelemetry.Proto.Collector.Logs.V1; using MetricsOtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1; using TraceOtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; @@ -110,6 +111,16 @@ public static THeaders GetHeaders(this OtlpExporterOptions options, Ac _ => throw new NotSupportedException($"Protocol {options.Protocol} is not supported."), }; + public static IExportClient GetLogExportClient(this OtlpExporterOptions options) => + options.Protocol switch + { + OtlpExportProtocol.Grpc => new OtlpGrpcLogExportClient(options), + OtlpExportProtocol.HttpProtobuf => new OtlpHttpLogExportClient( + options, + options.HttpClientFactory?.Invoke() ?? throw new InvalidOperationException("OtlpExporterOptions was missing HttpClientFactory or it returned null.")), + _ => throw new NotSupportedException($"Protocol {options.Protocol} is not supported."), + }; + public static OtlpExportProtocol? ToOtlpExportProtocol(this string protocol) => protocol.Trim() switch { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs index 22fdd978d4d..a712646cd7d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs @@ -55,8 +55,7 @@ internal OtlpLogExporter(OtlpExporterOptions options, IExportClient