Developing Log Synchronization Applications with MONO on macOS

When developing cross-platform applications, MONO provides a viable solution for running .NET applications on macOS. This article explores creating a log monitoring and synchronization tool for Apple iMacs in a production environment.

Configuration Management The application requires flexible configuration handling to monitor multiple log directories with different settings. Since XML parsing libraries weren't fully supported, a custom text-based configuration approach was implemented.

The configuration format supports multiple monitoring profiles:

MonitoringDirectoryType:['Immediate','Cycle']

Path:['/Users/PRODUCTION/Public/FinalTest/LogsFolder','/Users/PRODUCTION/Public/FinalTest/LogsFolder']

TargetPath:['/Volumes/mes_data/FINALTEST/n81a/{Y}/{M}/{D}/FIN012','/Volumes/mes_data/FINALTEST/n81a/{Y}/{M}/{D}/FIN012']

IncludeSubdirectories:['FALSE','false']

Filter:['.TXT','.CSV']

BackupPath:['/Users/PRODUCTION/BACKUP/{Y}/{M}/{D}','/Users/PRODUCTION/BACKUP/{Y}/{M}/{D}']

BackupExpired:['2','2']

CycleMinutes:['','1440']

The configuration parser reads and processes these settings:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using MonitoringApp.Utilities;

namespace MonitoringApp.Configuration
{
    public class SettingsManager
    {
        private static bool initialized = false;
        private static List<MonitoringProfile> profiles;
        
        public static List<MonitoringProfile> Profiles
        {
            get
            {
                if (profiles == null)
                {
                    try
                    {
                        if (!initialized)
                            return LoadConfiguration();
                        else
                            return null;
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex);
                        return null;
                    }
                }
                return profiles;
            }
            set { profiles = value; }
        }

        private static List<MonitoringProfile> LoadConfiguration()
        {
            initialized = true;
            string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.txt");
            
            if (File.Exists(configPath))
            {
                if (Profiles == null)
                    Profiles = new List<MonitoringProfile>();
                
                using (StreamReader reader = new StreamReader(configPath))
                {
                    int lineNumber = 1;
                    while (reader.Peek() > 0)
                    {
                        string line = reader.ReadLine().Trim();
                        if (string.IsNullOrEmpty(line))
                            continue;
                            
                        if (!line.Contains("[") || !line.Contains("]"))
                        {
                            Logger.LogError($"Configuration error: Missing brackets at line {lineNumber}");
                            continue;
                        }
                        
                        if (!line.Contains(":"))
                        {
                            Logger.LogError($"Configuration error: Missing colon at line {lineNumber}");
                            continue;
                        }
                        
                        string[] parts = line.Split(':');
                        string values = line.Substring(line.IndexOf(':') + 1)
                                         .Trim(']').Trim('[').Trim('\'');
                        string[] configValues = values.Split(',');
                        
                        ProcessSetting(parts[0], configValues, lineNumber == 1);
                        lineNumber++;
                    }
                }
                return Profiles;
            }
            else
            {
                Logger.LogError($"Configuration file not found: {configPath}");
                return null;
            }
        }

        private static void ProcessSetting(string settingName, string[] values, bool isFirstLine)
        {
            try
            {
                var settingType = (ConfigurationType)Enum.Parse(typeof(ConfigurationType), settingName, true);
                
                for (int i = 0; i < values.Length; i++)
                {
                    string value = values[i].Trim('\'');
                    
                    switch (settingType)
                    {
                        case ConfigurationType.MonitoringDirectoryType:
                            HandleDirectoryType(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.Path:
                            HandlePath(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.IncludeSubdirectories:
                            HandleSubdirectories(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.Filter:
                            HandleFilter(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.BackupPath:
                            HandleBackupPath(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.TargetPath:
                            HandleTargetPath(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.BackupExpired:
                            HandleBackupExpiration(value, i, isFirstLine);
                            break;
                            
                        case ConfigurationType.CycleMinutes:
                            HandleCycleMinutes(value, i, isFirstLine);
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        // Additional handler methods would be implemented here...
    }
}

Logging Infrastructure Robust logging is essential for monitoring application behavior and troubleshooting issues in production environments.

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

namespace MonitoringApp.Logging
{
    public class Logger
    {
        private static readonly object lockObject = new object();
        private static string logDirectory;
        
        public static string LogDirectory
        {
            get
            {
                lock (lockObject)
                {
                    if (string.IsNullOrEmpty(logDirectory))
                    {
                        string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", 
                                                 DateTime.Now.ToString("yyyy-MM"), DateTime.Now.Day.ToString());
                        if (!Directory.Exists(path))
                            Directory.CreateDirectory(path);
                        return path + Path.DirectorySeparatorChar;
                    }
                    return logDirectory;
                }
            }
        }

        public static void WriteLog(string logFile, string message)
        {
            try
            {
                lock (lockObject)
                {
                    StringBuilder builder = new StringBuilder();
                    builder.Append(LogDirectory).Append(logFile).Append(".log");
                    
                    using (StreamWriter writer = File.AppendText(builder.ToString()))
                    {
                        writer.WriteLine("================================ {0} =================================", 
                                       DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                        writer.WriteLine(message);
                        writer.Flush();
                        writer.Close();
                        Console.WriteLine(message);
                    }
                }
            }
            catch
            {
                // Silent catch to prevent logging errors from crashing the application
            }
        }

        public static void WriteError(Exception exception)
        {
            StringBuilder errorBuilder = new StringBuilder();
            errorBuilder.AppendLine("[Message] " + exception.Message);
            errorBuilder.AppendLine("[TargetSite] " + exception.TargetSite);
            errorBuilder.AppendLine("[StackTrace] " + exception.StackTrace);
            errorBuilder.AppendLine("[Source] " + exception.Source);
            
            foreach (var key in exception.Data.Keys)
            {
                errorBuilder.AppendLine($"[{key}] {exception.Data[key]}");
            }
            
            if (exception.InnerException != null && 
                exception.InnerException.Message != exception.Message)
            {
                errorBuilder.AppendLine("_______________________________________________________________________");
                errorBuilder.AppendLine("[Inner Message] " + exception.InnerException.Message);
                errorBuilder.AppendLine("[Inner TargetSite] " + exception.InnerException.TargetSite);
                errorBuilder.AppendLine("[Inner StackTrace] " + exception.InnerException.StackTrace);
                errorBuilder.AppendLine("[Inner Source] " + exception.InnerException.Source);
            }
            
            WriteLog("error", errorBuilder.ToString());
        }

        public static void WriteWarning(string message)
        {
            WriteLog("warning", message);
        }

        public static void WriteTrace(string message)
        {
            Console.WriteLine(message);
        }
    }

    public enum LogFile
    {
        Trace,
        Warning,
        Error,
        SQL,
        System
    }
}

Configuration Parameters The application supports several configuration parameters:

MonitoringDirectoryType: Determines monitoring behavior. Supports "Immediate" (real-time) or "Cycle" (periodic) monitoring. Path: The directory to monitor. Must be an existing path on the iMac. TargetPath: Destination for uploaded logs. Should use macOS volume paths like "/Volumes/mes_data/n81a". IncludeSubdirectories: Whether to monitor subdirectories within the target path. Filter: File filter pattern (e.g., "*.txt" for text files). BackupPath: Local backup location for processed logs. BackupExpired: Number of days to retain backup files (default: 30). CycleMinutes: Monitoring interval in minutes for cycle-based monitoring (default: 60).

When deploying to production environments, careful attention to macOS-specific behaviors is crucial. The application must handle differences between Windows and macOS file system paths appropriately.

Tags: MONO .NET macOS log-monitoring cross-platform-development

Posted on Sun, 30 Aug 2026 16:19:40 +0000 by ConnorSBB