Skip to content

Commit e7f06af

Browse files
authored
OTLP exporter: Standardize handling of attributes (#3262)
1 parent a53900f commit e7f06af

File tree

5 files changed

+394
-35
lines changed

5 files changed

+394
-35
lines changed

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ option
1818
* Fix handling of array-valued attributes for the OTLP trace exporter.
1919
([#3238](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3238))
2020

21+
* Improve the conversion and formatting of attribute values to the OTLP format
22+
for resources, metrics, and logs. The list of data types that must be
23+
supported per the
24+
[OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/common#attribute)
25+
is more narrow than what the .NET OpenTelemetry SDK supports. Numeric
26+
[built-in value types](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/built-in-types)
27+
are supported by converting to a `long` or `double` as appropriate except for
28+
numeric types that could cause overflow (`ulong`) or rounding (`decimal`)
29+
which are converted to strings. Non-numeric built-in types - `string`,
30+
`char`, `bool` are supported. All other types are converted to a `string`.
31+
Array values are also supported.
32+
([#3262](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3262))
33+
2134
## 1.3.0-beta.1
2235

2336
Released 2022-Apr-15

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -201,41 +201,6 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity)
201201
return otlpSpan;
202202
}
203203

204-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
205-
internal static OtlpCommon.KeyValue ToOtlpAttribute(this KeyValuePair<string, object> kvp)
206-
{
207-
if (kvp.Value == null)
208-
{
209-
return null;
210-
}
211-
212-
var attrib = new OtlpCommon.KeyValue { Key = kvp.Key, Value = new OtlpCommon.AnyValue { } };
213-
214-
switch (kvp.Value)
215-
{
216-
case string s:
217-
attrib.Value.StringValue = s;
218-
break;
219-
case bool b:
220-
attrib.Value.BoolValue = b;
221-
break;
222-
case int i:
223-
attrib.Value.IntValue = i;
224-
break;
225-
case long l:
226-
attrib.Value.IntValue = l;
227-
break;
228-
case double d:
229-
attrib.Value.DoubleValue = d;
230-
break;
231-
default:
232-
attrib.Value.StringValue = kvp.Value.ToString();
233-
break;
234-
}
235-
236-
return attrib;
237-
}
238-
239204
[MethodImpl(MethodImplOptions.AggressiveInlining)]
240205
private static OtlpTrace.Status ToOtlpStatus(this Activity activity, ref TagEnumerationState otlpTags)
241206
{

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,11 @@ public void CouldNotTranslateLogRecord(string exceptionMessage)
7979
{
8080
this.WriteEvent(9, exceptionMessage);
8181
}
82+
83+
[Event(10, Message = "Unsupported attribute type '{0}' for '{1}'. Attribute will not be exported.", Level = EventLevel.Warning)]
84+
public void UnsupportedAttributeType(string type, string key)
85+
{
86+
this.WriteEvent(10, type.ToString(), key);
87+
}
8288
}
8389
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// <copyright file="OtlpCommonExtensions.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Runtime.CompilerServices;
20+
using OtlpCommon = Opentelemetry.Proto.Common.V1;
21+
22+
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
23+
{
24+
internal static class OtlpCommonExtensions
25+
{
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
public static OtlpCommon.KeyValue ToOtlpAttribute(this KeyValuePair<string, object> kvp)
28+
{
29+
if (kvp.Value == null)
30+
{
31+
return null;
32+
}
33+
34+
var value = ToOtlpValue(kvp.Value);
35+
36+
if (value == null)
37+
{
38+
OpenTelemetryProtocolExporterEventSource.Log.UnsupportedAttributeType(kvp.Value.GetType().ToString(), kvp.Key);
39+
return null;
40+
}
41+
42+
return new OtlpCommon.KeyValue { Key = kvp.Key, Value = value };
43+
}
44+
45+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
46+
private static OtlpCommon.AnyValue ToOtlpValue(object value)
47+
{
48+
switch (value)
49+
{
50+
case char:
51+
case string:
52+
return new OtlpCommon.AnyValue { StringValue = Convert.ToString(value) };
53+
case bool b:
54+
return new OtlpCommon.AnyValue { BoolValue = b };
55+
case byte:
56+
case sbyte:
57+
case short:
58+
case ushort:
59+
case int:
60+
case uint:
61+
case long:
62+
return new OtlpCommon.AnyValue { IntValue = Convert.ToInt64(value) };
63+
case float:
64+
case double:
65+
return new OtlpCommon.AnyValue { DoubleValue = Convert.ToDouble(value) };
66+
case Array array:
67+
return ToOtlpArrayValue(array);
68+
69+
// All other types are converted to strings including the following
70+
// built-in value types:
71+
// case nint: Pointer type.
72+
// case nuint: Pointer type.
73+
// case ulong: May throw an exception on overflow.
74+
// case decimal: Converting to double produces rounding errors.
75+
default:
76+
try
77+
{
78+
return new OtlpCommon.AnyValue { StringValue = value.ToString() };
79+
}
80+
catch
81+
{
82+
return null;
83+
}
84+
}
85+
}
86+
87+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88+
private static OtlpCommon.AnyValue ToOtlpArrayValue(Array array)
89+
{
90+
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
91+
var arrayValue = new OtlpCommon.ArrayValue();
92+
switch (array)
93+
{
94+
case char[]:
95+
case string[]:
96+
case bool[]:
97+
case byte[]:
98+
case sbyte[]:
99+
case short[]:
100+
case ushort[]:
101+
case int[]:
102+
case uint[]:
103+
case long[]:
104+
case float[]:
105+
case double[]:
106+
foreach (var item in array)
107+
{
108+
arrayValue.Values.Add(ToOtlpValue(item));
109+
}
110+
111+
return new OtlpCommon.AnyValue { ArrayValue = arrayValue };
112+
default:
113+
foreach (var item in array)
114+
{
115+
try
116+
{
117+
arrayValue.Values.Add(ToOtlpValue(item.ToString()));
118+
}
119+
catch
120+
{
121+
return null;
122+
}
123+
}
124+
125+
return new OtlpCommon.AnyValue { ArrayValue = arrayValue };
126+
}
127+
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)