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/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
diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs
index 8762c0c1ad6..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,10 +37,10 @@ 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,
+ OtlpExporter otlpExporter,
in Batch activityBatch)
{
Dictionary spansByLibrary = new Dictionary();
@@ -52,14 +50,10 @@ internal static void AddBatch(
{
if (resourceSpans == null)
{
- resourceSpans = new OtlpTrace.ResourceSpans();
-
- if (processResource == null)
+ resourceSpans = new OtlpTrace.ResourceSpans
{
- BuildProcessResource(activity.GetResource());
- }
-
- resourceSpans.Resource = processResource;
+ Resource = otlpExporter.EnsureProcessResource(activity),
+ };
request.ResourceSpans.Add(resourceSpans);
}
@@ -101,22 +95,6 @@ internal static void Return(this OtlpCollector.ExportTraceServiceRequest request
}
}
- internal static void BuildProcessResource(Resource resource)
- {
- OtlpResource.Resource processResource = new OtlpResource.Resource();
-
- foreach (KeyValuePair attribute in resource.Attributes)
- {
- var oltpAttribute = ToOtlpAttribute(attribute);
- if (oltpAttribute != null)
- {
- processResource.Attributes.Add(oltpAttribute);
- }
- }
-
- ActivityExtensions.processResource = processResource;
- }
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static OtlpTrace.InstrumentationLibrarySpans GetSpanListFromPool(string name, string version)
{
@@ -227,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)
{
@@ -308,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 c7fa3117808..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
{
@@ -29,9 +36,11 @@ 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;
+ private OtlpResource.Resource processResource;
///
/// Initializes a new instance of the class.
@@ -40,7 +49,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 +67,7 @@ public override ExportResult Export(in Batch activityBatch)
{
OtlpCollector.ExportTraceServiceRequest request = new OtlpCollector.ExportTraceServiceRequest();
- request.AddBatch(activityBatch);
+ request.AddBatch(this, activityBatch);
try
{
@@ -77,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/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.
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;
+ }
+ }
}
}