Skip to content

Commit

Permalink
[*] Structure logging (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
amoraller authored Apr 18, 2023
1 parent 99780ab commit 261bb51
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 78 deletions.
154 changes: 80 additions & 74 deletions src/EdjCase.JsonRpc.Router/Defaults/DefaultRpcInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,81 +108,87 @@ public async Task<List<RpcResponse>> InvokeBatchRequestAsync(IList<RpcRequest> r
{
throw new ArgumentNullException(nameof(request));
}

this.logger.InvokingRequest(request.Id);
RpcContext routeContext = this.contextAccessor.Get();
RpcInvokeContext? invokeContext = null;
if (this.serverConfig.Value.OnInvokeStart != null)
{
invokeContext = new RpcInvokeContext(routeContext.RequestServices, request, routeContext.Path);
this.serverConfig.Value.OnInvokeStart(invokeContext);
using (this.logger.BeginScope(new KeyValuePair<string, object>[] { new("request", new {
@id = request.Id.ToString(),
@method = request.Method,
@params = request.Parameters?.Value
}) }))
{
this.logger.InvokingRequest(request.Id);
RpcContext routeContext = this.contextAccessor.Get();
RpcInvokeContext? invokeContext = null;
if (this.serverConfig.Value.OnInvokeStart != null)
{
invokeContext = new RpcInvokeContext(routeContext.RequestServices, request, routeContext.Path);
this.serverConfig.Value.OnInvokeStart(invokeContext);
}
RpcResponse rpcResponse;
try
{
IRpcMethodInfo rpcMethod;
using (var requestSignature = RpcRequestSignature.Create(request))
{
rpcMethod = this.rpcRequestMatcher.GetMatchingMethod(requestSignature);
}

bool isAuthorized = await this.authorizationHandler.IsAuthorizedAsync(rpcMethod);

if (isAuthorized)
{
object[] realParameters = this.ParseParameters(request.Parameters, rpcMethod.Parameters);

this.logger.InvokeMethod(request.Method);
object? result = await this.InvokeAsync(rpcMethod, realParameters, request, routeContext.RequestServices);
this.logger.InvokeMethodComplete(request.Method);

if (result is IRpcMethodResult methodResult)
{
rpcResponse = methodResult.ToRpcResponse(request.Id);
}
else
{
rpcResponse = new RpcResponse(request.Id, result);
}
}
else
{
var authError = new RpcError(RpcErrorCode.InvalidRequest, "Unauthorized");
rpcResponse = new RpcResponse(request.Id, authError);
}
}
catch (Exception ex)
{
const string errorMessage = "An Rpc error occurred while trying to invoke request.";
this.logger.LogException(ex, errorMessage);
RpcError error;
if (ex is RpcException rpcException)
{
error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions);
}
else
{
error = new RpcError(RpcErrorCode.InternalError, errorMessage, ex);
}
rpcResponse = new RpcResponse(request.Id, error);
}
if (this.serverConfig.Value.OnInvokeEnd != null)
{
if (invokeContext == null)
{
invokeContext = new RpcInvokeContext(routeContext.RequestServices, request, routeContext.Path);
}
this.serverConfig.Value.OnInvokeEnd(invokeContext, rpcResponse);
}
if (request.Id.HasValue)
{
this.logger.FinishedRequest(request.Id.ToString()!);
//Only give a response if there is an id
return rpcResponse;
}
//TODO make no id run in a non-blocking way
this.logger.FinishedRequestNoId();
return null;
}
RpcResponse rpcResponse;
try
{
IRpcMethodInfo rpcMethod;
using (var requestSignature = RpcRequestSignature.Create(request))
{
rpcMethod = this.rpcRequestMatcher.GetMatchingMethod(requestSignature);
}

bool isAuthorized = await this.authorizationHandler.IsAuthorizedAsync(rpcMethod);

if (isAuthorized)
{
object[] realParameters = this.ParseParameters(request.Parameters, rpcMethod.Parameters);

this.logger.InvokeMethod(request.Method);
object? result = await this.InvokeAsync(rpcMethod, realParameters, request, routeContext.RequestServices);
this.logger.InvokeMethodComplete(request.Method);

if (result is IRpcMethodResult methodResult)
{
rpcResponse = methodResult.ToRpcResponse(request.Id);
}
else
{
rpcResponse = new RpcResponse(request.Id, result);
}
}
else
{
var authError = new RpcError(RpcErrorCode.InvalidRequest, "Unauthorized");
rpcResponse = new RpcResponse(request.Id, authError);
}
}
catch (Exception ex)
{
const string errorMessage = "An Rpc error occurred while trying to invoke request.";
this.logger.LogException(ex, errorMessage);
RpcError error;
if (ex is RpcException rpcException)
{
error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions);
}
else
{
error = new RpcError(RpcErrorCode.InternalError, errorMessage, ex);
}
rpcResponse = new RpcResponse(request.Id, error);
}
if (this.serverConfig.Value.OnInvokeEnd != null)
{
if (invokeContext == null)
{
invokeContext = new RpcInvokeContext(routeContext.RequestServices, request, routeContext.Path);
}
this.serverConfig.Value.OnInvokeEnd(invokeContext, rpcResponse);
}
if (request.Id.HasValue)
{
this.logger.FinishedRequest(request.Id.ToString()!);
//Only give a response if there is an id
return rpcResponse;
}
//TODO make no id run in a non-blocking way
this.logger.FinishedRequestNoId();
return null;
}

private object[] ParseParameters(TopLevelRpcParameters? requestParameters, IReadOnlyList<IRpcParameterInfo> methodParameters)
Expand Down
72 changes: 68 additions & 4 deletions test/EdjCase.JsonRpc.Router.Tests/InvokerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@
using System.IO;
using System.Linq;
using System.Text.Json;

using Xunit.Abstractions;

namespace EdjCase.JsonRpc.Router.Tests
{
public class InvokerTests
{
{
private readonly ITestOutputHelper _output;

public InvokerTests(ITestOutputHelper output)
{
this._output = output;
}
private DefaultRpcInvoker GetInvoker(string? methodName, RpcPath? path = null,
Action<RpcServerConfiguration>? configure = null)
{
Expand All @@ -37,7 +44,7 @@ private DefaultRpcInvoker GetInvoker(MethodInfo? methodInfo, RpcPath? path = nul
Action<RpcServerConfiguration>? configure = null)

{
var logger = new Mock<ILogger<DefaultRpcInvoker>>(MockBehavior.Loose);
var logger = new XunitLogger<DefaultRpcInvoker>(this._output);
var options = new Mock<IOptions<RpcServerConfiguration>>(MockBehavior.Strict);
var matcher = new Mock<IRpcRequestMatcher>(MockBehavior.Strict);
var accessor = new Mock<IRpcContextAccessor>(MockBehavior.Strict);
Expand Down Expand Up @@ -70,7 +77,7 @@ private DefaultRpcInvoker GetInvoker(MethodInfo? methodInfo, RpcPath? path = nul

var logger2 = new Mock<ILogger<DefaultRpcParameterConverter>>();
var parameterConverter = new DefaultRpcParameterConverter(options.Object, logger2.Object);
return new DefaultRpcInvoker(logger.Object, options.Object, matcher.Object, accessor.Object, authHandler.Object, parameterConverter);
return new DefaultRpcInvoker(logger, options.Object, matcher.Object, accessor.Object, authHandler.Object, parameterConverter);
}

private IServiceProvider GetServiceProvider()
Expand Down Expand Up @@ -627,5 +634,62 @@ public int Test()
public class TestInjectionClass
{

}

public class XunitLogger<T> : ILogger<T>, IDisposable
{
private ITestOutputHelper _output;

public XunitLogger(ITestOutputHelper output)
{
this._output = output;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(formatter(state, exception));
foreach (var item in this.context)
{
if (item is IEnumerable <KeyValuePair<string, object>> col)
{
foreach(var c in col)
sb.AppendLine(c.Key + ": "+ System.Text.Json.JsonSerializer.Serialize(c.Value));
} else
{
sb.AppendLine(System.Text.Json.JsonSerializer.Serialize(item));
}
}
this._output.WriteLine(sb.ToString());
}

public bool IsEnabled(LogLevel logLevel)
{
return true;
}
List<object> context = new();
public IDisposable BeginScope<TState>(TState state)
{
this.context.Add(state);
return new DisposableAction(() => this.context.Remove(state));
}

public void Dispose()
{
}

class DisposableAction: IDisposable
{
private readonly Action disp;

public DisposableAction(Action disp)
{
this.disp = disp;
}

public void Dispose()
{
disp();
}
}
}
}

0 comments on commit 261bb51

Please sign in to comment.