65.9K
CodeProject 正在变化。 阅读更多。
Home

AlphaLog

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2015 年 10 月 2 日

CPOL

6分钟阅读

viewsIcon

12275

downloadIcon

162

一个用于过滤和合并大型文本文件的工具。

引言

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 可以处理多种日志文件类型,因此不可能为数据网格定义一组固定的列。因此,我需要在运行时生成列项。

我发现以下文章为我指明了正确的方向。

本质上,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 文件。

 

© . All rights reserved.