10

我的 C# winforms 4.0 应用程序一直在使用线程安全的流编写器来执行内部调试日志信息。当我的应用程序打开时,它会删除该文件,然后重新创建它。当应用程序关闭时,它会保存文件。

我想做的是修改我的应用程序,以便它执行附加而不是替换。这是一个简单的修复。

但是,这是我的问题:

我想保持我的日志文件最大 10 兆字节。我的约束很简单。当你去关闭文件时,如果文件大于 10 兆,剪掉前 10%。

有没有“更好”的方法然后执行以下操作:

  1. 关闭文件
  2. 检查文件是否 > 10 兆
  3. 如果是,请打开文件
  4. 解析整个事情
  5. 剔除前 10%
  6. 写回文件

编辑:好吧,我最终推出了自己的(如下所示)将公开迁移到 Log4Net 的建议是一个很好的建议,但是学习新库并移动我所有的日志语句(数千个)所花费的时间不是对于我试图做的小改进来说,时间有效。

  private static void PerformFileTrim(string filename)
  {
     var FileSize = Convert.ToDecimal((new System.IO.FileInfo(filename)).Length);

     if (FileSize > 5000000)
     {
        var file = File.ReadAllLines(filename).ToList();
        var AmountToCull = (int)(file.Count * 0.33); 
        var trimmed = file.Skip(AmountToCull).ToList();
        File.WriteAllLines(filename, trimmed);
     }
  }
4

6 回答 6

7

我对此进行了一次研究,但从未提出任何建议,但我可以在这里为您提供 B 计划:

我使用下面的选择来保留最多 3 个日志文件。首先,创建日志文件 1 并将其附加到。当它超过 maxsize 时,会创建 log 2 和更高版本的 log 3。当 log 3 太大时, log 1 被删除,剩余的 log 被推下堆栈。

string[] logFileList = Directory.GetFiles(Path.GetTempPath(), "add_all_*.log", SearchOption.TopDirectoryOnly);
if (logFileList.Count() > 1)
{
    Array.Sort(logFileList, 0, logFileList.Count());
}

if (logFileList.Any())
{
    string currFilePath = logFileList.Last();
    string[] dotSplit = currFilePath.Split('.');
    string lastChars = dotSplit[0].Substring(dotSplit[0].Length - 3);
    ctr = Int32.Parse(lastChars);
    FileInfo f = new FileInfo(currFilePath);

    if (f.Length > MaxLogSize)
    {
        if (logFileList.Count() > MaxLogCount)
        {
            File.Delete(logFileList[0]);
            for (int i = 1; i < MaxLogCount + 1; i++)
            {
                Debug.WriteLine(string.Format("moving: {0} {1}", logFileList[i], logFileList[i - 1]));
                File.Move(logFileList[i], logFileList[i - 1]); // push older log files back, in order to pop new log on top
            }
        }
        else
        {
            ctr++;
        }
    }
}
于 2013-06-05T22:00:23.453 回答
3

这里的解决方案并不适合我。我拿了 user3902302 的答案,这又是基于 bigtech 的答案并写了一个完整的课程。另外,我没有使用 StreamWriter,您可以更改一行(针对 StreamWrite 等效项的 AppendAllText)。

几乎没有错误处理(例如,当它失败时重试访问,尽管锁应该捕获所有内部并发访问)。

对于一些以前不得不使用像 log4net 或 nlog 这样的大型解决方案的人来说,这可能就足够了。(而且 log4net RollingAppender 甚至不是线程安全的,这个是。:))

public class RollingLogger
{
    readonly static string LOG_FILE = @"c:\temp\logfile.log";
    readonly static int MaxRolledLogCount = 3;
    readonly static int MaxLogSize = 1024; // 1 * 1024 * 1024; <- small value for testing that it works, you can try yourself, and then use a reasonable size, like 1M-10M

    public static void LogMessage(string msg)
    {
        lock (LOG_FILE) // lock is optional, but.. should this ever be called by multiple threads, it is safer
        {
            RollLogFile(LOG_FILE);
            File.AppendAllText(LOG_FILE, msg + Environment.NewLine, Encoding.UTF8);
        }
    }

    private static void RollLogFile(string logFilePath)
    {
        try
        {
            var length = new FileInfo(logFilePath).Length;

            if (length > MaxLogSize)
            {
                var path = Path.GetDirectoryName(logFilePath);
                var wildLogName = Path.GetFileNameWithoutExtension(logFilePath) + "*" + Path.GetExtension(logFilePath);
                var bareLogFilePath = Path.Combine(path, Path.GetFileNameWithoutExtension(logFilePath));
                string[] logFileList = Directory.GetFiles(path, wildLogName, SearchOption.TopDirectoryOnly);
                if (logFileList.Length > 0)
                {
                    // only take files like logfilename.log and logfilename.0.log, so there also can be a maximum of 10 additional rolled files (0..9)
                    var rolledLogFileList = logFileList.Where(fileName => fileName.Length == (logFilePath.Length + 2)).ToArray();
                    Array.Sort(rolledLogFileList, 0, rolledLogFileList.Length);
                    if (rolledLogFileList.Length >= MaxRolledLogCount)
                    {
                        File.Delete(rolledLogFileList[MaxRolledLogCount - 1]);
                        var list = rolledLogFileList.ToList();
                        list.RemoveAt(MaxRolledLogCount - 1);
                        rolledLogFileList = list.ToArray();
                    }
                    // move remaining rolled files
                    for (int i = rolledLogFileList.Length; i > 0; --i)
                        File.Move(rolledLogFileList[i - 1], bareLogFilePath + "." + i + Path.GetExtension(logFilePath));
                    var targetPath = bareLogFilePath + ".0" + Path.GetExtension(logFilePath);
                    // move original file
                    File.Move(logFilePath, targetPath);
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.ToString());
        }
    }
}

编辑:
因为我刚刚注意到你问了一个稍微不同的问题:如果你的线条大小差异很大,这将是一个变化(虽然在 90% 的情况下不会比你的有所改善,并且可能会稍微快一点,还引入了一个新的未处理错误(\n 不存在)):

    private static void PerformFileTrim(string filename)
    {
        var fileSize = (new System.IO.FileInfo(filename)).Length;

        if (fileSize > 5000000)
        {
            var text = File.ReadAllText(filename);
            var amountToCull = (int)(text.Length * 0.33);
            amountToCull = text.IndexOf('\n', amountToCull);
            var trimmedText = text.Substring(amountToCull + 1);
            File.WriteAllText(filename, trimmedText);
        }
    }
于 2015-10-21T16:09:37.960 回答
2

这来自bigtech的回答:

private static string RollLogFile()
    {
        string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        string appName = Path.GetFileNameWithoutExtension(Environment.GetCommandLineArgs()[0]);
        string wildLogName = string.Format("{0}*.log",appName);

        int fileCounter = 0;
        string[] logFileList = Directory.GetFiles(path, wildLogName, SearchOption.TopDirectoryOnly);
        if (logFileList.Length > 0)
        {
            Array.Sort(logFileList, 0, logFileList.Length);
            fileCounter = logFileList.Length - 1;
            //Make sure we apply the MaxLogCount (but only once to reduce the delay) 
            if (logFileList.Length > MaxLogCount)
            {
                //Too many files - remove one and rename the others
                File.Delete(logFileList[0]);
                for (int i = 1; i < logFileList.Length; i++)
                {
                    File.Move(logFileList[i], logFileList[i - 1]); 
                }
                --fileCounter;
            }

            string currFilePath = logFileList[fileCounter];
            FileInfo f = new FileInfo(currFilePath);
            if (f.Length < MaxLogSize)
            {
                //still room in the current file
                return currFilePath;
            }
            else
            {
                //need another filename
                ++fileCounter;                  
            }

        }
        return string.Format("{0}{1}{2}{3:00}.log", path, Path.DirectorySeparatorChar, appName, fileCounter);
    }

用法:

string logFileName = RollLogFile();
using (StreamWriter sw = new StreamWriter(logFileName, true))
{
    sw.AutoFlush = true;
    sw.WriteLine(string.Format("{0:u} {1}", DateTime.Now, message));
}
于 2015-03-17T20:55:18.117 回答
2

此功能将允许您根据工作日轮换日志。第一次我们的应用程序将在星期一启动,将检查星期一日期的任何现有条目,如果今天尚未初始化,将丢弃旧条目并重新初始化新文件。从那天开始,文件将继续将文本附加到同一个日志文件中。

因此,总共将创建 7 个日志文件。调试-Mon.txt,调试-Tue.txt...

它还将添加实际记录消息的方法名称以及日期时间。对于一般用途非常有用。

private void log(string text)
        {
            string dd = DateTime.Now.ToString("yyyy-MM-dd");
            string mm = DateTime.Now.ToString("ddd");

            if (File.Exists("debug-" + mm + ".txt"))
            {
                String contents = File.ReadAllText("debug-" + mm + ".txt");


                if (!contents.Contains("Date: " + dd))
                {
                    File.Delete("debug-" + mm + ".txt");
                }
            }

            File.AppendAllText("debug-" + mm + ".txt", "\r\nDate: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:s") + " =>\t" + new System.Diagnostics.StackFrame(1, true).GetMethod().Name + "\t" + text);

        }
于 2017-07-11T07:56:46.610 回答
2

我喜欢 greggorob64 的解决方案,但也想压缩旧文件。除了将旧文件压缩为 zip 的部分之外,这包含您需要的一切,您可以在此处找到:Create zip file in memory from bytes (text with random encoding)

static int iMaxLogLength = 2000; // Probably should be bigger, say 200,000
static int KeepLines = 5; // minimum of how much of the old log to leave

public static void ManageLogs(string strFileName)
{
    try
    {
        FileInfo fi = new FileInfo(strFileName);
        if (fi.Length > iMaxLogLength) // if the log file length is already too long
        {
            int TotalLines = 0;
                var file = File.ReadAllLines(strFileName);
                var LineArray = file.ToList();
                var AmountToCull = (int)(LineArray.Count - KeepLines);
                var trimmed = LineArray.Skip(AmountToCull).ToList();
                File.WriteAllLines(strFileName, trimmed);
                string archiveName = strFileName + "-" + DateTime.Now.ToString("MM-dd-yyyy") + ".zip";
                File.WriteAllBytes(archiveName, Compression.Zip(string.Join("\n", file)));
        }

    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed to write to logfile : " + ex.Message);
    }
}

我将此作为我的应用程序的初始化/重新初始化部分的一部分,因此它每天运行几次。

ErrorLogging.ManageLogs("Application.log");
于 2019-02-08T16:51:25.043 回答
1

我正在查看win32 api,我什至不确定是否可以使用本机win32 vfs调用来做到这一点,更不用说通过.Net了。

关于我唯一的解决方案是使用内存映射文件并手动移动数据,.Net 似乎从 .Net 4.0 开始支持。

内存映射文件

于 2013-06-05T21:39:20.613 回答