草庐IT

c# - 每个客户端和日期的单独日志文件和目录

coder 2024-06-03 原文

我有一个 windows TCP 服务,有很多设备连接到它,一个客户端可以有一个或多个设备。

要求:

每个客户端的单独文件夹,每个设备都有单独的日志文件。

所以像这样:

/MyService/25-04-2016/
    Client 1/
        Device1.txt 
        Device2.txt 
        Device3.txt 

    Client 2/
        Device1.txt 
        Device2.txt 
        Device3.txt 

现在我还没有使用像 log4netNLog 这样的第 3 方库,我有一个类可以处理这个问题。

public class xPTLogger : IDisposable
{
    private static object fileLocker = new object();

    private readonly string _logFileName;
    private readonly string _logFilesLocation;
    private readonly int _clientId;

    public xPTLogger() : this("General") { }

    public xPTLogger(string logFileName)
    {
        _clientId = -1;
        _logFileName = logFileName;
        _logFilesLocation = SharedConstants.LogFilesLocation; // D:/LogFiles/
    }

    public xPTLogger(string logFileName, int companyId)
    {
        _clientId = companyId;
        _logFileName = logFileName;
        _logFilesLocation = SharedConstants.LogFilesLocation;
    }

    public void LogMessage(MessageType messageType, string message)
    {
        LogMessage(messageType, message, _logFileName);
    }

    public void LogExceptionMessage(string message, Exception innerException, string stackTrace)
    {
        var exceptionMessage = innerException != null
                ? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", message, innerException.Message, stackTrace)
                : string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace);

        LogMessage(MessageType.Error, exceptionMessage, "Exceptions");
    }

    public void LogMessage(MessageType messageType, string message, string logFileName)
    {
        var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy");

        var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime);

        if (_clientId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _clientId); }


        var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt", logFileName);


        var msg = string.Format("{0} | {1} | {2}\r\n", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"), messageType, message);

        fullLogFile = GenerateLogFilePath(logFilesLocation, fullLogFile);

        LogToFile(fullLogFile, msg);
    }

    private string GenerateLogFilePath(string objectLogDirectory, string objectLogFileName)
    {
        if (string.IsNullOrEmpty(objectLogDirectory))
            throw new ArgumentNullException(string.Format("{0} location cannot be null or empty", "objectLogDirectory"));
        if (string.IsNullOrEmpty(objectLogFileName))
            throw new ArgumentNullException(string.Format("{0} cannot be null or empty", "objectLogFileName"));

        if (!Directory.Exists(objectLogDirectory))
            Directory.CreateDirectory(objectLogDirectory);
        string logFilePath = string.Format("{0}\\{1}", objectLogDirectory, objectLogFileName);
        return logFilePath;
    }

    private void LogToFile(string logFilePath, string message)
    {
        if (!File.Exists(logFilePath))
        {
            File.WriteAllText(logFilePath, message);
        }
        else
        {
            lock (fileLocker)
            {
                File.AppendAllText(logFilePath, message);
            }
        }
    }

    public void Dispose()
    {
        fileLocker = new object();
    }
}

然后我可以像这样使用它:

 var _logger = new xPTLogger("DeviceId", 12);

 _logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1));

上述类的问题在于,由于服务是多线程的,一些线程试图同时访问同一个日志文件导致抛出异常。

25-Apr-2016 13:07:00 | Error | Exception: The process cannot access the file 'D:\LogFiles\25-Apr-2016\0\LogFile.txt' because it is being used by another process.

这有时会导致我的服务崩溃。

如何让我的 Logger 类在多线程服务中工作?

编辑

记录器类的变化

public class xPTLogger : IDisposable
{
    private object fileLocker = new object();

    private readonly string _logFileName;
    private readonly string _logFilesLocation;
    private readonly int _companyId;

    public xPTLogger() : this("General") { }

    public xPTLogger(string logFileName)
    {
        _companyId = -1;
        _logFileName = logFileName;
        _logFilesLocation = SharedConstants.LogFilesLocation; // "D:\\MyLogs";
    }

    public xPTLogger(string logFileName, int companyId)
    {
        _companyId = companyId;
        _logFileName = logFileName;
        _logFilesLocation = SharedConstants.LogFilesLocation;
    }

    public void LogMessage(MessageType messageType, string message)
    {
        LogMessage(messageType, message, _logFileName);
    }

    public void LogExceptionMessage(string message, Exception innerException, string stackTrace)
    {
        var exceptionMessage = innerException != null
                ? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", message, innerException.Message, stackTrace)
                : string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace);

        LogMessage(MessageType.Error, exceptionMessage, "Exceptions");
    }

    public void LogMessage(MessageType messageType, string message, string logFileName)
    {
        if (messageType == MessageType.Debug)
        {
            if (!SharedConstants.EnableDebugLog)
                return;
        }

        var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy");

        var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime);

        if (_companyId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _companyId); }


        var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt", logFileName);


        var msg = string.Format("{0} | {1} | {2}\r\n", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"), messageType, message);

        fullLogFile = GenerateLogFilePath(logFilesLocation, fullLogFile);

        LogToFile(fullLogFile, msg);
    }

    private string GenerateLogFilePath(string objectLogDirectory, string objectLogFileName)
    {
        if (string.IsNullOrEmpty(objectLogDirectory))
            throw new ArgumentNullException(string.Format("{0} location cannot be null or empty", "objectLogDirectory"));
        if (string.IsNullOrEmpty(objectLogFileName))
            throw new ArgumentNullException(string.Format("{0} cannot be null or empty", "objectLogFileName"));

        if (!Directory.Exists(objectLogDirectory))
            Directory.CreateDirectory(objectLogDirectory);
        string logFilePath = string.Format("{0}\\{1}", objectLogDirectory, objectLogFileName);
        return logFilePath;
    }

    private void LogToFile(string logFilePath, string message)
    {
        lock (fileLocker)
        {
            try
            {
                if (!File.Exists(logFilePath))
                {
                    File.WriteAllText(logFilePath, message);
                }
                else
                {
                    File.AppendAllText(logFilePath, message);
                }
            }
            catch (Exception ex)
            {
                var exceptionMessage = ex.InnerException != null
                                ? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", ex.Message, ex.InnerException.Message, ex.StackTrace)
                                : string.Format("Exception: [{0}], Stack Trace: [{1}]", ex.Message, ex.StackTrace);

                var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, DateTime.UtcNow.ToString("dd-MMM-yyyy"));

                var logFile = GenerateLogFilePath(logFilesLocation, "FileAccessExceptions.txt");

                try
                {
                    if (!File.Exists(logFile))
                    {
                        File.WriteAllText(logFile, exceptionMessage);
                    }
                    else
                    {
                        File.AppendAllText(logFile, exceptionMessage);
                    }
                }
                catch (Exception) { }
            }

        }
    }

    public void Dispose()
    {
        //fileLocker = new object();
        //_logFileName = null;
        //_logFilesLocation = null;
        //_companyId = null;
    }
}

最佳答案

如果您不想使用现有的解决方案,在记录器中处理多线程写入的合理方法是使用队列。这是一个草图:

public class LogQueue : IDisposable {
    private static readonly Lazy<LogQueue> _isntance = new Lazy<LogQueue>(CreateInstance, true);
    private Thread _thread;
    private readonly BlockingCollection<LogItem> _queue = new BlockingCollection<LogItem>(new ConcurrentQueue<LogItem>());

    private static LogQueue CreateInstance() {
        var queue = new LogQueue();
        queue.Start();
        return queue;
    }

    public static LogQueue Instance => _isntance.Value;

    public void QueueItem(LogItem item) {
        _queue.Add(item);
    }

    public void Dispose() {
        _queue.CompleteAdding();
        // wait here until all pending messages are written
        _thread.Join();
    }

    private void Start() {
        _thread = new Thread(ConsumeQueue) {
            IsBackground = true
        };
        _thread.Start();
    }

    private void ConsumeQueue() {
        foreach (var item in _queue.GetConsumingEnumerable()) {
            try {
                // append to your item.TargetFile here                    
            }
            catch (Exception ex) {
                // do something or ignore
            }
        }
    }
}

public class LogItem {
    public string TargetFile { get; set; }
    public string Message { get; set; }
    public MessageType MessageType { get; set; }
}

然后在您的记录器类中:

private void LogToFile(string logFilePath, string message) {
    LogQueue.Instance.QueueItem(new LogItem() {
        TargetFile = logFilePath,
        Message = message
    });
}

这里我们将实际的日志记录委托(delegate)给单独的类,一个一个地写入日志消息,因此不会有任何多线程问题。这种方法的另一个好处是日志记录是异步发生的,因此不会减慢实际工作。

缺点是在进程崩溃的情况下你可能会丢失一些消息(不要认为这真的是个问题但还是要提一下)并且你使用单独的线程来异步记录。当只有一个线程时,这不是问题,但如果您为每个设备创建一个线程,那可能是(尽管没有必要 - 只需使用单个队列,除非您真的每秒写入大量消息)。

关于c# - 每个客户端和日期的单独日志文件和目录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36842836/

有关c# - 每个客户端和日期的单独日志文件和目录的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  10. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

随机推荐