Browse Source

File Channel

pull/1/head
Sebastian Stehle 9 years ago
parent
commit
10e96d1913
  1. 4
      .dockerignore
  2. 4
      .gitignore
  3. 16
      src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs
  4. 34
      src/Squidex.Infrastructure/Log/FileChannel.cs
  5. 95
      src/Squidex.Infrastructure/Log/Internal/FileLogChannel.cs
  6. 6
      src/Squidex.Infrastructure/Log/TimestampLogAppender.cs
  7. 13
      src/Squidex/Config/Domain/InfrastructureModule.cs
  8. 2
      src/Squidex/appsettings.json
  9. 16
      tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs

4
.dockerignore

@ -19,4 +19,6 @@
**/node_modules/ **/node_modules/
# Scripts (should be copied from node_modules on build) # Scripts (should be copied from node_modules on build)
**/wwwroot/scripts/**/*.* **/wwwroot/scripts/**/*.*
**/src/Squidex/appsettings.Development.json

4
.gitignore

@ -17,4 +17,6 @@ _test-output/
node_modules/ node_modules/
# Scripts (should be copied from node_modules on build) # Scripts (should be copied from node_modules on build)
**/wwwroot/scripts/**/*.* **/wwwroot/scripts/**/*.*
/src/Squidex/appsettings.Development.json

16
src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs

@ -17,20 +17,26 @@ namespace Squidex.Infrastructure.Log
private readonly string applicationVersion; private readonly string applicationVersion;
private readonly string applicationSessionId; private readonly string applicationSessionId;
public ApplicationInfoLogAppender(Assembly assembly) public ApplicationInfoLogAppender(Type type, Guid applicationSession)
: this(type?.GetTypeInfo().Assembly, applicationSession)
{
}
public ApplicationInfoLogAppender(Assembly assembly, Guid applicationSession)
{ {
Guard.NotNull(assembly, nameof(assembly)); Guard.NotNull(assembly, nameof(assembly));
applicationName = assembly.GetName().Name; applicationName = assembly.GetName().Name;
applicationVersion = assembly.GetName().Version.ToString(); applicationVersion = assembly.GetName().Version.ToString();
applicationSessionId = Guid.NewGuid().ToString(); applicationSessionId = applicationSession.ToString();
} }
public void Append(IObjectWriter writer) public void Append(IObjectWriter writer)
{ {
writer.WriteProperty("appName", applicationName); writer.WriteObject("app", w => w
writer.WriteProperty("appVersion", applicationVersion); .WriteProperty("name", applicationName)
writer.WriteProperty("appSessionId", applicationSessionId); .WriteProperty("version", applicationVersion)
.WriteProperty("sessionId", applicationSessionId));
} }
} }
} }

34
src/Squidex.Infrastructure/Log/FileChannel.cs

@ -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();
}
}
}

95
src/Squidex.Infrastructure/Log/Internal/FileLogChannel.cs

@ -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)
{
}
}
}
}

6
src/Squidex.Infrastructure/Log/TimestampLogAppender.cs

@ -12,14 +12,14 @@ namespace Squidex.Infrastructure.Log
{ {
public sealed class TimestampLogAppender : ILogAppender public sealed class TimestampLogAppender : ILogAppender
{ {
private readonly Func<long> timestamp; private readonly Func<DateTime> timestamp;
public TimestampLogAppender() public TimestampLogAppender()
: this(() => DateTimeOffset.UtcNow.ToUnixTimeSeconds()) : this(() => DateTime.UtcNow)
{ {
} }
public TimestampLogAppender(Func<long> timestamp) public TimestampLogAppender(Func<DateTime> timestamp)
{ {
Guard.NotNull(timestamp, nameof(timestamp)); Guard.NotNull(timestamp, nameof(timestamp));

13
src/Squidex/Config/Domain/InfrastructureModule.cs

@ -23,7 +23,6 @@ using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Pipeline; using Squidex.Pipeline;
using IntrospectionExtensions = System.Reflection.IntrospectionExtensions;
// ReSharper disable UnusedAutoPropertyAccessor.Local // ReSharper disable UnusedAutoPropertyAccessor.Local
@ -53,7 +52,17 @@ namespace Squidex.Config.Domain
.SingleInstance(); .SingleInstance();
} }
builder.Register(c => new ApplicationInfoLogAppender(IntrospectionExtensions.GetTypeInfo(typeof(InfrastructureModule)).Assembly)) var logFile = Configuration.GetValue<string>("squidex:logging:file");
if (!string.IsNullOrWhiteSpace(logFile))
{
builder.RegisterInstance(new FileChannel(logFile))
.As<ILogChannel>()
.As<IExternalSystem>()
.SingleInstance();
}
builder.Register(c => new ApplicationInfoLogAppender(GetType(), Guid.NewGuid()))
.As<ILogAppender>() .As<ILogAppender>()
.SingleInstance(); .SingleInstance();

2
src/Squidex/appsettings.json

@ -4,7 +4,7 @@
"baseUrl": "http://localhost:5000" "baseUrl": "http://localhost:5000"
}, },
"logging": { "logging": {
"human": true "human": false
}, },
"clusterer": { "clusterer": {
"type": "none", "type": "none",

16
tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs

@ -45,14 +45,16 @@ namespace Squidex.Infrastructure.Log
[Fact] [Fact]
public void Should_log_timestamp() public void Should_log_timestamp()
{ {
appenders.Add(new TimestampLogAppender(() => 1500)); var now = DateTime.UtcNow;
appenders.Add(new TimestampLogAppender(() => now));
Log.LogFatal(w => {}); Log.LogFatal(w => {});
var expected = var expected =
MakeTestCall(w => w MakeTestCall(w => w
.WriteProperty("logLevel", "Fatal") .WriteProperty("logLevel", "Fatal")
.WriteProperty("timestamp", 1500)); .WriteProperty("timestamp", now));
Assert.Equal(expected, output); Assert.Equal(expected, output);
} }
@ -75,15 +77,19 @@ namespace Squidex.Infrastructure.Log
[Fact] [Fact]
public void Should_log_application_info() public void Should_log_application_info()
{ {
appenders.Add(new ApplicationInfoLogAppender(GetType().GetTypeInfo().Assembly)); var sessionId = Guid.NewGuid();
appenders.Add(new ApplicationInfoLogAppender(GetType(), sessionId));
Log.LogFatal(m => { }); Log.LogFatal(m => { });
var expected = var expected =
MakeTestCall(w => w MakeTestCall(w => w
.WriteProperty("logLevel", "Fatal") .WriteProperty("logLevel", "Fatal")
.WriteProperty("applicationName", "Squidex.Infrastructure.Tests") .WriteObject("app", a => a
.WriteProperty("applicationVersion", "1.0.0.0")); .WriteProperty("name", "Squidex.Infrastructure.Tests")
.WriteProperty("version", "1.0.0.0")
.WriteProperty("sessionId", sessionId.ToString())));
Assert.Equal(expected, output); Assert.Equal(expected, output);
} }

Loading…
Cancel
Save