From bb95e81dda1bdf0f4e9caaa86c7505475842b5d0 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 29 Oct 2020 10:45:15 -0700 Subject: [PATCH 1/3] Added ServiceName option to OtlpExporter. --- .../AspNetCore/Examples.AspNetCore.csproj | 1 + examples/AspNetCore/Startup.cs | 10 +++++++++ examples/AspNetCore/appsettings.json | 6 ++++- .../Implementation/ActivityExtensions.cs | 22 ++++++++++++++++--- .../OtlpExporter.cs | 6 +++-- .../OtlpExporterOptions.cs | 7 ++++++ 6 files changed, 46 insertions(+), 6 deletions(-) diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj index 9df2337990c..e067ca3eba6 100644 --- a/examples/AspNetCore/Examples.AspNetCore.csproj +++ b/examples/AspNetCore/Examples.AspNetCore.csproj @@ -18,6 +18,7 @@ + diff --git a/examples/AspNetCore/Startup.cs b/examples/AspNetCore/Startup.cs index 8b8b587365a..d672af9d8b3 100644 --- a/examples/AspNetCore/Startup.cs +++ b/examples/AspNetCore/Startup.cs @@ -77,6 +77,16 @@ public void ConfigureServices(IServiceCollection services) zipkinOptions.Endpoint = new Uri(this.Configuration.GetValue("Zipkin:Endpoint")); })); break; + case "otlp": + services.AddOpenTelemetryTracing((builder) => builder + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddOtlpExporter(otlpOptions => + { + otlpOptions.ServiceName = this.Configuration.GetValue("Otlp:ServiceName"); + otlpOptions.Endpoint = this.Configuration.GetValue("Otlp:Endpoint"); + })); + break; default: services.AddOpenTelemetryTracing((builder) => builder .AddAspNetCoreInstrumentation() diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json index 79e501b6f53..6fbc8b3aa1d 100644 --- a/examples/AspNetCore/appsettings.json +++ b/examples/AspNetCore/appsettings.json @@ -7,7 +7,7 @@ } }, "AllowedHosts": "*", - "UseExporter": "console", + "UseExporter": "console", "Jaeger": { "ServiceName": "jaeger-test", "Host": "localhost", @@ -16,5 +16,9 @@ "Zipkin": { "ServiceName": "zipkin-test", "Endpoint": "http://localhost:9411/api/v2/spans" + }, + "Otlp": { + "ServiceName": "otlp-test", + "Endpoint": "localhost:55680" } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index 8762c0c1ad6..624c00738c6 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -43,7 +43,8 @@ internal static class ActivityExtensions internal static void AddBatch( this OtlpCollector.ExportTraceServiceRequest request, - in Batch activityBatch) + in Batch activityBatch, + OtlpExporterOptions options) { Dictionary spansByLibrary = new Dictionary(); OtlpTrace.ResourceSpans resourceSpans = null; @@ -56,7 +57,7 @@ internal static void AddBatch( if (processResource == null) { - BuildProcessResource(activity.GetResource()); + BuildProcessResource(activity.GetResource(), options); } resourceSpans.Resource = processResource; @@ -101,7 +102,7 @@ internal static void Return(this OtlpCollector.ExportTraceServiceRequest request } } - internal static void BuildProcessResource(Resource resource) + internal static void BuildProcessResource(Resource resource, OtlpExporterOptions options) { OtlpResource.Resource processResource = new OtlpResource.Resource(); @@ -114,6 +115,21 @@ internal static void BuildProcessResource(Resource resource) } } + if (!processResource.Attributes.Any(kvp => kvp.Key == Resource.ServiceNameKey)) + { + string serviceName = options.ServiceName; + if (string.IsNullOrEmpty(serviceName)) + { + serviceName = OtlpExporterOptions.DefaultServiceName; + } + + processResource.Attributes.Add(new OtlpCommon.KeyValue + { + Key = Resource.ServiceNameKey, + Value = new OtlpCommon.AnyValue { StringValue = serviceName }, + }); + } + ActivityExtensions.processResource = processResource; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs index c7fa3117808..20aca78d140 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs @@ -29,6 +29,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol /// public class OtlpExporter : BaseExporter { + private readonly OtlpExporterOptions options; private readonly Channel channel; private readonly OtlpCollector.TraceService.ITraceServiceClient traceClient; private readonly Metadata headers; @@ -40,7 +41,8 @@ public class OtlpExporter : BaseExporter /// . internal OtlpExporter(OtlpExporterOptions options, OtlpCollector.TraceService.ITraceServiceClient traceServiceClient = null) { - this.headers = options?.Headers ?? throw new ArgumentNullException(nameof(options)); + this.options = options ?? throw new ArgumentNullException(nameof(options)); + this.headers = options.Headers ?? throw new ArgumentException("Headers were not provided on options.", nameof(options)); if (traceServiceClient != null) { this.traceClient = traceServiceClient; @@ -57,7 +59,7 @@ public override ExportResult Export(in Batch activityBatch) { OtlpCollector.ExportTraceServiceRequest request = new OtlpCollector.ExportTraceServiceRequest(); - request.AddBatch(activityBatch); + request.AddBatch(activityBatch, options); try { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 40d7911a464..a85420dffc9 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -24,6 +24,13 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol /// public class OtlpExporterOptions { + internal const string DefaultServiceName = "OpenTelemetry Exporter"; + + /// + /// Gets or sets the name of the service reporting telemetry. Default value: OpenTelemetry Exporter. + /// + public string ServiceName { get; set; } = DefaultServiceName; + /// /// Gets or sets the target to which the exporter is going to send traces or metrics. /// The valid syntax is described at https://github.com/grpc/grpc/blob/master/doc/naming.md. From 12735552d72924e940266ea07374b144ebc16c89 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 29 Oct 2020 10:50:24 -0700 Subject: [PATCH 2/3] CHANGELOG update. --- src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 435c0233ba3..3fde80a4cc1 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -6,6 +6,8 @@ following the [Zipkin remote endpoint rules](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk_exporters/zipkin.md#remote-endpoint) ([#1392](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1392)) +* Added `ServiceName` to options available on the `AddOtlpExporter` extension + ([#1420](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1420)) ## 0.7.0-beta.1 From 0e784d25a8bb75f320fecbea801b677176b53d3b Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 30 Oct 2020 11:20:47 -0700 Subject: [PATCH 3/3] Cleanup and tests. --- .../Implementation/ActivityExtensions.cs | 193 ++++-------------- .../OtlpExporter.cs | 47 ++++- .../OtlpExporterTest.cs | 66 ++++-- 3 files changed, 136 insertions(+), 170 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index 624c00738c6..7039abdee0a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -25,11 +25,9 @@ using Google.Protobuf; using Google.Protobuf.Collections; using OpenTelemetry.Internal; -using OpenTelemetry.Resources; using OpenTelemetry.Trace; using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; using OtlpCommon = Opentelemetry.Proto.Common.V1; -using OtlpResource = Opentelemetry.Proto.Resource.V1; using OtlpTrace = Opentelemetry.Proto.Trace.V1; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation @@ -39,12 +37,11 @@ internal static class ActivityExtensions private static readonly ConcurrentBag SpanListPool = new ConcurrentBag(); private static readonly Action, int> RepeatedFieldOfSpanSetCountAction = CreateRepeatedFieldOfSpanSetCountAction(); private static readonly Func ByteStringCtorFunc = CreateByteStringCtorFunc(); - private static OtlpResource.Resource processResource; internal static void AddBatch( this OtlpCollector.ExportTraceServiceRequest request, - in Batch activityBatch, - OtlpExporterOptions options) + OtlpExporter otlpExporter, + in Batch activityBatch) { Dictionary spansByLibrary = new Dictionary(); OtlpTrace.ResourceSpans resourceSpans = null; @@ -53,14 +50,10 @@ internal static void AddBatch( { if (resourceSpans == null) { - resourceSpans = new OtlpTrace.ResourceSpans(); - - if (processResource == null) + resourceSpans = new OtlpTrace.ResourceSpans { - BuildProcessResource(activity.GetResource(), options); - } - - resourceSpans.Resource = processResource; + Resource = otlpExporter.EnsureProcessResource(activity), + }; request.ResourceSpans.Add(resourceSpans); } @@ -102,37 +95,6 @@ internal static void Return(this OtlpCollector.ExportTraceServiceRequest request } } - internal static void BuildProcessResource(Resource resource, OtlpExporterOptions options) - { - OtlpResource.Resource processResource = new OtlpResource.Resource(); - - foreach (KeyValuePair attribute in resource.Attributes) - { - var oltpAttribute = ToOtlpAttribute(attribute); - if (oltpAttribute != null) - { - processResource.Attributes.Add(oltpAttribute); - } - } - - if (!processResource.Attributes.Any(kvp => kvp.Key == Resource.ServiceNameKey)) - { - string serviceName = options.ServiceName; - if (string.IsNullOrEmpty(serviceName)) - { - serviceName = OtlpExporterOptions.DefaultServiceName; - } - - processResource.Attributes.Add(new OtlpCommon.KeyValue - { - Key = Resource.ServiceNameKey, - Value = new OtlpCommon.AnyValue { StringValue = serviceName }, - }); - } - - ActivityExtensions.processResource = processResource; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static OtlpTrace.InstrumentationLibrarySpans GetSpanListFromPool(string name, string version) { @@ -243,6 +205,41 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity) return otlpSpan; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static OtlpCommon.KeyValue ToOtlpAttribute(this KeyValuePair kvp) + { + if (kvp.Value == null) + { + return null; + } + + var attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; + + switch (kvp.Value) + { + case string s: + attrib.Value.StringValue = s; + break; + case bool b: + attrib.Value.BoolValue = b; + break; + case int i: + attrib.Value.IntValue = i; + break; + case long l: + attrib.Value.IntValue = l; + break; + case double d: + attrib.Value.DoubleValue = d; + break; + default: + attrib.Value.StringValue = kvp.Value.ToString(); + break; + } + + return attrib; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static OtlpTrace.Status ToOtlpStatus(ref TagEnumerationState otlpTags) { @@ -324,116 +321,6 @@ private static OtlpTrace.Span.Types.Event ToOtlpEvent(ActivityEvent activityEven return otlpEvent; } - private static OtlpCommon.KeyValue ToOtlpAttribute(KeyValuePair kvp) - { - if (kvp.Value == null) - { - return null; - } - - var attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; - - switch (kvp.Value) - { - case string s: - attrib.Value.StringValue = s; - break; - case bool b: - attrib.Value.BoolValue = b; - break; - case int i: - attrib.Value.IntValue = i; - break; - case long l: - attrib.Value.IntValue = l; - break; - case double d: - attrib.Value.DoubleValue = d; - break; - default: - attrib.Value.StringValue = kvp.Value.ToString(); - break; - } - - return attrib; - } - - private static List ToOtlpAttributes(KeyValuePair kvp) - { - if (kvp.Value == null) - { - return null; - } - - var attributes = new List(); - var attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; - switch (kvp.Value) - { - case string s: - attrib.Value.StringValue = s; - attributes.Add(attrib); - break; - case bool b: - attrib.Value.BoolValue = b; - attributes.Add(attrib); - break; - case int i: - attrib.Value.IntValue = i; - attributes.Add(attrib); - break; - case long l: - attrib.Value.IntValue = l; - attributes.Add(attrib); - break; - case double d: - attrib.Value.DoubleValue = d; - attributes.Add(attrib); - break; - case int[] intArray: - foreach (var item in intArray) - { - attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; - attrib.Value.IntValue = item; - attributes.Add(attrib); - } - - break; - case double[] doubleArray: - foreach (var item in doubleArray) - { - attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; - attrib.Value.DoubleValue = item; - attributes.Add(attrib); - } - - break; - case bool[] boolArray: - foreach (var item in boolArray) - { - attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; - attrib.Value.BoolValue = item; - attributes.Add(attrib); - } - - break; - case string[] stringArray: - foreach (var item in stringArray) - { - attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } }; - attrib.Value.StringValue = item; - attributes.Add(attrib); - } - - break; - default: - attrib.Value.StringValue = kvp.Value.ToString(); - attributes.Add(attrib); - break; - } - - return attributes; - } - private static Action, int> CreateRepeatedFieldOfSpanSetCountAction() { FieldInfo repeatedFieldOfSpanCountField = typeof(RepeatedField).GetField("count", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs index 20aca78d140..8bdcaa0dc4f 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporter.cs @@ -15,11 +15,18 @@ // using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Grpc.Core; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; +using OtlpCommon = Opentelemetry.Proto.Common.V1; +using OtlpResource = Opentelemetry.Proto.Resource.V1; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol { @@ -33,6 +40,7 @@ public class OtlpExporter : BaseExporter private readonly Channel channel; private readonly OtlpCollector.TraceService.ITraceServiceClient traceClient; private readonly Metadata headers; + private OtlpResource.Resource processResource; /// /// Initializes a new instance of the class. @@ -59,7 +67,7 @@ public override ExportResult Export(in Batch activityBatch) { OtlpCollector.ExportTraceServiceRequest request = new OtlpCollector.ExportTraceServiceRequest(); - request.AddBatch(activityBatch, options); + request.AddBatch(this, activityBatch); try { @@ -79,6 +87,43 @@ public override ExportResult Export(in Batch activityBatch) return ExportResult.Success; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal OtlpResource.Resource EnsureProcessResource(Activity activity) + { + if (this.processResource != null) + { + return this.processResource; + } + + OtlpResource.Resource processResource = new OtlpResource.Resource(); + + foreach (KeyValuePair attribute in activity.GetResource().Attributes) + { + var oltpAttribute = attribute.ToOtlpAttribute(); + if (oltpAttribute != null) + { + processResource.Attributes.Add(oltpAttribute); + } + } + + if (!processResource.Attributes.Any(kvp => kvp.Key == Resource.ServiceNameKey)) + { + string serviceName = this.options.ServiceName; + if (string.IsNullOrEmpty(serviceName)) + { + serviceName = OtlpExporterOptions.DefaultServiceName; + } + + processResource.Attributes.Add(new OtlpCommon.KeyValue + { + Key = Resource.ServiceNameKey, + Value = new OtlpCommon.AnyValue { StringValue = serviceName }, + }); + } + + return this.processResource = processResource; + } + /// protected override bool OnShutdown(int timeoutMilliseconds) { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs index 719eebd2815..52a9d934e4d 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs @@ -18,12 +18,16 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using Google.Protobuf.Collections; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +#if NET452 using OpenTelemetry.Internal; +#endif using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; +using GrpcCore = Grpc.Core; using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; using OtlpCommon = Opentelemetry.Proto.Common.V1; using OtlpTrace = Opentelemetry.Proto.Trace.V1; @@ -53,8 +57,10 @@ public void OtlpExporter_BadArgs() Assert.Throws(() => builder.AddOtlpExporter()); } - [Fact] - public void ToOtlpResourceSpansTest() + [Theory] + [InlineData(true, null)] + [InlineData(false, "test-service")] + public void ToOtlpResourceSpansTest(bool addResource, string optionsServiceName) { var evenTags = new[] { new KeyValuePair("k0", "v0") }; var oddTags = new[] { new KeyValuePair("k1", "v1") }; @@ -64,18 +70,24 @@ public void ToOtlpResourceSpansTest() new ActivitySource("odd", "1.3.5"), }; - var resource = new Resources.Resource( - new List> - { - new KeyValuePair(Resources.Resource.ServiceNamespaceKey, "ns1"), - }); - - // This following is done just to set Resource to Activity. - using var openTelemetrySdk = Sdk.CreateTracerProviderBuilder() + var builder = Sdk.CreateTracerProviderBuilder() .AddSource(sources[0].Name) - .AddSource(sources[1].Name) - .SetResource(resource) - .Build(); + .AddSource(sources[1].Name); + + Resources.Resource resource = null; + if (addResource) + { + resource = new Resources.Resource( + new List> + { + new KeyValuePair(Resources.Resource.ServiceNameKey, "service-name"), + new KeyValuePair(Resources.Resource.ServiceNamespaceKey, "ns1"), + }); + + builder.SetResource(resource); + } + + using var openTelemetrySdk = builder.Build(); var processor = new BatchExportProcessor(new TestExporter(RunTest)); const int numOfSpans = 10; @@ -97,12 +109,26 @@ void RunTest(Batch batch) { var request = new OtlpCollector.ExportTraceServiceRequest(); - request.AddBatch(batch); + request.AddBatch( + new OtlpExporter( + new OtlpExporterOptions + { + ServiceName = optionsServiceName, + }, + new NoopTraceServiceClient()), + batch); Assert.Single(request.ResourceSpans); var oltpResource = request.ResourceSpans.First().Resource; - Assert.Equal(resource.Attributes.First().Key, oltpResource.Attributes.First().Key); - Assert.Equal(resource.Attributes.First().Value, oltpResource.Attributes.First().Value.StringValue); + if (addResource) + { + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.Resource.ServiceNameKey && kvp.Value.StringValue == "service-name"); + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.Resource.ServiceNamespaceKey && kvp.Value.StringValue == "ns1"); + } + else + { + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.Resource.ServiceNameKey && kvp.Value.StringValue == optionsServiceName); + } foreach (var instrumentationLibrarySpans in request.ResourceSpans.First().InstrumentationLibrarySpans) { @@ -387,5 +413,13 @@ private static void AssertOtlpAttributeValue(object originalValue, OtlpCommon.Ke break; } } + + private class NoopTraceServiceClient : OtlpCollector.TraceService.ITraceServiceClient + { + public OtlpCollector.ExportTraceServiceResponse Export(OtlpCollector.ExportTraceServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + return null; + } + } } }