diff --git a/.gitignore b/.gitignore index b94e9a479..69800e3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ bld/ # Visual Studio 2015 cache/options directory .vs/ -# Files created by bundling and minification on startup +# .js files created on build: src/Web/WebMVC/wwwroot/js/site* # Uncomment if you have tasks that create the project's static files in wwwroot diff --git a/docker-compose.override.yml b/docker-compose.override.yml index f8d608a27..7353c744c 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -7,6 +7,12 @@ version: '3.4' # An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance. services: + seq: + environment: + - ACCEPT_EULA=Y + ports: + - "5340:80" + sql.data: environment: - SA_PASSWORD=Pass@word diff --git a/docker-compose.yml b/docker-compose.yml index 194d7e06c..55d5b10e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,9 @@ version: '3.4' services: + seq: + image: datalust/seq:latest + sql.data: image: microsoft/mssql-server-linux:2017-latest diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs index eeb58ac3b..924b5b1aa 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs @@ -57,7 +57,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs index 0fcd13a41..6d3da29b7 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs @@ -57,7 +57,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs index 2e0555e61..93e5b2917 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs @@ -72,7 +72,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex.ToString()); + _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message); } ); @@ -88,7 +88,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _connection.CallbackException += OnCallbackException; _connection.ConnectionBlocked += OnConnectionBlocked; - _logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events"); + _logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName); return true; } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index a3b6437ef..f24616dc2 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -76,7 +76,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex.ToString()); + _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message); }); using (var channel = _persistentConnection.CreateModel()) diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs index d16eb4625..a60e9a147 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs @@ -83,7 +83,7 @@ } catch (ServiceBusException) { - _logger.LogInformation($"The messaging entity {eventName} already exists."); + _logger.LogWarning("The messaging entity {eventName} already exists.", eventName); } } @@ -105,7 +105,7 @@ } catch (MessagingEntityNotFoundException) { - _logger.LogInformation($"The messaging entity {eventName} Could not be found."); + _logger.LogWarning("The messaging entity {eventName} Could not be found.", eventName); } _subsManager.RemoveSubscription(); @@ -194,7 +194,7 @@ } catch (MessagingEntityNotFoundException) { - _logger.LogInformation($"The messaging entity { RuleDescription.DefaultRuleName } Could not be found."); + _logger.LogWarning("The messaging entity {DefaultRuleName} Could not be found.", RuleDescription.DefaultRuleName); } } } diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs index 0da18f581..fa06e0a0b 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Hosting try { - logger.LogInformation($"Migrating database associated with context {typeof(TContext).Name}"); + logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); if (underK8s) { @@ -55,11 +55,11 @@ namespace Microsoft.AspNetCore.Hosting retry.Execute(() => InvokeSeeder(seeder, context, services)); } - logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}"); + logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); } catch (Exception ex) { - logger.LogError(ex, $"An error occurred while migrating the database used on context {typeof(TContext).Name}"); + logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); if (underK8s) { throw; // Rethrow under k8s because we rely on k8s to re-run the pod diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index d6459698c..087fdca66 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -13,25 +13,28 @@ - - + + + + + + - - - - + + + + - diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 0e15e65dd..7bab4e969 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Services; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Net; using System.Threading.Tasks; @@ -19,9 +21,15 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers private readonly IBasketRepository _repository; private readonly IIdentityService _identityService; private readonly IEventBus _eventBus; + private readonly ILogger _logger; - public BasketController(IBasketRepository repository, IIdentityService identityService, IEventBus eventBus) + public BasketController( + ILogger logger, + IBasketRepository repository, + IIdentityService identityService, + IEventBus eventBus) { + _logger = logger; _repository = repository; _identityService = identityService; _eventBus = eventBus; @@ -50,7 +58,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers public async Task CheckoutAsync([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) { var userId = _identityService.GetUserIdentity(); - + basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ? guid : basketCheckout.RequestId; @@ -70,7 +78,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers // Once basket is checkout, sends an integration event to // ordering.api to convert basket to order and proceeds with // order creation process - _eventBus.Publish(eventMessage); + try + { + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppName, eventMessage); + + _eventBus.Publish(eventMessage); + } + catch (Exception ex) + { + _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); + + throw; + } return Accepted(); } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs index 19ae1b594..cb7b6a2d6 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs @@ -1,6 +1,9 @@ using Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.Services.Basket.API; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Threading.Tasks; @@ -9,15 +12,24 @@ namespace Basket.API.IntegrationEvents.EventHandling public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler { private readonly IBasketRepository _repository; + private readonly ILogger _logger; - public OrderStartedIntegrationEventHandler(IBasketRepository repository) + public OrderStartedIntegrationEventHandler( + IBasketRepository repository, + ILogger logger) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStartedIntegrationEvent @event) { - await _repository.DeleteBasketAsync(@event.UserId.ToString()); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _repository.DeleteBasketAsync(@event.UserId.ToString()); + } } } } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs index 546483b40..c27200e6f 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Linq; using System.Threading.Tasks; @@ -9,22 +11,31 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even { public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler { + private readonly ILogger _logger; private readonly IBasketRepository _repository; - public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository) + public ProductPriceChangedIntegrationEventHandler( + ILogger logger, + IBasketRepository repository) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _repository = repository ?? throw new ArgumentNullException(nameof(repository)); } public async Task Handle(ProductPriceChangedIntegrationEvent @event) { - var userIds = _repository.GetUsers(); - - foreach (var id in userIds) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var basket = await _repository.GetBasketAsync(id); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); + var userIds = _repository.GetUsers(); + + foreach (var id in userIds) + { + var basket = await _repository.GetBasketAsync(id); + + await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); + } } } @@ -35,17 +46,19 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even if (itemsToUpdate != null) { + _logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); + foreach (var item in itemsToUpdate) { - if(item.UnitPrice == oldPrice) - { + if (item.UnitPrice == oldPrice) + { var originalPrice = item.UnitPrice; item.UnitPrice = newPrice; item.OldUnitPrice = originalPrice; } } await _repository.UpdateBasketAsync(basket); - } + } } } } diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index cf53c1b13..893a52400 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -12,52 +12,80 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) .UseFailing(options => - { - options.ConfigPath = "/Failing"; - }) - .UseContentRoot(Directory.GetCurrentDirectory()) + options.ConfigPath = "/Failing") .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); - - var configurationBuilder = new ConfigurationBuilder(); - - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } - - configurationBuilder.AddEnvironmentVariables(); - - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) .UseApplicationInsights() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 4b9d65cc7..235b787d0 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -50,7 +50,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { - RegisterAppInsights(services); + RegisterAppInsights(services); // Add framework services. services.AddMvc(options => @@ -66,7 +66,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddCustomHealthCheck(Configuration); - services.Configure(Configuration); + services.Configure(Configuration); //By connecting here we are making sure that our service //cannot start until redis is ready. This might slow down startup, @@ -179,9 +179,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) diff --git a/src/Services/Basket/Basket.API/appsettings.json b/src/Services/Basket/Basket.API/appsettings.json index 4bff4d70d..33f1c299f 100644 --- a/src/Services/Basket/Basket.API/appsettings.json +++ b/src/Services/Basket/Basket.API/appsettings.json @@ -1,10 +1,13 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "IdentityUrl": "http://localhost:5105", diff --git a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs index a2e382b92..0045ce4aa 100644 --- a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs +++ b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; using Moq; using System; using System.Collections.Generic; @@ -20,12 +21,14 @@ namespace UnitTest.Basket.Application private readonly Mock _basketRepositoryMock; private readonly Mock _identityServiceMock; private readonly Mock _serviceBusMock; + private readonly Mock> _loggerMock; public BasketWebApiTest() { _basketRepositoryMock = new Mock(); _identityServiceMock = new Mock(); _serviceBusMock = new Mock(); + _loggerMock = new Mock>(); } [Fact] @@ -43,7 +46,11 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); + var actionResult = await basketController.GetBasketByIdAsync(fakeCustomerId); //Assert @@ -65,14 +72,17 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); var actionResult = await basketController.UpdateBasketAsync(fakeCustomerBasket); //Assert Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId); - } + } [Fact] public async Task Doing_Checkout_Without_Basket_Should_Return_Bad_Request() @@ -84,7 +94,10 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); var result = await basketController.CheckoutAsync(new BasketCheckout(), Guid.NewGuid().ToString()) as BadRequestResult; Assert.NotNull(result); @@ -102,7 +115,10 @@ namespace UnitTest.Basket.Application _identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId); var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); basketController.ControllerContext = new ControllerContext() { @@ -122,7 +138,7 @@ namespace UnitTest.Basket.Application } private CustomerBasket GetCustomerBasketFake(string fakeCustomerId) - { + { return new CustomerBasket(fakeCustomerId) { Items = new List() diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index e7c996586..983cc291a 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -34,24 +34,25 @@ - + + - - - + + + diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs index e7ea1e09d..8bdd2a401 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs @@ -76,14 +76,14 @@ } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPreconfiguredCatalogBrands(); } return File.ReadAllLines(csvFileCatalogBrands) .Skip(1) // skip header row .SelectTry(x => CreateCatalogBrand(x)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -131,14 +131,14 @@ } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPreconfiguredCatalogTypes(); } return File.ReadAllLines(csvFileCatalogTypes) .Skip(1) // skip header row .SelectTry(x => CreateCatalogType(x)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -186,7 +186,7 @@ } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPreconfiguredItems(); } @@ -197,7 +197,7 @@ .Skip(1) // skip header row .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") ) .SelectTry(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -377,7 +377,7 @@ sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}"); + logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); } ); } diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs index 8c550bf27..3b9476b9f 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs @@ -4,7 +4,10 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; +using Microsoft.eShopOnContainers.Services.Catalog.API; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Data.Common; using System.Threading.Tasks; @@ -17,10 +20,15 @@ namespace Catalog.API.IntegrationEvents private readonly IEventBus _eventBus; private readonly CatalogContext _catalogContext; private readonly IIntegrationEventLogService _eventLogService; + private readonly ILogger _logger; - public CatalogIntegrationEventService(IEventBus eventBus, CatalogContext catalogContext, - Func integrationEventLogServiceFactory) + public CatalogIntegrationEventService( + ILogger logger, + IEventBus eventBus, + CatalogContext catalogContext, + Func integrationEventLogServiceFactory) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _catalogContext = catalogContext ?? throw new ArgumentNullException(nameof(catalogContext)); _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); @@ -31,26 +39,31 @@ namespace Catalog.API.IntegrationEvents { try { + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); + await _eventLogService.MarkEventAsInProgressAsync(evt.Id); _eventBus.Publish(evt); await _eventLogService.MarkEventAsPublishedAsync(evt.Id); } - catch (Exception) + catch (Exception ex) { + _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); await _eventLogService.MarkEventAsFailedAsync(evt.Id); - } + } } public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) { + _logger.LogInformation("----- CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id); + //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - await ResilientTransaction.New(_catalogContext) - .ExecuteAsync(async () => { - // Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction - await _catalogContext.SaveChangesAsync(); - await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction()); - }); + await ResilientTransaction.New(_catalogContext).ExecuteAsync(async () => + { + // Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction + await _catalogContext.SaveChangesAsync(); + await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction()); + }); } } } diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs index 0f30a3e0a..493a271cc 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -8,39 +8,51 @@ using System.Linq; using global::Catalog.API.IntegrationEvents; using IntegrationEvents.Events; + using Serilog.Context; + using Microsoft.Extensions.Logging; public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : IIntegrationEventHandler { private readonly CatalogContext _catalogContext; private readonly ICatalogIntegrationEventService _catalogIntegrationEventService; + private readonly ILogger _logger; - public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(CatalogContext catalogContext, - ICatalogIntegrationEventService catalogIntegrationEventService) + public OrderStatusChangedToAwaitingValidationIntegrationEventHandler( + CatalogContext catalogContext, + ICatalogIntegrationEventService catalogIntegrationEventService, + ILogger logger) { _catalogContext = catalogContext; _catalogIntegrationEventService = catalogIntegrationEventService; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent command) + public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) { - var confirmedOrderStockItems = new List(); - - foreach (var orderStockItem in command.OrderStockItems) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); - var hasStock = catalogItem.AvailableStock >= orderStockItem.Units; - var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - confirmedOrderStockItems.Add(confirmedOrderStockItem); - } + var confirmedOrderStockItems = new List(); + + foreach (var orderStockItem in @event.OrderStockItems) + { + var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); + var hasStock = catalogItem.AvailableStock >= orderStockItem.Units; + var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock); - var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock) - ? (IntegrationEvent) new OrderStockRejectedIntegrationEvent(command.OrderId, confirmedOrderStockItems) - : new OrderStockConfirmedIntegrationEvent(command.OrderId); + confirmedOrderStockItems.Add(confirmedOrderStockItem); + } - await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent); - await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent); + var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock) + ? (IntegrationEvent)new OrderStockRejectedIntegrationEvent(@event.OrderId, confirmedOrderStockItems) + : new OrderStockConfirmedIntegrationEvent(@event.OrderId); + + await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent); + await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent); + + } } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs index 0a45547f9..7d383254f 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs @@ -4,28 +4,40 @@ using System.Threading.Tasks; using Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; + using Microsoft.Extensions.Logging; + using Serilog.Context; public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler { private readonly CatalogContext _catalogContext; + private readonly ILogger _logger; - public OrderStatusChangedToPaidIntegrationEventHandler(CatalogContext catalogContext) + public OrderStatusChangedToPaidIntegrationEventHandler( + CatalogContext catalogContext, + ILogger logger) { _catalogContext = catalogContext; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderStatusChangedToPaidIntegrationEvent command) + public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event) { - //we're not blocking stock/inventory - foreach (var orderStockItem in command.OrderStockItems) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - catalogItem.RemoveStock(orderStockItem.Units); - } + //we're not blocking stock/inventory + foreach (var orderStockItem in @event.OrderStockItems) + { + var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); + + catalogItem.RemoveStock(orderStockItem.Units); + } - await _catalogContext.SaveChangesAsync(); + await _catalogContext.SaveChangesAsync(); + + } } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index c4ebb6183..c75b56c39 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -9,66 +9,97 @@ using Microsoft.Extensions.Options; using Serilog; using System; using System.IO; + namespace Microsoft.eShopOnContainers.Services.Catalog.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args) - .MigrateDbContext((context,services)=> + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + host.MigrateDbContext((context, services) => { var env = services.GetService(); var settings = services.GetService>(); var logger = services.GetService>(); new CatalogContextSeed() - .SeedAsync(context,env,settings,logger) - .Wait(); - + .SeedAsync(context, env, settings, logger) + .Wait(); }) - .MigrateDbContext((_,__)=> { }) - .Run(); + .MigrateDbContext((_, __) => { }); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseStartup() + .CaptureStartupErrors(false) + .UseStartup() .UseApplicationInsights() .UseContentRoot(Directory.GetCurrentDirectory()) .UseWebRoot("Pics") - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); + .UseConfiguration(configuration) + .UseSerilog() + .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var configurationBuilder = new ConfigurationBuilder(); + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); - configurationBuilder.AddEnvironmentVariables(); + var config = builder.Build(); - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json index 2b21ca280..8f2cde4db 100644 --- a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json +++ b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json @@ -13,7 +13,10 @@ "launchBrowser": true, "launchUrl": "/swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ConnectionString": "server=localhost,5433;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", + "ASPNETCORE_ENVIRONMENT": "Development", + "EventBusConnection": "localhost", + "Serilog:SeqServerUrl": "http://locahost:5340" } }, "Microsoft.eShopOnContainers.Services.Catalog.API": { diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 9c91a743e..0258a0a98 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -60,17 +60,18 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API } - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //Configure logs - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } diff --git a/src/Services/Catalog/Catalog.API/appsettings.json b/src/Services/Catalog/Catalog.API/appsettings.json index 0bf489699..b26f63bff 100644 --- a/src/Services/Catalog/Catalog.API/appsettings.json +++ b/src/Services/Catalog/Catalog.API/appsettings.json @@ -2,12 +2,15 @@ "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", "UseCustomizationData": false, - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "AzureServiceBusEnabled": false, diff --git a/src/Services/Catalog/Catalog.API/web.config b/src/Services/Catalog/Catalog.API/web.config index e04a0397b..2157aef31 100644 --- a/src/Services/Catalog/Catalog.API/web.config +++ b/src/Services/Catalog/Catalog.API/web.config @@ -2,8 +2,10 @@ - + - + + + \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs index 3dbf28171..a1fda6455 100644 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs +++ b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs @@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data { retryForAvaiability++; - logger.LogError(ex.Message,$"There is an error migrating data for ApplicationDbContext"); + logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext)); await SeedAsync(context,env,logger,settings, retryForAvaiability); } @@ -80,7 +80,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetDefaultUser(); } @@ -89,7 +89,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data .Skip(1) // skip header column .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") ) .SelectTry(column => CreateApplicationUser(column, csvheaders)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null) .ToList(); @@ -199,7 +199,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); if (!File.Exists(imagesZipFile)) { - logger.LogError($" zip file '{imagesZipFile}' does not exists."); + logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); return; } @@ -221,14 +221,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data } else { - logger.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'"); + logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); } } } } catch (Exception ex) { - logger.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}"); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ; } } } diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index fe4432af8..fd5f1474e 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -13,25 +13,28 @@ - - - + + + - + + + + - - - + + + + - + - diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index 401a2b8a5..8204a1ebf 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -14,70 +14,99 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args) - .MigrateDbContext((_, __) => { }) - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var logger = services.GetService>(); - var settings = services.GetService>(); - - new ApplicationDbContextSeed() - .SeedAsync(context, env, logger, settings) - .Wait(); - }) - .MigrateDbContext((context,services)=> - { - var configuration = services.GetService(); - - new ConfigurationDbContextSeed() - .SeedAsync(context, configuration) - .Wait(); - }).Run(); - } + var configuration = GetConfiguration(); - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); - var configurationBuilder = new ConfigurationBuilder(); + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + host.MigrateDbContext((_, __) => { }) + .MigrateDbContext((context, services) => + { + var env = services.GetService(); + var logger = services.GetService>(); + var settings = services.GetService>(); - if (Convert.ToBoolean(builtConfig["UseVault"])) + new ApplicationDbContextSeed() + .SeedAsync(context, env, logger, settings) + .Wait(); + }) + .MigrateDbContext((context, services) => { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } - - configurationBuilder.AddEnvironmentVariables(); - - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) + new ConfigurationDbContextSeed() + .SeedAsync(context, configuration) + .Wait(); + }); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => + WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) + .UseStartup() .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } + } } diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index b6e0d1390..e672bab60 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -42,12 +42,12 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API // Add framework services. services.AddDbContext(options => options.UseSqlServer(Configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - })); + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + })); services.AddIdentity() .AddEntityFrameworkStores() @@ -90,22 +90,22 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(migrationsAssembly); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(migrationsAssembly); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); }) .Services.AddTransient(); @@ -117,8 +117,11 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + { + //loggerFactory.AddConsole(Configuration.GetSection("Logging")); + //loggerFactory.AddDebug(); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); if (env.IsDevelopment()) { @@ -133,7 +136,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } diff --git a/src/Services/Identity/Identity.API/appsettings.json b/src/Services/Identity/Identity.API/appsettings.json index c5a109218..74d8e826c 100644 --- a/src/Services/Identity/Identity.API/appsettings.json +++ b/src/Services/Identity/Identity.API/appsettings.json @@ -5,12 +5,15 @@ "SpaClient": "http://localhost:5104", "XamarinCallback": "http://localhost:5105/xamarincallback", "UseCustomizationData": false, - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "ApplicationInsights": { diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index e9c299d23..f6b9ed708 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -6,6 +6,7 @@ using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Locations.API.Model; using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; + using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -14,11 +15,16 @@ { private readonly ILocationsRepository _locationsRepository; private readonly IEventBus _eventBus; + private readonly ILogger _logger; - public LocationsService(ILocationsRepository locationsRepository, IEventBus eventBus) + public LocationsService( + ILocationsRepository locationsRepository, + IEventBus eventBus, + ILogger logger) { _locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task GetLocationAsync(int locationId) @@ -37,11 +43,11 @@ } public async Task AddOrUpdateUserLocationAsync(string userId, LocationRequest currentPosition) - { + { // Get the list of ordered regions the user currently is within var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition); - - if(currentUserAreaLocationList is null) + + if (currentUserAreaLocationList is null) { throw new LocationDomainException("User current area not found"); } @@ -66,13 +72,17 @@ { var newUserLocations = MapUserLocationDetails(newLocations); var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations); + + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _eventBus.Publish(@event); } private List MapUserLocationDetails(List newLocations) { var result = new List(); - newLocations.ForEach(location => { + newLocations.ForEach(location => + { result.Add(new UserLocationDetails() { LocationId = location.LocationId, diff --git a/src/Services/Location/Locations.API/Locations.API.csproj b/src/Services/Location/Locations.API/Locations.API.csproj index 247865c85..9784c1f4d 100644 --- a/src/Services/Location/Locations.API/Locations.API.csproj +++ b/src/Services/Location/Locations.API/Locations.API.csproj @@ -6,28 +6,32 @@ aspnet-Locations.API-20161122013619 - - + + + - - + + + + + diff --git a/src/Services/Location/Locations.API/Program.cs b/src/Services/Location/Locations.API/Program.cs index 11be8d68c..c13df985f 100644 --- a/src/Services/Location/Locations.API/Program.cs +++ b/src/Services/Location/Locations.API/Program.cs @@ -11,48 +11,78 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseContentRoot(Directory.GetCurrentDirectory()) + .CaptureStartupErrors(false) .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); - - var configurationBuilder = new ConfigurationBuilder(); - - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } - - configurationBuilder.AddEnvironmentVariables(); - - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } -} +} \ No newline at end of file diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index f92000b84..6d5fe3200 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -153,9 +153,10 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) diff --git a/src/Services/Location/Locations.API/appsettings.json b/src/Services/Location/Locations.API/appsettings.json index cd4166bb0..43fe53873 100644 --- a/src/Services/Location/Locations.API/appsettings.json +++ b/src/Services/Location/Locations.API/appsettings.json @@ -2,12 +2,15 @@ "ConnectionString": "mongodb://nosql.data", "Database": "LocationsDb", "IdentityUrl": "http://localhost:5105", - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "AzureServiceBusEnabled": false, diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs index c42b1975e..af1786940 100644 --- a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs +++ b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs @@ -76,7 +76,7 @@ sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}"); + logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); } ); } diff --git a/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs b/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs index 77574c0e5..3d5e62e45 100644 --- a/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs +++ b/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs @@ -4,6 +4,8 @@ using Marketing.API.Model; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories; + using Microsoft.Extensions.Logging; + using Serilog.Context; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -12,20 +14,29 @@ : IIntegrationEventHandler { private readonly IMarketingDataRepository _marketingDataRepository; + private readonly ILogger _logger; - public UserLocationUpdatedIntegrationEventHandler(IMarketingDataRepository repository) + public UserLocationUpdatedIntegrationEventHandler( + IMarketingDataRepository repository, + ILogger logger) { _marketingDataRepository = repository ?? throw new ArgumentNullException(nameof(repository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(UserLocationUpdatedIntegrationEvent @event) { - var userMarketingData = await _marketingDataRepository.GetAsync(@event.UserId); - userMarketingData = userMarketingData ?? - new MarketingData() { UserId = @event.UserId }; + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - userMarketingData.Locations = MapUpdatedUserLocations(@event.LocationList); - await _marketingDataRepository.UpdateLocationAsync(userMarketingData); + var userMarketingData = await _marketingDataRepository.GetAsync(@event.UserId); + userMarketingData = userMarketingData ?? + new MarketingData() { UserId = @event.UserId }; + + userMarketingData.Locations = MapUpdatedUserLocations(@event.LocationList); + await _marketingDataRepository.UpdateLocationAsync(userMarketingData); + } } private List MapUpdatedUserLocations(List newUserLocations) diff --git a/src/Services/Marketing/Marketing.API/Marketing.API.csproj b/src/Services/Marketing/Marketing.API/Marketing.API.csproj index 23116cc28..373bcb0a6 100644 --- a/src/Services/Marketing/Marketing.API/Marketing.API.csproj +++ b/src/Services/Marketing/Marketing.API/Marketing.API.csproj @@ -21,27 +21,30 @@ - - + + + + - - + + + diff --git a/src/Services/Marketing/Marketing.API/Program.cs b/src/Services/Marketing/Marketing.API/Program.cs index cca7f4d79..ae26c9786 100644 --- a/src/Services/Marketing/Marketing.API/Program.cs +++ b/src/Services/Marketing/Marketing.API/Program.cs @@ -12,59 +12,89 @@ public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args) - .MigrateDbContext((context, services) => + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try { - var logger = services.GetService>(); + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + host.MigrateDbContext((context, services) => + { + var logger = services.GetService>(); + + new MarketingContextSeed() + .SeedAsync(context, logger) + .Wait(); + }); - new MarketingContextSeed() - .SeedAsync(context,logger) - .Wait(); + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); - }).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) + .UseStartup() .UseApplicationInsights() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() .UseWebRoot("Pics") - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); + .UseConfiguration(configuration) + .UseSerilog() + .Build(); - var configurationBuilder = new ConfigurationBuilder(); + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } - configurationBuilder.AddEnvironmentVariables(); + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) - .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } } diff --git a/src/Services/Marketing/Marketing.API/Startup.cs b/src/Services/Marketing/Marketing.API/Startup.cs index 276a82fa9..75c749452 100644 --- a/src/Services/Marketing/Marketing.API/Startup.cs +++ b/src/Services/Marketing/Marketing.API/Startup.cs @@ -179,7 +179,8 @@ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; diff --git a/src/Services/Marketing/Marketing.API/appsettings.json b/src/Services/Marketing/Marketing.API/appsettings.json index 2af660446..c16978dce 100644 --- a/src/Services/Marketing/Marketing.API/appsettings.json +++ b/src/Services/Marketing/Marketing.API/appsettings.json @@ -1,8 +1,13 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word", diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/BehaviorsHelperExtensions.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/BehaviorsHelperExtensions.cs new file mode 100644 index 000000000..7374ff164 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/BehaviorsHelperExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Behaviors +{ + internal static class BehaviorsHelperExtensions + { + internal static string GetGenericTypeName(this object @object) + { + var typeName = string.Empty; + var type = @object.GetType(); + + if (type.IsGenericType) + { + var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray()); + typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>"; + } + else + { + typeName = type.Name; + } + + return typeName; + } + + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs index 2708db8a9..70eaa724c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/LoggingBehavior.cs @@ -1,9 +1,10 @@ using MediatR; using Microsoft.Extensions.Logging; +using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Ordering.API.Infrastructure.Behaviors +namespace Ordering.API.Application.Behaviors { public class LoggingBehavior : IPipelineBehavior { @@ -12,9 +13,10 @@ namespace Ordering.API.Infrastructure.Behaviors public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - _logger.LogInformation($"Handling {typeof(TRequest).Name}"); + _logger.LogInformation("----- Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request); var response = await next(); - _logger.LogInformation($"Handled {typeof(TResponse).Name}"); + _logger.LogInformation("----- Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response); + return response; } } diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs index 6f9aed9e5..134564fdd 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs @@ -3,9 +3,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using Microsoft.Extensions.Logging; using Ordering.API.Application.IntegrationEvents; +using Serilog.Context; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -28,33 +27,41 @@ namespace Ordering.API.Application.Behaviors public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - TResponse response = default(TResponse); + var response = default(TResponse); + var typeName = request.GetGenericTypeName(); try { - var strategy = _dbContext.Database.CreateExecutionStrategy(); - await strategy.ExecuteAsync(async () => + if (_dbContext.HasActiveTransaction) { - _logger.LogInformation($"Begin transaction {typeof(TRequest).Name}"); + return await next(); + } + + var strategy = _dbContext.Database.CreateExecutionStrategy(); - await _dbContext.BeginTransactionAsync(); + await strategy.ExecuteAsync(async () => + { + using (var transaction = await _dbContext.BeginTransactionAsync()) + using (LogContext.PushProperty("TransactionContext", transaction.TransactionId)) + { + _logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); - response = await next(); + response = await next(); - await _dbContext.CommitTransactionAsync(); + _logger.LogInformation("----- Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); - _logger.LogInformation($"Committed transaction {typeof(TRequest).Name}"); + await _dbContext.CommitTransactionAsync(transaction); + } await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync(); }); return response; } - catch (Exception) + catch (Exception ex) { - _logger.LogInformation($"Rollback transaction executed {typeof(TRequest).Name}"); + _logger.LogError(ex, "ERROR Handling transaction for {CommandName} ({@Command})", typeName, request); - _dbContext.RollbackTransaction(); throw; } } diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/ValidatorBehavior.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/ValidatorBehavior.cs index de0a2ba4b..0fb6e38d4 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/ValidatorBehavior.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/ValidatorBehavior.cs @@ -1,20 +1,30 @@ using FluentValidation; using MediatR; +using Microsoft.Extensions.Logging; using Ordering.Domain.Exceptions; -using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Ordering.API.Infrastructure.Behaviors +namespace Ordering.API.Application.Behaviors { public class ValidatorBehavior : IPipelineBehavior { + private readonly ILogger> _logger; private readonly IValidator[] _validators; - public ValidatorBehavior(IValidator[] validators) => _validators = validators; + + public ValidatorBehavior(IValidator[] validators, ILogger> logger) + { + _validators = validators; + _logger = logger; + } public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { + var typeName = request.GetGenericTypeName(); + + _logger.LogInformation("----- Validating command {CommandType}", typeName); + var failures = _validators .Select(v => v.Validate(request)) .SelectMany(result => result.Errors) @@ -23,12 +33,13 @@ namespace Ordering.API.Infrastructure.Behaviors if (failures.Any()) { + _logger.LogWarning("Validation errors - {CommandType} - Command: {@Command} - Errors: {@ValidationErrors}", typeName, request, failures); + throw new OrderingDomainException( $"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures)); } - var response = await next(); - return response; + return await next(); } } -} +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs index a4c4facb8..85858486c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; @@ -40,7 +41,11 @@ namespace Ordering.API.Application.Commands // Use for Idempotency in Command process public class CancelOrderIdentifiedCommandHandler : IdentifiedCommandHandler { - public CancelOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public CancelOrderIdentifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs index 9a3035d5c..00a088c09 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs @@ -6,6 +6,7 @@ using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; + using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; @@ -18,17 +19,20 @@ private readonly IIdentityService _identityService; private readonly IMediator _mediator; private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; + private readonly ILogger _logger; // Using DI to inject infrastructure persistence Repositories public CreateOrderCommandHandler(IMediator mediator, IOrderingIntegrationEventService orderingIntegrationEventService, - IOrderRepository orderRepository, - IIdentityService identityService) + IOrderRepository orderRepository, + IIdentityService identityService, + ILogger logger) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(CreateOrderCommand message, CancellationToken cancellationToken) @@ -36,20 +40,22 @@ // Add Integration event to clean the basket var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId); await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent); - + // Add/Update the Buyer AggregateRoot // DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root // methods and constructor so validations, invariants and business logic // make sure that consistency is preserved across the whole aggregate var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode); var order = new Order(message.UserId, message.UserName, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration); - + foreach (var item in message.OrderItems) { order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); } - _orderRepository.Add(order); + _logger.LogInformation("----- Creating Order - Order: {@Order}", order); + + _orderRepository.Add(order); return await _orderRepository.UnitOfWork .SaveEntitiesAsync(); @@ -60,7 +66,11 @@ // Use for Idempotency in Command process public class CreateOrderIdentifiedCommandHandler : IdentifiedCommandHandler { - public CreateOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public CreateOrderIdentifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs index f00ea44c8..e1fdc43ef 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs @@ -1,5 +1,9 @@ using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; +using Ordering.API.Application.Behaviors; +using Ordering.API.Application.Commands; +using System; using System.Threading; using System.Threading.Tasks; @@ -16,11 +20,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands { private readonly IMediator _mediator; private readonly IRequestManager _requestManager; + private readonly ILogger> _logger; - public IdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) + public IdentifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) { _mediator = mediator; _requestManager = requestManager; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } /// @@ -48,16 +57,60 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands else { await _requestManager.CreateRequestForCommandAsync(message.Id); - try - { - // Send the embeded business command to mediator so it runs its related CommandHandler - var result = await _mediator.Send(message.Command); - return result; - } - catch - { - return default(R); - } + try + { + var command = message.Command; + var commandName = command.GetGenericTypeName(); + var idProperty = string.Empty; + var commandId = string.Empty; + + switch (command) + { + case CreateOrderCommand createOrderCommand: + idProperty = nameof(createOrderCommand.UserId); + commandId = createOrderCommand.UserId; + break; + + case CancelOrderCommand cancelOrderCommand: + idProperty = nameof(cancelOrderCommand.OrderNumber); + commandId = $"{cancelOrderCommand.OrderNumber}"; + break; + + case ShipOrderCommand shipOrderCommand: + idProperty = nameof(shipOrderCommand.OrderNumber); + commandId = $"{shipOrderCommand.OrderNumber}"; + break; + + default: + idProperty = "Id?"; + commandId = "n/a"; + break; + } + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + commandName, + idProperty, + commandId, + command); + + // Send the embeded business command to mediator so it runs its related CommandHandler + var result = await _mediator.Send(command); + + _logger.LogInformation( + "----- Command result: {@Result} - {CommandName} - {IdProperty}: {CommandId} ({@Command})", + result, + commandName, + idProperty, + commandId, + command); + + return result; + } + catch + { + return default(R); + } } } } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs index cee307ca2..c40ddb37f 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; @@ -40,7 +41,11 @@ namespace Ordering.API.Application.Commands // Use for Idempotency in Command process public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler { - public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public SetAwaitingValidationIdentifiedOrderStatusCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs index 211e568cb..8282bf6ab 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; @@ -43,7 +44,11 @@ namespace Ordering.API.Application.Commands // Use for Idempotency in Command process public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler { - public SetPaidIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public SetPaidIdentifiedOrderStatusCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs index 4e1bc6185..e9c3eb75c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; @@ -43,7 +44,11 @@ namespace Ordering.API.Application.Commands // Use for Idempotency in Command process public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler { - public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public SetStockConfirmedOrderStatusIdenfifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs index 2b7a72526..2e6c794d2 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; @@ -44,7 +45,11 @@ namespace Ordering.API.Application.Commands // Use for Idempotency in Command process public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler { - public SetStockRejectedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public SetStockRejectedOrderStatusIdenfifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs index b4a6ac26d..fcac86ef9 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ namespace Ordering.API.Application.Commands { // Regular CommandHandler public class ShipOrderCommandHandler : IRequestHandler - { + { private readonly IOrderRepository _orderRepository; public ShipOrderCommandHandler(IOrderRepository orderRepository) @@ -26,7 +27,7 @@ namespace Ordering.API.Application.Commands public async Task Handle(ShipOrderCommand command, CancellationToken cancellationToken) { var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); - if(orderToUpdate == null) + if (orderToUpdate == null) { return false; } @@ -40,7 +41,11 @@ namespace Ordering.API.Application.Commands // Use for Idempotency in Command process public class ShipOrderIdentifiedCommandHandler : IdentifiedCommandHandler { - public ShipOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + public ShipOrderIdentifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) { } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs index d738c07ee..bea8eaac5 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs @@ -8,16 +8,16 @@ using System.Threading.Tasks; namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified { - public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler + public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler : INotificationHandler { - private readonly IOrderRepository _orderRepository; - private readonly ILoggerFactory _logger; + private readonly IOrderRepository _orderRepository; + private readonly ILoggerFactory _logger; public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( - IOrderRepository orderRepository, ILoggerFactory logger) + IOrderRepository orderRepository, ILoggerFactory logger) { - _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); + _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -28,10 +28,11 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri { var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); - orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id); + orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id); - _logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler)) - .LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }"); + _logger.CreateLogger() + .LogTrace("Order with Id: {OrderId} has been successfully updated with a payment method {PaymentMethod} ({Id})", + buyerPaymentMethodVerifiedEvent.OrderId, nameof(buyerPaymentMethodVerifiedEvent.Payment), buyerPaymentMethodVerifiedEvent.Payment.Id); } - } + } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs index 32967f6a7..f8fddf3a8 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs @@ -33,9 +33,9 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger(nameof(OrderCancelledDomainEvent)) - .LogTrace($"Order with Id: {orderCancelledDomainEvent.Order.Id} has been successfully updated with " + - $"a status order id: {OrderStatus.Shipped.Id}"); + _logger.CreateLogger() + .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", + orderCancelledDomainEvent.Order.Id, nameof(OrderStatus.Cancelled), OrderStatus.Cancelled.Id); var order = await _orderRepository.GetAsync(orderCancelledDomainEvent.Order.Id); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs index e1c54af4f..edea66895 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs @@ -33,9 +33,9 @@ public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger(nameof(OrderStatusChangedToAwaitingValidationDomainEvent)) - .LogTrace($"Order with Id: {orderStatusChangedToAwaitingValidationDomainEvent.OrderId} has been successfully updated with " + - $"a status order id: {OrderStatus.AwaitingValidation.Id}"); + _logger.CreateLogger() + .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", + orderStatusChangedToAwaitingValidationDomainEvent.OrderId, nameof(OrderStatus.AwaitingValidation), OrderStatus.AwaitingValidation.Id); var order = await _orderRepository.GetAsync(orderStatusChangedToAwaitingValidationDomainEvent.OrderId); diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs index d3dca202f..a934a621d 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs @@ -35,9 +35,9 @@ public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger(nameof(OrderStatusChangedToPaidDomainEventHandler)) - .LogTrace($"Order with Id: {orderStatusChangedToPaidDomainEvent.OrderId} has been successfully updated with " + - $"a status order id: {OrderStatus.Paid.Id}"); + _logger.CreateLogger() + .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", + orderStatusChangedToPaidDomainEvent.OrderId, nameof(OrderStatus.Paid), OrderStatus.Paid.Id); var order = await _orderRepository.GetAsync(orderStatusChangedToPaidDomainEvent.OrderId); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs index 3be83a2ae..7f4754865 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs @@ -33,9 +33,9 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderShipped public async Task Handle(OrderShippedDomainEvent orderShippedDomainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger(nameof(OrderShippedDomainEvent)) - .LogTrace($"Order with Id: {orderShippedDomainEvent.Order.Id} has been successfully updated with " + - $"a status order id: {OrderStatus.Shipped.Id}"); + _logger.CreateLogger() + .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", + orderShippedDomainEvent.Order.Id, nameof(OrderStatus.Shipped), OrderStatus.Shipped.Id); var order = await _orderRepository.GetAsync(orderShippedDomainEvent.Order.Id); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs index 99b2a21a0..e47192dcd 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -61,7 +61,9 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name); await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent); - _logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}."); + _logger.CreateLogger() + .LogTrace("Buyer {BuyerId} and related payment method were validated or updated for orderId: {OrderId}.", + buyerUpdated.Id, orderStartedEvent.Order.Id); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs index e910964e8..0e9ca4f08 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs @@ -33,9 +33,9 @@ public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent, CancellationToken cancellationToken) { - _logger.CreateLogger(nameof(OrderStatusChangedToStockConfirmedDomainEventHandler)) - .LogTrace($"Order with Id: {orderStatusChangedToStockConfirmedDomainEvent.OrderId} has been successfully updated with " + - $"a status order id: {OrderStatus.StockConfirmed.Id}"); + _logger.CreateLogger() + .LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})", + orderStatusChangedToStockConfirmedDomainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id); var order = await _orderRepository.GetAsync(orderStatusChangedToStockConfirmedDomainEvent.OrderId); var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs index f8dcc6edb..0c4c8b470 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs @@ -1,8 +1,12 @@ using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.Services.Ordering.API; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.Extensions.Logging; +using Ordering.API.Application.Behaviors; using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; +using Serilog.Context; using System.Threading.Tasks; namespace Ordering.API.Application.IntegrationEvents.EventHandling @@ -10,10 +14,14 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; + private readonly ILogger _logger; - public GracePeriodConfirmedIntegrationEventHandler(IMediator mediator) + public GracePeriodConfirmedIntegrationEventHandler( + IMediator mediator, + ILogger logger) { _mediator = mediator; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } /// @@ -26,8 +34,21 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling /// public async Task Handle(GracePeriodConfirmedIntegrationEvent @event) { - var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); - await _mediator.Send(command); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + command.GetGenericTypeName(), + nameof(command.OrderNumber), + command.OrderNumber, + command); + + await _mediator.Send(command); + } } } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs index 5f4fc28e1..b145de52c 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs @@ -2,9 +2,13 @@ { using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; + using Microsoft.eShopOnContainers.Services.Ordering.API; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using Microsoft.Extensions.Logging; + using Ordering.API.Application.Behaviors; using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; + using Serilog.Context; using System; using System.Threading.Tasks; @@ -12,16 +16,33 @@ IIntegrationEventHandler { private readonly IMediator _mediator; + private readonly ILogger _logger; - public OrderPaymentFailedIntegrationEventHandler(IMediator mediator) + public OrderPaymentFailedIntegrationEventHandler( + IMediator mediator, + ILogger logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderPaymentFailedIntegrationEvent @event) { - var command = new CancelOrderCommand(@event.OrderId); - await _mediator.Send(command); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + var command = new CancelOrderCommand(@event.OrderId); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + command.GetGenericTypeName(), + nameof(command.OrderNumber), + command.OrderNumber, + command); + + await _mediator.Send(command); + } } } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs index 6c201d77e..c6c0fc232 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs @@ -2,9 +2,13 @@ { using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; + using Microsoft.eShopOnContainers.Services.Ordering.API; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using Microsoft.Extensions.Logging; + using Ordering.API.Application.Behaviors; using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; + using Serilog.Context; using System; using System.Threading.Tasks; @@ -12,16 +16,33 @@ IIntegrationEventHandler { private readonly IMediator _mediator; + private readonly ILogger _logger; - public OrderPaymentSuccededIntegrationEventHandler(IMediator mediator) + public OrderPaymentSuccededIntegrationEventHandler( + IMediator mediator, + ILogger logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderPaymentSuccededIntegrationEvent @event) { - var command = new SetPaidOrderStatusCommand(@event.OrderId); - await _mediator.Send(command); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + var command = new SetPaidOrderStatusCommand(@event.OrderId); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + command.GetGenericTypeName(), + nameof(command.OrderNumber), + command.OrderNumber, + command); + + await _mediator.Send(command); + } } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs index c5561508b..c26746783 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs @@ -7,21 +7,42 @@ using MediatR; using System; using Ordering.API.Application.Commands; + using Microsoft.Extensions.Logging; + using Serilog.Context; + using Microsoft.eShopOnContainers.Services.Ordering.API; + using Ordering.API.Application.Behaviors; - public class OrderStockConfirmedIntegrationEventHandler : + public class OrderStockConfirmedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; + private readonly ILogger _logger; - public OrderStockConfirmedIntegrationEventHandler(IMediator mediator) + public OrderStockConfirmedIntegrationEventHandler( + IMediator mediator, + ILogger logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStockConfirmedIntegrationEvent @event) { - var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId); - await _mediator.Send(command); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + command.GetGenericTypeName(), + nameof(command.OrderNumber), + command.OrderNumber, + command); + + await _mediator.Send(command); + } } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs index af7d98f74..fc1b26588 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs @@ -7,25 +7,46 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using MediatR; using Ordering.API.Application.Commands; + using Microsoft.Extensions.Logging; + using Serilog.Context; + using Microsoft.eShopOnContainers.Services.Ordering.API; + using Ordering.API.Application.Behaviors; public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; + private readonly ILogger _logger; - public OrderStockRejectedIntegrationEventHandler(IMediator mediator) + public OrderStockRejectedIntegrationEventHandler( + IMediator mediator, + ILogger logger) { _mediator = mediator; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStockRejectedIntegrationEvent @event) { - var orderStockRejectedItems = @event.OrderStockItems - .FindAll(c => !c.HasStock) - .Select(c => c.ProductId) - .ToList(); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems); - await _mediator.Send(command); + var orderStockRejectedItems = @event.OrderStockItems + .FindAll(c => !c.HasStock) + .Select(c => c.ProductId) + .ToList(); + + var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + command.GetGenericTypeName(), + nameof(command.OrderNumber), + command.OrderNumber, + command); + + await _mediator.Send(command); + } } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs index 33f327c6b..1861c1339 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs @@ -5,48 +5,77 @@ using System.Threading.Tasks; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.Extensions.Logging; using Ordering.API.Application.IntegrationEvents.Events; +using Serilog.Context; +using Microsoft.eShopOnContainers.Services.Ordering.API; +using Ordering.API.Application.Behaviors; namespace Ordering.API.Application.IntegrationEvents.EventHandling { public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; - private readonly ILoggerFactory _logger; + private readonly ILogger _logger; - public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator, - ILoggerFactory logger) + public UserCheckoutAcceptedIntegrationEventHandler( + IMediator mediator, + ILogger logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// Integration event handler which starts the create order process /// - /// + /// /// Integration event message which is sent by the /// basket.api once it has successfully process the /// order items. /// /// - public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg) + public async Task Handle(UserCheckoutAcceptedIntegrationEvent @event) { - var result = false; - - if (eventMsg.RequestId != Guid.Empty) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street, - eventMsg.State, eventMsg.Country, eventMsg.ZipCode, - eventMsg.CardNumber, eventMsg.CardHolderName, eventMsg.CardExpiration, - eventMsg.CardSecurityNumber, eventMsg.CardTypeId); - - var requestCreateOrder = new IdentifiedCommand(createOrderCommand, eventMsg.RequestId); - result = await _mediator.Send(requestCreateOrder); - } - - _logger.CreateLogger(nameof(UserCheckoutAcceptedIntegrationEventHandler)) - .LogTrace(result ? $"UserCheckoutAccepted integration event has been received and a create new order process is started with requestId: {eventMsg.RequestId}" : - $"UserCheckoutAccepted integration event has been received but a new order process has failed with requestId: {eventMsg.RequestId}"); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + var result = false; + + if (@event.RequestId != Guid.Empty) + { + using (LogContext.PushProperty("IdentifiedCommandId", @event.RequestId)) + { + var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street, + @event.State, @event.Country, @event.ZipCode, + @event.CardNumber, @event.CardHolderName, @event.CardExpiration, + @event.CardSecurityNumber, @event.CardTypeId); + + var requestCreateOrder = new IdentifiedCommand(createOrderCommand, @event.RequestId); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + requestCreateOrder.GetGenericTypeName(), + nameof(requestCreateOrder.Id), + requestCreateOrder.Id, + requestCreateOrder); + + result = await _mediator.Send(requestCreateOrder); + + if (result) + { + _logger.LogInformation("----- CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId); + } + else + { + _logger.LogWarning("CreateOrderCommand failed - RequestId: {RequestId}", @event.RequestId); + } + } + } + else + { + _logger.LogWarning("Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}", @event); + } + } } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index 9c1bd4e1b..9d85e2dd4 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -5,7 +5,9 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; +using Microsoft.eShopOnContainers.Services.Ordering.API; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; +using Microsoft.Extensions.Logging; using System; using System.Data.Common; using System.Diagnostics; @@ -21,39 +23,49 @@ namespace Ordering.API.Application.IntegrationEvents private readonly OrderingContext _orderingContext; private readonly IntegrationEventLogContext _eventLogContext; private readonly IIntegrationEventLogService _eventLogService; + private readonly ILogger _logger; - public OrderingIntegrationEventService(IEventBus eventBus, + public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext, IntegrationEventLogContext eventLogContext, - Func integrationEventLogServiceFactory) + Func integrationEventLogServiceFactory, + ILogger logger) { _orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); _eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext)); _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); _eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection()); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task PublishEventsThroughEventBusAsync() { var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync(); + foreach (var logEvt in pendindLogEvents) { + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppName, logEvt.IntegrationEvent); + try { await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId); _eventBus.Publish(logEvt.IntegrationEvent); await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId); } - catch (Exception) + catch (Exception ex) { + _logger.LogError(ex, "ERROR publishing integration event: {IntegrationEventId} from {AppName}", logEvt.EventId, Program.AppName); + await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId); - } + } } } public async Task AddAndSaveEventAsync(IntegrationEvent evt) { + _logger.LogInformation("----- Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt); + await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction()); } } diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index e2c711b69..4a4cd8352 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; +using Microsoft.Extensions.Logging; +using Ordering.API.Application.Behaviors; using Ordering.API.Application.Commands; using System; using System.Collections.Generic; @@ -20,12 +22,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers private readonly IMediator _mediator; private readonly IOrderQueries _orderQueries; private readonly IIdentityService _identityService; + private readonly ILogger _logger; - public OrdersController(IMediator mediator, IOrderQueries orderQueries, IIdentityService identityService) + public OrdersController( + IMediator mediator, + IOrderQueries orderQueries, + IIdentityService identityService, + ILogger logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); _orderQueries = orderQueries ?? throw new ArgumentNullException(nameof(orderQueries)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } [Route("cancel")] @@ -39,6 +47,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) { var requestCancelOrder = new IdentifiedCommand(command, guid); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + requestCancelOrder.GetGenericTypeName(), + nameof(requestCancelOrder.Command.OrderNumber), + requestCancelOrder.Command.OrderNumber, + requestCancelOrder); + commandResult = await _mediator.Send(requestCancelOrder); } @@ -61,6 +77,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) { var requestShipOrder = new IdentifiedCommand(command, guid); + + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + requestShipOrder.GetGenericTypeName(), + nameof(requestShipOrder.Command.OrderNumber), + requestShipOrder.Command.OrderNumber, + requestShipOrder); + commandResult = await _mediator.Send(requestShipOrder); } @@ -114,6 +138,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers [HttpPost] public async Task> CreateOrderDraftFromBasketDataAsync([FromBody] CreateOrderDraftCommand createOrderDraftCommand) { + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + createOrderDraftCommand.GetGenericTypeName(), + nameof(createOrderDraftCommand.BuyerId), + createOrderDraftCommand.BuyerId, + createOrderDraftCommand); + return await _mediator.Send(createOrderDraftCommand); } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index 99a413f9f..67275a587 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -7,7 +7,6 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Ordering.API.Application.Behaviors; using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; using Ordering.API.Application.Validations; -using Ordering.API.Infrastructure.Behaviors; namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.AutofacModules { diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs b/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs index 5c82b3952..1e95df7dc 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs @@ -74,7 +74,7 @@ } catch (Exception ex) { - log.LogError(ex.Message); + log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPredefinedCardTypes(); } @@ -82,7 +82,7 @@ return File.ReadAllLines(csvFileCardTypes) .Skip(1) // skip header column .SelectTry(x => CreateCardType(x, ref id)) - .OnCaughtException(ex => { log.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -118,7 +118,7 @@ } catch (Exception ex) { - log.LogError(ex.Message); + log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPredefinedOrderStatus(); } @@ -126,7 +126,7 @@ return File.ReadAllLines(csvFileOrderStatus) .Skip(1) // skip header row .SelectTry(x => CreateOrderStatus(x, ref id)) - .OnCaughtException(ex => { log.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -182,7 +182,7 @@ sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}"); + logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); } ); } diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index ff8ae6607..b3fa6797e 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -5,6 +5,7 @@ aspnet-Ordering.API-20161122013547 $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; ..\..\..\..\docker-compose.dcproj + latest @@ -27,30 +28,33 @@ - - + + + + + - + - - + + + + - - diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index 8db59598c..8fc18c8bb 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -15,10 +15,22 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args) - .MigrateDbContext((context, services) => + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + host.MigrateDbContext((context, services) => { var env = services.GetService(); var settings = services.GetService>(); @@ -28,47 +40,66 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API .SeedAsync(context, env, settings, logger) .Wait(); }) - .MigrateDbContext((_,__)=>{}) - .Run(); + .MigrateDbContext((_, __) => { }); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => + WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) .UseStartup() + .UseApplicationInsights() .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); + .UseConfiguration(configuration) + .UseSerilog() + .Build(); - var configurationBuilder = new ConfigurationBuilder(); - - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - configurationBuilder.AddEnvironmentVariables(); + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) - .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } -} +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 03e9b1d7f..cd34f98dd 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -74,14 +74,15 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); - } + } app.UseCors("CorsPolicy"); @@ -121,10 +122,9 @@ eventBus.Subscribe>(); eventBus.Subscribe>(); eventBus.Subscribe>(); - eventBus.Subscribe>(); + eventBus.Subscribe>(); } - protected virtual void ConfigureAuth(IApplicationBuilder app) { if (Configuration.GetValue("UseLoadTest")) @@ -167,7 +167,7 @@ }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddControllersAsServices(); //Injecting Controllers themselves thru DI - //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services + //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services services.AddCors(options => { diff --git a/src/Services/Ordering/Ordering.API/appsettings.json b/src/Services/Ordering/Ordering.API/appsettings.json index 96dd74630..64b24a354 100644 --- a/src/Services/Ordering/Ordering.API/appsettings.json +++ b/src/Services/Ordering/Ordering.API/appsettings.json @@ -2,12 +2,15 @@ "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;", "IdentityUrl": "http://localhost:5105", "UseCustomizationData": false, - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "AzureServiceBusEnabled": false, diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj index bd31074e7..f0fc00b04 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj @@ -24,7 +24,10 @@ + + + diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs index 3c78efa10..8f8cf41e1 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs @@ -1,33 +1,75 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog; +using System; +using System.IO; namespace Ordering.BackgroundTasks { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace; + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseStartup() - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddDebug(); - builder.AddConsole(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .CaptureStartupErrors(false) + .UseStartup() + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + return builder.Build(); + } } } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs index fbf92f978..328fb95c4 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs @@ -20,9 +20,10 @@ namespace Ordering.BackgroundTasks.Tasks private readonly BackgroundTaskSettings _settings; private readonly IEventBus _eventBus; - public GracePeriodManagerService(IOptions settings, - IEventBus eventBus, - ILogger logger) + public GracePeriodManagerService( + IOptions settings, + IEventBus eventBus, + ILogger logger) { _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); @@ -32,27 +33,27 @@ namespace Ordering.BackgroundTasks.Tasks protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - _logger.LogDebug($"GracePeriodManagerService is starting."); + _logger.LogDebug("GracePeriodManagerService is starting."); - stoppingToken.Register(() => _logger.LogDebug($"#1 GracePeriodManagerService background task is stopping.")); + stoppingToken.Register(() => _logger.LogDebug("#1 GracePeriodManagerService background task is stopping.")); while (!stoppingToken.IsCancellationRequested) { - _logger.LogDebug($"GracePeriodManagerService background task is doing background work."); + _logger.LogDebug("GracePeriodManagerService background task is doing background work."); CheckConfirmedGracePeriodOrders(); await Task.Delay(_settings.CheckUpdateTime, stoppingToken); } - _logger.LogDebug($"GracePeriodManagerService background task is stopping."); + _logger.LogDebug("GracePeriodManagerService background task is stopping."); await Task.CompletedTask; } private void CheckConfirmedGracePeriodOrders() { - _logger.LogDebug($"Checking confirmed grace period orders"); + _logger.LogDebug("Checking confirmed grace period orders"); var orderIds = GetConfirmedGracePeriodOrders(); @@ -60,6 +61,8 @@ namespace Ordering.BackgroundTasks.Tasks { var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId); + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", confirmGracePeriodEvent.Id, Program.AppName, confirmGracePeriodEvent); + _eventBus.Publish(confirmGracePeriodEvent); } } @@ -81,7 +84,7 @@ namespace Ordering.BackgroundTasks.Tasks } catch (SqlException exception) { - _logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}"); + _logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message); } } diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json index fd26645ab..3697c53a5 100644 --- a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json +++ b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json @@ -1,15 +1,13 @@ { "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;", - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Debug" - } - }, - "Console": { - "LogLevel": { - "Default": "Debug" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" } } }, diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index ac9ec608f..18e72fe29 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -27,10 +27,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure private readonly IMediator _mediator; private IDbContextTransaction _currentTransaction; - private OrderingContext(DbContextOptions options) : base (options) { } + private OrderingContext(DbContextOptions options) : base(options) { } public IDbContextTransaction GetCurrentTransaction => _currentTransaction; + public bool HasActiveTransaction => _currentTransaction != null; + public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); @@ -47,7 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new CardTypeEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration()); - modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration()); + modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration()); } public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) @@ -67,17 +69,24 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure return true; } - public async Task BeginTransactionAsync() + public async Task BeginTransactionAsync() { - _currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); + if (_currentTransaction != null) return null; + + _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); + + return _currentTransaction; } - public async Task CommitTransactionAsync() + public async Task CommitTransactionAsync(IDbContextTransaction transaction) { + if (transaction == null) throw new ArgumentNullException(nameof(transaction)); + if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current"); + try { await SaveChangesAsync(); - _currentTransaction?.Commit(); + transaction.Commit(); } catch { @@ -118,7 +127,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure var optionsBuilder = new DbContextOptionsBuilder() .UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;Integrated Security=true"); - return new OrderingContext(optionsBuilder.Options,new NoMediator()); + return new OrderingContext(optionsBuilder.Options, new NoMediator()); } class NoMediator : IMediator diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs index 171eb81f7..6257bb237 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToCancelledIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; using Ordering.SignalrHub.IntegrationEvents.Events; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -11,18 +13,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling public class OrderStatusChangedToCancelledIntegrationEventHandler : IIntegrationEventHandler { private readonly IHubContext _hubContext; + private readonly ILogger _logger; - public OrderStatusChangedToCancelledIntegrationEventHandler(IHubContext hubContext) + public OrderStatusChangedToCancelledIntegrationEventHandler( + IHubContext hubContext, + ILogger logger) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStatusChangedToCancelledIntegrationEvent @event) { - await _hubContext.Clients - .Group(@event.BuyerName) - .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _hubContext.Clients + .Group(@event.BuyerName) + .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + } } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs index 4b0eb780c..836e02d3c 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; using Ordering.SignalrHub.IntegrationEvents.Events; +using Serilog.Context; using System; using System.Threading.Tasks; @@ -9,18 +11,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler { private readonly IHubContext _hubContext; + private readonly ILogger _logger; - public OrderStatusChangedToPaidIntegrationEventHandler(IHubContext hubContext) + public OrderStatusChangedToPaidIntegrationEventHandler( + IHubContext hubContext, + ILogger logger) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event) { - await _hubContext.Clients - .Group(@event.BuyerName) - .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _hubContext.Clients + .Group(@event.BuyerName) + .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + } } } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs index 7a19a0659..5b08d08b4 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToShippedIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; using Ordering.SignalrHub.IntegrationEvents.Events; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -11,18 +13,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler { private readonly IHubContext _hubContext; + private readonly ILogger _logger; - public OrderStatusChangedToShippedIntegrationEventHandler(IHubContext hubContext) + public OrderStatusChangedToShippedIntegrationEventHandler( + IHubContext hubContext, + ILogger logger) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event) { - await _hubContext.Clients - .Group(@event.BuyerName) - .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _hubContext.Clients + .Group(@event.BuyerName) + .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + } } } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs index 324ea6259..348627716 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; using Ordering.SignalrHub.IntegrationEvents.Events; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -12,18 +14,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling IIntegrationEventHandler { private readonly IHubContext _hubContext; + private readonly ILogger _logger; - public OrderStatusChangedToStockConfirmedIntegrationEventHandler(IHubContext hubContext) + public OrderStatusChangedToStockConfirmedIntegrationEventHandler( + IHubContext hubContext, + ILogger logger) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStatusChangedToStockConfirmedIntegrationEvent @event) { - await _hubContext.Clients - .Group(@event.BuyerName) - .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _hubContext.Clients + .Group(@event.BuyerName) + .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + } } } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs index 5fac4c1da..422bc4a7a 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; using Ordering.SignalrHub.IntegrationEvents.Events; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -12,18 +14,27 @@ namespace Ordering.SignalrHub.IntegrationEvents.EventHandling IIntegrationEventHandler { private readonly IHubContext _hubContext; + private readonly ILogger _logger; - public OrderStatusChangedToSubmittedIntegrationEventHandler(IHubContext hubContext) + public OrderStatusChangedToSubmittedIntegrationEventHandler( + IHubContext hubContext, + ILogger logger) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStatusChangedToSubmittedIntegrationEvent @event) { - await _hubContext.Clients - .Group(@event.BuyerName) - .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _hubContext.Clients + .Group(@event.BuyerName) + .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + } } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/orderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/orderStatusChangedToAwaitingValidationIntegrationEventHandler.cs index 6c2733b77..0e2665232 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/orderStatusChangedToAwaitingValidationIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/IntegrationEvents/EventHandling/orderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -10,18 +12,27 @@ namespace Ordering.SignalrHub.IntegrationEvents public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : IIntegrationEventHandler { private readonly IHubContext _hubContext; + private readonly ILogger _logger; - public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(IHubContext hubContext) + public OrderStatusChangedToAwaitingValidationIntegrationEventHandler( + IHubContext hubContext, + ILogger logger) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) { - await _hubContext.Clients - .Group(@event.BuyerName) - .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _hubContext.Clients + .Group(@event.BuyerName) + .SendAsync("UpdatedOrderState", new { OrderId = @event.OrderId, Status = @event.OrderStatus }); + } } } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj b/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj index bca77a619..a4dd8cc9d 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj +++ b/src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj @@ -11,23 +11,25 @@ - + - + + + + + - - - - - + + + diff --git a/src/Services/Ordering/Ordering.SignalrHub/Program.cs b/src/Services/Ordering/Ordering.SignalrHub/Program.cs index 3fe2e8c24..fdb3f5f3a 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Program.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/Program.cs @@ -13,28 +13,67 @@ namespace Ordering.SignalrHub { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace; + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) .UseStartup() - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + return builder.Build(); + } + } } diff --git a/src/Services/Ordering/Ordering.SignalrHub/Startup.cs b/src/Services/Ordering/Ordering.SignalrHub/Startup.cs index 20f8b739b..e8bb7ecd1 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/Startup.cs +++ b/src/Services/Ordering/Ordering.SignalrHub/Startup.cs @@ -30,6 +30,8 @@ namespace Ordering.SignalrHub Configuration = configuration; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public IServiceProvider ConfigureServices(IServiceCollection services) @@ -115,17 +117,18 @@ namespace Ordering.SignalrHub return new AutofacServiceProvider(container.Build()); } - public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddConsole(Configuration.GetSection("Logging")); + //loggerFactory.AddDebug(); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } @@ -156,13 +159,13 @@ namespace Ordering.SignalrHub private void ConfigureEventBus(IApplicationBuilder app) { var eventBus = app.ApplicationServices.GetRequiredService(); - + eventBus.Subscribe(); eventBus.Subscribe(); eventBus.Subscribe(); eventBus.Subscribe(); eventBus.Subscribe(); - eventBus.Subscribe(); + eventBus.Subscribe(); } private void ConfigureAuthService(IServiceCollection services) diff --git a/src/Services/Ordering/Ordering.SignalrHub/appsettings.json b/src/Services/Ordering/Ordering.SignalrHub/appsettings.json index ab02fda0f..bd752de48 100644 --- a/src/Services/Ordering/Ordering.SignalrHub/appsettings.json +++ b/src/Services/Ordering/Ordering.SignalrHub/appsettings.json @@ -1,11 +1,14 @@ { "IdentityUrl": "http://localhost:5105", - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "AzureServiceBusEnabled": false, diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs index e0f861017..95bc4cc81 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/IdentifiedCommandHandlerTest.cs @@ -8,6 +8,7 @@ namespace UnitTest.Ordering.Application using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; + using Microsoft.Extensions.Logging; using Moq; using System.Collections; using System.Collections.Generic; @@ -17,11 +18,13 @@ namespace UnitTest.Ordering.Application { private readonly Mock _requestManager; private readonly Mock _mediator; + private readonly Mock>> _loggerMock; public IdentifiedCommandHandlerTest() { _requestManager = new Mock(); _mediator = new Mock(); + _loggerMock = new Mock>>(); } [Fact] @@ -38,7 +41,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(true)); //Act - var handler = new IdentifiedCommandHandler(_mediator.Object, _requestManager.Object); + var handler = new IdentifiedCommandHandler(_mediator.Object, _requestManager.Object, _loggerMock.Object); var cltToken = new System.Threading.CancellationToken(); var result = await handler.Handle(fakeOrderCmd, cltToken); @@ -61,7 +64,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(true)); //Act - var handler = new IdentifiedCommandHandler(_mediator.Object, _requestManager.Object); + var handler = new IdentifiedCommandHandler(_mediator.Object, _requestManager.Object, _loggerMock.Object); var cltToken = new System.Threading.CancellationToken(); var result = await handler.Handle(fakeOrderCmd, cltToken); diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs index 80a0deb25..77fede857 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs @@ -13,6 +13,7 @@ namespace UnitTest.Ordering.Application using global::Ordering.API.Application.IntegrationEvents; using global::Ordering.API.Application.Models; using MediatR; + using Microsoft.Extensions.Logging; using System.Collections; using System.Collections.Generic; using Xunit; @@ -50,8 +51,9 @@ namespace UnitTest.Ordering.Application _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); + var LoggerMock = new Mock>(); //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object, LoggerMock.Object); var cltToken = new System.Threading.CancellationToken(); var result = await handler.Handle(fakeOrderCmd, cltToken); diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs index c5b8a1764..b19a764e9 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/OrdersWebApiTest.cs @@ -4,6 +4,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; +using Microsoft.Extensions.Logging; using Moq; using Ordering.API.Application.Commands; using System; @@ -19,12 +20,14 @@ namespace UnitTest.Ordering.Application private readonly Mock _mediatorMock; private readonly Mock _orderQueriesMock; private readonly Mock _identityServiceMock; + private readonly Mock> _loggerMock; public OrdersWebApiTest() { _mediatorMock = new Mock(); _orderQueriesMock = new Mock(); _identityServiceMock = new Mock(); + _loggerMock = new Mock>(); } [Fact] @@ -35,7 +38,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(true)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; //Assert @@ -51,7 +54,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(true)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), String.Empty) as BadRequestResult; //Assert @@ -66,7 +69,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(true)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult; //Assert @@ -82,7 +85,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(true)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), String.Empty) as BadRequestResult; //Assert @@ -102,7 +105,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(fakeDynamicResult)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.GetOrdersAsync(); //Assert @@ -119,7 +122,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(fakeDynamicResult)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.GetOrderAsync(fakeOrderId) as OkObjectResult; //Assert @@ -135,7 +138,7 @@ namespace UnitTest.Ordering.Application .Returns(Task.FromResult(fakeDynamicResult)); //Act - var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object); + var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object); var actionResult = await orderController.GetCardTypesAsync(); //Assert diff --git a/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs b/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs index c70a32093..b26d64d40 100644 --- a/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Payment/Payment.API/IntegrationEvents/EventHandling/OrderStatusChangedToStockConfirmedIntegrationEventHandler.cs @@ -2,45 +2,58 @@ { using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Payment.API.IntegrationEvents.Events; + using Serilog.Context; using System.Threading.Tasks; - public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : + public class OrderStatusChangedToStockConfirmedIntegrationEventHandler : IIntegrationEventHandler { private readonly IEventBus _eventBus; private readonly PaymentSettings _settings; + private readonly ILogger _logger; - public OrderStatusChangedToStockConfirmedIntegrationEventHandler(IEventBus eventBus, - IOptionsSnapshot settings) + public OrderStatusChangedToStockConfirmedIntegrationEventHandler( + IEventBus eventBus, + IOptionsSnapshot settings, + ILogger logger) { _eventBus = eventBus; _settings = settings.Value; - } + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + } public async Task Handle(OrderStatusChangedToStockConfirmedIntegrationEvent @event) { - IntegrationEvent orderPaymentIntegrationEvent; + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - //Business feature comment: - // When OrderStatusChangedToStockConfirmed Integration Event is handled. - // Here we're simulating that we'd be performing the payment against any payment gateway - // Instead of a real payment we just take the env. var to simulate the payment - // The payment can be successful or it can fail + IntegrationEvent orderPaymentIntegrationEvent; - if (_settings.PaymentSucceded) - { - orderPaymentIntegrationEvent = new OrderPaymentSuccededIntegrationEvent(@event.OrderId); - } - else - { - orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId); - } + //Business feature comment: + // When OrderStatusChangedToStockConfirmed Integration Event is handled. + // Here we're simulating that we'd be performing the payment against any payment gateway + // Instead of a real payment we just take the env. var to simulate the payment + // The payment can be successful or it can fail + + if (_settings.PaymentSucceded) + { + orderPaymentIntegrationEvent = new OrderPaymentSuccededIntegrationEvent(@event.OrderId); + } + else + { + orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId); + } - _eventBus.Publish(orderPaymentIntegrationEvent); + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", orderPaymentIntegrationEvent.Id, Program.AppName, orderPaymentIntegrationEvent); - await Task.CompletedTask; + _eventBus.Publish(orderPaymentIntegrationEvent); + + await Task.CompletedTask; + } } } } \ No newline at end of file diff --git a/src/Services/Payment/Payment.API/Payment.API.csproj b/src/Services/Payment/Payment.API/Payment.API.csproj index 6a00a1699..e912727e8 100644 --- a/src/Services/Payment/Payment.API/Payment.API.csproj +++ b/src/Services/Payment/Payment.API/Payment.API.csproj @@ -7,20 +7,23 @@ - + + - - + + + + diff --git a/src/Services/Payment/Payment.API/Program.cs b/src/Services/Payment/Payment.API/Program.cs index 7a589e8a4..9b0aded5c 100644 --- a/src/Services/Payment/Payment.API/Program.cs +++ b/src/Services/Payment/Payment.API/Program.cs @@ -4,40 +4,85 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog; +using System; using System.IO; namespace Payment.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace; + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseContentRoot(Directory.GetCurrentDirectory()) + .CaptureStartupErrors(false) .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } -} +} \ No newline at end of file diff --git a/src/Services/Payment/Payment.API/Startup.cs b/src/Services/Payment/Payment.API/Startup.cs index 62a3b2321..4d5010868 100644 --- a/src/Services/Payment/Payment.API/Startup.cs +++ b/src/Services/Payment/Payment.API/Startup.cs @@ -91,7 +91,8 @@ namespace Payment.API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) diff --git a/src/Services/Payment/Payment.API/appsettings.json b/src/Services/Payment/Payment.API/appsettings.json index acc9a2be0..bf6157648 100644 --- a/src/Services/Payment/Payment.API/appsettings.json +++ b/src/Services/Payment/Payment.API/appsettings.json @@ -1,8 +1,13 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "PaymentSucceded": true, diff --git a/src/Web/WebMVC/Infrastructure/WebContextSeed.cs b/src/Web/WebMVC/Infrastructure/WebContextSeed.cs index 65063e34f..c34730ac1 100644 --- a/src/Web/WebMVC/Infrastructure/WebContextSeed.cs +++ b/src/Web/WebMVC/Infrastructure/WebContextSeed.cs @@ -15,7 +15,7 @@ namespace WebMVC.Infrastructure { public static void Seed(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory) { - var log = loggerFactory.CreateLogger("WebMVC seed"); + var log = loggerFactory.CreateLogger(); var settings = (AppSettings)applicationBuilder .ApplicationServices.GetRequiredService>().Value; @@ -39,7 +39,7 @@ namespace WebMVC.Infrastructure string overrideCssFile = Path.Combine(contentRootPath, "Setup", "override.css"); if (!File.Exists(overrideCssFile)) { - log.LogError($" override css file '{overrideCssFile}' does not exists."); + log.LogError("Override css file '{FileName}' does not exists.", overrideCssFile); return; } @@ -48,7 +48,7 @@ namespace WebMVC.Infrastructure } catch (Exception ex) { - log.LogError($"Exception in method GetPreconfiguredCSS WebMVC. Exception Message={ex.Message}"); + log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); } } @@ -59,7 +59,7 @@ namespace WebMVC.Infrastructure string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); if (!File.Exists(imagesZipFile)) { - log.LogError($" zip file '{imagesZipFile}' does not exists."); + log.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); return; } @@ -81,14 +81,14 @@ namespace WebMVC.Infrastructure } else { - log.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'"); + log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); } } } } catch ( Exception ex ) { - log.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}"); + log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); } } diff --git a/src/Web/WebMVC/Program.cs b/src/Web/WebMVC/Program.cs index 5000e0422..c329237ce 100644 --- a/src/Web/WebMVC/Program.cs +++ b/src/Web/WebMVC/Program.cs @@ -3,40 +3,74 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog; +using System; using System.IO; namespace Microsoft.eShopOnContainers.WebMVC { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseContentRoot(Directory.GetCurrentDirectory()) + .CaptureStartupErrors(false) .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + return builder.Build(); + } } -} +} \ No newline at end of file diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index 0ec33bc8c..6156bdf09 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -52,7 +52,8 @@ namespace Microsoft.eShopOnContainers.WebMVC { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); app.UseHealthChecks("/hc", new HealthCheckOptions() { @@ -73,7 +74,7 @@ namespace Microsoft.eShopOnContainers.WebMVC var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{PathBase}'", pathBase); app.UsePathBase(pathBase); } @@ -92,8 +93,6 @@ namespace Microsoft.eShopOnContainers.WebMVC app.UseAuthentication(); - var log = loggerFactory.CreateLogger("identity"); - WebContextSeed.Seed(app, env, loggerFactory); app.UseHttpsRedirection(); diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index a019498ae..20ddc085b 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -37,7 +37,10 @@ + + + diff --git a/src/Web/WebMVC/appsettings.json b/src/Web/WebMVC/appsettings.json index 161e247a6..591964725 100644 --- a/src/Web/WebMVC/appsettings.json +++ b/src/Web/WebMVC/appsettings.json @@ -11,12 +11,15 @@ "UseLoadTest": false, "ActivateCampaignDetailFunction": "False", "UseCustomizationData": false, - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "ApplicationInsights": { diff --git a/src/Web/WebMonolithic/eShopWeb/Infrastructure/CatalogContextSeed.cs b/src/Web/WebMonolithic/eShopWeb/Infrastructure/CatalogContextSeed.cs index c67baebc3..e95fdd8f4 100644 --- a/src/Web/WebMonolithic/eShopWeb/Infrastructure/CatalogContextSeed.cs +++ b/src/Web/WebMonolithic/eShopWeb/Infrastructure/CatalogContextSeed.cs @@ -51,7 +51,7 @@ { retryForAvaiability++; var log = loggerFactory.CreateLogger("catalog seed"); - log.LogError(ex.Message); + log.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); await SeedAsync(applicationBuilder, loggerFactory, retryForAvaiability); } } diff --git a/src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs b/src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs index a2fde45b3..74de66b5c 100644 --- a/src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs +++ b/src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs @@ -15,7 +15,7 @@ namespace WebSPA.Infrastructure { public static void Seed(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory) { - var log = loggerFactory.CreateLogger("WebSPA seed"); + var log = loggerFactory.CreateLogger(); var settings = (AppSettings)applicationBuilder .ApplicationServices.GetRequiredService>().Value; @@ -37,7 +37,7 @@ namespace WebSPA.Infrastructure string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); if (!File.Exists(imagesZipFile)) { - log.LogError($" zip file '{imagesZipFile}' does not exists."); + log.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); return; } @@ -59,14 +59,14 @@ namespace WebSPA.Infrastructure } else { - log.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'"); + log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); } } } } catch (Exception ex) { - log.LogError($"Exception in method GetPreconfiguredImages WebSPA. Exception Message={ex.Message}"); + log.LogError(ex, "ERROR in GetPreconfiguredImages: {Message}", ex.Message); } } } diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs index 5cfeb98fc..7a418c432 100644 --- a/src/Web/WebSPA/Startup.cs +++ b/src/Web/WebSPA/Startup.cs @@ -116,7 +116,7 @@ namespace eShopConContainers.WebSPA var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } diff --git a/src/Web/WebStatus/Program.cs b/src/Web/WebStatus/Program.cs index 11a4f02b9..d2de3ceae 100644 --- a/src/Web/WebStatus/Program.cs +++ b/src/Web/WebStatus/Program.cs @@ -3,36 +3,85 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog; +using System; using System.IO; namespace WebStatus { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace; + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - builder.AddAzureWebAppDiagnostics(); - }) + .CaptureStartupErrors(false) + .UseStartup() .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } -} +} \ No newline at end of file diff --git a/src/Web/WebStatus/Startup.cs b/src/Web/WebStatus/Startup.cs index fbfa5e74c..5b35ee84f 100644 --- a/src/Web/WebStatus/Startup.cs +++ b/src/Web/WebStatus/Startup.cs @@ -39,7 +39,8 @@ namespace WebStatus // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); if (env.IsDevelopment()) { diff --git a/src/Web/WebStatus/WebStatus.csproj b/src/Web/WebStatus/WebStatus.csproj index f2b912dbb..e898a3a2e 100644 --- a/src/Web/WebStatus/WebStatus.csproj +++ b/src/Web/WebStatus/WebStatus.csproj @@ -18,13 +18,16 @@ + - - + + + + diff --git a/src/Web/WebStatus/appsettings.json b/src/Web/WebStatus/appsettings.json index 6f486424c..e2aadbc9e 100644 --- a/src/Web/WebStatus/appsettings.json +++ b/src/Web/WebStatus/appsettings.json @@ -70,6 +70,16 @@ "Uri": "http://localhost:5121/hc" } ], + "Serilog": { + "SeqServerUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } + } "Webhooks": [ { "Name": "",