diff --git a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests.csproj b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests.csproj index a4849c97fd17..95deb59484c2 100644 --- a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests.csproj +++ b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests/Google.Cloud.Diagnostics.AspNetCore.IntegrationTests.csproj @@ -19,6 +19,7 @@ + diff --git a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/Google.Cloud.Diagnostics.AspNetCore.Snippets.csproj b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/Google.Cloud.Diagnostics.AspNetCore.Snippets.csproj index 9fa422e2edaa..8b805a3ae0cb 100644 --- a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/Google.Cloud.Diagnostics.AspNetCore.Snippets.csproj +++ b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/Google.Cloud.Diagnostics.AspNetCore.Snippets.csproj @@ -20,6 +20,7 @@ + diff --git a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/StandaloneTraceSnippets.cs b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/StandaloneTraceSnippets.cs new file mode 100644 index 000000000000..9116a051ce8c --- /dev/null +++ b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Snippets/StandaloneTraceSnippets.cs @@ -0,0 +1,109 @@ +// Copyright 2021 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.ClientTesting; +using Google.Cloud.Diagnostics.Common; +using Google.Cloud.Diagnostics.Common.IntegrationTests; +using Google.Protobuf.WellKnownTypes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Google.Cloud.Diagnostics.AspNetCore.Snippets +{ + public class StandaloneTraceSnippets + { + private static readonly string ProjectId = TestEnvironment.GetTestProjectId(); + + private static readonly TraceEntryPolling s_polling = new TraceEntryPolling(); + + private readonly string _testId; + + private readonly Timestamp _startTime; + + public StandaloneTraceSnippets() + { + _testId = IdGenerator.FromDateTime(); + _startTime = Timestamp.FromDateTime(DateTime.UtcNow); + + // The rate limiter instance is static and only set once. If we do not reset it at the + // beginning of each tests the qps will not change. This is dependent on the tests not + // running in parallel. + RateLimiter.Reset(); + } + + // Sample: Configure + public static IHostBuilder CreateHostBuilder() => + Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddGoogleTrace(options => options.ProjectId = ProjectId); + // Register other services here if you need them. + }); + // End sample + + [Fact] + public async Task TraceAsync() + { + // Naming it like an instance variable so that it looks like that on sample code. + IHost _host = null; + + try + { + // Sample: Start + _host = CreateHostBuilder().Build(); + await _host.StartAsync(); + // End sample + + // Sample: IncomingContext + ITraceContext traceContext = GetTraceContextFromIncomingRequest(); + var tracerFactory = _host.Services.GetRequiredService>(); + IManagedTracer tracer = tracerFactory(traceContext); + ContextTracerManager.SetCurrentTracer(tracer); + // End sample + + // Let's just start a span with the test ID so we can find it faster. + // But we don't show this in sample code. + using (tracer.StartSpan(_testId)) + { + // Sample: Trace + IManagedTracer currentTracer = _host.Services.GetRequiredService(); + using (currentTracer.StartSpan("standalone_tracing")) + { + Console.WriteLine("Using Cloud Trace from a non ASP.NET Core app"); + } + // End sample + } + + var trace = s_polling.GetTrace(_testId, _startTime); + TraceEntryVerifiers.AssertParentChildSpan(trace, _testId, "standalone_tracing"); + Assert.Equal(traceContext.TraceId, trace.TraceId); + } + finally + { + if (_host != null) + { + await _host.StopAsync(); + } + } + } + + private static ITraceContext GetTraceContextFromIncomingRequest() => + new SimpleTraceContext(TraceIdFactory.Create().NextId(), null, true); + } +} + + diff --git a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Tests/Google.Cloud.Diagnostics.AspNetCore.Tests.csproj b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Tests/Google.Cloud.Diagnostics.AspNetCore.Tests.csproj index c12981263fd5..71c769ea0dee 100644 --- a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Tests/Google.Cloud.Diagnostics.AspNetCore.Tests.csproj +++ b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore.Tests/Google.Cloud.Diagnostics.AspNetCore.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore/Trace/CloudTraceExtension.cs b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore/Trace/CloudTraceExtension.cs index c733290c01b4..7711f613fb9d 100644 --- a/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore/Trace/CloudTraceExtension.cs +++ b/apis/Google.Cloud.Diagnostics.AspNetCore/Google.Cloud.Diagnostics.AspNetCore/Trace/CloudTraceExtension.cs @@ -14,13 +14,11 @@ using Google.Api.Gax; using Google.Cloud.Diagnostics.Common; -using Google.Cloud.Trace.V1; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System; -using System.Net.Http; #if NETCOREAPP3_1 namespace Google.Cloud.Diagnostics.AspNetCore3 @@ -112,32 +110,27 @@ public static IServiceCollection AddGoogleTrace( var serviceOptions = new TraceServiceOptions(); setupAction(serviceOptions); - var client = serviceOptions.Client ?? TraceServiceClient.Create(); - var options = serviceOptions.Options ?? TraceOptions.Create(); - var traceFallbackPredicate = serviceOptions.TraceFallbackPredicate ?? TraceDecisionPredicate.Default; - var projectId = Project.GetAndCheckProjectId(serviceOptions.ProjectId); + services.AddGoogleTrace((Common.TraceServiceOptions commonOptions) => + { + commonOptions.ProjectId = serviceOptions.ProjectId; + commonOptions.Options = serviceOptions.Options; + commonOptions.Client = serviceOptions.Client; + }); - var consumer = ManagedTracer.CreateConsumer(client, options); - var tracerFactory = ManagedTracer.CreateFactory(projectId, consumer, options); + var traceFallbackPredicate = serviceOptions.TraceFallbackPredicate ?? TraceDecisionPredicate.Default; // We use TryAdd... here to allow user code to inject their own trace context provider // and matching trace context response propagator. We use Google trace header otherwise. services.TryAddScoped(ProvideGoogleTraceHeaderContext); services.TryAddSingleton>(PropagateGoogleTraceHeaders); - services.AddSingleton(tracerFactory); // Obsolete: Adding this for backwards compatibility in case someone is using the old factory type. // The new and prefered factory type is Func. - services.AddSingleton>(tracerFactory); + services.AddSingleton>(sp => sp.GetRequiredService>()); + services.AddHttpContextAccessor(); - services.AddSingleton(ManagedTracer.CreateDelegatingTracer(ContextTracerManager.GetCurrentTracer)); services.AddTransient(); - // On .Net Standard 2.0 or higher, we can use the System.Net.Http.IHttpClientFactory defined in Microsoft.Extensions.Http, - // for which we need a DelagatingHandler with no InnerHandler set. This is the recommended way. - // It should be registered as follows. - services.AddTransient(UnchainedTraceHeaderPropagatingHandlerFactory); - // This is to be used for explicitly creating an HttpClient instance. Valid for all platforms but subject to // issues with HttpClient lifetime as described in // https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests. @@ -173,15 +166,5 @@ internal static void PropagateGoogleTraceHeaders(HttpResponse response, ITraceCo var googleHeader = TraceHeaderContext.Create(traceContext.TraceId, traceContext.SpanId ?? 0, traceContext.ShouldTrace); response.Headers.Add(TraceHeaderContext.TraceHeader, googleHeader.ToString()); } - - /// - /// Returns an configured with the user specified trace context - /// outgoing propagator. If user code has not specified a trace context outgoing propagator, the Google header will - /// be propagated. - /// - internal static UnchainedTraceHeaderPropagatingHandler UnchainedTraceHeaderPropagatingHandlerFactory(IServiceProvider serviceProvider) => - serviceProvider.GetService>() is Action traceContextOutgoingPropagator ? - new UnchainedTraceHeaderPropagatingHandler(ContextTracerManager.GetCurrentTracer, traceContextOutgoingPropagator) : - new UnchainedTraceHeaderPropagatingHandler(ContextTracerManager.GetCurrentTracer); } } diff --git a/apis/Google.Cloud.Diagnostics.AspNetCore/docs/index.md b/apis/Google.Cloud.Diagnostics.AspNetCore/docs/index.md index ef4c0cca0302..50f2255970db 100644 --- a/apis/Google.Cloud.Diagnostics.AspNetCore/docs/index.md +++ b/apis/Google.Cloud.Diagnostics.AspNetCore/docs/index.md @@ -247,3 +247,34 @@ that is propagated in a `custom_trace_id` header. This is of no use in the real {{sample:Trace.CustomTraceContext}} +## Using Google Trace in applications not based in ASP.NET Core + +If you want to use Google Cloud Trace in applications not based on ASP.NET Core you may use the +`Google.Cloud.Diagnostics.Common` package directly and .NET's dependency injection mechanism. +You can read more about .NET dependency injection in non ASP.NET Core apps in the +[Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-usage). + +Note that this is useful for both installed applications and services that process incoming messages +other than HTTP requests, such as a service reacting to Pub/Sub messages. + +Following you'll see a very simplified example of how you could set up Google Cloud Trace for these +types of applications. + +- Configure Google Cloud Trace. You can set tracing options the same as you would do for ASP.NET Core apps. + +{{sample:StandaloneTrace.Configure}} + +- Build and start a `Microsoft.Extensions.Hosting.IHost`. + +{{sample:StandaloneTrace.Start}} + +- Create a tracing context when appropiate, for instance, when you receive a Pub/Sub message. You can create +an empty tracing context (with all null values) if there's none. The tracer will create a tracing context +depending on configuration options like QPS, etc. + +{{sample:StandaloneTrace.IncomingContext}} + +- Trace normally in your code + +{{sample:StandaloneTrace.Trace}} + diff --git a/apis/Google.Cloud.Diagnostics.Common/Google.Cloud.Diagnostics.Common/Trace/CloudTraceExtensions.cs b/apis/Google.Cloud.Diagnostics.Common/Google.Cloud.Diagnostics.Common/Trace/CloudTraceExtensions.cs new file mode 100644 index 000000000000..5d56c9b73149 --- /dev/null +++ b/apis/Google.Cloud.Diagnostics.Common/Google.Cloud.Diagnostics.Common/Trace/CloudTraceExtensions.cs @@ -0,0 +1,67 @@ +// Copyright 2021 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.Api.Gax; +using Google.Cloud.Trace.V1; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Net.Http; + +namespace Google.Cloud.Diagnostics.Common +{ + /// + /// Extension methods for registering Google Cloud Trace for + /// dependency injection. + /// + public static class CloudTraceExtensions + { + /// + /// Configures Google Cloud Trace for dependency injection. + /// + public static IServiceCollection AddGoogleTrace( + this IServiceCollection services, Action setupAction) + { + GaxPreconditions.CheckNotNull(services, nameof(services)); + GaxPreconditions.CheckNotNull(setupAction, nameof(setupAction)); + + var serviceOptions = new TraceServiceOptions(); + setupAction(serviceOptions); + + var client = serviceOptions.Client ?? TraceServiceClient.Create(); + var options = serviceOptions.Options ?? TraceOptions.Create(); + var projectId = Project.GetAndCheckProjectId(serviceOptions.ProjectId); + + var consumer = ManagedTracer.CreateConsumer(client, options); + var tracerFactory = ManagedTracer.CreateFactory(projectId, consumer, options); + + services.AddSingleton(tracerFactory); + services.AddSingleton(ManagedTracer.CreateDelegatingTracer(ContextTracerManager.GetCurrentTracer)); + + // On .Net Standard 2.0 or higher, we can use the System.Net.Http.IHttpClientFactory defined in Microsoft.Extensions.Http, + // for which we need a DelegatingHandler with no InnerHandler set. This is the recommended way. + // It should be registered as follows. + return services.AddTransient(UnchainedTraceHeaderPropagatingHandlerFactory); + } + + /// + /// Returns an configured with the user specified trace context + /// outgoing propagator. If user code has not specified a trace context outgoing propagator, the Google header will + /// be propagated. + /// + internal static UnchainedTraceHeaderPropagatingHandler UnchainedTraceHeaderPropagatingHandlerFactory(IServiceProvider serviceProvider) => + serviceProvider.GetService>() is Action traceContextOutgoingPropagator ? + new UnchainedTraceHeaderPropagatingHandler(ContextTracerManager.GetCurrentTracer, traceContextOutgoingPropagator) : + new UnchainedTraceHeaderPropagatingHandler(ContextTracerManager.GetCurrentTracer); + } +} diff --git a/apis/Google.Cloud.Diagnostics.Common/Google.Cloud.Diagnostics.Common/Trace/TraceServiceOptions.cs b/apis/Google.Cloud.Diagnostics.Common/Google.Cloud.Diagnostics.Common/Trace/TraceServiceOptions.cs new file mode 100644 index 000000000000..b78732966133 --- /dev/null +++ b/apis/Google.Cloud.Diagnostics.Common/Google.Cloud.Diagnostics.Common/Trace/TraceServiceOptions.cs @@ -0,0 +1,40 @@ +// Copyright 2021 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.Trace.V1; + +namespace Google.Cloud.Diagnostics.Common +{ + /// + /// Options for initializing tracing. + /// + public sealed class TraceServiceOptions + { + /// + /// The Google Cloud Platform project ID. If unspecified and running on GAE or GCE the project + /// ID will be detected from the platform. + /// + public string ProjectId { get; set; } + + /// + /// Trace options. May be null. + /// + public TraceOptions Options { get; set; } + + /// + /// A client to send traces with. May be null. + /// + public TraceServiceClient Client { get; set; } + } +} diff --git a/apis/apis.json b/apis/apis.json index fafe32ffce0f..8b474688aa77 100644 --- a/apis/apis.json +++ b/apis/apis.json @@ -714,7 +714,8 @@ "Google.Cloud.Diagnostics.Common.IntegrationTests": "project", "Google.Cloud.Diagnostics.Common.Tests": "project", "Microsoft.AspNetCore.Mvc": "2.1.3", - "Microsoft.AspNetCore.TestHost": "2.1.1" + "Microsoft.AspNetCore.TestHost": "2.1.1", + "Microsoft.Extensions.Hosting": "3.0.0" }, "additionalAnalyzerTestDependencies": { "Microsoft.AspNetCore.Mvc.Core": "1.0.4",