Feat/opentracing (#1243)

This commit is contained in:
Tom Pallister 2020-05-25 18:42:48 +01:00 committed by GitHub
parent 3439be8927
commit 865520f2d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 929 additions and 76 deletions

View File

@ -82,6 +82,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic", "basic", "{ED066001
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "graphql", "graphql", "{C15CD120-5F8D-41DE-9B21-00E3EA77D6C1}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "graphql", "graphql", "{C15CD120-5F8D-41DE-9B21-00E3EA77D6C1}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{33BE6D88-F188-4E60-83AC-3C4B94D24675}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -214,6 +228,9 @@ Global
{1F1F324D-6EA4-4E63-A6A7-C6053F412F1A} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C} {1F1F324D-6EA4-4E63-A6A7-C6053F412F1A} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
{ED066001-BAF7-4117-9884-DF591A56347D} = {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} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}

View File

@ -1,8 +1,41 @@
Tracing 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 This page details how to perform distributed tracing with Ocelot.
anything Ocelot specific.
OpenTracing
^^^^^^^^^^^
Ocelot providers tracing functionality from the excellent `OpenTracing C# <https://github.com/opentracing/opentracing-csharp>`_ project. The code for the Ocelot integration
can be found `here <https://github.com/ThreeMammals/Ocelot.Tracing.OpenTracing>`_.
The example below uses `Jaeger C# <https://github.com/jaegertracing/jaeger-client-csharp>`_ client to provide the tracer used in Ocelot.
.. code-block:: csharp
services.AddSingleton<ITracer>(sp =>
{
var loggerFactory = sp.GetService<ILoggerFactory>();
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 Butterfly
^^^^^^^^^ ^^^^^^^^^

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Jaeger" Version="0.3.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ocelot.Tracing.OpenTracing\Ocelot.Tracing.OpenTracing.csproj" />
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="appsettings.json">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="ocelot.json">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,65 @@
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 =>
{
webBuilder
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json",
optional: true, reloadOnChange: false)
.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
services.AddSingleton<ITracer>(sp =>
{
var loggerFactory = sp.GetService<ILoggerFactory>();
Configuration config = new Configuration(context.HostingEnvironment.ApplicationName, loggerFactory);
var tracer = config.GetTracer();
GlobalTracer.Register(tracer);
return tracer;
});
services
.AddOcelot()
.AddOpenTracing();
})
.ConfigureLogging(logging =>
{
logging.AddConsole();
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
})
.Build()
.Run();
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,24 @@
{
"ReRoutes": [
{
"HttpHandlerOptions": {
"UseTracing": true
},
"DownstreamPathTemplate": "/todos/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "jsonplaceholder.typicode.com",
"Port": 443
}
],
"UpstreamPathTemplate": "/posts/{id}",
"UpstreamHttpMethod": [
"Get"
]
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:5000"
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Version>0.0.0-dev</Version>
<Authors>Kjell-Åke Gafvelin</Authors>
<Description>This package provides OpenTracing support to Ocelot.</Description>
<PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
<PackageTags>API Gateway;.NET core; OpenTracing</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTracing" Version="0.12.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ocelot\Ocelot.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
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.TryAddSingleton<ITracer, OpenTracingTracer>();
return builder;
}
}
}

View File

@ -0,0 +1,75 @@
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;
public OpenTracingTracer(ITracer tracer)
{
_tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
}
public void Event(HttpContext httpContext, string @event)
{
}
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken,
Action<string> addTraceIdToRepo,
Func<HttpRequestMessage,
CancellationToken,
Task<HttpResponseMessage>> baseSendAsync)
{
using (IScope scope = _tracer.BuildSpan(request.RequestUri.AbsoluteUri).StartActive(finishSpanOnDispose: true))
{
var span = scope.Span;
span.SetTag(Tags.SpanKind, Tags.SpanKindClient)
.SetTag(Tags.HttpMethod, request.Method.Method)
.SetTag(Tags.HttpUrl, request.RequestUri.OriginalString);
addTraceIdToRepo(span.Context.SpanId);
var headers = new Dictionary<string, string>();
_tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter(headers));
foreach (var item in headers)
{
request.Headers.Add(item.Key, item.Value);
}
try
{
var response = await baseSendAsync(request, cancellationToken);
span.SetTag(Tags.HttpStatus, (int)response.StatusCode);
return response;
}
catch (HttpRequestException ex)
{
Tags.Error.Set(scope.Span, true);
span.Log(new Dictionary<string, object>(3)
{
{ LogFields.Event, Tags.Error.Key },
{ LogFields.ErrorKind, ex.GetType().Name },
{ LogFields.ErrorObject, ex }
});
throw;
}
}
}
}
}

View File

@ -26,6 +26,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj" /> <ProjectReference Include="..\..\src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj" />
<ProjectReference Include="..\..\src\Ocelot.Tracing.OpenTracing\Ocelot.Tracing.OpenTracing.csproj" />
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" /> <ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
<ProjectReference Include="..\..\src\Ocelot.Cache.CacheManager\Ocelot.Cache.CacheManager.csproj" /> <ProjectReference Include="..\..\src\Ocelot.Cache.CacheManager\Ocelot.Cache.CacheManager.csproj" />
<ProjectReference Include="..\..\src\Ocelot.Provider.Consul\Ocelot.Provider.Consul.csproj" /> <ProjectReference Include="..\..\src\Ocelot.Provider.Consul\Ocelot.Provider.Consul.csproj" />
@ -41,6 +42,7 @@
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.1" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.13.1" /> <PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="OpenTracing" Version="0.12.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.164"> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.164">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@ -0,0 +1,516 @@
namespace Ocelot.AcceptanceTests
{
using Butterfly.Client.AspNetCore;
using Ocelot.Configuration.File;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using OpenTracing;
using OpenTracing.Propagation;
using OpenTracing.Tag;
using Rafty.Infrastructure;
using Shouldly;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using TestStack.BDDfy;
using Xunit;
using Xunit.Abstractions;
public class OpenTracingTests : IDisposable
{
private IWebHost _serviceOneBuilder;
private IWebHost _serviceTwoBuilder;
private IWebHost _fakeOpenTracing;
private readonly Steps _steps;
private string _downstreamPathOne;
private string _downstreamPathTwo;
private readonly ITestOutputHelper _output;
public OpenTracingTests(ITestOutputHelper output)
{
_output = output;
_steps = new Steps();
}
[Fact]
public void should_forward_tracing_information_from_ocelot_and_downstream_services()
{
int port1 = RandomPortFinder.GetRandomPort();
int port2 = RandomPortFinder.GetRandomPort();
var configuration = new FileConfiguration()
{
Routes = new List<FileRoute>()
{
new FileRoute()
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port1,
}
},
UpstreamPathTemplate = "/api001/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
}
},
new FileRoute()
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort()
{
Host = "localhost",
Port = port2,
}
},
UpstreamPathTemplate = "/api002/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
}
}
}
};
var tracingPort = RandomPortFinder.GetRandomPort();
var tracingUrl = $"http://localhost:{tracingPort}";
var fakeTracer = new FakeTracer();
this.Given(_ => GivenFakeOpenTracing(tracingUrl))
.And(_ => GivenServiceOneIsRunning($"http://localhost:{port1}", "/api/values", 200, "Hello from Laura", tracingUrl))
.And(_ => GivenServiceTwoIsRunning($"http://localhost:{port2}", "/api/values", 200, "Hello from Tom", tracingUrl))
.And(_ => _steps.GivenThereIsAConfiguration(configuration))
.And(_ => _steps.GivenOcelotIsRunningUsingOpenTracing(fakeTracer))
.When(_ => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
.Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.When(_ => _steps.WhenIGetUrlOnTheApiGateway("/api002/values"))
.Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.And(_ => ThenTheTracerIsCalled(fakeTracer))
.BDDfy();
}
[Fact]
public void should_return_tracing_header()
{
int port = RandomPortFinder.GetRandomPort();
var configuration = new FileConfiguration
{
Routes = new List<FileRoute>
{
new FileRoute
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = port,
}
},
UpstreamPathTemplate = "/api001/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
},
DownstreamHeaderTransform = new Dictionary<string, string>()
{
{"Trace-Id", "{TraceId}"},
{"Tom", "Laura"}
}
}
}
};
var butterflyPort = RandomPortFinder.GetRandomPort();
var butterflyUrl = $"http://localhost:{butterflyPort}";
var fakeTracer = new FakeTracer();
this.Given(x => GivenFakeOpenTracing(butterflyUrl))
.And(x => GivenServiceOneIsRunning($"http://localhost:{port}", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingOpenTracing(fakeTracer))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id"))
.And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura"))
.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()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureServices(services =>
{
services.AddButterfly(option =>
{
option.CollectorUrl = butterflyUrl;
option.Service = "Service One";
option.IgnoredRoutesRegexPatterns = new string[0];
});
})
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
_downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if (_downstreamPathOne != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
})
.Build();
_serviceOneBuilder.Start();
}
private void GivenFakeOpenTracing(string baseUrl)
{
_fakeOpenTracing = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("OK...");
});
})
.Build();
_fakeOpenTracing.Start();
}
private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
{
_serviceTwoBuilder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureServices(services =>
{
services.AddButterfly(option =>
{
option.CollectorUrl = butterflyUrl;
option.Service = "Service Two";
option.IgnoredRoutesRegexPatterns = new string[0];
});
})
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
_downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if (_downstreamPathTwo != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
})
.Build();
_serviceTwoBuilder.Start();
}
public void Dispose()
{
_serviceOneBuilder?.Dispose();
_serviceTwoBuilder?.Dispose();
_fakeOpenTracing?.Dispose();
_steps.Dispose();
}
}
internal class FakeTracer : ITracer
{
public IScopeManager ScopeManager => throw new NotImplementedException();
public ISpan ActiveSpan => throw new NotImplementedException();
public ISpanBuilder BuildSpan(string operationName)
{
this.BuildSpanCalled++;
return new FakeSpanBuilder();
}
public int BuildSpanCalled { get; set; }
public ISpanContext Extract<TCarrier>(IFormat<TCarrier> format, TCarrier carrier)
{
this.ExtractCalled++;
return null;
}
public int ExtractCalled { get; set; }
public void Inject<TCarrier>(ISpanContext spanContext, IFormat<TCarrier> format, TCarrier carrier)
{
this.InjectCalled++;
}
public int InjectCalled { get; set; }
}
internal class FakeSpanBuilder : ISpanBuilder
{
public ISpanBuilder AddReference(string referenceType, ISpanContext referencedContext)
{
throw new NotImplementedException();
}
public ISpanBuilder AsChildOf(ISpanContext parent)
{
throw new NotImplementedException();
}
public ISpanBuilder AsChildOf(ISpan parent)
{
throw new NotImplementedException();
}
public ISpanBuilder IgnoreActiveSpan()
{
throw new NotImplementedException();
}
public ISpan Start()
{
throw new NotImplementedException();
}
public IScope StartActive()
{
throw new NotImplementedException();
}
public IScope StartActive(bool finishSpanOnDispose)
{
return new FakeScope(finishSpanOnDispose);
}
public ISpanBuilder WithStartTimestamp(DateTimeOffset timestamp)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(string key, string value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(string key, bool value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(string key, int value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(string key, double value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(BooleanTag tag, bool value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(IntOrStringTag tag, string value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(IntTag tag, int value)
{
throw new NotImplementedException();
}
public ISpanBuilder WithTag(StringTag tag, string value)
{
throw new NotImplementedException();
}
}
internal class FakeScope : IScope
{
private readonly bool finishSpanOnDispose;
public FakeScope(bool finishSpanOnDispose)
{
this.finishSpanOnDispose = finishSpanOnDispose;
}
public ISpan Span { get; } = new FakeSpan();
public void Dispose()
{
if (this.finishSpanOnDispose)
{
this.Span.Finish();
}
}
}
internal class FakeSpan : ISpan
{
public ISpanContext Context => new FakeSpanContext();
public void Finish()
{
}
public void Finish(DateTimeOffset finishTimestamp)
{
throw new NotImplementedException();
}
public string GetBaggageItem(string key)
{
throw new NotImplementedException();
}
public ISpan Log(IEnumerable<KeyValuePair<string, object>> fields)
{
return this;
}
public ISpan Log(DateTimeOffset timestamp, IEnumerable<KeyValuePair<string, object>> fields)
{
throw new NotImplementedException();
}
public ISpan Log(string @event)
{
throw new NotImplementedException();
}
public ISpan Log(DateTimeOffset timestamp, string @event)
{
throw new NotImplementedException();
}
public ISpan SetBaggageItem(string key, string value)
{
throw new NotImplementedException();
}
public ISpan SetOperationName(string operationName)
{
throw new NotImplementedException();
}
public ISpan SetTag(string key, string value)
{
return this;
}
public ISpan SetTag(string key, bool value)
{
return this;
}
public ISpan SetTag(string key, int value)
{
return this;
}
public ISpan SetTag(string key, double value)
{
return this;
}
public ISpan SetTag(BooleanTag tag, bool value)
{
return this;
}
public ISpan SetTag(IntOrStringTag tag, string value)
{
return this;
}
public ISpan SetTag(IntTag tag, int value)
{
return this;
}
public ISpan SetTag(StringTag tag, string value)
{
return this;
}
}
internal class FakeSpanContext : ISpanContext
{
public static string FakeTraceId = "FakeTraceId";
public static string FakeSpanId = "FakeSpanId";
public string TraceId => FakeTraceId;
public string SpanId => FakeSpanId;
public IEnumerable<KeyValuePair<string, string>> GetBaggageItems()
{
throw new NotImplementedException();
}
}
}

View File

@ -46,6 +46,7 @@ namespace Ocelot.AcceptanceTests
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
using CookieHeaderValue = Microsoft.Net.Http.Headers.CookieHeaderValue; using CookieHeaderValue = Microsoft.Net.Http.Headers.CookieHeaderValue;
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
using Ocelot.Tracing.OpenTracing;
public class Steps : IDisposable public class Steps : IDisposable
{ {
@ -1214,6 +1215,41 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _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<OpenTracing.ITracer>(fakeTracer);
})
.Configure(app =>
{
app.Use(async (_, next) =>
{
await next.Invoke();
});
app.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
public void ThenWarningShouldBeLogged() public void ThenWarningShouldBeLogged()
{ {
MockLoggerFactory loggerFactory = (MockLoggerFactory)_ocelotServer.Host.Services.GetService<IOcelotLoggerFactory>(); MockLoggerFactory loggerFactory = (MockLoggerFactory)_ocelotServer.Host.Services.GetService<IOcelotLoggerFactory>();