Skip to content

Commit

Permalink
Merge pull request #118 from infoshareacademy/feature/ja-123-logging-…
Browse files Browse the repository at this point in the history
…in-bl-services

Feature/JA-123 Logging in `BusinessLogic` services and `DbRepository`
  • Loading branch information
skrawus authored Jul 7, 2024
2 parents adcff6b + 4345bec commit a86d800
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 107 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoFixture;
using Microsoft.Extensions.Logging;
using Moq;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;
using TutorLizard.BusinessLogic.Models;
Expand All @@ -12,10 +13,13 @@ public abstract class BrowseServiceTestsBase : TestsWithInMemoryDbBase
protected Fixture Fixture = new();
protected Mock<IDbRepository<Ad>> MockAdRepository = new();
protected Mock<IDbRepository<ScheduleItem>> MockScheduleItemRepository = new();
protected Mock<ILogger<BrowseService>> MockLogger = new();

protected BrowseServiceTestsBase() : base()
{
BrowseService = new(MockAdRepository.Object, MockScheduleItemRepository.Object);
BrowseService = new(MockAdRepository.Object,
MockScheduleItemRepository.Object,
MockLogger.Object);
}

protected void SetupMockGetAllAds(List<Ad> ads)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoFixture;
using Microsoft.Extensions.Logging;
using Moq;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;
using TutorLizard.BusinessLogic.Models;
Expand All @@ -14,14 +15,15 @@ public class StudentServiceTestBase : TestsWithInMemoryDbBase
protected Mock<IDbRepository<AdRequest>> MockAdRequestRepository = new();
protected Mock<IDbRepository<ScheduleItem>> MockScheduleItemRepository = new();
protected Mock<IDbRepository<ScheduleItemRequest>> MockScheduleItemRequestRepository = new();

protected Mock<ILogger<StudentService>> MockLogger = new();

protected StudentServiceTestBase() : base()
{
StudentService = new StudentService(MockAdRepository.Object,
MockAdRequestRepository.Object,
MockScheduleItemRepository.Object,
MockScheduleItemRequestRepository.Object);
MockAdRequestRepository.Object,
MockScheduleItemRepository.Object,
MockScheduleItemRequestRepository.Object,
MockLogger.Object);
}

protected void SetupMockGetScheduleItemById(ScheduleItem? scheduleItem)
Expand All @@ -38,18 +40,5 @@ protected void SetupMockGetAllScheduleItems(List<ScheduleItem> scheduleItems)
.Setup(x => x.GetAll())
.Returns(scheduleItemsInDb);
}

protected IQueryable<TEntity> AddEntitiesToInMemoryDb<TEntity>(List<TEntity> entities)
where TEntity : class
{
DbContext
.Set<TEntity>()
.AddRange(entities);
DbContext.SaveChanges();

return DbContext
.Set<TEntity>()
.AsQueryable();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Extensions;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;

namespace TutorLizard.BusinessLogic.Data.Repositories.DataBase;
Expand All @@ -7,10 +9,13 @@ public class DbRepository<TEntity, UDbContext> : IDbRepository<TEntity>
where UDbContext : DbContext
{
private readonly UDbContext _dbContext;
private readonly ILogger<DbRepository<TEntity, UDbContext>> _logger;

public DbRepository(UDbContext dbContext)
public DbRepository(UDbContext dbContext,
ILogger<DbRepository<TEntity, UDbContext>> logger)
{
_dbContext = dbContext;
_logger = logger;
}

public IQueryable<TEntity> GetAll()
Expand All @@ -21,8 +26,20 @@ public IQueryable<TEntity> GetAll()

public async Task<TEntity?> GetById<VId>(VId id)
{
return await _dbContext.Set<TEntity>()
TEntity? entity = await _dbContext
.Set<TEntity>()
.FindAsync(id);

if (entity is null)
{
_logger.LogEntityNotFound(nameof(GetById), id);
}
else
{
_logger.LogEntityFound(nameof(GetById), id);
}

return entity;
}

public async Task<TEntity> Create(TEntity entity)
Expand All @@ -31,31 +48,55 @@ public async Task<TEntity> Create(TEntity entity)
.Set<TEntity>()
.Add(entity);
await _dbContext.SaveChangesAsync();

_logger.LogEntityCreated(nameof(Create), GetEntityId(entity));

return entity;
}

public async Task<TEntity?> Update<VId>(VId id, Action<TEntity> updateAction)
{
TEntity? toUpdate = await GetById(id);
if (toUpdate is null)
{
_logger.LogEntityNotUpdated(nameof(Update), id);
return null;
}

updateAction.Invoke(toUpdate);
await _dbContext.SaveChangesAsync();

_logger.LogEntityUpdated(nameof(Update), id);

return toUpdate;
}

public async Task<TEntity?> Delete<VId>(VId id)
{
TEntity? toDelete = await GetById(id);
if (toDelete is null)
{
_logger.LogEntityNotDeleted(nameof(Delete), id);
return null;
}

_dbContext.Set<TEntity>()
.Remove(toDelete);
await _dbContext.SaveChangesAsync();

_logger.LogEntityDeleted(nameof(Delete), id);

return toDelete;
}

private object? GetEntityId(TEntity entity)
{
var entry = _dbContext.Entry(entity);
var id = entry.Metadata.FindPrimaryKey()?
.Properties
.Select(p => entry.Property(p.Name).CurrentValue)
.FirstOrDefault();

return id;
}
}
3 changes: 1 addition & 2 deletions TutorLizard.BusinessLogic/Extensions/DtoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ public static UserDto ToDto(this User user)
user.Name,
user.UserType,
user.Email,
user.DateCreated,
user.GoogleId);
user.DateCreated);
}
71 changes: 71 additions & 0 deletions TutorLizard.BusinessLogic/Extensions/LoggingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;

namespace TutorLizard.BusinessLogic.Extensions;
internal static class LoggingExtensions
{
internal static IDisposable? BeginMethodCallScope<TService, TRequest>(this ILogger<TService> logger, string methodName, TRequest request, bool destructureRequest = true)
{
string scopeMessageFormat = $"Service: {{Service}} Method: {{ServiceMethod}} Request: {(destructureRequest ? "{@Request}" : "{Request}")}";
var scope = logger.BeginScope(scopeMessageFormat, typeof(TService).Name, methodName, request);

string logMessageFormat = $"[Service method call] Service: {{Service}} Method: {{ServiceMethod}} Request: {(destructureRequest ? "{@Request}" : "{Request}")}";
logger.LogInformation(logMessageFormat, typeof(TService).Name, methodName, request);

return scope;
}

internal static void LogReturningResponse<TService, TResponse>(this ILogger<TService> logger, TResponse response, bool destructureResponse = true)
{
string logMessageFormat = $"[Returning response] Response: {(destructureResponse ? "{@Response}" : "{Response}")}";
logger.LogInformation(logMessageFormat, response);
}

internal static void LogEntityCreated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} created successfully with Id: {EntityId}", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotCreated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} not created", methodName, typeof(TEntity).Name);
}

internal static void LogEntityUpdated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} updated successfully", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotUpdated<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not updated", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityDeleted<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} deleted successfully", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotDeleted<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not deleted", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityFound<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} found", methodName, typeof(TEntity).Name, id);
}

internal static void LogEntityNotFound<TEntity, TId>(this ILogger<IDbRepository<TEntity>> logger, string methodName, TId id)
where TEntity : class
{
logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not found", methodName, typeof(TEntity).Name, id);
}
}
16 changes: 15 additions & 1 deletion TutorLizard.BusinessLogic/Services/BrowseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@
using TutorLizard.Shared.Models.DTOs;
using TutorLizard.Shared.Models.DTOs.Requests;
using TutorLizard.Shared.Models.DTOs.Responses;
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Extensions;

namespace TutorLizard.BusinessLogic.Services;
public class BrowseService : IBrowseService
{
private readonly IDbRepository<Ad> _adRepository;
private readonly IDbRepository<ScheduleItem> _scheduleItemRepository;
private readonly ILogger<BrowseService> _logger;

public BrowseService(IDbRepository<Ad> adRepository,
IDbRepository<ScheduleItem> _scheduleItemRepository)
IDbRepository<ScheduleItem> _scheduleItemRepository,
ILogger<BrowseService> logger)
{
_adRepository = adRepository;
this._scheduleItemRepository = _scheduleItemRepository;
_logger = logger;
}
public async Task<GetBrowseAdsPageResponse> GetBrowseAdsPage(GetBrowseAdsPageRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetBrowseAdsPage), request);

if (request.PageSize < 1 || request.PageNumber < 1)
{
return new()
Expand Down Expand Up @@ -64,11 +71,14 @@ public async Task<GetBrowseAdsPageResponse> GetBrowseAdsPage(GetBrowseAdsPageReq
SearchCriteria = request.SearchCriteria
};

_logger.LogReturningResponse(response);
return response;
}

public async Task<GetAdDetailsResponse?> GetAdDetails(GetAdDetailsRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetAdDetails), request);

GetAdDetailsResponse? response = await _adRepository.GetAll()
.Where(a => a.Id == request.AdId)
.Select(a => new GetAdDetailsResponse
Expand All @@ -92,11 +102,14 @@ public async Task<GetBrowseAdsPageResponse> GetBrowseAdsPage(GetBrowseAdsPageReq
})
.FirstOrDefaultAsync();

_logger.LogReturningResponse(response);
return response;
}

public async Task<GetUsersScheduleResponse> GetUsersSchedule(GetUsersScheduleRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetUsersSchedule), request);

List<TutorsScheduleItemSummaryDto> tutorsSchedule = await _scheduleItemRepository.GetAll()
.Where(i => i.Ad.TutorId == request.UserId &&
i.DateTime.Month == request.Month &&
Expand Down Expand Up @@ -144,6 +157,7 @@ public async Task<GetUsersScheduleResponse> GetUsersSchedule(GetUsersScheduleReq
Year = request.Year,
};

_logger.LogReturningResponse(response);
return response;
}
private IQueryable<Ad> ApplySearchCriteria(IQueryable<Ad> ads, AdSearchCriteriaDto searchCriteria)
Expand Down
13 changes: 11 additions & 2 deletions TutorLizard.BusinessLogic/Services/CategoryService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TutorLizard.BusinessLogic.Extensions;
using TutorLizard.BusinessLogic.Interfaces.Data.Repositories;
using TutorLizard.BusinessLogic.Interfaces.Services;
Expand All @@ -11,23 +12,31 @@ namespace TutorLizard.BusinessLogic.Services;
public class CategoryService : ICategoryService
{
private readonly IDbRepository<Category> _categoryRepository;
private readonly ILogger<CategoryService> _logger;

public CategoryService(IDbRepository<Category> categoryRepository)
public CategoryService(IDbRepository<Category> categoryRepository,
ILogger<CategoryService> logger)
{
_categoryRepository = categoryRepository;
_logger = logger;
}

public async Task<GetCategoriesResponse> GetCategories(GetCategoriesRequest request)
{
using var scope = _logger.BeginMethodCallScope(nameof(GetCategories), request);

List<CategoryDto> categories = await _categoryRepository
.GetAll()
.Select(category => category.ToDto())
.ToListAsync();

return new GetCategoriesResponse()
GetCategoriesResponse response = new()
{
Success = true,
Categories = categories
};

_logger.LogReturningResponse(response);
return response;
}
}
Loading

0 comments on commit a86d800

Please sign in to comment.