mirror of https://github.com/Squidex/squidex.git
44 changed files with 1664 additions and 195 deletions
@ -1,19 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// RedisInfrastructureErrors.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.Extensions.Logging; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.Redis |
|
||||
{ |
|
||||
public static class RedisInfrastructureErrors |
|
||||
{ |
|
||||
public static readonly EventId InvalidatingReceivedFailed = new EventId(50001, "InvalidingReceivedFailed"); |
|
||||
|
|
||||
public static readonly EventId InvalidatingPublishedFailed = new EventId(50002, "InvalidatingPublishedFailed"); |
|
||||
} |
|
||||
} |
|
||||
@ -1,25 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// InfrastructureErrors.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.Extensions.Logging; |
|
||||
|
|
||||
namespace Squidex.Infrastructure |
|
||||
{ |
|
||||
public class InfrastructureErrors |
|
||||
{ |
|
||||
public static readonly EventId CommandUnknown = new EventId(20000, "CommandUnknown"); |
|
||||
|
|
||||
public static readonly EventId CommandFailed = new EventId(20001, "CommandFailed"); |
|
||||
|
|
||||
public static readonly EventId EventResetFailed = new EventId(10000, "EventResetFailed"); |
|
||||
|
|
||||
public static readonly EventId EventHandlingFailed = new EventId(10001, "EventHandlingFailed"); |
|
||||
|
|
||||
public static readonly EventId EventDeserializationFailed = new EventId(10002, "EventDeserializationFailed"); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,102 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLogLogger.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
// ReSharper disable SwitchStatementMissingSomeCases
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Adapter |
||||
|
{ |
||||
|
internal sealed class SemanticLogLogger : ILogger |
||||
|
{ |
||||
|
private readonly ISemanticLog semanticLog; |
||||
|
|
||||
|
public SemanticLogLogger(ISemanticLog semanticLog) |
||||
|
{ |
||||
|
this.semanticLog = semanticLog; |
||||
|
} |
||||
|
|
||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) |
||||
|
{ |
||||
|
SemanticLogLevel semanticLogLevel; |
||||
|
|
||||
|
switch (logLevel) |
||||
|
{ |
||||
|
case LogLevel.Trace: |
||||
|
semanticLogLevel = SemanticLogLevel.Trace; |
||||
|
break; |
||||
|
case LogLevel.Debug: |
||||
|
semanticLogLevel = SemanticLogLevel.Debug; |
||||
|
break; |
||||
|
case LogLevel.Information: |
||||
|
semanticLogLevel = SemanticLogLevel.Information; |
||||
|
break; |
||||
|
case LogLevel.Warning: |
||||
|
semanticLogLevel = SemanticLogLevel.Warning; |
||||
|
break; |
||||
|
case LogLevel.Error: |
||||
|
semanticLogLevel = SemanticLogLevel.Error; |
||||
|
break; |
||||
|
case LogLevel.Critical: |
||||
|
semanticLogLevel = SemanticLogLevel.Fatal; |
||||
|
break; |
||||
|
default: |
||||
|
semanticLogLevel = SemanticLogLevel.Debug; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
semanticLog.Log(semanticLogLevel, writer => |
||||
|
{ |
||||
|
var message = formatter(state, exception); |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(message)) |
||||
|
{ |
||||
|
writer.WriteProperty(nameof(message), message); |
||||
|
} |
||||
|
|
||||
|
if (eventId.Id > 0) |
||||
|
{ |
||||
|
writer.WriteObject(nameof(eventId), eventIdWriter => |
||||
|
{ |
||||
|
eventIdWriter.WriteProperty("id", eventId.Id); |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(eventId.Name)) |
||||
|
{ |
||||
|
eventIdWriter.WriteProperty("name", eventId.Name); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (exception != null) |
||||
|
{ |
||||
|
writer.WriteException(exception); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public bool IsEnabled(LogLevel logLevel) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public IDisposable BeginScope<TState>(TState state) |
||||
|
{ |
||||
|
return NoopDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
private class NoopDisposable : IDisposable |
||||
|
{ |
||||
|
public static readonly NoopDisposable Instance = new NoopDisposable(); |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLogLoggerFactoryExtensions.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Adapter |
||||
|
{ |
||||
|
public static class SemanticLogLoggerFactoryExtensions |
||||
|
{ |
||||
|
public static ILoggerFactory AddSemanticLog(this ILoggerFactory factory, ISemanticLog semanticLog) |
||||
|
{ |
||||
|
factory.AddProvider(new SemanticLogLoggerProvider(semanticLog)); |
||||
|
|
||||
|
return factory; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLogLoggerProvider.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Adapter |
||||
|
{ |
||||
|
public class SemanticLogLoggerProvider : ILoggerProvider |
||||
|
{ |
||||
|
private readonly ISemanticLog semanticLog; |
||||
|
|
||||
|
public SemanticLogLoggerProvider(ISemanticLog semanticLog) |
||||
|
{ |
||||
|
this.semanticLog = semanticLog; |
||||
|
} |
||||
|
|
||||
|
public ILogger CreateLogger(string categoryName) |
||||
|
{ |
||||
|
return new SemanticLogLogger(semanticLog.CreateScope(writer => |
||||
|
{ |
||||
|
writer.WriteProperty("category", categoryName); |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ApplicationInfoLogAppender.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class ApplicationInfoLogAppender : ILogAppender |
||||
|
{ |
||||
|
private readonly string applicationName; |
||||
|
private readonly string applicationVersion; |
||||
|
|
||||
|
public ApplicationInfoLogAppender(Assembly assembly) |
||||
|
{ |
||||
|
Guard.NotNull(assembly, nameof(assembly)); |
||||
|
|
||||
|
applicationName = assembly.GetName().Name; |
||||
|
applicationVersion = assembly.GetName().Version.ToString(); |
||||
|
} |
||||
|
|
||||
|
public void Append(IObjectWriter writer) |
||||
|
{ |
||||
|
writer.WriteProperty("applicationName", applicationName); |
||||
|
writer.WriteProperty("applicationVersion", applicationVersion); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConsoleLogChannel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Log.Internal; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class ConsoleLogChannel : ILogChannel, IDisposable |
||||
|
{ |
||||
|
private readonly ConsoleLogProcessor processor = new ConsoleLogProcessor(); |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
processor.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public void Log(SemanticLogLevel logLevel, string message) |
||||
|
{ |
||||
|
processor.EnqueueMessage(new LogMessageEntry { Message = message, Level = logLevel }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConstantsLogWriter.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class ConstantsLogWriter : ILogAppender |
||||
|
{ |
||||
|
private readonly Action<IObjectWriter> objectWriter; |
||||
|
|
||||
|
public ConstantsLogWriter(Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
Guard.NotNull(objectWriter, nameof(objectWriter)); |
||||
|
|
||||
|
this.objectWriter = objectWriter; |
||||
|
} |
||||
|
|
||||
|
public void Append(IObjectWriter writer) |
||||
|
{ |
||||
|
objectWriter(writer); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DebugLogChannel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class DebugLogChannel : ILogChannel |
||||
|
{ |
||||
|
public void Log(SemanticLogLevel logLevel, string message) |
||||
|
{ |
||||
|
Debug.WriteLine(message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IArrayWriter.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public interface IArrayWriter |
||||
|
{ |
||||
|
IArrayWriter WriteValue(string value); |
||||
|
IArrayWriter WriteValue(double value); |
||||
|
IArrayWriter WriteValue(long value); |
||||
|
IArrayWriter WriteValue(bool value); |
||||
|
|
||||
|
IArrayWriter WriteValue(TimeSpan value); |
||||
|
IArrayWriter WriteValue(DateTime value); |
||||
|
IArrayWriter WriteValue(DateTimeOffset value); |
||||
|
|
||||
|
IArrayWriter WriteObject(string property, Action<IObjectWriter> objectWriter); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ILogAppender.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public interface ILogAppender |
||||
|
{ |
||||
|
void Append(IObjectWriter writer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ILogChannel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public interface ILogChannel |
||||
|
{ |
||||
|
void Log(SemanticLogLevel logLevel, string message); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IObjectWriter.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public interface IObjectWriter |
||||
|
{ |
||||
|
IObjectWriter WriteProperty(string property, string value); |
||||
|
IObjectWriter WriteProperty(string property, double value); |
||||
|
IObjectWriter WriteProperty(string property, long value); |
||||
|
IObjectWriter WriteProperty(string property, bool value); |
||||
|
|
||||
|
IObjectWriter WriteProperty(string property, TimeSpan value); |
||||
|
IObjectWriter WriteProperty(string property, DateTime value); |
||||
|
IObjectWriter WriteProperty(string property, DateTimeOffset value); |
||||
|
|
||||
|
IObjectWriter WriteObject(string property, Action<IObjectWriter> objectWriter); |
||||
|
IObjectWriter WriteArray(string property, Action<IArrayWriter> arrayWriter); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ISemanticLog.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public interface ISemanticLog |
||||
|
{ |
||||
|
void Log(SemanticLogLevel logLevel, Action<IObjectWriter> action); |
||||
|
|
||||
|
ISemanticLog CreateScope(Action<IObjectWriter> objectWriter); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AnsiLogConsole.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Text; |
||||
|
|
||||
|
// ReSharper disable SwitchStatementMissingSomeCases
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Internal |
||||
|
{ |
||||
|
public class AnsiLogConsole : IConsole |
||||
|
{ |
||||
|
private readonly StringBuilder outputBuilder = new StringBuilder(); |
||||
|
|
||||
|
public void WriteLine(SemanticLogLevel level, string message) |
||||
|
{ |
||||
|
if (level >= SemanticLogLevel.Error) |
||||
|
{ |
||||
|
outputBuilder.Append("\x1B[1m\x1B[31m"); |
||||
|
outputBuilder.Append(message); |
||||
|
outputBuilder.Append("\x1B[39m\x1B[22m"); |
||||
|
outputBuilder.AppendLine(); |
||||
|
|
||||
|
Console.Error.Write(outputBuilder.ToString()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Console.Out.Write(outputBuilder.ToString()); |
||||
|
} |
||||
|
|
||||
|
outputBuilder.Clear(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConsoleLogProcessor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Internal |
||||
|
{ |
||||
|
public class ConsoleLogProcessor : IDisposable |
||||
|
{ |
||||
|
private readonly IConsole console; |
||||
|
private const int MaxQueuedMessages = 1024; |
||||
|
private readonly BlockingCollection<LogMessageEntry> messageQueue = new BlockingCollection<LogMessageEntry>(MaxQueuedMessages); |
||||
|
private readonly Task outputTask; |
||||
|
|
||||
|
public ConsoleLogProcessor() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
console = new WindowsLogConsole(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
console = new AnsiLogConsole(); |
||||
|
} |
||||
|
|
||||
|
outputTask = Task.Factory.StartNew(ProcessLogQueue, this, TaskCreationOptions.LongRunning); |
||||
|
} |
||||
|
|
||||
|
public void EnqueueMessage(LogMessageEntry message) |
||||
|
{ |
||||
|
messageQueue.Add(message); |
||||
|
} |
||||
|
|
||||
|
private void ProcessLogQueue() |
||||
|
{ |
||||
|
foreach (var entry in messageQueue.GetConsumingEnumerable()) |
||||
|
{ |
||||
|
console.WriteLine(entry.Level, entry.Message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ProcessLogQueue(object state) |
||||
|
{ |
||||
|
var processor = (ConsoleLogProcessor)state; |
||||
|
|
||||
|
processor.ProcessLogQueue(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
messageQueue.CompleteAdding(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
outputTask.Wait(1500); |
||||
|
} |
||||
|
catch (TaskCanceledException) |
||||
|
{ |
||||
|
} |
||||
|
catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IConsole.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
namespace Squidex.Infrastructure.Log.Internal |
||||
|
{ |
||||
|
public interface IConsole |
||||
|
{ |
||||
|
void WriteLine(SemanticLogLevel level, string message); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// LogMessageEntry.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
namespace Squidex.Infrastructure.Log.Internal |
||||
|
{ |
||||
|
public struct LogMessageEntry |
||||
|
{ |
||||
|
public SemanticLogLevel Level; |
||||
|
|
||||
|
public string Message; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// WindowsLogConsole.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Internal |
||||
|
{ |
||||
|
public class WindowsLogConsole : IConsole |
||||
|
{ |
||||
|
public void WriteLine(SemanticLogLevel level, string message) |
||||
|
{ |
||||
|
if (level >= SemanticLogLevel.Error) |
||||
|
{ |
||||
|
Console.ForegroundColor = ConsoleColor.Red; |
||||
|
Console.Error.WriteLine(message); |
||||
|
Console.ResetColor(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Console.Out.WriteLine(message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,185 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JsonLogWriter.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.IO; |
||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class JsonLogWriter : IObjectWriter, IArrayWriter |
||||
|
{ |
||||
|
private readonly bool extraLine; |
||||
|
private readonly StringWriter textWriter = new StringWriter(); |
||||
|
private readonly JsonWriter jsonWriter; |
||||
|
|
||||
|
public JsonLogWriter(Formatting formatting = Formatting.None, bool extraLine = false) |
||||
|
{ |
||||
|
this.extraLine = extraLine; |
||||
|
|
||||
|
jsonWriter = new JsonTextWriter(textWriter) { Formatting = formatting }; |
||||
|
jsonWriter.WriteStartObject(); |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(string value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(double value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(long value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(bool value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(DateTime value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value.ToString("o", CultureInfo.InvariantCulture)); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(DateTimeOffset value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value.ToString("o", CultureInfo.InvariantCulture)); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteValue(TimeSpan value) |
||||
|
{ |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, string value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, double value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, long value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, bool value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, DateTime value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value.ToString("o", CultureInfo.InvariantCulture)); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, DateTimeOffset value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value.ToString("o", CultureInfo.InvariantCulture)); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteProperty(string property, TimeSpan value) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteValue(value); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteObject(string property, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteStartObject(); |
||||
|
|
||||
|
objectWriter?.Invoke(this); |
||||
|
|
||||
|
jsonWriter.WriteEndObject(); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IObjectWriter IObjectWriter.WriteArray(string property, Action<IArrayWriter> arrayWriter) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteStartArray(); |
||||
|
|
||||
|
arrayWriter?.Invoke(this); |
||||
|
|
||||
|
jsonWriter.WriteEndArray(); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
IArrayWriter IArrayWriter.WriteObject(string property, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
jsonWriter.WritePropertyName(property); |
||||
|
jsonWriter.WriteStartObject(); |
||||
|
|
||||
|
objectWriter?.Invoke(this); |
||||
|
|
||||
|
jsonWriter.WriteEndObject(); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
jsonWriter.WriteEndObject(); |
||||
|
|
||||
|
var result = textWriter.ToString(); |
||||
|
|
||||
|
if (extraLine) |
||||
|
{ |
||||
|
result += Environment.NewLine; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLog.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class SemanticLog : ISemanticLog |
||||
|
{ |
||||
|
private readonly IEnumerable<ILogChannel> channels; |
||||
|
private readonly IEnumerable<ILogAppender> appenders; |
||||
|
private readonly Func<IObjectWriter> writerFactory; |
||||
|
|
||||
|
public SemanticLog( |
||||
|
IEnumerable<ILogChannel> channels, |
||||
|
IEnumerable<ILogAppender> appenders, |
||||
|
Func<IObjectWriter> writerFactory) |
||||
|
{ |
||||
|
Guard.NotNull(channels, nameof(channels)); |
||||
|
Guard.NotNull(appenders, nameof(appenders)); |
||||
|
|
||||
|
this.channels = channels; |
||||
|
this.appenders = appenders; |
||||
|
this.writerFactory = writerFactory; |
||||
|
} |
||||
|
|
||||
|
public void Log(SemanticLogLevel logLevel, Action<IObjectWriter> action) |
||||
|
{ |
||||
|
Guard.NotNull(action, nameof(action)); |
||||
|
|
||||
|
var formattedText = FormatText(logLevel, action); |
||||
|
|
||||
|
List<Exception> exceptions = null; |
||||
|
|
||||
|
foreach (var channel in channels) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
channel.Log(logLevel, formattedText); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
if (exceptions == null) |
||||
|
{ |
||||
|
exceptions = new List<Exception>(); |
||||
|
} |
||||
|
|
||||
|
exceptions.Add(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (exceptions != null && exceptions.Count > 0) |
||||
|
{ |
||||
|
throw new AggregateException("An error occurred while writing to logger(s).", exceptions); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private string FormatText(SemanticLogLevel logLevel, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
var writer = writerFactory(); |
||||
|
|
||||
|
writer.WriteProperty(nameof(logLevel), logLevel.ToString()); |
||||
|
|
||||
|
objectWriter(writer); |
||||
|
|
||||
|
foreach (var appender in appenders) |
||||
|
{ |
||||
|
appender.Append(writer); |
||||
|
} |
||||
|
|
||||
|
return writer.ToString(); |
||||
|
} |
||||
|
|
||||
|
public ISemanticLog CreateScope(Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
return new SemanticLog(channels, appenders.Union(new ILogAppender[] { new ConstantsLogWriter(objectWriter) }).ToArray(), writerFactory); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLogExtensions.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public static class SemanticLogExtensions |
||||
|
{ |
||||
|
public static void LogTrace(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Trace, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static IDisposable MeasureTrace(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
return new TimeMeasurer(log, SemanticLogLevel.Trace, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static void LogDebug(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Debug, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static IDisposable MeasureDebug(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
return new TimeMeasurer(log, SemanticLogLevel.Debug, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static void LogInformation(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Information, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static IDisposable MeasureInformation(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
return new TimeMeasurer(log, SemanticLogLevel.Information, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static void LogWarning(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Warning, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static void LogWarning(this ISemanticLog log, Exception exception, Action<IObjectWriter> objectWriter = null) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Warning, writer => writer.WriteException(exception, objectWriter)); |
||||
|
} |
||||
|
|
||||
|
public static void LogError(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Error, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static void LogError(this ISemanticLog log, Exception exception, Action<IObjectWriter> objectWriter = null) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Error, writer => writer.WriteException(exception, objectWriter)); |
||||
|
} |
||||
|
|
||||
|
public static void LogFatal(this ISemanticLog log, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Fatal, objectWriter); |
||||
|
} |
||||
|
|
||||
|
public static void LogFatal(this ISemanticLog log, Exception exception, Action<IObjectWriter> objectWriter = null) |
||||
|
{ |
||||
|
log.Log(SemanticLogLevel.Fatal, writer => writer.WriteException(exception, objectWriter)); |
||||
|
} |
||||
|
|
||||
|
private static void WriteException(this IObjectWriter writer, Exception exception, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
objectWriter?.Invoke(writer); |
||||
|
|
||||
|
if (exception != null) |
||||
|
{ |
||||
|
writer.WriteException(exception); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static IObjectWriter WriteException(this IObjectWriter writer, Exception exception) |
||||
|
{ |
||||
|
return writer.WriteObject(nameof(exception), inner => |
||||
|
{ |
||||
|
inner.WriteProperty("message", exception.Message); |
||||
|
inner.WriteProperty("stackTrace", exception.StackTrace); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private sealed class TimeMeasurer : IDisposable |
||||
|
{ |
||||
|
private readonly Stopwatch watch = Stopwatch.StartNew(); |
||||
|
private readonly SemanticLogLevel logLevel; |
||||
|
private readonly Action<IObjectWriter> objectWriter; |
||||
|
private readonly ISemanticLog log; |
||||
|
|
||||
|
public TimeMeasurer(ISemanticLog log, SemanticLogLevel logLevel, Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
this.logLevel = logLevel; |
||||
|
this.log = log; |
||||
|
|
||||
|
this.objectWriter = objectWriter; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
watch.Stop(); |
||||
|
|
||||
|
log.Log(logLevel, writer => |
||||
|
{ |
||||
|
objectWriter?.Invoke(writer); |
||||
|
|
||||
|
writer.WriteProperty("elapsedMs", watch.ElapsedMilliseconds); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLogLevel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public enum SemanticLogLevel |
||||
|
{ |
||||
|
Trace, |
||||
|
Debug, |
||||
|
Information, |
||||
|
Warning, |
||||
|
Error, |
||||
|
Fatal |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// TimestampLogAppender.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class TimestampLogAppender : ILogAppender |
||||
|
{ |
||||
|
private readonly Func<long> timestamp; |
||||
|
|
||||
|
public TimestampLogAppender() |
||||
|
: this(() => DateTimeOffset.UtcNow.ToUnixTimeSeconds()) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TimestampLogAppender(Func<long> timestamp) |
||||
|
{ |
||||
|
Guard.NotNull(timestamp, nameof(timestamp)); |
||||
|
|
||||
|
this.timestamp = timestamp; |
||||
|
} |
||||
|
|
||||
|
public void Append(IObjectWriter writer) |
||||
|
{ |
||||
|
writer.WriteProperty("timestamp", timestamp()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,163 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JsonLogWriterTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public class JsonLogWriterTests |
||||
|
{ |
||||
|
private readonly IObjectWriter sut = new JsonLogWriter(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_boolean_property() |
||||
|
{ |
||||
|
var result = sut.WriteProperty("property", true).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":true}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_long_property() |
||||
|
{ |
||||
|
var result = sut.WriteProperty("property", 120).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":120}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_double_property() |
||||
|
{ |
||||
|
var result = sut.WriteProperty("property", 1.5).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":1.5}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_string_property() |
||||
|
{ |
||||
|
var result = sut.WriteProperty("property", "my-string").ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":""my-string""}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_timespan_property() |
||||
|
{ |
||||
|
var result = sut.WriteProperty("property", new TimeSpan(1, 40, 30, 20, 100)).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":""2.16:30:20.1000000""}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_datetimeoffset_property() |
||||
|
{ |
||||
|
var value = DateTimeOffset.UtcNow; |
||||
|
var result = sut.WriteProperty("property", value).ToString(); |
||||
|
|
||||
|
Assert.Equal($"{{\"property\":\"{value.ToString("o")}\"}}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_date_property() |
||||
|
{ |
||||
|
var value = DateTime.UtcNow; |
||||
|
var result = sut.WriteProperty("property", value).ToString(); |
||||
|
|
||||
|
Assert.Equal($"{{\"property\":\"{value.ToString("o")}\"}}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_boolean_value() |
||||
|
{ |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue(true)).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":[true]}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_long_value() |
||||
|
{ |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue(120)).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":[120]}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_double_value() |
||||
|
{ |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue(1.5)).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":[1.5]}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_string_value() |
||||
|
{ |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue("my-string")).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":[""my-string""]}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_timespan_value() |
||||
|
{ |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue(new TimeSpan(1, 40, 30, 20, 100))).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":[""2.16:30:20.1000000""]}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_datetimeoffset_value() |
||||
|
{ |
||||
|
var value = DateTimeOffset.UtcNow; |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue(value)).ToString(); |
||||
|
|
||||
|
Assert.Equal($"{{\"property\":[\"{value.ToString("o")}\"]}}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_date_value() |
||||
|
{ |
||||
|
var value = DateTime.UtcNow; |
||||
|
var result = sut.WriteArray("property", a => a.WriteValue(value)).ToString(); |
||||
|
|
||||
|
Assert.Equal($"{{\"property\":[\"{value.ToString("o")}\"]}}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_nested_object() |
||||
|
{ |
||||
|
var result = sut.WriteObject("property", a => a.WriteProperty("nested", "my-string")).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":{""nested"":""my-string""}}", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_pretty_json() |
||||
|
{ |
||||
|
IObjectWriter prettySut = new JsonLogWriter(Formatting.Indented, false); |
||||
|
|
||||
|
var result = prettySut.WriteProperty("property", 1.5).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{NL ""property"": 1.5NL}".Replace("NL", Environment.NewLine), result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_extra_line_after_object() |
||||
|
{ |
||||
|
IObjectWriter prettySut = new JsonLogWriter(Formatting.None, true); |
||||
|
|
||||
|
var result = prettySut.WriteProperty("property", 1.5).ToString(); |
||||
|
|
||||
|
Assert.Equal(@"{""property"":1.5}NL".Replace("NL", Environment.NewLine), result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,300 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SemanticLogTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reflection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Moq; |
||||
|
using Squidex.Infrastructure.Log.Adapter; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public class SemanticLogTests |
||||
|
{ |
||||
|
private readonly List<ILogAppender> appenders = new List<ILogAppender>(); |
||||
|
private readonly List<ILogChannel> channels = new List<ILogChannel>(); |
||||
|
private readonly Lazy<SemanticLog> log; |
||||
|
private readonly Mock<ILogChannel> channel = new Mock<ILogChannel>(); |
||||
|
private string output; |
||||
|
|
||||
|
public SemanticLog Log |
||||
|
{ |
||||
|
get { return log.Value; } |
||||
|
} |
||||
|
|
||||
|
public SemanticLogTests() |
||||
|
{ |
||||
|
channels.Add(channel.Object); |
||||
|
|
||||
|
channel.Setup(x => x.Log(It.IsAny<SemanticLogLevel>(), It.IsAny<string>())).Callback( |
||||
|
new Action<SemanticLogLevel, string>((level, message) => |
||||
|
{ |
||||
|
output = message; |
||||
|
})); |
||||
|
|
||||
|
log = new Lazy<SemanticLog>(() => new SemanticLog(channels, appenders, () => new JsonLogWriter())); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_timestamp() |
||||
|
{ |
||||
|
appenders.Add(new TimestampLogAppender(() => 1500)); |
||||
|
|
||||
|
Log.LogFatal(w => {}); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal") |
||||
|
.WriteProperty("timestamp", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_values_with_appender() |
||||
|
{ |
||||
|
appenders.Add(new ConstantsLogWriter(w => w.WriteProperty("logValue", 1500))); |
||||
|
|
||||
|
Log.LogFatal(m => { }); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_application_info() |
||||
|
{ |
||||
|
appenders.Add(new ApplicationInfoLogAppender(GetType().GetTypeInfo().Assembly)); |
||||
|
|
||||
|
Log.LogFatal(m => { }); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal") |
||||
|
.WriteProperty("applicationName", "Squidex.Infrastructure.Tests") |
||||
|
.WriteProperty("applicationVersion", "1.0.0.0")); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_trace() |
||||
|
{ |
||||
|
Log.LogTrace(w => w.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Trace") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_debug() |
||||
|
{ |
||||
|
Log.LogDebug(w => w.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Debug") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_information() |
||||
|
{ |
||||
|
Log.LogInformation(w => w.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Information") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_warning() |
||||
|
{ |
||||
|
Log.LogWarning(w => w.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Warning") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_warning_exception() |
||||
|
{ |
||||
|
var exception = new InvalidOperationException(); |
||||
|
|
||||
|
Log.LogWarning(exception); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Warning") |
||||
|
.WriteException(exception)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_error() |
||||
|
{ |
||||
|
Log.LogError(w => w.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Error") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_error_exception() |
||||
|
{ |
||||
|
var exception = new InvalidOperationException(); |
||||
|
|
||||
|
Log.LogError(exception); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Error") |
||||
|
.WriteException(exception)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_fatal() |
||||
|
{ |
||||
|
Log.LogFatal(w => w.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal") |
||||
|
.WriteProperty("logValue", 1500)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_fatal_exception() |
||||
|
{ |
||||
|
var exception = new InvalidOperationException(); |
||||
|
|
||||
|
Log.LogFatal(exception); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal") |
||||
|
.WriteException(exception)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_nothing_when_exception_is_null() |
||||
|
{ |
||||
|
Log.LogFatal((Exception)null); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal")); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_measure_trace() |
||||
|
{ |
||||
|
Log.MeasureTrace(w => w.WriteProperty("message", "My Message")).Dispose(); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Trace") |
||||
|
.WriteProperty("message", "My Message") |
||||
|
.WriteProperty("elapsedMs", 0)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_measure_debug() |
||||
|
{ |
||||
|
Log.MeasureDebug(w => w.WriteProperty("message", "My Message")).Dispose(); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Debug") |
||||
|
.WriteProperty("message", "My Message") |
||||
|
.WriteProperty("elapsedMs", 0)); |
||||
|
|
||||
|
Assert.True(output.StartsWith(expected.Substring(0, 55))); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_measure_information() |
||||
|
{ |
||||
|
Log.MeasureInformation(w => w.WriteProperty("message", "My Message")).Dispose(); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Information") |
||||
|
.WriteProperty("message", "My Message") |
||||
|
.WriteProperty("elapsedMs", 0)); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_log_with_extensions_logger() |
||||
|
{ |
||||
|
var exception = new InvalidOperationException(); |
||||
|
|
||||
|
var loggerFactory = new LoggerFactory().AddSemanticLog(Log); |
||||
|
var loggerInstance = loggerFactory.CreateLogger<SemanticLogTests>(); |
||||
|
|
||||
|
loggerInstance.LogCritical(new EventId(123, "EventName"), exception, "Log {0}", 123); |
||||
|
|
||||
|
var expected = |
||||
|
MakeTestCall(w => w |
||||
|
.WriteProperty("logLevel", "Fatal") |
||||
|
.WriteProperty("message", "Log 123") |
||||
|
.WriteObject("eventId", e => e |
||||
|
.WriteProperty("id", 123) |
||||
|
.WriteProperty("name", "EventName")) |
||||
|
.WriteException(exception) |
||||
|
.WriteProperty("category", "Squidex.Infrastructure.Log.SemanticLogTests")); |
||||
|
|
||||
|
Assert.Equal(expected, output); |
||||
|
} |
||||
|
|
||||
|
private static string MakeTestCall(Action<IObjectWriter> writer) |
||||
|
{ |
||||
|
IObjectWriter sut = new JsonLogWriter(); |
||||
|
|
||||
|
writer(sut); |
||||
|
|
||||
|
return sut.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue