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 ================================================ Colour Colours Initialise Initialisation Appender Ms Gui Mvvm ================================================ 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 ================================================ # ![](docs/Debug.png) sentinel Log-viewer with filtering and highlighting ![](docs/Home_sentinel-0.11.0.0.png) ## 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. ![Preferences - Classifiers](docs/Home_Preferences-Classifiers.png) ## 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. ![Preferences - Highlighters](docs/Home_Preferences-Highlighters.png) Highlighters can match the contents of the Type and System fields * Exact Strings * Substrings * Regular Expressions ![Adding Regex Highlighter](docs/Home_Add%20Highlighter%20-%20Regex.png) 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. ![Toolbar - User defined highlighters](docs/Home_Toolbar%20-%20User%20defined%20highlighter.png) 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 ================================================