Adding logging to our API in middleware

In simple words, logging is nothing but the process or act of getting log files in one place to get the events or other actions that occur in APIs during communication. In this section, we will implement logging for our product APIs.

Before we start looking at how to log our APIs' events, let's first take a quick look at our existing product APIs.

Refer to the Request delegates section to refresh your memory as to how you can create a new ASP.NET Core project.

The following screenshot shows the project structure of our product APIs:

Here is our Product model:

public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
public decimal Price { get; set; }
public Guid CategoryId { get; set; }
public virtual Category Category { get; set; }
}

The Product model is a class that represents a product, containing properties.

Here is our repository interface:

public interface IProductRepository
{
void Add(Product product);
IEnumerable<Product> GetAll();
Product GetBy(Guid id);
void Remove(Guid id);
void Update(Product product);
}

The IProductRepository interface has methods that are required for our APIs to start with operations for a product.

Let's take a look at our ProductRepository class:

public class ProductRepository : IProductRepository
{
private readonly ProductContext _context;
public ProductRepository(ProductContext context) =>
_context = context;
public IEnumerable<Product> GetAll() => _context.Products.
Include(c => c.Category).ToList();
public Product GetBy(Guid id) => _context.Products.Include
(c => c.Category).FirstOrDefault(x => x.Id == id);
public void Add(Product product)
{
_context.Products.Add(product);
_context.SaveChanges();
}
public void Update(Product product)
{
_context.Update(product);
_context.SaveChanges();
}
public void Remove(Guid id)
{
var product = GetBy(id);
_context.Remove(product);
_context.SaveChanges();
}
}

The ProductRepository class implements the IProductRepository interface. The preceding code is self-explanatory.

Open the Startup.cs file and add the following code:

services.AddScoped<IProductRepository, ProductRepository>();
services.AddDbContext<ProductContext>(
o => o.UseSqlServer(Configuration.GetConnectionString
("ProductConnection")));
services.AddSwaggerGen(swagger =>
{
swagger.SwaggerDoc("v1", new Info { Title = "Product APIs",
Version = "v1" });
});

For Swagger support for our Product APIs, you need to add the Swashbuckle.ASPNETCore NuGet package.

Now, open the appsettings.json file and add the following code:

"ConnectionStrings": 
{
"ProductConnection": "Data Source=.;Initial
Catalog=ProductsDB;Integrated
Security=True;MultipleActiveResultSets=True"
}

Let's see what our ProductController contains:

[HttpGet]
[Route("productlist")]
public IActionResult GetList()
{
return new
OkObjectResult(_productRepository.GetAll().
Select(ToProductvm).ToList());
}

The preceding code is the GET resource of our product APIs. It calls the GetAll() method of our ProductRepository, transposes the response, and returns it. In the previous code, we have already instructed the system to resolve the IProductRepository interface with the ProductRepository class. Refer to the Startup class.

Here is the method that transposes the response:

private ProductViewModel ToProductvm(Product productModel)
{
return new ProductViewModel
{
CategoryId = productModel.CategoryId,
CategoryDescription = productModel.Category.Description,
CategoryName = productModel.Category.Name,
ProductDescription = productModel.Description,
ProductId = productModel.Id,
ProductImage = productModel.Image,
ProductName = productModel.Name,
ProductPrice = productModel.Price
};
}

The preceding code accepts a parameter of the Product type and then returns an object of the ProductViewModel type.

The following code shows how our controller constructor is injected:

private readonly IProductRepository _productRepository;
public ProductController(IProductRepository productRepository)
{
_productRepository = productRepository;
}

In the preceding code, we injected our ProductRepository, and it will be automatically initialized whenever anyone calls any resources of the product APIs.

Now, you are ready to play with the application. Run the application from the menu or click F5. In a web browser, you can use the suffix /swagger to the URL of the address.

For the complete source code, refer to the GitHub repository link at https://github.com/PacktPublishing/Building-RESTful-Web-services-with-DOTNET-Core.

It will show the Swagger API documentation, as shown in the following screenshot:

Click on the GET /api/Product/productlist resource. It will return a list of products, as shown in the following screenshot:

Let's implement logging for our API. Please note that to make our demo short and simple, I am not adding complex scenarios to track everything. I am adding simple logs to showcase the logging capabilities.

To start implementing logging for our product APIs, add a new class called LogAction in a new folder called Logging. Here is the code from the LogAction class:

public class LogActions 
{
public const int InsertProduct = 1000;
public const int ListProducts = 1001;
public const int GetProduct = 1002;
public const int RemoveProduct = 1003;
}

The preceding code contains constants that are nothing but our application's actions, also called events.

Update our ProductController; it should now look like the following code:

private readonly IProductRepository _productRepository;
private readonly ILogger _logger;
public ProductController(IProductRepository productRepository, ILogger logger)
{
_productRepository = productRepository;
_logger = logger;
}

In the preceding code, we added an ILogger interface, which comes from a dependency injection container (see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.0 for more details).

Let's add the logging capability to the GET resource of the product API:

[HttpGet]
[Route("productlist")]
public IActionResult GetList()
{
_logger.LogInformation(LogActions.ListProducts, "Getting all
products.");
return new
OkObjectResult(_productRepository.GetAll().Select(ToProductvm).
ToList());
}

The preceding code returns the product list and logs the information.

To test this scenario, we need a client or an API tool so we can see the output. To do this, we will use the Postman extension (see https://www.getpostman.com/ for more details).

First, we need to run the application. To do so, open the Visual Studio command prompt, move to your project folder, and then pass the command dotnet run. You will see a similar message to the one shown in the following screenshot:

Now, launch Postman and invoke the GET /api/product/productlist resource:

By clicking the Send button, you would expect a list of products to be returned, but this is not the case, as shown in the following screenshot:

The preceding exception occurs because we are using a non-generic type in our ProductController that is not injectable. 

So, we need to make slight changes in our ProductController. Look at the following code snippet:

private readonly IProductRepository _productRepository;
private readonly ILogger<ProductController> _logger;
public ProductController(IProductRepository productRepository, ILogger<ProductController> logger)
{
_productRepository = productRepository;
_logger = logger;
}

In the preceding code, I added a generic ILogger<ProductController> type. As it is injectable, it will get resolved automatically.

Logging is slightly different in .NET Core 2.0 compared to its earlier versions. The implementation of the nongeneric ILogger is not available by default, but it is available for ILogger<T>. If you want to use nongeneric implementation, use ILoggerFactory instead of ILogger.

In this case, the constructor of our ProductController would look like the following:


private readonly IProductRepository _productRepository;
private readonly ILogger _logger;

public ProductController(IProductRepository productRepository, ILoggerFactory logger)
{
_productRepository = productRepository;
_logger = logger.CreateLogger("Product logger");
}

Open the Program class and update it. It should look like the following code snippet:

public static void Main(string[] args)
{
var webHost = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true,
reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
    logging.AddConfiguration(hostingContext.Configuration.
GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>()
.Build();
webHost.Run();
}

You also need to update the appsettings.json file and write more code for the logger so that your file looks like the following snippet:

{
"ApplicationInsights":
{
"InstrumentationKey": ""
},
"Logging":
{
"IncludeScopes": false,
"Console":
{
"LogLevel":
{
"Default": "Warning",
"System": "Information",
"Microsoft": "Information"
}
}
},
"ConnectionStrings":
{
"ProductConnection": "Data Source=.;Initial
Catalog=ProductsDB;Integrated
Security=True;MultipleActiveResultSets=True"
}
}

Now, once again, open the Visual Studio command prompt and write the dotnet build command. It will build the project, and you will get a message similar to the following screenshot:


From this point, if you run Postman, it will give you the results, as shown in the following screenshot:

The preceding code adds the ability to log the actions. You will receive similar log actions to those shown in the following screenshot:


Here, we have written some code that uses the default ILogger. We have used default methods to invoke the logger; however, there are scenarios where we need a customized logger. In the next section, we will discuss how to write middleware for a custom logger.