// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.IO;
using OpenTap.Diagnostic;
using System.Threading;
using System.Collections.Generic;
using System.Text;
namespace OpenTap
{
///
/// TraceListener to be used in the App.Config file of the executable to write trace/log data to
/// a file.
///
sealed class FileTraceListener : TextWriterTraceListener
{
string _fileName;
///
/// If the log should be written with absolute or relative time.
///
public bool IsRelative { get; set; }
/// The current file name of the trace file.
public string FileName => _fileName;
///
/// Initializes a new instance of the FileTraceListener class.
///
/// Name of the file to write to.
public FileTraceListener(string fileName)
: base(fileName)
{
_fileName = Path.GetFullPath(fileName);
}
public event EventHandler FileSizeLimitReached;
/// Installs a file limit. When the limit is reached FileSIzeLimitReached is invoked.
public ulong FileSizeLimit = ulong.MaxValue;
///
/// Initializes a new instance of the
/// class, using the stream as the recipient of the debugging and tracing output.
///
/// A System.IO.Stream that represents the stream the System.Diagnostics.TextWriterTraceListener writes to.
public FileTraceListener(Stream stream) : base(stream)
{
}
///
/// Changes the target file name of the file trace listener. Depending on arguments it might leave the old file as it is
/// or move the content to the new file.
///
/// The file name of the target file.
/// Allow multiple processes to modify the file.
/// Whether to move the content or to start a new log file.
internal void ChangeFileName(string fileName, bool noExclusiveWriteLock, bool startNewLog = false)
{
string dir = Path.GetDirectoryName(fileName);
if (string.IsNullOrWhiteSpace(dir) == false)
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
StreamWriter newwriter = null;
if (noExclusiveWriteLock)
{
// Initialize a stream where the underlying file can be deleted. If the file is deleted, writes just go into the void.
var stream = new FileStream(fileName, FileMode.Append, FileAccess.Write, FileShare.Read | FileShare.Delete);
newwriter = new StreamWriter(stream, Encoding.UTF8)
{
AutoFlush = true
};
}
else
{
newwriter = new StreamWriter(fileName, true, System.Text.Encoding.UTF8)
{
AutoFlush = true
};
}
var OldWriter = base.Writer;
base.Writer = newwriter;
OldWriter.Close();
if (!startNewLog && _fileName != null)
{
Writer.Write(File.ReadAllText(_fileName));
File.Delete(_fileName);
}
_fileName = Path.GetFullPath(fileName);
}
long first_timestamp = -1;
static readonly ThreadLocal _sb = new ThreadLocal(() => new System.Text.StringBuilder());
readonly object fileTraceLock = new object();
public override void TraceEvents(IEnumerable events)
{
// Note, this function is heavily optimized.
// profile carefully after doing any changes!!
if (events == null)
throw new ArgumentNullException(nameof(events));
System.Text.StringBuilder sb = _sb.Value;
sb.Clear();
long lastTick = 0;
string tickmsg = "";
foreach (var evt in events)
{
if (first_timestamp == -1)
first_timestamp = evt.Timestamp;
if (lastTick != evt.Timestamp)
{ // lastTick is to ms resolution
// If its the same, dont waste time generating a new string.
if (IsRelative)
{
var elapsed = TimeSpan.FromTicks(evt.Timestamp - first_timestamp);
tickmsg = elapsed.ToString("hh\\:mm\\:ss\\.ffffff");
}
else
{
var time = new DateTime(evt.Timestamp);
tickmsg = time.ToString("yyyy-MM-dd HH:mm:ss.ffffff");
}
lastTick = evt.Timestamp;
}
sb.Append(tickmsg);
sb.Append(" ; ");
sb.Append(evt.Source);
int paddingcount = 14 - evt.Source.Length;
if (paddingcount > 0)
sb.Append(' ', paddingcount);
sb.Append(" ; ");
switch ((LogEventType)evt.EventType)
{ // All these should have same padding, but dont calculate runtime.
case LogEventType.Debug:
sb.Append("Debug ");
break;
case LogEventType.Information:
sb.Append("Information");
break;
case LogEventType.Warning:
sb.Append("Warning ");
break;
case LogEventType.Error:
sb.Append("Error ");
break;
}
sb.Append(" ; ");
if (evt.Message != null)
{
sb.Append(evt.Message.Replace("\n", "").Replace("\r", ""));
}
sb.AppendLine();
}
var logStr = sb.ToString();
lock (fileTraceLock)
{
this.Write(logStr);
if (Writer is StreamWriter streamWriter && (ulong)streamWriter.BaseStream.Length > FileSizeLimit)
{
if (FileSizeLimitReached != null)
FileSizeLimitReached(this, new EventArgs());
}
}
}
}
}