Browse Source

Merge branch 'master' into feature/measure-traffic

# Conflicts:
#	backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs
pull/491/head
Sebastian 6 years ago
parent
commit
2b6ea96a30
  1. 3
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  2. 26
      backend/src/Squidex.Infrastructure/Log/Adapter/SemanticLogLogger.cs
  3. 2
      backend/src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs
  4. 2
      backend/src/Squidex.Infrastructure/Log/ConstantsLogWriter.cs
  5. 4
      backend/src/Squidex.Infrastructure/Log/ILogAppender.cs
  6. 8
      backend/src/Squidex.Infrastructure/Log/ISemanticLog.cs
  7. 57
      backend/src/Squidex.Infrastructure/Log/SemanticLog.cs
  8. 122
      backend/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs
  9. 3
      backend/src/Squidex.Infrastructure/Log/TimestampLogAppender.cs
  10. 6
      backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs
  11. 2
      backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs
  12. 31
      backend/src/Squidex.Web/Pipeline/DefaultExceptionHandler.cs
  13. 17
      backend/src/Squidex.Web/Pipeline/IExceptionHandler.cs
  14. 11
      backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs
  15. 3
      backend/src/Squidex/Config/Web/WebServices.cs
  16. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/Notifications/NotificationEmailEventConsumerTests.cs
  17. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/Notifications/NotificationEmailSenderTests.cs
  18. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs
  19. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs
  20. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs
  21. 7
      backend/tests/Squidex.Infrastructure.Tests/Commands/LogCommandMiddlewareTests.cs
  22. 25
      backend/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs
  23. 28
      backend/tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs
  24. 2
      backend/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs
  25. 6
      backend/tests/Squidex.Infrastructure.Tests/Orleans/LoggingFilterTests.cs
  26. 26
      backend/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs

3
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs

@ -8,7 +8,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Confluent.Kafka; using Confluent.Kafka;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
namespace Squidex.Extensions.Actions.Kafka namespace Squidex.Extensions.Actions.Kafka
@ -65,7 +64,7 @@ namespace Squidex.Extensions.Actions.Kafka
break; break;
} }
log.Log<None>(level, default, (_, w) => w log.Log(level, null, w => w
.WriteProperty("action", "KafkaAction") .WriteProperty("action", "KafkaAction")
.WriteProperty("name", message.Name) .WriteProperty("name", message.Name)
.WriteProperty("message", message.Message)); .WriteProperty("message", message.Message));

26
backend/src/Squidex.Infrastructure/Log/Adapter/SemanticLogLogger.cs

@ -49,9 +49,20 @@ namespace Squidex.Infrastructure.Log.Adapter
break; break;
} }
if (state is IReadOnlyList<KeyValuePair<string, object>> parameters)
{
foreach (var (key, value) in parameters)
{
if (value is Exception ex && exception == null)
{
exception = ex;
}
}
}
var context = (eventId, state, exception, formatter); var context = (eventId, state, exception, formatter);
semanticLog.Log(semanticLogLevel, context, (ctx, writer) => semanticLog.Log(semanticLogLevel, context, exception, (ctx, writer) =>
{ {
var message = ctx.formatter(ctx.state, ctx.exception); var message = ctx.formatter(ctx.state, ctx.exception);
@ -73,26 +84,23 @@ namespace Squidex.Infrastructure.Log.Adapter
}); });
} }
if (ctx.state is IReadOnlyList<KeyValuePair<string, object>> parameters) if (ctx.state is IReadOnlyList<KeyValuePair<string, object>> parameters2)
{ {
foreach (var (key, value) in parameters) foreach (var (key, value) in parameters2)
{ {
if (value != null) if (value != null)
{ {
var trimmedName = key.Trim('{', '}', ' '); var trimmedName = key.Trim('{', '}', ' ');
if (trimmedName.Length > 2 && !string.Equals(trimmedName, "originalFormat", StringComparison.OrdinalIgnoreCase)) if (trimmedName.Length > 2 &&
!string.Equals(trimmedName, "exception", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(trimmedName, "originalFormat", StringComparison.OrdinalIgnoreCase))
{ {
writer.WriteProperty(trimmedName.ToCamelCase(), value.ToString()); writer.WriteProperty(trimmedName.ToCamelCase(), value.ToString());
} }
} }
} }
} }
if (ctx.exception != null)
{
writer.WriteException(ctx.exception);
}
}); });
} }

2
backend/src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs

@ -30,7 +30,7 @@ namespace Squidex.Infrastructure.Log
applicationSessionId = applicationSession.ToString(); applicationSessionId = applicationSession.ToString();
} }
public void Append(IObjectWriter writer, SemanticLogLevel logLevel) public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{ {
writer.WriteObject("app", w => w writer.WriteObject("app", w => w
.WriteProperty("name", applicationName) .WriteProperty("name", applicationName)

2
backend/src/Squidex.Infrastructure/Log/ConstantsLogWriter.cs

@ -20,7 +20,7 @@ namespace Squidex.Infrastructure.Log
this.objectWriter = objectWriter; this.objectWriter = objectWriter;
} }
public void Append(IObjectWriter writer, SemanticLogLevel logLevel) public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{ {
objectWriter(writer); objectWriter(writer);
} }

4
backend/src/Squidex.Infrastructure/Log/ILogAppender.cs

@ -5,10 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
namespace Squidex.Infrastructure.Log namespace Squidex.Infrastructure.Log
{ {
public interface ILogAppender public interface ILogAppender
{ {
void Append(IObjectWriter writer, SemanticLogLevel logLevel); void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception);
} }
} }

8
backend/src/Squidex.Infrastructure/Log/ISemanticLog.cs

@ -9,9 +9,15 @@ using System;
namespace Squidex.Infrastructure.Log namespace Squidex.Infrastructure.Log
{ {
public delegate void LogFormatter(IObjectWriter writer);
public delegate void LogFormatter<T>(T context, IObjectWriter writer);
public interface ISemanticLog public interface ISemanticLog
{ {
void Log<T>(SemanticLogLevel logLevel, T context, Action<T, IObjectWriter> action); void Log<T>(SemanticLogLevel logLevel, T context, Exception? exception, LogFormatter<T> action);
void Log(SemanticLogLevel logLevel, Exception? exception, LogFormatter action);
ISemanticLog CreateScope(Action<IObjectWriter> objectWriter); ISemanticLog CreateScope(Action<IObjectWriter> objectWriter);
} }

57
backend/src/Squidex.Infrastructure/Log/SemanticLog.cs

@ -30,13 +30,13 @@ namespace Squidex.Infrastructure.Log
Guard.NotNull(appenders); Guard.NotNull(appenders);
Guard.NotNull(writerFactory); Guard.NotNull(writerFactory);
this.options = options;
this.channels = channels.ToArray(); this.channels = channels.ToArray();
this.appenders = appenders.ToArray(); this.appenders = appenders.ToArray();
this.options = options;
this.writerFactory = writerFactory; this.writerFactory = writerFactory;
} }
public void Log<T>(SemanticLogLevel logLevel, T context, Action<T, IObjectWriter> action) public void Log<T>(SemanticLogLevel logLevel, T context, Exception? exception, LogFormatter<T> action)
{ {
Guard.NotNull(action); Guard.NotNull(action);
@ -45,7 +45,21 @@ namespace Squidex.Infrastructure.Log
return; return;
} }
var formattedText = FormatText(logLevel, context, action); var formattedText = FormatText(logLevel, context, exception, action);
LogFormattedText(logLevel, formattedText);
}
public void Log(SemanticLogLevel logLevel, Exception? exception, LogFormatter action)
{
Guard.NotNull(action);
if (logLevel < options.Value.Level)
{
return;
}
var formattedText = FormatText(logLevel, exception, action);
LogFormattedText(logLevel, formattedText); LogFormattedText(logLevel, formattedText);
} }
@ -77,7 +91,7 @@ namespace Squidex.Infrastructure.Log
} }
} }
private string FormatText<T>(SemanticLogLevel logLevel, T context, Action<T, IObjectWriter> objectWriter) private string FormatText(SemanticLogLevel logLevel, Exception? exception, LogFormatter action)
{ {
var writer = writerFactory.Create(); var writer = writerFactory.Create();
@ -85,13 +99,40 @@ namespace Squidex.Infrastructure.Log
{ {
writer.WriteProperty(nameof(logLevel), logLevel.ToString()); writer.WriteProperty(nameof(logLevel), logLevel.ToString());
objectWriter(context, writer); action(writer);
for (var i = 0; i < appenders.Length; i++) for (var i = 0; i < appenders.Length; i++)
{ {
appenders[i].Append(writer, logLevel); appenders[i].Append(writer, logLevel, exception);
} }
writer.WriteException(exception);
return writer.ToString();
}
finally
{
writerFactory.Release(writer);
}
}
private string FormatText<T>(SemanticLogLevel logLevel, T context, Exception? exception, LogFormatter<T> action)
{
var writer = writerFactory.Create();
try
{
writer.WriteProperty(nameof(logLevel), logLevel.ToString());
action(context, writer);
for (var i = 0; i < appenders.Length; i++)
{
appenders[i].Append(writer, logLevel, exception);
}
writer.WriteException(exception);
return writer.ToString(); return writer.ToString();
} }
finally finally
@ -102,7 +143,9 @@ namespace Squidex.Infrastructure.Log
public ISemanticLog CreateScope(Action<IObjectWriter> objectWriter) public ISemanticLog CreateScope(Action<IObjectWriter> objectWriter)
{ {
return new SemanticLog(options, channels, appenders.Union(new ILogAppender[] { new ConstantsLogWriter(objectWriter) }).ToArray(), writerFactory); var newAppenders = appenders.Union(Enumerable.Repeat(new ConstantsLogWriter(objectWriter), 1));
return new SemanticLog(options, channels, newAppenders, writerFactory);
} }
} }
} }

122
backend/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs

@ -11,114 +11,94 @@ namespace Squidex.Infrastructure.Log
{ {
public static class SemanticLogExtensions public static class SemanticLogExtensions
{ {
public static void LogTrace(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static void LogTrace(this ISemanticLog log, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Trace, None.Value, (_, w) => objectWriter(w)); log.Log(SemanticLogLevel.Trace, null, action);
} }
public static void LogTrace<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static void LogTrace<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Trace, context, objectWriter); log.Log(SemanticLogLevel.Trace, context, null, action);
} }
public static void LogDebug(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static void LogDebug(this ISemanticLog log, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Debug, None.Value, (_, w) => objectWriter(w)); log.Log(SemanticLogLevel.Debug, null, action);
} }
public static void LogDebug<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static void LogDebug<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Debug, context, objectWriter); log.Log(SemanticLogLevel.Debug, context, null, action);
} }
public static void LogInformation(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static void LogInformation(this ISemanticLog log, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Information, None.Value, (_, w) => objectWriter(w)); log.Log(SemanticLogLevel.Information, null, action);
} }
public static void LogInformation<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static void LogInformation<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Information, context, objectWriter); log.Log(SemanticLogLevel.Information, context, null, action);
} }
public static void LogWarning(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static void LogWarning(this ISemanticLog log, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Warning, None.Value, (_, w) => objectWriter(w)); log.Log(SemanticLogLevel.Warning, null, action);
} }
public static void LogWarning<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static void LogWarning<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Warning, context, objectWriter); log.Log(SemanticLogLevel.Warning, context, null, action);
} }
public static void LogWarning(this ISemanticLog log, Exception exception, Action<IObjectWriter>? objectWriter = null) public static void LogWarning(this ISemanticLog log, Exception exception, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Warning, None.Value, (_, w) => w.WriteException(exception, objectWriter)); log.Log(SemanticLogLevel.Warning, exception, action);
} }
public static void LogWarning<T>(this ISemanticLog log, Exception exception, T context, Action<T, IObjectWriter>? objectWriter = null) public static void LogWarning<T>(this ISemanticLog log, Exception exception, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Warning, context, (ctx, w) => w.WriteException(exception, ctx, objectWriter)); log.Log(SemanticLogLevel.Warning, context, exception, action);
} }
public static void LogError(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static void LogError(this ISemanticLog log, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Error, None.Value, (_, w) => objectWriter(w)); log.Log(SemanticLogLevel.Error, null, action);
} }
public static void LogError<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static void LogError<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Error, context, objectWriter); log.Log(SemanticLogLevel.Error, context, null, action);
} }
public static void LogError(this ISemanticLog log, Exception exception, Action<IObjectWriter>? objectWriter = null) public static void LogError(this ISemanticLog log, Exception exception, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Error, None.Value, (_, w) => w.WriteException(exception, objectWriter)); log.Log(SemanticLogLevel.Error, exception, action);
} }
public static void LogError<T>(this ISemanticLog log, Exception exception, T context, Action<T, IObjectWriter>? objectWriter = null) public static void LogError<T>(this ISemanticLog log, Exception exception, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Error, context, (ctx, w) => w.WriteException(exception, ctx, objectWriter)); log.Log(SemanticLogLevel.Error, context, exception, action);
} }
public static void LogFatal(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static void LogFatal(this ISemanticLog log, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Fatal, None.Value, (_, w) => objectWriter(w)); log.Log(SemanticLogLevel.Fatal, null, action);
} }
public static void LogFatal<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static void LogFatal<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Fatal, context, objectWriter); log.Log(SemanticLogLevel.Fatal, context, null, action);
} }
public static void LogFatal(this ISemanticLog log, Exception? exception, Action<IObjectWriter>? objectWriter = null) public static void LogFatal(this ISemanticLog log, Exception? exception, LogFormatter action)
{ {
log.Log(SemanticLogLevel.Fatal, None.Value, (_, w) => w.WriteException(exception, objectWriter)); log.Log(SemanticLogLevel.Fatal, exception, action);
} }
public static void LogFatal<T>(this ISemanticLog log, Exception? exception, T context, Action<T, IObjectWriter>? objectWriter = null) public static void LogFatal<T>(this ISemanticLog log, Exception? exception, T context, LogFormatter<T> action)
{ {
log.Log(SemanticLogLevel.Fatal, context, (ctx, w) => w.WriteException(exception, ctx, objectWriter)); log.Log(SemanticLogLevel.Fatal, context, exception, action);
}
private static void WriteException(this IObjectWriter writer, Exception? exception, Action<IObjectWriter>? objectWriter)
{
objectWriter?.Invoke(writer);
if (exception != null)
{
writer.WriteException(exception);
}
}
private static void WriteException<T>(this IObjectWriter writer, Exception? exception, T context, Action<T, IObjectWriter>? objectWriter)
{
objectWriter?.Invoke(context, writer);
if (exception != null)
{
writer.WriteException(exception);
}
} }
public static IObjectWriter WriteException(this IObjectWriter writer, Exception? exception) public static IObjectWriter WriteException(this IObjectWriter writer, Exception? exception)
@ -144,37 +124,37 @@ namespace Squidex.Infrastructure.Log
}); });
} }
public static IDisposable MeasureTrace(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static IDisposable MeasureTrace(this ISemanticLog log, LogFormatter action)
{ {
return log.Measure(SemanticLogLevel.Trace, None.Value, (_, w) => objectWriter(w)); return log.Measure(SemanticLogLevel.Trace, None.Value, (_, w) => action(w));
} }
public static IDisposable MeasureTrace<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static IDisposable MeasureTrace<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
return log.Measure(SemanticLogLevel.Trace, context, objectWriter); return log.Measure(SemanticLogLevel.Trace, context, action);
} }
public static IDisposable MeasureDebug(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static IDisposable MeasureDebug(this ISemanticLog log, LogFormatter action)
{ {
return log.Measure(SemanticLogLevel.Debug, None.Value, (_, w) => objectWriter(w)); return log.Measure(SemanticLogLevel.Debug, None.Value, (_, w) => action(w));
} }
public static IDisposable MeasureDebug<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static IDisposable MeasureDebug<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
return log.Measure(SemanticLogLevel.Debug, context, objectWriter); return log.Measure(SemanticLogLevel.Debug, context, action);
} }
public static IDisposable MeasureInformation(this ISemanticLog log, Action<IObjectWriter> objectWriter) public static IDisposable MeasureInformation(this ISemanticLog log, LogFormatter action)
{ {
return log.Measure(SemanticLogLevel.Information, None.Value, (_, w) => objectWriter(w)); return log.Measure(SemanticLogLevel.Information, None.Value, (_, w) => action(w));
} }
public static IDisposable MeasureInformation<T>(this ISemanticLog log, T context, Action<T, IObjectWriter> objectWriter) public static IDisposable MeasureInformation<T>(this ISemanticLog log, T context, LogFormatter<T> action)
{ {
return log.Measure(SemanticLogLevel.Information, context, objectWriter); return log.Measure(SemanticLogLevel.Information, context, action);
} }
private static IDisposable Measure<T>(this ISemanticLog log, SemanticLogLevel logLevel, T context, Action<T, IObjectWriter> objectWriter) private static IDisposable Measure<T>(this ISemanticLog log, SemanticLogLevel logLevel, T context, LogFormatter<T> action)
{ {
var watch = ValueStopwatch.StartNew(); var watch = ValueStopwatch.StartNew();
@ -182,9 +162,9 @@ namespace Squidex.Infrastructure.Log
{ {
var elapsedMs = watch.Stop(); var elapsedMs = watch.Stop();
log.Log(logLevel, (Context: context, elapsedMs), (ctx, w) => log.Log(logLevel, (Context: context, elapsedMs), null, (ctx, w) =>
{ {
objectWriter?.Invoke(ctx.Context, w); action?.Invoke(ctx.Context, w);
w.WriteProperty("elapsedMs", elapsedMs); w.WriteProperty("elapsedMs", elapsedMs);
}); });

3
backend/src/Squidex.Infrastructure/Log/TimestampLogAppender.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using NodaTime; using NodaTime;
namespace Squidex.Infrastructure.Log namespace Squidex.Infrastructure.Log
@ -18,7 +19,7 @@ namespace Squidex.Infrastructure.Log
this.clock = clock ?? SystemClock.Instance; this.clock = clock ?? SystemClock.Instance;
} }
public void Append(IObjectWriter writer, SemanticLogLevel logLevel) public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{ {
writer.WriteProperty("timestamp", clock.GetCurrentInstant()); writer.WriteProperty("timestamp", clock.GetCurrentInstant());
} }

6
backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs

@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Web.Pipeline; using Squidex.Infrastructure.Log;
namespace Squidex.Web namespace Squidex.Web
{ {
@ -33,9 +33,9 @@ namespace Squidex.Web
if (!wellKnown) if (!wellKnown)
{ {
var exceptionHandler = context.HttpContext.RequestServices.GetService<IExceptionHandler>(); var log = context.HttpContext.RequestServices.GetService<ISemanticLog>();
exceptionHandler.Handle(context.Exception, context.HttpContext); log.LogError(context.Exception, w => w.WriteProperty("messag", "An unexpected exception has occurred."));
} }
context.Result = GetResult(error); context.Result = GetResult(error);

2
backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs

@ -29,7 +29,7 @@ namespace Squidex.Web.Pipeline
this.httpContextAccessor = httpContextAccessor; this.httpContextAccessor = httpContextAccessor;
} }
public void Append(IObjectWriter writer, SemanticLogLevel logLevel) public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{ {
var httpContext = httpContextAccessor.HttpContext; var httpContext = httpContextAccessor.HttpContext;

31
backend/src/Squidex.Web/Pipeline/DefaultExceptionHandler.cs

@ -1,31 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
namespace Squidex.Web.Pipeline
{
public class DefaultExceptionHandler : IExceptionHandler
{
private readonly ISemanticLog log;
public DefaultExceptionHandler(ISemanticLog log)
{
Guard.NotNull(log);
this.log = log;
}
public virtual void Handle(Exception exception, HttpContext? httpContext = null)
{
log.LogError(exception, w => w.WriteProperty("status", "UnhandledException"));
}
}
}

17
backend/src/Squidex.Web/Pipeline/IExceptionHandler.cs

@ -1,17 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.AspNetCore.Http;
namespace Squidex.Web.Pipeline
{
public interface IExceptionHandler
{
void Handle(Exception exception, HttpContext? httpContext = null);
}
}

11
backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs

@ -9,18 +9,19 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
namespace Squidex.Web.Pipeline namespace Squidex.Web.Pipeline
{ {
public sealed class RequestExceptionMiddleware : IMiddleware public sealed class RequestExceptionMiddleware : IMiddleware
{ {
private readonly IExceptionHandler exceptionHandler; private readonly ISemanticLog log;
public RequestExceptionMiddleware(IExceptionHandler exceptionHandler) public RequestExceptionMiddleware(ISemanticLog log)
{ {
Guard.NotNull(exceptionHandler); Guard.NotNull(log);
this.exceptionHandler = exceptionHandler; this.log = log;
} }
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
@ -31,7 +32,7 @@ namespace Squidex.Web.Pipeline
} }
catch (Exception ex) catch (Exception ex)
{ {
exceptionHandler.Handle(ex, context); log.LogError(ex, w => w.WriteProperty("messag", "An unexpected exception has occurred."));
context.Response.StatusCode = 500; context.Response.StatusCode = 500;
} }

3
backend/src/Squidex/Config/Web/WebServices.cs

@ -67,9 +67,6 @@ namespace Squidex.Config.Web
services.AddSingletonAs<ActionContextAccessor>() services.AddSingletonAs<ActionContextAccessor>()
.As<IActionContextAccessor>(); .As<IActionContextAccessor>();
services.AddSingletonAs<DefaultExceptionHandler>()
.AsOptional<IExceptionHandler>();
services.Configure<ApiBehaviorOptions>(options => services.Configure<ApiBehaviorOptions>(options =>
{ {
options.SuppressModelStateInvalidFilter = true; options.SuppressModelStateInvalidFilter = true;

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/Notifications/NotificationEmailEventConsumerTests.cs

@ -153,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation.Notifications
private void MustLogWarning() private void MustLogWarning()
{ {
A.CallTo(() => log.Log(SemanticLogLevel.Warning, A<None>._, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/Notifications/NotificationEmailSenderTests.cs

@ -11,7 +11,6 @@ using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Email; using Squidex.Infrastructure.Email;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
@ -137,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation.Notifications
private void MustLogWarning() private void MustLogWarning()
{ {
A.CallTo(() => log.Log(SemanticLogLevel.Warning, A<None>._, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs

@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
A.CallTo(() => commandBus.PublishAsync(A<DeleteAsset>.That.Matches(x => x.AssetId == childId2))) A.CallTo(() => commandBus.PublishAsync(A<DeleteAsset>.That.Matches(x => x.AssetId == childId2)))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>.Ignored)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs

@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
await sut.QueryAsync(); await sut.QueryAsync();
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
await sut.HandleAsync(@event); await sut.HandleAsync(@event);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs

@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Search
result.Should().BeEquivalentTo(result2); result.Should().BeEquivalentTo(result2);
A.CallTo(() => log.Log(SemanticLogLevel.Error, query, A<Action<string, IObjectWriter>>._)) A.CallTo(() => log.Log<string>(A<SemanticLogLevel>._, A<string>._, A<Exception?>._, A<LogFormatter<string>>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

7
backend/tests/Squidex.Infrastructure.Tests/Commands/LogCommandMiddlewareTests.cs

@ -25,7 +25,12 @@ namespace Squidex.Infrastructure.Commands
{ {
public Dictionary<SemanticLogLevel, int> LogLevels { get; } = new Dictionary<SemanticLogLevel, int>(); public Dictionary<SemanticLogLevel, int> LogLevels { get; } = new Dictionary<SemanticLogLevel, int>();
public void Log<T>(SemanticLogLevel logLevel, T context, Action<T, IObjectWriter> action) public void Log<T>(SemanticLogLevel logLevel, T context, Exception? exception, LogFormatter<T> action)
{
LogLevels[logLevel] = LogLevels.GetOrDefault(logLevel) + 1;
}
public void Log(SemanticLogLevel logLevel, Exception? exception, LogFormatter action)
{ {
LogLevels[logLevel] = LogLevels.GetOrDefault(logLevel) + 1; LogLevels[logLevel] = LogLevels.GetOrDefault(logLevel) + 1;
} }

25
backend/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs

@ -126,14 +126,33 @@ namespace Squidex.Infrastructure.Log
var logger = sut.CreateLogger("my-category"); var logger = sut.CreateLogger("my-category");
logger.Log(LogLevel.Debug, new EventId(0), 1, exception, (x, e) => "my-message"); logger.Log(LogLevel.Debug, new EventId(0), exception, "my-message");
var expected = var expected =
MakeTestCall(w => w MakeTestCall(w => w
.WriteProperty("logLevel", "Debug") .WriteProperty("logLevel", "Debug")
.WriteProperty("message", "my-message") .WriteProperty("message", "my-message")
.WriteException(exception) .WriteProperty("category", "my-category")
.WriteProperty("category", "my-category")); .WriteException(exception));
Assert.Equal(expected, output);
}
[Fact]
public void Should_log_message_with_integrated_exception()
{
var exception = new InvalidOperationException();
var logger = sut.CreateLogger("my-category");
logger.Log(LogLevel.Debug, new EventId(0), "exception: {exception}", exception);
var expected =
MakeTestCall(w => w
.WriteProperty("logLevel", "Debug")
.WriteProperty("message", $"exception: {exception}")
.WriteProperty("category", "my-category")
.WriteException(exception));
Assert.Equal(expected, output); Assert.Equal(expected, output);
} }

28
backend/tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs

@ -49,8 +49,8 @@ namespace Squidex.Infrastructure.Log
[Fact] [Fact]
public void Should_log_multiple_lines() public void Should_log_multiple_lines()
{ {
Log.Log(SemanticLogLevel.Error, None.Value, (_, w) => w.WriteProperty("logMessage", "Msg1")); Log.Log(SemanticLogLevel.Error, null, w => w.WriteProperty("logMessage", "Msg1"));
Log.Log(SemanticLogLevel.Error, None.Value, (_, w) => w.WriteProperty("logMessage", "Msg2")); Log.Log(SemanticLogLevel.Error, null, w => w.WriteProperty("logMessage", "Msg2"));
var expected1 = var expected1 =
LogTest(w => w LogTest(w => w
@ -229,7 +229,7 @@ namespace Squidex.Infrastructure.Log
{ {
var exception = new InvalidOperationException(); var exception = new InvalidOperationException();
Log.LogWarning(exception); Log.LogWarning(exception, w => { });
var expected = var expected =
LogTest(w => w LogTest(w => w
@ -286,7 +286,7 @@ namespace Squidex.Infrastructure.Log
{ {
var exception = new InvalidOperationException(); var exception = new InvalidOperationException();
Log.LogError(exception); Log.LogError(exception, w => { });
var expected = var expected =
LogTest(w => w LogTest(w => w
@ -343,7 +343,7 @@ namespace Squidex.Infrastructure.Log
{ {
var exception = new InvalidOperationException(); var exception = new InvalidOperationException();
Log.LogFatal(exception); Log.LogFatal(exception, w => { });
var expected = var expected =
LogTest(w => w LogTest(w => w
@ -369,18 +369,6 @@ namespace Squidex.Infrastructure.Log
Assert.Equal(expected, output); Assert.Equal(expected, output);
} }
[Fact]
public void Should_log_nothing_when_exception_is_null()
{
Log.LogFatal((Exception?)null);
var expected =
LogTest(w => w
.WriteProperty("logLevel", "Fatal"));
Assert.Equal(expected, output);
}
[Fact] [Fact]
public void Should_measure_trace() public void Should_measure_trace()
{ {
@ -484,8 +472,8 @@ namespace Squidex.Infrastructure.Log
.WriteObject("eventId", e => e .WriteObject("eventId", e => e
.WriteProperty("id", 123) .WriteProperty("id", 123)
.WriteProperty("name", "EventName")) .WriteProperty("name", "EventName"))
.WriteException(exception) .WriteProperty("category", "Squidex.Infrastructure.Log.SemanticLogTests")
.WriteProperty("category", "Squidex.Infrastructure.Log.SemanticLogTests")); .WriteException(exception));
Assert.Equal(expected, output); Assert.Equal(expected, output);
} }
@ -506,7 +494,7 @@ namespace Squidex.Infrastructure.Log
try try
{ {
sut.Log(SemanticLogLevel.Debug, None.Value, (_, w) => w.WriteProperty("should", "throw")); sut.Log(SemanticLogLevel.Debug, null, w => w.WriteProperty("should", "throw"));
Assert.False(true); Assert.False(true);
} }

2
backend/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs

@ -131,7 +131,7 @@ namespace Squidex.Infrastructure.Migrations
await Assert.ThrowsAsync<MigrationFailedException>(() => sut.MigrateAsync()); await Assert.ThrowsAsync<MigrationFailedException>(() => sut.MigrateAsync());
A.CallTo(() => log.Log(SemanticLogLevel.Fatal, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(SemanticLogLevel.Fatal, ex, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => migrator_1_2.UpdateAsync()) A.CallTo(() => migrator_1_2.UpdateAsync())

6
backend/tests/Squidex.Infrastructure.Tests/Orleans/LoggingFilterTests.cs

@ -31,7 +31,7 @@ namespace Squidex.Infrastructure.Orleans
{ {
await sut.Invoke(context); await sut.Invoke(context);
A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<None>._, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -43,7 +43,7 @@ namespace Squidex.Infrastructure.Orleans
await Assert.ThrowsAsync<ValidationException>(() => sut.Invoke(context)); await Assert.ThrowsAsync<ValidationException>(() => sut.Invoke(context));
A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<None>._, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -55,7 +55,7 @@ namespace Squidex.Infrastructure.Orleans
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.Invoke(context)); await Assert.ThrowsAsync<InvalidOperationException>(() => sut.Invoke(context));
A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<None>._, A<Action<None, IObjectWriter>>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

26
backend/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs

@ -17,15 +17,15 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Web.Pipeline;
using Xunit; using Xunit;
namespace Squidex.Web namespace Squidex.Web
{ {
public class ApiExceptionFilterAttributeTests public class ApiExceptionFilterAttributeTests
{ {
private readonly IExceptionHandler exceptionHandler = A.Fake<IExceptionHandler>(); private readonly ISemanticLog log = A.Fake<ISemanticLog>();
private readonly ApiExceptionFilterAttribute sut = new ApiExceptionFilterAttribute(); private readonly ApiExceptionFilterAttribute sut = new ApiExceptionFilterAttribute();
[Fact] [Fact]
@ -49,7 +49,7 @@ namespace Squidex.Web
Assert.Equal(new[] { "Error1", "P: Error2", "P1, P2: Error3" }, ((ErrorDto)result.Value).Details); Assert.Equal(new[] { "Error1", "P: Error2", "P1, P2: Error3" }, ((ErrorDto)result.Value).Details);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -62,7 +62,7 @@ namespace Squidex.Web
Assert.IsType<NotFoundResult>(context.Result); Assert.IsType<NotFoundResult>(context.Result);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -75,7 +75,7 @@ namespace Squidex.Web
Validate(500, context.Result, null); Validate(500, context.Result, null);
A.CallTo(() => exceptionHandler.Handle(context.Exception, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, context.Exception, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -88,7 +88,7 @@ namespace Squidex.Web
Validate(400, context.Result, context.Exception); Validate(400, context.Result, context.Exception);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -101,7 +101,7 @@ namespace Squidex.Web
Validate(400, context.Result, context.Exception); Validate(400, context.Result, context.Exception);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -114,7 +114,7 @@ namespace Squidex.Web
Validate(412, context.Result, context.Exception); Validate(412, context.Result, context.Exception);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -127,7 +127,7 @@ namespace Squidex.Web
Validate(403, context.Result, context.Exception); Validate(403, context.Result, context.Exception);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -140,7 +140,7 @@ namespace Squidex.Web
Validate(403, context.Result, null); Validate(403, context.Result, null);
A.CallTo(() => exceptionHandler.Handle(context.Exception, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, context.Exception, A<LogFormatter>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -153,7 +153,7 @@ namespace Squidex.Web
Validate(403, context.Result, null); Validate(403, context.Result, null);
A.CallTo(() => exceptionHandler.Handle(A<Exception>._, A<HttpContext>._)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<Exception?>._, A<LogFormatter>._!))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -189,8 +189,8 @@ namespace Squidex.Web
{ {
var services = A.Fake<IServiceProvider>(); var services = A.Fake<IServiceProvider>();
A.CallTo(() => services.GetService(typeof(IExceptionHandler))) A.CallTo(() => services.GetService(typeof(ISemanticLog)))
.Returns(exceptionHandler); .Returns(log);
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {

Loading…
Cancel
Save