Skip to content

Commit 540d9de

Browse files
author
Emanuele Palazzetti
authored
Merge pull request #30 from bmermet/aspnetcorebase
Asp.Net Core Integration
2 parents 1596837 + 3f68197 commit 540d9de

22 files changed

+749
-17
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
6+
<IsPackable>false</IsPackable>
7+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.0.1" />
16+
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.2" />
17+
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.1" />
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
19+
<PackageReference Include="System.Reactive" Version="3.1.1" />
20+
<PackageReference Include="xunit" Version="2.3.1" />
21+
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
22+
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
23+
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<ProjectReference Include="..\Datadog.Trace.AspNetCore\Datadog.Trace.AspNetCore.csproj" />
28+
<ProjectReference Include="..\Datadog.Trace\Datadog.Trace.csproj" />
29+
</ItemGroup>
30+
31+
</Project>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Threading;
5+
using Microsoft.Extensions.DiagnosticAdapter;
6+
7+
namespace Datadog.Trace.AspNetCore.Tests
8+
{
9+
public class EndRequestWaiter : IDisposable
10+
{
11+
private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);
12+
private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
13+
14+
public EndRequestWaiter()
15+
{
16+
DiagnosticListener.AllListeners.Subscribe(x =>
17+
{
18+
if (x.Name == "Microsoft.AspNetCore")
19+
{
20+
_subscriptions.Add(x.SubscribeWithAdapter(this));
21+
}
22+
});
23+
}
24+
25+
[DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
26+
public void OnHttpRequestInStop()
27+
{
28+
_resetEvent.Set();
29+
}
30+
31+
[DiagnosticName("Microsoft.AspNetCore.Hosting.UnhandledException")]
32+
public void OnUnhandledException()
33+
{
34+
_resetEvent.Set();
35+
}
36+
37+
public void Wait()
38+
{
39+
_resetEvent.WaitOne();
40+
}
41+
42+
public void Dispose()
43+
{
44+
foreach (var subscription in _subscriptions)
45+
{
46+
subscription.Dispose();
47+
}
48+
}
49+
}
50+
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Xunit;
10+
11+
namespace Datadog.Trace.AspNetCore.Tests
12+
{
13+
public class FunctionalTests : IDisposable
14+
{
15+
private const string MethodTag = "http.method";
16+
private const string UrlTag = "http.url";
17+
private const string StatusCodeTag = "http.status_code";
18+
private const string ErrorMsgTag = "error.msg";
19+
private const string ErrorTypeTag = "error.type";
20+
private const string ErrorStackTag = "error.stack";
21+
22+
private const string Content = "Hello World!";
23+
private const string DefaultServiceName = "testhost";
24+
25+
private readonly IWebHost _host;
26+
private readonly HttpClient _client;
27+
private readonly MockWriter _writer;
28+
private readonly Tracer _tracer;
29+
private readonly EndRequestWaiter _waiter;
30+
31+
public FunctionalTests()
32+
{
33+
_writer = new MockWriter();
34+
_tracer = new Tracer(_writer);
35+
_waiter = new EndRequestWaiter();
36+
_host = new WebHostBuilder()
37+
.UseUrls("http://localhost:5050")
38+
.UseKestrel()
39+
.ConfigureServices(s => s.AddDatadogTrace(_tracer)
40+
.AddMvc())
41+
.Configure(app => app
42+
.Map("/error", HandleError)
43+
.Map("/child", HandleWithChild)
44+
.UseMvcWithDefaultRoute()
45+
.Run(HandleNormal))
46+
.Build();
47+
_host.Start();
48+
_client = new HttpClient() { BaseAddress = new Uri("http://localhost:5050") };
49+
}
50+
51+
public void Dispose()
52+
{
53+
_host.Dispose();
54+
_waiter.Dispose();
55+
}
56+
57+
[Fact]
58+
public async void OkResponse()
59+
{
60+
var response = await _client.GetAsync("/");
61+
var content = await response.Content.ReadAsStringAsync();
62+
_waiter.Wait();
63+
64+
Assert.Equal(Content, content);
65+
var span = _writer.Traces.Single().Single();
66+
Assert.Equal("GET", span.Tags[MethodTag]);
67+
Assert.Equal("/", span.Tags[UrlTag]);
68+
Assert.Equal("200", span.Tags[StatusCodeTag]);
69+
Assert.Equal("GET 200", span.ResourceName);
70+
Assert.Equal(DefaultServiceName, span.ServiceName);
71+
}
72+
73+
[Fact]
74+
public async void OkResponseOverrideServiceName()
75+
{
76+
const string serviceNameOverride = "Blublu";
77+
_host.Dispose();
78+
using (var host = new WebHostBuilder()
79+
.UseUrls("http://localhost:5050")
80+
.UseKestrel()
81+
.ConfigureServices(s => s.AddDatadogTrace(_tracer, serviceNameOverride))
82+
.Configure(app => app
83+
.UseDeveloperExceptionPage()
84+
.Run(HandleNormal))
85+
.Build())
86+
{
87+
host.Start();
88+
var response = await _client.GetAsync("/");
89+
var content = await response.Content.ReadAsStringAsync();
90+
_waiter.Wait();
91+
92+
Assert.Equal(Content, content);
93+
var span = _writer.Traces.Single().Single();
94+
Assert.Equal("GET", span.Tags[MethodTag]);
95+
Assert.Equal("/", span.Tags[UrlTag]);
96+
Assert.Equal("200", span.Tags[StatusCodeTag]);
97+
Assert.Equal("GET 200", span.ResourceName);
98+
Assert.Equal(serviceNameOverride, span.ServiceName);
99+
}
100+
}
101+
102+
[Fact]
103+
public async void OkResponseWithChildSpan()
104+
{
105+
var response = await _client.GetAsync("/child");
106+
var content = await response.Content.ReadAsStringAsync();
107+
_waiter.Wait();
108+
109+
Assert.Equal(Content, content);
110+
var trace = _writer.Traces.Single();
111+
Assert.Equal(2, trace.Count);
112+
var root = trace[0];
113+
Assert.Equal("GET", root.Tags[MethodTag]);
114+
Assert.Equal("/child", root.Tags[UrlTag]);
115+
Assert.Equal("200", root.Tags[StatusCodeTag]);
116+
Assert.Equal("GET 200", root.ResourceName);
117+
Assert.Equal(DefaultServiceName, root.ServiceName);
118+
var child = trace[1];
119+
Assert.Equal("Child", child.OperationName);
120+
Assert.Equal(root.Context, child.Context.Parent);
121+
}
122+
123+
[Fact]
124+
public async void Error()
125+
{
126+
var response = await _client.GetAsync("/error");
127+
_waiter.Wait();
128+
129+
var span = _writer.Traces.Single().Single();
130+
Assert.Equal("GET", span.Tags[MethodTag]);
131+
Assert.Equal("/error", span.Tags[UrlTag]);
132+
Assert.Equal("500", span.Tags[StatusCodeTag]);
133+
Assert.True(span.Error);
134+
Assert.Equal(typeof(InvalidOperationException).ToString(), span.GetTag(ErrorTypeTag));
135+
Assert.Equal("Invalid", span.GetTag(ErrorMsgTag));
136+
Assert.False(string.IsNullOrEmpty(span.GetTag(ErrorStackTag)));
137+
Assert.Equal("GET 500", span.ResourceName);
138+
Assert.Equal(DefaultServiceName, span.ServiceName);
139+
}
140+
141+
[Fact]
142+
public async void MvcOkResponse()
143+
{
144+
var response = await _client.GetAsync("/Test");
145+
var content = await response.Content.ReadAsStringAsync();
146+
_waiter.Wait();
147+
148+
Assert.Equal("ActionContent", content);
149+
var span = _writer.Traces.Single().Single();
150+
Assert.Equal("GET", span.Tags[MethodTag]);
151+
Assert.Equal("/Test", span.Tags[UrlTag]);
152+
Assert.Equal("200", span.Tags[StatusCodeTag]);
153+
Assert.Equal("Test.Index", span.ResourceName);
154+
Assert.Equal(DefaultServiceName, span.ServiceName);
155+
}
156+
157+
[Fact]
158+
public async void DeveloperExceptionPage()
159+
{
160+
_host.Dispose();
161+
using (var host = new WebHostBuilder()
162+
.UseUrls("http://localhost:5050")
163+
.UseKestrel()
164+
.ConfigureServices(s => s.AddDatadogTrace(_tracer))
165+
.Configure(app => app
166+
.UseDeveloperExceptionPage()
167+
.Map("/error", HandleError))
168+
.Build())
169+
{
170+
host.Start();
171+
var response = await _client.GetAsync("/error");
172+
_waiter.Wait();
173+
174+
var span = _writer.Traces.Single().Single();
175+
Assert.Equal("GET", span.Tags[MethodTag]);
176+
Assert.Equal("/error", span.Tags[UrlTag]);
177+
Assert.Equal("500", span.Tags[StatusCodeTag]);
178+
Assert.True(span.Error);
179+
Assert.Equal(typeof(InvalidOperationException).ToString(), span.GetTag(ErrorTypeTag));
180+
Assert.Equal("Invalid", span.GetTag(ErrorMsgTag));
181+
Assert.False(string.IsNullOrEmpty(span.GetTag(ErrorStackTag)));
182+
Assert.Equal("GET 500", span.ResourceName);
183+
Assert.Equal(DefaultServiceName, span.ServiceName);
184+
}
185+
}
186+
187+
[Fact]
188+
public async void ExceptionHandler()
189+
{
190+
_host.Dispose();
191+
using (var host = new WebHostBuilder()
192+
.UseUrls("http://localhost:5050")
193+
.UseKestrel()
194+
.ConfigureServices(s => s.AddDatadogTrace(_tracer))
195+
.Configure(app => app
196+
.UseExceptionHandler("/index")
197+
.Map("/error", HandleError)
198+
.Run(HandleNormal))
199+
.Build())
200+
{
201+
host.Start();
202+
var response = await _client.GetAsync("/error");
203+
_waiter.Wait();
204+
205+
var span = _writer.Traces.Single().Single();
206+
Assert.Equal("GET", span.Tags[MethodTag]);
207+
Assert.Equal("/error", span.Tags[UrlTag]);
208+
Assert.Equal("500", span.Tags[StatusCodeTag]);
209+
Assert.True(span.Error);
210+
Assert.Equal(typeof(InvalidOperationException).ToString(), span.GetTag(ErrorTypeTag));
211+
Assert.Equal("Invalid", span.GetTag(ErrorMsgTag));
212+
Assert.False(string.IsNullOrEmpty(span.GetTag(ErrorStackTag)));
213+
Assert.Equal("GET 500", span.ResourceName);
214+
Assert.Equal(DefaultServiceName, span.ServiceName);
215+
}
216+
}
217+
218+
private static async Task HandleNormal(HttpContext context)
219+
{
220+
await context.Response.WriteAsync(Content);
221+
}
222+
223+
private static void HandleError(IApplicationBuilder app)
224+
{
225+
app.Run(context =>
226+
{
227+
throw new InvalidOperationException("Invalid");
228+
});
229+
}
230+
231+
private void HandleWithChild(IApplicationBuilder app)
232+
{
233+
app.Run(async context =>
234+
{
235+
using (_tracer.StartActive("Child"))
236+
{
237+
await context.Response.WriteAsync(Content);
238+
}
239+
});
240+
}
241+
}
242+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// This file is used by Code Analysis to maintain SuppressMessage
2+
// attributes that are applied to this project.
3+
// Project-level suppressions either have no target or are given
4+
// a specific target and scoped to a namespace, type, member, etc.
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:FieldNamesMustNotBeginWithUnderscore", Justification = "Reviewed.")]
9+
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File must have header", Justification = "Reviewed.")]
10+
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1652:Enable XML documentation output", Justification = "Reviewed.")]
11+
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Reviewed.")]
12+
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:UsingDirectivesMustBePlacedWithinNamespace", Justification = "Reviewed.")]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
4+
namespace Datadog.Trace.AspNetCore.Tests
5+
{
6+
public class MockWriter : IAgentWriter
7+
{
8+
public MockWriter()
9+
{
10+
Traces = new List<List<Span>>();
11+
}
12+
13+
public List<List<Span>> Traces { get; set; }
14+
15+
public Task FlushAndCloseAsync()
16+
{
17+
return Task.FromResult(true);
18+
}
19+
20+
public void WriteServiceInfo(ServiceInfo serviceInfo)
21+
{
22+
}
23+
24+
public void WriteTrace(List<Span> trace)
25+
{
26+
Traces.Add(trace);
27+
}
28+
}
29+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace Datadog.Trace.AspNetCore.Tests.Controllers
5+
{
6+
public class TestController : Controller
7+
{
8+
public string Index(int id)
9+
{
10+
return "ActionContent";
11+
}
12+
13+
public string Error()
14+
{
15+
throw new InvalidOperationException("Invalid");
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)