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": { |
|||
"baseUrl": "http://localhost:5000" |
|||
"urls": { |
|||
"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": { |
|||
"type": "none", |
|||
"consume": true |
|||
}, |
|||
"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": { |
|||
"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