view ServerMonitor/Objects/Logger.cs @ 28:437442cd8090

Fix checks still running after a server is deleted and checks not running immediately after a server is created.
author Brad Greco <brad@bgreco.net>
date Sun, 02 Jun 2019 17:55:38 -0400
parents 68d7834dc28e
children
line wrap: on
line source

using ServerMonitorApp.Properties;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ServerMonitorApp
{
    /// <summary>Manages reading and writing check results to a log file.</summary>
    public class Logger
    {
        private const int logVersion = 1;
        private readonly string logFile;
        
        /// <summary>Logger constructor.</summary>
        /// <param name="file">The path of the log file to use.</param>
        public Logger(string file)
        {
            logFile = file;
        }

        /// <summary>Appends a string to the log file, creating the log file if it does not exist.</summary>
        /// <param name="logString">The string to log.</param>
        public async void Log(string logString)
        {
            // Create the file if it does not exist.
            // Write the current version at the beginning in case we ever change the format.
            if (!File.Exists(logFile))
                logString = "Server Monitor log version " + logVersion + Environment.NewLine + logString;
            // Write the message.
            using (FileStream stream = new FileStream(logFile, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, true))
            using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
            {
                await writer.WriteLineAsync(logString);
            }
        }

        /// <summary>Appends a check result to the log file, creating the log file if it does not exist.</summary>
        /// <param name="result">The check result to log.</param>
        public void Log(CheckResult result)
        {
            Log(result.ToLogString());
        }

        /// <summary>Reads all check results from the log for a server.</summary>
        /// <param name="server">The server whose check results should be read.</param>
        /// <returns>A list of all check results found in the log file for the given server.</returns>
        public IList<CheckResult> Read(Server server)
        {
            // Store the checks by ID.
            Dictionary<int, Check> checks = server.Checks.ToDictionary(c => c.Id);
            List<CheckResult> results = new List<CheckResult>();
            using (FileStream stream = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                // Read through the whole file, searching for results belonging to the server.
                while (true)
                {
                    try
                    {
                        string line = reader.ReadLine();
                        // Exit the loop when we reach the end.
                        if (line == null)
                            break;
                        // The check ID is a fixed length.
                        // Parse it and see if it matches any of the checks we are interested in.
                        if (int.TryParse(line.Substring(0, 5), out int id) && checks.TryGetValue(id, out Check check))
                        {
                            // If it is, add it to our results list.
                            results.Add(CheckResult.FromLogString(check, line));
                        }
                    }
                    // Don't know why this catch block is empty.
                    // Maybe to ignore errors if the file does not exist?
                    catch (Exception) { }
                }
            }
            // Return the newest results first since that's how they are displayed.
            results.Reverse();
            return results;
        }

        /// <summary>Removes old entries from the log.</summary>
        public async void TrimLog()
        {
            // Delete entries older than this date.
            DateTime maxAge = DateTime.Now.AddDays(-1 * Settings.Default.KeepLogDays);
            string tempFile = logFile + ".tmp";
            try
            {
                // Read through the log file and check the date of each entry.
                // If it is newer than the max age, copy it to a temporary file.
                using (FileStream stream = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                using (FileStream outStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None))
                using (StreamWriter writer = new StreamWriter(outStream, Encoding.UTF8))
                {
                    while (true)
                    {
                        try
                        {
                            string line = reader.ReadLine();
                            if (line == null)
                                break;
                            if (DateTime.TryParse(line.Substring(6, 10), out DateTime date) && date > maxAge)
                            {
                                await writer.WriteLineAsync(line);
                            }
                        }
                        catch (Exception) { }
                    }
                }
                // Delete the original file and rename the temporary file that contains only recent entries.
                File.Delete(logFile);
                File.Move(tempFile, logFile);
            }
            catch (FileNotFoundException)
            {
                // Do nothing if the log file does not exist yet.
            }
        }
    }
}