//******************************************************************************************************
// RunTimeLog.cs - Gbtc
//
// Copyright © 2010, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 06/23/2014 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Timers;
using Timer = System.Timers.Timer;
namespace GSF.IO
{
///
/// Represents a persisted run-time log that tracks last start, stop and running times.
///
public class RunTimeLog : ISupportLifecycle, IProvideStatus
{
#region [ Members ]
// Constants
///
/// Date-time format used by .
///
public const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fff";
///
/// Log file key name for last start time used by .
///
public const string LastStartTimeKey = "LastStartTime";
///
/// Log file key name for last stop time used by .
///
public const string LastStopTimeKey = "LastStopTime";
///
/// Log file key name for last running time used by .
///
public const string LastRunningTimeKey = "LastRunningTime";
// Events
///
/// Event is raised when there is an exception encountered while processing run-time log.
///
///
/// is the exception that was thrown.
///
public event EventHandler> ProcessException;
///
/// Raised after the run-time log has been properly disposed.
///
public event EventHandler Disposed;
// Fields
private readonly Timer m_flushTimer;
private readonly object m_readerWriterLock;
private string m_fileName;
private DateTimeOffset m_startTime;
private DateTimeOffset m_stopTime;
private DateTimeOffset m_runningTime;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new run-time log.
///
public RunTimeLog()
{
m_flushTimer = new Timer
{
AutoReset = true,
Interval = 10000.0D
};
m_flushTimer.Elapsed += m_flushTimer_Elapsed;
m_readerWriterLock = new object();
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~RunTimeLog()
{
Dispose(false);
}
#endregion
#region [ Properties ]
///
/// Gets or sets the file name for the run-time log; file name can be set with a relative path.
///
public string FileName
{
get
{
return m_fileName;
}
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(nameof(value));
m_fileName = FilePath.GetAbsolutePath(FilePath.GetValidFilePath(value));
}
}
///
/// Gets last known start-time.
///
public DateTimeOffset StartTime
{
get
{
return m_startTime;
}
set
{
m_startTime = value;
}
}
///
/// Gets last known stop-time.
///
public DateTimeOffset StopTime
{
get
{
return m_stopTime;
}
set
{
m_stopTime = value;
}
}
///
/// Gets last known running-time (10-second resolution).
///
public DateTimeOffset RunningTime
{
get
{
return m_runningTime;
}
set
{
m_runningTime = value;
}
}
///
/// Gets the up-time calculation for run-time log, i.e., - (10-second resolution).
///
public TimeSpan UpTime => m_runningTime - m_startTime;
///
/// Gets or sets a boolean value that indicates whether the run-time log is enabled.
///
///
/// This property controls the automatic write log timer.
///
public virtual bool Enabled
{
get
{
return m_flushTimer.Enabled;
}
set
{
m_flushTimer.Enabled = value;
}
}
///
/// Gets a flag that indicates whether the object has been disposed.
///
public bool IsDisposed => m_disposed;
// Gets the name of the run-time log
string IProvideStatus.Name => FilePath.GetFileNameWithoutExtension(m_fileName.ToNonNullString("undefined"));
///
/// Gets the current status details about .
///
public virtual string Status
{
get
{
StringBuilder status = new StringBuilder();
status.AppendFormat(" Run-time log file name: {0}", FilePath.TrimFileName(m_fileName.ToNonNullString("undefined"), 51));
status.AppendLine();
status.AppendFormat(" Auto-flush timer enabled: {0}", Enabled);
status.AppendLine();
status.AppendFormat(" Last start time: {0}", m_startTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture));
status.AppendLine();
status.AppendFormat(" Last stop time: {0}", m_stopTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture));
status.AppendLine();
status.AppendFormat(" Last running time: {0}", m_runningTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture));
status.AppendLine();
status.AppendFormat(" Total up time: {0}", UpTime.ToElapsedTimeString());
status.AppendLine();
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Releases all the resources used by the object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if ((object)m_flushTimer != null)
{
m_flushTimer.Stop();
m_flushTimer.Elapsed -= m_flushTimer_Elapsed;
m_flushTimer.Dispose();
}
}
lock (m_readerWriterLock)
{
m_stopTime = DateTime.UtcNow;
WriteLog();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
if ((object)Disposed != null)
Disposed(this, EventArgs.Empty);
}
}
}
///
/// Initialize the run-time log.
///
///
///
/// Initialization performs initial run-time log read, establishes new start time and enables automatic write log timer.
///
///
/// Last logged stop time will be validated against last logged running time. If the last logged running time is later
/// than the last logged stop time, the stop time will be set to the running time with the assumption that the log file
/// was not properly shut down (e.g., due to abnormal host termination).
///
///
/// It is important to separate initialization from construction such that consumer can attach to events before class
/// is initialized in case initialization causes events to be raised.
///
///
public virtual void Initialize()
{
if (string.IsNullOrWhiteSpace(m_fileName))
throw new NullReferenceException("No run-time log file name was specified");
ReadLog();
m_startTime = DateTime.UtcNow;
// Validate stop time - the point of keeping a running time is so that if host application fails to
// flush log during shutdown - the last known running time can become the assumed stop time.
if (m_runningTime > m_stopTime)
m_stopTime = m_runningTime;
if ((object)m_flushTimer != null && !m_flushTimer.Enabled)
m_flushTimer.Start();
}
///
/// Reads the run-time log.
///
protected void ReadLog()
{
try
{
if (string.IsNullOrWhiteSpace(m_fileName))
throw new NullReferenceException("No run-time log file name was specified");
lock (m_readerWriterLock)
{
if (File.Exists(m_fileName))
{
using (StreamReader reader = File.OpenText(m_fileName))
{
string fileData = reader.ReadToEnd();
if (fileData.Length > 0)
{
Dictionary settings = fileData.Replace(Environment.NewLine, ";").ParseKeyValuePairs();
string setting;
if (!settings.TryGetValue(LastStartTimeKey, out setting) || !DateTimeOffset.TryParseExact(setting.Trim(), DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowInnerWhite, out m_startTime))
m_startTime = DateTimeOffset.UtcNow;
if (!settings.TryGetValue(LastStopTimeKey, out setting) || !DateTimeOffset.TryParseExact(setting.Trim(), DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowInnerWhite, out m_stopTime))
m_stopTime = DateTimeOffset.UtcNow;
if (!settings.TryGetValue(LastRunningTimeKey, out setting) || !DateTimeOffset.TryParseExact(setting.Trim(), DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowInnerWhite, out m_runningTime))
m_runningTime = DateTimeOffset.UtcNow;
}
else
{
m_startTime = m_stopTime = m_runningTime = DateTimeOffset.UtcNow;
}
}
}
else
{
m_startTime = m_stopTime = m_runningTime = DateTimeOffset.UtcNow;
}
}
}
catch (Exception ex)
{
m_startTime = m_stopTime = m_runningTime = DateTimeOffset.UtcNow;
OnProcessException(new InvalidOperationException("Failed to read run-time log: " + ex.Message, ex));
}
}
///
/// Writes the run-time log - times are in a human readable format.
///
protected void WriteLog()
{
try
{
if (Monitor.TryEnter(m_readerWriterLock))
{
try
{
if (string.IsNullOrWhiteSpace(m_fileName))
throw new NullReferenceException("No run-time log file name was specified");
using (StreamWriter writer = File.CreateText(m_fileName))
{
writer.WriteLine("{0}={1}", LastStartTimeKey, m_startTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture));
writer.WriteLine("{0}={1}", LastStopTimeKey, m_stopTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture));
writer.WriteLine("{0}={1}", LastRunningTimeKey, m_runningTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture));
writer.Flush();
}
}
finally
{
Monitor.Exit(m_readerWriterLock);
}
}
}
catch (Exception ex)
{
OnProcessException(new InvalidOperationException("Failed to write run-time log: " + ex.Message, ex));
}
}
///
/// Raises event.
///
/// Processing .
protected virtual void OnProcessException(Exception ex)
{
if ((object)ProcessException != null)
ProcessException(this, new EventArgs(ex));
}
private void m_flushTimer_Elapsed(object sender, ElapsedEventArgs e)
{
m_runningTime = DateTimeOffset.UtcNow;
WriteLog();
}
#endregion
}
}