/Utilities/Log.cs
C# | 1749 lines | 953 code | 158 blank | 638 comment | 94 complexity | a4fae9a7924d1d1351afe3d79a30e061 MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.IO;
- using System.Reflection;
- using System.Text;
- using Delta.Utilities.Helpers;
- using Delta.Utilities.Profiling;
- using NUnit.Framework;
-
- namespace Delta.Utilities
- {
- /// <summary>
- /// Log, handles all the message logging and storing the messages in an
- /// extra file which helps for later tracking and fixing bugs. Some extra
- /// functionality is provided by Delta.Engine, which sets up the log server
- /// to send log messages to a log server if specified in the application
- /// settings. Usually logging happens to the console and a log file.
- /// </summary>
- public class Log : IDisposable
- {
- #region Constants
- /// <summary>
- /// The text encoding format for all our logs.
- /// </summary>
- private static readonly Encoding TextFormat = Encoding.UTF8;
-
- /// <summary>
- /// The default extension for all our log files. Note 2011-04-06: Changed
- /// to .txt to make it easier to open by end users (.log is not longer
- /// used and has no special behavior anymore).
- /// </summary>
- private const string FileExtension = "txt";
-
- /// <summary>
- /// The maximum file size for a log file is 10 MB, if we go above that
- /// (e.g. if too much info is written every frame), then we cut the file
- /// in half (5 MB) and append from there. This way we have both the
- /// first 5 MB of the application run and the last log entries as well.
- /// </summary>
- private const int MaxLogFileSize = 10 * 1024 * 1024;
-
- /// <summary>
- /// The maximum size for log archive files is 20 MB, if we go above that,
- /// we will remove data from the beginning before appending new stuff at
- /// the end (the FileHelper.AppendTextToFile handles this all for us).
- /// </summary>
- private const int MaxArchiveLogFileSize = 20 * 1024 * 1024;
-
- /// <summary>
- /// Default log name. Currently set to Engine in case something is logged
- /// in DynamicBase or Factory, will be overwritten in Application.Start!
- /// </summary>
- internal const string DefaultLogName = "Engine";
-
- /// <summary>
- /// Helper to figure out when this application was started. This is
- /// not really the application start time because we will use the time
- /// the Log class is first accessed, but since we do that pretty much
- /// always at the application start (just to set the ProgramName, etc.)
- /// it will be the correct time. Used for the LogHeader!
- /// </summary>
- private static readonly DateTime applicationStartTime = DateTime.Now;
-
- /// <summary>
- /// Only remember the last 8 logged messages for CheckDuplicateMessage!
- /// </summary>
- private static readonly Queue<string> lastLoggedMessages = new Queue<string>(8);
-
- /// <summary>
- /// Maximum of Console.WriteLines we allow because of poor WriteLine
- /// performance in debug mode with debugging window open in Visual Studio!
- /// </summary>
- private const int MaxConsoleWritesPerSecond = 10;
-
- /// <summary>
- /// Only remember the last 10 Console.WriteLines, skip all lines than 10
- /// console writes, completely cut of any Console.WriteLines then. This
- /// way we should not lose more than 10*5ms = 50ms per second. See unit
- /// test below: 6ms for 30 characters writing to an attached debug window.
- /// </summary>
- private static readonly Queue<long> lastConsoleWriteTimes =
- new Queue<long>(MaxConsoleWritesPerSecond);
- #endregion
-
- #region Delegates
- /// <summary>
- /// The log delegate is used for other classes to attach to all log events
- /// that happen (both Log.Info and Log.Warning calls, but not Log.Test).
- /// It provides an already formated log text string and it used for example
- /// in the Delta.Engine.Application class to add Log Server features.
- /// </summary>
- /// <param name="message">Message</param>
- public delegate void LogDelegate(string message);
- #endregion
-
- #region Equals (Static)
- /// <summary>
- /// Equals, just trying to hide it.
- /// </summary>
- /// <param name="objA">Object A</param>
- /// <param name="objB">Object B</param>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new static bool Equals(object objA, object objB)
- {
- return object.Equals(objA, objB);
- }
- #endregion
-
- #region ReferenceEquals (Static)
- /// <summary>
- /// ReferenceEquals, just trying to hide it.
- /// </summary>
- /// <param name="objA">Object A</param>
- /// <param name="objB">Object B</param>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new static bool ReferenceEquals(object objA, object objB)
- {
- return object.ReferenceEquals(objA, objB);
- }
- #endregion
-
- #region ShowCurrentLogInTextEditor (Static)
- /// <summary>
- /// Show current log in text editor
- /// </summary>
- public static void ShowCurrentLogInTextEditor()
- {
- if (currentLogInstance != null &&
- File.Exists(currentLogInstance.Filename))
- {
- try
- {
- if (CheckIfLogWasCreatedFromNunitTestRunner() == false)
- {
- Process.Start(currentLogInstance.Filename);
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine(
- "Failed to open log file in text editor: " + ex.Message);
- }
- } // if
- }
- #endregion
-
- #region Test (Static)
- /// <summary>
- /// Verbose log information that is usually only used for unit tests.
- /// Can be disabled in release mode and should be used only for tests
- /// anyway. This is not sent to the DeltaEngine.net server and not even
- /// put into the log file, just to the console for the debugging
- /// console or the TestRunner or TestDriven.net output window!
- /// Please note that we will skip logging the same message over and over
- /// again because it is REALLY slow in VisualStudio (@see "ToConsole()").
- /// If you really want to log everything out and do not care about
- /// performance, just use Console.WriteLine yourself (e.g. when just
- /// quickly testing if a click event happens). With Log.Test you can even
- /// spam millions of messages per second, most of them will be skipped.
- /// </summary>
- /// <param name="message">Message</param>
- public static void Test(string message)
- {
- Test(message, false);
- }
-
- /// <summary>
- /// Test
- /// </summary>
- /// <param name="message">Message</param>
- /// <param name="skipRepeatingMessages">
- /// If 'true', messages which are repeating will be skipped (recommended
- /// for looping calls like in render oder updating) to avoid log spamming.
- /// </param>
- public static void Test(string message, bool skipRepeatingMessages)
- {
- // Format the message with a time stamp and pass it to the console only.
- ToConsole(FormatMessage(message), skipRepeatingMessages);
- }
- #endregion
-
- #region Info (Static)
- /// <summary>
- /// This method logs general information to the log file, to the console,
- /// if available to the screen, and finally to the DeltaEngine.net server.
- /// These kind of messages are usually for figuring stuff out that is
- /// working, but sometimes we need some additional details. On a normal
- /// release program run we should still have zero log messages!
- /// </summary>
- /// <param name="message">Message to show in the log and console</param>
- public static void Info(string message)
- {
- Instance.WriteInfo(message, true);
- }
-
- /// <summary>
- /// This method logs general information to the log file, to the console,
- /// if available to the screen, and finally to the DeltaEngine.net server.
- /// These kind of messages are usually for figuring stuff out that is
- /// working, but sometimes we need some additional details. On a normal
- /// release program run we should still have zero log messages!
- /// </summary>
- /// <param name="message">Message to show in the log and console</param>
- /// <param name="checkForDuplicates">Check if this message already was
- /// logged in the last 10 messages, then skip it to prevent log spam.
- /// This is on by default.
- /// </param>
- public static void Info(string message, bool checkForDuplicates)
- {
- Instance.WriteInfo(message, checkForDuplicates);
- }
- #endregion
-
- #region Warning (Static)
- /// <summary>
- /// Outputs a warning, which usually indicates something went really
- /// wrong. It could be a required file was missing, an exception occurred
- /// and was caught. Sometimes even a dynamic class initialization failed
- /// and we provided some fallback, but we want to warn about the initial
- /// failure. Non recoverable errors should throw an exception and will end
- /// up in Application.HandleUnhandledExceptions (which will still use this
- /// method to put the error to the log, but then will abort the
- /// application). A programmer or user can sometimes ignore warnings and
- /// still continue working without the application crashing, but it might
- /// not behave correctly. Note: Warnings always have a stack trace!
- /// </summary>
- /// <param name="message">Message</param>
- public static void Warning(string message)
- {
- // Note: Same code as in the other Warning overload. This is done to
- // prevent bloating the callstack more because we show it for warnings!
- // Helpers to display warnings in Scene.Draw!
- if (logTimer != null)
- {
- LastWarningTime =
- //too precise/slow: (float)logTimer.ElapsedMilliseconds / 1000.0f;
- //(float)logTimer.CachedMilliseconds / 1000.0f;
- logTimer.ElapsedMilliseconds;
- }
- if (String.IsNullOrEmpty(message) == false)
- {
- LastWarningMessage =
- // Only take the first line of the warning
- message.SplitMultiLineText()[0];
- }
-
- // The default is to display each warning with a stack trace.
- Instance.WriteWarning(message, true);
- }
-
- /// <summary>
- /// Outputs a warning, which usually indicates something went really
- /// wrong. It could be a required file was missing, an exception occurred
- /// and was caught. Sometimes even a dynamic class initialization failed
- /// and we provided some fallback, but we want to warn about the initial
- /// failure. Non recoverable errors should throw an exception and will end
- /// up in Application.HandleUnhandledExceptions (which will still use this
- /// method to put the error to the log, but then will abort the
- /// application). A programmer or user can sometimes ignore warnings and
- /// still continue working without the application crashing, but it might
- /// not behave correctly. Note: Warnings always have a stack trace!
- /// </summary>
- /// <param name="message">Message</param>
- /// <param name="showStackTrace">Show Stack Trace</param>
- public static void Warning(string message, bool showStackTrace)
- {
- // Helpers to display warnings in Scene.Draw!
- if (logTimer != null)
- {
- LastWarningTime =
- //too precise/slow: (float)logTimer.ElapsedMilliseconds / 1000.0f;
- //(float)logTimer.CachedMilliseconds / 1000.0f;
- logTimer.ElapsedMilliseconds;
- }
- if (String.IsNullOrEmpty(message) == false)
- {
- LastWarningMessage =
- // Only take the first line of the warning
- message.SplitMultiLineText()[0];
- }
-
- // And normally process the warning.
- Instance.WriteWarning(message, showStackTrace);
- }
- #endregion
-
- #region InvalidVersionWarning (Static)
- /// <summary>
- /// Logs a warning because of an invalid data version.
- /// </summary>
- /// <param name="className">Class name</param>
- /// <param name="dataVersion">Data version</param>
- /// <param name="maxSupportedVersion">Maximum supported version</param>
- public static void InvalidVersionWarning(string className,
- int dataVersion, int maxSupportedVersion)
- {
- Warning(
- "The version '" + dataVersion + "' of the '" + className +
- "' data is currently not supported, the max. possible version is '" +
- maxSupportedVersion + "'. Please make sure that the data is correct.");
- }
- #endregion
-
- #region Close (Static)
- /// <summary>
- /// Close log, will disconnect from the Log server and even open up the
- /// log file in editor in debug mode (if a log was generated at all).
- /// </summary>
- public static void Close()
- {
- // Write out the last console message if we had to discard it earlier
- // in ToConsole because of Console.WriteLine spam, which is very slow!
- if (String.IsNullOrEmpty(lastDiscardedConsoleMessage) == false)
- {
- Console.WriteLine(lastDiscardedConsoleMessage);
- lastDiscardedConsoleMessage = "";
- }
-
- // Open log in editor at shutdown, but only in debug mode :) This should
- // force us NOT to have any log output (then this won't happen).
- // First handle Engine.txt we want to show it!
- if (remToLaunchEngineLogAtEndOfProgram != "" &&
- File.Exists(remToLaunchEngineLogAtEndOfProgram))
- {
- try
- {
- if (CheckIfLogWasCreatedFromNunitTestRunner() == false)
- {
- Process.Start(remToLaunchEngineLogAtEndOfProgram);
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine(
- "Failed to open engine log file in text editor: " + ex.Message);
- }
- remToLaunchEngineLogAtEndOfProgram = "";
- } // if
-
- // Note: There could be an Setting to disable this launching log file
- // feature, but for now we want to show it always in debug mode to the
- // programmer (we do not want any logging at all in a functional app)!
- if (currentLogInstance != null &&
- FileHelper.Exists(currentLogInstance.Filename))
- {
- // Always close the log file first (if we can and it is still open)!
- if (currentLogInstance.logFile != null &&
- currentLogInstance.logFile.BaseStream.CanWrite)
- {
- try
- {
- currentLogInstance.logFile.Close();
- }
- catch (ObjectDisposedException)
- {
- // Ignore this, it can happen when this is called via the
- // destructor, which happens if we don't call Close explicitly.
- }
- } // if
- currentLogInstance.logFile = null;
-
- // Show current log in text editor
- ShowCurrentLogInTextEditor();
-
- // Make sure the log instance is now gone!
- currentLogInstance = null;
- } // if
- }
- #endregion
-
- #region LogHappened (Static)
- /// <summary>
- /// You can attach delegates to LogHappened to get notified whenever a
- /// log message is happening (usually should only happen for testing or
- /// when something goes wrong and a log warning is put out). Used
- /// in the Delta.Engine.Application class to add Log Server features.
- /// </summary>
- public static LogDelegate LogHappened;
- #endregion
-
- #region LastWarningMessage (Static)
- /// <summary>
- /// Last warning message we want to display in Scene.Draw, usually not
- /// used, only if something bad happened. Set here, used there.
- /// </summary>
- public static string LastWarningMessage = "";
- #endregion
-
- #region LastWarningTime (Static)
- /// <summary>
- /// Helpers to display warnings in Scene.Draw, will be reduced there.
- /// Set here, used there (thus public access is needed).
- /// </summary>
- public static long LastWarningTime = -10;
- #endregion
-
- #region LogName (Static)
- /// <summary>
- /// Current log name, initially "Engine" until Application.Start is
- /// called, then it will be the current game, unit test or tool name.
- /// </summary>
- public static string LogName
- {
- get
- {
- return logName;
- }
- internal set
- {
- // Check if the new value is valid
- if (String.IsNullOrEmpty(value))
- {
- throw new ArgumentNullException(
- "Invalid ProgramName for the log filename, please set a valid " +
- "program name!");
- }
-
- // No change? Then just quit here (no need to do all the checking)
- if (logName == value)
- {
- return;
- }
-
- // If we have already created a log file, we need to close it and
- // create a new log file. This happens in case we had initialization
- // log warnings (in the engine usually because loading content,
- // rendering and game logic is all delayed until the app has started).
- if (currentLogInstance != null &&
- currentLogInstance.logFile != null)
- {
- // Close the old log file.
- if (currentLogInstance.logFile.BaseStream != null)
- {
- currentLogInstance.logFile.Close();
- }
- currentLogInstance.logFile = null;
-
- // In debug mode we launch the log file that has been created
- // because there most likely are warnings or information the
- // programmer wants to know about (we have a zero logging policy)
- remToLaunchEngineLogAtEndOfProgram = currentLogInstance.Filename;
- }
-
- // Set the new programName, if a log is needed, it will be created!
- logName = value;
-
- // Make sure the name does not contain any invalid characters
- StringHelper.RemoveAnyForbiddenLettersForFilename(ref logName);
-
- // Because our programName has changed, we need to check if we
- // should archive the programName.log file!
- if (currentLogInstance != null)
- {
- currentLogInstance.ArchiveLogFile();
- }
- }
- }
- #endregion
-
- #region ExtraHardwareInfo (Static)
- /// <summary>
- /// Platform modules have the ability to add extra hardware information
- /// we use the DeviceHardware class to show more information about the
- /// </summary>
- public static string ExtraHardwareInfo = "";
- #endregion
-
- #region Filename (Public)
- /// <summary>
- /// Returns the complete file name of the current log file.
- /// </summary>
- public string Filename
- {
- get
- {
- return Path.Combine(
- Path.Combine(DirectoryHelper.GetBaseDirectory(), Directory),
- LogName + "." + FileExtension);
- }
- }
- #endregion
-
- #region ArchivedLogFilename (Public)
- /// <summary>
- /// Archived log filename
- /// </summary>
- public string ArchivedLogFilename
- {
- get
- {
- return Path.Combine(
- Path.Combine(DirectoryHelper.GetBaseDirectory(), Directory),
- LogName + "-Archive." + FileExtension);
- }
- }
- #endregion
-
- #region Internal
-
- #region Directory (Internal)
- /// <summary>
- /// By default no log directory is used, all logs end up in the application
- /// directory (named by the program or unit test that was started).
- /// Note: This is just a directory, no filename should be specified here.
- /// Also note that this must be set before the actual logging happens,
- /// so in case you first have some log warnings and then load the settings
- /// the initial log directory is not set.
- /// </summary>
- internal static string Directory = "";
- #endregion
-
- #region LogHeader (Internal)
- /// <summary>
- /// The header information of our log.
- /// </summary>
- internal static string LogHeader
- {
- get
- {
- Version programVersion = new Version(1, 0);
- try
- {
- var programAssembly = Assembly.GetEntryAssembly();
- // If getting the programAssembly is not possible (e.g. in the
- // SampleBrowser it will return null), then use the current one.
- if (programAssembly == null)
- {
- programAssembly = Assembly.GetCallingAssembly();
- }
- programVersion = programAssembly.GetName().Version;
- }
- // Ignore if this does not work (e.g. unit test)
- catch
- {
- }
-
- return
- "Program: " + LogName + " v" + programVersion +
- Environment.NewLine +
- "Modules: " + loadedModules +
- Environment.NewLine +
- "Start Time: " + applicationStartTime.GetIsoDateTime() +
- Environment.NewLine +
- "Platform: " + GetPlatformInfo() + ExtraHardwareInfo +
- Environment.NewLine +
- "User: " + Environment.UserName +
- " (" + Environment.MachineName + ")" +
- Environment.NewLine;
- }
- }
- #endregion
-
- #region loadedModules (Internal)
- /// <summary>
- /// This is also set in DynamicBase and will obviously only report
- /// the loaded modules if they have been set (Graphics, Sound, Video).
- /// </summary>
- internal static string loadedModules = "None";
- #endregion
-
- #region logTimer (Internal)
- /// <summary>
- /// Use timer to have more accurate timing (uses PerformanceCounters
- /// internally and can return accurate millisecond values for many million
- /// years due the use of long integer values). Usually setup in the Time
- /// class, but if that is not used or was not called before logging
- /// happens we will initialize this on our own with a simple
- /// StopwatchTimeSource and update it ourself.
- /// </summary>
- internal static BaseTimeSource logTimer;
- #endregion
-
- #region allowDuplicateLogChecking (Internal)
- /// <summary>
- /// By default Allow Duplicate Log Checking. This speeds up console writing
- /// by a lot (skips messages if too much is going on, making logging
- /// 10-100x faster). It also checks for duplicate messages written into
- /// the log file (keeping the log file much smaller).
- /// <para />
- /// But it can be useful if you really want every single log message in
- /// the console and log file including duplicates, then disable this
- /// feature (via Application Settings or by setting this to false).
- /// </summary>
- internal static bool allowDuplicateLogChecking = true;
- #endregion
-
- #endregion
-
- #region Private
-
- #region logName (Private)
- /// <summary>
- /// Current log name, will be "Engine" until Application.Start is
- /// called, then it will be the current game, unit test or tool name.
- /// </summary>
- private static string logName = DefaultLogName;
- #endregion
-
- #region remToLaunchEngineLogAtEndOfProgram (Private)
- /// <summary>
- /// Extra helper to show even the Engine.txt file when closing the
- /// program in case there were Engine warnings we need to know about!
- /// </summary>
- private static string remToLaunchEngineLogAtEndOfProgram = "";
- #endregion
-
- #region isLogFileCreatable (Private)
- /// <summary>
- /// Can the log file be created, helps us to remember if we have already
- /// tried to create a log file and was it successful.
- /// </summary>
- private bool isLogFileCreatable;
- #endregion
-
- #region remOurSetupLogTimer (Private)
- /// <summary>
- /// Helper to remember which timer we have setup (if we have done so at
- /// all). If this is null or not equal to logTimer than the engine has
- /// changed this and is now started. It is save to use the retrying
- /// connection feature of the Client class if so because Close is
- /// called by force when the application quits (otherwise not).
- /// </summary>
- private static BaseTimeSource remOurSetupLogTimer;
- #endregion
-
- #region logFile (Private)
- /// <summary>
- /// The handle to our (local) log file.
- /// </summary>
- private StreamWriter logFile;
- #endregion
-
- #region currentLogInstance (Private)
- /// <summary>
- /// Current log instance.
- /// </summary>
- private static Log currentLogInstance;
- #endregion
-
- /// <summary>
- /// Remember if the log creation in Instance was done from a test runner.
- /// </summary>
- private static bool rememberLogWasCreatedFromNunitTestRunner = false;
- /// <summary>
- /// Helper method to find out if the log instance was created by the NUnit
- /// test runner or by ReSharpers Unit Test Sessions. This is important
- /// because if the log class is being disposed by a finalizer (e.g. from
- /// WPF) and we need to show the log file in a text editor it will crash
- /// with an System.Runtime.InteropServices.InvalidComObjectException: COM
- /// object that has been separated from its underlying RCW cannot be used.
- /// We can prevent this from happening by setting this to true in the
- /// Instance property. Since we see the output in the test runner anyway
- /// opening the text editor is not important anyway.
- /// </summary>
- private static bool CheckIfLogWasCreatedFromNunitTestRunner()
- {
- if (rememberLogWasCreatedFromNunitTestRunner)
- {
- return true;
- }
-
- // Check if we are coming from NUnit or ReSharper test runner. Note: If
- // we only get a Finalize stack frame (4 calls long) this won't work!
- foreach (StackFrame frame in new StackTrace().GetFrames())
- {
- Type frameClass = frame.GetMethod().DeclaringType;
- if (frameClass.Name.StartsWith("NUnit") ||
- frameClass.Name.StartsWith("ReSharper"))
- {
- rememberLogWasCreatedFromNunitTestRunner = true;
- return true;
- }
- }
-
- // Otherwise nothing was found, return false
- return false;
- }
-
- #region Instance (Private)
- /// <summary>
- /// Current log instance we are using, if there is no instance, we will
- /// create one with the current program name (defaults to Engine.txt).
- /// </summary>
- private static Log Instance
- {
- get
- {
- // Got no log instance yet? Then create it ourselves.
- if (currentLogInstance == null)
- {
- currentLogInstance = new Log();
- CheckIfLogWasCreatedFromNunitTestRunner();
- }
- // But usually it is set in the constructor anyway!
- return currentLogInstance;
- }
- }
- #endregion
-
- #region lastDiscardedConsoleMessage (Private)
- /// <summary>
- /// Remember the last discarded console message. Show it when the app
- /// quits. It often contains interesting information about performance.
- /// </summary>
- private static string lastDiscardedConsoleMessage = "";
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create log
- /// </summary>
- public Log()
- {
- // Start our own time counting if it has not been setup yet, usually
- // the Time module from the Delta project should have set this up!
- if (logTimer == null)
- {
- remOurSetupLogTimer = logTimer = new StopwatchTimeSource();
- }
-
- // We assume always that we can create a log file. For some platforms
- // application directory, so do not create a log file there (just use
- // LogService).
- isLogFileCreatable = true;
-
- // From now on static methods can use this log instance :)
- currentLogInstance = this;
-
- // Archive always the last log file (if exists) to have a completely
- // fresh new log for every new session
- ArchiveLogFile();
- }
- #endregion
-
- #region Destructor
- /// <summary>
- /// ~Log destructor, just calls Dispose, which calls Close.
- /// Note: Not often helpful because this is only called when everything
- /// else was killed and this only cleans up after all threads and objects
- /// are gone anyway.
- /// </summary>
- ~Log()
- {
- Dispose();
- }
- #endregion
-
- #region IDisposable Members
- /// <summary>
- /// Dispose, just calls Close to make sure we dispose stuff if the
- /// application is quitting (either forcefully or normally via
- /// Application.Quit).
- /// </summary>
- public void Dispose()
- {
- Close();
- }
- #endregion
-
- #region ArchiveLogFile (Public)
- /// <summary>
- /// Archives a log file to an existing log-file-archive or creates a new
- /// log-file-archive, if the archive-file doesn't exist yet. However in any
- /// case the current log file will be deleted after processing to the
- /// archive. This is used for in the LogService too.
- /// </summary>
- public void ArchiveLogFile()
- {
- // Log file that should be archived is Filename (current log file)
- // Filename of the (new/existing) log-archive: ArchivedLogFilename
- // We only need to archive an existing log file
- if (FileHelper.Exists(Filename) == false)
- {
- return;
- }
-
- // Grab all the logged text from last session log for adding that to the
- // log archive file
- string appendLogText = FileHelper.GetText(Filename);
-
- // If the archive file already exists
- if (FileHelper.Exists(ArchivedLogFilename))
- {
- // Add some empty lines
- appendLogText = Environment.NewLine +
- Environment.NewLine +
- // Before the real text to separate this in log sessions
- appendLogText;
- }
-
- try
- {
- // Finally we write this to the archive file. This file will be created
- // automatically if it does not exist yet (only for the first time)
- FileHelper.AppendTextToFile(ArchivedLogFilename, appendLogText,
- // If the existing data is more than 20 MB already, remove data from
- // the beginning and then append the appendLogText.
- MaxArchiveLogFileSize);
- }
- catch
- {
- // We should not use Info or Warning here, the log file has not
- // been deleted yet, so only warn in the console if appending failed.
- Console.WriteLine("Warning: Appending to the log archive failed: " +
- ArchivedLogFilename);
- }
-
- // And delete the original file
- FileHelper.SafeDelete(Filename);
- }
- #endregion
-
- #region Methods (Private)
-
- #region GetPlatformInfo
- /// <summary>
- /// Get platform info helper method for LogHeader to output some useful
- /// information about the currently running platform (especially nice for
- /// the log server, which shows this info in the logs too).
- /// </summary>
- private static string GetPlatformInfo()
- {
- string platform = "Windows";
-
- // Add some extra information about the environment (might not be
- // complete on all platforms, but on Windows this shows good stuff)
- platform +=
- ", " + Environment.ProcessorCount + " CPU Cores, " +
- // This functionality is only available on Windows!
- (Environment.Is64BitProcess
- ? "64 bit"
- : Environment.Is64BitOperatingSystem
- ? "32 bit (on 64 bit OS)"
- : "32 bit") + ", " +
- // This shows lots of detailed information about the OS version,
- // e.g. Windows NT 6.1.7600.0 means Windows 7, 7600 is the SP!
- Environment.OSVersion.VersionString.
- Replace("Microsoft ", "").
- Replace("Windows NT 6.1.", "Windows 7 v").
- Replace("Windows NT 6.0.", "Windows Vista v");
-
- return platform;
- }
- #endregion
-
- #region CreateLogFile
- /// <summary>
- /// Create log file
- /// </summary>
- /// <param name="logFilename">Log filename</param>
- private bool CreateLogFile(string logFilename)
- {
- #region Validation
- if (String.IsNullOrEmpty(logFilename))
- {
- // If the filename is not valid, this is a fatal exception error!
- throw new ArgumentException("Sorry, can't create a log file without " +
- "a valid filename.", "logFilename");
- }
-
- if (isLogFileCreatable == false)
- {
- // Build error message and write it into the console at least
- string errorMessage = FormatMessage(
- "Sorry, can't create a log file, because an earlier try failed " +
- "already. Maybe you haven't the right to create files here?");
- Console.WriteLine(errorMessage);
- // If we can't create a log file, then log it to the log server
- if (LogHappened != null)
- {
- LogHappened(errorMessage);
- }
- return false;
- }
- #endregion
-
- try
- {
- // We always create a new log file (the old one will be archived
- // automatically at the program start)
- FileStream logFileStream = FileHelper.Open(logFilename,
- FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
- // for the log text we use here the 8-bit unicode
- logFile = new StreamWriter(logFileStream, TextFormat);
-
- // Activate the "Auto Flush" (to be always "up-to-date" for reading)
- logFile.AutoFlush = true;
-
- // and start the log file with the header
- logFile.WriteLine(LogHeader);
- }
- catch (Exception ex)
- {
- isLogFileCreatable = false;
- logFile = null;
-
- // Build error message and write it into the console at least
- string errorMessage = FormatMessage(
- "Sorry, can't create a log file because: " + ex);
- Console.WriteLine(errorMessage);
- // And finally send it to the log server (if connected and possible)!
- if (LogHappened != null)
- {
- LogHappened(errorMessage);
- }
- }
-
- return logFile != null;
- }
- #endregion
-
- #region CheckDuplicateMessage
- /// <summary>
- /// Returns true if we should skip this message in case we are logging it
- /// over and over again (prevent log spam). Make sure to call this BEFORE
- /// adding the time stamp, which most likely will make every message
- /// unique and prevents us from detecting log spam.
- /// </summary>
- /// <param name="message">Message</param>
- private static bool CheckDuplicateMessage(string message)
- {
- if (allowDuplicateLogChecking == false)
- {
- return false;
- }
-
- // We need to keep this thread safe!
- lock (lastLoggedMessages)
- {
- // Was this logged before? Note: This will need some performance, more
- // if we need to check many messages, so keep lastLoggedMessages low.
- if (lastLoggedMessages.Contains(message))
- {
- // Then abort, do not log it again, it costs too much performance!
- return true;
- }
-
- // Else add it to our queue and check for it next time (and log it).
- lastLoggedMessages.Enqueue(message);
- }
-
- return false;
- }
- #endregion
-
- #region ToFile
- /// <summary>
- /// To file
- /// </summary>
- /// <param name="message">Message</param>
- private void ToFile(string message)
- {
- // If we don't have a log file created yet
- if ((logFile == null ||
- logFile.BaseStream == null) &&
- isLogFileCreatable)
- {
- // Then just try to do it now
- CreateLogFile(Filename);
- }
-
- // Obviously only log if we have a logFile
- if (logFile != null)
- {
- // Keep this thread safe!
- lock (logFile)
- {
- // First just write to the log file
- logFile.WriteLine(message);
-
- // Is the log file still above 10 MB now?
- if (logFile.BaseStream.Length > MaxLogFileSize)
- {
- try
- {
- // Remove the last 5 MB and start from there again!
- // For that just close the file, copy half the content to a new
- // file and then open that file again to continue logging!
- logFile.Close();
- string[] logLines = FileHelper.GetLines(Filename);
- // Create new log file with half the size
- FileStream logFileStream = FileHelper.Open(Filename,
- FileMode.Truncate, FileAccess.Write, FileShare.ReadWrite);
- // for the log text we use here the 8-bit unicode
- logFile = new StreamWriter(logFileStream, TextFormat);
- // Activate AutoFlush to be always up-to-date for reading
- logFile.AutoFlush = true;
- // and dump out half of the previous log lines!
- for (int line = 0; line < logLines.Length / 2; line++)
- {
- logFile.WriteLine(logLines[line]);
- }
-
- // Add a note that the file was too big and 5 MB of log data
- // was removed!
- logFile.WriteLine("");
- logFile.WriteLine("****************************");
- logFile.WriteLine("Log file was exceeding " +
- StringHelper.WriteKbMbGbNumber(MaxLogFileSize) +
- ", the " +
- "last " +
- StringHelper.WriteKbMbGbNumber(MaxLogFileSize / 2) +
- " were removed and logging will continue here!");
- logFile.WriteLine("****************************");
- logFile.WriteLine("");
- // And log again what we last tried to log
- logFile.WriteLine(message);
- }
- catch (Exception ex)
- {
- // Note: We cannot display a dialog here, no assembly, we can
- // only spam into the log, which won't be open anyway for that
- // much logging (its too slow in Visual Studio).
- ToConsole("Failed to resize log file, it won't log anymore: " +
- ex, true);
- isLogFileCreatable = false;
- logFile = null;
- }
- }
- }
- }
- }
- #endregion
-
- #region ToConsole
- /// <summary>
- /// Print to console, which is horribly slow in Visual Studio, but still
- /// very useful for debugging. Instead of logging everything to the
- /// console we gonna limit the amount of console writing. Usually we
- /// should have less than 10 message lines per second (we don't want to
- /// throw any warnings, informational logs or even test messages in
- /// a normal application run, it should have zero logging!)
- /// For additional details about bad performance of Debug.WriteLine see:
- /// http://rdn-consulting.com/blog/2008/05/28/net-consolewriteline-performance-issues/
- /// Also see the below unit test, which is horribly slow: 6ms for 30 chars
- /// </summary>
- /// <param name="message">Message</param>
- /// <param name="checkForDuplicates">Check for Duplicates</param>
- private static void ToConsole(string message, bool checkForDuplicates)
- {
- // First check if we have already logged more than 10 messages in the
- // last second, skip any new console writes until a second has passed.
- if (allowDuplicateLogChecking &&
- checkForDuplicates &&
- logTimer != null)
- {
- long thisTime = logTimer.ElapsedMilliseconds;
- if (lastConsoleWriteTimes.Count == MaxConsoleWritesPerSecond)
- {
- long longestTime = lastConsoleWriteTimes.Peek();
- // Abort if less than 1000ms have passed since 10 console writes ago
- if (thisTime - longestTime < 1000)
- {
- // Remember this message in case the application quits now, it
- // would be really useful to show the last message, it often
- // contains interesting information about performance, etc.
- lastDiscardedConsoleMessage = message;
- return;
- }
- }
-
- // Add current logging time at the end of the queue
- lastConsoleWriteTimes.Enqueue(thisTime);
- // And kill the lastDiscardedLogMessage, we are logging now!
- lastDiscardedConsoleMessage = "";
- }
-
- // Just pass the message directly to the IDE console. This is still slow
- // as hell, especially if debugging with the debug window open.
- // Might be a little faster with Debug.WriteLine, but we won't see it
- // in release mode. Also useful for whatever is listing at the console.
- Console.WriteLine(message);
- }
- #endregion
-
- #region FormatMessage
- /// <summary>
- /// Format message for use with the log methods (log server, file, console)
- /// </summary>
- /// <param name="rawMessage">Raw message</param>
- private static string FormatMessage(string rawMessage)
- {
- // Make sure the instance has been created, otherwise we have no timer!
- if (Instance != null &&
- logTimer == null)
- {
- return rawMessage;
- }
-
- // Note: In threading situations this can sometimes crash ..
- try
- {
- return logTimer.ToTimeString(' ', rawMessage);
- }
- catch
- {
- return rawMessage;
- }
- }
- #endregion
-
- #region WriteInfo
- /// <summary>
- /// Write information string to the log, the console and to the
- /// DeltaEngin.net server. For details see the static Info method.
- /// </summary>
- /// <param name="message">Message to show in the log and console</param>
- /// <param name="checkForDuplicates">Check if this message already was
- /// logged in the last 10 messages, then skip it to prevent log spam.
- /// This is on by default.
- /// </param>
- private void WriteInfo(string message, bool checkForDuplicates)
- {
- // Skip message if we just logging it over and over again (log spam)
- if (message == null ||
- (checkForDuplicates &&
- CheckDuplicateMessage(message)))
- {
- return;
- }
-
- // Format the message with a time stamp and whatever is needed
- string formattedMessage = FormatMessage(message);
- // Log the formatted message to the console first
- ToConsole(formattedMessage, checkForDuplicates);
- try
- {
- // Also send the message to all attached handlers like the log server
- if (LogHappened != null)
- {
- LogHappened(formattedMessage);
- }
- // And then finally to the current log file
- ToFile(formattedMessage);
- }
- catch (Exception ex)
- {
- // Note: The formattedMessage text was just put into the console!
- Console.WriteLine("Failed to log this info to file: " + ex);
- }
- }
- #endregion
-
- #region WriteWarning
- /// <summary>
- /// Warning, thought for logging "light errors" which can be handled itself
- /// by the application. For details see the static Warning method!
- /// </summary>
- /// <param name="message">Warning to show in the log and console</param>
- /// <param name="showStackTrace">Show Stack Trace</param>
- private void WriteWarning(string message, bool showStackTrace)
- {
- string[] messageLines = message.SplitMultiLineText();
- message = "";
- bool gotAlreadyStackTrace = false;
- foreach (string line in messageLines)
- {
- // .NET exception stack traces are always formated this way
- if (line.Trim().StartsWith("at ") ||
- line.Trim().StartsWith("bei ") ||
- line.Contains(" Delta."))
- {
- // Fix exception trace by putting file:line at front (to allow
- // clicking on it in VS).
-
- // First ignore the lines starting with TestRunner or VS process
- // starting stuff (see below):
- if (line.Contains(new[]
- {
- "System.RuntimeMethodHandle", "System.Reflection",
- "TestDriven", "System.Threading", "System.AppDomain",
- "System.Activator", "System.Runtime", "System.Windows.",
- "Delta.Utilities.Testing", "Microsoft.VisualStudio.HostingProcess",
- // Also exclude NUnit, xUnit and ReSharper stack traces
- "NUnit.Core.", "xUnit.", "JetBrains.ReSharper.",
- }))
- {
- continue;
- }
-
- // Okay, now split up the 5 parts: at method in file line
- string result = line.Trim();
- string trimmedLine = StringHelper.RemoveFirstWord(result);
- string[] methodAndRest = trimmedLine.Split(" in ");
- if (methodAndRest.Length == 2)
- {
- string[] fileAndLine = methodAndRest[1].Split(":line ");
- if (fileAndLine.Length == 2)
- {
- result = fileAndLine[0] + "(" + fileAndLine[1] + ",1): at " +
- methodAndRest[0];
- }
- }
- // Output result (reformatted stack trace line)
- message +=
- (message.Length > 0
- ? "\n"
- : "") + "\t" + result;
-
- gotAlreadyStackTrace = true;
- }
- else
- {
- message +=
- (message.Length > 0
- ? "\n"
- : "") + line;
- }
- }
-
- if (gotAlreadyStackTrace == false &&
- showStackTrace)
- {
- // Always show a callstack for warnings to help figure out who called
- // this method and it might (not always of course) help :) But remove
- // the first callstack line (just Log.Warning, we know that we are
- // here). Also remove the last 12 lines for generic
- // TestDriven.TestRunner or Launcher stuff:
- // System.RuntimeMethodHandle._InvokeMethodFast(...)
- // System.RuntimeMethodHandle.InvokeMethodFast(...)
- // System.Reflection.RuntimeMethodInfo.Invoke(...)
- // System.Reflection.RuntimeMethodInfo.Invoke(...)
- // System.Reflection.MethodBase.Invoke(...)
- // TestDriven.AdHoc.TestRunner.AdHocTestRunner.runAdHoc(...)
- // TestDriven.AdHoc.TestRunner.AdHocTestRunner.Run(...)
- // TestDriven.TestRunner.AdaptorTestRunner.Run(...)
- // TestDriven.TestRunner.ThreadTestRunner.Runner.Run()
- // System.Threading.ThreadHelper.ThreadStart_Context(...)
- // System.Threading.ExecutionContext.Run(...)
- // System.Threading.ThreadHelper.ThreadStart()
-
- // true for file and line info!
- StackTrace trace = new StackTrace(true);
- // This code is from Tools\TestRunner\UnitTestManager.cs:479
- StackFrame[] frames = trace.GetFrames();
- int frameNumber = 0;
- string callstackLines = "";
- foreach (var frame in frames)
- {
- frameNumber++;
-
- // Ignore the first two lines: this method and its caller
- // Log.Warning, both are not very exciting and always the same.
- if (frameNumber <= 2)
- {
- continue;
- }
-
- var method = frame.GetMethod();
- // Also exclude TestRunner code and boring invoking stuff
- if (method.DeclaringType.FullName.Contains(new[]
- {
- "System.RuntimeMethodHandle", "System.Reflection",
- "TestDriven", "System.Threading", "System.AppDomain",
- "System.Activator", "System.Runtime", "Delta.Utilities.Testing",
- "Microsoft.VisualStudio.HostingProcess", "System.Windows.",
- "MS.Win32.", "MS.Internal.",
- // Also exclude NUnit, xUnit and ReSharper stack traces
- "NUnit.Core.", "xUnit.", "JetBrains.ReSharper.",
- }))
- {
- continue;
- }
-
- // Try to get parameters with detailed value info
- string parametersText = "";
- var parameters = method.GetParameters();
- foreach (var parameter in parameters)
- {
- parametersText +=
- (parametersText.Length > 0
- ? ", "
- : "") +
- parameter.ParameterType + " " + parameter.Name;
- }
-
- // Try to get filename with line and column information
- string filenameAndLine =
- frame.GetFileName() + "(" +
- frame.GetFileLineNumber() + "," +
- frame.GetFileColumnNumber() + "): ";
- // But cut it if we do not have it (e.g. system classes)
- if (filenameAndLine == "(0,0): ")
- {
- filenameAndLine = "";
- }
-
- callstackLines +=
- "\t" +
- // This is not working, projectPath is relative to the assembly
- // we are investigating, but not the project VS is running
- // sometimes
- //FileHelper.GetRelativeContentFilePath(
- //frame.GetFileName(), projectPath) + "(" +
- filenameAndLine +
- // Class and name should be useful enough :)
- "at " + method.DeclaringType + "." + method.Name +
- "(" + parametersText + ")\n";
-
- // Abort in release mode for TypeListManager warnings!
- } // foreach
-
- if (String.IsNullOrEmpty(callstackLines) == false)
- {
- message += "\n" + callstackLines;
- }
- } // if
-
- // Skip message if we just logging it over and over again (log spam)
- // Note: This will only skip messages that have been written exactly
- // in this same way (full warning with stack trace).
- if (CheckDuplicateMessage(message))
- {
- return;
- }
-
- // Format the message with a time stamp and whatever is needed
- string formattedMessage = FormatMessage(
- // Only add "Warning: " if this is not already a fatal error.
- message.StartsWith("Error: ")
- ? message
- : "Warning: " + message);
- // Log the formatted message to the console first
- ToConsole(formattedMessage, true);
- try
- {
- // Also send the message to all attached handlers like the log server
- if (LogHappened != null)
- {
- LogHappened(formattedMessage);
- }
- // And then finally to the current log file
- ToFile(formattedMessage);
- }
- catch (Exception ex)
- {
- // Note: The formattedMessage text was just put into the console!
- Console.WriteLine("Failed to log this warning to file: " + ex);
- }
- }
- #endregion
-
- #endregion
-
- /// <summary>
- /// Tests
- /// </summary>
- [NUnit.Framework.Category("LongRunning")]
- internal class LogTests
- {
- #region TestLogging (Static)
- /// <summary>
- /// Test logging
- /// </summary>
- [Test]
- public static void TestLogging()
- {
- Info("Just some boring info line...");
-
- // Open the text editor and make sure the log is sent to the server.
- // Note: If we can connect to the log server quick enough this line
- // is not required, but if we timeout or the connection is slow, this
- // is much better and consistent to test. Also good for opening editor!
- Close();
- }
- #endregion
-
- #region TestConsoleLog (Static)
- /// <summary>
- /// Test console log
- /// </summary>
- [Test]
- public static void TestConsoleLog()
- {
- // Just printing to the console to see how a log would looks like
- Console.WriteLine(Log.LogHeader);
- Test("Just some boring test line...");
- Console.WriteLine("Current directory: " +
- System.IO.Directory.GetCurrentDirectory());
- //Console.WriteLine("Does " + Log.Instance.Filename + " exists: " +
- // File.Exists(Log.Instance.Filename));
- }
- #endregion
-
- #region ConsoleWriteLinePerformance (Static)
- /// <summary>
- /// ConsoleWriteLinePerformance
- /// </summary>
- [Test]
- public static void ConsoleWriteLinePerformance()
- {
- // Simple Log.Test performance test
- Stopwatch timer = new Stopwatch();
- timer.Start();
-
- // Note: Makes no difference if using 1000 messages with 1 line each or
- // 100 messages with 10 lines, both take 5.8s to execute, which is
- // ridiculous. Per line that is 6ms for 30 characters, wtf?
- const int NumOfWriteLines = 1000;
- for (int i = 0; i < NumOfWriteLines; i++)
- {
- Test("Hi ho " + i);
- }
- // Note: About 6-7 times faster with shorter string (less than 1s):
- //Console.WriteLine("Just");
- //Console.WriteLine("Just some boring test line...");
- // Note: Same speed if doing 100 messages with 10 lines each:
- //Console.WriteLine("Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...\n"+
- // "Just some boring test line...");
-
- timer.Stop();
-
- Test("Writing took " + timer.ElapsedMilliseconds + "ms");
- }
- #endregion
-
- #region TestMassiveLogging2 (Static)
- /// <summary>
- /// Test massive logging 2
- /// </summary>
- [Test]
- public static void TestMassiveLogging2()
- {
- for (int i = 0; i < 50; i++)
- {
- Info("i'm message number " + i);
- }
- }
- #endregion
-
- #region TestInfoLogging (Static)
- /// <summary>
- /// Test logging
- /// </summary>
- [Test]
- public static void TestInfoLogging()
- {
- Info("Just some boring test line...");
- }
- #endregion
-
- #region TestWarningLogging (Static)
- /// <summary>
- /// TestWarningLogging
- /// </summary>
- [Test]
- public static void TestWarningLogging()
- {
- Warning("Just some boring warning...");
- }
- #endregion
-
- #region ArchiveLogFileWhileFileIsOpen (Static)
- /// <summary>
- /// Archive log file while file is open
- /// </summary>
- [Test]
- public static void ArchiveLogFileWhileFileIsOpen()
- {
- Log log = new Log(); //CreateTestLog("Test");
- LogName = "Test";
- // We must log something, else there might be no Test log file!
- log.WriteInfo("Hi log file", false);
- // Okay, archive that
- log.ArchiveLogFile();
- // Now open ArchiveLogFile again
- string archiveText = FileHelper.GetText("Test-Archive." +
- FileExtension);
- Assert.True(archiveText.Length > 0, "We must have some archiveText");
- // Still works, hmm, try to open file manually (but allow writing too)
- FileStream archiveStream = FileHelper.Open("Test-Archive." +
- FileExtension, FileMode.Open,
- FileAccess.Read, FileShare.ReadWrite);
- Assert.NotEqual(0, archiveStream.ReadByte());
- // And try to write it again, but close our archiveStream handle after!
- log.ArchiveLogFile();
- archiveStream.Close();
-
- // And finally restore the DefaultProgramName again
- logName = DefaultLogName;
- }
- #endregion
-
- #region CreateLogFile (Static)
- /// <summary>
- /// Create log file. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test]
- public static void CreateLogFile()
- {
- // At first we want to use an extra test log file to don't get in
- // conflict with the already existing real logs
- string testLogFilename = "CreateLogFile.tst";
-
- // At the beginning there shouldn't exist this file
- Assert.False(FileHelper.Exists(testLogFilename),
- "log file should exist");
-
- // Now we create the new (test)log file
- Log log = new Log(); //CreateTestLog("tst");
- LogName = "tst";
- Assert.True(log.CreateLogFile(testLogFilename), "log creation");
- // and expect that the file created successfully too
- Assert.True(FileHelper.Exists(testLogFilename),
- "log file should exist");
-
- // Finally cleanup our test log file. We have to dispose the log
- // handle explicitly, so we don't open the log in a text editor ^^
- log.logFile.Dispose();
- log.logFile = null;
- FileHelper.SafeDelete(testLogFilename);
-
- // And finally restore the DefaultProgramName again
- logName = DefaultLogName;
- }
- #endregion
-
- #region TestLogHeader (Static)
- /// <summary>
- /// Log header
- /// </summary>
- [Test]
- public static void TestLogHeader()
- {
- Assert.True(Log.LogHeader.Contains(
- "Program: Engine"));
- Assert.True(Log.LogHeader.Contains(
- "Modules: None"));
- Assert.True(Log.LogHeader.Contains(
- "Start Time: " + DateTime.Now.GetIsoDateTime()));
- Assert.True(Log.LogHeader.Contains(
- "User: " + Environment.UserName +
- " (" + Environment.MachineName + ")"));
- }
- #endregion
-
- #region LogToFile (Static)
- /// <summary>
- /// Log to file. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test]
- public static void LogToFile()
- {
- // We want to use an extra test log file to don't get in conflict with
- // the already existing real logs
- Log log = new Log();
- LogName = "LogToFile";
- // and just make a sanity check that the file doesn't exists
- Assert.False(FileHelper.Exists(log.Filename),
- "log file shouldn't exist");
-
- // Now we log something to the file
- string testLogMessage = "Hellooo unit test world";
- log.ToFile(testLogMessage);
-
- // and expect that the file was created (inclusive our log message)
- Assert.True(FileHelper.Exists(log.Filename), "log file should exist");
-
- try
- {
- // Make sure we got no logFile anymore. Close the log file handle,
- // so that we have the rights to read the file and stop writing.
- log.logFile.Dispose();
- log.logFile = null;
-
- // Look if our test log was really written
- string expectedLogFileText =
- Log.LogHeader + Environment.NewLine +
- testLogMessage + Environment.NewLine;
- //does not work because we have time now in the header:
- //Assert.Equal(expectedLogFileText, FileHelper.GetText(log.Filename));
- }
- finally
- {
- // We always make sure that we cleanup our testing stuff again
- FileHelper.SafeDelete(log.Filename);
- }
-
- // And finally restore the DefaultProgramName again
- logName = DefaultLogName;
- }
- #endregion
-
- #region ArchiveLogFile (Static)
- /// <summary>
- /// Archive log file
- /// </summary>
- [Test]
- public static void ArchiveLogFile()
- {
- // We want to use an extra test log file to don't get in conflict with the
- // already existing real logs
- Log log = new Log(); //CreateTestLog("ArchiveLogFile");
- LogName = "ArchiveLogFile";
-
- // and just make a sanity check that the file doesn't exists
- Assert.False(FileHelper.Exists(log.Filename), "log file should exist");
-
- // Just make a unimportant log
- string firstLogText = "Helloo...";
- string firstLogHeader = Log.LogHeader;
- log.ToFile(firstLogText);
-
- // Now we have to "simulate" an exit and restart the program
- // -> Close the log file (handle) again, so that we have the rights to
- // read the file
- log.logFile.Dispose();
- log.logFile = null;
-
- // For the first time there shouldn't exist the archive file
- Assert.False(FileHelper.Exists(log.ArchivedLogFilename),
- "archived log file should not exist yet");
- log.ArchiveLogFile();
- // and after completing archive it should
- Assert.True(FileHelper.Exists(log.ArchivedLogFilename),
- "archived log file should exist");
- try
- {
- // but now the "original" log don't
- Assert.False(FileHelper.Exists(log.Filename),
- "log file should not exist");
-
- // Now we re-spam to the log again
- string secondLogText = "Wo... Unit-Test ^^";
- string secondLogHeader = Log.LogHeader;
- log.ToFile(secondLogText);
-
- // and "simulate" an exit again too
- log.logFile.Dispose();
- log.logFile = null;
-
- // to call the archive function a second time
- log.ArchiveLogFile();
-
- // Finally we look if our complete log information was archived
- // correctly
- string archivedText =
- firstLogHeader + Environment.NewLine +
- firstLogText + Environment.NewLine +
- Environment.NewLine +
- Environment.NewLine +
- secondLogHeader + Environment.NewLine +
- secondLogText + Environment.NewLine;
- //does not work because we have time now in the header:
- //Assert.Equal(archivedText,
- // FileHelper.GetText(log.ArchivedLogFilename));
-
- // Just for fun we make a sanity check if the original log file was
- // deleted
- Assert.False(FileHelper.Exists(log.Filename),
- "log file should not longer exist");
- }
- finally
- {
- // At the end make sure that we cleanup our testing file stuff again
- // -> Original log file (shouldn't be normally executed here)
- FileHelper.SafeDelete(log.Filename);
- // -> and our archived log
- FileHelper.SafeDelete(log.ArchivedLogFilename);
- }
-
- // And finally restore the DefaultProgramName again
- logName = DefaultLogName;
- }
- #endregion
-
- #region MultipleLogFileAccess (Static)
- /// <summary>
- /// Multiple log file access. Will open Notepad.exe and wait until the
- /// user closes it again before this test finishes. Note: If the user
- /// uses another text editor it might return right away and the test
- /// finishes after 100-200ms.
- /// </summary>
- [Test, NUnit.Framework.Category("Visual")]
- public static void MultipleLogFileAccess()
- {
- Console.WriteLine(
- "CurrentPath=" + System.IO.Directory.GetCurrentDirectory());
-
- string testLogFile = "MultipleLogFileAccess.txt";
- FileHelper.CreateTextFile(testLogFile, "Test - MultipleLogFileAccess");
- Assert.True(FileHelper.Exists(testLogFile),
- "test log file should exist");
-
- // Open file in external program (Notepad if no other text editor is
- // linked up).
- Process notepad = Process.Start(testLogFile);
-
- try
- {
- // We create here at first a log with the default name to avoid the
- // automated archiving of current created log above
- Log log = new Log(); //CreateTestLog("tst");
- // Re-set the log filename to already opened one from the other
- // process
- LogName = "MultipleLogFileAccess";
- log.ToFile("ToFile: Test - MultipleLogFileAccess");
-
- // Wait until we have closed the notepad again
- notepad.WaitForExit();
-
- // to know when we can close the log file handle for final deleting
- log.logFile.Dispose();
- }
- finally
- {
- FileHelper.SafeDelete(testLogFile);
- }
-
- // And finally restore the DefaultProgramName again
- logName = DefaultLogName;
- }
- #endregion
-
- #region TestQueue (Static)
- /// <summary>
- /// Test queue
- /// </summary>
- [Test]
- public static void TestQueue()
- {
- Queue<string> testQueue = new Queue<string>();
- testQueue.Enqueue("First entry");
- testQueue.Enqueue("Second entry");
- testQueue.Enqueue("Third entry");
-
- int num = 0;
- while (testQueue.Count != 0)
- {
- Console.WriteLine("Process the " + (++num) + ". entry= " +
- testQueue.Dequeue());
- }
- }
- #endregion
- }
- }
- }