Skip to content

Commit

Permalink
Adds GoogleTraceProvider to be used by Logging when Tracing is not co…
Browse files Browse the repository at this point in the history
…nfigured.

Closes #5359.
  • Loading branch information
amanda-tarafa committed Sep 28, 2020
1 parent 924c503 commit 5710321
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
Expand Down Expand Up @@ -500,7 +501,7 @@ public static object[][] ExternalTraceBuilders
return new object[][]
{
new object[] { new WebHostBuilder().UseStartup<NoBufferWarningLoggerExternalTraceTestApplication>() },
new object[] { new WebHostBuilder().UseStartup<LoggerNoTracingActivatedTestApplication>() }
new object[] { new WebHostBuilder().UseStartup<ExternalTracingTestApplication>() }
};
}
}
Expand Down Expand Up @@ -567,6 +568,74 @@ public async Task Logging_Trace_External_MultipleEntries(IWebHostBuilder builder
});
}

[Fact]
public async Task Logging_Trace_GoogleExternal()
{
Timestamp startTime = Timestamp.FromDateTime(DateTime.UtcNow);
string testId = IdGenerator.FromGuid();

string traceId = "105445aa7843bc8bf206b12000100f00";
ulong spanId = 0x12D687;
// The spanId set on the log entry should confirm to x16
// format so that the backend can really associate the log entry
// to the span.
string expectedSpanId = "000000000012d687";

string url = $"/Main/Critical/{testId}";
var builder = new WebHostBuilder().UseStartup<GoogleTraceAsExternalTracingTestApplication>();
using (var server = new TestServer(builder))
using (var client = server.CreateClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
// Set the Google tracing header so that it can be read by the
// GoogleTraceProvider. This is the same that GCP would do.
// Note that we are actually not creating the trace on Google Trace,
// which is fine for the purposes of this test.
string traceHeaderValue = $"{traceId}/{spanId};o=1";
request.Headers.Add("X-Cloud-Trace-Context", traceHeaderValue);
await client.SendAsync(request);
}

_fixture.AddValidator(testId, results =>
{
// We only have one log entry.
LogEntry entry = Assert.Single(results);

// And the resource name of the trace associated to it contains the external trace id.
Assert.Contains(TestEnvironment.GetTestProjectId(), entry.Trace);
Assert.Contains(traceId, entry.Trace);

// The span associated to our entry is the external span.
Assert.Equal(expectedSpanId, entry.SpanId);
});
}

[Fact]
public async Task Logging_Trace_GoogleExternal_NoTraceHeader()
{
Timestamp startTime = Timestamp.FromDateTime(DateTime.UtcNow);
string testId = IdGenerator.FromGuid();

string url = $"/Main/Critical/{testId}";
var builder = new WebHostBuilder().UseStartup<GoogleTraceAsExternalTracingTestApplication>();
using (var server = new TestServer(builder))
using (var client = server.CreateClient())
{
await client.GetAsync(url);
}

_fixture.AddValidator(testId, results =>
{
// We only have one log entry.
LogEntry entry = Assert.Single(results);

// The entry does not have any trace information.
// These are empty strings instead of null.
Assert.Equal("", entry.Trace);
Assert.Equal("", entry.SpanId);
});
}

[Fact]
public async Task Logging_Labels()
{
Expand Down Expand Up @@ -744,15 +813,14 @@ public void Configure(IApplicationBuilder app) =>
/// <summary>
/// A simple web application to test the <see cref="GoogleLogger"/> and associated classes.
/// </summary>
public class LoggerNoTracingActivatedTestApplication
public abstract class LoggerNoTracingActivatedTestApplication
{
protected readonly string _projectId = TestEnvironment.GetTestProjectId();

public virtual void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddMvc();
services.AddSingleton<IExternalTraceProvider>(new SpanCountingExternalTraceProvider());
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
Expand All @@ -769,6 +837,24 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
}
}

public class ExternalTracingTestApplication : LoggerNoTracingActivatedTestApplication
{
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddSingleton<IExternalTraceProvider, SpanCountingExternalTraceProvider>();
}
}

public class GoogleTraceAsExternalTracingTestApplication : LoggerNoTracingActivatedTestApplication
{
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddSingleton<IExternalTraceProvider, GoogleTraceProvider>();
}
}

/// <summary>
/// A simple web application to test the <see cref="GoogleLogger"/> and associated classes.
/// </summary>
Expand Down Expand Up @@ -798,7 +884,7 @@ public virtual void ConfigureServices(IServiceCollection services)
public void SetupRoutes(IApplicationBuilder app)
{
app.UseGoogleTrace()
.UseMvc(routes =>
.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Cloud.Diagnostics.Common;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Moq;
using System;
using Xunit;

namespace Google.Cloud.Diagnostics.AspNetCore.Tests
{
public class GoogleTraceProviderTests
{
[Fact]
public void GetCurrentTraceContext()
{
string traceId = "105445aa7843bc8bf206b12000100f00";
ulong spanId = 0x12D687;
// The spanId set on the log entry should confirm to x16
// format so that the backend can really associate the log entry
// to the span.
string expectedSpanId = "000000000012d687";

IServiceProvider serviceProvider = MockServiceProvider(traceId, spanId, true);

GoogleTraceProvider traceProvider = new GoogleTraceProvider();
TraceContextForLogEntry traceContext = traceProvider.GetCurrentTraceContext(serviceProvider);

Assert.Equal(traceId, traceContext.TraceId);
Assert.Equal(expectedSpanId, traceContext.SpanId);
}

[Fact]
public void GetCurrentTraceContext_ShouldNotTrace()
{
string traceId = "105445aa7843bc8bf206b12000100f00";
ulong spanId = 1234567;

IServiceProvider serviceProvider = MockServiceProvider(traceId, spanId, false);

GoogleTraceProvider traceProvider = new GoogleTraceProvider();
Assert.Null(traceProvider.GetCurrentTraceContext(serviceProvider));
}

private IServiceProvider MockServiceProvider(string traceId, ulong spanId, bool shouldTrace)
{
char shouldTraceBit = shouldTrace ? '1' : '0';
StringValues headerValue = $"{traceId}/{spanId};o={shouldTraceBit}";

Mock<IHeaderDictionary> headerDictionaryMock = new Mock<IHeaderDictionary>(MockBehavior.Strict);
headerDictionaryMock.Setup(hd => hd.TryGetValue(TraceHeaderContext.TraceHeader, out headerValue)).Returns(true);

Mock<HttpRequest> requestMock = new Mock<HttpRequest>(MockBehavior.Strict);
requestMock.Setup(r => r.Headers).Returns(headerDictionaryMock.Object);

Mock<HttpContext> contextMock = new Mock<HttpContext>(MockBehavior.Strict);
contextMock.Setup(c => c.Request).Returns(requestMock.Object);

Mock<IHttpContextAccessor> contextAccessorMock = new Mock<IHttpContextAccessor>(MockBehavior.Strict);
contextAccessorMock.Setup(a => a.HttpContext).Returns(contextMock.Object);

Mock<IServiceProvider> serviceProviderMock = new Mock<IServiceProvider>(MockBehavior.Strict);
serviceProviderMock.Setup(p => p.GetService(typeof(IHttpContextAccessor))).Returns(contextAccessorMock.Object);

return serviceProviderMock.Object;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;

namespace Google.Cloud.Diagnostics.AspNetCore
{
/// <summary>
/// If this is registered as a dependency, then Log Entries will be associated with
/// the Google trace and span.
/// </summary>
/// <remarks>
/// To be used when the Tracing component of the Google.Cloud.Diagnostics libraries
/// is not configured, but Google traces are still being generated, for instance,
/// because the application is being run in Google Cloud.
/// If the Tracing component is configured, log entries are automatically associated
/// to Google traces and spans.
/// </remarks>
public class GoogleTraceProvider : IExternalTraceProvider
{
/// <inheritdoc/>
public TraceContextForLogEntry GetCurrentTraceContext(IServiceProvider serviceProvider) =>
TraceContextForLogEntry.FromGoogleTraceHeader(serviceProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

using Google.Api.Gax;
using Google.Cloud.Diagnostics.Common;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using System;

namespace Google.Cloud.Diagnostics.AspNetCore
Expand Down Expand Up @@ -59,6 +61,21 @@ internal static TraceContextForLogEntry FromGoogleTrace() =>
new TraceContextForLogEntry(traceId, SpanIdToHex(tracer.GetCurrentSpanId())) :
null;

internal static TraceContextForLogEntry FromGoogleTraceHeader(IServiceProvider serviceProvider)
{
if (serviceProvider?.GetService<IHttpContextAccessor>()?
.HttpContext.Request.Headers
.TryGetValue(TraceHeaderContext.TraceHeader, out StringValues headerValue) == true)
{
var traceContext = TraceHeaderContext.FromHeader(headerValue);
if (traceContext.ShouldTrace == true)
{
return new TraceContextForLogEntry(traceContext.TraceId, SpanIdToHex(traceContext.SpanId));
}
}
return null;
}

internal static TraceContextForLogEntry FromExternalTrace(IServiceProvider serviceProvider) =>
serviceProvider?.GetService<IExternalTraceProvider>()?.GetCurrentTraceContext(serviceProvider);

Expand Down

0 comments on commit 5710321

Please sign in to comment.