AlphaLog





5.00/5 (1投票)
一个用于过滤和合并大型文本文件的工具。
引言
AlphaLog 是一个开发人员工具,能够解析、合并和过滤各种日志文件。该工具支持插件模型,允许开发人员创建与其特定日志文件类型兼容的插件。
该工具仍处于积极开发中,还有一些功能尚缺失。但是,我觉得现在是开始记录该工具的好时机。我将随着工具中功能的添加而更新本文。
背景
在我的工作中,我遇到过一些场景,需要研究多个系统生成的日志文件,以了解导致错误的事件链。这些系统通常会生成各种格式的日志文件,这使得合并文件变得具有挑战性。此外,我经常发现当将系统日志设置为调试模式时,生成的信息量使得分析日志文件变得困难。
通常我只对特定时间范围内的特定日志条目感兴趣。因此,我需要一个工具,能够合并和过滤以各种不同格式生成的日志条目。
使用代码
下图提供了 AlphaLog 结构的基本概述
我已尽力使 AlphaLog 的 API 尽可能简单。
- 当 AlphaLog 获得一个新的日志文件时,它会查询每个
Columniser Descriptor
以找到一个有效的列分析器。 - 一旦找到匹配项,就会为该特定文件创建一个新的 Columniser 实例。
- 一个或多个订阅者可以订阅日志文件的更新。
- 当 AlphaLog 上的
Update
方法被调用时,日志文件会由列分析器处理,并且任何日志条目都会推送给订阅者。
因此,AlphaLogParser
负责管理列分析器和订阅者的生命周期。
列分析器
在日志文件可以合并或过滤之前,必须将它们解析为通用格式。这就是引入列分析器概念的地方。列分析器本质上是一个插件,开发人员创建它来处理他们的日志文件类型。AlphaLog 已经开发了一些通用列分析器。它们如下:
RegexColumniser:
一种旨在解析文本文件的列分析器。该列分析器利用正则表达式定义的命名组将日志条目解析为字典。CSVColumniser
: 一种旨在解析 CSV 文件的列分析器。CSV 文件必须定义标题,因为列标题用于将日志条目解析为字典。XMLColumniser
: 一种旨在通过一组预定义的 XPath 规则解析 XML 文件的列分析器。
用户还可以定义专有的列分析器来解析定制的日志文件。AlphaLog 代码库中一个这样的例子是 log4net XML 列。该列分析器不需要任何配置,它只是解析 log4net XML 格式的日志文件。CsvColumniser 的示例类图如下:
名称 | 描述 |
---|---|
IColumniserDescriptor | 列分析器描述符的接口。 |
ColumnizerDescriptor | ColumniserDescriptors 的抽象基类。 |
CsvColumniserDescriptor | CSV 文件描述符的实现。 |
IColumniser | 列分析器的接口 |
列分析器 | Columnisers 的抽象基类。 |
CsvColumniser | CSV 文件的列分析器。 |
UI 插件
在定义新的列分析器时,用户可以选择定义一个视图,允许配置该列分析器。
这允许开发人员为出现在设置窗口中的 UI 创建一个插件。这些列分析器可以在下图中列分析器节点下看到。
AlphaLogParser
AplhaLogParser 负责为提供的文件选择一个列分析器,并对观察到的文件执行类型检查。
当添加文件时,解析器尝试使用 Columniser Descriptors 选择一个匹配的 Columniser。然后解析器确保所选列分析器的数据类型不与现有列分析器冲突。
public Details Add(string file) { if (this.disposed) { throw new ObjectDisposedException(typeof(AlphaLogParser).Name); } IColumnizer tempColumniser = this.GetColumniser(file); tempColumniser.Subscribe(this.alphaLogSubscribers.AsObserver()); var columniserDetails = new Details(tempColumniser.File, tempColumniser.Name); if (this.UpdateMappingTable(tempColumniser)) { columniserDetails = new ErrorDetails(file, "The data types conflict with another file."); if (this.progress != null) { this.progress.Report(ProcessState.Error, 0); } } return columniserDetails; }
Remove
方法会删除映射到指定文件的列分析器,并重建用于类型检查的映射表。
public void Remove(string file) { if (this.disposed) { throw new ObjectDisposedException(typeof(AlphaLogParser).Name); } if (this.activeColumnisers.ContainsKey(file)) { this.activeColumnisers[file].Dispose(); this.activeColumnisers.Remove(file); } if (this.RebuildMappingTable()) { if (this.progress != null) { this.progress.Report(ProcessState.Error, 0); } } else { if (this.progress != null) { this.progress.Report(ProcessState.None, 0); } } }
关注点
在开发 AlphaLog 的过程中,必须克服许多技术挑战。
匹配器
将来,我计划为 AlphaLog 的过滤阶段引入一个插件模型。然而,在第一个实现中,我为过滤阶段构建了一个简单的解释器。
Matchers 库是 AlphaLog 中一个有趣的部分,因为它本质上是一个简单的规则引擎,可以在许多不同的项目中找到应用。Matchers 允许以抽象语法树的形式定义规则,该抽象语法树可以序列化为 XML。提供了一个 WPF 用户界面,允许用户定义规则。规则引擎的类图如下:
名称 | 描述 |
---|---|
IMatch | Matchers 的接口。 |
MatchBase | Matchers 的抽象基类。 |
MatchFactory | Matchers 的工厂。 |
MatcherType | 可用 Matchers 的枚举。 |
NumericBase | 基于数字的 Matchers 的抽象基类。 |
TextBase | 基于字符串/文本的 Matchers 的抽象基类。 |
GroupBase | 聚合/分组其他 Matchers 的 Matchers 的抽象基类。 |
在 C# 代码中定义的简单规则如下所示。
new And(
new ContainsMatcher("Hello", "Message", false),
new ExactMatcher("Hello", "Message", false));
显然,在 C# 代码中定义规则并不是特别有用,因为你可以直接编写代码。真正的优势在于能够从 XML 序列化和反序列化规则。
<And>
<Contains searchField="Message" searchValue="Hello" caseSensitive="false" />
<Exact searchField="Message" searchValue="World" caseSensitive="false"/>
</And
动态列
我在开发 AlphaLog 时遇到的更具挑战性的问题之一是动态定义数据网格列。由于 AlphaLog 可以处理多种日志文件类型,因此不可能为数据网格定义一组固定的列。因此,我需要在运行时生成列项。
我发现以下文章为我指明了正确的方向。
- http://www.reimers.dk/jacob-reimers-blog/auto-generating-datagrid-columns-from-dynamicobjects
- https://codeproject.org.cn/Articles/575856/Windows-Forms-Binding-through-ITypedList-interface
本质上,WPF Datagrid 在使用“自动生成列”功能时,会使用反射来确定需要生成哪些列。由于我们无法创建强类型类,因此我们必须覆盖在反射日志条目对象时返回的信息。
实现 ITypedList 接口允许您提供集合的不同视图。由于单个日志条目可以有不同数量的列,DynamicItemCollection 类会合并列信息。
public class DynamicItemCollection<T> : ObservableCollection<T>, ITypedList where T : PropertyDescriptorCollection, ICustomTypeDescriptor { /// <summary> /// Initializes a new instance of the <see cref="DynamicItemCollection{T}"/> class. /// </summary> /// <param name="toList"> /// The to list. /// </param> public DynamicItemCollection(List<T> toList) : base(toList) { } /// <summary> /// The get item properties. /// </summary> /// <param name="listAccessors"> /// The list accessors. /// </param> /// <returns> /// The <see cref="PropertyDescriptorCollection"/>. /// </returns> public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { var set = new HashSet<PropertyDescriptor>(); foreach (T item in this.Items) { foreach (object i in item) { set.Add((PropertyDescriptor)i); } } return new PropertyDescriptorCollection(set.ToArray()); } /// <summary> /// The get list name. /// </summary> /// <param name="listAccessors"> /// The list accessors. /// </param> /// <returns> /// The <see cref="string"/>. /// </returns> public string GetListName(PropertyDescriptor[] listAccessors) { return null; } }
PropertyDescriptorCollection
LogDescriptorCollection 为单个日志条目提供了一个包装器。
public class LogDescriptorCollection : PropertyDescriptorCollection, ICustomTypeDescriptor
{
/// <summary>
/// The log items.
/// </summary>
private readonly Dictionary<string, object> logItems = new Dictionary<string, object>();
/// <summary>
/// Initializes a new instance of the <see cref="LogDescriptorCollection"/> class.
/// </summary>
/// <param name="dictionary">
/// The dictionary.
/// </param>
public LogDescriptorCollection(Dictionary<string, object> dictionary)
: base(
dictionary.Where(k => k.Value != null)
.Select(k => new LogItemDescriptor(k.Key, k.Value.GetType()))
.ToArray())
{
this.logItems = dictionary;
}
...
/// <summary>
/// Returns the properties for this instance of a component.
/// </summary>
/// <returns>
/// A <see cref="T:System.ComponentModel.PropertyDescriptorCollection" /> that represents the properties for this
/// component instance.
/// </returns>
public PropertyDescriptorCollection GetProperties()
{
var col = new PropertyDescriptorCollection(null);
foreach (var item in this.logItems.Where(k => k.Value != null))
{
col.Add(new LogItemDescriptor(item.Key, item.Value.GetType()));
}
return col;
}
...
}
PropertyDescriptor
PropertyDescriptor 类公开一个字典条目(日志项),用于反射目的。
public class LogItemDescriptor : PropertyDescriptor
{
/// <summary>
/// The m_prop type.
/// </summary>
private readonly Type type;
/// <summary>
/// Initializes a new instance of the <see cref="LogItemDescriptor"/> class.
/// </summary>
/// <param name="name">
/// The name.
/// </param>
/// <param name="type">
/// The type.
/// </param>
public LogItemDescriptor(string name, Type type)
: base(name, null)
{
this.type = type;
}
...
/// <summary>
/// When overridden in a derived class, gets the current value of the property on a component.
/// </summary>
/// <returns>
/// The value of a property for a given component.
/// </returns>
/// <param name="component">
/// The component with the property for which to retrieve the value.
/// </param>
public override object GetValue(object component)
{
return ((LogDescriptorCollection)component).Items[this.Name];
}
/// <summary>
/// When overridden in a derived class, sets the value of the component to a different value.
/// </summary>
/// <param name="component">
/// The component with the property value that is to be set.
/// </param>
/// <param name="value">
/// The new value.
/// </param>
public override void SetValue(object component, object value)
{
((LogDescriptorCollection)component).Items[this.Name] = value;
}
...
}
应用程序设置
由于 AlphaLog 的性质,需要存储大量的应用程序设置。我设计了一个简单的存储库,它以 XML 格式存储应用程序设置。这可以在下面的类图中看到
名称 | 描述 |
---|---|
ISettingsManager | 主设置存储库的接口。 |
SettingsManager | 设置存储库的具体实现。 |
IRepository | 可以从 XML 重构的对象的接口。 |
存储库 | 存储库的抽象基类。 |
IPersist | 可以序列化为 XML 的对象的接口 |
MatcherDatabase | 存储 MatcherItem 对象的存储库。 |
MatcherItems | 可以存储在 MatcherDatabase 存储库中的项。 |
SettingsManager 类基于服务定位器模式。SettingsManager 是存储库的存储库。GetSettings 方法允许获取由其类型指定的存储库。GetSettings 方法的代码如下:
public TSettings GetsSetting<TSettings>() where TSettings : IRepository, new() { IRepository settings = null; if (!this.settingsCollection.TryGetValue(typeof(TSettings), out settings)) { var newSettings = new TSettings(); XElement settingsElement = this.GetElement(newSettings.SettingsName); if (settingsElement != null) { newSettings.Initialize(settingsElement); } this.settingsCollection[typeof(TSettings)] = newSettings; return newSettings; } return (TSettings)settings; }
给定一个 settings manager 实例,MatcherDatabase 存储库可以按如下方式获取:
[ImportingConstructor] public ManageMatcherVM(ISettingsManager settingsManager) : base("Matchers") { this.settings = settingsManager.GetsSetting<FieldSettings>(); this.matchers = settingsManager.GetsSetting<MatcherDatabase>(); ... }
源代码
如果您想查看库的源代码和演示应用程序,可以在我的 Bit Bucket 站点上找到代码。
https://bitbucket.org/chrism233/alphalog
git 存储库地址如下:
https://chrism233@bitbucket.org/chrism233/alphalog.git
历史
日期 | 变更 |
---|---|
07/01/2015 | 初始发布。 |
06/10/2015 | 上传的包含可执行文件的演示 zip 文件。 |