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

