我试图编写一个日志类来记录一个文件,但由于不同的线程同时尝试登录,我不断遇到日志记录问题。
A first chance exception of type 'System.UnauthorizedAccessException' occurred in mscorlib.dll
System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Log.Diag.<DebugPrint>d__0.MoveNext()
这是我的代码:
public static async void DebugPrint(string msg, LogLevel level)
{
if (ShouldLog(level))
{
#if DEBUG
// Only do this in debug
Debug.WriteLine(msg);
#endif
#if !DEBUG // Never crash in release build
try
{
#endif
if (sFile == null && !(await GetLogFile()))
{
throw new FileNotFoundException("Cannot create ms-appdata:///local/log.txt");
}
try
{
await Windows.Storage.FileIO.AppendTextAsync(sFile, ComposeMessage(msg, level));
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#if !DEBUG
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#endif
}
}
/// <summary>
/// Initialise the log file.
/// </summary>
/// <returns></returns>
private async static Task<bool> GetLogFile()
{
try
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
sFile = await localFolder.CreateFileAsync("log.txt", CreationCollisionOption.OpenIfExists);
return true;
}
catch (Exception)
{
return false;
}
}
我该怎么做才能确保所有线程都可以登录到文件?
答案 0 :(得分:1)
以下是我使用事件追踪的方法。
Task.cs
sealed class LogEventSource : EventSource
{
public static LogEventSource Log = new LogEventSource();
[Event(1, Level = EventLevel.LogAlways)]
public void Debug(string message)
{
this.WriteEvent(1, message);
}
}
/// <summary>
/// Storage event listner to do thread safe logging to a file.
/// </summary>
sealed class StorageFileEventListener : EventListener
{
private object syncObj = new object();
private List<string> logLines;
private StorageFile logFile;
private ThreadPoolTimer periodicTimer;
public StorageFileEventListener()
{
Debug.WriteLine("StorageFileEventListener for {0}", GetHashCode());
logLines = new List<string>();
}
// Should be called right after the constructor (since constructors can't have async calls)
public async Task InitializeAsync()
{
logFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("logs.txt", CreationCollisionOption.OpenIfExists);
// We don't want to write to disk every single time a log event occurs, so let's schedule a
// thread pool task
periodicTimer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// We have to lock when writing to disk as well, otherwise the in memory cache could change
// or we might try to write lines to disk more than once
lock (syncObj)
{
if (logLines.Count > 0)
{
// Write synchronously here. We'll never be called on a UI thread and you
// cannot make an async call within a lock statement
FileIO.AppendLinesAsync(logFile, logLines).AsTask().Wait();
logLines = new List<string>();
}
}
CheckLogFile();
}, TimeSpan.FromSeconds(5));
}
private async void CheckLogFile()
{
BasicProperties p = await logFile.GetBasicPropertiesAsync();
if(p.Size > (1024 * 1024))
{
// TODO: Create new log file and compress old.
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// This could be called from any thread, and we want our logs in order, so lock here
lock (syncObj)
{
logLines.Add((string)eventData.Payload[0]);
}
}
}
包含在日志记录类中。
/// <summary>
/// A static class for help with debugging and logging.
/// </summary>
public static class Log
{
public enum LogLevel {
NONE = 0,
FATAL,
ERROR,
INFO,
DEBUG,
VERBOSE,
TRACE
};
private static StorageFileEventListener eventListener;
#if DEBUG
public static LogLevel logLevel = LogLevel.DEBUG;
#else
public static LogLevel logLevel = LogLevel.NONE;
#endif
/// <summary>
/// Print out the debug message.
/// </summary>
/// <param name="msg">Message to print</param>
/// <param name="level">Debug level of message</param>
public async static void DebugPrint(string msg, LogLevel level)
{
if (ShouldLog(level))
{
msg = ComposeMessage(msg, level);
#if DEBUG
// Only do this in debug
Debug.WriteLine(msg);
#endif
#if !DEBUG // Never crash in release build
try
{
#endif
if (eventListener == null)
{
eventListener = new StorageFileEventListener();
eventListener.EnableEvents(LogEventSource.Log, EventLevel.LogAlways);
await eventListener.InitializeAsync();
}
LogEventSource.Log.Debug(msg);
#if !DEBUG
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#endif
}
}
/// <summary>
/// Construc the formatted log message
/// </summary>
/// <param name="msg">Main message</param>
/// <param name="level">Log level</param>
/// <returns>Formated message</returns>
private static string ComposeMessage(string msg, LogLevel level)
{
return DateTime.Now.ToString(@"M/d/yyyy hh:mm:ss.fff tt") + " [" + Environment.CurrentManagedThreadId.ToString("X4") + "] " + LevelToString(level) + " " + msg;
}
/// <summary>
/// Get the string alias for a log level.
/// </summary>
/// <param name="level">The log level</param>
/// <returns>String representation of the log level.</returns>
private static string LevelToString(LogLevel level)
{
string res = "NOT FOUND";
switch (level)
{
case LogLevel.NONE:
throw new Exception("You should not log at this level (NONE)");
case LogLevel.FATAL: res = "FATAL"; break;
case LogLevel.ERROR: res = "ERROR"; break;
case LogLevel.INFO: res = "INFO"; break;
case LogLevel.DEBUG: res = "DEBUG"; break;
case LogLevel.VERBOSE: res = "VERBOSE"; break;
case LogLevel.TRACE: res = "TRACE"; break;
}
return res;
}
/// <summary>
/// Check the passed log level against the current log level
/// to see if the message should be logged.
/// </summary>
/// <param name="level">Log level to check against</param>
/// <returns>True is should be logeed otherwise false.</returns>
private static bool ShouldLog(LogLevel level)
{
if (level <= logLevel)
return true;
else
return false;
}
}
用法:
Log.DebugPrint("Hello, Thread safe logger!", Log.LogLevel.DEBUG);
答案 1 :(得分:0)
为了避免并发问题,您需要使用locks。
答案 2 :(得分:0)
最好将所有消息写入队列并使用后台线程将队列写入文件。这有很多好处:
轻松实现多线程保存。只需锁定对队列的每次访问
只有1个帖子写入文件=&gt;没有更多的多线程问题
添加到队列非常快(微秒)并且很难锁定,而写入文件不仅会产生多线程问题,而且可能会造成毫秒延迟甚至异常。
日志记录可以从第一行代码开始。消息被写入队列,只有在文件系统准备好后才会清空