Skip to content

Commit 13dac00

Browse files
authored
feat(csharp/src): Add support for adding and configuring OTel exporters (#2949)
Adds a new project and tests to support adding OpenTelemetry exporters (TracerProvider). - Adds an ADBC `FileExporter` implementation. - Adds a `ExportersBuilder` that - Builds a list of supported exporters - Activates and returns the appropriate exporter based on the passed option or environment variable setting. Note to reviewer: `ExportersBuilder` is a convenience class - let me know if you think it is useful.
1 parent 7f65c52 commit 13dac00

16 files changed

+1870
-0
lines changed

csharp/Apache.Arrow.Adbc.sln

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Arrow.Adbc.Drivers.D
4242
EndProject
4343
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Arrow.Adbc.Tests.Drivers.Databricks", "test\Drivers\Databricks\Apache.Arrow.Adbc.Tests.Drivers.Databricks.csproj", "{BA07EB2C-5246-EB72-153C-493C7E7412D2}"
4444
EndProject
45+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
46+
EndProject
47+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Traces", "Traces", "{22EF23A3-1566-446F-B696-9323F3B6F56C}"
48+
EndProject
49+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exporters", "Exporters", "{4A0C233A-90A0-42ED-8D8A-F21E7D5BB7EB}"
50+
EndProject
51+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Arrow.Adbc.Telemetry.Traces.Exporters", "src\Telemetry\Traces\Exporters\Apache.Arrow.Adbc.Telemetry.Traces.Exporters.csproj", "{85D2EC10-F461-02F4-83B6-E4FC320C244F}"
52+
EndProject
53+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{9FE39661-2A39-4E9F-A5F2-11FB0D54CB42}"
54+
EndProject
55+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Traces", "Traces", "{B74532A7-8A78-4AD9-9B2E-584765491E48}"
56+
EndProject
57+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exporters", "Exporters", "{43910445-FEC4-4AEC-A698-6A48327600C3}"
58+
EndProject
59+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Arrow.Adbc.Tests.Telemetry.Traces.Exporters", "test\Telemetry\Traces\Exporters\Apache.Arrow.Adbc.Tests.Telemetry.Traces.Exporters.csproj", "{1558BC4B-6E76-434B-8877-6C49B1460544}"
60+
EndProject
4561
Global
4662
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4763
Debug|Any CPU = Debug|Any CPU
@@ -112,6 +128,14 @@ Global
112128
{BA07EB2C-5246-EB72-153C-493C7E7412D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
113129
{BA07EB2C-5246-EB72-153C-493C7E7412D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
114130
{BA07EB2C-5246-EB72-153C-493C7E7412D2}.Release|Any CPU.Build.0 = Release|Any CPU
131+
{85D2EC10-F461-02F4-83B6-E4FC320C244F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
132+
{85D2EC10-F461-02F4-83B6-E4FC320C244F}.Debug|Any CPU.Build.0 = Debug|Any CPU
133+
{85D2EC10-F461-02F4-83B6-E4FC320C244F}.Release|Any CPU.ActiveCfg = Release|Any CPU
134+
{85D2EC10-F461-02F4-83B6-E4FC320C244F}.Release|Any CPU.Build.0 = Release|Any CPU
135+
{1558BC4B-6E76-434B-8877-6C49B1460544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
136+
{1558BC4B-6E76-434B-8877-6C49B1460544}.Debug|Any CPU.Build.0 = Debug|Any CPU
137+
{1558BC4B-6E76-434B-8877-6C49B1460544}.Release|Any CPU.ActiveCfg = Release|Any CPU
138+
{1558BC4B-6E76-434B-8877-6C49B1460544}.Release|Any CPU.Build.0 = Release|Any CPU
115139
EndGlobalSection
116140
GlobalSection(SolutionProperties) = preSolution
117141
HideSolutionNode = FALSE
@@ -133,6 +157,13 @@ Global
133157
{BAF2CF14-BA77-429E-AF54-A34B978E9F5C} = {5BD04C26-CE52-4893-8C1A-479705195CEF}
134158
{25042111-6B86-8B75-7EF6-5BFAA36F72B1} = {FEB257A0-4FD3-495E-9A47-9E1649755445}
135159
{BA07EB2C-5246-EB72-153C-493C7E7412D2} = {C7290227-E925-47E7-8B6B-A8B171645D58}
160+
{22EF23A3-1566-446F-B696-9323F3B6F56C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
161+
{4A0C233A-90A0-42ED-8D8A-F21E7D5BB7EB} = {22EF23A3-1566-446F-B696-9323F3B6F56C}
162+
{85D2EC10-F461-02F4-83B6-E4FC320C244F} = {4A0C233A-90A0-42ED-8D8A-F21E7D5BB7EB}
163+
{9FE39661-2A39-4E9F-A5F2-11FB0D54CB42} = {5BD04C26-CE52-4893-8C1A-479705195CEF}
164+
{B74532A7-8A78-4AD9-9B2E-584765491E48} = {9FE39661-2A39-4E9F-A5F2-11FB0D54CB42}
165+
{43910445-FEC4-4AEC-A698-6A48327600C3} = {B74532A7-8A78-4AD9-9B2E-584765491E48}
166+
{1558BC4B-6E76-434B-8877-6C49B1460544} = {43910445-FEC4-4AEC-A698-6A48327600C3}
136167
EndGlobalSection
137168
GlobalSection(ExtensibilityGlobals) = postSolution
138169
SolutionGuid = {4795CF16-0FDB-4BE0-9768-5CF31564DC03}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
5+
<Nullable>enable</Nullable>
6+
<PackageReadmeFile>readme.md</PackageReadmeFile>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Content Include="readme.md">
10+
<Pack>true</Pack>
11+
<PackagePath>\</PackagePath>
12+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
13+
</Content>
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
18+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\..\..\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using System;
19+
using System.Collections.Generic;
20+
using Apache.Arrow.Adbc.Telemetry.Traces.Exporters.FileExporter;
21+
using OpenTelemetry;
22+
using OpenTelemetry.Resources;
23+
using OpenTelemetry.Trace;
24+
25+
namespace Apache.Arrow.Adbc.Telemetry.Traces.Exporters
26+
{
27+
public class ExportersBuilder
28+
{
29+
private static readonly IReadOnlyDictionary<string, Func<string, string?, TracerProvider?>> s_tracerProviderFactoriesDefault;
30+
31+
private readonly string _sourceName;
32+
private readonly string? _sourceVersion;
33+
private readonly IReadOnlyDictionary<string, Func<string, string?, TracerProvider?>> _tracerProviderFactories;
34+
35+
static ExportersBuilder()
36+
{
37+
var defaultProviders = new Dictionary<string, Func<string, string?, TracerProvider?>>
38+
{
39+
[ExportersOptions.Exporters.None] = NewNoopTracerProvider,
40+
[ExportersOptions.Exporters.Otlp] = NewOtlpTracerProvider,
41+
[ExportersOptions.Exporters.Console] = NewConsoleTracerProvider,
42+
[ExportersOptions.Exporters.AdbcFile] = NewAdbcFileTracerProvider,
43+
};
44+
s_tracerProviderFactoriesDefault = defaultProviders;
45+
}
46+
47+
private ExportersBuilder(Builder builder)
48+
{
49+
_sourceName = builder.SourceName;
50+
_sourceVersion = builder.SourceVersion;
51+
_tracerProviderFactories = builder.TracerProviderFactories;
52+
}
53+
54+
/// <summary>
55+
/// Build an <see cref="ExportersBuilder"/> from different possible Exporters. Use the Add* functions to add
56+
/// one or more <see cref="TracerProvider"/> factories.
57+
/// </summary>
58+
/// <param name="sourceName">The name of the source that the exporter will filter on.</param>
59+
/// <param name="sourceVersion">The (optional) version of the source that the exporter will filter on.</param>
60+
/// <returns>A <see cref="Builder"/> object.</returns>
61+
/// <exception cref="ArgumentNullException"></exception>
62+
public static Builder Build(string sourceName, string? sourceVersion = default, bool addDefaultExporters = false)
63+
{
64+
if (string.IsNullOrWhiteSpace(sourceName))
65+
{
66+
throw new ArgumentNullException(nameof(sourceName));
67+
}
68+
return new Builder(sourceName, sourceVersion, addDefaultExporters);
69+
}
70+
71+
/// <summary>
72+
/// <para>
73+
/// Attempts to activate an exporter based on the dictionary of <see cref="TracerProvider"/> factories
74+
/// added to the <see cref="Builder"/>. If the value of exporterOption is not null and not empty, it will be
75+
/// used as the key to the dictionary. If exporterOption is null or empty, then the exporter option will
76+
/// check the environment variable identified by environmentName (default <see cref="ExportersOptions.Environment.Exporter"/>).
77+
/// If both the exporterOption and the environment variable value are null or empty, then this function will return null
78+
/// and no exporeter will be activated.
79+
/// </para>
80+
/// <para>
81+
/// If the exporterOption or the value of the environment variable are not null and not empty, then
82+
/// if a matching factory delegate is found, it is called to activate the exporter. If no exception is thrown,
83+
/// the result of the factory method returns the result which may be null. If a matching function is found, the expoertName is set.
84+
/// If the factory delegate throws an exception it is not caught.
85+
/// </para>
86+
/// </summary>
87+
/// <param name="exporterOption">The value (name) of the exporter option, typically passed as option <see cref="ExportersOptions.Exporter"/>.</param>
88+
/// <param name="exporterName">The actual exporter name when successfully activated.</param>
89+
/// <param name="environmentName">The (optional) name of the environment variable to test for the exporter name. Default: <see cref="ExportersOptions.Environment.Exporter"/></param>
90+
/// <exception cref="AdbcException" />
91+
/// <returns>The a non-null <see cref="TracerProvider"/> when successfully activated. Note: this object must be explicitly disposed when no longer necessary.</returns>
92+
public TracerProvider? Activate(
93+
string? exporterOption,
94+
out string? exporterName,
95+
string environmentName = ExportersOptions.Environment.Exporter)
96+
{
97+
TracerProvider? tracerProvider = null;
98+
exporterName = null;
99+
100+
if (string.IsNullOrWhiteSpace(exporterOption))
101+
{
102+
// Fall back to check the environment variable
103+
exporterOption = Environment.GetEnvironmentVariable(environmentName);
104+
}
105+
if (string.IsNullOrWhiteSpace(exporterOption))
106+
{
107+
// Neither option or environment variable is set - no tracer provider will be activated.
108+
return null;
109+
}
110+
111+
if (!_tracerProviderFactories.TryGetValue(exporterOption!, out Func<string, string?, TracerProvider?>? factory))
112+
{
113+
// Requested option has not been added via the builder
114+
throw AdbcException.NotImplemented($"Exporter option '{exporterOption}' is not implemented.");
115+
}
116+
117+
tracerProvider = factory.Invoke(_sourceName, _sourceVersion);
118+
if (tracerProvider != null)
119+
{
120+
exporterName = exporterOption;
121+
}
122+
return tracerProvider;
123+
}
124+
125+
public static TracerProvider NewAdbcFileTracerProvider(string sourceName, string? sourceVersion) =>
126+
Sdk.CreateTracerProviderBuilder()
127+
.AddSource(sourceName)
128+
.ConfigureResource(resource =>
129+
resource.AddService(
130+
serviceName: sourceName,
131+
serviceVersion: sourceVersion))
132+
.AddAdbcFileExporter(sourceName)
133+
.Build();
134+
135+
public static TracerProvider NewConsoleTracerProvider(string sourceName, string? sourceVersion) =>
136+
Sdk.CreateTracerProviderBuilder()
137+
.AddSource(sourceName)
138+
.ConfigureResource(resource =>
139+
resource.AddService(
140+
serviceName: sourceName,
141+
serviceVersion: sourceVersion))
142+
.AddConsoleExporter()
143+
.Build();
144+
145+
public static TracerProvider NewOtlpTracerProvider(string sourceName, string? sourceVersion) =>
146+
Sdk.CreateTracerProviderBuilder()
147+
.AddSource(sourceName)
148+
.ConfigureResource(resource =>
149+
resource.AddService(
150+
serviceName: sourceName,
151+
serviceVersion: sourceVersion))
152+
.AddOtlpExporter()
153+
.Build();
154+
155+
public static TracerProvider? NewNoopTracerProvider(string sourceName, string? sourceVersion) =>
156+
null;
157+
158+
public class Builder
159+
{
160+
private readonly string _sourceName;
161+
private readonly string? _sourceVersion;
162+
private readonly Dictionary<string, Func<string, string?, TracerProvider?>> _tracerProviderFactories;
163+
164+
internal string SourceName => _sourceName;
165+
166+
internal string? SourceVersion => _sourceVersion;
167+
168+
internal IReadOnlyDictionary<string, Func<string, string?, TracerProvider?>> TracerProviderFactories => _tracerProviderFactories;
169+
170+
public Builder(string sourceName, string? sourceVersion, bool addDefaultExporters = false)
171+
{
172+
_sourceName = sourceName;
173+
_sourceVersion = sourceVersion;
174+
_tracerProviderFactories = [];
175+
if (addDefaultExporters)
176+
{
177+
AddDefaultExporters();
178+
}
179+
}
180+
181+
public ExportersBuilder Build()
182+
{
183+
if (_tracerProviderFactories.Count == 0)
184+
{
185+
throw new InvalidOperationException("No options provided. Please add one or more exporter.");
186+
}
187+
return new ExportersBuilder(this);
188+
}
189+
190+
public Builder AddExporter(string exporterName, Func<string, string?, TracerProvider?> tracerProviderFactory)
191+
{
192+
if (string.IsNullOrWhiteSpace(exporterName))
193+
{
194+
throw new ArgumentNullException(nameof(exporterName));
195+
}
196+
197+
_tracerProviderFactories.Add(exporterName, tracerProviderFactory);
198+
return this;
199+
}
200+
201+
private Builder AddDefaultExporters()
202+
{
203+
foreach (KeyValuePair<string, Func<string, string?, TracerProvider?>> item in s_tracerProviderFactoriesDefault)
204+
{
205+
_tracerProviderFactories[item.Key] = item.Value;
206+
}
207+
return this;
208+
}
209+
}
210+
}
211+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace Apache.Arrow.Adbc.Telemetry.Traces.Exporters
19+
{
20+
public class ExportersOptions
21+
{
22+
public const string Exporter = "adbc.traces.exporter";
23+
24+
public static class Environment
25+
{
26+
public const string Exporter = "OTEL_TRACES_EXPORTER";
27+
}
28+
29+
public static class Exporters
30+
{
31+
public const string None = "none";
32+
public const string Otlp = "otlp";
33+
public const string Console = "console";
34+
public const string AdbcFile = "adbcfile";
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)