diff --git a/Ocelot.sln b/Ocelot.sln index 203dfb92..511c7983 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -82,6 +82,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic", "basic", "{ED066001 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "graphql", "graphql", "{C15CD120-5F8D-41DE-9B21-00E3EA77D6C1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.OpenTracing", "src\Ocelot.Tracing.OpenTracing\Ocelot.Tracing.OpenTracing.csproj", "{11C622AD-8C0A-4CF4-811B-3DBB76550797}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "open-tracing", "open-tracing", "{731C6A8A-69ED-445C-A132-C638AA93F9C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotOpenTracing", "samples\OcelotOpenTracing\OcelotOpenTracing.csproj", "{C9427E78-4281-4F59-A66E-17C0B66550E5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -180,6 +186,14 @@ Global {33BE6D88-F188-4E60-83AC-3C4B94D24675}.Debug|Any CPU.Build.0 = Debug|Any CPU {33BE6D88-F188-4E60-83AC-3C4B94D24675}.Release|Any CPU.ActiveCfg = Release|Any CPU {33BE6D88-F188-4E60-83AC-3C4B94D24675}.Release|Any CPU.Build.0 = Release|Any CPU + {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11C622AD-8C0A-4CF4-811B-3DBB76550797}.Release|Any CPU.Build.0 = Release|Any CPU + {C9427E78-4281-4F59-A66E-17C0B66550E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9427E78-4281-4F59-A66E-17C0B66550E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9427E78-4281-4F59-A66E-17C0B66550E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9427E78-4281-4F59-A66E-17C0B66550E5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -214,6 +228,9 @@ Global {1F1F324D-6EA4-4E63-A6A7-C6053F412F1A} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C} {ED066001-BAF7-4117-9884-DF591A56347D} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C} {C15CD120-5F8D-41DE-9B21-00E3EA77D6C1} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C} + {11C622AD-8C0A-4CF4-811B-3DBB76550797} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} + {731C6A8A-69ED-445C-A132-C638AA93F9C7} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C} + {C9427E78-4281-4F59-A66E-17C0B66550E5} = {731C6A8A-69ED-445C-A132-C638AA93F9C7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index 5ff66882..a400a809 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -1,8 +1,41 @@ Tracing ======= -This page details how to perform distributed tracing with Ocelot. At the moment we only support Butterfly but other tracers might just work without -anything Ocelot specific. +This page details how to perform distributed tracing with Ocelot. + +OpenTracing +^^^^^^^^^^^ + +Ocelot providers tracing functionality from the excellent `OpenTracing C# `_ project. The code for the Ocelot integration +can be found `here `_. + +The example below uses `Jaeger C# `_ client to provide the tracer used in Ocelot. + +.. code-block:: csharp + + services.AddSingleton(sp => + { + var loggerFactory = sp.GetService(); + Configuration config = new Configuration(context.HostingEnvironment.ApplicationName, loggerFactory); + + var tracer = config.GetTracer(); + GlobalTracer.Register(tracer); + return tracer; + }); + + services + .AddOcelot() + .AddOpenTracing(); + +Then in your ocelot.json add the following to the Route you want to trace.. + +.. code-block:: json + + "HttpHandlerOptions": { + "UseTracing": true + }, + +Ocelot will now send tracing information to Jaeger when this Route is called. Butterfly ^^^^^^^^^ diff --git a/samples/OcelotOpenTracing/Program.cs b/samples/OcelotOpenTracing/Program.cs index 5a2984c4..e9527930 100644 --- a/samples/OcelotOpenTracing/Program.cs +++ b/samples/OcelotOpenTracing/Program.cs @@ -1,23 +1,22 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using System.IO; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Microsoft.Extensions.Logging; -using Ocelot.Tracing.OpenTracing; -using Jaeger; -using Microsoft.Extensions.DependencyInjection; -using OpenTracing; -using OpenTracing.Util; - -namespace OcelotOpenTracing +namespace OcelotOpenTracing { + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Hosting; + using System.IO; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using Microsoft.Extensions.Logging; + using Ocelot.Tracing.OpenTracing; + using Jaeger; + using Microsoft.Extensions.DependencyInjection; + using OpenTracing; + using OpenTracing.Util; + internal static class Program { private static void Main(string[] args) { - Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(webBuilder => { @@ -36,10 +35,6 @@ namespace OcelotOpenTracing }) .ConfigureServices((context, services) => { - services - .AddOcelot() - .AddOpenTracing(); - services.AddSingleton(sp => { var loggerFactory = sp.GetService(); @@ -50,6 +45,9 @@ namespace OcelotOpenTracing return tracer; }); + services + .AddOcelot() + .AddOpenTracing(); }) .ConfigureLogging(logging => { diff --git a/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj b/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj index c2a3e9fd..9b6d5aba 100644 --- a/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj +++ b/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj @@ -10,8 +10,8 @@ true - - + + diff --git a/src/Ocelot.Tracing.OpenTracing/OcelotBuilderExtensions.cs b/src/Ocelot.Tracing.OpenTracing/OcelotBuilderExtensions.cs index d6243d5d..59d78975 100644 --- a/src/Ocelot.Tracing.OpenTracing/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Tracing.OpenTracing/OcelotBuilderExtensions.cs @@ -1,16 +1,14 @@ -using Microsoft.Extensions.DependencyInjection; -using Ocelot.DependencyInjection; -using Ocelot.Logging; -using System; - -namespace Ocelot.Tracing.OpenTracing +namespace Ocelot.Tracing.OpenTracing { + using Microsoft.Extensions.DependencyInjection.Extensions; + using Ocelot.DependencyInjection; + using Ocelot.Logging; + public static class OcelotBuilderExtensions { public static IOcelotBuilder AddOpenTracing(this IOcelotBuilder builder) { - builder.Services.AddSingleton(); - + builder.Services.TryAddSingleton(); return builder; } } diff --git a/src/Ocelot.Tracing.OpenTracing/OpenTracingTracer.cs b/src/Ocelot.Tracing.OpenTracing/OpenTracingTracer.cs index 875cd431..744e6ddd 100644 --- a/src/Ocelot.Tracing.OpenTracing/OpenTracingTracer.cs +++ b/src/Ocelot.Tracing.OpenTracing/OpenTracingTracer.cs @@ -1,32 +1,36 @@ -using Microsoft.AspNetCore.Http; -using OpenTracing; -using OpenTracing.Propagation; -using OpenTracing.Tag; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Ocelot.Tracing.OpenTracing +namespace Ocelot.Tracing.OpenTracing { + using global::OpenTracing; + using global::OpenTracing.Propagation; + using global::OpenTracing.Tag; + using Microsoft.AspNetCore.Http; + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + class OpenTracingTracer : Logging.ITracer { - private readonly ITracer tracer; + private readonly ITracer _tracer; public OpenTracingTracer(ITracer tracer) { - this.tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); + _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); } public void Event(HttpContext httpContext, string @event) { } - public async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken, - Action addTraceIdToRepo, Func> baseSendAsync) + public async Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken, + Action addTraceIdToRepo, + Func> baseSendAsync) { - using (IScope scope = this.tracer.BuildSpan(request.RequestUri.AbsoluteUri).StartActive(finishSpanOnDispose: true)) + using (IScope scope = _tracer.BuildSpan(request.RequestUri.AbsoluteUri).StartActive(finishSpanOnDispose: true)) { var span = scope.Span; @@ -38,7 +42,7 @@ namespace Ocelot.Tracing.OpenTracing var headers = new Dictionary(); - this.tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter(headers)); + _tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter(headers)); foreach (var item in headers) { diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 5adb82a6..4ddf06a3 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,75 +1,77 @@ - - - 0.0.0-dev - netcoreapp3.1 - Ocelot.AcceptanceTests - Exe - Ocelot.AcceptanceTests - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - ..\..\codeanalysis.ruleset - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - + + + 0.0.0-dev + netcoreapp3.1 + Ocelot.AcceptanceTests + Exe + Ocelot.AcceptanceTests + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + ..\..\codeanalysis.ruleset + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/OpenTracingTests.cs b/test/Ocelot.AcceptanceTests/OpenTracingTests.cs index f4dbaff4..d93f9c1b 100644 --- a/test/Ocelot.AcceptanceTests/OpenTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/OpenTracingTests.cs @@ -41,9 +41,9 @@ namespace Ocelot.AcceptanceTests int port2 = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration() { - ReRoutes = new List() + Routes = new List() { - new FileReRoute() + new FileRoute() { DownstreamPathTemplate = "/api/values", DownstreamScheme = "http", @@ -62,7 +62,7 @@ namespace Ocelot.AcceptanceTests UseTracing = true } }, - new FileReRoute() + new FileRoute() { DownstreamPathTemplate = "/api/values", DownstreamScheme = "http", @@ -100,13 +100,8 @@ namespace Ocelot.AcceptanceTests .When(_ => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) .Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .And(_ => ThenTheTracerIsCalled(fakeTracer)) .BDDfy(); - - var commandOnAllStateMachines = Wait.WaitFor(10000).Until(() => fakeTracer.BuildSpanCalled >= 2); - - _output.WriteLine($"fakeTracer.BuildSpanCalled is {fakeTracer.BuildSpanCalled}"); - - commandOnAllStateMachines.ShouldBeTrue(); } [Fact] @@ -115,9 +110,9 @@ namespace Ocelot.AcceptanceTests int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - ReRoutes = new List + Routes = new List { - new FileReRoute + new FileRoute { DownstreamPathTemplate = "/api/values", DownstreamScheme = "http", @@ -145,6 +140,7 @@ namespace Ocelot.AcceptanceTests }; var butterflyPort = RandomPortFinder.GetRandomPort(); + var butterflyUrl = $"http://localhost:{butterflyPort}"; var fakeTracer = new FakeTracer(); @@ -161,6 +157,15 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + private void ThenTheTracerIsCalled(FakeTracer fakeTracer) + { + var commandOnAllStateMachines = Wait.WaitFor(10000).Until(() => fakeTracer.BuildSpanCalled >= 2); + + _output.WriteLine($"fakeTracer.BuildSpanCalled is {fakeTracer.BuildSpanCalled}"); + + commandOnAllStateMachines.ShouldBeTrue(); + } + private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) { _serviceOneBuilder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index de2556b2..45330311 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -46,6 +46,7 @@ namespace Ocelot.AcceptanceTests using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using CookieHeaderValue = Microsoft.Net.Http.Headers.CookieHeaderValue; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; + using Ocelot.Tracing.OpenTracing; public class Steps : IDisposable { @@ -1214,6 +1215,41 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + internal void GivenOcelotIsRunningUsingOpenTracing(OpenTracing.ITracer fakeTracer) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddOpenTracing(); + + s.AddSingleton(fakeTracer); + }) + .Configure(app => + { + app.Use(async (_, next) => + { + await next.Invoke(); + }); + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + public void ThenWarningShouldBeLogged() { MockLoggerFactory loggerFactory = (MockLoggerFactory)_ocelotServer.Host.Services.GetService();