Repository: yarseyah/sentinel
Branch: master
Commit: 93aa321bb74e
Files: 280
Total size: 767.7 KB
Directory structure:
gitextract_gjrp28h9/
├── .gitattributes
├── .github/
│ └── dependabot.yml
├── .gitignore
├── CONTRIBUTING.md
├── CustomDictionary.xml
├── GitVersion.yml
├── LICENSE.md
├── README.md
├── Sentinel/
│ ├── .vscode/
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── Classification/
│ │ ├── Classifier.cs
│ │ ├── ClassifyingService.cs
│ │ ├── Gui/
│ │ │ ├── AddClassifier.cs
│ │ │ ├── AddEditClassifier.cs
│ │ │ ├── AddEditClassifierWindow.xaml
│ │ │ ├── AddEditClassifierWindow.xaml.cs
│ │ │ ├── ClassificationsControl.xaml
│ │ │ ├── ClassificationsControl.xaml.cs
│ │ │ ├── EditClassifier.cs
│ │ │ └── RemoveClassifier.cs
│ │ └── Interfaces/
│ │ ├── IAddClassifyingService.cs
│ │ ├── IClassifier.cs
│ │ ├── IClassifyingService.cs
│ │ ├── IEditClassifyingService.cs
│ │ └── IRemoveClassifyingService.cs
│ ├── Controls/
│ │ ├── AboutWindow.xaml
│ │ ├── AboutWindow.xaml.cs
│ │ ├── ImageTypesControl.xaml
│ │ ├── ImageTypesControl.xaml.cs
│ │ ├── IntegerTextBox.cs
│ │ ├── LogActivityControl.xaml
│ │ ├── LogActivityControl.xaml.cs
│ │ ├── MainWindow.xaml
│ │ ├── MainWindow.xaml.cs
│ │ ├── PersistingSettings.cs
│ │ ├── PreferencesControl.xaml
│ │ ├── PreferencesControl.xaml.cs
│ │ ├── PreferencesWindow.xaml
│ │ ├── PreferencesWindow.xaml.cs
│ │ ├── RecentFileInfo.cs
│ │ └── WindowPlacementInfo.cs
│ ├── EventLogMonitor/
│ │ ├── CommandLineOptions.cs
│ │ ├── EventLogEntry.cs
│ │ └── Interfaces/
│ │ └── IEventLogEntry.cs
│ ├── Extractors/
│ │ ├── ExtractingService.cs
│ │ ├── Extractor.cs
│ │ ├── Gui/
│ │ │ ├── AddEditExtractor.cs
│ │ │ ├── AddEditExtractorWindow.xaml
│ │ │ ├── AddEditExtractorWindow.xaml.cs
│ │ │ ├── AddExtractor.cs
│ │ │ ├── EditExtractor.cs
│ │ │ ├── ExtractorsControl.xaml
│ │ │ ├── ExtractorsControl.xaml.cs
│ │ │ └── RemoveExtractor.cs
│ │ ├── Interfaces/
│ │ │ ├── IAddExtractorService.cs
│ │ │ ├── IEditExtractorService.cs
│ │ │ ├── IExtractingService.cs
│ │ │ ├── IExtractor.cs
│ │ │ ├── IRemoveExtractorService.cs
│ │ │ └── ISearchExtractor.cs
│ │ └── SearchExtractor.cs
│ ├── FileMonitor/
│ │ ├── CustomMessageDecoderPage.xaml
│ │ ├── CustomMessageDecoderPage.xaml.cs
│ │ ├── FileMonitorProviderPage.xaml
│ │ ├── FileMonitorProviderPage.xaml.cs
│ │ ├── FileMonitoringProvider.cs
│ │ ├── FileMonitoringProviderSettings.cs
│ │ ├── IFileMonitoringProviderSettings.cs
│ │ ├── LogEntry.cs
│ │ ├── MessageFormatPage.xaml
│ │ ├── MessageFormatPage.xaml.cs
│ │ └── ProviderRegistrationInformation.cs
│ ├── Filters/
│ │ ├── Filter.cs
│ │ ├── FilteringService.cs
│ │ ├── Gui/
│ │ │ ├── AddEditFilter.cs
│ │ │ ├── AddEditFilterWindow.xaml
│ │ │ ├── AddEditFilterWindow.xaml.cs
│ │ │ ├── AddFilter.cs
│ │ │ ├── EditFilter.cs
│ │ │ ├── FiltersControl.xaml
│ │ │ ├── FiltersControl.xaml.cs
│ │ │ └── RemoveFilter.cs
│ │ ├── Interfaces/
│ │ │ ├── IAddFilterService.cs
│ │ │ ├── IEditFilterService.cs
│ │ │ ├── IFilter.cs
│ │ │ ├── IFilteringService.cs
│ │ │ ├── IRemoveFilterService.cs
│ │ │ ├── ISearchFilter.cs
│ │ │ └── IStandardDebuggingFilter.cs
│ │ ├── SearchFilter.cs
│ │ └── StandardFilter.cs
│ ├── Highlighters/
│ │ ├── Gui/
│ │ │ ├── AddEditHighlighter.cs
│ │ │ ├── AddEditHighlighterWindow.xaml
│ │ │ ├── AddEditHighlighterWindow.xaml.cs
│ │ │ ├── AddNewHighlighterService.cs
│ │ │ ├── EditHighlighterService.cs
│ │ │ ├── HighlightersControl.xaml
│ │ │ ├── HighlightersControl.xaml.cs
│ │ │ └── RemoveHighlighterService.cs
│ │ ├── Highlighter.cs
│ │ ├── HighlighterConverter.cs
│ │ ├── HighlighterStyle.cs
│ │ ├── HighlightingSelector.cs
│ │ ├── HighlightingService.cs
│ │ ├── Interfaces/
│ │ │ ├── IAddHighlighterService.cs
│ │ │ ├── IEditHighlighterService.cs
│ │ │ ├── IHighlighter.cs
│ │ │ ├── IHighlightingService.cs
│ │ │ ├── IRemoveHighlighterService.cs
│ │ │ ├── ISearchHighlighter.cs
│ │ │ └── IStandardDebuggingHighlighter.cs
│ │ ├── SearchHighlighter.cs
│ │ └── StandardHighlighter.cs
│ ├── Images/
│ │ ├── AddEditTypeImageViewModel.cs
│ │ ├── AddTypeImageService.cs
│ │ ├── Controls/
│ │ │ ├── AddImageWindow.xaml
│ │ │ └── AddImageWindow.xaml.cs
│ │ ├── EditTypeImageMapping.cs
│ │ ├── ImageQuality.cs
│ │ ├── ImageTypeRecord.cs
│ │ ├── Interfaces/
│ │ │ ├── IAddTypeImage.cs
│ │ │ ├── IEditTypeImage.cs
│ │ │ ├── IRemoveTypeImage.cs
│ │ │ ├── ITypeImageService.cs
│ │ │ └── ImageOptions.cs
│ │ ├── RemoveTypeImageMapping.cs
│ │ └── TypeToImageService.cs
│ ├── Interfaces/
│ │ ├── CaseInsensitiveComparer.cs
│ │ ├── CodeContracts/
│ │ │ ├── ThrowIfNull.cs
│ │ │ ├── ThrowIfNullOrWhitespace.cs
│ │ │ └── ValidatedNotNullAttribute.cs
│ │ ├── CollectionChangeHelper.cs
│ │ ├── EnumerableExtensions.cs
│ │ ├── IDefaultInitialisation.cs
│ │ ├── IHighlighterStyle.cs
│ │ ├── ILogEntry.cs
│ │ ├── ILogger.cs
│ │ ├── IUserPreferences.cs
│ │ ├── LinqHelpers.cs
│ │ ├── LogEntryFields.cs
│ │ ├── MatchMode.cs
│ │ └── Providers/
│ │ ├── ILogProvider.cs
│ │ ├── INetworkProvider.cs
│ │ ├── IProviderInfo.cs
│ │ ├── IProviderRegistrationRecord.cs
│ │ └── IProviderSettings.cs
│ ├── Log4Net/
│ │ ├── ConfigurationPage.xaml
│ │ ├── ConfigurationPage.xaml.cs
│ │ ├── IUdpAppenderListenerSettings.cs
│ │ ├── Log4NetProvider.cs
│ │ ├── LogEntry.cs
│ │ ├── ProviderRegistrationInformation.cs
│ │ ├── UdpAppenderSettings.cs
│ │ └── XElementHelpers.cs
│ ├── Logger/
│ │ ├── ILogViewerDetails.cs
│ │ ├── IUdpLogViewer.cs
│ │ └── LogWriter.cs
│ ├── Logs/
│ │ ├── Gui/
│ │ │ ├── AddNewLoggerWelcomePage.xaml
│ │ │ ├── AddNewLoggerWelcomePage.xaml.cs
│ │ │ ├── NewLoggerSettings.cs
│ │ │ ├── NewLoggerSummaryPage.xaml
│ │ │ ├── NewLoggerSummaryPage.xaml.cs
│ │ │ ├── NewLoggerWizard.cs
│ │ │ ├── ProvidersPage.xaml
│ │ │ ├── ProvidersPage.xaml.cs
│ │ │ ├── SetLoggerNamePage.xaml
│ │ │ ├── SetLoggerNamePage.xaml.cs
│ │ │ ├── ViewSelectionPage.xaml
│ │ │ └── ViewSelectionPage.xaml.cs
│ │ ├── Interfaces/
│ │ │ ├── ILogFileExporter.cs
│ │ │ └── ILogManager.cs
│ │ ├── Log.cs
│ │ ├── LogFileExporter.cs
│ │ └── LogManager.cs
│ ├── MSBuild/
│ │ ├── ConfigurationPage.xaml
│ │ ├── ConfigurationPage.xaml.cs
│ │ ├── IMSBuildListenerSettings.cs
│ │ ├── LogEntry.cs
│ │ ├── MSBuildListenerSettings.cs
│ │ ├── MSBuildProvider.cs
│ │ ├── ProviderInfo.cs
│ │ └── ProviderRegistrationInformation.cs
│ ├── MainApplication.xaml
│ ├── MainApplication.xaml.cs
│ ├── NLog/
│ │ ├── INLogAppenderSettings.cs
│ │ ├── LogEntry.cs
│ │ ├── NLogViewerProvider.cs
│ │ ├── NetworkClientWrapper.cs
│ │ ├── NetworkConfigurationPage.xaml
│ │ ├── NetworkConfigurationPage.xaml.cs
│ │ ├── NetworkProtocol.cs
│ │ ├── NetworkSettings.cs
│ │ ├── ProviderInfo.cs
│ │ ├── ProviderRegistrationInformation.cs
│ │ └── ProviderSettings.cs
│ ├── Preferences/
│ │ └── UserPreferences.cs
│ ├── Properties/
│ │ ├── AssemblyInfo.cs
│ │ ├── Resources.Designer.cs
│ │ ├── Resources.resx
│ │ ├── Settings.Designer.cs
│ │ ├── Settings.settings
│ │ └── app.manifest
│ ├── Providers/
│ │ ├── Interfaces/
│ │ │ ├── INewProviderWizard.cs
│ │ │ ├── IProviderManager.cs
│ │ │ └── PendingProviderRecord.cs
│ │ ├── NewProviderWizard.cs
│ │ ├── ProviderInfo.cs
│ │ ├── ProviderManager.cs
│ │ ├── ProviderRegistrationRecord.cs
│ │ ├── ProviderSettings.cs
│ │ ├── SelectProviderPage.xaml
│ │ └── SelectProviderPage.xaml.cs
│ ├── Sentinel.csproj
│ ├── Services/
│ │ ├── AttributeHelper.cs
│ │ ├── DictionaryHelper.cs
│ │ ├── Interfaces/
│ │ │ └── ISessionManager.cs
│ │ ├── ServiceLocator.cs
│ │ └── SessionManager.cs
│ ├── StartUp/
│ │ ├── IOptions.cs
│ │ ├── Log4NetOptions.cs
│ │ └── NLogOptions.cs
│ ├── Support/
│ │ ├── Converters/
│ │ │ ├── BooleanToWidthConverter.cs
│ │ │ ├── CollapseIfNullConverter.cs
│ │ │ ├── DatePreferenceConverter.cs
│ │ │ ├── ImageLibraryConverter.cs
│ │ │ ├── LongPathToShortPathConverter.cs
│ │ │ ├── MessageHasExceptionMetadataConverter.cs
│ │ │ ├── MetaDataConverter.cs
│ │ │ ├── MetaDataParameterConverter.cs
│ │ │ ├── TimePreferenceConverter.cs
│ │ │ ├── TimeSinceDateTimeConverter.cs
│ │ │ ├── TypeToImageConverter.cs
│ │ │ ├── TypeToLargeImageConverter.cs
│ │ │ ├── TypeToMediumImageConverter.cs
│ │ │ └── TypeToSmallImageConverter.cs
│ │ ├── GridViewSort.cs
│ │ ├── JsonHelper.cs
│ │ ├── ScrollingHelper.cs
│ │ └── Wpf/
│ │ ├── DataBoundToolbar.cs
│ │ ├── FixedWidthColumn.cs
│ │ ├── ObservableDictionary.cs
│ │ └── ThemeInfo.cs
│ ├── Views/
│ │ ├── Gui/
│ │ │ ├── LogMessages.cs
│ │ │ ├── LogMessagesControl.xaml
│ │ │ ├── LogMessagesControl.xaml.cs
│ │ │ ├── LogViewerToolbarButton.cs
│ │ │ ├── MultipleViewFrame.xaml
│ │ │ ├── MultipleViewFrame.xaml.cs
│ │ │ └── ViewInformation.cs
│ │ ├── Heartbeat/
│ │ │ ├── HeartbeatControl.xaml
│ │ │ ├── HeartbeatControl.xaml.cs
│ │ │ ├── MessageHeatbeat.cs
│ │ │ └── ViewInformation.cs
│ │ ├── Interfaces/
│ │ │ ├── ILogViewer.cs
│ │ │ ├── ILogViewerToolbarButton.cs
│ │ │ ├── IViewInformation.cs
│ │ │ ├── IViewManager.cs
│ │ │ └── IWindowFrame.cs
│ │ └── ViewManager.cs
│ └── WpfExtras/
│ ├── Converters/
│ │ ├── BooleanInvertingValueConverter.cs
│ │ ├── BooleanToDisabledConverter.cs
│ │ ├── CollapseIfFalseConverter.cs
│ │ ├── CollapseIfZeroConverter.cs
│ │ ├── ColourConverter.cs
│ │ ├── VisibilityToCollapsedConverter.cs
│ │ └── VisibilityToHiddenConverter.cs
│ ├── DelegateCommand.cs
│ ├── EstablishPossibleNavigation.cs
│ ├── IWizardPage.cs
│ ├── PageChange.cs
│ ├── PageNavigationTreeEntry.cs
│ ├── ViewModelBase.cs
│ ├── Wizard.xaml
│ └── Wizard.xaml.cs
├── Sentinel.sln
├── Sentinel.sln.DotSettings
├── Settings.StyleCop
├── SharedAssemblyInfo.cs
├── StyleCop/
│ └── StyleCopRules.ruleset
└── docs/
└── Home.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=CRLF
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/" # Location of package manifests
schedule:
interval: "daily"
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
Installs/
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
.vs/
# Squirrel releases
Releases/
*.nupkg
*.nuspec
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.pfx
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
#LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
# =========================
# Windows detritus
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac desktop service store files
.DS_Store
# Nuget
packages
/.vs
/.idea
================================================
FILE: CONTRIBUTING.md
================================================
Releases are made from the master branch, but new developments are done on the develop branch. There are a lot of new developments on that branch, so please be aware as changes to master directly may not merge nicely.
================================================
FILE: CustomDictionary.xml
================================================
ColourColoursInitialiseInitialisationAppenderMsGuiMvvm
================================================
FILE: GitVersion.yml
================================================
mode: Mainline
branches: {}
ignore:
sha: []
merge-message-formats: {}
================================================
FILE: LICENSE.md
================================================
# Microsoft Public License (Ms-PL)
Microsoft Public License (Ms-PL)
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
================================================
FILE: README.md
================================================
#  sentinel
Log-viewer with filtering and highlighting

## Log Sources
Sentinel is a viewer for log-files - specifically I designed it to act as a network end-point for the likes of nLog and log4net, additionally it then works really well for capturing log entries from multiple sources.
Source | Status
--------------------- | ------
Log4Net UdpAppender | Supported
nLog's nLogViewer | Supported
Trace Listener | Planned
Log-File Watcher | Experimental
Custom, via plug-in | Planned
MSBuild | Plug-in in source-repo
## Command-Line usage
There are command line options that allow control over Sentinel when started, options available include the following:
* Loading of a saved Session File
* nLog network listener
* log4net network listener
## Command line options
If no command line options are specified, the standard New Session wizard will launch at start-up.
### Launch with NLog listener enabled
```
sentinel nlog [--port ] [--tcp]
```
Defaults to port 9999 and Udp if not specified.
### Launch with Log4Net listener enabled
```
sentinel log4net [--port ]
```
Defaults to port 9998 if not specified.
### Launch with previously saved session file
```
sentinel filename.SNTL
```
## nLog's NLogViewer target configuration
To allow a nLog based application transmit its log messages to Sentinel, use a configuration like the one shown below:
```xml
```
### Showing nLog debug information (0.12.0.0 onwards)
If the above configuration is adjusted to enable {{includeSourceInfo}} then it is possible to see the file, class, method and line number corresponding to where the message is emitted. Some of this information is only reported if the source program is compiled in DEBUG mode (e.g. RELEASE mode strips this information)
```xml
```
## Log4Net UdpAppender configuration
To allow a log4net application transmit its log messages to Sentinel, use a configuration like the one shown below:
```xml
```
### Showing log4net debug information (0.12.1.0 onwards)
If the above configuration is adjusted to enable {{locationInfo}} then it is possible to see the file, class, method and line number corresponding to where the message is emitted. Some of this information is only reported if the source program is compiled in DEBUG mode (e.g. RELEASE mode strips this information)
```xml
```
## Log Entries
Log file entries map the the following interface. This is core record for a log-file entry and used to populate the columns within the live-log view. Note, by using [Classifiers](#Classifiers) it is possible to rearrange and/or change the content of these fields upon receiving a new log-entry.
```c#
public interface ILogEntry
{
string Classification { get; set; }
DateTime DateTime { get; set; }
string Description { get; set; }
string Source { get; set; }
string System { get; set; }
string Thread { get; set; }
string Type { get; set; }
}
```
Log entries may be classified, highlighted and filtered based upon special services:
* Classifiers can change the properties of a log entry
* Highlighters can change its appearance.
* Filters can be used to suppress the displaying of matching entries.
## Classifiers
Upon receiving a new log-entry it is processed through registered classifiers. Classifiers have the ability to rewrite the log-entry prior to passing it to the visualisation aspect of Sentinel.
As an example, suppose the incoming message when starting with the phrase "Sleeping for another" is intended to be switched from its supplied type (e.g. DEBUG or INFO) to its own type **Timing**. The ClassifierViewModel registers this on construction. Note that the regular expression below also rewrites the Description to the named-capture _description_, effectively stripping off the prefix "Sleeping for another" - information no longer needed due to the reclassification.
```c#
items.Add(
new DescriptionTypeClassifier("Timing", "Timing")
{
Enabled = true,
Name = "Timing messages",
RegexString = @"^Sleeping for another (?[^$](^$)+)$"
});
```
In addition to reclassifying messages, the Classifier mechanism is currently used to make other changes to the message appearance. Continuing the example above, using a {{TypeImageClassifier}} it is possible to specify the image to be used for entries with a type of **Timing**.
```c#
items.Add(
new TypeImageClassifier("Timing", "/Resources/Clock.png")
{
Enabled = true,
Name = "Timing Image",
});
```
The classifiers can be seen in the Preferences dialog-box.

## Highlighters
Customisable and extendible highlighters that may be toggled on and off during live preview. The current implementation limits pattern matching to the Type and System fields, although this will be extended to all fields. An example of why this is useful, in the classifiers section above, a new type of "Timing" was added, this type can get its own highlighting style.

Highlighters can match the contents of the Type and System fields
* Exact Strings
* Substrings
* Regular Expressions

If the matching field is set to **Type** an the match string specified as "Timing", a new highlighter for the timing can be added. User-defined highlighters are automatically added to the toolbar for ease of enabling and disabling of the highlighting.

The highlighters work on a first-come, wins principle. Therefore, the order of the entries in the highlighters section of the Preferences dialog-box are important. It is possible to hide the highlighting of FATAL messages if a highlighter is positioned before FATAL and gets a match.
## Filters
Filters are very much like highlighters except their purpose is to remove log-entries from the displayed values (note, the values are not lost, just hidden). Filters may be toggled on and off during the session. Filters are evaluated in the order specified, but since filters works on an any-match = hide principle the evaluation stops on the first match, resulting on the entry being hidden. Note, this means that messages displayed have travelled through ALL of the filters without being matched, this isn't a cost free aspect, so be careful about how many enabled filters you have!
## Extractors
Extractors are the inverse of Filters, log entries must match the extractor to be visible. This can be very useful if you, for example, define an extractor for a specific logging condition and want to quickly see if and how often it happened.
================================================
FILE: Sentinel/.vscode/launch.json
================================================
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net5.0/Sentinel.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
================================================
FILE: Sentinel/.vscode/tasks.json
================================================
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Sentinel.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Sentinel.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/Sentinel.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
================================================
FILE: Sentinel/Classification/Classifier.cs
================================================
namespace Sentinel.Classification
{
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Sentinel.Classification.Interfaces;
using Sentinel.Interfaces;
using WpfExtras;
[DataContract]
public class Classifier : ViewModelBase, IClassifier
{
private bool enabled = true;
private LogEntryFields field;
private MatchMode mode;
private string name;
private string type;
private string pattern;
private Regex regex;
public Classifier()
{
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "Field" || e.PropertyName == "Mode" || e.PropertyName == "Pattern")
{
if (Mode == MatchMode.RegularExpression && Pattern != null)
{
regex = new Regex(Pattern);
}
OnPropertyChanged(nameof(Description));
}
};
}
public Classifier(string name, bool enabled, LogEntryFields field, MatchMode mode, string pattern, string type)
{
Name = name;
Enabled = enabled;
Field = field;
Mode = mode;
Pattern = pattern;
Type = type;
regex = new Regex(pattern);
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "Field" || e.PropertyName == "Mode" || e.PropertyName == "Pattern")
{
if (Mode == MatchMode.RegularExpression && Pattern != null)
{
regex = new Regex(Pattern);
}
OnPropertyChanged(nameof(Description));
}
};
}
public string Description
{
get
{
var modeDescription = "Exact";
switch (Mode)
{
case MatchMode.RegularExpression:
modeDescription = "RegEx";
break;
case MatchMode.CaseSensitive:
modeDescription = "Case sensitive";
break;
case MatchMode.CaseInsensitive:
modeDescription = "Case insensitive";
break;
}
return $"{modeDescription} match of {Pattern} in the {Field} field";
}
}
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled != value)
{
enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
}
public LogEntryFields Field
{
get
{
return field;
}
set
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
public string HighlighterType => "Basic Highlighter";
public MatchMode Mode
{
get
{
return mode;
}
set
{
if (mode != value)
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (pattern != value)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public string Type
{
get
{
return type;
}
set
{
if (type != value)
{
type = value;
OnPropertyChanged(nameof(Type));
}
}
}
public ILogEntry Classify(ILogEntry logEntry)
{
Debug.Assert(logEntry != null, "logEntry can not be null.");
if (IsMatch(logEntry))
{
logEntry.MetaData["Classification"] = Type;
logEntry.Type = Type;
}
return logEntry;
}
public bool IsMatch(ILogEntry logEntry)
{
Debug.Assert(logEntry != null, "logEntry can not be null.");
if (string.IsNullOrWhiteSpace(Pattern))
{
return false;
}
string target;
switch (Field)
{
case LogEntryFields.None:
target = string.Empty;
break;
case LogEntryFields.Type:
target = logEntry.Type;
break;
case LogEntryFields.System:
target = logEntry.System;
break;
case LogEntryFields.Classification:
target = string.Empty;
break;
case LogEntryFields.Thread:
target = logEntry.Thread;
break;
case LogEntryFields.Source:
target = logEntry.Source;
break;
case LogEntryFields.Description:
target = logEntry.Description;
break;
case LogEntryFields.Host:
target = string.Empty;
break;
default:
target = string.Empty;
break;
}
switch (Mode)
{
case MatchMode.Exact:
return target.Equals(Pattern);
case MatchMode.CaseSensitive:
return target.Contains(Pattern);
case MatchMode.CaseInsensitive:
return target.ToLower().Contains(Pattern.ToLower());
case MatchMode.RegularExpression:
return regex != null && regex.IsMatch(target);
}
return false;
}
}
}
================================================
FILE: Sentinel/Classification/ClassifyingService.cs
================================================
namespace Sentinel.Classification
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows.Input;
using Sentinel.Classification.Gui;
using Sentinel.Classification.Interfaces;
using Sentinel.Interfaces;
using WpfExtras;
///
/// View Model for classifier collection. This has been written to operate
/// as a ServiceLocator provided resource, so there is only one collection
/// across the whole of the system.
///
/// The first generic type parameter.
[DataContract]
public class ClassifyingService : ViewModelBase, IClassifyingService, IDefaultInitialisation
where T : class, IClassifier
{
private readonly CollectionChangeHelper collectionHelper = new CollectionChangeHelper();
private readonly IAddClassifyingService addClassifyingService = new AddClassifier();
private readonly IEditClassifyingService editClassifyingService = new EditClassifier();
private readonly IRemoveClassifyingService removeClassifyingService = new RemoveClassifier();
private int selectedIndex = -1;
///
/// Initializes a new instance of the class.
///
public ClassifyingService()
{
Add = new DelegateCommand(AddClassifier);
Edit = new DelegateCommand(EditClassifier, e => SelectedIndex != -1);
Remove = new DelegateCommand(RemoveClassifier, e => SelectedIndex != -1);
OrderEarlier = new DelegateCommand(MoveItemUp, e => SelectedIndex > 0);
OrderLater = new DelegateCommand(
MoveItemDown,
e => SelectedIndex < (Classifiers.Count - 1) && SelectedIndex != -1);
Classifiers = new ObservableCollection();
// Register self as an observer of the collection.
collectionHelper.ManagerName = "Classifiers";
collectionHelper.OnPropertyChanged += CustomClassifierPropertyChanged;
collectionHelper.NameLookup += e => e.Name;
Classifiers.CollectionChanged += collectionHelper.AttachDetach;
}
///
/// Gets the ICommand providing the add-classifier functionality.
///
public ICommand Add { get; private set; }
///
/// Gets the ICommand providing the edit-classifier functionality.
///
public ICommand Edit { get; private set; }
///
/// Gets or sets the ObservableCollection of items representing the
/// collection of Classifiers.
///
public ObservableCollection Classifiers { get; set; }
///
/// Gets the ICommand providing the functionality to
/// move the currently selected element to an earlier position
/// in the ordered list of classifiers.
///
public ICommand OrderEarlier { get; private set; }
///
/// Gets the ICommand providing the functionality to
/// move the currently selected element to a later position
/// in the ordered list of classifiers.
///
public ICommand OrderLater { get; private set; }
///
/// Gets the ICommand providing the functionality to
/// remove the selected element from the list of classifiers.
///
public ICommand Remove { get; private set; }
///
/// Gets or sets the index for the selected item in the list
/// of classifiers. The selected index is used for commands
/// such as OrderEarlier,
/// OrderLater
/// and Remove.
///
public int SelectedIndex
{
get => selectedIndex;
set
{
if (value != selectedIndex)
{
selectedIndex = value;
OnPropertyChanged(nameof(SelectedIndex));
CommandManager.InvalidateRequerySuggested();
}
}
}
public void Initialise()
{
Classifiers.Add(new Classifier("Timing messages", true, LogEntryFields.Description, MatchMode.RegularExpression, @"^\[SimulationTime\] (?[^$]+)$", "TIMING") as T);
Classifiers.Add(new Classifier("Smp messages", true, LogEntryFields.Description, MatchMode.RegularExpression, "Src:'(?[^']+)', Msg:'(?.*)'$", "TIMING") as T);
Classifiers.Add(new Classifier("SimSat messages", true, LogEntryFields.Description, MatchMode.RegularExpression, "SIMSAT:'(?[^']+)', Msg:'(?.*)'$", "TIMING") as T);
}
public ILogEntry Classify(ILogEntry entry)
{
return Classifiers
.Where(classifier => classifier.Enabled)
.Aggregate(entry, (current, classifier) => classifier.Classify(current));
}
///
/// Add a new classifier to the collection of classifiers.
///
/// Private method that implements the ICommand
/// Add through a DelegateCommand.
///
///
/// Delegate object data - unused.
private void AddClassifier(object obj)
{
addClassifyingService.Add();
}
///
/// Edit the currently selected classifier (defined by the
/// SelectedIndex
/// property).
///
/// Private method that implements the ICommand
/// Edit through a DelegateCommand.
///
///
/// Delegate object data - unused.
private void EditClassifier(object obj)
{
var classifier = Classifiers.ElementAt(SelectedIndex);
if (classifier != null)
{
editClassifyingService.Edit(classifier);
}
}
///
/// Move the currently selected classifier (defined by the
/// SelectedIndex
/// property) to later in the ordered list.
///
/// Private method that implements the ICommand
/// OrderLater through a DelegateCommand.
///
///
/// Delegate object data - unused.
private void MoveItemDown(object obj)
{
if (selectedIndex != -1)
{
lock (this)
{
Debug.Assert(
selectedIndex >= 0,
"SelectedIndex must be >= 0.");
Debug.Assert(
selectedIndex < Classifiers.Count - 1,
"SelectedIndex must be within the index range of Items collection");
Debug.Assert(
Classifiers.Count > 1,
"Can not move an item unless there is more than one.");
lock (Classifiers)
{
Classifiers.Swap(selectedIndex, selectedIndex + 1);
}
}
}
}
///
/// Move the currently selected classifier (defined by the
/// SelectedIndex
/// property) to earlier in the ordered list.
///
/// Private method that implements the ICommand
/// OrderEarlier through a DelegateCommand.
///
///
/// Delegate object data - unused.
private void MoveItemUp(object obj)
{
if (selectedIndex != -1)
{
lock (this)
{
Debug.Assert(selectedIndex >= 0, "SelectedIndex must be valid, e.g. >= 0");
Debug.Assert(Classifiers.Count > 1, "Can only move item if more than one.");
lock (Classifiers)
{
Classifiers.Swap(selectedIndex, selectedIndex - 1);
}
}
}
}
///
/// Removes the currently selected classifier (defined by the
/// SelectedIndex
/// property).
///
/// Private method that implements the ICommand
/// Remove through a DelegateCommand.
///
///
/// Delegate object data - unused.
private void RemoveClassifier(object obj)
{
var classifier = Classifiers.ElementAt(SelectedIndex);
removeClassifyingService.Remove(classifier);
}
private void CustomClassifierPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var classifier = sender as IClassifier;
if (classifier != null)
{
Trace.WriteLine(
string.Format(
"ClassifyingService saw some activity on {0} (IsEnabled = {1})",
classifier.Name,
classifier.Enabled));
}
OnPropertyChanged(string.Empty);
}
}
}
================================================
FILE: Sentinel/Classification/Gui/AddClassifier.cs
================================================
namespace Sentinel.Classification.Gui
{
using System.Windows;
using Sentinel.Classification.Interfaces;
using Sentinel.Services;
public class AddClassifier : IAddClassifyingService
{
public void Add()
{
var classifierWindow = new AddEditClassifierWindow();
using (var data = new AddEditClassifier(classifierWindow, false))
{
classifierWindow.DataContext = data;
classifierWindow.Owner = Application.Current.MainWindow;
var dialogResult = classifierWindow.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
var classifier = Construct(data);
if (classifier != null)
{
var service = ServiceLocator.Instance.Get>();
service?.Classifiers.Add(classifier);
}
}
}
}
private static Classifier Construct(AddEditClassifier data)
{
return new Classifier
{
Name = data.Name,
Type = data.Type,
Field = data.Field,
Mode = data.Mode,
Pattern = data.Pattern,
Enabled = true,
};
}
}
}
================================================
FILE: Sentinel/Classification/Gui/AddEditClassifier.cs
================================================
namespace Sentinel.Classification.Gui
{
using System.Windows;
using System.Windows.Input;
using Sentinel.Interfaces;
using WpfExtras;
public class AddEditClassifier : ViewModelBase
{
private readonly Window window;
private string name = "Unnamed";
private string pattern = "pattern";
private LogEntryFields field;
private MatchMode mode;
private string type;
public AddEditClassifier(Window window, bool editMode)
{
this.window = window;
if (window != null)
{
window.Title = $"{(editMode ? "Edit" : "Register")} Classifier";
}
Accept = new DelegateCommand(AcceptDialog, Validates);
Reject = new DelegateCommand(RejectDialog);
}
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Accept { get; private set; }
public LogEntryFields Field
{
get
{
return field;
}
set
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
public MatchMode Mode
{
get
{
return mode;
}
set
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (value != pattern)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public string Type
{
get
{
return type;
}
set
{
if (value != type)
{
type = value;
OnPropertyChanged(nameof(Type));
}
}
}
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Reject { get; private set; }
private void AcceptDialog(object obj)
{
window.DialogResult = true;
window.Close();
}
private void RejectDialog(object obj)
{
window.DialogResult = false;
window.Close();
}
private bool Validates(object obj)
{
return Name.Length > 0 && Pattern.Length > 0;
}
}
}
================================================
FILE: Sentinel/Classification/Gui/AddEditClassifierWindow.xaml
================================================
================================================
FILE: Sentinel/Classification/Gui/AddEditClassifierWindow.xaml.cs
================================================
namespace Sentinel.Classification.Gui
{
///
/// Interaction logic for AddEditClassifierWindow.xaml.
///
public partial class AddEditClassifierWindow
{
public AddEditClassifierWindow()
{
InitializeComponent();
}
}
}
================================================
FILE: Sentinel/Classification/Gui/ClassificationsControl.xaml
================================================
================================================
FILE: Sentinel/Classification/Gui/ClassificationsControl.xaml.cs
================================================
namespace Sentinel.Classification.Gui
{
using System.Windows.Controls;
using Sentinel.Classification.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for ClassificationsControl.xaml.
///
public partial class ClassificationsControl : UserControl
{
public ClassificationsControl()
{
InitializeComponent();
Classifier = ServiceLocator.Instance.Get>();
DataContext = this;
}
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once UnusedAutoPropertyAccessor.Global
public IClassifyingService Classifier { get; private set; }
}
}
================================================
FILE: Sentinel/Classification/Gui/EditClassifier.cs
================================================
namespace Sentinel.Classification.Gui
{
using System.Diagnostics;
using System.Windows;
using Sentinel.Classification.Interfaces;
public class EditClassifier : IEditClassifyingService
{
public void Edit(IClassifier classifier)
{
Debug.Assert(classifier != null, "Extractor must be supplied to allow editing.");
var window = new AddEditClassifierWindow();
var data = new AddEditClassifier(window, true);
window.DataContext = data;
window.Owner = Application.Current.MainWindow;
data.Name = classifier.Name;
data.Field = classifier.Field;
data.Pattern = classifier.Pattern;
data.Mode = classifier.Mode;
data.Type = classifier.Type;
var dialogResult = window.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
classifier.Name = data.Name;
classifier.Pattern = data.Pattern;
classifier.Mode = data.Mode;
classifier.Field = data.Field;
classifier.Type = data.Type;
}
}
}
}
================================================
FILE: Sentinel/Classification/Gui/RemoveClassifier.cs
================================================
namespace Sentinel.Classification.Gui
{
using System.Windows;
using Sentinel.Classification.Interfaces;
using Sentinel.Services;
public class RemoveClassifier : IRemoveClassifyingService
{
public void Remove(IClassifier classifier)
{
var service = ServiceLocator.Instance.Get>();
if (service != null)
{
var prompt =
"Are you sure you want to remove the selected classifier?\r\n\r\n" +
$"Classifier Name = \"{classifier.Name}\"";
var result = MessageBox.Show(
prompt,
"Remove Extractor",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No);
if (result == MessageBoxResult.Yes)
{
service.Classifiers.Remove(classifier);
}
}
}
}
}
================================================
FILE: Sentinel/Classification/Interfaces/IAddClassifyingService.cs
================================================
namespace Sentinel.Classification.Interfaces
{
public interface IAddClassifyingService
{
void Add();
}
}
================================================
FILE: Sentinel/Classification/Interfaces/IClassifier.cs
================================================
namespace Sentinel.Classification.Interfaces
{
using System.Runtime.Serialization;
using Sentinel.Interfaces;
public interface IClassifier
{
[DataMember]
string Name { get; set; }
[DataMember]
string Type { get; set; }
[DataMember]
bool Enabled { get; set; }
[DataMember]
string Pattern { get; set; }
[DataMember]
string Description { get; }
[DataMember]
LogEntryFields Field { get; set; }
[DataMember]
MatchMode Mode { get; set; }
bool IsMatch(ILogEntry entry);
ILogEntry Classify(ILogEntry entry);
}
}
================================================
FILE: Sentinel/Classification/Interfaces/IClassifyingService.cs
================================================
namespace Sentinel.Classification.Interfaces
{
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Windows.Input;
using Sentinel.Interfaces;
public interface IClassifyingService
{
ICommand Add { get; }
ICommand Edit { get; }
[DataMember]
ObservableCollection Classifiers { get; }
ICommand OrderEarlier { get; }
ICommand OrderLater { get; }
ICommand Remove { get; }
int SelectedIndex { get; set; }
ILogEntry Classify(ILogEntry entry);
}
}
================================================
FILE: Sentinel/Classification/Interfaces/IEditClassifyingService.cs
================================================
namespace Sentinel.Classification.Interfaces
{
public interface IEditClassifyingService
{
void Edit(IClassifier classifier);
}
}
================================================
FILE: Sentinel/Classification/Interfaces/IRemoveClassifyingService.cs
================================================
namespace Sentinel.Classification.Interfaces
{
public interface IRemoveClassifyingService
{
void Remove(IClassifier classifier);
}
}
================================================
FILE: Sentinel/Controls/AboutWindow.xaml
================================================
================================================
FILE: Sentinel/Controls/AboutWindow.xaml.cs
================================================
namespace Sentinel.Controls
{
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;
///
/// Interaction logic for AboutWindow.xaml.
///
public partial class AboutWindow
{
public AboutWindow(Window parent)
{
InitializeComponent();
Owner = parent;
try
{
var assembly = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);
AssemblyNameLabel.Text = assembly.ProductName;
VersionNumberLabel.Text = assembly.ProductVersion;
DescriptionLabel.Text = assembly.Comments;
DeveloperInfoLabel.Text = assembly.CompanyName;
CopyrightInfoLabel.Text = assembly.LegalCopyright;
}
catch (FileNotFoundException)
{
// Can be thrown by the GetVersionInfo call
}
}
}
}
================================================
FILE: Sentinel/Controls/ImageTypesControl.xaml
================================================
================================================
FILE: Sentinel/Controls/ImageTypesControl.xaml.cs
================================================
namespace Sentinel.Controls
{
using System.Windows.Controls;
using Sentinel.Images.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for ImageTypesControl.xaml.
///
public partial class ImageTypesControl : UserControl
{
public ImageTypesControl()
{
InitializeComponent();
Images = ServiceLocator.Instance.Get();
DataContext = this;
}
public ITypeImageService Images { get; private set; }
}
}
================================================
FILE: Sentinel/Controls/IntegerTextBox.cs
================================================
namespace Sentinel.Controls
{
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
public class IntegerTextBox : TextBox
{
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
e.Handled = !e.Text.All(char.IsDigit);
base.OnPreviewTextInput(e);
}
}
}
================================================
FILE: Sentinel/Controls/LogActivityControl.xaml
================================================
================================================
FILE: Sentinel/Controls/LogActivityControl.xaml.cs
================================================
namespace Sentinel.Controls
{
using Sentinel.Highlighters.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for LogActivityControl.xaml.
///
public partial class LogActivityControl
{
public LogActivityControl()
{
InitializeComponent();
Highlight = ServiceLocator.Instance.Get>();
}
public IHighlightingService Highlight { get; private set; }
}
}
================================================
FILE: Sentinel/Controls/MainWindow.xaml
================================================
================================================
FILE: Sentinel/Controls/MainWindow.xaml.cs
================================================
namespace Sentinel.Controls
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Controls.Ribbon;
using System.Windows.Data;
using System.Windows.Input;
using CommandLine;
using log4net;
using Microsoft.Win32;
using Sentinel.Classification.Interfaces;
using Sentinel.Extractors.Interfaces;
using Sentinel.Filters.Interfaces;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Interfaces;
using Sentinel.Interfaces.CodeContracts;
using Sentinel.Log4Net;
using Sentinel.Logs.Interfaces;
using Sentinel.NLog;
using Sentinel.Providers.Interfaces;
using Sentinel.Services;
using Sentinel.Services.Interfaces;
using Sentinel.StartUp;
using Sentinel.Support;
using Sentinel.Views.Interfaces;
using WpfExtras;
using WpfExtras.Converters;
///
/// Interaction logic for MainWindow.xaml.
///
public partial class MainWindow
{
private static readonly ILog Log = log4net.LogManager.GetLogger(typeof(MainWindow));
private readonly string persistingFilename;
private readonly string persistingRecentFileName;
private List recentFilePathList;
private PreferencesWindow preferencesWindow;
private int preferencesWindowTabSelected;
public MainWindow()
{
InitializeComponent();
var savingDirectory = ServiceLocator.Instance.SaveLocation;
persistingFilename = Path.Combine(savingDirectory, "MainWindow");
persistingRecentFileName = Path.Combine(savingDirectory, "RecentFiles");
var fileName = Path.ChangeExtension(persistingFilename, ".json");
var settings = PersistingSettings.Load(fileName);
// Restore persisted window placement if provided
if (settings?.WindowPlacementInfo != null)
{
RestoreWindowPosition(settings.WindowPlacementInfo);
}
if (settings?.UserPreferences != null)
{
// TODO: is this already set?
Preferences = settings.UserPreferences;
}
// Get recently opened files
GetRecentlyOpenedFiles();
}
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Add { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand About { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand ShowPreferences { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand ExportLogs { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Exit { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand NewSession { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand SaveSession { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand LoadSession { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public IUserPreferences Preferences { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public IViewManager ViewManager { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public IFilteringService Filters => ServiceLocator.Instance.Get>();
// ReSharper disable once MemberCanBePrivate.Global
public IHighlightingService Highlighters
=> ServiceLocator.Instance.Get>();
// ReSharper disable once MemberCanBePrivate.Global
public IClassifyingService ClassifyingService
=> ServiceLocator.Instance.Get>();
// ReSharper disable once MemberCanBePrivate.Global
public IExtractingService Extractors
=> ServiceLocator.Instance.Get>();
// ReSharper disable once MemberCanBePrivate.Global
public ISearchHighlighter Search => ServiceLocator.Instance.Get();
// ReSharper disable once MemberCanBePrivate.Global
public ISearchFilter SearchFilter => ServiceLocator.Instance.Get();
// ReSharper disable once MemberCanBePrivate.Global
public ISearchExtractor SearchExtractor => ServiceLocator.Instance.Get();
// ReSharper disable once MemberCanBePrivate.Global
public ObservableCollection RecentFiles { get; private set; }
private static WindowPlacementInfo ValidateScreenPosition(WindowPlacementInfo wp)
{
if (wp == null)
{
return null;
}
try
{
var virtualScreen = new Rect(
SystemParameters.VirtualScreenLeft,
SystemParameters.VirtualScreenTop,
SystemParameters.VirtualScreenWidth,
SystemParameters.VirtualScreenHeight);
var window = new Rect(wp.Left, wp.Top, wp.Width, wp.Height);
return virtualScreen.IntersectsWith(window) ? wp : null;
}
catch (Exception e)
{
Log.Error("Unable to calculate rectangle or perform intersection with window", e);
}
return null;
}
private void ShowPreferencesAction(object obj)
{
preferencesWindowTabSelected = Convert.ToInt32(obj);
Preferences.Show = true;
}
private void ExportLogsAction(object obj)
{
// Get Log
var tab = (TabItem)TabControl.SelectedItem;
var frame = (IWindowFrame)tab.Content;
var restartLogging = false;
// Notify user that log messages will be paused during this operation
if (frame.Log.Enabled)
{
var messageBoxResult =
MessageBox.Show(
"The log viewer must be paused momentarily for this operation to continue. Is it OK to pause logging?",
"Sentinel",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (messageBoxResult == MessageBoxResult.Yes)
{
frame.Log.Enabled = false;
restartLogging = true;
}
else
{
return;
}
}
// Open a save file dialog
var savefile = new SaveFileDialog
{
FileName = frame.Log.Name,
DefaultExt = ".log",
Filter = "Log documents (.log)|*.log|Text documents (.txt)|*.txt",
FilterIndex = 0,
};
if (savefile.ShowDialog(this) == true)
{
var logFileExporter = ServiceLocator.Instance.Get();
logFileExporter.SaveLogViewerToFile(frame, savefile.FileName);
}
frame.Log.Enabled = restartLogging;
}
private void SaveSessionAction(object obj)
{
var sessionManager = ServiceLocator.Instance.Get();
// Open a save file dialog
var savefile = new SaveFileDialog
{
FileName = sessionManager.Name,
DefaultExt = ".sntl",
Filter = "Sentinel session (.sntl)|*.sntl",
FilterIndex = 0,
};
if (savefile.ShowDialog(this) == true)
{
sessionManager.SaveSession(savefile.FileName);
AddToRecentFiles(savefile.FileName);
}
}
private void AddToRecentFiles(string fileName)
{
if (RecentFiles.Contains(fileName))
{
RecentFiles.Move(RecentFiles.IndexOf(fileName), 0);
}
else
{
RecentFiles.Insert(0, fileName);
}
// Keep list at no more than 13
if (RecentFiles.Count > 13)
{
RecentFiles.Remove(RecentFiles.LastOrDefault());
}
}
private void NewSessionAction(object obj)
{
var sessionManager = ServiceLocator.Instance.Get();
if (!sessionManager.IsSaved)
{
var userResult = MessageBox.Show(
"Do you want to save changes you made to " + sessionManager.Name + "?",
"Sentinel",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Warning);
if (userResult == MessageBoxResult.Cancel)
{
return;
}
if (userResult == MessageBoxResult.Yes)
{
SaveSession.Execute(null);
// if the user clicked "Cancel" at the save dialog box
if (!sessionManager.IsSaved)
{
return;
}
}
}
// Remove the tab control.
if (TabControl.Items.Count > 0)
{
var tab = TabControl.SelectedItem;
TabControl.Items.Remove(tab);
}
Add.Execute(null);
}
private void LoadSessionAction(object obj)
{
var sessionManager = ServiceLocator.Instance.Get();
var fileNameToLoad = (string)obj;
if (!sessionManager.IsSaved)
{
var userResult = MessageBox.Show(
"Do you want to save changes you made to " + sessionManager.Name + "?",
"Sentinel",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Warning);
if (userResult == MessageBoxResult.Cancel)
{
return;
}
if (userResult == MessageBoxResult.Yes)
{
SaveSession.Execute(null);
// if the user clicked "Cancel" at the save dialog box
if (!sessionManager.IsSaved)
{
return;
}
}
}
if (fileNameToLoad == null)
{
// open a save file dialog
var openFile = new OpenFileDialog
{
FileName = sessionManager.Name,
DefaultExt = ".sntl",
Filter = "Sentinel session (.sntl)|*.sntl",
FilterIndex = 0,
};
if (openFile.ShowDialog(this) == true)
{
fileNameToLoad = openFile.FileName;
}
else
{
return;
}
}
// Remove the tab control.
if (TabControl.Items.Count > 0)
{
var tab = TabControl.SelectedItem;
TabControl.Items.Remove(tab);
}
RemoveBindingReferences();
sessionManager.LoadSession(fileNameToLoad);
AddToRecentFiles(fileNameToLoad);
BindViewToViewModel();
if (!sessionManager.ProviderSettings.Any())
{
return;
}
var frame = ServiceLocator.Instance.Get();
// Add to the tab control.
var newTab = new TabItem { Header = sessionManager.Name, Content = frame };
TabControl.Items.Add(newTab);
TabControl.SelectedItem = newTab;
}
///
/// AddNewListenerAction method provides a mechanism for the user to add additional
/// listeners to the log-viewer.
///
/// Object to add as a new listener.
private void AddNewListenerAction(object obj)
{
// Load a new session
var sessionManager = ServiceLocator.Instance.Get();
RemoveBindingReferences();
sessionManager.LoadNewSession(this);
BindViewToViewModel();
if (!sessionManager.ProviderSettings.Any())
{
return;
}
var frame = ServiceLocator.Instance.Get();
// Add to the tab control.
var tab = new TabItem { Header = sessionManager.Name, Content = frame };
TabControl.Items.Add(tab);
TabControl.SelectedItem = tab;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Exit = new DelegateCommand(ee => Close());
About = new DelegateCommand(
ee =>
{
var about = new AboutWindow(this);
about.ShowDialog();
});
Add = new DelegateCommand(AddNewListenerAction, b => TabControl.Items.Count < 1);
ShowPreferences = new DelegateCommand(ShowPreferencesAction);
ExportLogs = new DelegateCommand(ExportLogsAction, b => TabControl.Items.Count > 0);
SaveSession = new DelegateCommand(SaveSessionAction);
NewSession = new DelegateCommand(NewSessionAction);
LoadSession = new DelegateCommand(LoadSessionAction);
RecentFiles = new ObservableCollection(recentFilePathList.Take(13));
BindViewToViewModel();
var commandLine = Environment.GetCommandLineArgs();
if (commandLine.Length == 1)
{
Add.Execute(null);
}
else
{
ProcessCommandLine(commandLine.Skip(1));
}
// Debug the available loggers.
var logManager = ServiceLocator.Instance.Get();
foreach (var logger in logManager)
{
Log.DebugFormat("Log: {0}", logger.Name);
}
var providerManager = ServiceLocator.Instance.Get();
foreach (var instance in providerManager.Instances)
{
Log.DebugFormat("Provider: {0}", instance.Name);
Log.DebugFormat(" - is {0}active", instance.IsActive ? string.Empty : "not ");
Log.DebugFormat(" - logger = {0}", instance.Logger);
}
}
private void ProcessCommandLine(IEnumerable commandLine)
{
commandLine.ThrowIfNull(nameof(commandLine));
var commandLineArguments = commandLine as string[] ?? commandLine.ToArray();
if (!commandLineArguments.Any())
{
throw new ArgumentException("Collection must have at least one element", nameof(commandLine));
}
var sessionManager = ServiceLocator.Instance.Get();
var unknownCommandLine = false;
var args = commandLineArguments.ToArray();
var options = Parser.Default.ParseArguments(args).MapResult(
(Log4NetOptions o) => Tuple.Create("log4net", o),
(NLogOptions o) => Tuple.Create("nlog", o),
_ => Tuple.Create(string.Empty, null));
var verb = options.Item1;
if (string.IsNullOrWhiteSpace(options.Item1))
{
var filePath = commandLineArguments.FirstOrDefault();
if (!File.Exists(filePath) || Path.GetExtension(filePath)?.ToUpper() != ".SNTL")
{
unknownCommandLine = true;
}
}
if (unknownCommandLine)
{
// TODO: command line usage dialog
MessageBox.Show(
"File does not exist or is not a Sentinel session file.",
"Sentinel",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
RemoveBindingReferences();
switch (verb)
{
case "nlog":
CreateDefaultNLogListener((NLogOptions)options.Item2, sessionManager);
break;
case "log4net":
CreateDefaultLog4NetListener((Log4NetOptions)options.Item2, sessionManager);
break;
default:
sessionManager.LoadSession(commandLineArguments.FirstOrDefault());
break;
}
BindViewToViewModel();
var frame = ServiceLocator.Instance.Get();
// Add to the tab control.
var newTab = new TabItem { Header = sessionManager.Name, Content = frame };
TabControl.Items.Add(newTab);
TabControl.SelectedItem = newTab;
}
private void CreateDefaultLog4NetListener(Log4NetOptions log4NetOptions, ISessionManager sessionManager)
{
var info = $"Using log4net listener on Udp port {log4NetOptions.Port}";
Log.Debug(info);
var providerSettings = new UdpAppenderSettings
{
Port = log4NetOptions.Port,
Name = info,
Info = Log4NetProvider.ProviderRegistrationInformation.Info,
};
var providers =
Enumerable.Repeat(
new PendingProviderRecord
{
Info = Log4NetProvider.ProviderRegistrationInformation.Info,
Settings = providerSettings,
},
1);
sessionManager.LoadProviders(providers);
}
private void CreateDefaultNLogListener(NLogOptions verbOptions, ISessionManager sessionManager)
{
var name = $"Using nlog listener on {(verbOptions.IsUdp ? "Udp" : "Tcp")} port {verbOptions.Port}";
var info = NLogViewerProvider.ProviderRegistrationInformation.Info;
Log.Debug(name);
var providerSettings = new NetworkSettings
{
Protocol =
verbOptions.IsUdp
? NetworkProtocol.Udp
: NetworkProtocol.Tcp,
Port = verbOptions.Port,
Name = name,
Info = info,
};
var providers = Enumerable.Repeat(
new PendingProviderRecord { Info = info, Settings = providerSettings },
1);
sessionManager.LoadProviders(providers);
}
private void RestoreWindowPosition(WindowPlacementInfo wp)
{
_ = wp ?? throw new ArgumentNullException(nameof(wp));
// Validation routine will cope with Null being passed and if it finds an error, it returns null.
wp = ValidateScreenPosition(wp);
if (wp != null)
{
Log.DebugFormat(
"Window position being restored to ({0},{1})-({2},{3}) {4}",
wp.Top,
wp.Left,
wp.Top + wp.Height,
wp.Left + wp.Width,
wp.WindowState);
Top = wp.Top;
Left = wp.Left;
Width = wp.Width;
Height = wp.Height;
// TODO: would it make sense to start up minimized if that was how it was terminated?
WindowState = wp.WindowState;
}
}
private void PreferencesChanged(object sender, PropertyChangedEventArgs e)
{
if (Preferences != null)
{
if (e.PropertyName == "Show")
{
if (Preferences.Show)
{
preferencesWindow = new PreferencesWindow(preferencesWindowTabSelected) { Owner = this };
preferencesWindow.Show();
}
else if (preferencesWindow != null)
{
preferencesWindow.Close();
preferencesWindow = null;
}
}
}
}
private void ViewManagerChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
TabControl.SelectedIndex = TabControl.Items.Count - 1;
}
}
private void OnClosed(object sender, CancelEventArgs e)
{
var windowInfo = new WindowPlacementInfo
{
Height = (int)Height,
Top = (int)Top,
Left = (int)Left,
Width = (int)Width,
WindowState = WindowState,
};
var filename = Path.ChangeExtension(persistingFilename, ".json");
PersistingSettings.Save(filename, windowInfo, Preferences);
var recentFileInfo = new RecentFileInfo { RecentFilePaths = RecentFiles.ToList() };
JsonHelper.SerializeToFile(recentFileInfo, Path.ChangeExtension(persistingRecentFileName, ".json"));
}
private void RetainOnlyStandardFilters(object sender, FilterEventArgs e)
{
sender.ThrowIfNull(nameof(sender));
e.ThrowIfNull(nameof(e));
e.Accepted = e.Item is IStandardDebuggingFilter;
}
private void ExcludeStandardFilters(object sender, FilterEventArgs e)
{
sender.ThrowIfNull(nameof(sender));
e.ThrowIfNull(nameof(e));
e.Accepted = !(e.Item is IStandardDebuggingFilter || e.Item is ISearchFilter);
}
private void RetainOnlyStandardHighlighters(object sender, FilterEventArgs e)
{
sender.ThrowIfNull(nameof(sender));
e.ThrowIfNull(nameof(e));
e.Accepted = e.Item is IStandardDebuggingHighlighter;
}
private void ExcludeStandardHighlighters(object sender, FilterEventArgs e)
{
sender.ThrowIfNull(nameof(sender));
e.ThrowIfNull(nameof(e));
e.Accepted = !(e.Item is IStandardDebuggingHighlighter);
}
private void SearchToggleButtonChecked(object sender, RoutedEventArgs e)
{
Debug.Assert(
sender.GetType() == typeof(RibbonToggleButton),
$"A {sender.GetType()} accessed the wrong method");
var button = (RibbonToggleButton)sender;
switch (button.Label)
{
case "Highlight":
BindSearchToSearchHighlighter();
break;
case "Filter":
BindSearchToSearchFilter();
break;
case "Extract":
BindSearchToSearchExtractor();
break;
}
}
private void BindSearchToSearchExtractor()
{
SearchRibbonTextBox.SetBinding(TextBox.TextProperty, CreateBinding("Pattern", SearchExtractor));
SearchModeListBox.SetBinding(Selector.SelectedItemProperty, CreateBinding("Mode", SearchExtractor));
SearchTargetComboBox.SetBinding(Selector.SelectedItemProperty, CreateBinding("Field", SearchExtractor));
HighlightToggleButton.IsChecked = false;
FilterToggleButton.IsChecked = false;
}
private Binding CreateBinding(string path, object source)
{
return new Binding
{
Source = source,
Path = new PropertyPath(path),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
};
}
private void BindSearchToSearchFilter()
{
SearchRibbonTextBox.SetBinding(TextBox.TextProperty, CreateBinding("Pattern", SearchFilter));
SearchModeListBox.SetBinding(Selector.SelectedItemProperty, CreateBinding("Mode", SearchFilter));
SearchTargetComboBox.SetBinding(Selector.SelectedItemProperty, CreateBinding("Field", SearchFilter));
HighlightToggleButton.IsChecked = false;
ExtractToggleButton.IsChecked = false;
}
private void BindSearchToSearchHighlighter()
{
SearchRibbonTextBox.SetBinding(TextBox.TextProperty, CreateBinding("Search", Search));
SearchModeListBox.SetBinding(Selector.SelectedItemProperty, CreateBinding("Mode", Search));
SearchTargetComboBox.SetBinding(Selector.SelectedItemProperty, CreateBinding("Field", Search));
FilterToggleButton.IsChecked = false;
ExtractToggleButton.IsChecked = false;
}
private void RemoveBindingReferences()
{
var notifyPropertyChanged = Preferences as INotifyPropertyChanged;
if (notifyPropertyChanged != null)
{
notifyPropertyChanged.PropertyChanged -= PreferencesChanged;
}
ViewManager.Viewers.CollectionChanged -= ViewManagerChanged;
}
private void BindViewToViewModel()
{
// Append version number to caption (to save effort of producing an about screen)
Title =
$"{Assembly.GetExecutingAssembly().GetName().Name} ({Assembly.GetExecutingAssembly().GetName().Version})"
+ $" {ServiceLocator.Instance.Get().Name}";
// Preferences, if initialised, has come from persistence, so register current copy
// otherwise, ask for one that will be shared.
if (Preferences != null)
{
ServiceLocator.Instance.Register(Preferences);
}
else
{
Preferences = ServiceLocator.Instance.Get();
}
ViewManager = ServiceLocator.Instance.Get();
// Maintaining column widths is proving difficult in Xaml alone, so
// add an observer here and deal with it in code.
if (Preferences is INotifyPropertyChanged changed)
{
changed.PropertyChanged += PreferencesChanged;
}
DataContext = this;
// When a new item is added, select the newest one.
ViewManager.Viewers.CollectionChanged += ViewManagerChanged;
// View-specific bindings
var collapseIfZero = new CollapseIfZeroConverter();
var standardHighlighters = new CollectionViewSource { Source = Highlighters.Highlighters };
standardHighlighters.View.Filter = c => c is IStandardDebuggingHighlighter;
var customHighlighters = new CollectionViewSource { Source = Highlighters.Highlighters };
customHighlighters.View.Filter = c => !(c is IStandardDebuggingHighlighter);
StandardHighlightersRibbonGroup.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = standardHighlighters });
StandardHighlighterRibbonGroupOnTab.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = standardHighlighters });
var collapsingStandardHighlightersBinding = new Binding
{
Source = standardHighlighters,
Path = new PropertyPath("Count"),
Converter = collapseIfZero,
};
StandardHighlighterRibbonGroupOnTab.SetBinding(VisibilityProperty, collapsingStandardHighlightersBinding);
CustomHighlighterRibbonGroupOnTab.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = customHighlighters });
var collapsingCustomHighlightersBinding = new Binding
{
Source = customHighlighters,
Path = new PropertyPath("Count"),
Converter = collapseIfZero,
};
CustomHighlighterRibbonGroupOnTab.SetBinding(VisibilityProperty, collapsingCustomHighlightersBinding);
var standardFilters = new CollectionViewSource { Source = Filters.Filters };
standardFilters.View.Filter = c => c is IStandardDebuggingFilter;
var customFilters = new CollectionViewSource { Source = Filters.Filters };
customFilters.View.Filter = c => !(c is IStandardDebuggingFilter);
StandardFiltersRibbonGroup.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = standardFilters });
StandardFiltersRibbonGroupOnTab.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = standardFilters });
StandardFiltersRibbonGroupOnTab.SetBinding(
VisibilityProperty,
new Binding { Source = standardFilters, Path = new PropertyPath("Count"), Converter = collapseIfZero });
CustomFiltersRibbonGroupOnTab.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = customFilters });
CustomFiltersRibbonGroupOnTab.SetBinding(
VisibilityProperty,
new Binding { Source = customFilters, Path = new PropertyPath("Count"), Converter = collapseIfZero });
var customExtractors = Extractors.Extractors;
CustomExtractorsRibbonGroupOnTab.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = customExtractors });
var customClassifyiers = ClassifyingService.Classifiers;
CustomClassifiersRibbonGroupOnTab.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding { Source = customClassifyiers });
BindToSearchElements();
// Column view buttons
ExceptionRibbonToggleButton.SetBinding(
ToggleButton.IsCheckedProperty,
new Binding { Source = Preferences, Path = new PropertyPath("ShowExceptionColumn") });
ThreadRibbonToggleButton.SetBinding(
ToggleButton.IsCheckedProperty,
new Binding { Source = Preferences, Path = new PropertyPath("ShowThreadColumn") });
SourceHostRibbonToggleButton.SetBinding(
ToggleButton.IsCheckedProperty,
new Binding { Source = Preferences, Path = new PropertyPath("ShowSourceColumn") });
DebugSourceRibbonToggleButton.SetBinding(
ToggleButton.IsCheckedProperty,
new Binding { Source = Preferences, Path = new PropertyPath("ShowSourceInformationColumns") });
ContextRibbonToggleButton.SetBinding(
ToggleButton.IsCheckedProperty,
new Binding { Source = Preferences, Path = new PropertyPath("ShowContextColumn") });
}
private void BindToSearchElements()
{
// Bind search
HighlightToggleButton.SetBinding(ToggleButton.IsCheckedProperty, CreateBinding("Enabled", Search));
FilterToggleButton.SetBinding(ToggleButton.IsCheckedProperty, CreateBinding("Enabled", SearchFilter));
ExtractToggleButton.SetBinding(ToggleButton.IsCheckedProperty, CreateBinding("Enabled", SearchExtractor));
if (Search.Enabled)
{
BindSearchToSearchHighlighter();
}
else if (SearchFilter.Enabled)
{
BindSearchToSearchFilter();
}
else if (SearchExtractor.Enabled)
{
BindSearchToSearchExtractor();
}
}
private void GetRecentlyOpenedFiles()
{
if (string.IsNullOrWhiteSpace(persistingRecentFileName))
{
return;
}
var fileName = Path.ChangeExtension(persistingRecentFileName, ".json");
var recentFileInfo = JsonHelper.DeserializeFromFile(fileName);
recentFilePathList = recentFileInfo?.RecentFilePaths.ToList() ?? new List();
}
}
}
================================================
FILE: Sentinel/Controls/PersistingSettings.cs
================================================
namespace Sentinel.Controls
{
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using Sentinel.Interfaces;
using Sentinel.Support;
[DataContract]
public class PersistingSettings
{
[DataMember]
public WindowPlacementInfo WindowPlacementInfo { get; set; }
[DataMember]
public IUserPreferences UserPreferences { get; set; }
internal static PersistingSettings Load(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new System.ArgumentException("Value cannot be null or whitespace.", nameof(fileName));
}
// Note that PersistingSettings is a new file format, so need to detect whether using
// new serialisation or upgrading.
if (System.IO.File.Exists(fileName))
{
var fileContents = System.IO.File.ReadAllText(fileName);
// Version detect from file-header signature:
var fileHeader = fileContents
.Substring(0, 52)
.Replace(" ", string.Empty)
.Replace("\r", string.Empty)
.Replace("\n", string.Empty);
switch (fileHeader)
{
case "{\"$type\":\"Sentinel.Controls.WindowPlacementInfo":
return DeserializeFromV1(fileContents);
case "{\"$type\":\"Sentinel.Controls.PersistingSettings,":
return DeserializeFromV2(fileContents);
default:
return null;
}
}
return null;
}
internal static void Save(string fileName, WindowPlacementInfo placementInfo, IUserPreferences userPreferences)
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName));
}
var wrapper = new PersistingSettings
{
WindowPlacementInfo = placementInfo,
UserPreferences = userPreferences,
};
try
{
JsonHelper.SerializeToFile(wrapper, fileName);
}
catch (Exception e)
{
Trace.TraceError($"Unable to write persistence file: {e.Message}");
}
}
private static PersistingSettings DeserializeFromV1(string fileContents)
{
Trace.WriteLine("DeserializeFromV1");
PersistingSettings wrapper = null;
try
{
wrapper = new PersistingSettings
{
WindowPlacementInfo = JsonHelper.DeserializeFromString(fileContents),
};
}
catch (Exception e)
{
Trace.TraceError($"Deserialize error: {e.Message}");
}
return wrapper;
}
private static PersistingSettings DeserializeFromV2(string fileContents)
{
Trace.WriteLine("DeserializeFromV1");
try
{
return JsonHelper.DeserializeFromString(fileContents);
}
catch (Exception e)
{
Trace.TraceError($"Deserialize error: {e.Message}");
return null;
}
}
}
}
================================================
FILE: Sentinel/Controls/PreferencesControl.xaml
================================================
Convert UTC times to local timezone
Use time of message receipt by Sentinel
Use sender's time
================================================
FILE: Sentinel/Controls/PreferencesControl.xaml.cs
================================================
namespace Sentinel.Controls
{
///
/// Interaction logic for PreferencesControl.xaml.
///
public partial class PreferencesControl
{
public PreferencesControl()
{
InitializeComponent();
}
}
}
================================================
FILE: Sentinel/Controls/PreferencesWindow.xaml
================================================
================================================
FILE: Sentinel/Controls/PreferencesWindow.xaml.cs
================================================
namespace Sentinel.Controls
{
using System;
using Sentinel.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for PreferencesWindow.xaml.
///
public partial class PreferencesWindow
{
public PreferencesWindow()
: this(0)
{
}
public PreferencesWindow(int selectedTabIndex)
{
InitializeComponent();
Preferences = ServiceLocator.Instance.Get();
SelectedTabIndex = selectedTabIndex;
DataContext = this;
}
// ReSharper disable once MemberCanBePrivate.Global
public int SelectedTabIndex { get; set; }
// ReSharper disable once MemberCanBePrivate.Global
public IUserPreferences Preferences { get; private set; }
private void WindowClosed(object sender, EventArgs e)
{
Preferences.Show = false;
}
}
}
================================================
FILE: Sentinel/Controls/RecentFileInfo.cs
================================================
namespace Sentinel.Controls
{
using System.Collections.Generic;
using System.Runtime.Serialization;
[DataContract]
public class RecentFileInfo
{
[DataMember]
public IEnumerable RecentFilePaths { get; set; }
}
}
================================================
FILE: Sentinel/Controls/WindowPlacementInfo.cs
================================================
namespace Sentinel.Controls
{
using System.Runtime.Serialization;
using System.Windows;
[DataContract]
public class WindowPlacementInfo
{
[DataMember]
public int Top { get; set; }
[DataMember]
public int Left { get; set; }
[DataMember]
public int Width { get; set; }
[DataMember]
public int Height { get; set; }
[DataMember]
public WindowState WindowState { get; set; }
}
}
================================================
FILE: Sentinel/EventLogMonitor/CommandLineOptions.cs
================================================
namespace Sentinel.EventLogMonitor
{
using CommandLine;
public class CommandLineOptions
{
[Option('b', "no-banner", Default = false, HelpText = "Hide the copyright banner shown on application startup")]
public bool SuppressBanner { get; set; }
}
}
================================================
FILE: Sentinel/EventLogMonitor/EventLogEntry.cs
================================================
namespace Sentinel.EventLogMonitor
{
using System;
using System.Diagnostics;
using System.Text;
using Sentinel.EventLogMonitor.Interfaces;
using Sentinel.Interfaces.CodeContracts;
internal class EventLogEntry : IEventLogEntry
{
public EventLogEntry(System.Diagnostics.EventLogEntry entry)
{
entry.ThrowIfNull(nameof(entry));
Entry = entry;
}
public string MachineName => Entry.MachineName;
public EventLogEntryType EntryType => Entry.EntryType;
public string Message => Entry.Message;
public DateTime EventTime => Entry.TimeGenerated;
public string Category => Entry.Category;
public long InstanceId => Entry.InstanceId;
public string Source => Entry.Source;
public string UserName => Entry.UserName;
private System.Diagnostics.EventLogEntry Entry { get; }
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine($"Type : {EntryType}");
sb.AppendLine($"Created : {EventTime}");
sb.AppendLine($"Category: {Category}");
sb.AppendLine($"Message : {Message}");
sb.AppendLine($"Machine : {MachineName}");
sb.AppendLine($"Source : {Source}");
sb.AppendLine($"User : {UserName}");
sb.AppendLine($"Inst Id : {InstanceId}");
return sb.ToString();
}
}
}
================================================
FILE: Sentinel/EventLogMonitor/Interfaces/IEventLogEntry.cs
================================================
namespace Sentinel.EventLogMonitor.Interfaces
{
using System;
using System.Diagnostics;
using Newtonsoft.Json;
public interface IEventLogEntry
{
[JsonProperty(Order = 1, Required = Required.Always)]
EventLogEntryType EntryType { get; }
[JsonProperty(Order = 2)]
string Message { get; }
[JsonProperty(Order = 3)]
string Source { get; }
[JsonProperty(Order = 4)]
DateTime EventTime { get; }
[JsonProperty(Order = 5)]
string Category { get; }
[JsonProperty(Order = 6, DefaultValueHandling = DefaultValueHandling.Ignore)]
long InstanceId { get; }
[JsonProperty(Order = 7, DefaultValueHandling = DefaultValueHandling.Ignore)]
string MachineName { get; }
[JsonProperty(Order = 8, DefaultValueHandling = DefaultValueHandling.Ignore)]
string UserName { get; }
}
}
================================================
FILE: Sentinel/Extractors/ExtractingService.cs
================================================
namespace Sentinel.Extractors
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows.Input;
using Sentinel.Extractors.Gui;
using Sentinel.Extractors.Interfaces;
using Sentinel.Interfaces;
using Sentinel.Services;
using WpfExtras;
[DataContract]
public class ExtractingService : ViewModelBase, IExtractingService, IDefaultInitialisation
where T : class, IExtractor
{
private readonly CollectionChangeHelper collectionHelper = new CollectionChangeHelper();
private readonly IAddExtractorService addExtractorService = new AddExtractor();
private readonly IEditExtractorService editExtractorService = new EditExtractor();
private readonly IRemoveExtractorService removeExtractorService = new RemoveExtractor();
private int selectedIndex = -1;
public ExtractingService()
{
Add = new DelegateCommand(AddExtractor);
Edit = new DelegateCommand(EditExtractor, e => selectedIndex != -1);
Remove = new DelegateCommand(RemoveExtractor, e => selectedIndex != -1);
Extractors = new ObservableCollection();
SearchExtractors = new ObservableCollection();
// Register self as an observer of the collection.
collectionHelper.OnPropertyChanged += CustomExtractorPropertyChanged;
collectionHelper.ManagerName = "ExtractingService";
collectionHelper.NameLookup += e => e.Name;
Extractors.CollectionChanged += collectionHelper.AttachDetach;
SearchExtractors.CollectionChanged += collectionHelper.AttachDetach;
var searchExtractor = ServiceLocator.Instance.Get();
if (searchExtractor != null)
{
SearchExtractors.Add(searchExtractor as T);
}
else
{
Debug.Fail("The search extractor is null.");
}
}
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Add { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Edit { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public ICommand Remove { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
public int SelectedIndex
{
get
{
return selectedIndex;
}
set
{
if (value != selectedIndex)
{
selectedIndex = value;
OnPropertyChanged(nameof(SelectedIndex));
}
}
}
public ObservableCollection Extractors { get; set; }
public ObservableCollection SearchExtractors { get; set; }
public void Initialise()
{
// For adding standard extractors
}
public bool IsFiltered(ILogEntry entry)
{
return Extractors.Any(filter => filter.Enabled && filter.IsMatch(entry))
|| SearchExtractors.Any(filter => filter.Enabled && filter.IsMatch(entry));
}
private void AddExtractor(object obj)
{
addExtractorService.Add();
}
private void CustomExtractorPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var extractor = sender as Extractor;
if (extractor != null)
{
Trace.WriteLine(
$"ExtractingService saw some activity on {extractor.Name} (IsEnabled = {extractor.Enabled})");
}
OnPropertyChanged(string.Empty);
}
private void EditExtractor(object obj)
{
var extractor = Extractors.ElementAt(SelectedIndex);
if (extractor != null)
{
editExtractorService.Edit(extractor);
}
}
private void RemoveExtractor(object obj)
{
var extractor = Extractors.ElementAt(SelectedIndex);
removeExtractorService.Remove(extractor);
}
}
}
================================================
FILE: Sentinel/Extractors/Extractor.cs
================================================
namespace Sentinel.Extractors
{
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Sentinel.Extractors.Interfaces;
using Sentinel.Interfaces;
using WpfExtras;
[DataContract]
public class Extractor : ViewModelBase, IExtractor
{
///
/// Is the extractor enabled? If so, it will remove anything matching from the output.
///
private bool enabled;
private string name;
private string pattern;
private LogEntryFields field;
private MatchMode mode = MatchMode.Exact;
public Extractor()
{
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "Field" || e.PropertyName == "Mode" || e.PropertyName == "Pattern")
{
OnPropertyChanged(nameof(Description));
}
};
}
public Extractor(string name, LogEntryFields field, string pattern)
{
Name = name;
Pattern = pattern;
Field = field;
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
///
/// Gets or sets a value indicating whether the extractor is enabled.
///
public bool Enabled
{
get
{
return enabled;
}
set
{
if (value != enabled)
{
enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (pattern != value)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public LogEntryFields Field
{
get
{
return field;
}
set
{
if (field != value)
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
}
public MatchMode Mode
{
get
{
return mode;
}
set
{
if (mode != value)
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
}
public string Description
{
get
{
string modeDescription = "Exact";
switch (Mode)
{
case MatchMode.RegularExpression:
modeDescription = "RegEx";
break;
case MatchMode.CaseSensitive:
modeDescription = "Substring";
break;
}
return $"{modeDescription} match of {Pattern} in the {Field} field";
}
}
public bool IsMatch(ILogEntry logEntry)
{
Debug.Assert(logEntry != null, "LogEntry can not be null.");
if (string.IsNullOrWhiteSpace(Pattern))
{
return false;
}
string target;
switch (Field)
{
case LogEntryFields.None:
target = string.Empty;
break;
case LogEntryFields.Type:
target = logEntry.Type;
break;
case LogEntryFields.System:
target = logEntry.System;
break;
case LogEntryFields.Classification:
target = string.Empty;
break;
case LogEntryFields.Thread:
target = logEntry.Thread;
break;
case LogEntryFields.Source:
target = logEntry.Source;
break;
case LogEntryFields.Description:
target = logEntry.Description;
break;
case LogEntryFields.Host:
target = string.Empty;
break;
default:
target = string.Empty;
break;
}
switch (Mode)
{
case MatchMode.Exact:
return !target.Equals(Pattern);
case MatchMode.CaseSensitive:
return !target.Contains(Pattern);
case MatchMode.CaseInsensitive:
return !target.ToUpperInvariant().Contains(Pattern.ToUpperInvariant());
case MatchMode.RegularExpression:
var regex = new Regex(Pattern);
return !regex.IsMatch(target);
default:
return false;
}
}
#if DEBUG
public override string ToString()
{
return Description;
}
#endif
}
}
================================================
FILE: Sentinel/Extractors/Gui/AddEditExtractor.cs
================================================
namespace Sentinel.Extractors.Gui
{
using System.Windows;
using System.Windows.Input;
using Sentinel.Interfaces;
using WpfExtras;
public class AddEditExtractor : ViewModelBase
{
private readonly Window window;
private string name = "Unnamed";
private string pattern = "pattern";
private LogEntryFields field;
private MatchMode mode;
public AddEditExtractor(Window window, bool editMode)
{
this.window = window;
if (window != null)
{
window.Title = $"{(editMode ? "Edit" : "Register")} Extractor";
}
Accept = new DelegateCommand(AcceptDialog, Validates);
Reject = new DelegateCommand(RejectDialog);
}
public ICommand Accept { get; private set; }
public LogEntryFields Field
{
get
{
return field;
}
set
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
public MatchMode Mode
{
get
{
return mode;
}
set
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (value != pattern)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public ICommand Reject { get; private set; }
private void AcceptDialog(object obj)
{
window.DialogResult = true;
window.Close();
}
private void RejectDialog(object obj)
{
window.DialogResult = false;
window.Close();
}
private bool Validates(object obj)
{
return Name.Length > 0 && Pattern.Length > 0;
}
}
}
================================================
FILE: Sentinel/Extractors/Gui/AddEditExtractorWindow.xaml
================================================
================================================
FILE: Sentinel/Extractors/Gui/AddEditExtractorWindow.xaml.cs
================================================
namespace Sentinel.Extractors.Gui
{
using System.Windows;
///
/// Interaction logic for AddEditExtractorWindow.xaml.
///
public partial class AddEditExtractorWindow : Window
{
public AddEditExtractorWindow()
{
InitializeComponent();
}
}
}
================================================
FILE: Sentinel/Extractors/Gui/AddExtractor.cs
================================================
namespace Sentinel.Extractors.Gui
{
using System.Windows;
using Sentinel.Extractors.Interfaces;
using Sentinel.Services;
public class AddExtractor : IAddExtractorService
{
public void Add()
{
var extractorWindow = new AddEditExtractorWindow();
using (var data = new AddEditExtractor(extractorWindow, false))
{
extractorWindow.DataContext = data;
extractorWindow.Owner = Application.Current.MainWindow;
var dialogResult = extractorWindow.ShowDialog();
if (dialogResult == null || !(bool)dialogResult)
{
return;
}
var extractor = Construct(data);
if (extractor == null)
{
return;
}
var service = ServiceLocator.Instance.Get>();
service?.Extractors.Add(extractor);
}
}
private static Extractor Construct(AddEditExtractor data)
{
return new Extractor
{
Name = data.Name,
Field = data.Field,
Mode = data.Mode,
Pattern = data.Pattern,
Enabled = true,
};
}
}
}
================================================
FILE: Sentinel/Extractors/Gui/EditExtractor.cs
================================================
namespace Sentinel.Extractors.Gui
{
using System.Diagnostics;
using System.Windows;
using Sentinel.Extractors.Interfaces;
public class EditExtractor : IEditExtractorService
{
public void Edit(IExtractor extractor)
{
Debug.Assert(extractor != null, "Extractor must be supplied to allow editing.");
var window = new AddEditExtractorWindow();
var data = new AddEditExtractor(window, true);
window.DataContext = data;
window.Owner = Application.Current.MainWindow;
data.Name = extractor.Name;
data.Field = extractor.Field;
data.Pattern = extractor.Pattern;
data.Mode = extractor.Mode;
var dialogResult = window.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
extractor.Name = data.Name;
extractor.Pattern = data.Pattern;
extractor.Mode = data.Mode;
extractor.Field = data.Field;
}
}
}
}
================================================
FILE: Sentinel/Extractors/Gui/ExtractorsControl.xaml
================================================
================================================
FILE: Sentinel/Extractors/Gui/ExtractorsControl.xaml.cs
================================================
namespace Sentinel.Extractors.Gui
{
using System.Windows.Controls;
using Sentinel.Extractors.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for ExtractorsControl.xaml.
///
public partial class ExtractorsControl : UserControl
{
public ExtractorsControl()
{
InitializeComponent();
var service = ServiceLocator.Instance.Get>();
if (service != null)
{
Extractors = service;
}
DataContext = this;
}
public IExtractingService Extractors { get; private set; }
}
}
================================================
FILE: Sentinel/Extractors/Gui/RemoveExtractor.cs
================================================
namespace Sentinel.Extractors.Gui
{
using System.Windows;
using Sentinel.Extractors.Interfaces;
using Sentinel.Services;
public class RemoveExtractor
: IRemoveExtractorService
{
public void Remove(IExtractor extractor)
{
var service = ServiceLocator.Instance.Get>();
if (service != null)
{
var prompt = "Are you sure you want to remove the selected extractor?\r\n\r\n" +
$"Extractor Name = \"{extractor.Name}\"";
var result = MessageBox.Show(
prompt,
"Remove Extractor",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No);
if (result == MessageBoxResult.Yes)
{
service.Extractors.Remove(extractor);
}
}
}
}
}
================================================
FILE: Sentinel/Extractors/Interfaces/IAddExtractorService.cs
================================================
namespace Sentinel.Extractors.Interfaces
{
public interface IAddExtractorService
{
void Add();
}
}
================================================
FILE: Sentinel/Extractors/Interfaces/IEditExtractorService.cs
================================================
namespace Sentinel.Extractors.Interfaces
{
public interface IEditExtractorService
{
void Edit(IExtractor extractor);
}
}
================================================
FILE: Sentinel/Extractors/Interfaces/IExtractingService.cs
================================================
namespace Sentinel.Extractors.Interfaces
{
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using Sentinel.Interfaces;
public interface IExtractingService
{
[DataMember]
ObservableCollection Extractors { get; set; }
[IgnoreDataMember]
ObservableCollection SearchExtractors { get; set; }
bool IsFiltered(ILogEntry entry);
}
}
================================================
FILE: Sentinel/Extractors/Interfaces/IExtractor.cs
================================================
namespace Sentinel.Extractors.Interfaces
{
using System.Runtime.Serialization;
using Sentinel.Interfaces;
public interface IExtractor
{
[DataMember]
string Name { get; set; }
///
/// Gets or sets a value indicating whether the filter is enabled.
///
[DataMember]
bool Enabled { get; set; }
[DataMember]
string Pattern { get; set; }
[DataMember]
string Description { get; }
[DataMember]
LogEntryFields Field { get; set; }
[DataMember]
MatchMode Mode { get; set; }
bool IsMatch(ILogEntry entry);
}
}
================================================
FILE: Sentinel/Extractors/Interfaces/IRemoveExtractorService.cs
================================================
namespace Sentinel.Extractors.Interfaces
{
public interface IRemoveExtractorService
{
void Remove(IExtractor extractor);
}
}
================================================
FILE: Sentinel/Extractors/Interfaces/ISearchExtractor.cs
================================================
namespace Sentinel.Extractors.Interfaces
{
public interface ISearchExtractor : IExtractor
{
}
}
================================================
FILE: Sentinel/Extractors/SearchExtractor.cs
================================================
namespace Sentinel.Extractors
{
using System.Runtime.Serialization;
using Sentinel.Extractors.Interfaces;
using Sentinel.Interfaces;
[DataContract]
public class SearchExtractor
: Extractor, IDefaultInitialisation, ISearchExtractor
{
public void Initialise()
{
Name = "SearchExtractor";
Field = LogEntryFields.System;
Pattern = string.Empty;
}
}
}
================================================
FILE: Sentinel/FileMonitor/CustomMessageDecoderPage.xaml
================================================
Define a regular expression, using named captures, to decompose the log message into its appropriate fields.An example pattern would therefore include something like:Common field names are Description, Type and DateTime and at least one of these must be matched.
================================================
FILE: Sentinel/FileMonitor/CustomMessageDecoderPage.xaml.cs
================================================
namespace Sentinel.FileMonitor
{
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using WpfExtras;
///
/// Interaction logic for CustomMessageDecoderPage.xaml.
///
public partial class CustomMessageDecoderPage : IWizardPage, IDataErrorInfo
{
private readonly ObservableCollection children = new ObservableCollection();
private readonly ReadOnlyObservableCollection readonlyChildren;
private string customFormat;
private string error;
private bool isValid;
public CustomMessageDecoderPage()
{
InitializeComponent();
DataContext = this;
readonlyChildren = new ReadOnlyObservableCollection(children);
PropertyChanged += PropertyChangedHandler;
}
public event PropertyChangedEventHandler PropertyChanged;
public string Title => "Custom Message Decoder";
public ReadOnlyObservableCollection Children => readonlyChildren;
public string Description => "Specify how to decompose the message into its individual fields.";
public bool IsValid
{
get => isValid;
set
{
if (isValid != value)
{
isValid = value;
OnPropertyChanged(nameof(IsValid));
}
}
}
public Control PageContent => this;
public string CustomFormat
{
get => customFormat;
set
{
if (customFormat != value)
{
customFormat = value;
OnPropertyChanged(nameof(CustomFormat));
}
}
}
///
/// Gets an error message indicating what is wrong with this object.
///
///
/// An error message indicating what is wrong with this object. The default is a null.
public string Error
{
get => error;
private set
{
if (error != value)
{
error = value;
OnPropertyChanged(nameof(Error));
}
}
}
///
/// Gets the error message for the property with the given name.
///
///
/// The error message for the property. The default is a null.
/// The name of the property whose error message to get.
public string this[string columnName]
{
get
{
if (columnName == "CustomFormat")
{
string err = null;
if (string.IsNullOrEmpty(CustomFormat))
{
err = "Pattern can not be empty";
}
else
{
// See whether the string validates as a Regex
try
{
_ = new Regex(CustomFormat);
// See if it contains the minimal fields
if (!ContainsKeyFields(CustomFormat))
{
err = "The pattern does not define any of the core fields, Description, Type or "
+ "DateTime. At least one of these should be defined.";
}
}
catch (ArgumentException)
{
err = "The custom pattern does not equate to a valid regular expression";
}
}
Error = err;
return err;
}
return null;
}
}
public object Save(object saveData)
{
// Todo: Implement page save....
return saveData;
//// Debug.Assert(settings != null,
//// "Settings not set, did the previous page not provide this? " +
//// "Was SuggestPreviousPage not called by the caller of this class?");
//// settings.MessageDecoder = CustomFormat;
//// return settings;
}
public void AddChild(IWizardPage newItem)
{
children.Add(newItem);
OnPropertyChanged(nameof(Children));
}
public void RemoveChild(IWizardPage item)
{
children.Remove(item);
OnPropertyChanged(nameof(Children));
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private static bool ContainsKeyFields(string pattern)
{
string p = pattern.ToLower();
if (p.Contains("(?") || p.Contains("(?") || p.Contains("(?"))
{
return true;
}
return false;
}
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CustomFormat")
{
IsValid = this["CustomFormat"] == null;
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Set the custom format after the constructor to force the validation
// to show immediately, this will retrigger the validation.
OnPropertyChanged(nameof(CustomFormat));
}
}
}
================================================
FILE: Sentinel/FileMonitor/FileMonitorProviderPage.xaml
================================================
================================================
FILE: Sentinel/FileMonitor/FileMonitorProviderPage.xaml.cs
================================================
namespace Sentinel.FileMonitor
{
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Win32;
using Sentinel.Interfaces.Providers;
using WpfExtras;
///
/// Interaction logic for FileMonitorProviderPage.xaml.
///
public partial class FileMonitorProviderPage : IWizardPage, IDataErrorInfo
{
private readonly ObservableCollection children = new ObservableCollection();
private readonly ReadOnlyObservableCollection readonlyChildren;
private string fileName = string.Empty;
private bool loadExisting;
private double refresh;
private bool warnFileNotFound;
private bool isValid;
public FileMonitorProviderPage()
{
InitializeComponent();
DataContext = this;
readonlyChildren = new ReadOnlyObservableCollection(children);
PropertyChanged += PropertyChangedHandler;
Browse = new DelegateCommand(BrowseForFile);
// Need a subsequent page to define message format.
AddChild(new MessageFormatPage());
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand Browse { get; private set; }
public string FileName
{
get => fileName;
set
{
if (fileName != value)
{
fileName = value;
OnPropertyChanged(nameof(FileName));
}
}
}
public bool WarnFileNotFound
{
get => warnFileNotFound;
set
{
if (warnFileNotFound != value)
{
Trace.WriteLine("Setting WarnFileNotFound to " + value);
warnFileNotFound = value;
OnPropertyChanged(nameof(WarnFileNotFound));
}
}
}
public double Refresh
{
get => refresh;
set
{
if (Math.Abs(refresh - value) > 0.01)
{
refresh = value;
OnPropertyChanged(nameof(Refresh));
}
}
}
public int MaxRefresh => 5000;
public int MinRefresh => 50;
public bool LoadExisting
{
get => loadExisting;
set
{
if (loadExisting != value)
{
loadExisting = value;
OnPropertyChanged(nameof(LoadExisting));
}
}
}
public string Title => "Log file monitoring provider";
public ReadOnlyObservableCollection Children => readonlyChildren;
public Control PageContent => this;
public string Description => "Configure Sentinel to monitor a file for new entries";
public bool IsValid
{
get => isValid;
private set
{
if (isValid != value)
{
isValid = value;
OnPropertyChanged(nameof(IsValid));
}
}
}
///
/// Gets an error message indicating what is wrong with this object.
///
///
/// An error message indicating what is wrong with this object. The default is an empty string ("").
///
public string Error => this["FileName"];
///
/// Gets the error message for the property with the given name.
///
/// The name of the property whose error message to get.
/// The error message for the property. The default is an empty string ("").
public string this[string columnName]
{
get
{
if (columnName != "FileName")
{
return null;
}
if (string.IsNullOrWhiteSpace(FileName))
{
return "File name not specified";
}
string reason;
return !CheckSuppliedFilenameIsValid(FileName, out reason) ? reason : null;
}
}
public object Save(object saveData)
{
Debug.Assert(saveData != null, "Expecting a non-null instance of a class to save settings into");
Debug.Assert(saveData is IProviderSettings, "Expecting save object to be an IProviderSettings");
var settings = saveData as IProviderSettings;
if (settings != null)
{
if (settings is IFileMonitoringProviderSettings fileSettings)
{
fileSettings.Update(fileName, (int)Refresh, LoadExisting);
return fileSettings;
}
return new FileMonitoringProviderSettings(
settings.Info,
settings.Name,
fileName,
(int)Refresh,
LoadExisting);
}
return saveData;
}
public void AddChild(IWizardPage newItem)
{
children.Add(newItem);
OnPropertyChanged(nameof(Children));
}
public void RemoveChild(IWizardPage item)
{
children.Remove(item);
OnPropertyChanged(nameof(Children));
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private void BrowseForFile(object obj)
{
// Display the File Open Dialog.
var dlg = new OpenFileDialog
{
FileName = "logFile",
DefaultExt = ".log",
Multiselect = false,
Filter = "Log files (.log)|*.log|Text documents (.txt)|*.txt|All files|*.*",
};
var result = dlg.ShowDialog();
if (result == true)
{
FileName = dlg.FileName;
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Refresh = 250;
}
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "FileName")
{
return;
}
try
{
var fi = new FileInfo(FileName);
WarnFileNotFound = !fi.Exists;
IsValid = this["FileName"] == null;
}
catch (Exception)
{
// For exceptions, let the validation handler show the error.
WarnFileNotFound = false;
IsValid = false;
}
}
private bool CheckSuppliedFilenameIsValid(string fileNameToValidate, out string reason)
{
try
{
reason = null;
_ = new FileInfo(fileNameToValidate);
return true;
}
catch (UnauthorizedAccessException)
{
reason = "The file name specified is in a location unauthorised";
}
catch (NotSupportedException)
{
reason = "The file name specified is not valid for a file.";
}
catch (ArgumentException)
{
reason = "The file name specified is not valid for a file.";
}
catch (PathTooLongException)
{
reason = "The file name specified is too long to be a valid file.";
}
catch (SecurityException)
{
reason = "You do not have permission to work with that file/location.";
}
return false;
}
}
}
================================================
FILE: Sentinel/FileMonitor/FileMonitoringProvider.cs
================================================
namespace Sentinel.FileMonitor
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using log4net;
using Sentinel.Interfaces;
using Sentinel.Interfaces.CodeContracts;
using Sentinel.Interfaces.Providers;
public class FileMonitoringProvider : ILogProvider, IDisposable
{
private const string LoggerIdentifier = "Logger";
private static readonly ILog Log = LogManager.GetLogger(nameof(FileMonitoringProvider));
private readonly bool loadExistingContent;
private readonly Regex patternMatching;
private readonly Queue pendingQueue = new Queue();
private readonly int refreshInterval = 250;
private readonly List usedGroupNames = new List();
private long bytesRead;
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Reliability",
"CA2000: DisposeObjectsBeforeLosingScope",
Justification = "Both Worker and PurgeWorker are disposed in the IDispose implementation (or finalizer)")]
public FileMonitoringProvider(IProviderSettings settings)
{
settings.ThrowIfNull(nameof(settings));
var fileSettings = settings as IFileMonitoringProviderSettings;
var message = "The FileMonitoringProvider class expects configuration information "
+ "to be of IFileMonitoringProviderSettings type";
Debug.Assert(fileSettings != null, message);
ProviderSettings = fileSettings;
FileName = fileSettings.FileName;
Information = settings.Info;
refreshInterval = fileSettings.RefreshPeriod;
loadExistingContent = fileSettings.LoadExistingContent;
patternMatching = new Regex(fileSettings.MessageDecoder, RegexOptions.Singleline | RegexOptions.Compiled);
PredetermineGroupNames(fileSettings.MessageDecoder);
// Chain up callbacks to the workers.
Worker.DoWork += DoWork;
Worker.WorkerSupportsCancellation = true;
Worker.RunWorkerCompleted += DoWorkComplete;
PurgeWorker.DoWork += PurgeWorkerDoWork;
PurgeWorker.WorkerSupportsCancellation = true;
}
~FileMonitoringProvider()
{
Dispose(false);
}
public static IProviderRegistrationRecord ProviderRegistrationInformation { get; } =
new ProviderRegistrationInformation(new ProviderInfo());
public string FileName { get; }
public IProviderInfo Information { get; }
public IProviderSettings ProviderSettings { get; }
public ILogger Logger { get; set; }
public string Name { get; set; }
public bool IsActive => Worker.IsBusy;
private BackgroundWorker Worker { get; set; } = new BackgroundWorker();
private BackgroundWorker PurgeWorker { get; set; } = new BackgroundWorker { WorkerReportsProgress = true };
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Start()
{
Debug.Assert(!string.IsNullOrEmpty(FileName), "Filename not specified");
Debug.Assert(Logger != null, "No logger has been registered, this is required before starting a provider");
lock (pendingQueue)
{
Log.DebugFormat(CultureInfo.InvariantCulture, "Starting of file-monitor upon {0}", FileName);
}
Worker.RunWorkerAsync();
PurgeWorker.RunWorkerAsync();
}
public void Close()
{
Worker.CancelAsync();
PurgeWorker.CancelAsync();
}
public void Pause()
{
if (Worker != null)
{
// TODO: need a better pause mechanism...
Close();
}
}
protected virtual void Dispose(bool disposing)
{
Worker?.Dispose();
Worker = null;
PurgeWorker?.Dispose();
PurgeWorker = null;
}
private void PurgeWorkerDoWork(object sender, DoWorkEventArgs e)
{
while (!e.Cancel)
{
// Go to sleep.
Thread.Sleep(refreshInterval);
lock (pendingQueue)
{
if (pendingQueue.Any())
{
Log.DebugFormat(CultureInfo.InvariantCulture, "Adding a batch of {0} entries to the logger", pendingQueue.Count());
Logger.AddBatch(pendingQueue);
Trace.WriteLine("Done adding the batch");
}
}
}
}
private void PredetermineGroupNames(string messageDecoder)
{
var decoder = messageDecoder.ToUpperInvariant();
if (decoder.Contains("(?"))
{
usedGroupNames.Add("Description");
}
if (decoder.Contains("(?"))
{
usedGroupNames.Add("DateTime");
}
if (decoder.Contains("(?"))
{
usedGroupNames.Add("Type");
}
if (decoder.Contains("(?"))
{
usedGroupNames.Add(LoggerIdentifier);
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// Read existing content.
var fi = new FileInfo(FileName);
// Keep hold of incomplete lines, if any.
var incomplete = string.Empty;
var sb = new StringBuilder();
if (!loadExistingContent)
{
bytesRead = fi.Length;
}
while (!e.Cancel)
{
fi.Refresh();
if (fi.Exists)
{
fi.Refresh();
var length = fi.Length;
if (length > bytesRead)
{
try
{
using (var fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Write))
{
var position = fs.Seek(bytesRead, SeekOrigin.Begin);
Debug.Assert(position == bytesRead, "Seek did not go to where we asked.");
// Calculate length of file.
var bytesToRead = length - position;
Debug.Assert(bytesToRead < int.MaxValue, "Too much data to read using this method!");
var buffer = new byte[bytesToRead];
var bytesSuccessfullyRead = fs.Read(buffer, 0, (int)bytesToRead);
Debug.Assert(bytesSuccessfullyRead == bytesToRead, "Did not get as much as expected!");
// Put results into a buffer (prepend any unprocessed data retained from last read).
sb.Length = 0;
sb.Append(incomplete);
sb.Append(Encoding.ASCII.GetString(buffer, 0, bytesSuccessfullyRead));
using (var sr = new StringReader(sb.ToString()))
{
while (sr.Peek() != -1)
{
var line = sr.ReadLine();
// Trace.WriteLine("Read: " + line);
DecodeAndQueueMessage(line);
}
}
// Can we determine whether any tailing data was unprocessed?
bytesRead = position + bytesSuccessfullyRead;
}
}
catch (Exception ex)
{
MessageBox.Show(
$"Error in FileMonitorProvider: {ex}",
"Error in FileMonitorProvider",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
}
Thread.Sleep(refreshInterval);
}
}
private void DecodeAndQueueMessage(string message)
{
Debug.Assert(patternMatching != null, "Regular expression has not be set");
var m = patternMatching.Match(message);
if (!m.Success)
{
Trace.WriteLine("Message decoding did not work!");
return;
}
lock (pendingQueue)
{
var entry = new LogEntry();
if (usedGroupNames.Contains("Description"))
{
entry.Description = m.Groups["Description"].Value;
}
if (usedGroupNames.Contains("DateTime"))
{
DateTime dt;
if (!DateTime.TryParse(m.Groups["DateTime"].Value, out dt))
{
Log.DebugFormat(
CultureInfo.InvariantCulture,
"Failed to parse date {0}",
m.Groups["DateTime"].Value);
}
entry.DateTime = dt;
}
if (usedGroupNames.Contains("Type"))
{
entry.Type = m.Groups["Type"].Value;
}
if (usedGroupNames.Contains(LoggerIdentifier))
{
entry.Source = m.Groups[LoggerIdentifier].Value;
entry.System = m.Groups[LoggerIdentifier].Value;
}
entry.MetaData = new Dictionary
{
{ "Classification", string.Empty }, { "Host", FileName },
};
if (entry.Description.ToUpper(CultureInfo.InvariantCulture).Contains("EXCEPTION"))
{
entry.MetaData.Add("Exception", true);
}
pendingQueue.Enqueue(entry);
}
}
private void DoWorkComplete(object sender, RunWorkerCompletedEventArgs e)
{
Worker.CancelAsync();
}
private class ProviderInfo : IProviderInfo
{
public Guid Identifier => new Guid("1a2f8249-b390-4baa-ba5e-3d67804ba1ed");
public string Name => "File Monitoring Provider";
public string Description => "Monitor a text file for new log entries.";
}
}
}
================================================
FILE: Sentinel/FileMonitor/FileMonitoringProviderSettings.cs
================================================
namespace Sentinel.FileMonitor
{
using System.Globalization;
using Sentinel.Interfaces.Providers;
public class FileMonitoringProviderSettings : IFileMonitoringProviderSettings
{
public FileMonitoringProviderSettings(
IProviderInfo info,
string providerName,
string fileName,
int refreshPeriod,
bool loadExistingContent)
{
Info = info;
Name = providerName;
FileName = fileName;
RefreshPeriod = refreshPeriod;
LoadExistingContent = loadExistingContent;
}
public string FileName { get; private set; }
///
/// Reference back to the provider this setting is appropriate to.
///
public IProviderInfo Info { get; private set; }
public bool LoadExistingContent { get; private set; }
public string MessageDecoder { get; set; }
public string Name { get; private set; }
public int RefreshPeriod { get; private set; }
public string Summary
=> string.Format(CultureInfo.InvariantCulture, "Monitor the file {0} for new log entries", FileName);
public void Update(string fileName, int refreshPeriod, bool loadExistingContent)
{
FileName = fileName;
RefreshPeriod = refreshPeriod;
LoadExistingContent = loadExistingContent;
}
}
}
================================================
FILE: Sentinel/FileMonitor/IFileMonitoringProviderSettings.cs
================================================
namespace Sentinel.FileMonitor
{
using Sentinel.Interfaces.Providers;
public interface IFileMonitoringProviderSettings : IProviderSettings
{
string FileName { get; }
int RefreshPeriod { get; }
bool LoadExistingContent { get; }
string MessageDecoder { get; set; }
void Update(string fileName, int refresh, bool loadExisting);
}
}
================================================
FILE: Sentinel/FileMonitor/LogEntry.cs
================================================
namespace Sentinel.FileMonitor
{
using System;
using System.Collections.Generic;
using Sentinel.Interfaces;
internal class LogEntry : ILogEntry
{
///
/// Classification for the log entry. Can be free-text but will typically
/// contain values like "DEBUG" or "ERROR".
///
public string Type { get; set; }
///
/// Date/Time for the original log entry.
///
public DateTime DateTime { get; set; }
///
/// The main body of the log entry.
///
public string Description { get; set; }
///
/// Source of the log entry, e.g. where it came from.
///
public string Source { get; set; }
///
/// The system (e.g. machine) where this message came from.
///
public string System { get; set; }
///
/// Thread identifier for the source of the message.
///
public string Thread { get; set; }
///
/// Dictionary of any meta-data that doesn't fit into the above values.
///
public Dictionary MetaData { get; set; }
}
}
================================================
FILE: Sentinel/FileMonitor/MessageFormatPage.xaml
================================================
================================================
FILE: Sentinel/FileMonitor/MessageFormatPage.xaml.cs
================================================
namespace Sentinel.FileMonitor
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using WpfExtras;
///
/// Interaction logic for MessageFormatPage.xaml.
///
public partial class MessageFormatPage : IWizardPage
{
private readonly ObservableCollection children = new ObservableCollection();
private readonly ReadOnlyObservableCollection readonlyChildren;
private bool showCustomWarning = false;
private int selectedDecoderIndex;
private IWizardPage customPage = null;
public MessageFormatPage()
{
InitializeComponent();
DataContext = this;
readonlyChildren = new ReadOnlyObservableCollection(children);
PropertyChanged += PropertyChangedHandler;
DecodingStyles = new List
{
"nLog default message format decoder",
"Custom",
};
}
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable DecodingStyles { get; private set; }
public int SelectedDecoderIndex
{
get => selectedDecoderIndex;
set
{
if (selectedDecoderIndex != value)
{
selectedDecoderIndex = value;
OnPropertyChanged(nameof(SelectedDecoderIndex));
}
}
}
public bool ShowCustomWarning
{
get => showCustomWarning;
private set
{
if (showCustomWarning != value)
{
showCustomWarning = value;
OnPropertyChanged(nameof(ShowCustomWarning));
}
}
}
public string Title => "Message Part Identification";
public ReadOnlyObservableCollection Children => readonlyChildren;
public string Description => "Define how the entries in the log file are categorised.";
public bool IsValid => true;
public Control PageContent => this;
protected bool IsCustom => SelectedDecoderIndex == DecodingStyles.Count() - 1;
public void AddChild(IWizardPage newItem)
{
children.Add(newItem);
OnPropertyChanged(nameof(Children));
}
public void RemoveChild(IWizardPage item)
{
children.Remove(item);
OnPropertyChanged(nameof(Children));
}
public object Save(object saveData)
{
Debug.Assert(saveData != null, "Expecting a valid save-data instance");
Debug.Assert(saveData is IFileMonitoringProviderSettings, "Should be an IFileMonitoringProviderSettings");
if (saveData is IFileMonitoringProviderSettings settings)
{
if (!IsCustom)
{
settings.MessageDecoder = GetDecoder();
}
}
return saveData;
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
var e = new PropertyChangedEventArgs(propertyName);
handler?.Invoke(this, e);
}
private string GetDecoder()
{
switch (SelectedDecoderIndex)
{
case 0:
return "^(?[^|]+)\\|(?[^|]+)\\|(?[^|]+)\\|(?[^$]*)$";
default:
throw new NotSupportedException("Custom message formats are not handled on this page.");
}
}
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedDecoderIndex")
{
if (IsCustom)
{
if (customPage == null)
{
customPage = new CustomMessageDecoderPage();
}
if (!Children.Contains(customPage))
{
AddChild(customPage);
}
ShowCustomWarning = true;
}
else
{
ShowCustomWarning = false;
if (Children.Any())
{
children.Clear();
OnPropertyChanged(nameof(Children));
}
}
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Trigger the validation on these fields (work around a WPF 3.x issue).
OnPropertyChanged(nameof(SelectedDecoderIndex));
}
}
}
================================================
FILE: Sentinel/FileMonitor/ProviderRegistrationInformation.cs
================================================
namespace Sentinel.FileMonitor
{
using System;
using Sentinel.Interfaces.Providers;
public class ProviderRegistrationInformation : IProviderRegistrationRecord
{
public ProviderRegistrationInformation(IProviderInfo providerInfo)
{
Info = providerInfo;
}
public Guid Identifier
{
get
{
return Info.Identifier;
}
}
public IProviderInfo Info { get; private set; }
public Type Settings
{
get
{
return typeof(FileMonitorProviderPage);
}
}
public Type Implementer
{
get
{
return typeof(FileMonitoringProvider);
}
}
}
}
================================================
FILE: Sentinel/Filters/Filter.cs
================================================
namespace Sentinel.Filters
{
using System;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Sentinel.Filters.Interfaces;
using Sentinel.Interfaces;
using WpfExtras;
[DataContract]
public class Filter : ViewModelBase, IFilter
{
///
/// Is the filter enabled? If so, it will remove anything matching from the output.
///
private bool enabled;
private string name;
private string pattern;
private LogEntryFields field;
private MatchMode mode = MatchMode.Exact;
private Regex regex;
public Filter()
{
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) || e.PropertyName == nameof(Pattern))
{
if (Mode == MatchMode.RegularExpression && Pattern != null)
{
regex = new Regex(Pattern);
}
OnPropertyChanged(nameof(Description));
}
};
}
public Filter(string name, LogEntryFields field, string pattern)
{
Name = name;
Pattern = pattern;
Field = field;
regex = new Regex(pattern);
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) || e.PropertyName == nameof(Pattern))
{
if (Mode == MatchMode.RegularExpression && Pattern != null)
{
regex = new Regex(Pattern);
}
OnPropertyChanged(nameof(Description));
}
};
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
///
/// Gets or sets a value indicating whether the filter is enabled.
///
public bool Enabled
{
get
{
return enabled;
}
set
{
if (value != enabled)
{
enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (pattern != value)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public LogEntryFields Field
{
get
{
return field;
}
set
{
if (field != value)
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
}
public MatchMode Mode
{
get
{
return mode;
}
set
{
if (mode != value)
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
}
public string Description
{
get
{
var modeDescription = "Exact";
switch (Mode)
{
case MatchMode.RegularExpression:
modeDescription = "RegEx";
break;
case MatchMode.CaseSensitive:
modeDescription = "Case sensitive";
break;
case MatchMode.CaseInsensitive:
modeDescription = "Case insensitive";
break;
}
return $"{modeDescription} match of {Pattern} in the {Field} field";
}
}
public bool IsMatch(ILogEntry logEntry)
{
logEntry = logEntry ?? throw new ArgumentNullException(nameof(logEntry));
if (string.IsNullOrWhiteSpace(Pattern))
{
return false;
}
string target;
switch (Field)
{
case LogEntryFields.None:
target = string.Empty;
break;
case LogEntryFields.Type:
target = logEntry.Type;
break;
case LogEntryFields.System:
target = logEntry.System;
break;
case LogEntryFields.Classification:
target = string.Empty;
break;
case LogEntryFields.Thread:
target = logEntry.Thread;
break;
case LogEntryFields.Source:
target = logEntry.Source;
break;
case LogEntryFields.Description:
target = logEntry.Description;
break;
case LogEntryFields.Host:
target = string.Empty;
break;
default:
target = string.Empty;
break;
}
switch (Mode)
{
case MatchMode.Exact:
return target.Equals(Pattern);
case MatchMode.CaseSensitive:
return target.Contains(Pattern);
case MatchMode.CaseInsensitive:
return target.ToLower().Contains(Pattern.ToLower());
case MatchMode.RegularExpression:
return regex != null && regex.IsMatch(target);
default:
return false;
}
}
#if DEBUG
public override string ToString()
{
return Description;
}
#endif
}
}
================================================
FILE: Sentinel/Filters/FilteringService.cs
================================================
namespace Sentinel.Filters
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows.Input;
using Sentinel.Filters.Gui;
using Sentinel.Filters.Interfaces;
using Sentinel.Interfaces;
using Sentinel.Services;
using WpfExtras;
[DataContract]
public class FilteringService : ViewModelBase, IFilteringService, IDefaultInitialisation
where T : class, IFilter
{
private readonly CollectionChangeHelper collectionHelper = new CollectionChangeHelper();
private readonly IAddFilterService addFilterService = new AddFilter();
private readonly IEditFilterService editFilterService = new EditFilter();
private readonly IRemoveFilterService removeFilterService = new RemoveFilter();
private int selectedIndex = -1;
public FilteringService()
{
Add = new DelegateCommand(AddFilter);
Edit = new DelegateCommand(EditFilter, e => selectedIndex != -1);
Remove = new DelegateCommand(RemoveFilter, e => selectedIndex != -1);
Filters = new ObservableCollection();
SearchFilters = new ObservableCollection();
// Register self as an observer of the collection.
collectionHelper.OnPropertyChanged += CustomFilterPropertyChanged;
collectionHelper.ManagerName = "FilteringService";
collectionHelper.NameLookup += e => e.Name;
Filters.CollectionChanged += collectionHelper.AttachDetach;
SearchFilters.CollectionChanged += collectionHelper.AttachDetach;
var searchFilter = ServiceLocator.Instance.Get();
if (searchFilter != null)
{
SearchFilters.Add(searchFilter as T);
}
else
{
Debug.Fail("The search filter is null.");
}
}
public ICommand Add { get; private set; }
public ICommand Edit { get; private set; }
public ICommand Remove { get; private set; }
public int SelectedIndex
{
get
{
return selectedIndex;
}
set
{
if (value != selectedIndex)
{
selectedIndex = value;
OnPropertyChanged(nameof(SelectedIndex));
}
}
}
public ObservableCollection Filters { get; set; }
public ObservableCollection SearchFilters { get; set; }
public void Initialise()
{
// Add the standard debugging filters
Filters.Add(new StandardFilter("Trace", LogEntryFields.Type, "TRACE") as T);
Filters.Add(new StandardFilter("Debug", LogEntryFields.Type, "DEBUG") as T);
Filters.Add(new StandardFilter("Info", LogEntryFields.Type, "INFO") as T);
Filters.Add(new StandardFilter("Warn", LogEntryFields.Type, "WARN") as T);
Filters.Add(new StandardFilter("Error", LogEntryFields.Type, "ERROR") as T);
Filters.Add(new StandardFilter("Fatal", LogEntryFields.Type, "FATAL") as T);
}
public bool IsFiltered(ILogEntry entry)
{
return Filters.Any(filter => filter.Enabled && filter.IsMatch(entry)) ||
SearchFilters.Any(filter => filter.Enabled && filter.IsMatch(entry));
}
private void AddFilter(object obj)
{
addFilterService.Add();
}
private void CustomFilterPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var filter = sender as Filter;
if (filter != null)
{
Trace.WriteLine(
$"FilteringService saw some activity on {filter.Name} (IsEnabled = {filter.Enabled})");
}
OnPropertyChanged(string.Empty);
}
private void EditFilter(object obj)
{
var filter = Filters.ElementAt(SelectedIndex);
if (filter != null)
{
editFilterService.Edit(filter);
}
}
private void RemoveFilter(object obj)
{
var filter = Filters.ElementAt(SelectedIndex);
removeFilterService.Remove(filter);
}
}
}
================================================
FILE: Sentinel/Filters/Gui/AddEditFilter.cs
================================================
namespace Sentinel.Filters.Gui
{
using System.Windows;
using System.Windows.Input;
using Sentinel.Interfaces;
using WpfExtras;
public class AddEditFilter : ViewModelBase
{
private readonly Window window;
private string name = "Unnamed";
private string pattern = "pattern";
private LogEntryFields field;
private MatchMode mode;
public AddEditFilter(Window window, bool editMode)
{
this.window = window;
if (window != null)
{
window.Title = $"{(editMode ? "Edit" : "Register")} Filter";
}
Accept = new DelegateCommand(AcceptDialog, Validates);
Reject = new DelegateCommand(RejectDialog);
}
public ICommand Accept { get; private set; }
public LogEntryFields Field
{
get
{
return field;
}
set
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
public MatchMode Mode
{
get
{
return mode;
}
set
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (value != pattern)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public ICommand Reject { get; private set; }
private void AcceptDialog(object obj)
{
window.DialogResult = true;
window.Close();
}
private void RejectDialog(object obj)
{
window.DialogResult = false;
window.Close();
}
private bool Validates(object obj)
{
return Name.Length > 0
&& Pattern.Length > 0;
}
}
}
================================================
FILE: Sentinel/Filters/Gui/AddEditFilterWindow.xaml
================================================
================================================
FILE: Sentinel/Filters/Gui/AddEditFilterWindow.xaml.cs
================================================
namespace Sentinel.Filters.Gui
{
using System.Windows;
///
/// Interaction logic for AddEditFilterWindow.xaml.
///
public partial class AddEditFilterWindow : Window
{
public AddEditFilterWindow()
{
InitializeComponent();
}
}
}
================================================
FILE: Sentinel/Filters/Gui/AddFilter.cs
================================================
namespace Sentinel.Filters.Gui
{
using System.Windows;
using Sentinel.Filters.Interfaces;
using Sentinel.Services;
public class AddFilter : IAddFilterService
{
public void Add()
{
var filterWindow = new AddEditFilterWindow();
using (var data = new AddEditFilter(filterWindow, false))
{
filterWindow.DataContext = data;
filterWindow.Owner = Application.Current.MainWindow;
var dialogResult = filterWindow.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
var filter = Construct(data);
if (filter != null)
{
var service = ServiceLocator.Instance.Get>();
service?.Filters.Add(filter);
}
}
}
}
private static Filter Construct(AddEditFilter data)
{
return new Filter
{
Name = data.Name,
Field = data.Field,
Mode = data.Mode,
Pattern = data.Pattern,
Enabled = true,
};
}
}
}
================================================
FILE: Sentinel/Filters/Gui/EditFilter.cs
================================================
namespace Sentinel.Filters.Gui
{
using System.Diagnostics;
using System.Windows;
using Sentinel.Filters.Interfaces;
public class EditFilter : IEditFilterService
{
public void Edit(IFilter filter)
{
Debug.Assert(filter != null, "Filter must be supplied to allow editing.");
var window = new AddEditFilterWindow();
var data = new AddEditFilter(window, true);
window.DataContext = data;
window.Owner = Application.Current.MainWindow;
data.Name = filter.Name;
data.Field = filter.Field;
data.Pattern = filter.Pattern;
data.Mode = filter.Mode;
var dialogResult = window.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
filter.Name = data.Name;
filter.Pattern = data.Pattern;
filter.Mode = data.Mode;
filter.Field = data.Field;
}
}
}
}
================================================
FILE: Sentinel/Filters/Gui/FiltersControl.xaml
================================================
================================================
FILE: Sentinel/Filters/Gui/FiltersControl.xaml.cs
================================================
namespace Sentinel.Filters.Gui
{
using System.Windows.Controls;
using Sentinel.Filters.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for FiltersControl.xaml.
///
public partial class FiltersControl : UserControl
{
public FiltersControl()
{
InitializeComponent();
var service = ServiceLocator.Instance.Get>();
if (service != null)
{
Filters = service;
}
DataContext = this;
}
public IFilteringService Filters { get; private set; }
}
}
================================================
FILE: Sentinel/Filters/Gui/RemoveFilter.cs
================================================
namespace Sentinel.Filters.Gui
{
using System.Windows;
using Sentinel.Filters.Interfaces;
using Sentinel.Services;
public class RemoveFilter : IRemoveFilterService
{
public void Remove(IFilter filter)
{
var service = ServiceLocator.Instance.Get>();
if (service != null)
{
var prompt = "Are you sure you want to remove the selected filter?\r\n\r\n" +
$"Filter Name = \"{filter.Name}\"";
var result = MessageBox.Show(
prompt,
"Remove Filter",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No);
if (result == MessageBoxResult.Yes)
{
service.Filters.Remove(filter);
}
}
}
}
}
================================================
FILE: Sentinel/Filters/Interfaces/IAddFilterService.cs
================================================
namespace Sentinel.Filters.Interfaces
{
public interface IAddFilterService
{
void Add();
}
}
================================================
FILE: Sentinel/Filters/Interfaces/IEditFilterService.cs
================================================
namespace Sentinel.Filters.Interfaces
{
public interface IEditFilterService
{
void Edit(IFilter filter);
}
}
================================================
FILE: Sentinel/Filters/Interfaces/IFilter.cs
================================================
namespace Sentinel.Filters.Interfaces
{
using System.Runtime.Serialization;
using Sentinel.Interfaces;
public interface IFilter
{
[DataMember]
string Name { get; set; }
///
/// Gets or sets a value indicating whether the filter is enabled.
///
[DataMember]
bool Enabled { get; set; }
[DataMember]
string Pattern { get; set; }
[DataMember]
string Description { get; }
[DataMember]
LogEntryFields Field { get; set; }
[DataMember]
MatchMode Mode { get; set; }
bool IsMatch(ILogEntry entry);
}
}
================================================
FILE: Sentinel/Filters/Interfaces/IFilteringService.cs
================================================
namespace Sentinel.Filters.Interfaces
{
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using Sentinel.Interfaces;
public interface IFilteringService
{
[DataMember]
ObservableCollection Filters { get; set; }
ObservableCollection SearchFilters { get; set; }
bool IsFiltered(ILogEntry entry);
}
}
================================================
FILE: Sentinel/Filters/Interfaces/IRemoveFilterService.cs
================================================
namespace Sentinel.Filters.Interfaces
{
public interface IRemoveFilterService
{
void Remove(IFilter filter);
}
}
================================================
FILE: Sentinel/Filters/Interfaces/ISearchFilter.cs
================================================
namespace Sentinel.Filters.Interfaces
{
public interface ISearchFilter : IFilter
{
}
}
================================================
FILE: Sentinel/Filters/Interfaces/IStandardDebuggingFilter.cs
================================================
namespace Sentinel.Filters.Interfaces
{
public interface IStandardDebuggingFilter : IFilter
{
}
}
================================================
FILE: Sentinel/Filters/SearchFilter.cs
================================================
namespace Sentinel.Filters
{
using System.Runtime.Serialization;
using Sentinel.Filters.Interfaces;
using Sentinel.Interfaces;
[DataContract]
public class SearchFilter : Filter, IDefaultInitialisation, ISearchFilter
{
public void Initialise()
{
Name = "SearchFilter";
Field = LogEntryFields.System;
Pattern = string.Empty;
}
}
}
================================================
FILE: Sentinel/Filters/StandardFilter.cs
================================================
namespace Sentinel.Filters
{
using Sentinel.Filters.Interfaces;
using Sentinel.Interfaces;
public class StandardFilter : Filter, IStandardDebuggingFilter
{
public StandardFilter(string name, LogEntryFields field, string pattern)
: base(name, field, pattern)
{
}
}
}
================================================
FILE: Sentinel/Highlighters/Gui/AddEditHighlighter.cs
================================================
namespace Sentinel.Highlighters.Gui
{
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Sentinel.Interfaces;
using Sentinel.Interfaces.CodeContracts;
using WpfExtras;
public class AddEditHighlighter : ViewModelBase
{
private readonly Window window;
private readonly Dictionary colours = GetColours();
private int backgroundColourIndex = 1;
private bool coloursAreClose;
private int foregroundColourIndex;
private bool overrideBackgroundColour = false;
private bool overrideForegroundColour = false;
private string name = "Untitled";
private string pattern = "pattern";
private LogEntryFields field;
private MatchMode mode;
public AddEditHighlighter(Window window, bool editMode)
{
this.window = window;
if (window != null)
{
window.Title = editMode ? "Edit Highlighter" : "Add Highlighter";
}
PropertyChanged += CloseColourCheck;
Accept = new DelegateCommand(AcceptDialog, Validates);
Reject = new DelegateCommand(RejectDialog);
}
public ICommand Accept { get; private set; }
public Color BackgroundColour
{
get
{
string key = colours.Keys.OrderBy(e => e).ToList()[backgroundColourIndex];
return colours[key];
}
set
{
var find = colours.FirstOrDefault(r => r.Value == value);
find.Key.ThrowIfNull(nameof(find.Key));
var index = colours.Keys.OrderBy(n => n).ToList().IndexOf(find.Key);
BackgroundColourIndex = index;
}
}
public int BackgroundColourIndex
{
get
{
return backgroundColourIndex;
}
set
{
if (value != backgroundColourIndex)
{
backgroundColourIndex = value;
OnPropertyChanged(nameof(BackgroundColourIndex));
}
}
}
public IEnumerable BackgroundColours => colours.Keys;
public bool ColoursClose
{
get
{
return coloursAreClose;
}
private set
{
if (coloursAreClose != value)
{
coloursAreClose = value;
OnPropertyChanged(nameof(ColoursClose));
}
}
}
public Color ForegroundColour
{
get
{
string key = colours.Keys.OrderBy(e => e).ToList()[foregroundColourIndex];
return colours[key];
}
set
{
var find = colours.FirstOrDefault(r => r.Value == value);
find.Key.ThrowIfNull(nameof(find.Key));
var index = colours.Keys.OrderBy(n => n).ToList().IndexOf(find.Key);
ForegroundColourIndex = index;
}
}
public int ForegroundColourIndex
{
get
{
return foregroundColourIndex;
}
set
{
if (value != foregroundColourIndex)
{
foregroundColourIndex = value;
OnPropertyChanged(nameof(ForegroundColourIndex));
}
}
}
public IEnumerable ForegroundColours => colours.Keys;
public LogEntryFields Field
{
get
{
return field;
}
set
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
public MatchMode Mode
{
get
{
return mode;
}
set
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public bool OverrideBackgroundColour
{
get
{
return overrideBackgroundColour;
}
set
{
if (value != overrideBackgroundColour)
{
overrideBackgroundColour = value;
OnPropertyChanged(nameof(OverrideBackgroundColour));
}
}
}
public bool OverrideForegroundColour
{
get
{
return overrideForegroundColour;
}
set
{
if (value != overrideForegroundColour)
{
overrideForegroundColour = value;
OnPropertyChanged(nameof(OverrideForegroundColour));
}
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (value != pattern)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public ICommand Reject { get; private set; }
private static Dictionary GetColours()
{
var colours = new Dictionary();
foreach (var propertyInfo in typeof(Colors).GetProperties())
{
var colour = ColorConverter.ConvertFromString(propertyInfo.Name);
if (colour != null)
{
colours.Add(propertyInfo.Name, (Color)colour);
}
}
return colours;
}
private void AcceptDialog(object obj)
{
window.DialogResult = true;
window.Close();
}
private void CloseColourCheck(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "OverrideForegroundColour":
case "OverrideBackgroundColour":
case "BackgroundColourIndex":
case "ForegroundColourIndex":
ColoursClose = OverrideBackgroundColour && OverrideForegroundColour && Color.AreClose(ForegroundColour, BackgroundColour);
break;
}
}
private void RejectDialog(object obj)
{
window.DialogResult = false;
window.Close();
}
private bool Validates(object obj)
{
return !ColoursClose
&& Name.Length > 0
&& Pattern.Length > 0;
}
}
}
================================================
FILE: Sentinel/Highlighters/Gui/AddEditHighlighterWindow.xaml
================================================
================================================
FILE: Sentinel/Highlighters/Gui/AddEditHighlighterWindow.xaml.cs
================================================
namespace Sentinel.Highlighters.Gui
{
using System.Windows;
///
/// Interaction logic for AddEditHighlighterWindow.xaml.
///
public partial class AddEditHighlighterWindow : Window
{
public AddEditHighlighterWindow()
{
InitializeComponent();
}
}
}
================================================
FILE: Sentinel/Highlighters/Gui/AddNewHighlighterService.cs
================================================
namespace Sentinel.Highlighters.Gui
{
using System.Windows;
using System.Windows.Media;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Services;
public class AddNewHighlighterService : IAddHighlighterService
{
public void Add()
{
var window = new AddEditHighlighterWindow();
var data = new AddEditHighlighter(window, false);
window.DataContext = data;
window.Owner = Application.Current.MainWindow;
var dialogResult = window.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
var highlighter = Construct(data);
if (highlighter != null)
{
var service = ServiceLocator.Instance.Get>();
service?.Highlighters.Add(highlighter);
}
}
}
public Highlighter Construct(AddEditHighlighter data)
{
Color? background = null;
Color? foreground = null;
var highlighter = new Highlighter
{
Name = data.Name,
Field = data.Field,
Pattern = data.Pattern,
Mode = data.Mode,
Enabled = true,
};
if (data.OverrideBackgroundColour)
{
background = data.BackgroundColour;
}
if (data.OverrideForegroundColour)
{
foreground = data.ForegroundColour;
}
highlighter.Style = new HighlighterStyle { Background = background, Foreground = foreground };
return highlighter;
}
}
}
================================================
FILE: Sentinel/Highlighters/Gui/EditHighlighterService.cs
================================================
namespace Sentinel.Highlighters.Gui
{
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using Sentinel.Highlighters.Interfaces;
// [Export(typeof(IEditHighlighterService))]
public class EditHighlighterService : IEditHighlighterService
{
public void Edit(IHighlighter highlighter)
{
Debug.Assert(highlighter != null, "Highlighter must be supplied for editing.");
var window = new AddEditHighlighterWindow();
var data = new AddEditHighlighter(window, false);
window.DataContext = data;
window.Owner = Application.Current.MainWindow;
data.Name = highlighter.Name;
data.Pattern = highlighter.Pattern;
data.Mode = highlighter.Mode;
data.Field = highlighter.Field;
if (highlighter.Style?.Background != null)
{
data.OverrideBackgroundColour = true;
data.BackgroundColour = (Color)highlighter.Style.Background;
}
else
{
data.OverrideBackgroundColour = false;
data.BackgroundColourIndex = 1;
}
if (highlighter.Style?.Foreground != null)
{
data.OverrideForegroundColour = true;
data.ForegroundColour = (Color)highlighter.Style.Foreground;
}
else
{
data.OverrideForegroundColour = false;
data.ForegroundColourIndex = 0;
}
var dialogResult = window.ShowDialog();
if (dialogResult != null && (bool)dialogResult)
{
highlighter.Name = data.Name;
highlighter.Pattern = data.Pattern;
highlighter.Mode = data.Mode;
highlighter.Field = data.Field;
if (highlighter.Style == null && (data.OverrideBackgroundColour || data.OverrideForegroundColour))
{
highlighter.Style = new HighlighterStyle();
}
if (highlighter.Style != null)
{
highlighter.Style.Background = data.OverrideBackgroundColour
? (Color?)data.BackgroundColour
: null;
highlighter.Style.Foreground = data.OverrideForegroundColour
? (Color?)data.ForegroundColour
: null;
}
}
}
}
}
================================================
FILE: Sentinel/Highlighters/Gui/HighlightersControl.xaml
================================================
================================================
FILE: Sentinel/Highlighters/Gui/HighlightersControl.xaml.cs
================================================
namespace Sentinel.Highlighters.Gui
{
using System.Windows.Controls;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Services;
///
/// Interaction logic for HighlightersControl.xaml.
///
public partial class HighlightersControl : UserControl
{
public HighlightersControl()
{
InitializeComponent();
Highlighters = ServiceLocator.Instance.Get>();
DataContext = this;
}
public IHighlightingService Highlighters { get; private set; }
}
}
================================================
FILE: Sentinel/Highlighters/Gui/RemoveHighlighterService.cs
================================================
namespace Sentinel.Highlighters.Gui
{
using System.Windows;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Services;
public class RemoveHighlighterService : IRemoveHighlighterService
{
public void Remove(IHighlighter highlighter)
{
var service = ServiceLocator.Instance.Get>();
if (service == null)
{
return;
}
var prompt = "Are you sure you want to remove the selected highlighter?\r\n\r\n" +
$"Highlighter Name = \"{highlighter.Name}\"";
var result = MessageBox.Show(
prompt,
"Remove Highlighter",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No);
if (result == MessageBoxResult.Yes)
{
service.Highlighters.Remove(highlighter);
}
}
}
}
================================================
FILE: Sentinel/Highlighters/Highlighter.cs
================================================
namespace Sentinel.Highlighters
{
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Interfaces;
using WpfExtras;
[DataContract]
public class Highlighter : ViewModelBase, IHighlighter
{
private bool enabled = true;
private LogEntryFields field;
private MatchMode mode;
private string name;
private IHighlighterStyle style;
private string pattern;
private Regex regex;
public Highlighter()
{
Pattern = string.Empty;
Enabled = false;
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) || e.PropertyName == nameof(Pattern))
{
if (Mode == MatchMode.RegularExpression && Pattern != null)
{
regex = new Regex(Pattern);
}
OnPropertyChanged(nameof(Description));
}
};
}
protected Highlighter(string name, bool enabled, LogEntryFields field, MatchMode mode, string pattern, IHighlighterStyle style)
{
Name = name;
Enabled = enabled;
Field = field;
Mode = mode;
Pattern = pattern;
Style = style;
regex = new Regex(pattern);
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) ||
e.PropertyName == nameof(Pattern))
{
if (Mode == MatchMode.RegularExpression && Pattern != null)
{
regex = new Regex(Pattern);
}
OnPropertyChanged(nameof(Description));
}
};
}
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled != value)
{
enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
}
public LogEntryFields Field
{
get
{
return field;
}
set
{
field = value;
OnPropertyChanged(nameof(Field));
}
}
public string HighlighterType => "Basic Highlighter";
public MatchMode Mode
{
get
{
return mode;
}
set
{
if (mode != value)
{
mode = value;
OnPropertyChanged(nameof(Mode));
}
}
}
public string Description
{
get
{
string modeDescription = "Exact";
switch (Mode)
{
case MatchMode.RegularExpression:
modeDescription = "RegEx";
break;
case MatchMode.CaseSensitive:
modeDescription = "Case sensitive";
break;
case MatchMode.CaseInsensitive:
modeDescription = "Case insensitive";
break;
}
return $"{modeDescription} match of {Pattern} in the {Field} field";
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
if (pattern != value)
{
pattern = value;
OnPropertyChanged(nameof(Pattern));
}
}
}
public IHighlighterStyle Style
{
get
{
return style;
}
set
{
if (style != value)
{
style = value;
OnPropertyChanged(nameof(Style));
}
}
}
public bool IsMatch(ILogEntry logEntry)
{
Debug.Assert(logEntry != null, "logEntry can not be null.");
if (logEntry == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(Pattern))
{
return false;
}
string target;
switch (Field)
{
case LogEntryFields.Type:
target = logEntry.Type;
break;
case LogEntryFields.System:
target = logEntry.System;
break;
case LogEntryFields.Thread:
target = logEntry.Thread;
break;
case LogEntryFields.Source:
target = logEntry.Source;
break;
case LogEntryFields.Description:
target = logEntry.Description;
break;
////case LogEntryField.Classification:
////case LogEntryField.None:
////case LogEntryField.Host:
default:
target = string.Empty;
break;
}
switch (Mode)
{
case MatchMode.Exact:
return target.Equals(Pattern);
case MatchMode.CaseSensitive:
return target.Contains(Pattern);
case MatchMode.CaseInsensitive:
return target.ToLower().Contains(Pattern.ToLower());
case MatchMode.RegularExpression:
return regex != null && regex.IsMatch(target);
}
return false;
}
}
}
================================================
FILE: Sentinel/Highlighters/HighlighterConverter.cs
================================================
namespace Sentinel.Highlighters
{
using System;
using System.Globalization;
using System.Windows.Data;
using log4net;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Interfaces;
public class HighlighterConverter : IValueConverter
{
private static readonly ILog Log = LogManager.GetLogger(typeof(HighlighterConverter));
public HighlighterConverter(IHighlighter highlighter)
{
Highlighter = highlighter;
}
private IHighlighter Highlighter { get; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var match = false;
if (value != null)
{
var entry = value as ILogEntry;
if (entry == null)
{
Log.WarnFormat("Expected 'value' to be an ILogEntry but found {0}", value);
}
else
{
match = Highlighter.Enabled && Highlighter.IsMatch(entry);
}
}
return match ? "Match" : "Not Match";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
================================================
FILE: Sentinel/Highlighters/HighlighterStyle.cs
================================================
namespace Sentinel.Highlighters
{
using System.Runtime.Serialization;
using System.Windows.Media;
using Newtonsoft.Json;
using Sentinel.Interfaces;
using WpfExtras;
[DataContract]
public class HighlighterStyle : ViewModelBase, IHighlighterStyle
{
private Color? background;
private Color? foreground;
[DataMember]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Color? Background
{
get
{
return background;
}
set
{
if (value != background)
{
background = value;
OnPropertyChanged(nameof(Background));
}
}
}
[DataMember]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Color? Foreground
{
get
{
return foreground;
}
set
{
if (value != foreground)
{
foreground = value;
OnPropertyChanged(nameof(Foreground));
}
}
}
}
}
================================================
FILE: Sentinel/Highlighters/HighlightingSelector.cs
================================================
namespace Sentinel.Highlighters
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using Sentinel.Highlighters.Interfaces;
using Sentinel.Interfaces;
using Sentinel.Interfaces.CodeContracts;
using Sentinel.Services;
using Sentinel.Support.Wpf;
///
/// Style selector that provides a implements the highlighters of the QuickHighlighter
/// (fancy name for the highlighting of results for the search box) and other registered
/// highlighters. This class gets disposed of and rebuilt from scratch when the constituent
/// highlighters change their status.
///
public class HighlightingSelector : StyleSelector
{
private readonly Dictionary styles = new Dictionary();
///
/// Initializes a new instance of the class.
///
/// Action to perform on double click.
public HighlightingSelector(Action