Skip to content

Commit 0c01cc7

Browse files
pjanotticijothomas
andauthored
Add OTLP for Activity Exporter (#679)
* Add OTLP Exporter for Activity * Initial PR feedback * Check ID format and use DisplayName * Skip the activity instances that could not be translated Co-authored-by: Cijo Thomas <[email protected]>
1 parent 1b1902e commit 0c01cc7

File tree

17 files changed

+928
-65
lines changed

17 files changed

+928
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// <copyright file="InstrumentationWithActivitySource.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.Diagnostics;
20+
using System.Globalization;
21+
using System.IO;
22+
using System.Net;
23+
using System.Net.Http;
24+
using System.Text;
25+
using System.Threading;
26+
using System.Threading.Tasks;
27+
28+
namespace Samples
29+
{
30+
internal class InstrumentationWithActivitySource : IDisposable
31+
{
32+
private const string RequestPath = "/api/request";
33+
private SampleServer server = new SampleServer();
34+
private SampleClient client = new SampleClient();
35+
36+
public void Start(ushort port = 19999)
37+
{
38+
var url = $"http://localhost:{port.ToString(CultureInfo.InvariantCulture)}{RequestPath}/";
39+
this.server.Start(url);
40+
this.client.Start(url);
41+
}
42+
43+
public void Dispose()
44+
{
45+
this.client.Dispose();
46+
this.server.Dispose();
47+
}
48+
49+
private class SampleServer : IDisposable
50+
{
51+
private HttpListener listener = new HttpListener();
52+
53+
public void Start(string url)
54+
{
55+
this.listener.Prefixes.Add(url);
56+
this.listener.Start();
57+
58+
Task.Run(() =>
59+
{
60+
using var source = new ActivitySource("Samples.SampleServer");
61+
62+
while (this.listener.IsListening)
63+
{
64+
try
65+
{
66+
var context = this.listener.GetContext();
67+
68+
using var activity = source.StartActivity(
69+
$"{context.Request.HttpMethod}:{context.Request.Url.AbsolutePath}",
70+
ActivityKind.Server);
71+
72+
var headerKeys = context.Request.Headers.AllKeys;
73+
foreach (var headerKey in headerKeys)
74+
{
75+
string headerValue = context.Request.Headers[headerKey];
76+
activity?.AddTag($"http.header.{headerKey}", headerValue);
77+
}
78+
79+
string requestContent;
80+
using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding))
81+
{
82+
requestContent = reader.ReadToEnd();
83+
}
84+
85+
activity?.AddTag("request.content", requestContent);
86+
activity?.AddTag("request.length", requestContent.Length.ToString());
87+
88+
var echo = Encoding.UTF8.GetBytes("echo: " + requestContent);
89+
context.Response.ContentEncoding = Encoding.UTF8;
90+
context.Response.ContentLength64 = echo.Length;
91+
context.Response.OutputStream.Write(echo, 0, echo.Length);
92+
context.Response.Close();
93+
}
94+
catch (Exception)
95+
{
96+
// expected when closing the listener.
97+
}
98+
}
99+
});
100+
}
101+
102+
public void Dispose()
103+
{
104+
((IDisposable)this.listener).Dispose();
105+
}
106+
}
107+
108+
private class SampleClient : IDisposable
109+
{
110+
private CancellationTokenSource cts;
111+
private Task requestTask;
112+
113+
public void Start(string url)
114+
{
115+
this.cts = new CancellationTokenSource();
116+
var cancellationToken = this.cts.Token;
117+
118+
this.requestTask = Task.Run(async () =>
119+
{
120+
using var source = new ActivitySource("Samples.SampleClient");
121+
using var client = new HttpClient();
122+
123+
var count = 1;
124+
while (!cancellationToken.IsCancellationRequested)
125+
{
126+
var content = new StringContent($"client message: {DateTime.Now}", Encoding.UTF8);
127+
128+
using (var activity = source.StartActivity("POST:" + RequestPath, ActivityKind.Client))
129+
{
130+
count++;
131+
132+
activity?.AddEvent(new ActivityEvent("PostAsync:Started"));
133+
using var response = await client.PostAsync(url, content, cancellationToken).ConfigureAwait(false);
134+
activity?.AddEvent(new ActivityEvent("PostAsync:Ended"));
135+
136+
activity?.AddTag("http.status_code", $"{response.StatusCode:D}");
137+
138+
var responseContent = await response.Content.ReadAsStringAsync();
139+
activity?.AddTag("response.content", responseContent);
140+
activity?.AddTag("response.length", responseContent.Length.ToString(CultureInfo.InvariantCulture));
141+
142+
foreach (var header in response.Headers)
143+
{
144+
if (header.Value is IEnumerable<object> enumerable)
145+
{
146+
activity?.AddTag($"http.header.{header.Key}", string.Join(",", enumerable));
147+
}
148+
else
149+
{
150+
activity?.AddTag($"http.header.{header.Key}", header.Value.ToString());
151+
}
152+
}
153+
}
154+
155+
try
156+
{
157+
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
158+
}
159+
catch (TaskCanceledException)
160+
{
161+
return;
162+
}
163+
}
164+
},
165+
cancellationToken);
166+
}
167+
168+
public void Dispose()
169+
{
170+
if (this.cts != null)
171+
{
172+
this.cts.Cancel();
173+
this.requestTask.Wait();
174+
this.requestTask.Dispose();
175+
this.cts.Dispose();
176+
}
177+
}
178+
}
179+
}
180+
}

samples/Exporters/Console/Program.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static void Main(string[] args)
4747
(ZPagesOptions options) => TestZPages.Run(),
4848
(ConsoleOptions options) => TestConsole.Run(options),
4949
(ConsoleActivityOptions options) => TestConsoleActivity.Run(options),
50-
(OtlpOptions options) => TestOtlp.Run(options.Endpoint),
50+
(OtlpOptions options) => TestOtlp.Run(options.Endpoint, options.UseActivitySource),
5151
errs => 1);
5252

5353
Console.ReadLine();
@@ -122,6 +122,9 @@ internal class OtlpOptions
122122
{
123123
[Option('e', "endpoint", HelpText = "Target to which the exporter is going to send traces or metrics", Default = "localhost:55680")]
124124
public string Endpoint { get; set; }
125+
126+
[Option('a', "activity", HelpText = "Set it to true to export ActivitySource data", Default = false)]
127+
public bool UseActivitySource { get; set; }
125128
}
126129

127130
#pragma warning restore SA1402 // File may only contain a single type

samples/Exporters/Console/TestOtlp.cs

+34-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,17 @@ namespace Samples
2424
{
2525
internal static class TestOtlp
2626
{
27-
internal static object Run(string endpoint)
27+
internal static object Run(string endpoint, bool useActivitySource)
28+
{
29+
if (useActivitySource)
30+
{
31+
return RunWithActivitySource(endpoint);
32+
}
33+
34+
return RunWithSdk(endpoint);
35+
}
36+
37+
private static object RunWithSdk(string endpoint)
2838
{
2939
using var tracerFactory = TracerFactory.Create(builder => builder
3040
.SetResource(Resources.CreateServiceResource("otlp-test"))
@@ -47,5 +57,28 @@ internal static object Run(string endpoint)
4757

4858
return null;
4959
}
60+
61+
private static object RunWithActivitySource(string endpoint)
62+
{
63+
// Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient"
64+
// and use OTLP exporter.
65+
OpenTelemetrySdk.EnableOpenTelemetry(
66+
builder => builder
67+
.AddActivitySource("Samples.SampleServer")
68+
.AddActivitySource("Samples.SampleClient")
69+
.UseOpenTelemetryProtocolActivityExporter(opt => opt.Endpoint = endpoint));
70+
71+
// The above line is required only in Applications
72+
// which decide to use OT.
73+
using (var sample = new InstrumentationWithActivitySource())
74+
{
75+
sample.Start();
76+
77+
Console.WriteLine("Sample is running on the background, press ENTER to stop");
78+
Console.ReadLine();
79+
}
80+
81+
return null;
82+
}
5083
}
5184
}

src/OpenTelemetry.Exporter.Console/ConsoleActivityExporter.cs

+14
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public override Task<ExportResult> ExportAsync(IEnumerable<Activity> activityBat
6262

6363
System.Console.WriteLine("Activity OperationName - " + activity.OperationName);
6464
System.Console.WriteLine("Activity DisplayName - " + activity.DisplayName);
65+
System.Console.WriteLine("Activity Kind - " + activity.Kind);
6566
System.Console.WriteLine("Activity StartTime - " + activity.StartTimeUtc);
6667
System.Console.WriteLine("Activity Duration - " + activity.Duration);
6768
if (activity.Tags.Count() > 0)
@@ -79,6 +80,19 @@ public override Task<ExportResult> ExportAsync(IEnumerable<Activity> activityBat
7980
foreach (var activityEvent in activity.Events)
8081
{
8182
System.Console.WriteLine($"Event Name: {activityEvent.Name} TimeStamp: {activityEvent.Timestamp}");
83+
foreach (var attribute in activityEvent.Attributes)
84+
{
85+
System.Console.WriteLine($"\t {attribute.Key} : {attribute.Value}");
86+
}
87+
}
88+
}
89+
90+
if (activity.Baggage.Any())
91+
{
92+
System.Console.WriteLine("Activity Baggage");
93+
foreach (var baggage in activity.Baggage)
94+
{
95+
System.Console.WriteLine($"\t {baggage.Key} : {baggage.Value}");
8296
}
8397
}
8498

0 commit comments

Comments
 (0)