Skip to content
This repository was archived by the owner on Dec 18, 2023. It is now read-only.

Commit f9a47eb

Browse files
Simon ZeltserSergeyKanzhelev
authored andcommitted
Introducing Stackdriver Exporter for traces (#48)
* Introducing Stackdriver Exporter for Opencensus C# library - Current implementation can only store string values - Added the exporter and trace handler only - The exporter relies on newest Trace API from Stackdriver. * Updating translation from ISpan to Stackdriver's Span to cover more fields * Fixing the issue that prevented Stackdriver API call to succeed: now construction of Span resource is taken care of by SpanName class that is part of Stackdriver Trace V2 API.
1 parent a32e580 commit f9a47eb

File tree

10 files changed

+308
-5
lines changed

10 files changed

+308
-5
lines changed

OpenCensus.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vsts", ".vsts", "{61188153
3636
EndProject
3737
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.ApplicationInsights", "src\OpenCensus.Exporter.ApplicationInsights\OpenCensus.Exporter.ApplicationInsights.csproj", "{4493F5D9-874E-4FBF-B2F3-37890BD910E0}"
3838
EndProject
39+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Stackdriver", "src\OpenCensus.Exporter.Stackdriver\OpenCensus.Exporter.Stackdriver.csproj", "{DE1B4783-C01F-4672-A6EB-695F1717105B}"
40+
EndProject
3941
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "src\Samples\Samples.csproj", "{C58393EB-32E2-4AC6-9170-697B36306E15}"
4042
EndProject
4143
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Abstractions", "src\OpenCensus.Abstractions\OpenCensus.Abstractions.csproj", "{99F8A331-05E9-45A5-89BA-4C54E825E5B2}"
@@ -52,7 +54,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.AspNet
5254
EndProject
5355
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testdata", "testdata", "{77C7929A-2EED-4AA6-8705-B5C443C8AA0F}"
5456
EndProject
55-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp.AspNetCore.2.0", "test\TestApp.AspNetCore.2.0\TestApp.AspNetCore.2.0.csproj", "{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}"
57+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.2.0", "test\TestApp.AspNetCore.2.0\TestApp.AspNetCore.2.0.csproj", "{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}"
5658
EndProject
5759
Global
5860
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -76,6 +78,10 @@ Global
7678
{4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
7779
{4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
7880
{4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Release|Any CPU.Build.0 = Release|Any CPU
81+
{DE1B4783-C01F-4672-A6EB-695F1717105B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
82+
{DE1B4783-C01F-4672-A6EB-695F1717105B}.Debug|Any CPU.Build.0 = Debug|Any CPU
83+
{DE1B4783-C01F-4672-A6EB-695F1717105B}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{DE1B4783-C01F-4672-A6EB-695F1717105B}.Release|Any CPU.Build.0 = Release|Any CPU
7985
{C58393EB-32E2-4AC6-9170-697B36306E15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
8086
{C58393EB-32E2-4AC6-9170-697B36306E15}.Debug|Any CPU.Build.0 = Debug|Any CPU
8187
{C58393EB-32E2-4AC6-9170-697B36306E15}.Release|Any CPU.ActiveCfg = Release|Any CPU

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ finally
7979
}
8080
```
8181

82+
### Using Stackdriver Exporter
83+
84+
1. Enable [Stackdriver Trace][stackdriver-setup] resource.
85+
2. Instantiate a new instance of `StackdriverExporter` with your Google Cloud's ProjectId
86+
3. See [sample][stackdriver-sample] for example use.
87+
88+
``` csharp
89+
var exporter = new StackdriverExporter("YOUR-GOOGLE-PROJECT-ID", Tracing.ExportComponent);
90+
exporter.Start();
91+
```
92+
8293
### Using Application Insights exporter
8394

8495
1. Create [Application Insights][ai-get-started] resource.
@@ -127,8 +138,10 @@ deprecate it for 18 months before removing it, if possible.
127138
[good-first-issues]: https://github.com/census-instrumentation/opencensus-csharp/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
128139
[zipkin-get-started]: https://zipkin.io/pages/quickstart.html
129140
[ai-get-started]: https://docs.microsoft.com/azure/application-insights
141+
[stackdriver-setup]: https://cloud.google.com/trace/docs/setup/
130142
[semver]: http://semver.org/
131143
[ai-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestApplicationInsights.cs
144+
[stackdriver-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestStackdriver.cs
132145
[zipkin-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestZipkin.cs
133146
[prometheus-get-started]: https://prometheus.io/docs/introduction/first_steps/
134147
[prometheus-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestPrometheus.cs
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+

2+
namespace OpenCensus.Exporter.Stackdriver.Implementation
3+
{
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Google.Cloud.Trace.V2;
7+
using OpenCensus.Exporter.Stackdriver.Utils;
8+
using OpenCensus.Trace;
9+
using OpenCensus.Trace.Export;
10+
11+
static class SpanExtensions
12+
{
13+
/// <summary>
14+
/// Translating <see cref="ISpanData"/> to Stackdriver's Span
15+
/// According to <see cref="https://cloud.google.com/trace/docs/reference/v2/rpc/google.devtools.cloudtrace.v2"/> specifications
16+
/// </summary>
17+
/// <param name="spanData">Span in OpenCensus format</param>
18+
/// <param name="projectId">Google Cloud Platform Project Id</param>
19+
/// <returns></returns>
20+
public static Span ToSpan(this ISpanData spanData, string projectId)
21+
{
22+
string spanId = spanData.Context.SpanId.ToLowerBase16();
23+
var span = new Span
24+
{
25+
SpanName = new SpanName(projectId, spanData.Context.TraceId.ToLowerBase16(), spanId),
26+
SpanId = spanId,
27+
DisplayName = new TruncatableString { Value = spanData.Name },
28+
StartTime = spanData.StartTimestamp.ToTimestamp(),
29+
EndTime = spanData.EndTimestamp.ToTimestamp(),
30+
ChildSpanCount = spanData.ChildSpanCount,
31+
};
32+
33+
if (spanData.Attributes != null)
34+
{
35+
span.Attributes = new Span.Types.Attributes
36+
{
37+
DroppedAttributesCount = spanData.Attributes != null ? spanData.Attributes.DroppedAttributesCount : 0,
38+
39+
AttributeMap = { spanData.Attributes?.AttributeMap?.ToDictionary(
40+
s => s.Key,
41+
s => s.Value?.ToAttributeValue()) },
42+
};
43+
}
44+
45+
if (spanData.ParentSpanId != null)
46+
{
47+
string parentSpanId = spanData.ParentSpanId.ToLowerBase16();
48+
if (!string.IsNullOrEmpty(parentSpanId))
49+
{
50+
span.ParentSpanId = parentSpanId;
51+
}
52+
}
53+
54+
return span;
55+
}
56+
57+
public static Google.Cloud.Trace.V2.AttributeValue ToAttributeValue(this IAttributeValue av)
58+
{
59+
// TODO Currently we assume we store only strings.
60+
return new Google.Cloud.Trace.V2.AttributeValue
61+
{
62+
StringValue = new TruncatableString
63+
{
64+
Value = av.Match(
65+
s => s,
66+
b => b.ToString(),
67+
l => l.ToString(),
68+
obj => obj.ToString(),
69+
obj => obj.ToString())
70+
}
71+
};
72+
}
73+
}
74+
75+
/// <summary>
76+
/// Exports a group of spans to Stackdriver
77+
/// </summary>
78+
class StackdriverTraceExporter : IHandler
79+
{
80+
private Google.Api.Gax.ResourceNames.ProjectName googleCloudProjectId;
81+
82+
public StackdriverTraceExporter(string projectId)
83+
{
84+
googleCloudProjectId = new Google.Api.Gax.ResourceNames.ProjectName(projectId);
85+
86+
}
87+
88+
public void Export(IList<ISpanData> spanDataList)
89+
{
90+
TraceServiceClient traceWriter = TraceServiceClient.Create();
91+
var batchSpansRequest = new BatchWriteSpansRequest
92+
{
93+
ProjectName = googleCloudProjectId,
94+
Spans = { spanDataList.Select(s => s.ToSpan(googleCloudProjectId.ProjectId)) },
95+
};
96+
97+
traceWriter.BatchWriteSpans(batchSpansRequest);
98+
}
99+
}
100+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<!--
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenCensus.sln'))\build\Common.prod.props" />
4+
-->
5+
<PropertyGroup>
6+
<TargetFramework>netstandard2.0</TargetFramework>
7+
<IncludeSymbols>True</IncludeSymbols>
8+
</PropertyGroup>
9+
10+
<PropertyGroup>
11+
<Description>Stackdriver Exporter for OpenCensus.</Description>
12+
<PackageTags>Tracing;OpenCensus;Management;Monitoring;Stackdriver;Google;GCP;distributed-tracing</PackageTags>
13+
<PackageIconUrl>https://opencensus.io/images/opencensus-logo.png</PackageIconUrl>
14+
<PackageProjectUrl>https://opencensus.io</PackageProjectUrl>
15+
<PackageLicenseUrl>https://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
16+
<Authors>OpenCensus authors</Authors>
17+
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Include="Google.Cloud.Trace.V2" Version="1.0.0-beta02" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\OpenCensus.Abstractions\OpenCensus.Abstractions.csproj" />
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+

2+
// <copyright file="StackdriverExporter.cs" company="OpenCensus Authors">
3+
// Copyright 2018, OpenCensus Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of theLicense 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+
// </copyright>
17+
18+
namespace OpenCensus.Exporter.Stackdriver
19+
{
20+
using OpenCensus.Exporter.Stackdriver.Implementation;
21+
using OpenCensus.Trace.Export;
22+
using System;
23+
using System.Threading;
24+
25+
public class StackdriverExporter
26+
{
27+
private const string ExporterName = "StackdriverTraceExporter";
28+
29+
private readonly IExportComponent exportComponent;
30+
private readonly string projectId;
31+
private object locker = new object();
32+
private bool isInitialized = false;
33+
34+
public StackdriverExporter(string projectId, IExportComponent exportComponent)
35+
{
36+
this.projectId = projectId;
37+
this.exportComponent = exportComponent;
38+
}
39+
40+
public void Start()
41+
{
42+
lock (locker)
43+
{
44+
if (isInitialized)
45+
{
46+
return;
47+
}
48+
49+
var traceExporter = new StackdriverTraceExporter(projectId);
50+
exportComponent.SpanExporter.RegisterHandler(ExporterName, traceExporter);
51+
52+
isInitialized = true;
53+
}
54+
}
55+
56+
public void Stop()
57+
{
58+
lock (locker)
59+
{
60+
if (!isInitialized)
61+
{
62+
return;
63+
}
64+
65+
exportComponent.SpanExporter.UnregisterHandler(ExporterName);
66+
isInitialized = false;
67+
}
68+
}
69+
}
70+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+

2+
namespace OpenCensus.Exporter.Stackdriver.Utils
3+
{
4+
using Google.Protobuf.WellKnownTypes;
5+
using OpenCensus.Common;
6+
7+
public static class ProtoExtensions
8+
{
9+
public static Timestamp ToTimestamp(this ITimestamp timestamp)
10+
{
11+
return new Timestamp { Seconds = timestamp.Seconds, Nanos = timestamp.Nanos };
12+
}
13+
}
14+
}

src/OpenCensus/Trace/Export/SpanExporterWorker.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,10 @@ private void Export(IList<ISpanData> export)
137137
{
138138
handler.Export(export);
139139
}
140-
catch (Exception)
140+
catch (Exception ex)
141141
{
142-
// Log warning
142+
// TODO Log warning
143+
Console.WriteLine(ex);
143144
}
144145
}
145146
}

src/OpenCensus/Trace/TraceComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public TraceComponent(IClock clock, IRandomGenerator randomHandler, IEventQueue
4040
{
4141
this.ExportComponent = Export.ExportComponent.CreateWithoutInProcessStores(eventQueue);
4242
}
43-
else
43+
else
4444
{
4545
this.ExportComponent = Export.ExportComponent.CreateWithInProcessStores(eventQueue);
4646
}

src/Samples/Samples.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
@@ -7,6 +7,7 @@
77

88
<ItemGroup>
99
<ProjectReference Include="..\OpenCensus.Collector.Dependencies\OpenCensus.Collector.Dependencies.csproj" />
10+
<ProjectReference Include="..\OpenCensus.Exporter.Stackdriver\OpenCensus.Exporter.Stackdriver.csproj" />
1011
<ProjectReference Include="..\OpenCensus.Exporter.Prometheus\OpenCensus.Exporter.Prometheus.csproj" />
1112
<ProjectReference Include="..\OpenCensus\OpenCensus.csproj" />
1213
<ProjectReference Include="..\OpenCensus.Exporter.Zipkin\OpenCensus.Exporter.Zipkin.csproj" />

src/Samples/TestStackdriver.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
namespace Samples
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using OpenCensus.Exporter.Stackdriver;
7+
using OpenCensus.Stats;
8+
using OpenCensus.Stats.Aggregations;
9+
using OpenCensus.Stats.Measures;
10+
using OpenCensus.Tags;
11+
using OpenCensus.Trace;
12+
using OpenCensus.Trace.Sampler;
13+
14+
internal class TestStackdriver
15+
{
16+
private static ITracer tracer = Tracing.Tracer;
17+
private static ITagger tagger = Tags.Tagger;
18+
19+
private static IStatsRecorder statsRecorder = Stats.StatsRecorder;
20+
private static readonly IMeasureLong VideoSize = MeasureLong.Create("my.org/measure/video_size", "size of processed videos", "By");
21+
private static readonly ITagKey FrontendKey = TagKey.Create("my.org/keys/frontend");
22+
23+
private static long MiB = 1 << 20;
24+
25+
private static readonly IViewName VideoSizeViewName = ViewName.Create("my.org/views/video_size");
26+
27+
private static readonly IView VideoSizeView = View.Create(
28+
VideoSizeViewName,
29+
"processed video size over time",
30+
VideoSize,
31+
Distribution.Create(BucketBoundaries.Create(new List<double>() { 0.0, 16.0 * MiB, 256.0 * MiB })),
32+
new List<ITagKey>() { FrontendKey });
33+
34+
internal static void Run()
35+
{
36+
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", @"C:\Users\zeltser\Downloads\aspnetcoreissue-1af8a0ca869b.json");
37+
var exporter = new StackdriverExporter("aspnetcoreissue", Tracing.ExportComponent);
38+
exporter.Start();
39+
40+
ITagContextBuilder tagContextBuilder = tagger.CurrentBuilder.Put(FrontendKey, TagValue.Create("mobile-ios9.3.5"));
41+
42+
var spanBuilder = tracer
43+
.SpanBuilder("incoming request")
44+
.SetRecordEvents(true)
45+
.SetSampler(Samplers.AlwaysSample);
46+
47+
Stats.ViewManager.RegisterView(VideoSizeView);
48+
49+
using (var scopedTags = tagContextBuilder.BuildScoped())
50+
{
51+
using (var scopedSpan = spanBuilder.StartScopedSpan())
52+
{
53+
tracer.CurrentSpan.AddAnnotation("Start processing video.");
54+
Thread.Sleep(TimeSpan.FromMilliseconds(10));
55+
statsRecorder.NewMeasureMap().Put(VideoSize, 25 * MiB).Record();
56+
tracer.CurrentSpan.AddAnnotation("Finished processing video.");
57+
}
58+
}
59+
60+
Thread.Sleep(TimeSpan.FromMilliseconds(5100));
61+
62+
var viewData = Stats.ViewManager.GetView(VideoSizeViewName);
63+
64+
Console.WriteLine(viewData);
65+
66+
Console.WriteLine("Done... wait for events to arrive to backend!");
67+
Console.ReadLine();
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)