// edtFTPnet
//
// Copyright (C) 2004 Enterprise Distributed Technologies Ltd
//
// www.enterprisedt.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Bug fixes, suggestions and comments should posted on
// http://www.enterprisedt.com/forums/index.php
//
// Change Log:
//
// $Log: Logger.cs,v $
// Revision 1.30 2009-06-23 00:03:54 hans
// Prevent NRE when StackTrace is null.
//
// Revision 1.29 2009-04-02 04:10:38 bruceb
// CF defines
//
// Revision 1.28 2009-03-23 23:18:58 hans
// Fixed NRE in PrimaryLogFile.
//
// Revision 1.27 2008-12-04 01:36:33 bruceb
// catch errors reading config file
//
// Revision 1.26 2008-06-20 01:24:00 bruceb
// added ClearAppenders()
//
// Revision 1.25 2008-04-16 00:35:59 hans
// Added overload for Error that takes a variable argument-list.
//
// Revision 1.24 2008-04-14 00:10:38 bruceb
// synchronize iterations
//
// Revision 1.23 2007-06-26 01:25:04 bruceb
// CF changes
//
// Revision 1.22 2007-05-15 01:20:37 bruceb
// LogMessageReceived event and supporting code added
//
// Revision 1.21 2007/01/10 06:57:57 hans
// Added support for CustomLogLevelAppender
//
// Revision 1.20 2006/09/05 12:44:22 hans
// Improved exception logging.
//
// Revision 1.19 2006/08/15 10:12:24 hans
// Made Log(Level, string, Exception) more flexible.
//
// Revision 1.18 2006/08/14 11:10:28 hans
// Added Error(Exception) method.
//
// Revision 1.17 2006/07/13 07:44:16 hans
// Added logging.
//
// Revision 1.16 2006/07/07 10:21:02 hans
// Added LogObject
//
// Revision 1.15 2006/06/14 10:13:36 hans
// Fixed comments and added .NET 2.0 compatibility
//
// Revision 1.14 2006/05/03 06:38:30 bruceb
// log levels now can be enums or INFO etc
//
// Revision 1.13 2006/04/20 05:40:26 hans
// Fixed comment error.
//
// Revision 1.12 2006/03/16 22:19:24 hans
// Added simple properties to control logging. These are used by FTPConnection, but also provide a really simple interface for the user.
//
// Revision 1.11 2006/02/16 20:19:41 hans
// Added RemoveAppender feature.
//
// Revision 1.10 2006/01/08 19:05:49 bruceb
// change msg about param not found to INFO
//
// Revision 1.9 2005/12/13 20:19:11 hans
// Added properties for controlling logging output format.
//
// Revision 1.8 2005/02/07 17:22:39 bruceb
// make sure Exception message included in log
//
// Revision 1.7 2004/12/22 22:58:17 bruceb
// fixed bad level problem
//
// Revision 1.6 2004/11/13 18:20:52 bruceb
// clear appenders/loggers in shutdown
//
// Revision 1.5 2004/11/06 11:15:24 bruceb
// namespace tidying up
//
// Revision 1.4 2004/10/29 09:42:30 bruceb
// removed /// from file headers
//
//
//
using System;
using System.Globalization;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.Reflection;
using System.Text;
using System.ComponentModel;
namespace EnterpriseDT.Util.Debug{
/// <summary>
/// An instance of this class is supplied to the LogMessageReceived event
/// </summary>
public class LogMessageEventArgs : EventArgs
{
private string loggerName;
private Level level;
private string text;
private Exception e;
private object[] args;
/// <summary>
/// Constructor
/// </summary>
/// <param name="loggerName">name of logger</param>
/// <param name="level">log level of message</param>
/// <param name="text">message text</param>
/// <param name="args">any additional args supplied</param>
internal LogMessageEventArgs(
string loggerName, Level level, string text, params object[] args)
{
this.loggerName = loggerName;
this.level = level;
this.text = text;
this.args = args;
if (args!=null && args.Length == 1 && args[0] is Exception)
this.e = (Exception)args[0];
}
/// <summary>
/// Returns the level of this message
/// </summary>
public Level LogLevel
{
get { return level; }
}
/// <summary>
/// Returns the name of the logger for this message
/// </summary>
public string LoggerName
{
get { return loggerName; }
}
/// <summary>
/// The message text.
/// </summary>
/// <remarks>Normally this is a log message, but if additional arguments
/// are supplied, this will be a Format string so the extra arguments can
/// be displayed correctly.</remarks>
public string Text
{
get
{
return text;
}
}
/// <summary>
/// The formatted message text, constructed from the arguments and using
/// the Text as a formatting string.
/// </summary>
public string FormattedText
{
get
{
if (args != null)
return string.Format(text, args);
return text;
}
}
/// <summary>
/// An exception if it exists (passed in as the first in the
/// argument list).
/// </summary>
public Exception Exception
{
get { return e; }
}
/// <summary>
/// The array of variable arguments.
/// </summary>
public object[] Arguments
{
get { return args; }
}
}
/// <summary>
/// Delegate used for LogMessageReceived event
/// </summary>
public delegate void LogMessageHandler(object sender, LogMessageEventArgs e);
/// <summary>
/// Logger class that mimics log4net Logger class
/// </summary>
/// <author> Bruce Blackshaw
/// </author>
/// <version> $Revision: 1.30 $
/// </version>
public class Logger
{
/// <summary>
/// Set all loggers to this level
/// </summary>
public static Level CurrentLevel
{
set
{
globalLevel = value;
}
get
{
return globalLevel;
}
}
/// <summary>If true then class-names will be shown in log.</summary>
public static bool ShowClassNames
{
get
{
return showClassNames;
}
set
{
showClassNames = value;
}
}
/// <summary>If true then timestamps will be shown in log.</summary>
public static bool ShowTimestamp
{
get
{
return showTimestamp;
}
set
{
showTimestamp = value;
}
}
/// <summary>
/// Is debug logging enabled?
/// </summary>
/// <returns> true if enabled
/// </returns>
virtual public bool DebugEnabled
{
get
{
return IsEnabledFor(Level.DEBUG);
}
}
/// <summary> Is info logging enabled for the supplied level?
///
/// </summary>
/// <returns> true if enabled
/// </returns>
virtual public bool InfoEnabled
{
get
{
return IsEnabledFor(Level.INFO);
}
}
/// <summary>
/// The primary log file is simply the first file appender
/// that has been added to the logger.
/// </summary>
public static string PrimaryLogFile
{
get
{
return mainFileAppender!=null ? mainFileAppender.FileName : null;
}
set
{
string mainFileAppenderName = (mainFileAppender != null ? mainFileAppender.FileName : null);
if (mainFileAppenderName != value)
{
if (mainFileAppender != null)
RemoveAppender(mainFileAppender);
if (value != null)
AddAppender(new FileAppender(value));
}
}
}
/// <summary>
/// If this property is <c>true</c> then logs will be written to the
/// console.
/// </summary>
public static bool LogToConsole
{
get
{
return mainConsoleAppender!=null;
}
set
{
if (value==true)
{
if (mainConsoleAppender==null)
AddAppender(new StandardOutputAppender());
}
else
{
if (mainConsoleAppender!=null)
RemoveAppender(mainConsoleAppender);
}
}
}
/// <summary>
/// If this property is <c>true</c> then logs will be written using
/// <see cref="System.Diagnostics.Trace"/>.
/// </summary>
public static bool LogToTrace
{
get
{
return mainTraceAppender!=null;
}
set
{
if (value==true)
{
if (mainTraceAppender==null)
AddAppender(new TraceAppender());
}
else
{
if (mainTraceAppender!=null)
RemoveAppender(mainTraceAppender);
}
}
}
/// <summary>
/// If this event is set then all logging events are directed to the
/// event as well as the loggers.
/// </summary>
/// <remarks>If it is desired to only send logging to the log system subscribing
/// to this event, the <see cref="CurrentLevel"/> should be set to <see cref="Level.OFF"/>.</remarks>
public static event LogMessageHandler LogMessageReceived;
/// <summary> Level of all loggers</summary>
private static Level globalLevel;
/// <summary>Date format</summary>
private static readonly string format = "d MMM yyyy HH:mm:ss.fff";
private static readonly string LEVEL_PARAM = "edtftp.log.level";
/// <summary> Hash of all loggers that exist</summary>
private static Hashtable loggers = Hashtable.Synchronized(new Hashtable(10));
/// <summary> Vector of all appenders</summary>
private static ArrayList appenders = ArrayList.Synchronized(new ArrayList(2));
/// <summary> Timestamp</summary>
private DateTime ts;
/// <summary> Class name for this logger</summary>
private string clazz;
/// <summary>If true then class-names will be shown in log.</summary>
private static bool showClassNames = true;
/// <summary>If true then timestamps will be shown in log.</summary>
private static bool showTimestamp = true;
/// <summary>Main file appender</summary>
private static FileAppender mainFileAppender = null;
/// <summary>Main file appender</summary>
private static StandardOutputAppender mainConsoleAppender = null;
/// <summary>Main file appender</summary>
private static TraceAppender mainTraceAppender = null;
/// <summary>
/// Constructor
/// </summary>
/// <param name="clazz">
/// class this logger is for
/// </param>
private Logger(string clazz)
{
this.clazz = clazz;
}
/// <summary> Get a logger for the supplied class
///
/// </summary>
/// <param name="clazz"> full class name
/// </param>
/// <returns> logger for class
/// </returns>
public static Logger GetLogger(System.Type clazz)
{
return GetLogger(clazz.FullName);
}
/// <summary>
/// Get a logger for the supplied class
/// </summary>
/// <param name="clazz"> full class name
/// </param>
/// <returns> logger for class
/// </returns>
public static Logger GetLogger(string clazz)
{
Logger logger = (Logger) loggers[clazz];
if (logger == null)
{
logger = new Logger(clazz);
loggers[clazz] = logger;
}
return logger;
}
/// <summary>
/// Add an appender to our list
/// </summary>
/// <param name="newAppender">
/// new appender to add
/// </param>
public static void AddAppender(Appender newAppender)
{
appenders.Add(newAppender);
if (newAppender is FileAppender && mainFileAppender==null)
mainFileAppender = (FileAppender)newAppender;
if (newAppender is StandardOutputAppender && mainConsoleAppender==null)
mainConsoleAppender = (StandardOutputAppender)newAppender;
if (newAppender is TraceAppender && mainTraceAppender==null)
mainTraceAppender = (TraceAppender)newAppender;
}
/// <summary>
/// Remove an appender from our list
/// </summary>
/// <param name="appender">appender to remove</param>
public static void RemoveAppender(Appender appender)
{
appenders.Remove(appender);
if (appender==mainFileAppender)
mainFileAppender = null;
if (appender==mainConsoleAppender)
mainConsoleAppender = null;
if (appender==mainTraceAppender)
mainTraceAppender = null;
}
/// <summary> Close and remove all appenders and loggers</summary>
public static void Shutdown()
{
ClearAppenders();
loggers.Clear();
}
/// <summary> Close and remove all appenders</summary>
public static void ClearAppenders()
{
lock (appenders.SyncRoot)
{
for (int i = 0; i < appenders.Count; i++)
{
Appender a = (Appender)appenders[i];
try
{
a.Close();
}
catch (Exception) { }
}
}
appenders.Clear();
}
/// <summary>
/// Log a message using the given level.
/// </summary>
/// <param name="level">Log level.</param>
/// <param name="message">Message to log.</param>
/// <param name="args">Either an Exception or arguments substituted into message.</param>
public virtual void Log(Level level, string message, params object[] args)
{
// direct logging
if (LogMessageReceived != null)
{
LogMessageReceived(this, new LogMessageEventArgs(clazz, level, message, args));
}
if (IsEnabledFor(level))
{
if (args!=null && args.Length == 1 && args[0] is Exception)
OurLog(level, message, (Exception)args[0]);
else if (args!=null)
OurLog(level, string.Format(message, args), null);
else
OurLog(level, message, null);
}
}
/// <summary>
/// Log a message to our logging system
/// </summary>
/// <param name="level">log level</param>
/// <param name="message">message to log</param>
/// <param name="t">throwable object</param>
private void OurLog(Level level, string message, Exception t)
{
ts = DateTime.Now;
string stamp = ts.ToString(format, CultureInfo.CurrentCulture.DateTimeFormat);
System.Text.StringBuilder buf = new System.Text.StringBuilder(level.ToString());
if (showClassNames)
{
buf.Append(" [");
buf.Append(clazz);
buf.Append("]");
}
if (showTimestamp)
{
buf.Append(" ");
buf.Append(stamp);
}
buf.Append(" : ");
string prefix = buf.ToString();
buf.Append(message);
if (t != null)
buf.Append(" : ").Append(t.GetType().FullName).Append(": ").Append(t.Message);
if (appenders.Count == 0)
{
// by default to stdout
System.Console.Out.WriteLine(buf.ToString());
if (t != null)
{
if (t.StackTrace!=null)
foreach (string line in t.StackTrace.Replace("\r", "").Split('\n'))
OurLog(level, prefix + line, null);
if (t.InnerException!=null)
{
System.Console.Out.WriteLine(
string.Format("{0}CAUSED BY - {1}: {2}",
prefix,
t.InnerException.GetType().FullName,
t.InnerException.Message));
if (t.InnerException.StackTrace != null)
foreach (string line in t.InnerException.StackTrace.Replace("\r", "").Split('\n'))
OurLog(level, prefix + line, null);
}
}
}
else
{
bool appendToAll = globalLevel.IsGreaterOrEqual(level);
lock (appenders.SyncRoot)
{
for (int i = 0; i < appenders.Count; i++)
{
Appender a = (Appender)appenders[i];
bool appendToCustom = false;
if (a is CustomLogLevelAppender)
{
CustomLogLevelAppender appender = (CustomLogLevelAppender)a;
appendToCustom = appender.CurrentLevel.IsGreaterOrEqual(level);
}
if (appendToAll || appendToCustom)
{
a.Log(prefix + message);
if (t != null)
{
a.Log(prefix + t.GetType().FullName + ": " + t.Message);
if (t.StackTrace!=null)
foreach (string line in t.StackTrace.Replace("\r", "").Split('\n'))
a.Log(prefix + line);
if (t.InnerException != null)
{
a.Log(prefix + "CAUSED BY - " + t.InnerException.GetType().FullName + ": " + t.Message);
if (t.InnerException.StackTrace != null)
foreach (string line in t.InnerException.StackTrace.Replace("\r", "").Split('\n'))
a.Log(prefix + line);
}
}
}
}
}
}
}
/// <summary> Log an info level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
public virtual void Info(string message)
{
Log(Level.INFO, message, null);
}
/// <summary> Log an info level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
/// <param name="t"> throwable object
/// </param>
public virtual void Info(string message, Exception t)
{
Log(Level.INFO, message, t);
}
/// <summary> Log an info level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
/// <param name="args">arguments references in the message.
/// </param>
public virtual void Info(string message, params object[] args)
{
if (IsEnabledFor(Level.INFO))
Log(Level.INFO, string.Format(message, args), null);
}
/// <summary> Log a warning level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
public virtual void Warn(string message)
{
Log(Level.WARN, message, null);
}
/// <summary> Log a warning level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
/// <param name="t"> throwable object
/// </param>
public virtual void Warn(string message, Exception t)
{
Log(Level.WARN, message, t);
}
/// <summary> Log an error level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
public virtual void Error(string message)
{
Log(Level.ERROR, message, null);
}
/// <summary> Log an error level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
/// <param name="t"> throwable object
/// </param>
public virtual void Error(string message, Exception t)
{
Log(Level.ERROR, message, t);
}
/// <summary>Log an error level message</summary>
/// <param name="message">message to log</param>
/// <param name="t">throwable object</param>
/// <param name="args">arguments references in the message.</param>
public virtual void Error(string message, Exception t, params object[] args)
{
Log(Level.ERROR, string.Format(message, args), t);
}
/// <summary> Log an error level message
///
/// </summary>
/// <param name="t"> throwable object
/// </param>
public virtual void Error(Exception t)
{
Log(Level.ERROR, t.Message, t);
}
/// <summary> Log a fatal level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
public virtual void Fatal(string message)
{
Log(Level.FATAL, message, null);
}
/// <summary> Log a fatal level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
/// <param name="t"> throwable object
/// </param>
public virtual void Fatal(string message, Exception t)
{
Log(Level.FATAL, message, t);
}
/// <summary> Log a debug level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
public virtual void Debug(string message)
{
Log(Level.DEBUG, message, null);
}
/// <summary>Log a debug level message</summary>
/// <param name="message">message to log</param>
/// <param name="args">arguments references in the message.</param>
public virtual void Debug(string message, params object[] args)
{
if (IsEnabledFor(Level.DEBUG))
Log(Level.DEBUG, string.Format(message, args), null);
}
/// <summary> Log a debug level message
///
/// </summary>
/// <param name="message"> message to log
/// </param>
/// <param name="t"> throwable object
/// </param>
public virtual void Debug(string message, Exception t)
{
Log(Level.DEBUG, message, t);
}
/// <summary> Is logging enabled for the supplied level?
///
/// </summary>
/// <param name="level"> level to test for
/// </param>
/// <returns> true if enabled
/// </returns>
public virtual bool IsEnabledFor(Level level)
{
if (globalLevel.IsGreaterOrEqual(level))
return true;
lock (appenders.SyncRoot)
{
foreach (Appender a in appenders)
if (a is CustomLogLevelAppender)
{
CustomLogLevelAppender appender = (CustomLogLevelAppender)a;
if (appender.CurrentLevel.IsGreaterOrEqual(level))
return true;
}
}
return false;
}
/// <summary> Determine the logging level</summary>
static Logger()
{
{
globalLevel = null;
string level = null;
try
{
#if NET20
level = ConfigurationManager.AppSettings[LEVEL_PARAM];
#else
level = ConfigurationSettings.AppSettings[LEVEL_PARAM];
#endif
}
catch (Exception ex)
{
System.Console.WriteLine("WARN: Failure reading configuration file: " + ex.Message);
}
if (level != null)
{
// first try with the strings INFO etc
globalLevel = Level.GetLevel(level);
if (globalLevel == null)
{
try
{
// now try from the enum
LogLevel logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), level, true);
globalLevel = Level.GetLevel(logLevel);
}
catch (Exception)
{
}
}
}
// if no level set, switch off
if (globalLevel == null)
{
globalLevel = Level.OFF;
if (level != null)
{
System.Console.Out.WriteLine("WARN: '" + LEVEL_PARAM + "' configuration property invalid. Unable to parse '" + level + "' - logging switched off");
}
}
}
}
/// <summary>
/// Logs the public properties of an object.
/// </summary>
/// <param name="level">Logging level to use.</param>
/// <param name="prefix">Text to prepend to the properties.</param>
/// <param name="obj">Object whose properties are to be logged.</param>
public void LogObject(Level level, string prefix, object obj)
{
if (IsEnabledFor(level))
{
if (obj == null)
Log(level, prefix + "(null)", null);
Type objType = obj.GetType();
bool useShortFormat = true;
PropertyInfo[] properties = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (PropertyInfo p in properties)
{
if (RequiresLongFormat(p, obj))
{
useShortFormat = false;
break;
}
}
StringBuilder s = new StringBuilder();
foreach (PropertyInfo p in properties)
{
object v = p.GetValue(obj, null);
if (s.Length>0)
s.Append(useShortFormat ? ", " : "\n ");
s.Append(p.Name).Append("=");
DumpValue(v, s, " ");
}
Log(level, prefix + s, null);
}
}
private ArrayList GetAllProperties(Type t)
{
ArrayList fields = new ArrayList();
while (t != typeof(object))
{
fields.AddRange(t.GetProperties(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public));
t = t.BaseType;
}
return fields;
}
private bool RequiresLongFormat(PropertyInfo p, object obj)
{
object value = p.GetValue(obj, null);
if (value == null
|| value is string
|| value.GetType().IsPrimitive)
return false;
if (value.GetType().IsArray && value.GetType().GetElementType().IsPrimitive)
return ((Array)value).Length > 16;
if (value is StringDictionary)
return ((StringDictionary)value).Count > 1;
if (value is ICollection)
return ((ICollection)value).Count > 1;
return typeof(IEnumerable).IsAssignableFrom(p.PropertyType);
}
private void DumpValue(object value, StringBuilder valueStr, string indent)
{
if (value == null)
valueStr.Append("(null)");
else if (value.GetType().IsArray && value.GetType().GetElementType().IsPrimitive)
{
int count = 0;
Array arr = (Array)value;
if (arr.Length > 16)
valueStr.Append("(")
.Append(arr.Length)
.Append(" items)")
.Append("\n")
.Append(indent)
.Append(" ");
foreach (object o in arr)
{
if (o is byte)
valueStr.Append(((byte)o).ToString("X2"));
else
valueStr.Append(o);
count++;
if (count % 16 != 0)
valueStr.Append(" ");
else
valueStr.Append("\n").Append(indent).Append(" ");
}
}
else if (value is IDictionary)
{
IDictionary dict = (IDictionary)value;
bool useLongFormat = dict.Count > 1;
if (useLongFormat)
valueStr.Append("(").Append(dict.Count).Append(" items)");
foreach (object key in dict.Keys)
{
if (useLongFormat)
valueStr.Append("\n").Append(indent);
valueStr.Append(key).Append("=");
DumpValue(dict[key], valueStr, indent + " ");
}
}
else if (value is StringDictionary)
{
StringDictionary dict = (StringDictionary)value;
bool useLongFormat = dict.Count > 1;
if (useLongFormat)
valueStr.Append("(").Append(dict.Count).Append(" items)");
foreach (string key in dict.Keys)
{
if (useLongFormat)
valueStr.Append("\n").Append(indent);
valueStr.Append(key).Append("=");
DumpValue(dict[key], valueStr, indent + " ");
}
}
else if (!(value is string) && value is IEnumerable)
{
bool useLongFormat = true;
if (value is ICollection)
{
ICollection col = (ICollection)value;
useLongFormat = col.Count > 1;
if (useLongFormat)
valueStr.Append("(").Append(col.Count).Append(" items)");
}
foreach (object i in (IEnumerable)value)
{
if (useLongFormat)
valueStr.Append("\n").Append(indent);
DumpValue(i, valueStr, indent + " ");
}
}
else
valueStr.Append(value.ToString());
}
}
}
|