You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Serilog文件接收器写入CSV时大量空字符问题的排查与解决

问题描述

我们在多台PC上部署了一款应用,用户每次点击(鼠标或键盘)时,该应用会将相关信息采集至CSV文件。

问题表现:部分PC上会随机出现CSV文件中间插入大量空字符(<0x00>)的情况,导致两段不同进程的采集数据被空字符隔断,中间数据丢失。

应用工作流程
  • 用户点击时,通过以下代码异步调用CollectInfo方法:
    Task.Factory.StartNew(() => 
    { 
        eventInfoInstanceFromDI.CollectInfo(CancellationSource.Token, otherParams);
    }); 
    
  • CollectInfo方法采集当前用户操作进程的信息,最终调用AddRecord方法写入CSV:
    public void CollectInfo(CancellationToken token, otherParams)
    {
        lock (lockObject)
        {
            try
            { ... }
            catch
            { ... }
            finally
            {
                ...
                ...
                containerInstanceFromDI.AddRecord(data);
            }
        }
    }
    
  • AddRecord与AggregateData方法实现数据拼接与写入:
    /// <summary>
    /// Aggregates collected info fields into a string, separated by ';'
    /// </summary>
    private string AggregateData(ExportFields data, bool addHeaders, bool addValues, string separator)
    {
        StringBuilder resultingString = new StringBuilder();
        foreach (PropertyInfo propertyInfo in data.GetType().GetProperties())
        {
            if (_appSettingsOptions.ExcludeFields.Contains(propertyInfo.Name, StringComparer.OrdinalIgnoreCase) is false)
            {
                var val = propertyInfo.GetValue(data);
                MaxLengthAttribute attrMaxLength = propertyInfo.GetCustomAttributes(typeof(MaxLengthAttribute), false).FirstOrDefault() as MaxLengthAttribute;
                var maxLength = attrMaxLength?.Length ?? _fieldMaxLengthDefault;
    
                String valstr = Regex.Replace(val != null ? val.ToString() : "", "[\r\n\t]", "").Replace(";", "|").Trim();
                valstr = valstr.Length > maxLength ? valstr.Substring(0, maxLength) + " ..." : valstr;
                resultingString.Append((addHeaders ? propertyInfo.Name + "" : "") + (addValues ? valstr : "") + separator);
            }
        }
        return resultingString.ToString();
    }
    
    /// <summary>
    /// Adds collected info to .csv file
    /// </summary>
    public void AddRecord(ExportFields data, Dictionary<String, Object> extParams = null)
    {
        try
        {
            if (!string.IsNullOrEmpty(ApplicationConfiguration.FullPathToCsvFile))
            {
                if (_appSettingsOptions.ExcludeProcs.Contains(data.ProcessName, StringComparer.OrdinalIgnoreCase) is false)
                {
                    bool append = extParams != null && extParams.ContainsKey("Append") && extParams["Append"].GetType() == typeof(bool) && (bool)extParams["Append"];
                    bool initHeaders = extParams != null && extParams.ContainsKey("InitHeaders") && extParams["InitHeaders"].GetType() == typeof(bool) && (bool)extParams["InitHeaders"];
                    string dataLine = initHeaders ? AggregateData(data, true, false, ";") : AggregateData(data, false, true, ";");
    
                    _loggerCSV.Information(dataLine);
                }
            }
        }
        catch (Exception ex)
        {
            String errorMessage = $"Can't add a new record to CSV file. Full Path To Csv File: {ApplicationConfiguration.FullPathToCsvFile}.";
    
            _loggerMain.Error("{ErrorMessage}. Error in {MethodInClass}. Error: {@Exception}.", errorMessage, nameof(CSVContainer) + " => " + nameof(AddRecord), ex.ToString());
        }
    }
    
  • 拼接后的字符串通过Serilog文件接收器写入CSV文件。
已排查情况
  • 曾在Serilog官方仓库提问,得到的回复指向底层或内存问题
  • 参考过技术社区的类似空字符问题案例
根因分析

结合现象和已排查信息,可能的根因包括:

  1. Serilog文件接收器异常:旧版本的Serilog文件接收器在处理文件滚动、多线程写入时可能存在BUG,导致写入空字符或文件指针偏移。
  2. 内存 corruption:部分PC的硬件内存存在错误,或应用存在内存泄漏/野指针问题,导致内存中的字符串被空字符污染后写入文件。
  3. 文件IO冲突:虽然CollectInfo方法加了锁,但如果存在多个进程(或多个应用实例)同时写入同一个CSV文件,会导致文件内容被破坏,插入空字符。
  4. 字符串拼接异常:AggregateData方法中处理特殊类型的属性值时,可能意外引入空字符(当前代码逻辑中已做替换,概率较低)。
修复/规避方案
  1. 升级Serilog至最新稳定版:排查是否是旧版本Serilog的已知BUG,升级后验证问题是否消失。
  2. 强化文件写入的线程/进程安全:
    • 确认是否存在多进程写入同一文件的场景,若有则改为每个进程写入独立文件,或通过全局锁控制写入。
    • 在AddRecord方法外层增加全局锁,确保同一时间只有一个写入操作。
  3. 排查硬件内存问题:使用Windows内存诊断工具检测对应PC的内存是否存在硬件故障,排除物理内存错误。
  4. 替换写入方式验证:暂时绕过Serilog,改用File.AppendAllText配合全局锁的方式写入CSV,观察是否还出现空字符问题,以此排除Serilog的问题。
  5. 增加数据校验逻辑:在AddRecord方法中,写入前检查dataLine是否包含空字符,若存在则记录日志并跳过写入:
    if (dataLine.Contains('\0'))
    {
        _loggerMain.Warning("Data line contains null characters, skipped writing. Line: {DataLine}", dataLine);
        return;
    }
    
  6. 优化字符串拼接逻辑:在AggregateData中,对属性值的处理增加空字符过滤,确保生成的字符串无空字符:
    String valstr = Regex.Replace(val != null ? val.ToString() : "", "[\r\n\t\0]", "").Replace(";", "|").Trim();
    

内容的提问来源于stack exchange,提问作者RomanP8051

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:18:27