Skip to content

Commit

Permalink
Merge pull request #91 from tochka-public/CSHARP-73/master
Browse files Browse the repository at this point in the history
Exceptions logging and more examples in docs
  • Loading branch information
OptimumDev authored Jan 9, 2024
2 parents 42acb4a + c8a299c commit 8e924ed
Show file tree
Hide file tree
Showing 19 changed files with 617 additions and 48 deletions.
17 changes: 17 additions & 0 deletions docs/en/server/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ builder.Services.AddJsonRpcServer(static options =>
options.DefaultDataJsonSerializerOptions = JsonRpcSerializerOptions.SnakeCase;
options.HeadersJsonSerializerOptions = JsonRpcSerializerOptions.Headers;
options.RoutePrefix = "/api/jsonrpc";
options.LogExceptions = true;
});
```

Expand Down Expand Up @@ -150,6 +151,22 @@ Route can be overridden with framework's `RouteAttribute` like usual, and global
* Prefix can be set to `"/"` to get rid of it
* Templates are supported (see [Routing#Route templates](routing#Route-templates))

### LogExceptions

```cs
builder.Services.AddJsonRpcServer(static options => options.LogExceptions = /* true or false */);
```

> Default: `true`
> If `true`, all exceptions during JSON-RPC call processing will be logged with Error log level
[Usage examples](examples#Logging).

Exceptions thrown by this library, middleware, or user code, are intercepted and serialized as JSON-RPC error response in `IExceptionFilter`. Because of that, filter is the last place, where you can access exception object.

If you want to log only certain type of exceptions, you can set this option to `false` and add your own `IExceptionFilter` with logging logic (see [Examples#Logging > Certain exceptions](examples#Logging)).

## Attributes

### JsonRpcSerializerOptionsAttribute
Expand Down
82 changes: 80 additions & 2 deletions docs/en/server/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ HttpContext.SetJsonRpcResponse(response);
Different ways to return error from method. See [Errors](errors) for details.

<details>
<summary>IJsonRpcErrorFactory methods</summary>
<summary>IJsonRpcErrorFactory methods (recommended)</summary>

```cs
public class FailController : JsonRpcControllerBase
Expand Down Expand Up @@ -1871,14 +1871,92 @@ Response (does not depend on [`DetailedResponseExceptions`](configuration#Detail

</details>

## Requests logging
## Custom exceptions wrapping

If you want to add custom logic for wrapping certain exceptions in JSON-RPC response, you can register custom `IExceptionFilter` and use it to set `context.Result`.

<details>
<summary>Expand</summary>

> `Program.cs`
```cs
builder.Services.AddControllers(static options => options.Filters.Add<CustomExceptionWrappingFilter>());
builder.Services.AddJsonRpcServer();
```

> `CustomExceptionWrappingFilter.cs`
```cs
public class CustomExceptionWrappingFilter : IExceptionFilter
{
private readonly IJsonRpcErrorFactory errorFactory;

public CustomExceptionWrappingFilter(IJsonRpcErrorFactory errorFactory) => this.errorFactory = errorFactory;

public void OnException(ExceptionContext context)
{
if (context.Exception is not BusinessLogicException exception)
{
return;
}

var error = errorFactory.InternalError(exception);
context.Result = new ObjectResult(error);
}
}
```

</details>

## Logging

<details>
<summary>Incoming requests</summary>

```cs
app.UseJsonRpc()
.WithJsonRpcRequestLogging()
```

</details>

<details>
<summary>All exceptions</summary>

Exceptions logging is enabled by default, but you can configure it using options.

```cs
builder.Services.AddJsonRpcServer(static options => options.LogExceptions = true); // <-- by default
```

</details>

<details>
<summary>Certain exceptions</summary>

You can disable exceptions logging and add your own filter.

> `Program.cs`
```cs
builder.Services.AddControllers(static options => options.Filters.Add<CustomExceptionsLoggingFilter>());
builder.Services.AddJsonRpcServer(static options => options.LogExceptions = false);
```

> `CustomExceptionsLoggingFilter.cs`
```cs
public class CustomExceptionsLoggingFilter : IExceptionFilter
{
private readonly ILogger<CustomExceptionsLoggingFilter> log;

public CustomExceptionsLoggingFilter(ILogger<CustomExceptionsLoggingFilter> log) => this.log = log;

public void OnException(ExceptionContext context)
{
if (context.Exception is not BusinessLogicException)
{
log.LogError(context.Exception, "Unexpected exception");
}
}
}
```

</details>
17 changes: 17 additions & 0 deletions docs/ru/server/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ builder.Services.AddJsonRpcServer(static options =>
options.DefaultDataJsonSerializerOptions = JsonRpcSerializerOptions.SnakeCase;
options.HeadersJsonSerializerOptions = JsonRpcSerializerOptions.Headers;
options.RoutePrefix = "/api/jsonrpc";
options.LogExceptions = true;
});
```

Expand Down Expand Up @@ -148,6 +149,22 @@ Route может быть переопределен с помощью стан
* Может быть установлено значение `"/"`, чтобы избавиться от него
* Поддерживает шаблонизацию (см. [Маршрутизация#Шаблонизация route](routing#Шаблонизация-route))

### LogExceptions

```cs
builder.Services.AddJsonRpcServer(static options => options.LogExceptions = /* true или false */);
```

> Значение по умолчанию: `true`
> Если `true`, все исключения будут логироваться с уровнем Error
[Примеры использования](examples#Логирование).

Исключения, вызванные этой библиотекой, мидлварями или пользовательским кодом, перехватываются и сериализуются как JSON-RPC response в `IExceptionFilter`. Из-за этого, фильтр - последнее место, где можно получить доступ к объекту исключения.

Если требуется логировать только определенные типы исключений, можно установить эту настройку в `false` и реализовать свой `IExceptionFilter` для логирования (см. [Примеры#Логирование > Определенные исключения](examples#Логирование)).

## Атрибуты

### JsonRpcSerializerOptionsAttribute
Expand Down
82 changes: 80 additions & 2 deletions docs/ru/server/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -1390,7 +1390,7 @@ HttpContext.SetJsonRpcResponse(response);
Разные способы вернуть ошибку из метода. Подробности см. в [Ошибки](errors).

<details>
<summary>Методы IJsonRpcErrorFactory</summary>
<summary>Методы IJsonRpcErrorFactory (рекомендуется)</summary>

```cs
public class FailController : JsonRpcControllerBase
Expand Down Expand Up @@ -1872,14 +1872,92 @@ public class FailController : JsonRpcControllerBase

</details>

## Логирование запросов
## Кастомная обработка исключений

Если нужно реализовать особую логику заворачивания исключений в JSON-RPC response, можно зарегистрировать свой `IExceptionFilter` и просаживать в нем `context.Result`.

<details>
<summary>Развернуть</summary>

> `Program.cs`
```cs
builder.Services.AddControllers(static options => options.Filters.Add<CustomExceptionWrappingFilter>());
builder.Services.AddJsonRpcServer();
```

> `CustomExceptionWrappingFilter.cs`
```cs
public class CustomExceptionWrappingFilter : IExceptionFilter
{
private readonly IJsonRpcErrorFactory errorFactory;

public CustomExceptionWrappingFilter(IJsonRpcErrorFactory errorFactory) => this.errorFactory = errorFactory;

public void OnException(ExceptionContext context)
{
if (context.Exception is not BusinessLogicException exception)
{
return;
}

var error = errorFactory.InternalError(exception);
context.Result = new ObjectResult(error);
}
}
```

</details>

## Логирование

<details>
<summary>Входящие запросы</summary>

```cs
app.UseJsonRpc()
.WithJsonRpcRequestLogging()
```

</details>

<details>
<summary>Все исключения</summary>

Логирование исключений включено по умолчанию, но его можно конфигурировать через настройки.

```cs
builder.Services.AddJsonRpcServer(static options => options.LogExceptions = true); // <-- по умолчанию
```

</details>

<details>
<summary>Определенные исключения</summary>

Можно выключить логирование исключений и добавить собственный фильтр.

> `Program.cs`
```cs
builder.Services.AddControllers(static options => options.Filters.Add<CustomExceptionsLoggingFilter>());
builder.Services.AddJsonRpcServer(static options => options.LogExceptions = false);
```

> `CustomExceptionsLoggingFilter.cs`
```cs
public class CustomExceptionsLoggingFilter : IExceptionFilter
{
private readonly ILogger<CustomExceptionsLoggingFilter> log;

public CustomExceptionsLoggingFilter(ILogger<CustomExceptionsLoggingFilter> log) => this.log = log;

public void OnException(ExceptionContext context)
{
if (context.Exception is not BusinessLogicException)
{
log.LogError(context.Exception, "Unexpected exception");
}
}
}
```

</details>
19 changes: 17 additions & 2 deletions src/Tochka.JsonRpc.Server/Filters/JsonRpcExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Tochka.JsonRpc.Server.Extensions;
using Tochka.JsonRpc.Server.Services;
using Tochka.JsonRpc.Server.Settings;

namespace Tochka.JsonRpc.Server.Filters;

/// <inheritdoc />
/// <summary>
/// Filter for JSON-RPC actions to convert exceptions to JSON-RPC error
/// Filter for JSON-RPC actions to log exceptions and convert them to JSON-RPC error
/// </summary>
internal class JsonRpcExceptionFilter : IExceptionFilter
{
private readonly IJsonRpcErrorFactory errorFactory;
private readonly JsonRpcServerOptions options;
private readonly ILogger<JsonRpcExceptionFilter> log;

public JsonRpcExceptionFilter(IJsonRpcErrorFactory errorFactory) => this.errorFactory = errorFactory;
public JsonRpcExceptionFilter(IJsonRpcErrorFactory errorFactory, IOptions<JsonRpcServerOptions> options, ILogger<JsonRpcExceptionFilter> log)
{
this.errorFactory = errorFactory;
this.options = options.Value;
this.log = log;
}

// wrap exception in json rpc error
public void OnException(ExceptionContext context)
Expand All @@ -23,6 +33,11 @@ public void OnException(ExceptionContext context)
return;
}

if (options.LogExceptions)
{
log.LogError(context.Exception, "Exception during JSON-RPC call processing");
}

var error = errorFactory.Exception(context.Exception);
context.Result = new ObjectResult(error);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Tochka.JsonRpc.Server/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,5 @@ Tochka.JsonRpc.Server.Middlewares.JsonRpcRequestLoggingMiddleware
Tochka.JsonRpc.Server.Middlewares.JsonRpcRequestLoggingMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext! context) -> System.Threading.Tasks.Task!
Tochka.JsonRpc.Server.Middlewares.JsonRpcRequestLoggingMiddleware.JsonRpcRequestLoggingMiddleware(Microsoft.AspNetCore.Http.RequestDelegate! next, Microsoft.Extensions.Logging.ILogger<Tochka.JsonRpc.Server.Middlewares.JsonRpcRequestLoggingMiddleware!>! log) -> void
static Tochka.JsonRpc.Server.Extensions.DependencyInjectionExtensions.WithJsonRpcRequestLogging(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
Tochka.JsonRpc.Server.Settings.JsonRpcServerOptions.LogExceptions.get -> bool
Tochka.JsonRpc.Server.Settings.JsonRpcServerOptions.LogExceptions.set -> void
8 changes: 8 additions & 0 deletions src/Tochka.JsonRpc.Server/Settings/JsonRpcServerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,12 @@ public sealed class JsonRpcServerOptions
/// Batches will break if this option is enabled and one of requests returns non-json data!
/// </remarks>
public bool AllowRawResponses { get; set; }

/// <summary>
/// If `true`, all exceptions during JSON-RPC call processing will be logged with Error log level
/// </summary>
/// <remarks>
/// true by default
/// </remarks>
public bool LogExceptions { get; set; } = true;
}
Loading

0 comments on commit 8e924ed

Please sign in to comment.