一、背景介绍
LEAN是一个算法交易引擎。基于C#语言。QuantConnect.Logging为LEAN基础模块,提供日志功能。下面分析实现细节,并分享使用。
二、类关系结构图
三、核心类介绍
1、QuantConnect.Logging.LogEntry
标识一条日志。
using System;
namespace QuantConnect.Logging
{
/********************************************************
* CLASS DEFINITION
*********************************************************/
/// <summary>
/// Log entry wrapper to make logging simpler:
/// </summary>
public class LogEntry
{
/********************************************************
* CLASS PROPERTIES
*********************************************************/
/// <summary>
/// Time of the log entry
/// </summary>
public DateTime Time;
/// <summary>
/// Message of the log entry
/// </summary>
public string Message;
/********************************************************
* CLASS CONSTRUCTOR
*********************************************************/
/// <summary>
/// Create a default log message with the current time.
/// </summary>
/// <param name="message"></param>
public LogEntry(string message)
{
Time = DateTime.UtcNow;
Message = message;
}
/// <summary>
/// Create a log entry at a specific time in the analysis (for a backtest).
/// </summary>
/// <param name="message">Message for log</param>
/// <param name="time">Time of the message</param>
public LogEntry(string message, DateTime time)
{
Time = time.ToUniversalTime();
Message = message;
}
}
}
2、QuantConnect.Logging.ILogHandler
日志处理接口类。
using System;
namespace QuantConnect.Logging
{
/// <summary>
/// Interface for redirecting log output
/// </summary>
public interface ILogHandler : IDisposable
{
/// <summary>
/// Write error message to log
/// </summary>
/// <param name="text">The error text to log</param>
void Error(string text);
/// <summary>
/// Write debug message to log
/// </summary>
/// <param name="text">The debug text to log</param>
void Debug(string text);
/// <summary>
/// Write debug message to log
/// </summary>
/// <param name="text">The trace text to log</param>
void Trace(string text);
}
}
3、QuantConnect.Logging.Log
日志记录接口。
/**********************************************************
* USING NAMESPACES
**********************************************************/
using System;
using System.Threading;
using System.Text;
using System.Collections;
using System.Diagnostics;
using System.IO;
namespace QuantConnect.Logging
{
/********************************************************
* CLASS DEFINITIONS
*********************************************************/
/// <summary>
/// Logging management class.
/// </summary>
public static class Log
{
/********************************************************
* CLASS VARIABLES
*********************************************************/
private static string _lastTraceText = "";
private static string _lastErrorText = "";
private static bool _debuggingEnabled;
private static int _level = 1;
private static ILogHandler _logHandler = new ConsoleLogHandler();
/********************************************************
* CLASS PROPERTIES
*********************************************************/
/// <summary>
/// Gets or sets the ILogHandler instance used as the global logging implementation.
/// </summary>
public static ILogHandler LogHandler
{
get { return _logHandler; }
set { _logHandler = value; }
}
/// <summary>
/// Global flag whether to enable debugging logging:
/// </summary>
public static bool DebuggingEnabled
{
get { return _debuggingEnabled; }
set { _debuggingEnabled = value; }
}
/// <summary>
/// Set the minimum message level:
/// </summary>
public static int DebuggingLevel
{
get { return _level; }
set { _level = value; }
}
/********************************************************
* CLASS METHODS
*********************************************************/
/// <summary>
/// Log error
/// </summary>
/// <param name="error">String Error</param>
/// <param name="overrideMessageFloodProtection">Force sending a message, overriding the "do not flood" directive</param>
public static void Error(string error, bool overrideMessageFloodProtection = false)
{
try
{
if (error == _lastErrorText && !overrideMessageFloodProtection) return;
_logHandler.Error(error);
_lastErrorText = error; //Stop message flooding filling diskspace.
}
catch (Exception err)
{
Console.WriteLine("Log.Error(): Error writing error: " + err.Message);
}
}
/// <summary>
/// Log trace
/// </summary>
public static void Trace(string traceText, bool overrideMessageFloodProtection = false)
{
try
{
if (traceText == _lastTraceText && !overrideMessageFloodProtection) return;
_logHandler.Trace(traceText);
_lastTraceText = traceText;
}
catch (Exception err)
{
Console.WriteLine("Log.Trace(): Error writing trace: " +err.Message);
}
}
/// <summary>
/// Output to the console, and sleep the thread for a little period to monitor the results.
/// </summary>
/// <param name="text"></param>
/// <param name="level">debug level</param>
/// <param name="delay"></param>
public static void Debug(string text, int level = 1, int delay = 0)
{
try
{
if (!_debuggingEnabled || level < _level) return;
_logHandler.Debug(text);
Thread.Sleep(delay);
}
catch (Exception err)
{
Console.WriteLine("Log.Debug(): Error writing debug: " + err.Message);
}
}
/// <summary>
/// C# Equivalent of Print_r in PHP:
/// </summary>
/// <param name="obj"></param>
/// <param name="recursion"></param>
/// <returns></returns>
public static string VarDump(object obj, int recursion = 0)
{
var result = new StringBuilder();
// Protect the method against endless recursion
if (recursion < 5)
{
// Determine object type
var t = obj.GetType();
// Get array with properties for this object
var properties = t.GetProperties();
foreach (var property in properties)
{
try
{
// Get the property value
var value = property.GetValue(obj, null);
// Create indenting string to put in front of properties of a deeper level
// We'll need this when we display the property name and value
var indent = String.Empty;
var spaces = "| ";
var trail = "|...";
if (recursion > 0)
{
indent = new StringBuilder(trail).Insert(0, spaces, recursion - 1).ToString();
}
if (value != null)
{
// If the value is a string, add quotation marks
var displayValue = value.ToString();
if (value is string) displayValue = String.Concat('"', displayValue, '"');
// Add property name and value to return string
result.AppendFormat("{0}{1} = {2}\n", indent, property.Name, displayValue);
try
{
if (!(value is ICollection))
{
// Call var_dump() again to list child properties
// This throws an exception if the current property value
// is of an unsupported type (eg. it has not properties)
result.Append(VarDump(value, recursion + 1));
}
else
{
// 2009-07-29: added support for collections
// The value is a collection (eg. it's an arraylist or generic list)
// so loop through its elements and dump their properties
var elementCount = 0;
foreach (var element in ((ICollection)value))
{
var elementName = String.Format("{0}[{1}]", property.Name, elementCount);
indent = new StringBuilder(trail).Insert(0, spaces, recursion).ToString();
// Display the collection element name and type
result.AppendFormat("{0}{1} = {2}\n", indent, elementName, element.ToString());
// Display the child properties
result.Append(VarDump(element, recursion + 2));
elementCount++;
}
result.Append(VarDump(value, recursion + 1));
}
} catch { }
}
else
{
// Add empty (null) property to return string
result.AppendFormat("{0}{1} = {2}\n", indent, property.Name, "null");
}
}
catch
{
// Some properties will throw an exception on property.GetValue()
// I don't know exactly why this happens, so for now i will ignore them...
}
}
}
return result.ToString();
}
}
}
四、使用
在LEAN中,其他模块都依赖QuantConnect.Logging模块。通过QuantConnect.Logging.Log类的Error()\Trace()\Debug()方法,记录日志。
五、扩展
如果需要个性的日志处理,可以实现QuantConnect.Logging.ILogHandler接口,对日志进行个性处理。可以参考现有实现QuantConnect.Logging.FileLogHandler.cs。然后通过设置QuantConnect.Logging.Log中LogHandler为新实现。
综上,QuantConnect.Logging模块作为基础模块,给LEAN系统提供了日志功能。在设计上考虑了扩展。(C#生态有可重用的日志模块吗?)