idle_master_extended/Source/IdleMasterExtended/Logger.cs
2020-11-05 19:17:28 +01:00

109 lines
No EOL
3.1 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
namespace IdleMasterExtended
{
public static class Logger
{
private static readonly object LogLock = new object();
private static readonly string ExceptionPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @".\error.log";
public static void Exception(Exception ex, params string[] messages)
{
var contents = string.Concat(DateTime.Now, " ", string.Join(Environment.NewLine, messages),
Environment.NewLine, ex.ToString(), Environment.NewLine);
Write(contents, ExceptionPath);
}
private static void Write(string contents, string path)
{
Console.WriteLine(contents);
using (TimedLock.Lock(LogLock))
{
File.AppendAllText(path, contents, Encoding.UTF8);
}
}
}
// Thanks to Eric Gunnerson for recommending this be a struct rather
// than a class - avoids a heap allocation.
// Thanks to Change Gillespie and Jocelyn Coulmance for pointing out
// the bugs that then crept in when I changed it to use struct...
// Thanks to John Sands for providing the necessary incentive to make
// me invent a way of using a struct in both release and debug builds
// without losing the debug leak tracking.
public struct TimedLock : IDisposable
{
public static TimedLock Lock(object o)
{
return Lock(o, TimeSpan.FromSeconds(10));
}
public static TimedLock Lock(object o, TimeSpan timeout)
{
var tl = new TimedLock(o);
if (!Monitor.TryEnter(o, timeout))
{
#if DEBUG
GC.SuppressFinalize(tl.leakDetector);
#endif
throw new LockTimeoutException();
}
return tl;
}
private TimedLock(object o)
{
target = o;
#if DEBUG
leakDetector = new Sentinel();
#endif
}
private object target;
public void Dispose()
{
Monitor.Exit(target);
// It's a bad error if someone forgets to call Dispose,
// so in Debug builds, we put a finalizer in to detect
// the error. If Dispose is called, we suppress the
// finalizer.
#if DEBUG
GC.SuppressFinalize(leakDetector);
#endif
}
#if DEBUG
// (In Debug mode, we make it a class so that we can add a finalizer
// in order to detect when the object is not freed.)
private class Sentinel
{
~Sentinel()
{
// If this finalizer runs, someone somewhere failed to
// call Dispose, which means we've failed to leave
// a monitor!
Debug.Fail("Undisposed lock");
}
}
private Sentinel leakDetector;
#endif
}
public class LockTimeoutException : ApplicationException
{
public LockTimeoutException()
: base("Timeout waiting for lock")
{
}
}
}