Update Log to use worker thread
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
|
<PackageReference Include="System.Threading.Channels" Version="5.0.0" />
|
||||||
<AdditionalFiles Include="../stylecop.json" />
|
<AdditionalFiles Include="../stylecop.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(Mono)' == ''">
|
<ItemGroup Condition="'$(Mono)' == ''">
|
||||||
|
|||||||
@@ -10,8 +10,11 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -22,90 +25,154 @@ namespace OpenRA
|
|||||||
public TextWriter Writer;
|
public TextWriter Writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly struct ChannelData
|
||||||
|
{
|
||||||
|
public readonly string Channel;
|
||||||
|
public readonly string Text;
|
||||||
|
|
||||||
|
public ChannelData(string channel, string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Channel = channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Log
|
public static class Log
|
||||||
{
|
{
|
||||||
static readonly Dictionary<string, ChannelInfo> Channels = new Dictionary<string, ChannelInfo>();
|
const int CreateLogFileMaxRetryCount = 128;
|
||||||
|
|
||||||
static IEnumerable<string> FilenamesForChannel(string channelName, string baseFilename)
|
static readonly ConcurrentDictionary<string, ChannelInfo> Channels = new ConcurrentDictionary<string, ChannelInfo>();
|
||||||
|
static readonly Channel<ChannelData> Channel;
|
||||||
|
static readonly ChannelWriter<ChannelData> ChannelWriter;
|
||||||
|
static readonly CancellationTokenSource CancellationToken = new CancellationTokenSource();
|
||||||
|
|
||||||
|
static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(5);
|
||||||
|
static readonly Timer Timer;
|
||||||
|
static readonly Thread Thread;
|
||||||
|
|
||||||
|
static Log()
|
||||||
|
{
|
||||||
|
Channel = System.Threading.Channels.Channel.CreateUnbounded<ChannelData>();
|
||||||
|
ChannelWriter = Channel.Writer;
|
||||||
|
|
||||||
|
Thread = new Thread(DoWork);
|
||||||
|
var cancellationTokenToken = CancellationToken.Token;
|
||||||
|
Thread.Start(cancellationTokenToken);
|
||||||
|
|
||||||
|
Timer = new Timer(FlushToDisk, cancellationTokenToken, FlushInterval, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FlushToDisk(object state)
|
||||||
|
{
|
||||||
|
FlushToDisk();
|
||||||
|
|
||||||
|
var token = (CancellationToken)state;
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
Timer.Change(FlushInterval, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FlushToDisk()
|
||||||
|
{
|
||||||
|
foreach (var (_, channel) in Channels)
|
||||||
|
channel.Writer?.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DoWork(object obj)
|
||||||
|
{
|
||||||
|
var token = (CancellationToken)obj;
|
||||||
|
var reader = Channel.Reader;
|
||||||
|
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
if (reader.TryRead(out var item))
|
||||||
|
WriteValue(item);
|
||||||
|
|
||||||
|
while (reader.TryRead(out var item))
|
||||||
|
WriteValue(item);
|
||||||
|
|
||||||
|
FlushToDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteValue(ChannelData item)
|
||||||
|
{
|
||||||
|
var channel = GetChannel(item.Channel);
|
||||||
|
var writer = channel.Writer;
|
||||||
|
if (writer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!channel.IsTimestamped)
|
||||||
|
writer.WriteLine(item.Text);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var timestamp = DateTime.Now.ToString(Game.Settings.Server.TimestampFormat);
|
||||||
|
writer.WriteLine("[{0}] {1}", timestamp, item.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<string> FilenamesForChannel(string baseFilename)
|
||||||
{
|
{
|
||||||
var path = Platform.SupportDir + "Logs";
|
var path = Platform.SupportDir + "Logs";
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
for (var i = 0; ; i++)
|
for (var i = 0; i < CreateLogFileMaxRetryCount; i++)
|
||||||
yield return Path.Combine(path, i > 0 ? "{0}.{1}".F(baseFilename, i) : baseFilename);
|
yield return Path.Combine(path, i > 0 ? "{0}.{1}".F(baseFilename, i) : baseFilename);
|
||||||
|
|
||||||
|
throw new ApplicationException("Error creating log file \"{0}\"".F(baseFilename));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ChannelInfo Channel(string channelName)
|
static ChannelInfo GetChannel(string channelName)
|
||||||
{
|
{
|
||||||
ChannelInfo info;
|
if (!Channels.TryGetValue(channelName, out var info))
|
||||||
lock (Channels)
|
throw new ArgumentException("Tried logging to non-existent channel " + channelName, nameof(channelName));
|
||||||
if (!Channels.TryGetValue(channelName, out info))
|
|
||||||
throw new ArgumentException("Tried logging to non-existent channel " + channelName, nameof(channelName));
|
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddChannel(string channelName, string baseFilename, bool isTimestamped = false)
|
public static void AddChannel(string channelName, string baseFilename, bool isTimestamped = false)
|
||||||
{
|
{
|
||||||
lock (Channels)
|
if (Channels.ContainsKey(channelName))
|
||||||
{
|
return;
|
||||||
if (Channels.ContainsKey(channelName)) return;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(baseFilename))
|
if (string.IsNullOrEmpty(baseFilename))
|
||||||
|
{
|
||||||
|
Channels.TryAdd(channelName, default);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var filename in FilenamesForChannel(baseFilename))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Channels.Add(channelName, default(ChannelInfo));
|
var writer = File.CreateText(filename);
|
||||||
|
writer.AutoFlush = false;
|
||||||
|
|
||||||
|
Channels.TryAdd(channelName,
|
||||||
|
new ChannelInfo
|
||||||
|
{
|
||||||
|
Filename = filename,
|
||||||
|
IsTimestamped = isTimestamped,
|
||||||
|
Writer = TextWriter.Synchronized(writer)
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
catch (IOException) { }
|
||||||
foreach (var filename in FilenamesForChannel(channelName, baseFilename))
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var writer = File.CreateText(filename);
|
|
||||||
writer.AutoFlush = true;
|
|
||||||
|
|
||||||
Channels.Add(channelName,
|
|
||||||
new ChannelInfo
|
|
||||||
{
|
|
||||||
Filename = filename,
|
|
||||||
IsTimestamped = isTimestamped,
|
|
||||||
Writer = TextWriter.Synchronized(writer)
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (IOException) { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(string channelName, string value)
|
public static void Write(string channelName, string value)
|
||||||
{
|
{
|
||||||
var channel = Channel(channelName);
|
ChannelWriter.TryWrite(new ChannelData(channelName, value));
|
||||||
var writer = channel.Writer;
|
|
||||||
if (writer == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!channel.IsTimestamped)
|
|
||||||
writer.WriteLine(value);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var timestamp = DateTime.Now.ToString(Game.Settings.Server.TimestampFormat);
|
|
||||||
writer.WriteLine("[{0}] {1}", timestamp, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(string channelName, string format, params object[] args)
|
public static void Write(string channelName, string format, params object[] args)
|
||||||
{
|
{
|
||||||
var channel = Channel(channelName);
|
ChannelWriter.TryWrite(new ChannelData(channelName, format.F(args)));
|
||||||
if (channel.Writer == null)
|
}
|
||||||
return;
|
|
||||||
|
|
||||||
if (!channel.IsTimestamped)
|
public static void Dispose()
|
||||||
channel.Writer.WriteLine(format, args);
|
{
|
||||||
else
|
CancellationToken.Cancel();
|
||||||
{
|
Timer.Dispose();
|
||||||
var timestamp = DateTime.Now.ToString(Game.Settings.Server.TimestampFormat);
|
|
||||||
channel.Writer.WriteLine("[{0}] {1}", timestamp, format.F(args));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ namespace OpenRA.Launcher
|
|||||||
ExceptionHandler.HandleFatalError(e);
|
ExceptionHandler.HandleFatalError(e);
|
||||||
return (int)RunStatus.Error;
|
return (int)RunStatus.Error;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Log.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ namespace OpenRA.Server
|
|||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Run(args);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Log.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Run(string[] args)
|
||||||
{
|
{
|
||||||
var arguments = new Arguments(args);
|
var arguments = new Arguments(args);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ namespace OpenRA
|
|||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Run(args);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Log.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Run(string[] args)
|
||||||
{
|
{
|
||||||
var engineDir = Environment.GetEnvironmentVariable("ENGINE_DIR");
|
var engineDir = Environment.GetEnvironmentVariable("ENGINE_DIR");
|
||||||
if (!string.IsNullOrEmpty(engineDir))
|
if (!string.IsNullOrEmpty(engineDir))
|
||||||
|
|||||||
Reference in New Issue
Block a user