mirror of https://github.com/Squidex/squidex.git
108 changed files with 2684 additions and 597 deletions
@ -0,0 +1,100 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RabbitMqEventConsumer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json; |
||||
|
using RabbitMQ.Client; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.RabbitMq |
||||
|
{ |
||||
|
public sealed class RabbitMqEventConsumer : DisposableObjectBase, IExternalSystem, IEventConsumer |
||||
|
{ |
||||
|
private readonly JsonSerializerSettings serializerSettings; |
||||
|
private readonly string eventPublisherName; |
||||
|
private readonly string exchange; |
||||
|
private readonly string eventsFilter; |
||||
|
private readonly ConnectionFactory connectionFactory; |
||||
|
private readonly Lazy<IConnection> connection; |
||||
|
private readonly Lazy<IModel> channel; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return eventPublisherName; } |
||||
|
} |
||||
|
|
||||
|
public string EventsFilter |
||||
|
{ |
||||
|
get { return eventsFilter; } |
||||
|
} |
||||
|
|
||||
|
public RabbitMqEventConsumer(JsonSerializerSettings serializerSettings, string eventPublisherName, string uri, string exchange, string eventsFilter) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(uri, nameof(uri)); |
||||
|
Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName)); |
||||
|
Guard.NotNullOrEmpty(exchange, nameof(exchange)); |
||||
|
Guard.NotNull(serializerSettings, nameof(serializerSettings)); |
||||
|
|
||||
|
connectionFactory = new ConnectionFactory { Uri = uri }; |
||||
|
|
||||
|
connection = new Lazy<IConnection>(connectionFactory.CreateConnection); |
||||
|
channel = new Lazy<IModel>(() => connection.Value.CreateModel()); |
||||
|
|
||||
|
this.exchange = exchange; |
||||
|
this.eventsFilter = eventsFilter; |
||||
|
this.eventPublisherName = eventPublisherName; |
||||
|
this.serializerSettings = serializerSettings; |
||||
|
} |
||||
|
|
||||
|
protected override void DisposeObject(bool disposing) |
||||
|
{ |
||||
|
if (connection.IsValueCreated) |
||||
|
{ |
||||
|
connection.Value.Close(); |
||||
|
connection.Value.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Connect() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var currentConnection = connection.Value; |
||||
|
|
||||
|
if (!currentConnection.IsOpen) |
||||
|
{ |
||||
|
throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}"); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
public Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
var jsonString = JsonConvert.SerializeObject(@event, serializerSettings); |
||||
|
var jsonBytes = Encoding.UTF8.GetBytes(jsonString); |
||||
|
|
||||
|
channel.Value.BasicPublish(exchange, string.Empty, null, jsonBytes); |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard1.6</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<DebugType>full</DebugType> |
||||
|
<DebugSymbols>True</DebugSymbols> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="RabbitMQ.Client" Version="4.1.3" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -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,42 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ApplicationInfoLogAppender.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class ApplicationInfoLogAppender : ILogAppender |
||||
|
{ |
||||
|
private readonly string applicationName; |
||||
|
private readonly string applicationVersion; |
||||
|
private readonly string applicationSessionId; |
||||
|
|
||||
|
public ApplicationInfoLogAppender(Type type, Guid applicationSession) |
||||
|
: this(type?.GetTypeInfo().Assembly, applicationSession) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public ApplicationInfoLogAppender(Assembly assembly, Guid applicationSession) |
||||
|
{ |
||||
|
Guard.NotNull(assembly, nameof(assembly)); |
||||
|
|
||||
|
applicationName = assembly.GetName().Name; |
||||
|
applicationVersion = assembly.GetName().Version.ToString(); |
||||
|
applicationSessionId = applicationSession.ToString(); |
||||
|
} |
||||
|
|
||||
|
public void Append(IObjectWriter writer) |
||||
|
{ |
||||
|
writer.WriteObject("app", w => w |
||||
|
.WriteProperty("name", applicationName) |
||||
|
.WriteProperty("version", applicationVersion) |
||||
|
.WriteProperty("sessionId", applicationSessionId)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// FileChannel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.Log.Internal; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log |
||||
|
{ |
||||
|
public sealed class FileChannel : ILogChannel, IExternalSystem |
||||
|
{ |
||||
|
private readonly FileLogProcessor processor; |
||||
|
|
||||
|
public FileChannel(string path) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(path, nameof(path)); |
||||
|
|
||||
|
processor = new FileLogProcessor(path); |
||||
|
} |
||||
|
|
||||
|
public void Log(SemanticLogLevel logLevel, string message) |
||||
|
{ |
||||
|
processor.EnqueueMessage(new LogMessageEntry { Message = message, Level = logLevel }); |
||||
|
} |
||||
|
|
||||
|
public void Connect() |
||||
|
{ |
||||
|
processor.Connect(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(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,95 @@ |
|||||
|
// ==========================================================================
|
||||
|
// FileLogChannel.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Log.Internal |
||||
|
{ |
||||
|
public class FileLogProcessor : IDisposable |
||||
|
{ |
||||
|
private const int MaxQueuedMessages = 1024; |
||||
|
private const int Retries = 10; |
||||
|
private readonly BlockingCollection<LogMessageEntry> messageQueue = new BlockingCollection<LogMessageEntry>(MaxQueuedMessages); |
||||
|
private readonly Task outputTask; |
||||
|
private readonly string path; |
||||
|
|
||||
|
public FileLogProcessor(string path) |
||||
|
{ |
||||
|
this.path = path; |
||||
|
|
||||
|
outputTask = Task.Factory.StartNew(ProcessLogQueue, this, TaskCreationOptions.LongRunning); |
||||
|
} |
||||
|
|
||||
|
public void Connect() |
||||
|
{ |
||||
|
var fileInfo = new FileInfo(path); |
||||
|
|
||||
|
if (!fileInfo.Directory.Exists) |
||||
|
{ |
||||
|
throw new ConfigurationException($"Log directory '{fileInfo.Directory.FullName}' does not exist."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void EnqueueMessage(LogMessageEntry message) |
||||
|
{ |
||||
|
messageQueue.Add(message); |
||||
|
} |
||||
|
|
||||
|
private async Task ProcessLogQueue() |
||||
|
{ |
||||
|
foreach (var entry in messageQueue.GetConsumingEnumerable()) |
||||
|
{ |
||||
|
for (var i = 1; i <= Retries; i++) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
File.AppendAllText(path, entry.Message + Environment.NewLine, Encoding.UTF8); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
await Task.Delay(i * 10); |
||||
|
|
||||
|
if (i == Retries) |
||||
|
{ |
||||
|
Console.WriteLine("Failed to write to log file '{0}': {1}", path, ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Task ProcessLogQueue(object state) |
||||
|
{ |
||||
|
var processor = (FileLogProcessor)state; |
||||
|
|
||||
|
return 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,184 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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(Action<IObjectWriter> objectWriter) |
||||
|
{ |
||||
|
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<DateTime> timestamp; |
||||
|
|
||||
|
public TimestampLogAppender() |
||||
|
: this(() => DateTime.UtcNow) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TimestampLogAppender(Func<DateTime> timestamp) |
||||
|
{ |
||||
|
Guard.NotNull(timestamp, nameof(timestamp)); |
||||
|
|
||||
|
this.timestamp = timestamp; |
||||
|
} |
||||
|
|
||||
|
public void Append(IObjectWriter writer) |
||||
|
{ |
||||
|
writer.WriteProperty("timestamp", timestamp()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Singletons.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
|
||||
|
namespace Squidex.Infrastructure |
||||
|
{ |
||||
|
public static class Singletons<T> |
||||
|
{ |
||||
|
private static readonly ConcurrentDictionary<string, T> instances = new ConcurrentDictionary<string, T>(StringComparer.OrdinalIgnoreCase); |
||||
|
|
||||
|
public static T GetOrAdd(string key, Func<string, T> factory) |
||||
|
{ |
||||
|
return instances.GetOrAdd(key, factory); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,85 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ClusterModule.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Autofac; |
|
||||
using Microsoft.Extensions.Configuration; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
using Squidex.Infrastructure.Redis; |
|
||||
using StackExchange.Redis; |
|
||||
|
|
||||
namespace Squidex.Config.Domain |
|
||||
{ |
|
||||
public class ClusterModule : Module |
|
||||
{ |
|
||||
private IConfiguration Configuration { get; } |
|
||||
|
|
||||
public ClusterModule(IConfiguration configuration) |
|
||||
{ |
|
||||
Configuration = configuration; |
|
||||
} |
|
||||
|
|
||||
protected override void Load(ContainerBuilder builder) |
|
||||
{ |
|
||||
var handleEvents = Configuration.GetValue<bool>("squidex:handleEvents"); |
|
||||
|
|
||||
if (handleEvents) |
|
||||
{ |
|
||||
builder.RegisterType<EventReceiver>() |
|
||||
.AsSelf() |
|
||||
.InstancePerDependency(); |
|
||||
} |
|
||||
|
|
||||
var clustererType = Configuration.GetValue<string>("squidex:clusterer:type"); |
|
||||
|
|
||||
if (string.IsNullOrWhiteSpace(clustererType)) |
|
||||
{ |
|
||||
throw new ConfigurationException("You must specify the clusterer type in the 'squidex:clusterer:type' configuration section."); |
|
||||
} |
|
||||
|
|
||||
if (string.Equals(clustererType, "Redis", StringComparison.OrdinalIgnoreCase)) |
|
||||
{ |
|
||||
var connectionString = Configuration.GetValue<string>("squidex:clusterer:redis:connectionString"); |
|
||||
|
|
||||
if (string.IsNullOrWhiteSpace(connectionString)) |
|
||||
{ |
|
||||
throw new ConfigurationException("You must specify the Redis connection string in the 'squidex:clusterer:redis:connectionString' configuration section."); |
|
||||
} |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
var connectionMultiplexer = ConnectionMultiplexer.Connect(connectionString); |
|
||||
|
|
||||
builder.RegisterInstance(connectionMultiplexer) |
|
||||
.As<IConnectionMultiplexer>() |
|
||||
.SingleInstance(); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
throw new ConfigurationException($"Redis connection failed to connect to database {connectionString}", ex); |
|
||||
} |
|
||||
|
|
||||
builder.RegisterType<RedisPubSub>() |
|
||||
.As<IPubSub>() |
|
||||
.As<IExternalSystem>() |
|
||||
.SingleInstance(); |
|
||||
} |
|
||||
else if (string.Equals(clustererType, "None", StringComparison.OrdinalIgnoreCase)) |
|
||||
{ |
|
||||
builder.RegisterType<InMemoryPubSub>() |
|
||||
.As<IPubSub>() |
|
||||
.SingleInstance(); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
throw new ConfigurationException($"Unsupported clusterer type '{clustererType}' for key 'squidex:clusterer:type', supported: Redis, None."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,80 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RabbitMqModule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Autofac; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.RabbitMq; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Config.Domain |
||||
|
{ |
||||
|
public sealed class EventPublishersModule : Module |
||||
|
{ |
||||
|
private IConfiguration Configuration { get; } |
||||
|
|
||||
|
public EventPublishersModule(IConfiguration configuration) |
||||
|
{ |
||||
|
Configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
protected override void Load(ContainerBuilder builder) |
||||
|
{ |
||||
|
var eventPublishers = Configuration.GetSection("eventPublishers"); |
||||
|
|
||||
|
foreach (var child in eventPublishers.GetChildren()) |
||||
|
{ |
||||
|
var eventPublisherType = child.GetValue<string>("type"); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(eventPublisherType)) |
||||
|
{ |
||||
|
throw new ConfigurationException($"Configure EventPublisher type with 'eventPublishers:{child.Key}:type'."); |
||||
|
} |
||||
|
|
||||
|
var eventsFilter = Configuration.GetValue<string>("eventsFilter"); |
||||
|
|
||||
|
var enabled = child.GetValue<bool>("enabled"); |
||||
|
|
||||
|
if (string.Equals(eventPublisherType, "RabbitMq", StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
var configuration = child.GetValue<string>("configuration"); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(configuration)) |
||||
|
{ |
||||
|
throw new ConfigurationException($"Configure EventPublisher RabbitMq configuration with 'eventPublishers:{child.Key}:configuration'."); |
||||
|
} |
||||
|
|
||||
|
var exchange = child.GetValue<string>("exchange"); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(exchange)) |
||||
|
{ |
||||
|
throw new ConfigurationException($"Configure EventPublisher RabbitMq exchange with 'eventPublishers:{child.Key}:configuration'."); |
||||
|
} |
||||
|
|
||||
|
var name = $"EventPublishers_{child.Key}"; |
||||
|
|
||||
|
if (enabled) |
||||
|
{ |
||||
|
builder.Register(c => new RabbitMqEventConsumer(c.Resolve<JsonSerializerSettings>(), name, configuration, exchange, eventsFilter)) |
||||
|
.As<IEventConsumer>() |
||||
|
.As<IExternalSystem>() |
||||
|
.SingleInstance(); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new ConfigurationException($"Unsupported value '{child.Key}' for 'eventPublishers:{child.Key}:type', supported: RabbitMq."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ClusterModule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Autofac; |
||||
|
using Autofac.Core; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Redis; |
||||
|
using StackExchange.Redis; |
||||
|
|
||||
|
namespace Squidex.Config.Domain |
||||
|
{ |
||||
|
public sealed class PubSubModule : Module |
||||
|
{ |
||||
|
private const string RedisRegistration = "PubSubRedis"; |
||||
|
|
||||
|
private IConfiguration Configuration { get; } |
||||
|
|
||||
|
public PubSubModule(IConfiguration configuration) |
||||
|
{ |
||||
|
Configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
protected override void Load(ContainerBuilder builder) |
||||
|
{ |
||||
|
var pubSubType = Configuration.GetValue<string>("pubSub:type"); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(pubSubType)) |
||||
|
{ |
||||
|
throw new ConfigurationException("Configure the PubSub type with 'pubSub:type'."); |
||||
|
} |
||||
|
|
||||
|
if (string.Equals(pubSubType, "Redis", StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
var configuration = Configuration.GetValue<string>("pubsub:redis:configuration"); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(configuration)) |
||||
|
{ |
||||
|
throw new ConfigurationException("Configure PubSub Redis configuration with pubSub:redis:configuration'."); |
||||
|
} |
||||
|
|
||||
|
builder.Register(c => Singletons<IConnectionMultiplexer>.GetOrAdd(configuration, s => ConnectionMultiplexer.Connect(s))) |
||||
|
.Named<IConnectionMultiplexer>(RedisRegistration) |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterType<RedisPubSub>() |
||||
|
.WithParameter(ResolvedParameter.ForNamed<IConnectionMultiplexer>(RedisRegistration)) |
||||
|
.As<IPubSub>() |
||||
|
.As<IExternalSystem>() |
||||
|
.SingleInstance(); |
||||
|
} |
||||
|
else if (string.Equals(pubSubType, "InMemory", StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
builder.RegisterType<InMemoryPubSub>() |
||||
|
.As<IPubSub>() |
||||
|
.SingleInstance(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new ConfigurationException($"Unsupported value '{pubSubType}' for 'pubSub:type', supported: Redis, InMemory."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
// ==========================================================================
|
||||
|
// HttpLogAppender.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
|
||||
|
namespace Squidex.Pipeline |
||||
|
{ |
||||
|
public class ActionContextLogAppender : ILogAppender |
||||
|
{ |
||||
|
private readonly IActionContextAccessor actionContextAccessor; |
||||
|
|
||||
|
public ActionContextLogAppender(IActionContextAccessor actionContextAccessor) |
||||
|
{ |
||||
|
this.actionContextAccessor = actionContextAccessor; |
||||
|
} |
||||
|
|
||||
|
public void Append(IObjectWriter writer) |
||||
|
{ |
||||
|
var actionContext = actionContextAccessor.ActionContext; |
||||
|
|
||||
|
if (actionContext == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var httpContext = actionContext.HttpContext; |
||||
|
|
||||
|
Guid requestId; |
||||
|
|
||||
|
if (httpContext.Items.TryGetValue(nameof(requestId), out var value) && value is Guid requestIdValue) |
||||
|
{ |
||||
|
requestId = requestIdValue; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
httpContext.Items[nameof(requestId)] = requestId = Guid.NewGuid(); |
||||
|
} |
||||
|
|
||||
|
writer.WriteObject("web", w => w |
||||
|
.WriteProperty("requestId", requestId.ToString()) |
||||
|
.WriteProperty("requestPath", httpContext.Request.Path) |
||||
|
.WriteProperty("requestMethod", httpContext.Request.Method) |
||||
|
.WriteObject("routeValues", r => |
||||
|
{ |
||||
|
foreach (var kvp in actionContext.ActionDescriptor.RouteValues) |
||||
|
{ |
||||
|
r.WriteProperty(kvp.Key, kvp.Value); |
||||
|
} |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// ==========================================================================
|
||||
|
// LogPerformanceAttribute.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
using Microsoft.AspNetCore.Mvc.Filters; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
|
||||
|
namespace Squidex.Pipeline |
||||
|
{ |
||||
|
public sealed class LogPerformanceAttribute : ActionFilterAttribute |
||||
|
{ |
||||
|
private readonly ISemanticLog log; |
||||
|
|
||||
|
public LogPerformanceAttribute(ISemanticLog log) |
||||
|
{ |
||||
|
this.log = log; |
||||
|
} |
||||
|
|
||||
|
public override void OnActionExecuting(ActionExecutingContext context) |
||||
|
{ |
||||
|
context.HttpContext.Items["Watch"] = Stopwatch.StartNew(); |
||||
|
} |
||||
|
|
||||
|
public override void OnActionExecuted(ActionExecutedContext context) |
||||
|
{ |
||||
|
var stopWatch = (Stopwatch)context.HttpContext.Items["Watch"]; |
||||
|
|
||||
|
stopWatch.Stop(); |
||||
|
|
||||
|
log.LogInformation(w => w.WriteProperty("elapsedRequestMs", stopWatch.ElapsedMilliseconds)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,34 +1,53 @@ |
|||||
{ |
{ |
||||
"squidex": { |
"urls": { |
||||
"urls": { |
"baseUrl": "http://localhost:5000" |
||||
"baseUrl": "http://localhost:5000" |
}, |
||||
|
"logging": { |
||||
|
"human": false |
||||
|
}, |
||||
|
"pubSub": { |
||||
|
"type": "InMemory", |
||||
|
"redis": { |
||||
|
"configuration": "localhost:6379,resolveDns=1" |
||||
|
} |
||||
|
}, |
||||
|
"eventStore": { |
||||
|
"type": "MongoDb", |
||||
|
"mongoDb": { |
||||
|
"configuration": "mongodb://localhost", |
||||
|
"database": "Squidex" |
||||
}, |
}, |
||||
"clusterer": { |
"consume": true |
||||
"type": "none", |
}, |
||||
|
"eventPublishers": { |
||||
|
"allToRabbitMq": { |
||||
|
"type": "RabbitMq", |
||||
|
"configuration": "amqp://guest:guest@localhost/", |
||||
|
"exchange": "squidex", |
||||
|
"enabled": false, |
||||
|
"eventsFilter": "*" |
||||
|
} |
||||
|
}, |
||||
|
"store": { |
||||
|
"type": "MongoDb", |
||||
|
"mongoDb": { |
||||
|
"configuration": "mongodb://localhost", |
||||
|
"contentDatabase": "SquidexContent", |
||||
|
"database": "Squidex" |
||||
|
} |
||||
|
}, |
||||
|
"identity": { |
||||
|
"googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com", |
||||
|
"googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg", |
||||
|
"lockAutomatically": true, |
||||
|
"keysStore": { |
||||
|
"type": "InMemory", |
||||
"redis": { |
"redis": { |
||||
"connectionString": "localhost:6379,resolveDns=1" |
"configuration": "localhost:6379,resolveDns=1" |
||||
|
}, |
||||
|
"folder": { |
||||
|
"path": "keys" |
||||
} |
} |
||||
}, |
} |
||||
"eventStore": { |
|
||||
"type": "mongoDb", |
|
||||
"mongoDb": { |
|
||||
"connectionString": "mongodb://localhost", |
|
||||
"databaseName": "Squidex" |
|
||||
} |
|
||||
}, |
|
||||
"stores": { |
|
||||
"type": "mongoDb", |
|
||||
"mongoDb": { |
|
||||
"connectionString": "mongodb://localhost", |
|
||||
"databaseName": "Squidex", |
|
||||
"databaseNameContent": "SquidexContent" |
|
||||
} |
|
||||
}, |
|
||||
"identity": { |
|
||||
"googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com", |
|
||||
"googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg", |
|
||||
"lockAutomatically": true |
|
||||
}, |
|
||||
"handleEvents": true |
|
||||
} |
} |
||||
} |
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue