diff --git a/README.md b/README.md index 16bb004..ea66ee8 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,30 @@ -# YALV! - Yet Another Log4Net Viewer +# D-YALV! - (Dynamic) Yet Another Log4Net Viewer YALV! is a log file viewer for Log4Net with handy features like log merging, filtering, open most recently used files, items sorting and so on. It is easy to use, it requires no configuration, it has intuitive and user-friendly interface and available in several languages. It is a WPF Application based on .NET Framework 4.0 and written in C# language. -![Screenshot](/doc/images/YALV-Win.png?raw=true "YALV Main Window") -[More Screenshots](https://github.com/LukePet/YALV/wiki/Screenshots) +D-YALV is a fork of YALV! that supports custom columns & data. During your applications execution, just log your custom data as a element, like so: -## Main features -* Log files merging into one list -* Dynamic log events filtering -* Dynamic show/hide log events by log level -* Favorites log folders list -* Open most recently used files -* Sort and reorder columns -* Copy log event data to clipboard -* Open files by dragging them to the main window +```xml + + Testing logs.. + + + + + + + + + + + -## Localizations -* English -* French -* German -* Italian -* Russian -* Japanese -* Chinese -* Greek + + + +``` -## Configuration -YALV itself does not require any setup, but log4net must be setup in your application to write XML content in XmlLayoutSchemaLog4j layout to log files. [Read more...](https://github.com/LukePet/YALV/wiki) +and D-YALV will show them just like a default log4net column. ## Usage -Download latest binaries, unzip and launch YALV.exe. That's all! -YALV GUI language follows your Windows culture automatically, but you can override this behavior. +Use it just like how you would use YALV!. diff --git a/src/YALV.Core/Domain/LogItem.cs b/src/YALV.Core/Domain/LogItem.cs index 317580c..68e84f0 100644 --- a/src/YALV.Core/Domain/LogItem.cs +++ b/src/YALV.Core/Domain/LogItem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace YALV.Core.Domain { @@ -24,6 +25,8 @@ public class LogItem public string Line { get; set; } public string Uncategorized { get; set; } + public Dictionary CustomFields { get; set; } = new Dictionary(); + /// /// LevelIndex /// diff --git a/src/YALV.Core/Providers/XmlEntriesProvider.cs b/src/YALV.Core/Providers/XmlEntriesProvider.cs index 40d8776..850ed00 100644 --- a/src/YALV.Core/Providers/XmlEntriesProvider.cs +++ b/src/YALV.Core/Providers/XmlEntriesProvider.cs @@ -71,6 +71,14 @@ public override IEnumerable GetEntries(string dataSource, FilterParams case ("log4net:HostName"): entry.HostName = xmlTextReader.GetAttribute("value"); break; + default: + var name = xmlTextReader.GetAttribute("name"); + if (!name.StartsWith("log4net:")) + { + var val = xmlTextReader.GetAttribute("value"); + entry.CustomFields.Add(name, val); + } + break; } break; case ("log4j:throwable"): @@ -97,6 +105,7 @@ public override IEnumerable GetEntries(string dataSource, FilterParams } } } + } private static bool filterByParameters(LogItem entry, FilterParams parameters) diff --git a/src/YALV.Core/YALV.Core.csproj b/src/YALV.Core/YALV.Core.csproj index c33438e..9c76008 100644 --- a/src/YALV.Core/YALV.Core.csproj +++ b/src/YALV.Core/YALV.Core.csproj @@ -29,6 +29,7 @@ TRACE prompt 4 + false diff --git a/src/YALV.sln b/src/YALV.sln index b24ea8b..0d49ce6 100644 --- a/src/YALV.sln +++ b/src/YALV.sln @@ -1,6 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YALV", "YALV\YALV.csproj", "{D9109556-5B27-4FF2-AB53-837DF91A5466}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YALV.Samples", "YALV.Samples\YALV.Samples.csproj", "{5276151E-F717-4893-9A09-C9DD3CE65F83}" diff --git a/src/YALV/Common/FilteredGridManager.cs b/src/YALV/Common/FilteredGridManager.cs index 4403443..5c5137b 100644 --- a/src/YALV/Common/FilteredGridManager.cs +++ b/src/YALV/Common/FilteredGridManager.cs @@ -1,14 +1,21 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; -using System.Windows.Input; +using System.Windows.Forms; using YALV.Common.Converters; using YALV.Core.Domain; using YALV.Properties; +using Application = System.Windows.Application; +using Binding = System.Windows.Data.Binding; +using DataGrid = System.Windows.Controls.DataGrid; +using KeyEventHandler = System.Windows.Input.KeyEventHandler; +using Panel = System.Windows.Controls.Panel; +using TextBox = System.Windows.Controls.TextBox; namespace YALV.Common { @@ -31,7 +38,7 @@ public FilteredGridManager(DataGrid dg, Panel txtSearchPanel, KeyEventHandler ke #region Public Methods - public void BuildDataGrid(IList columns) + public void BuildDataGrid(IList columns, List newColumns = null) { if (_dg == null) return; @@ -43,10 +50,13 @@ public void BuildDataGrid(IList columns) if (columns != null) { + _dg.Columns.Clear(); + _txtSearchPanel.Children.Clear(); foreach (ColumnItem item in columns) { DataGridTextColumn col = new DataGridTextColumn(); - col.Header = item.Header; + + col.Header = item.Header; if (item.Alignment == CellAlignment.CENTER && _centerCellStyle != null) col.CellStyle = _centerCellStyle; if (item.MinWidth != null) @@ -54,8 +64,24 @@ public void BuildDataGrid(IList columns) if (item.Width != null) col.Width = item.Width.Value; - Binding bind = new Binding(item.Field) { Mode = BindingMode.OneWay }; - bind.ConverterCulture = System.Globalization.CultureInfo.GetCultureInfo(Properties.Resources.CultureName); + Binding bind; + if (newColumns != null && newColumns.Count > 0) + { + if (newColumns.Contains(item)) + { + bind = new Binding(string.Format("CustomFields[{0}]", item.Field)) {Mode = BindingMode.OneWay}; + } + else + { + bind = new Binding(item.Field) { Mode = BindingMode.OneWay }; + } + } + else + { + bind = new Binding(item.Field) { Mode = BindingMode.OneWay }; + } + + bind.ConverterCulture = System.Globalization.CultureInfo.GetCultureInfo(Properties.Resources.CultureName); if (!String.IsNullOrWhiteSpace(item.StringFormat)) bind.StringFormat = item.StringFormat; col.Binding = bind; @@ -78,13 +104,13 @@ public void BuildDataGrid(IList columns) Style txtStyle = Application.Current.FindResource("RoundWatermarkTextBox") as Style; if (txtStyle != null) txt.Style = txtStyle; - txt.Name = getTextBoxName(item.Field); + txt.Name = getTextBoxName(item.Field.Replace(" ", "")); txt.ToolTip = String.Format(Resources.FilteredGridManager_BuildDataGrid_FilterTextBox_Tooltip, item.Header); txt.Tag = txt.ToolTip.ToString().ToLower(); txt.Text = string.Empty; txt.AcceptsReturn = false; txt.SetBinding(TextBox.WidthProperty, widthBind); - _filterPropertyList.Add(item.Field); + _filterPropertyList.Add(item.Field.Replace(" ","")); if (_keyUpEvent != null) txt.KeyUp += _keyUpEvent; diff --git a/src/YALV/Common/FilteredGridManagerBase.cs b/src/YALV/Common/FilteredGridManagerBase.cs index 79dc0b1..2952296 100644 --- a/src/YALV/Common/FilteredGridManagerBase.cs +++ b/src/YALV/Common/FilteredGridManagerBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; @@ -20,7 +21,6 @@ public FilteredGridManagerBase(DataGrid dg, Panel txtSearchPanel, KeyEventHandle _txtSearchPanel = txtSearchPanel; _keyUpEvent = keyUpEvent; _filterPropertyList = new List(); - _txtCache = new Hashtable(); IsFilteringEnabled = true; } @@ -138,15 +138,9 @@ protected bool itemCheckFilter(object item) foreach (string prop in _filterPropertyList) { TextBox txt = null; - if (_txtCache.ContainsKey(prop)) - txt = _txtCache[prop] as TextBox; - else - { - txt = _txtSearchPanel.FindName(getTextBoxName(prop)) as TextBox; - _txtCache[prop] = txt; - } + txt = _txtSearchPanel.FindName(getTextBoxName(prop)) as TextBox; - res = false; + res = false; if (txt == null) res = true; else @@ -196,15 +190,30 @@ protected bool itemCheckFilter(object item) protected object getItemValue(object item, string prop) { object val = null; - try - { - val = item.GetType().GetProperty(prop).GetValue(item, null); - } - catch - { - val = null; - } - return val; + + var type = item.GetType(); + var properties = type.GetProperties(); + + var isBase = properties.Any(p => p.Name == prop); + if (isBase) + { + val = type.GetProperty(prop).GetValue(item, null); + } + else + { + try + { + var custom = type.GetProperty("CustomFields"); + var inf = custom.GetValue(item, null); + + val = ((Dictionary)inf)[prop]; + } + catch (Exception) + { + val = null; + } + } + return val; } #endregion diff --git a/src/YALV/MainWindow.xaml.cs b/src/YALV/MainWindow.xaml.cs index 7ff6acc..b1a94f3 100644 --- a/src/YALV/MainWindow.xaml.cs +++ b/src/YALV/MainWindow.xaml.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.Windows; using System.Windows.Controls; +using System.Windows.Data; using System.Windows.Input; using YALV.Common; using YALV.Common.Interfaces; @@ -49,6 +50,9 @@ public MainWindow(string[] args) _vm.RefreshUI = OnRefreshUI; this.DataContext = _vm; + dgItems.EnableColumnVirtualization = true; + dgItems.EnableRowVirtualization = true; + //Assign events dgItems.SelectionChanged += dgItems_SelectionChanged; txtItemId.KeyUp += txtItemId_KeyUp; @@ -111,6 +115,7 @@ private void OnRefreshUI(string eventName, object parameter = null) { try { + switch (eventName) { case MainWindowVM.NOTIFY_ScrollIntoView: diff --git a/src/YALV/ViewModel/MainWindowVM.cs b/src/YALV/ViewModel/MainWindowVM.cs index 9a42551..73e6ea6 100644 --- a/src/YALV/ViewModel/MainWindowVM.cs +++ b/src/YALV/ViewModel/MainWindowVM.cs @@ -1329,8 +1329,19 @@ where levelCheckFilter(it) SelectedLogItem = lastItem != null ? lastItem : Items[Items.Count - 1]; } - } - } + + var result = (List)((object[])e.Result)[0]; + if (result.Count > 0) + { + var maxCustomFields = result.Max(c => c.CustomFields.Count); + + var logItem = result.First(r => r.CustomFields.Count == maxCustomFields); + + ReInitDataGridWithNewColumns(logItem.CustomFields); + } + + } + } IsLoading = false; } @@ -1347,29 +1358,55 @@ public void InitDataGrid() { if (GridManager != null) { - IList dgColumns = new List() - { - new ColumnItem("Id", 37, null, CellAlignment.CENTER,string.Empty){Header = Resources.MainWindowVM_InitDataGrid_IdColumn_Header}, - new ColumnItem("TimeStamp", 120, null, CellAlignment.CENTER, GlobalHelper.DisplayDateTimeFormat){Header = Resources.MainWindowVM_InitDataGrid_TimeStampColumn_Header}, - new ColumnItem("Level", null, 50, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_LevelColumn_Header}, - new ColumnItem("Message", null, 300){Header = Resources.MainWindowVM_InitDataGrid_MessageColumn_Header}, - new ColumnItem("Logger", 150, null){Header = Resources.MainWindowVM_InitDataGrid_LoggerColumn_Header}, - new ColumnItem("MachineName", 110, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_MachineNameColumn_Header}, - new ColumnItem("HostName", 110, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_HostNameColumn_Header}, - new ColumnItem("UserName", 110, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_UserNameColumn_Header}, - new ColumnItem("App", 150, null){Header = Resources.MainWindowVM_InitDataGrid_AppColumn_Header}, - new ColumnItem("Thread", 44, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_ThreadColumn_Header}, - new ColumnItem("Class", null, 300){Header = Resources.MainWindowVM_InitDataGrid_ClassColumn_Header}, - new ColumnItem("Method", 200, null){Header = Resources.MainWindowVM_InitDataGrid_MethodColumn_Header} - //new ColumnItem("Delta", 60, null, CellAlignment.CENTER, null, "Δ"), - //new ColumnItem("Path", 50) - }; + IList dgColumns = GetDefaultColumns(); GridManager.BuildDataGrid(dgColumns); GridManager.AssignSource(new Binding(MainWindowVM.PROP_Items) { Source = this, Mode = BindingMode.OneWay }); GridManager.OnBeforeCheckFilter = levelCheckFilter; } } + private List GetDefaultColumns() + { + return new List() + { + new ColumnItem("Id", 37, null, CellAlignment.CENTER,string.Empty){Header = Resources.MainWindowVM_InitDataGrid_IdColumn_Header}, + new ColumnItem("TimeStamp", 120, null, CellAlignment.CENTER, GlobalHelper.DisplayDateTimeFormat){Header = Resources.MainWindowVM_InitDataGrid_TimeStampColumn_Header}, + new ColumnItem("Level", null, 50, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_LevelColumn_Header}, + new ColumnItem("Message", null, 300){Header = Resources.MainWindowVM_InitDataGrid_MessageColumn_Header}, + new ColumnItem("Logger", 150, null){Header = Resources.MainWindowVM_InitDataGrid_LoggerColumn_Header}, + new ColumnItem("MachineName", 110, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_MachineNameColumn_Header}, + new ColumnItem("HostName", 110, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_HostNameColumn_Header}, + new ColumnItem("UserName", 110, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_UserNameColumn_Header}, + new ColumnItem("App", 150, null){Header = Resources.MainWindowVM_InitDataGrid_AppColumn_Header}, + new ColumnItem("Thread", 44, null, CellAlignment.CENTER){Header = Resources.MainWindowVM_InitDataGrid_ThreadColumn_Header}, + new ColumnItem("Class", null, 300){Header = Resources.MainWindowVM_InitDataGrid_ClassColumn_Header}, + new ColumnItem("Method", 200, null){Header = Resources.MainWindowVM_InitDataGrid_MethodColumn_Header} + }; + } + + + public void ReInitDataGridWithNewColumns(Dictionary customFields) + { + if (GridManager != null) + { + var dgColumns = GetDefaultColumns(); + + var newColumns = new List(); + foreach (var field in customFields) + { + var column = new ColumnItem(field.Key, null, 200) + { + Header = field.Key + }; + newColumns.Add(column); + dgColumns.Add(column); + } + + GridManager.BuildDataGrid(dgColumns, newColumns); + GridManager.AssignSource(new Binding(MainWindowVM.PROP_Items) { Source = this, Mode = BindingMode.OneWay }); + GridManager.OnBeforeCheckFilter = levelCheckFilter; + } + } public void RefreshView() { if (GridManager != null) diff --git a/src/YALV/YALV.csproj b/src/YALV/YALV.csproj index 7731610..7616257 100644 --- a/src/YALV/YALV.csproj +++ b/src/YALV/YALV.csproj @@ -68,6 +68,7 @@ ;c:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules false false + false