Skip to content

Commit 4069b42

Browse files
authored
Merge pull request #4 from babisque/catalog/feature/logging
logging in products controller
2 parents ab44201 + b73b546 commit 4069b42

File tree

7 files changed

+227
-56
lines changed

7 files changed

+227
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,218 @@
11
using Catalog.Core.DTO;
22
using Catalog.Core.Entities;
3+
using Catalog.Core.Logging;
34
using Catalog.Core.Repositories;
45
using Microsoft.AspNetCore.Mvc;
56

6-
namespace Catalog.API.Controllers;
7-
7+
namespace Catalog.API.Controllers
8+
{
89
[ApiController]
910
[Route("/[controller]")]
1011
public class ProductController : ControllerBase
1112
{
12-
13-
// TODO: Logging
14-
// TODO: Validations - maybe using FluentValidation
1513
private readonly IProductRepository _productRepository;
14+
private readonly ILogger<ProductController> _logger;
1615

17-
public ProductController(IProductRepository productRepository)
16+
public ProductController(IProductRepository productRepository, ILogger<ProductController> logger)
1817
{
1918
_productRepository = productRepository;
19+
_logger = logger;
2020
}
2121

2222
[HttpPost]
2323
public async Task<ActionResult<Product>> Post([FromForm] ProductDtoRequest req)
2424
{
25+
_logger.LogInformation("POST request received to create a new product.");
26+
2527
try
2628
{
27-
using var ms = new MemoryStream();
28-
if (req.Image != null) await req.Image.CopyToAsync(ms);
29-
3029
var product = new Product
3130
{
3231
Name = req.Name,
3332
Description = req.Description,
3433
Price = req.Price,
3534
Category = req.Category,
36-
Image = ms.ToArray(),
35+
Image = req.Image != null ? await GetImageBytesAsync(req.Image) : null,
3736
Stock = req.Stock
3837
};
38+
3939
await _productRepository.CreateAsync(product);
40-
return CreatedAtAction(nameof(Post), new { product.Id }, product);
40+
_logger.LogInformation($"Product created successfully with ID {product.Id}.");
41+
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
4142
}
4243
catch (Exception e)
4344
{
45+
CustomLogger.LogFile = true;
46+
_logger.LogError(e, "Error occurred while creating a new product.");
4447
return BadRequest(new { ErrorMessage = e.Message });
4548
}
4649
}
4750

4851
[HttpGet]
49-
public IActionResult Get()
52+
public async Task<IActionResult> Get()
5053
{
51-
IList<ProductDtoResponse> res = new List<ProductDtoResponse>();
52-
var products = _productRepository.GetAllAsync();
53-
if (products?.Result == null)
54-
return NotFound();
55-
56-
foreach (var product in products.Result)
54+
_logger.LogInformation("GET request received to retrieve all products.");
55+
try
5756
{
58-
res.Add(new ProductDtoResponse
57+
var products = await _productRepository.GetAllAsync();
58+
if (products == null || products.Count == 0)
5959
{
60-
Id = product.Id,
61-
Name = product.Name,
62-
Description = product.Description,
63-
Price = product.Price,
64-
Category = product.Category,
65-
Stock = product.Stock,
66-
CreatedAt = product.CreatedAt,
67-
UpdatedAt = product.UpdatedAt
68-
});
69-
}
60+
_logger.LogWarning("No products found.");
61+
return NotFound();
62+
}
7063

71-
return Ok(res);
64+
var res = new List<ProductDtoResponse>();
65+
foreach (var product in products)
66+
{
67+
res.Add(new ProductDtoResponse
68+
{
69+
Id = product.Id,
70+
Name = product.Name,
71+
Description = product.Description,
72+
Price = product.Price,
73+
Category = product.Category,
74+
Stock = product.Stock,
75+
CreatedAt = product.CreatedAt,
76+
UpdatedAt = product.UpdatedAt
77+
});
78+
}
79+
80+
_logger.LogInformation("Products retrieved successfully.");
81+
return Ok(res);
82+
}
83+
catch (Exception e)
84+
{
85+
CustomLogger.LogFile = true;
86+
_logger.LogError(e, "Error occurred while retrieving products.");
87+
return StatusCode(500, new { ErrorMessage = e.Message });
88+
}
7289
}
7390

7491
[HttpGet("GetImage/{productId:int}")]
75-
public IActionResult GetImage([FromRoute]int productId)
92+
public async Task<IActionResult> GetImage([FromRoute] int productId)
7693
{
77-
var imageBinary = _productRepository.GetImage(productId);
78-
if (imageBinary is not null)
79-
return File(imageBinary, "image/png");
94+
_logger.LogInformation($"GET request received to retrieve image for product ID {productId}.");
95+
96+
try
97+
{
98+
var imageBinary = _productRepository.GetImage(productId);
99+
if (imageBinary != null)
100+
{
101+
_logger.LogInformation($"Image retrieved successfully for product ID {productId}.");
102+
return File(imageBinary, "image/png");
103+
}
80104

81-
return NoContent();
105+
_logger.LogWarning($"No image found for product ID {productId}.");
106+
return NoContent();
107+
}
108+
catch (Exception e)
109+
{
110+
CustomLogger.LogFile = true;
111+
_logger.LogError(e, $"Error occurred while retrieving image for product ID {productId}.");
112+
return StatusCode(500, new { ErrorMessage = e.Message });
113+
}
82114
}
83115

84116
[HttpGet("{id:int}")]
85117
public async Task<ActionResult<ProductDtoResponse>> GetProduct([FromRoute] int id)
86118
{
119+
_logger.LogInformation($"GET request received to retrieve product with ID {id}.");
87120
try
88121
{
89-
var res = new ProductDtoResponse();
90122
var product = await _productRepository.GetByIdAsync(id);
123+
if (product == null)
124+
{
125+
_logger.LogWarning($"Product with ID {id} not found.");
126+
return NotFound();
127+
}
128+
129+
var res = new ProductDtoResponse
130+
{
131+
Id = product.Id,
132+
Name = product.Name,
133+
Description = product.Description,
134+
Price = product.Price,
135+
Category = product.Category,
136+
Stock = product.Stock,
137+
CreatedAt = product.CreatedAt,
138+
UpdatedAt = product.UpdatedAt
139+
};
91140

92-
res.Id = product.Id;
93-
res.Name = product.Name;
94-
res.Description = product.Description;
95-
res.Price = product.Price;
96-
res.Category = product.Category;
97-
res.Stock = product.Stock;
98-
res.CreatedAt = product.CreatedAt;
99-
res.UpdatedAt = product.UpdatedAt;
100-
141+
_logger.LogInformation($"Product with ID {id} retrieved successfully.");
101142
return Ok(res);
102143
}
103144
catch (Exception e)
104145
{
105-
return NotFound(new { ErrorMessage = e.Message });
146+
CustomLogger.LogFile = true;
147+
_logger.LogError(e, $"Error occurred while retrieving product ID {id}.");
148+
return StatusCode(500, new { ErrorMessage = e.Message });
106149
}
107150
}
108151

109152
[HttpPut("{id:int}")]
110153
public async Task<ActionResult> Put([FromRoute] int id, [FromBody] ProductDtoUpdate req)
111154
{
155+
_logger.LogInformation($"PUT request received to update product with ID {id}.");
156+
112157
try
113158
{
114159
var product = await _productRepository.GetByIdAsync(id);
115-
116-
if (req.Name != null) product.Name = req.Name;
117-
if (req.Description != null) product.Description = req.Description;
118-
if (req.Price != null)product.Price = (decimal)req.Price;
119-
if (req.Category != null) product.Category = req.Category;
120-
if (req.Stock != null) product.Stock = (int)req.Stock;
160+
if (product == null)
161+
{
162+
_logger.LogWarning($"Product with ID {id} not found.");
163+
return NotFound();
164+
}
165+
166+
product.Name = req.Name ?? product.Name;
167+
product.Description = req.Description ?? product.Description;
168+
product.Price = req.Price ?? product.Price;
169+
product.Category = req.Category ?? product.Category;
170+
product.Stock = req.Stock ?? product.Stock;
121171
product.UpdatedAt = DateTime.Now;
122172

123173
await _productRepository.UpdateAsync(product);
174+
_logger.LogInformation($"Product with ID {id} updated successfully.");
124175
return Ok();
125176
}
126177
catch (Exception e)
127178
{
179+
CustomLogger.LogFile = true;
180+
_logger.LogError(e, $"Error occurred while updating product with ID {id}.");
128181
return BadRequest(new { ErrorMessage = e.Message });
129182
}
130183
}
131184

132185
[HttpDelete("{id:int}")]
133186
public async Task<ActionResult> Delete([FromRoute] int id)
134187
{
188+
_logger.LogInformation($"DELETE request received to delete product with ID {id}.");
189+
135190
try
136191
{
137192
var product = await _productRepository.GetByIdAsync(id);
193+
if (product == null)
194+
{
195+
_logger.LogWarning($"Product with ID {id} not found.");
196+
return NotFound();
197+
}
198+
138199
await _productRepository.RemoveAsync(product.Id);
200+
_logger.LogInformation($"Product with ID {id} deleted successfully.");
139201
return Ok();
140202
}
141203
catch (Exception e)
142204
{
143-
return NotFound(new { ErrorMessage = e.Message });
205+
CustomLogger.LogFile = true;
206+
_logger.LogError(e, $"Error occurred while deleting product with ID {id}.");
207+
return StatusCode(500, new { ErrorMessage = e.Message });
144208
}
145209
}
146-
}
210+
211+
private async Task<byte[]> GetImageBytesAsync(IFormFile image)
212+
{
213+
using var ms = new MemoryStream();
214+
await image.CopyToAsync(ms);
215+
return ms.ToArray();
216+
}
217+
}
218+
}

Catalog/Catalog.API/Program.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Catalog.Core.Logging;
12
using Catalog.Core.Repositories;
23
using Catalog.Infrastructure.Repositories;
34
using Microsoft.EntityFrameworkCore;
@@ -14,10 +15,16 @@
1415
builder.Services.AddEndpointsApiExplorer();
1516
builder.Services.AddSwaggerGen();
1617

18+
builder.Logging.ClearProviders();
19+
builder.Logging.AddProvider(new CustomLoggerProvider(new CustomLoggerProviderConfiguration
20+
{
21+
LogLevel = LogLevel.Information
22+
}));
23+
1724
builder.Services.AddDbContext<ApplicationDbContext>(opts =>
1825
{
1926
opts.UseSqlServer(configuration.GetConnectionString("ConnectionString"));
20-
}, ServiceLifetime.Scoped);
27+
});
2128

2229
builder.Services.AddScoped<IProductRepository, ProductRepository>();
2330

Catalog/Catalog.Core/Catalog.Core.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.0" />
1111
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="2.2.0" />
12+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
1213
</ItemGroup>
1314

1415
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Microsoft.Extensions.Logging;
2+
3+
namespace Catalog.Core.Logging;
4+
5+
public class CustomLogger : ILogger
6+
{
7+
private readonly string _loggerName;
8+
private readonly CustomLoggerProviderConfiguration _loggerConfig;
9+
public static bool LogFile { get; set; } = false;
10+
11+
public CustomLogger(string loggerName, CustomLoggerProviderConfiguration loggerConfig)
12+
{
13+
_loggerName = loggerName;
14+
_loggerConfig = loggerConfig;
15+
}
16+
17+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
18+
{
19+
var message = $"Execution log { logLevel }: {eventId} - {formatter(state, exception)}";
20+
21+
if (LogFile)
22+
WriteLogMessageInFile(message);
23+
else
24+
Console.WriteLine(message);
25+
}
26+
27+
private void WriteLogMessageInFile(string message)
28+
{
29+
var filePath = Environment.CurrentDirectory + $@"loggin\LOG-{DateTime.Now:yyyy-MM-dd}.txt";
30+
if (!File.Exists(filePath))
31+
{
32+
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
33+
File.Create(filePath).Dispose();
34+
}
35+
36+
using StreamWriter sw = new(filePath, true);
37+
sw.WriteLine(message);
38+
sw.Close();
39+
}
40+
41+
public bool IsEnabled(LogLevel logLevel)
42+
{
43+
return true;
44+
}
45+
46+
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
47+
{
48+
return null;
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Concurrent;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace Catalog.Core.Logging;
5+
6+
public class CustomLoggerProvider : ILoggerProvider
7+
{
8+
private readonly CustomLoggerProviderConfiguration _loggerConfig;
9+
private readonly ConcurrentDictionary<string, CustomLogger> _loggers = new ConcurrentDictionary<string, CustomLogger>();
10+
11+
public CustomLoggerProvider(CustomLoggerProviderConfiguration loggerConfig)
12+
{
13+
_loggerConfig = loggerConfig;
14+
}
15+
16+
public ILogger CreateLogger(string categoryName)
17+
{
18+
return _loggers.GetOrAdd(categoryName, name => new CustomLogger(name, _loggerConfig));
19+
}
20+
21+
public void Dispose()
22+
{
23+
throw new NotImplementedException();
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.Extensions.Logging;
2+
3+
namespace Catalog.Core.Logging;
4+
5+
public class CustomLoggerProviderConfiguration
6+
{
7+
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
8+
public int EventId { get; set; } = 0;
9+
}

0 commit comments

Comments
 (0)