Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic column loading for YALV #7

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 22 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <log:data> 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
<log4j:event logger="LoggingService" timestamp="1471208402881" level="DEBUG" thread="30">
<log4j:message>Testing logs..</log4j:message>
<log4j:properties>
<!-- Default log4net columns -->
<log4j:data name="log4jmachinename" value="user-machine" />
<log4j:data name="log4net:Identity" value="SYSTEM" />
<log4j:data name="log4net:UserName" value="SYSTEM\USER" />
<log4j:data name="log4japp" value="LoggingService.exe" />
<log4j:data name="log4net:HostName" value="system" />

<!-- Custom columns, added by you -->
<log4j:data name="userId" value="410" />
<log4j:data name="locationGuid" value="7FF2DFF98DA8314C8CF428A46FBE4555" />

## Localizations
* English
* French
* German
* Italian
* Russian
* Japanese
* Chinese
* Greek
</log4j:properties>
<log4j:locationInfo class="..." method="MoveNext" file="..." line="43" />
</log4j:event>
```

## 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!.
3 changes: 3 additions & 0 deletions src/YALV.Core/Domain/LogItem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace YALV.Core.Domain
{
Expand All @@ -24,6 +25,8 @@ public class LogItem
public string Line { get; set; }
public string Uncategorized { get; set; }

public Dictionary<string, string> CustomFields { get; set; } = new Dictionary<string, string>();

/// <summary>
/// LevelIndex
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions src/YALV.Core/Providers/XmlEntriesProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ public override IEnumerable<LogItem> 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"):
Expand All @@ -97,6 +105,7 @@ public override IEnumerable<LogItem> GetEntries(string dataSource, FilterParams
}
}
}

}

private static bool filterByParameters(LogItem entry, FilterParams parameters)
Expand Down
1 change: 1 addition & 0 deletions src/YALV.Core/YALV.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
Expand Down
4 changes: 3 additions & 1 deletion src/YALV.sln
Original file line number Diff line number Diff line change
@@ -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}"
Expand Down
40 changes: 33 additions & 7 deletions src/YALV/Common/FilteredGridManager.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -31,7 +38,7 @@ public FilteredGridManager(DataGrid dg, Panel txtSearchPanel, KeyEventHandler ke

#region Public Methods

public void BuildDataGrid(IList<ColumnItem> columns)
public void BuildDataGrid(IList<ColumnItem> columns, List<ColumnItem> newColumns = null)
{
if (_dg == null)
return;
Expand All @@ -43,19 +50,38 @@ public void BuildDataGrid(IList<ColumnItem> 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)
col.MinWidth = item.MinWidth.Value;
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;
Expand All @@ -78,13 +104,13 @@ public void BuildDataGrid(IList<ColumnItem> 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;

Expand Down
45 changes: 27 additions & 18 deletions src/YALV/Common/FilteredGridManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +21,6 @@ public FilteredGridManagerBase(DataGrid dg, Panel txtSearchPanel, KeyEventHandle
_txtSearchPanel = txtSearchPanel;
_keyUpEvent = keyUpEvent;
_filterPropertyList = new List<string>();
_txtCache = new Hashtable();
IsFilteringEnabled = true;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<string, string>)inf)[prop];
}
catch (Exception)
{
val = null;
}
}
return val;
}

#endregion
Expand Down
5 changes: 5 additions & 0 deletions src/YALV/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -111,6 +115,7 @@ private void OnRefreshUI(string eventName, object parameter = null)
{
try
{

switch (eventName)
{
case MainWindowVM.NOTIFY_ScrollIntoView:
Expand Down
75 changes: 56 additions & 19 deletions src/YALV/ViewModel/MainWindowVM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,8 +1329,19 @@ where levelCheckFilter(it)

SelectedLogItem = lastItem != null ? lastItem : Items[Items.Count - 1];
}
}
}

var result = (List<LogItem>)((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;
}

Expand All @@ -1347,29 +1358,55 @@ public void InitDataGrid()
{
if (GridManager != null)
{
IList<ColumnItem> dgColumns = new List<ColumnItem>()
{
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<ColumnItem> dgColumns = GetDefaultColumns();
GridManager.BuildDataGrid(dgColumns);
GridManager.AssignSource(new Binding(MainWindowVM.PROP_Items) { Source = this, Mode = BindingMode.OneWay });
GridManager.OnBeforeCheckFilter = levelCheckFilter;
}
}

private List<ColumnItem> GetDefaultColumns()
{
return new List<ColumnItem>()
{
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<string, string> customFields)
{
if (GridManager != null)
{
var dgColumns = GetDefaultColumns();

var newColumns = new List<ColumnItem>();
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)
Expand Down
Loading