Arga.Sla.Addins.Core 2.3.1

dotnet add package Arga.Sla.Addins.Core --version 2.3.1
                    
NuGet\Install-Package Arga.Sla.Addins.Core -Version 2.3.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Arga.Sla.Addins.Core" Version="2.3.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Arga.Sla.Addins.Core" Version="2.3.1" />
                    
Directory.Packages.props
<PackageReference Include="Arga.Sla.Addins.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Arga.Sla.Addins.Core --version 2.3.1
                    
#r "nuget: Arga.Sla.Addins.Core, 2.3.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Arga.Sla.Addins.Core@2.3.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Arga.Sla.Addins.Core&version=2.3.1
                    
Install as a Cake Addin
#tool nuget:?package=Arga.Sla.Addins.Core&version=2.3.1
                    
Install as a Cake Tool

SmartLogAnalyzer Addin System - Complete Documentation

Overview

The SmartLogAnalyzer addin system allows developers to extend the application's functionality through a plugin architecture. All addins inherit from the base IAddin interface and are delivered as compiled DLL files placed in the SmartLogAnalyzer addins folder, where they are loaded at runtime.

The system supports six addin types, each handling a different aspect of log analysis: parsing, transformation, visualization, filtering, and application control.


Base Interface: IAddin

All addins must implement IAddin. This interface defines the core contract for any extension.

Interface Definition

namespace Sla.Addins.Core
{
    /// <summary>
    /// Interface to be implemented by any kind of SLA Addin.
    /// </summary>
    public interface IAddin
    {
        /// <summary>
        /// Addin Name, to be displayed in the addins window and addin header.
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Addin description, to be displayed in the Addin window.
        /// </summary>
        string Description { get; }

        /// <summary>
        /// Does the addin have a configuration window?
        /// </summary>
        bool HasConfigurationWindow { get; }

        /// <summary>
        /// Get the addin configuration window.
        /// </summary>
        /// <param name="configurationFolder">Folder where SLA stores configuration files.</param>
        /// <returns>An instance of the configuration window for the addin.</returns>
        Window GetConfigurationWindow(string configurationFolder);
    }
}

Requirements

  • Name: A non-null string displayed in the UI. Must be unique within the application.
  • Description: A concise explanation (200 characters recommended) of what the addin does.
  • HasConfigurationWindow: Return true only if the addin provides a configuration UI.
  • GetConfigurationWindow: Only called if HasConfigurationWindow is true. Must return a valid WPF Window instance.

Configuration Folder

The configurationFolder parameter points to a writable directory where the addin can persist settings. This is typically appended with the addin name to avoid conflicts: Path.Combine(configurationFolder, Name).


Addin Type 1: ILineTranslator

Purpose: Transform or translate individual log lines. Use this to modify, enrich, or reformat log content.

Use Cases:

  • Parse embedded JSON and pretty-print it
  • Redact sensitive information
  • Expand abbreviations or codes
  • Translate one log format to another

Interface Definition

namespace Sla.Addins.Core.Processor
{
    public interface ILineTranslator : IAddin
    {
        /// <summary>
        /// Checks if this translator can process the given line.
        /// </summary>
        /// <param name="text">The log line to evaluate.</param>
        /// <returns>True if CanTranslate succeeds for this line; false otherwise.</returns>
        bool CanTranslate(string text);

        /// <summary>
        /// Translates the given line.
        /// </summary>
        /// <param name="line">The log line to transform.</param>
        /// <returns>The translated line. Return the original if translation is not applicable.</returns>
        string Translate(string line);
    }
}

Constraints

  • CanTranslate() must be fast (< 10ms per call recommended). It is called on every log line.
  • Translate() should handle null or empty strings gracefully.
  • Always return a string; never return null.
  • If translation fails, return the original line.
  • Translators are applied in registration order; output from one may feed into another.

Complete Implementation Example

using System;
using System.Text.Json;
using System.Windows;
using Sla.Addins.Core;
using Sla.Addins.Core.Processor;

namespace MyAddin.Processor
{
    /// <summary>
    /// Translates log lines containing JSON by pretty-printing the JSON payload.
    /// Example: "level=INFO message={\"key\":\"value\"}" 
    /// becomes "level=INFO message={\n  \"key\": \"value\"\n}"
    /// </summary>
    public class JsonFormatterTranslator : ILineTranslator
    {
        public string Name => "JSON Formatter Translator";

        public string Description => "Pretty-prints embedded JSON objects in log lines";

        public bool HasConfigurationWindow => false;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            throw new NotImplementedException("This addin has no configuration window.");
        }

        public bool CanTranslate(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
                return false;

            // Simple heuristic: does the line contain a JSON object?
            return text.Contains('{') && text.Contains('}');
        }

        public string Translate(string line)
        {
            if (string.IsNullOrWhiteSpace(line))
                return line;

            try
            {
                // Find and extract the first JSON-like substring
                var startIndex = line.IndexOf('{');
                var endIndex = line.LastIndexOf('}');

                if (startIndex == -1 || endIndex == -1 || startIndex >= endIndex)
                    return line;

                var jsonString = line.Substring(startIndex, endIndex - startIndex + 1);

                // Parse and re-format the JSON
                var jsonDoc = JsonDocument.Parse(jsonString);
                var prettyJson = JsonSerializer.Serialize(jsonDoc, new JsonSerializerOptions 
                { 
                    WriteIndented = true 
                });

                // Replace the original JSON with formatted version
                var prefix = line[..startIndex];
                var suffix = line[(endIndex + 1)..];

                return $"{prefix}{prettyJson}{suffix}";
            }
            catch
            {
                // If parsing fails, return original line
                return line;
            }
        }
    }
}

Configuration Window Example

using System.Windows;

namespace MyAddin.Processor
{
    /// <summary>
    /// Configuration window for the JSON Formatter Translator.
    /// </summary>
    public partial class JsonFormatterConfigWindow : Window
    {
        public JsonFormatterConfigWindow(string configurationFolder)
        {
            InitializeComponent();
            // Load settings from configurationFolder if needed
        }

        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            // Persist settings to configurationFolder
            DialogResult = true;
            Close();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = false;
            Close();
        }
    }
}

Addin Type 2: IDateTimeParser

Purpose: Parse timestamp formats found at the beginning of log lines. Enables SmartLogAnalyzer to understand various date/time patterns.

Use Cases:

  • Parse ISO 8601 timestamps
  • Parse custom application-specific datetime formats
  • Parse Unix timestamps or epoch milliseconds
  • Support multiple timezone formats

Interface Definition

using System;

namespace Sla.Addins.Core
{
    public interface IDateTimeParser : IAddin
    {
        /// <summary>
        /// Attempts to parse a datetime from the input string.
        /// </summary>
        /// <param name="input">The string to parse (typically the start of a log line).</param>
        /// <param name="dateTime">The parsed DateTime if successful; null otherwise.</param>
        /// <returns>True if parsing succeeded; false otherwise.</returns>
        bool TryParse(string input, out DateTime? dateTime);
    }
}

Constraints

  • TryParse() must be extremely fast (< 5ms per call). It is called on every log line.
  • Return the parsed datetime in UTC or local time consistently.
  • Set dateTime to null if parsing fails.
  • Parsing is attempted in registration order; the first successful parser wins.
  • The parser should consume only the datetime portion, not the entire line.

Complete Implementation Example

using System;
using System.Globalization;
using System.Windows;
using Sla.Addins.Core;

namespace MyAddin.Parsers
{
    /// <summary>
    /// Parses ISO 8601 datetime strings with optional timezone info.
    /// Examples: "2024-01-15T10:30:45Z" or "2024-01-15T10:30:45+02:00"
    /// </summary>
    public class Iso8601DateTimeParser : IDateTimeParser
    {
        public string Name => "ISO 8601 DateTime Parser";

        public string Description => "Parses ISO 8601 formatted timestamps (e.g., 2024-01-15T10:30:45Z)";

        public bool HasConfigurationWindow => false;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            throw new NotImplementedException("This addin has no configuration window.");
        }

        public bool TryParse(string input, out DateTime? dateTime)
        {
            dateTime = null;

            if (string.IsNullOrWhiteSpace(input) || input.Length < 19)
                return false;

            // ISO 8601 format: YYYY-MM-DDTHH:mm:ssZ or with timezone
            // Minimum length is 19 characters for "YYYY-MM-DDTHH:mm:ss"
            try
            {
                // Try to parse using ISO 8601 standard format
                if (DateTime.TryParseExact(
                    input,
                    new[] { "O", "o", "s", "u" },
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.AdjustToUniversal,
                    out var parsedDateTime))
                {
                    dateTime = parsedDateTime;
                    return true;
                }

                // More lenient parsing for variations
                if (DateTime.TryParse(
                    input,
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.AdjustToUniversal,
                    out parsedDateTime))
                {
                    dateTime = parsedDateTime;
                    return true;
                }
            }
            catch
            {
                // Parsing failed; return false
                return false;
            }

            return false;
        }
    }
}

Advanced Example: Unix Timestamp Parser

using System;
using System.Windows;
using Sla.Addins.Core;

namespace MyAddin.Parsers
{
    /// <summary>
    /// Parses Unix timestamps (seconds or milliseconds since epoch).
    /// Examples: "1705334445" (seconds) or "1705334445123" (milliseconds)
    /// </summary>
    public class UnixTimestampParser : IDateTimeParser
    {
        private static readonly DateTime EpochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public string Name => "Unix Timestamp Parser";

        public string Description => "Parses Unix timestamps (seconds or milliseconds since epoch)";

        public bool HasConfigurationWindow => false;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            throw new NotImplementedException("This addin has no configuration window.");
        }

        public bool TryParse(string input, out DateTime? dateTime)
        {
            dateTime = null;

            if (string.IsNullOrWhiteSpace(input))
                return false;

            try
            {
                // Extract the numeric portion from the start of the string
                var numericPart = "";
                foreach (var ch in input)
                {
                    if (char.IsDigit(ch))
                        numericPart += ch;
                    else
                        break;
                }

                if (numericPart.Length < 10)
                    return false;

                if (long.TryParse(numericPart[..10], out var secondsSinceEpoch))
                {
                    // Likely seconds
                    dateTime = EpochUtc.AddSeconds(secondsSinceEpoch);
                    return true;
                }

                if (numericPart.Length >= 13 && long.TryParse(numericPart[..13], out var millisecondsSinceEpoch))
                {
                    // Likely milliseconds
                    dateTime = EpochUtc.AddMilliseconds(millisecondsSinceEpoch);
                    return true;
                }
            }
            catch
            {
                // Parsing failed
                return false;
            }

            return false;
        }
    }
}

Addin Type 3: IVisualizer

Purpose: Provide rich UI controls to display detailed information about specific log lines. The visualizer is displayed in a detail panel when a log line is selected.

Use Cases:

  • Display JSON or XML in a tree view
  • Render exception stack traces with syntax highlighting
  • Show embedded images or metrics
  • Display formatted tables or charts for structured data

Interface Definition

using System.Windows.Controls;

namespace Sla.Addins.Core.Visualizer
{
    public interface IVisualizer : IAddin
    {
        /// <summary>
        /// Checks if this visualizer can handle the given log line.
        /// </summary>
        /// <param name="text">The log line to evaluate.</param>
        /// <returns>True if the visualizer can display this line; false otherwise.</returns>
        bool CanVisualize(string text);

        /// <summary>
        /// Creates a UI control to display the log line details.
        /// </summary>
        /// <param name="text">The log line to visualize.</param>
        /// <returns>A WPF Control configured to display the line's details.</returns>
        Control GetVisualizer(string text);
    }
}

Constraints

  • CanVisualize() is called frequently; keep it fast (< 10ms).
  • GetVisualizer() can return the same Control instance or create a new one each time (implementation choice).
  • The returned Control must support being added to a WPF Grid or StackPanel.
  • Handle rendering errors gracefully; show an error message in a TextBlock if visualization fails.
  • Respect the parent container's width; use HorizontalAlignment="Stretch".
  • Visualizers are checked in registration order; the first match is used.

Complete Implementation Example: Exception Visualizer

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Sla.Addins.Core;
using Sla.Addins.Core.Visualizer;

namespace MyAddin.Visualizers
{
    /// <summary>
    /// Visualizes exception stack traces with syntax highlighting and line formatting.
    /// Expected format: "ExceptionType: Message\n   at Namespace.Class.Method(...)..."
    /// </summary>
    public class ExceptionStackTraceVisualizer : IVisualizer
    {
        public string Name => "Exception Stack Trace Visualizer";

        public string Description => "Formats and displays exception stack traces with syntax highlighting";

        public bool HasConfigurationWindow => false;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            throw new NotImplementedException("This addin has no configuration window.");
        }

        public bool CanVisualize(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
                return false;

            // Check for common exception patterns
            return text.Contains("Exception") && text.Contains("at ");
        }

        public Control GetVisualizer(string text)
        {
            try
            {
                var stackPanel = new StackPanel
                {
                    Orientation = Orientation.Vertical,
                    Margin = new Thickness(5),
                    Background = Brushes.White
                };

                var lines = text.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);

                // First line: exception type and message
                if (lines.Length > 0)
                {
                    var headerTextBlock = new TextBlock
                    {
                        Text = lines[0],
                        FontWeight = FontWeights.Bold,
                        Foreground = Brushes.DarkRed,
                        FontSize = 14,
                        TextWrapping = TextWrapping.Wrap,
                        Margin = new Thickness(0, 0, 0, 10)
                    };
                    stackPanel.Children.Add(headerTextBlock);
                }

                // Stack trace lines
                for (var i = 1; i < lines.Length; i++)
                {
                    var line = lines[i];
                    var textBlock = new TextBlock
                    {
                        Text = line.Trim(),
                        FontFamily = new FontFamily("Courier New"),
                        FontSize = 11,
                        Foreground = Brushes.DarkGray,
                        TextWrapping = TextWrapping.Wrap,
                        Margin = new Thickness(10, 0, 0, 5)
                    };
                    stackPanel.Children.Add(textBlock);
                }

                var scrollViewer = new ScrollViewer
                {
                    Content = stackPanel,
                    VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                    HorizontalAlignment = HorizontalAlignment.Stretch,
                    Height = 300
                };

                return scrollViewer;
            }
            catch (Exception ex)
            {
                return new TextBlock
                {
                    Text = $"Error rendering visualization: {ex.Message}",
                    Foreground = Brushes.Red,
                    TextWrapping = TextWrapping.Wrap
                };
            }
        }
    }
}

Advanced Example: JSON Tree Visualizer

using System;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using Sla.Addins.Core;
using Sla.Addins.Core.Visualizer;

namespace MyAddin.Visualizers
{
    /// <summary>
    /// Visualizes JSON data in an expandable TreeView.
    /// </summary>
    public class JsonTreeVisualizer : IVisualizer
    {
        public string Name => "JSON Tree Visualizer";

        public string Description => "Displays JSON objects in an interactive tree view";

        public bool HasConfigurationWindow => false;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            throw new NotImplementedException("This addin has no configuration window.");
        }

        public bool CanVisualize(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
                return false;

            try
            {
                JsonDocument.Parse(text);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public Control GetVisualizer(string text)
        {
            try
            {
                var jsonDoc = JsonDocument.Parse(text);
                var treeView = new TreeView { IsEnabled = true };
                var rootItem = BuildTreeNode("JSON", jsonDoc.RootElement);
                treeView.Items.Add(rootItem);

                var scrollViewer = new ScrollViewer
                {
                    Content = treeView,
                    VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                    HorizontalAlignment = HorizontalAlignment.Stretch,
                    Height = 300
                };

                return scrollViewer;
            }
            catch (Exception ex)
            {
                return new TextBlock
                {
                    Text = $"JSON visualization failed: {ex.Message}",
                    Foreground = System.Windows.Media.Brushes.Red,
                    TextWrapping = TextWrapping.Wrap
                };
            }
        }

        private TreeViewItem BuildTreeNode(string key, JsonElement element)
        {
            var item = new TreeViewItem { Header = $"{key}: {GetValuePreview(element)}" };

            switch (element.ValueKind)
            {
                case JsonValueKind.Object:
                    foreach (var property in element.EnumerateObject())
                    {
                        var childItem = BuildTreeNode(property.Name, property.Value);
                        item.Items.Add(childItem);
                    }
                    break;

                case JsonValueKind.Array:
                    var index = 0;
                    foreach (var arrayElement in element.EnumerateArray())
                    {
                        var childItem = BuildTreeNode($"[{index}]", arrayElement);
                        item.Items.Add(childItem);
                        index++;
                    }
                    break;
            }

            return item;
        }

        private string GetValuePreview(JsonElement element)
        {
            return element.ValueKind switch
            {
                JsonValueKind.String => $"\"{element.GetString()}\"",
                JsonValueKind.Number => element.GetRawText(),
                JsonValueKind.True => "true",
                JsonValueKind.False => "false",
                JsonValueKind.Null => "null",
                JsonValueKind.Object => "{...}",
                JsonValueKind.Array => "[...]",
                _ => "unknown"
            };
        }
    }
}

Addin Type 4: IExplorerItemFilter

Purpose: Custom filtering logic for items in the file explorer. Filters determine which files/folders are displayed or hidden.

Use Cases:

  • Hide files matching certain patterns (e.g., *.tmp, *.bak)
  • Show only files modified within a time range
  • Filter by file size or permissions
  • Combine multiple filter criteria

Interface Definition

namespace Sla.Addins.Core.Explorer
{
    public enum ExplorerItemType
    {
        Folder,
        File
    }

    public interface IExplorerItemFilter : IAddin
    {
        /// <summary>
        /// Determines whether an explorer item (file or folder) passes the filter.
        /// </summary>
        /// <param name="type">The type of item: Folder or File.</param>
        /// <param name="itemName">The name of the item (filename or folder name, not full path).</param>
        /// <param name="passesDefaultFilter">Result of the default filter (before custom filters).</param>
        /// <returns>True if the item should be displayed; false otherwise.</returns>
        bool PassesFilter(ExplorerItemType type, string itemName, bool passesDefaultFilter);
    }
}

Constraints

  • PassesFilter() must be fast (< 5ms per call). It is called for every file/folder in the explorer.
  • Use passesDefaultFilter as a baseline; return false if the default filter already rejected it (unless you want to force-include).
  • itemName contains only the filename or folder name, not the full path.
  • Multiple filters are combined using AND logic by default; return false to exclude an item.
  • Filters are applied in registration order.

Complete Implementation Example

using System;
using System.IO;
using System.Windows;
using Sla.Addins.Core;
using Sla.Addins.Core.Explorer;

namespace MyAddin.Explorer
{
    /// <summary>
    /// Filters files by size. Allows hiding very large files from the explorer view.
    /// Configuration: max file size in MB.
    /// </summary>
    public class FileSizeFilter : IExplorerItemFilter
    {
        private const long DefaultMaxSizeMb = 100;
        private long _maxSizeBytes = DefaultMaxSizeMb * 1024 * 1024;

        public string Name => "File Size Filter";

        public string Description => "Hides files larger than a configured size limit";

        public bool HasConfigurationWindow => true;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            var window = new FileSizeFilterConfigWindow(configurationFolder);
            window.MaxSizeMbChanged += (sender, maxSizeMb) =>
            {
                _maxSizeBytes = maxSizeMb * 1024 * 1024;
                SaveConfiguration(configurationFolder, maxSizeMb);
            };

            return window;
        }

        public bool PassesFilter(ExplorerItemType type, string itemName, bool passesDefaultFilter)
        {
            // Folders always pass
            if (type == ExplorerItemType.Folder)
                return passesDefaultFilter;

            // If default filter rejected it, respect that decision
            if (!passesDefaultFilter)
                return false;

            try
            {
                // For files, check size against limit
                // Note: In a real implementation, you might have access to the full path
                // This is a simplified example that assumes you have the file info available
                return true; // Placeholder: size validation would happen here
            }
            catch
            {
                // On error, defer to default filter
                return passesDefaultFilter;
            }
        }

        private void SaveConfiguration(string configurationFolder, long maxSizeMb)
        {
            try
            {
                var configPath = Path.Combine(configurationFolder, $"{Name}.config");
                Directory.CreateDirectory(configurationFolder);
                File.WriteAllText(configPath, maxSizeMb.ToString());
            }
            catch
            {
                // Ignore configuration save errors
            }
        }
    }
}

Configuration Window for Filter

using System;
using System.Windows;
using System.Windows.Controls;

namespace MyAddin.Explorer
{
    public partial class FileSizeFilterConfigWindow : Window
    {
        public event EventHandler<long> MaxSizeMbChanged;

        private readonly string _configurationFolder;

        public FileSizeFilterConfigWindow(string configurationFolder)
        {
            InitializeComponent();
            _configurationFolder = configurationFolder;

            this.Title = "File Size Filter Configuration";
            this.Width = 400;
            this.Height = 200;

            var stackPanel = new StackPanel { Margin = new Thickness(10) };

            var label = new Label { Content = "Maximum file size (MB):" };
            stackPanel.Children.Add(label);

            var textBox = new TextBox { Name = "MaxSizeTextBox", Text = "100" };
            stackPanel.Children.Add(textBox);

            var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 10, 0, 0) };

            var okButton = new Button { Content = "OK", Width = 80, Margin = new Thickness(0, 0, 5, 0) };
            okButton.Click += (s, e) =>
            {
                if (long.TryParse(textBox.Text, out var maxSizeMb))
                {
                    MaxSizeMbChanged?.Invoke(this, maxSizeMb);
                    DialogResult = true;
                    Close();
                }
            };
            buttonPanel.Children.Add(okButton);

            var cancelButton = new Button { Content = "Cancel", Width = 80 };
            cancelButton.Click += (s, e) =>
            {
                DialogResult = false;
                Close();
            };
            buttonPanel.Children.Add(cancelButton);

            stackPanel.Children.Add(buttonPanel);
            this.Content = stackPanel;
        }
    }
}

Advanced Example: Pattern-Based Filter

using System;
using System.Text.RegularExpressions;
using System.Windows;
using Sla.Addins.Core;
using Sla.Addins.Core.Explorer;

namespace MyAddin.Explorer
{
    /// <summary>
    /// Filters files by name pattern (regex). Allows excluding temporary or backup files.
    /// Pattern examples: ".*\\.tmp$", ".*\\.bak$", "~.*"
    /// </summary>
    public class PatternExclusionFilter : IExplorerItemFilter
    {
        private Regex _excludePattern;

        public string Name => "Pattern Exclusion Filter";

        public string Description => "Hides files matching a regex pattern";

        public bool HasConfigurationWindow => true;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            return new PatternFilterConfigWindow(configurationFolder, pattern =>
            {
                try
                {
                    _excludePattern = new Regex(pattern, RegexOptions.IgnoreCase);
                }
                catch
                {
                    _excludePattern = null;
                }
            });
        }

        public bool PassesFilter(ExplorerItemType type, string itemName, bool passesDefaultFilter)
        {
            if (!passesDefaultFilter)
                return false;

            if (_excludePattern == null)
                return true;

            // Return false if the name matches the exclusion pattern
            return !_excludePattern.IsMatch(itemName);
        }
    }
}

Addin Type 5: ISlaController

Purpose: Programmatically control the SmartLogAnalyzer application. Useful for automation, batch processing, or integration with external systems.

Use Cases:

  • Automatically open files on startup
  • Switch between open views/sources
  • Trigger actions based on events
  • Integrate with external tools

Interface Definition

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Sla.Addins.Core.Controller
{
    /// <summary>
    /// Represents an item that can be controlled.
    /// </summary>
    public interface IControlable
    {
        /// <summary>
        /// Fired when the application starts up.
        /// </summary>
        event EventHandler<IStartupInfo> Started;

        /// <summary>
        /// Fired when the application is closing.
        /// </summary>
        event EventHandler Closing;

        /// <summary>
        /// Opens a log file or data source.
        /// </summary>
        void OpenSource(string location);

        /// <summary>
        /// Closes a previously opened source.
        /// </summary>
        void CloseSource(string location);

        /// <summary>
        /// Opens or creates a merged view from multiple sources.
        /// </summary>
        /// <param name="locations">Collection of file paths to include in the merged view.</param>
        /// <param name="name">Optional name for the merged view. Auto-generated if null.</param>
        Task OpenView(IEnumerable<string> locations, string name = null);

        /// <summary>
        /// Gets the list of currently open sources.
        /// </summary>
        IEnumerable<string> GetOpenSources();

        /// <summary>
        /// Activates (brings to focus) a specific source or view.
        /// </summary>
        void Activate(string sourceName);
    }

    public interface ISlaController : IAddin
    {
        /// <summary>
        /// Called by SmartLogAnalyzer to initialize the controller with access to application controls.
        /// </summary>
        /// <param name="controlable">The main application control interface.</param>
        void Initialize(IControlable controlable);
    }
}

Constraints

  • Initialize() is called once at application startup.
  • Store a reference to IControlable for later use.
  • Do not block the UI thread in event handlers (Started, Closing).
  • Use async/await when calling OpenView().
  • Locations should be full file paths; relative paths may not work.

Complete Implementation Example: Auto-Load Sources

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using Sla.Addins.Core;
using Sla.Addins.Core.Controller;

namespace MyAddin.Controller
{
    /// <summary>
    /// Automatically loads a predefined set of log files on application startup.
    /// Configuration: list of file paths and whether to create a merged view.
    /// </summary>
    public class AutoLoadController : ISlaController
    {
        private IControlable _controlable;
        private List<string> _filesToLoad;
        private bool _createMergedView;

        public string Name => "Auto-Load Controller";

        public string Description => "Automatically loads specified log files on startup";

        public bool HasConfigurationWindow => true;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            return new AutoLoadConfigWindow(configurationFolder, (files, merged) =>
            {
                _filesToLoad = files;
                _createMergedView = merged;
            });
        }

        public void Initialize(IControlable controlable)
        {
            _controlable = controlable ?? throw new ArgumentNullException(nameof(controlable));
            _filesToLoad = new List<string>();
            _createMergedView = false;

            // Subscribe to startup event
            _controlable.Started += Controlable_Started;
        }

        private async void Controlable_Started(object sender, IStartupInfo e)
        {
            try
            {
                if (_filesToLoad.Count == 0)
                    return;

                if (_createMergedView)
                {
                    // Create a merged view from all files
                    await _controlable.OpenView(_filesToLoad, "Auto-Loaded Merged View");
                }
                else
                {
                    // Open each file individually
                    foreach (var filePath in _filesToLoad)
                    {
                        if (File.Exists(filePath))
                        {
                            _controlable.OpenSource(filePath);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"AutoLoadController error: {ex.Message}");
            }
        }
    }
}

Advanced Example: Event-Driven Controller

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using Sla.Addins.Core;
using Sla.Addins.Core.Controller;

namespace MyAddin.Controller
{
    /// <summary>
    /// Monitors a folder and automatically loads new log files as they are created.
    /// </summary>
    public class FolderWatcherController : ISlaController
    {
        private IControlable _controlable;
        private FileSystemWatcher _watcher;
        private string _monitoredFolder;

        public string Name => "Folder Watcher Controller";

        public string Description => "Automatically opens new log files in a monitored folder";

        public bool HasConfigurationWindow => true;

        public Window GetConfigurationWindow(string configurationFolder)
        {
            return new FolderWatcherConfigWindow(configurationFolder, folder =>
            {
                _monitoredFolder = folder;
            });
        }

        public void Initialize(IControlable controlable)
        {
            _controlable = controlable ?? throw new ArgumentNullException(nameof(controlable));

            _controlable.Started += (s, e) =>
            {
                StartWatching();
            };

            _controlable.Closing += (s, e) =>
            {
                StopWatching();
            };
        }

        private void StartWatching()
        {
            if (string.IsNullOrWhiteSpace(_monitoredFolder) || !Directory.Exists(_monitoredFolder))
                return;

            _watcher = new FileSystemWatcher(_monitoredFolder)
            {
                Filter = "*.log",
                NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite
            };

            _watcher.Created += OnFileCreated;
            _watcher.EnableRaisingEvents = true;
        }

        private void StopWatching()
        {
            _watcher?.Dispose();
        }

        private void OnFileCreated(object sender, FileSystemEventArgs e)
        {
            try
            {
                // Wait a moment to ensure the file is fully written
                System.Threading.Thread.Sleep(500);

                _controlable.OpenSource(e.FullPath);
            }
            catch
            {
                // Ignore errors
            }
        }
    }
}

Configuration Window for Controller

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace MyAddin.Controller
{
    public partial class AutoLoadConfigWindow : Window
    {
        public event Action<List<string>, bool> OnConfigured;

        public AutoLoadConfigWindow(string configurationFolder, Action<List<string>, bool> onConfigured)
        {
            InitializeComponent();
            OnConfigured = onConfigured;

            this.Title = "Auto-Load Configuration";
            this.Width = 500;
            this.Height = 400;

            var stackPanel = new StackPanel { Margin = new Thickness(10) };

            var label = new Label { Content = "Files to auto-load (one per line):" };
            stackPanel.Children.Add(label);

            var textBox = new TextBox
            {
                Name = "FilesTextBox",
                AcceptsReturn = true,
                Height = 200,
                VerticalScrollBarVisibility = ScrollBarVisibility.Auto
            };
            stackPanel.Children.Add(textBox);

            var checkBox = new CheckBox { Content = "Create merged view" };
            stackPanel.Children.Add(checkBox);

            var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 10, 0, 0) };

            var okButton = new Button { Content = "OK", Width = 80, Margin = new Thickness(0, 0, 5, 0) };
            okButton.Click += (s, e) =>
            {
                var files = new List<string>(textBox.Text.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries));
                OnConfigured?.Invoke(files, checkBox.IsChecked ?? false);
                DialogResult = true;
                Close();
            };
            buttonPanel.Children.Add(okButton);

            var cancelButton = new Button { Content = "Cancel", Width = 80 };
            cancelButton.Click += (s, e) =>
            {
                DialogResult = false;
                Close();
            };
            buttonPanel.Children.Add(cancelButton);

            stackPanel.Children.Add(buttonPanel);
            this.Content = stackPanel;
        }
    }
}

Guide: Creating a New Addin

Step-by-Step Checklist

Step 1: Choose the Addin Type

  • Determine which interface your addin implements (ILineTranslator, IDateTimeParser, IVisualizer, IExplorerItemFilter, or ISlaController)
  • Review the constraints and use cases for that type
  • Verify that the interface matches your requirements

Step 2: Create the Project Structure

  • Create a new C# class library project targeting .NET 8
  • Add NuGet reference to Sla.Addins.Core
  • Create following folder structure:
    MyAddin/
    ├── MyAddin.csproj
    ├── Example/
    │   └── MyImplementation.cs
    ├── Configuration/
    │   └── ConfigurationWindow.xaml.cs (if HasConfigurationWindow = true)
    └── MyAddin.Tests/
        └── MyImplementationTests.cs
    

Step 3: Implement the Interface

  • Create a class that inherits from the chosen interface
  • Implement all interface members
  • Add XML documentation comments (/// <summary>)
  • Use C# 12 features where appropriate (target expressions, property patterns, etc.)

Step 4: Implement IAddin Base Members

  • Set Name to a unique, descriptive string
  • Set Description to a brief explanation (< 200 chars)
  • Set HasConfigurationWindow appropriately (true only if you provide a Window)
  • Implement GetConfigurationWindow() to return a configured WPF Window instance

Step 5: Add Error Handling

  • Wrap implementations in try-catch blocks
  • Return safe defaults (original input, null, false) on error
  • Use System.Diagnostics.Debug.WriteLine() for logging during development

Step 6: Add Configuration Support (if applicable)

  • Create a WPF Window class for configuration (if HasConfigurationWindow is true)
  • Persist configuration to the provided configurationFolder
  • Load configuration on window initialization
  • Use Path.Combine(configurationFolder, Name) to avoid conflicts

Step 7: Version and Document

  • Set assembly version (e.g., 1.0.0.0)
  • Add name and author to project properties
  • Include XML documentation on all public members
  • Add a README.md in the addin project root explaining configuration

Step 8: Unit Testing

  • Create a separate .Tests project (e.g., MyAddin.Tests)
  • Write MSTest unit tests for all public methods
  • Aim for > 80% code coverage
  • Test edge cases: null inputs, empty strings, exception scenarios

Step 9: Build and Prepare for Delivery

  • Build in Release mode
  • Verify no compiler warnings
  • Ensure the assembly is correctly named and versioned
  • Collect all required dependencies (only if not already present in SmartLogAnalyzer)

Step 10: Deploy to SmartLogAnalyzer

  • Copy the compiled DLL to the SmartLogAnalyzer addins folder
  • Restart the application to load the addin
  • Verify in the Addins window that the addin appears
  • Test functionality end-to-end

Deployment

Addin Delivery Method

All addins are delivered as compiled DLL files. This is the only supported distribution method.

  1. Build your addin project: Compile your addin implementation as a class library targeting .NET 8.
  2. Collect the DLL: Locate the compiled DLL (e.g., MyAddin.dll) in the bin\Release\net8.0\ folder.
  3. Include dependencies: If your addin depends on external NuGet packages, copy those DLL dependencies as well.
  4. Place in SmartLogAnalyzer addins folder: Copy the DLL and its dependencies to the SmartLogAnalyzer addins directory (typically <SLA Installation>\addins\ or configured addin path).
  5. Restart SmartLogAnalyzer: The application will discover and load the addin on the next startup.
  6. Verify: Check the Addins window in SmartLogAnalyzer to confirm the addin is loaded.

Naming and Organization

  • Name your DLL with a descriptive, unique name (e.g., MyCompany.LogAddin.dll).
  • Keep dependencies minimal; use only NuGet packages already in SmartLogAnalyzer's runtime if possible.
  • Document any external dependencies in a README file accompanying the DLL.

Best Practices

1. Performance

  • Keep CanTranslate(), CanVisualize(), TryParse(), and PassesFilter() fast (< 10ms).
  • Avoid expensive operations (network calls, file I/O) in these methods.
  • Consider caching or memoization for repeated parsing of the same patterns.
  • Profile your addin to identify bottlenecks.

2. Error Handling

  • Always return safe defaults instead of throwing exceptions in core methods.
  • Use try-catch sparingly; catch specific exceptions, not generic Exception.
  • Log errors to System.Diagnostics.Debug or to a file in the configuration folder.
  • Provide meaningful error messages in UI (TextBlock) when visualization fails.

3. Naming Conventions

  • Follow C# PascalCase for class and method names.
  • Use camelCase for private fields and local variables.
  • Prefix private fields with underscore: _configPath.
  • Include the addin type in the class name: MyJsonVisualizer, MyFilterTranslator.

4. Threading and Async

  • Do not block the UI thread in event handlers (ISlaController).
  • Use async/await for long-running operations.
  • Avoid Task.Run() unless necessary; prefer returning Task directly.
  • Be aware that config window operations may run on the UI thread.

5. MVVM Patterns

  • If using CommunityToolkit.MVVM, use [RelayCommand] and [ObservableProperty] attributes.
  • Avoid direct UI manipulation in ViewModels; use data binding.
  • Keep configuration state separate from business logic.

6. Resource Cleanup

  • Dispose of resources (file handles, connections) in configuration window Close events.
  • Unsubscribe from events when no longer needed (in FileSystemWatcher controller example).
  • Avoid resource leaks in Control creation (GetVisualizer).

7. Compatibility

  • Test on different Windows versions (Windows 10, Windows 11, etc.).
  • Ensure .NET 8 compatibility; do not use platform-specific APIs.
  • Avoid deprecated APIs; use modern C# 12 syntax.
  • Support both x64 and x86 architectures if possible.

Common Patterns

Pattern 1: Configuration Persistence

using System;
using System.IO;
using System.Text.Json;

namespace MyAddin.Common
{
    public class ConfigurationManager
    {
        public static void SaveConfiguration<T>(string configurationFolder, string addinName, T config)
        {
            try
            {
                var configPath = Path.Combine(configurationFolder, $"{addinName}.json");
                Directory.CreateDirectory(configurationFolder);
                var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
                File.WriteAllText(configPath, json);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Failed to save configuration: {ex.Message}");
            }
        }

        public static T LoadConfiguration<T>(string configurationFolder, string addinName, T defaultValue)
        {
            try
            {
                var configPath = Path.Combine(configurationFolder, $"{addinName}.json");
                if (!File.Exists(configPath))
                    return defaultValue;

                var json = File.ReadAllText(configPath);
                return JsonSerializer.Deserialize<T>(json) ?? defaultValue;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Failed to load configuration: {ex.Message}");
                return defaultValue;
            }
        }
    }
}

Pattern 2: Safe Parsing with Fallbacks

public bool TryParseDateTime(string input, out DateTime? result)
{
    result = null;

    if (string.IsNullOrWhiteSpace(input))
        return false;

    var formats = new[] { "O", "o", "s", "u", "yyyy-MM-dd HH:mm:ss" };

    try
    {
        if (DateTime.TryParseExact(input, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dt))
        {
            result = dt;
            return true;
        }

        // Fallback to loose parsing
        if (DateTime.TryParse(input, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt))
        {
            result = dt;
            return true;
        }
    }
    catch
    {
        // Parsing failed
    }

    return false;
}

Pattern 3: WPF Control Creation with Error Handling

public Control GetVisualizer(string text)
{
    try
    {
        if (string.IsNullOrWhiteSpace(text))
            return CreateErrorControl("No data to visualize");

        var data = ParseData(text);
        return CreateSuccessControl(data);
    }
    catch (Exception ex)
    {
        return CreateErrorControl($"Visualization failed: {ex.Message}");
    }
}

private Control CreateErrorControl(string message)
{
    return new TextBlock
    {
        Text = message,
        Foreground = System.Windows.Media.Brushes.Red,
        TextWrapping = TextWrapping.Wrap,
        Margin = new Thickness(5)
    };
}

Troubleshooting

Addin Not Loaded by SmartLogAnalyzer

  • Check: Ensure the assembly is in the correct addins folder.
  • Check: Verify the class implements one of the supported interfaces.
  • Check: Confirm the class is public and not abstract.
  • Check: Review SmartLogAnalyzer's log files for load errors.

Performance Issues

  • Profile: Use Visual Studio's Performance Profiler to identify bottlenecks.
  • Optimize: Cache expensive computations (parsing, regex compilation).
  • Async: Move long operations to background threads.

Configuration Not Persisting

  • Check: Verify configurationFolder is writable.
  • Check: Ensure file paths are correctly constructed with Path.Combine().
  • Check: Check for file permission errors in the application log.

UI Not Responsive

  • Avoid: Long-running operations on the UI thread.
  • Use: Task.Run() for background work (adhere to async guidelines).
  • Monitor: Use Task Parallel Library (TPL) for concurrency.

References

Product Compatible and additional computed target framework versions.
.NET net8.0-windows7.0 is compatible.  net9.0-windows was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0-windows7.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.3.1 129 6/1/2026
2.1.0 190 1/11/2026
2.0.0 264 1/1/2025
1.2.0 162 12/16/2024
1.1.0 239 9/17/2024
1.0.1 173 9/16/2024
1.0.0 177 9/16/2024