Windows应用商店应用程序 - C# - 如何处理多线程应用程序调试日志记录

时间:2014-03-19 11:21:20

标签: c# multithreading file logging windows-store-apps

我试图编写一个日志类来记录一个文件,但由于不同的线程同时尝试登录,我不断遇到日志记录问题。

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;
        }
    }

我该怎么做才能确保所有线程都可以登录到文件?

3 个答案:

答案 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;没有更多的多线程问题

  • 添加到队列非常快(微秒)并且很难锁定,而写入文件不仅会产生多线程问题,而且可能会造成毫秒延迟甚至异常。

  • 日志记录可以从第一行代码开始。消息被写入队列,只有在文件系统准备好后才会清空