// 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.Diagnostics;
using System.Reflection;
using System.IO;
using OpenTap.Diagnostic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace OpenTap
{
///
/// Identifies the type of event that is logged.
///
public enum LogEventType
{
///
/// Recoverable error.
///
Error = 10,
///
/// Noncritical problem.
///
Warning = 20,
///
/// Informational message.
///
Information = 30,
///
/// Debugging trace.
///
Debug = 40
}
///
/// Encapsulates the features of the OpenTAP logging infrastructure.
///
public class TraceSource
{
internal ILog log;
/// The object that owns this trace source.
internal object Owner;
internal TraceSource(ILog logSource)
{
log = logSource;
}
LogContext.LogInjector redirectedLog => Log.RedirectedLog;
///
/// Blocks until all messages posted up to this point have reached all TraceListeners.
///
public void Flush()
{
Log.Flush();
}
///
/// Register a single event.
///
public void TraceEvent(LogEventType te, int id, string message)
{
if (message == null)
throw new ArgumentNullException(nameof(message));
if(redirectedLog != null)
redirectedLog.LogEvent(log.Source, (int)te, message);
else
log.LogEvent((int)te, message);
}
/// Register a single event with formatting and duration.
public void TraceEvent(long durationNs, LogEventType te, int id, string message, params object[] args)
{
if(redirectedLog != null)
redirectedLog.LogEvent(log.Source, (int)te, durationNs, message, args);
else
log.LogEvent((int)te, durationNs, message, args);
}
/// Register a single event without formatting and duration.
public void TraceEvent(long durationNs, LogEventType te, int id, string message)
{
// this overload is important since otherwise the logging system will use
// the overload with [args] even though there are none. And that can give
// problems if the message itself contains formatting demarcations e.g '{0}'
if (redirectedLog != null)
redirectedLog.LogEvent(log.Source, (int)te, durationNs, message);
else
log.LogEvent((int)te, durationNs, message);
}
///
/// Register a single event with formatting
///
public void TraceEvent(LogEventType te, int id, string message, params object[] args)
{
if (message == null)
throw new ArgumentNullException(nameof(message));
if (args == null)
throw new ArgumentNullException(nameof(args));
if(redirectedLog != null)
redirectedLog.LogEvent(log.Source, (int)te, message, args);
else
log.LogEvent((int)te, message, args);
}
}
///
/// Base class for various listeners.
///
public class TraceListener : ILogListener
{
void ILogListener.EventsLogged(IEnumerable events)
{
TraceEvents(events);
}
///
/// Receives all log messages. The virtual method simply calls directly.
///
public virtual void TraceEvents(IEnumerable events)
{
foreach (var evt in events)
TraceEvent(evt.Source, (LogEventType)evt.EventType, 0, evt.Message);
}
///
/// Empty TraceEvent method.
///
public virtual void TraceEvent(string source, LogEventType eventType, int id, string format)
{
}
///
/// Empty TraceEvent method.
///
public virtual void TraceEvent(string source, LogEventType eventType, int id, string format, params object[] args)
{
TraceEvent(source, eventType, id, string.Format(format, args));
}
///
/// Virtual method to match System.Diagnostics.TraceListener. Might be removed.
///
public virtual void Write(string str)
{
}
///
/// Virtual method to match System.Diagnostics.TraceListener. Might be removed.
///
public virtual void WriteLine(string str)
{
}
///
/// Waits until all sent log messages have been processed by this and all other TraceListeners.
///
public virtual void Flush()
{
Log.Flush();
}
}
///
/// Simple TraceListener which outputs data to a TextWriter.
///
class TextWriterTraceListener : TraceListener, IDisposable
{
private TextWriter writer;
readonly object lockObject = new object();
///
/// The writer that is used as the output.
///
public TextWriter Writer
{
get => writer;
set
{
if (writer != value)
{
lock(lockObject)
writer = value;
}
}
}
///
/// Creates a new TextWriterTraceListener writing to the given filename.
///
public TextWriterTraceListener(string filename)
: this(new FileStream(filename, FileMode.Append))
{
}
///
/// Creates a new TextWriterTraceListener writing to the given stream.
///
public TextWriterTraceListener(Stream stream)
{
Writer = new StreamWriter(stream);
}
///
/// Writes a string to the current Writer.
///
public override void Write(string message)
{
lock(lockObject)
Writer.Write(message);
}
///
/// Writes a string including a newline to the current Writer.
///
public override void WriteLine(string message)
{
lock(lockObject)
Writer.WriteLine(message);
}
///
/// Flushes the log system and the current Writer.
///
public override void Flush()
{
base.Flush();
lock(lockObject)
writer.Flush();
}
///
/// Frees up the writer.
///
public void Dispose()
{
lock(lockObject)
{
if (writer != null)
{
writer.Close();
writer = null;
}
}
}
}
///
/// This class extends System.Diagnostics.Log to provide shorthand methods
/// for logging/tracing messages at different levels.
///
public static class Log
{
static readonly LogContext rootLogContext = (LogContext)LogFactory.CreateContext();
internal static ILogTimestampProvider Timestamper
{
get => rootLogContext.Timestamper;
set => rootLogContext.Timestamper = value;
}
static readonly SessionLocal logField = new SessionLocal(null);
static readonly SessionLocal sessionLogContext = new SessionLocal(rootLogContext);
internal static void WithNewContext()
{
var ctx = new LogContext();
logField.Value = new LogContext.LogInjector(ctx);
sessionLogContext.Value = ctx;
}
internal static LogContext.LogInjector RedirectedLog => logField.Value;
/// The current log context.
public static ILogContext Context => sessionLogContext.Value;
/// Makes a TraceListener start receiving log messages.
/// The TraceListener to add.
public static void AddListener(ILogListener listener)
{
if (listener == null)
throw new ArgumentNullException(nameof(listener));
var ctx = Context;
ctx.Flush();
ctx.AttachListener(listener);
}
/// Stops a specified TraceListener from receiving log messages.
/// The TraceListener to remove.
public static void RemoveListener(ILogListener listener)
{
if (listener == null)
throw new ArgumentNullException(nameof(listener));
var ctx = Context;
listener.Flush();
ctx.DetachListener(listener);
listener.Flush();
}
///
/// Gets all added TraceListeners.
///
/// A readonly collection of TraceListeners.
public static ReadOnlyCollection GetListeners()
{
return sessionLogContext.Value?.GetListeners();
}
/// Creates a new log source.
/// The name of the Log.
/// The created Log.
public static TraceSource CreateSource(string name)
{
return new TraceSource(rootLogContext.CreateLog(name));
}
// ConditionalWeakTable keys does not count as a reference and are automatically removed on GC. This way we avoid leak. CWT's are thread safe.
static readonly System.Runtime.CompilerServices.ConditionalWeakTable