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.