Skip to content

Commit

Permalink
- Logging to ElasticSearch using AddCleanLogger(string elasticSearchU…
Browse files Browse the repository at this point in the history
…ri) and Serilog configuration in appsettings.json.

Nuget Version 6.0.0
  • Loading branch information
arashazimi0032 committed Mar 21, 2024
1 parent 8122e47 commit 80868cd
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 30 deletions.
12 changes: 7 additions & 5 deletions Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>CleanTemplate</PackageId>
<Title>CleanTemplate</Title>
<Version>5.0.0</Version>
<Version>6.0.0</Version>
<Authors>arashazimi0032</Authors>
<Product>CleanTemplate</Product>
<Description>A Clean Architecture Base Template comprising all Baseic and Abstract and Contract types for initiating and structuring a .Net project.</Description>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>https://github.com/arashazimi0032/Core.git</RepositoryUrl>
<PackageTags>CleanTemplate;CleanArchitecture;Contracts;Abstraction</PackageTags>
<PackageReleaseNotes>- CleanBaseDependencyInjectionModule renamed to CleanBaseDIModule and moved to DIModules namespace instead of DependencyInjectionModules.
- GlobalExceptionHangling added to request pipeline.
- DefaultMessage abstract property added to CleanBaseException
- AddCleanTemplateDependencyInjection renamed to AddCleanTemplateDIModule.</PackageReleaseNotes>
<PackageReleaseNotes>- Logging to ElasticSearch using AddCleanLogger(string elasticSearchUri) and Serilog configuration in appsettings.json.</PackageReleaseNotes>
<NeutralLanguage>en</NeutralLanguage>
<PackAsTool>False</PackAsTool>
<PackageReadmeFile>README.md</PackageReadmeFile>
Expand Down Expand Up @@ -51,6 +48,11 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions Core/Domain/Extensions/CleanTemplate/CleanTemplateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using Core.Domain.Extensions.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Exceptions;
using Serilog.Sinks.Elasticsearch;
using System.Reflection;

namespace Core.Domain.Extensions.CleanTemplate;
Expand All @@ -28,6 +31,26 @@ public static IServiceCollection AddCleanTemplate(this IServiceCollection servic
return services;
}

public static WebApplicationBuilder AddCleanLogger(this WebApplicationBuilder builder, string elasticSearchUri)
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(elasticSearchUri))
{
AutoRegisterTemplate = true,
IndexFormat = $"{Assembly.GetExecutingAssembly().GetName().Name?.ToLower()}-{environment?.ToLower()}-{DateTime.Now:yyyy-MM-dd}" ?? "",
})
.Enrich.WithProperty("Environment", environment)
.ReadFrom.Configuration(builder.Configuration)
.CreateLogger();

builder.Host.UseSerilog();

return builder;
}

public static IApplicationBuilder UseCleanTemplate(this IApplicationBuilder app)
{
var assembly = Assembly.GetCallingAssembly();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Serilog;
using System.Net;

namespace Core.Domain.Extensions.GlobalExceptionHandling;
Expand Down Expand Up @@ -50,8 +51,74 @@ internal static void UseCleanGlobalExceptionHandling(this IApplicationBuilder ap
errorModel.UnHandledExceptionMessage = exception.Message;
}
await context.Response.WriteAsync(JsonConvert.SerializeObject(errorModel).ToString());
Log.Logger.Error(exception, CreateMessageTemplate(errorModel));
await context.Response.WriteAsync(JsonConvert.SerializeObject(errorModel));
});
});
}

#region Private
private static string CreateMessageTemplate(ErrorModel errorModel)
{
var toReturn = "{\n" +
$"\tErrorTime: {errorModel.ErrorTime}\n" +
$"\tControllerName: {errorModel.ControllerName}\n" +
$"\tActionName: {errorModel.ActionName}\n" +
$"\tHttpMethod: {errorModel.HttpMethod}\n" +
$"\tExceptionType: {errorModel.ExceptionType}\n" +
"\tCleanExceptionMessage: {\n" +
$"{GetNestedTypes(errorModel.CleanExceptionMessage)}\n" +
"\t}\n" +
$"\tUnHandledExceptionMessage: {errorModel.UnHandledExceptionMessage}\n" +
$"\tUserIpAddress: {errorModel.UserIpAddress}\n" +
$"\tRequestUrl: {errorModel.RequestUrl}\n" +
$"\tRequestBody: {errorModel.RequestBody}\n" +
"\tRequestHeaders: {\n" +
$"{GetDictionaryString(errorModel.RequestHeaders)}\n" +
"\t}\n" +
"}";

return toReturn;
}

private static string GetNestedTypes(ExceptionMessage? exceptionMessage, int nestedLevel = 1)
{
var toReturn = "";
var tab = string.Concat(Enumerable.Repeat("\t", nestedLevel));
if (exceptionMessage is not null)
{
foreach (var property in exceptionMessage.GetType().GetProperties())
{
if (property.PropertyType.Equals(typeof(ExceptionMessage)))
{
toReturn += $"\t{tab}{property.Name}: " + "{\n";
toReturn += $"{GetNestedTypes((ExceptionMessage?)property.GetValue(exceptionMessage), nestedLevel + 1)}" +
$"\t{tab}" + "}\n";
}
else
{
toReturn += $"\t{tab}{property.Name}: {property.GetValue(exceptionMessage)}\n";
}
}
}
else
{
toReturn += $"\t{tab}null\n";
}

return toReturn;
}

private static string GetDictionaryString(Dictionary<string, string> dictionary)
{
var toReturn = "";

foreach (var key in dictionary.Keys)
{
toReturn += $"\t\t{key}: {dictionary[key]}\n";
}
return toReturn;
}
#endregion
}
73 changes: 49 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Clean Architecture Base Template
# Clean Architecture Base Template
![NuGet Downloads](https://img.shields.io/nuget/dt/CleanTemplate) ![NuGet Version](https://img.shields.io/nuget/v/CleanTemplate)

This package is a Clean Architecture Base Template comprising all Baseic and Abstract and Contract types for initiating and structuring a .Net project.

Expand All @@ -10,7 +11,7 @@ The .Net version used in this project is net8.0

## Dependencies

#### net8.0
### net8.0
- Carter (>= 8.0.0)
- MediatR (>= 12.2.0)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
Expand Down Expand Up @@ -54,7 +55,7 @@ Cake

I tryed to use CleanBase Prefix for all Base types in CleanTemplate Package so you can easily find them.

#### Dependency Injection And Service Configurations / Registerations
### Dependency Injection And Service Configurations / Registerations

CleanTemplate Package has two Extension Method for configuring and registering all the types that are introduced in the rest of this tutorial.

Expand Down Expand Up @@ -106,7 +107,7 @@ The above service automatically registerd as below (you don't need to write the
builder.Services.AddTransient<IService, Service>();
```

#### Dependency Injection And Service Configurations / Registerations From another Assembly
### Dependency Injection And Service Configurations / Registerations From another Assembly

If you have **Assemblies** other than the Web API Assembly in your solution (for example, several *ClassLibraries* to which the Web API has reference), and you have services in these Assemblies that you want to be Registered as a LifeTime service, ``AddCleanTemplate`` cannot Register them from Assemblies other than Web API. Therefore, you must Register those Assemblies in the following way:

Expand All @@ -124,7 +125,7 @@ builder.Services.AddCleanTemplateDIModule<MyAssemblyNameDIModule>();
```
Now all the services inside this assembly are injected as a LifetTme Service. (Any type of LifeTime service that you set according to the rules of the LifeTime Service Registeration mentioned above).

#### Create Model
### Create Model

The Generic ``CleanBaseEntity<T>`` used for creating an entity with Id's type as generic parameter. All structural equality constraints implemented for this type.
The CleanBaseEntity class also has predefined `CreatedAt` and `ModifiedAt` properties that automatically filled in SaveChangesAsync.
Expand All @@ -147,7 +148,7 @@ public class Product : CleanBaseEntity<ProductId>
```
Also you can use ``CleanBaseAggregateRoot<TId>`` to follow DDD concepts. The TId should be a ``CleanBaseValueObject``.

#### Create DbContext
### Create DbContext
There are two Base DbContext implemented in CleanTemplate library; ``CleanBaseDbContext<TContext>`` and ``CleanBaseIdentityDbContext<TContext, TUser>``.
SaveChangesAsync overrided for them to Automatically Auditing and Publishing Domain Events of type ``ICleanBaseDomainEvent``.

Expand All @@ -174,7 +175,7 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
```
you can use ``CleanBaseIdentityDbContext<TContext, TUser>`` similar to ``CleanBaseDbContext<TContext>``.

#### Entity Configuration
### Entity Configuration
For Entity Configuration you can use ``ICleanBaseEntityConfiguration<TEntity>``. All configs automatically handled.
```csharp
public class ProductConfiguration : ICleanBaseEntityConfiguration<Product>
Expand All @@ -186,7 +187,7 @@ public class ProductConfiguration : ICleanBaseEntityConfiguration<Product>
}
```

#### Repositories
### Repositories
Repositories splitted to Query and Command Repositories based on CQRS Concepts.
For use of Base Repositories do as follows.
```csharp
Expand Down Expand Up @@ -308,7 +309,7 @@ IQueryable<TEntity> GetQueryable();
IQueryable<TEntity> GetQueryableAsNoTrack();
```

#### MediatR Request and RequestHandlers
### MediatR Request and RequestHandlers
We use of MediatR to implement CRUD operations instead of services.
```csharp
public record CreateProductCommand(string Name, decimal Price) : ICleanBaseCommand<Product>;
Expand Down Expand Up @@ -353,7 +354,7 @@ public class ProductController : ControllerBase
```
Also for Queries you can use of ``ICleanBaseQuery`` and ``ICleanBaseQueryHandler``.

#### Domain Events
### Domain Events

You can create a Domain Event and it's handler so CleanTemplate automatically config and publishing them in SaveChangesAsync.
```csharp
Expand Down Expand Up @@ -389,7 +390,7 @@ public class Product : CleanBaseEntity<Guid>
}
```

#### Pagination
### Pagination

The CleanTemplate introduces a ``CleanPaginatedList<T>`` for get data in pagination format with below properties:
```csharp
Expand All @@ -412,9 +413,10 @@ public class CleanPage

We also implemented ``ToPaginatedListAsync(CleanPage Page)`` and ``ToPaginatedList(CleanPage Page)`` extension methods for IQueryable and IEnumerable.

#### Exceptions
### Exceptions

``CleanBaseException<TCode>`` implemented as a base exception class to Handling Exceptions. where the TCode is an enum to set an specific in-App ExceptionCode for every Exception and then you can handle Exception Messages in other languages using *.resx* files in C#.
``CleanBaseException`` & ``CleanBaseException<TCode>`` implemented as a base exception class to Handling Exceptions. where the TCode is an enum to set an specific in-App ExceptionCode for every Exception and then you can handle Exception Messages in other languages using *.resx* files in C#.
When you use this type of exception, GlobalExceptionHandling of CleanTemplate Package will recognize it and give you more complete details of the error that occurred.

```csharp
public class TestException : CleanBaseException<ExceptionStatusCode>
Expand All @@ -429,28 +431,51 @@ public enum ExceptionStatusCode
}
```

#### Settings
### Logging to Elasticsearch

We use **Serilog** and **ElasticSearch** for logging all Exceptions. By default, the CleanTemplate GlobalExceptionHandling middleware records all logs only in Elasticsearch using serilog.
To take advantage of the ``Logging to Elasticsearch`` feature in the CleanTemplate package, you must make the following settings. These settings are the minimum possible settings to benefit from the logging capability in the **CleanTemplate**. If you need more settings for **Srilog**, you can add them in the *appsettings.json*

1- In the ``appsettings.json`` add below settings:
```
"Serilog": {
"MinimumLevel": {
"Default": "Error",
"Override": {
"Microsoft": "Error",
"System": "Error"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
}
```
2- in the ``program.cs`` add below configuration:
```csharp
builder.AddCleanLogger(builder.Configuration["ElasticConfiguration:Uri"]);
```

With above configurations, all error logs automatically records in Elasticsearch and you should see them in **Kibana** Dashboard.

### Settings

``CleanBaseSetting`` class added for reading and configuring settings from appsettings.json.
Make sure that the name of the settings section in appsettings.json must be equal to the name of your setting class.

1- Add a setting in *appsettings.json*:
```csharp
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"TestSetting": {
"Name": "Arash",
"Email": "arashazimi0032@gmail.com",
"Age": 26
}
}
```


2- Add A class with the same name as your setting class.
```csharp
public class TestSetting : CleanBaseSetting
{
public TestSetting1(IConfiguration configuration) : base(configuration)
Expand All @@ -465,7 +490,7 @@ public class TestSetting : CleanBaseSetting

Then you just need to inject this TestSetting Class inside your services to access to its Properties.

#### Middlewares
### Middlewares

``ICleanBaseMiddleware`` implemented for automatically handling Middlewares in CleanTemplate.
```csharp
Expand All @@ -479,7 +504,7 @@ public class TestMiddleware : ICleanBaseMiddleware
}
```

#### Endpoints (Minimal APIs)
### Endpoints (Minimal APIs)

``ICleanBaseEndpoint`` interface is a base type for implementing Endpoints as below:
```csharp
Expand Down

0 comments on commit 80868cd

Please sign in to comment.