diff --git a/Ocelot.sln b/Ocelot.sln index e943d369..60369059 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -9,23 +9,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore .gitignore = .gitignore - build-and-release-unstable.ps1 = build-and-release-unstable.ps1 - build-and-run-tests.ps1 = build-and-run-tests.ps1 build.cake = build.cake build.ps1 = build.ps1 codeanalysis.ruleset = codeanalysis.ruleset - docker-compose.yaml = docker-compose.yaml - Dockerfile = Dockerfile GitVersion.yml = GitVersion.yml - global.json = global.json LICENSE.md = LICENSE.md README.md = README.md - release.ps1 = release.ps1 ReleaseNotes.md = ReleaseNotes.md - run-acceptance-tests.ps1 = run-acceptance-tests.ps1 - run-benchmarks.ps1 = run-benchmarks.ps1 - run-unit-tests.ps1 = run-unit-tests.ps1 - version.ps1 = version.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5B401523-36DA-4491-B73A-7590A26E420B}" diff --git a/samples/OcelotBasic/Properties/launchSettings.json b/samples/OcelotBasic/Properties/launchSettings.json new file mode 100644 index 00000000..b500ae57 --- /dev/null +++ b/samples/OcelotBasic/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55029/", + "sslPort": 44390 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "OcelotBasic": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 92d3128e..a48aa84d 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,6 +1,9 @@ +using System.Net; +using System.Net.Http; using Ocelot.Logging; using Ocelot.Middleware; using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.Requester.Middleware { @@ -22,6 +25,8 @@ namespace Ocelot.Requester.Middleware { var response = await _requester.GetResponse(context); + CreateLogBasedOnResponse(response); + if (response.IsError) { Logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); @@ -36,5 +41,19 @@ namespace Ocelot.Requester.Middleware await _next.Invoke(context); } + + private void CreateLogBasedOnResponse(Response response) + { + if (response.Data?.StatusCode <= HttpStatusCode.BadRequest) + { + Logger.LogInformation( + $"{(int)response.Data.StatusCode} ({response.Data.ReasonPhrase}) status code, request uri: {response.Data.RequestMessage?.RequestUri}"); + } + else if (response.Data?.StatusCode >= HttpStatusCode.BadRequest) + { + Logger.LogWarning( + $"{(int) response.Data.StatusCode} ({response.Data.ReasonPhrase}) status code, request uri: {response.Data.RequestMessage?.RequestUri}"); + } + } } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 4782eeb5..356561bb 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,74 +1,75 @@ - - - 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/ResponseCodeTests.cs b/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs index ac2b6be8..cd5be7a7 100644 --- a/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs +++ b/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs @@ -34,7 +34,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 50092, + Port = 51092, } }, UpstreamPathTemplate = "/{everything}", @@ -43,7 +43,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:50092", "/inline.132.bundle.js", 304)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51092", "/inline.132.bundle.js", 304)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/inline.132.bundle.js")) diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index f72efff8..fd04af61 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -20,7 +20,8 @@ [Fact] public void should_return_internal_server_error_if_downstream_service_returns_internal_server_error() - { + { + var configuration = new FileConfiguration { ReRoutes = new List @@ -49,6 +50,39 @@ .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError)) .BDDfy(); + } + + [Fact] + public void should_log_warning_if_downstream_service_returns_internal_server_error() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53876, + }, + }, + DownstreamScheme = "http", + }, + }, + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53876")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithLogger()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenWarningShouldBeLogged()) + .BDDfy(); } private void GivenThereIsAServiceRunningOn(string url) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5bddcd92..9bfb2091 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -10,12 +10,14 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; + using Moq; using Newtonsoft.Json; using Ocelot.Cache.CacheManager; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; using Ocelot.Infrastructure; + using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.Middleware.Multiplexer; using Ocelot.Provider.Consul; @@ -1120,5 +1122,60 @@ _ocelotClient = _ocelotServer.CreateClient(); } + + public void GivenOcelotIsRunningWithLogger() + { + _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", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + s.AddSingleton(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void ThenWarningShouldBeLogged() + { + MockLoggerFactory loggerFactory = (MockLoggerFactory)_ocelotServer.Host.Services.GetService(); + loggerFactory.Verify(); + } + + internal class MockLoggerFactory : IOcelotLoggerFactory + { + private Mock _logger; + + public IOcelotLogger CreateLogger() + { + if (_logger == null) + { + _logger = new Mock(); + _logger.Setup(x => x.LogWarning(It.IsAny())).Verifiable(); + } + return _logger.Object; + } + + public void Verify() + { + _logger.Verify(x => x.LogWarning(It.IsAny()), Times.Once); + } + } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index a597df6b..8a7b1fc8 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -41,9 +41,10 @@ namespace Ocelot.UnitTests.Requester public void should_call_services_correctly() { this.Given(x => x.GivenTheRequestIs()) - .And(x => x.GivenTheRequesterReturns(new OkResponse(new HttpResponseMessage()))) + .And(x => x.GivenTheRequesterReturns(new OkResponse(new HttpResponseMessage(System.Net.HttpStatusCode.OK)))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheDownstreamResponseIsSet()) + .Then(x => InformationIsLogged()) .BDDfy(); } @@ -57,6 +58,17 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void should_log_downstream_internal_server_error() + { + this.Given(x => x.GivenTheRequestIs()) + .And(x => x.GivenTheRequesterReturns( + new OkResponse(new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError)))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.WarningIsLogged()) + .BDDfy(); + } + private void ThenTheErrorIsSet() { _downstreamContext.IsError.ShouldBeTrue(); @@ -98,5 +110,23 @@ namespace Ocelot.UnitTests.Requester _downstreamContext.DownstreamResponse.Content.ShouldBe(_response.Data.Content); _downstreamContext.DownstreamResponse.StatusCode.ShouldBe(_response.Data.StatusCode); } + + private void WarningIsLogged() + { + _logger.Verify( + x => x.LogWarning( + It.IsAny() + ), + Times.Once); + } + + private void InformationIsLogged() + { + _logger.Verify( + x => x.LogInformation( + It.IsAny() + ), + Times.Once); + } } }