#region Copyright 2008-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Security.Permissions;
using System.Diagnostics;
using CSharpTest.Net.Logging;
using CSharpTest.Net.Logging.Implementation;
using System.Runtime.CompilerServices;
///
/// Provides an abstraction api for logging to various outputs. This class in the global namespace for a reason.
/// Since you may want to change log infrastructure at any time, it's important that no 'Using' statements are
/// required to access the log infrastructure. Secondly, a static class api can provide improved performance
/// over other possible types. We will be forcing the default configuration of log4net rather than requiring
/// each component to independently configure itself.
///
[System.Reflection.Obfuscation(Exclude = true)]
[System.Diagnostics.DebuggerNonUserCode()]
public static partial class Log
{
static Log()
{
Configuration.Configure();
}
#region Config Options
///
/// Provides configuration options for the Log subsystem
///
[System.Diagnostics.DebuggerNonUserCode()]
public static class Config
{
static Config()
{
//force the static c'tor on Log()
Log.IsVerboseEnabled.ToString();
}
/// Gets or sets the current log LogLevel
public static LogLevels Level
{
get { return Configuration.LogLevel; }
[MethodImpl(MethodImplOptions.NoInlining)]
set
{
if ((Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose) MessageQueue.Push(1, LogLevels.Verbose, null, String.Format("Log Level changed from {0} to {1}.", Configuration.LogLevel, value), null);
Configuration.LogLevel = value;
}
}
/// Gets or sets the current log LogLevel
public static LogOutputs Output
{
get { return Configuration.LogOutput; }
[MethodImpl(MethodImplOptions.NoInlining)]
set
{
if ((Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose) MessageQueue.Push(1, LogLevels.Verbose, null, String.Format("Log Output changed from {0} to {1}.", Configuration.LogOutput, value), null);
Configuration.LogOutput = value;
}
}
///
/// Gets or sets the availability of stack info etc. The following performance characterists were taken with a
/// simple example app running 10 threads each writing 1000 log statements in a tight-loop. The times below reflect
/// threading enabled (someone having called Log.AppStart()). The following hardware was used:
/// AMD Turion 64x2 TL-62 2.10 GHz, 3 GB RAM running Vista 32-bit SP1 (HP Notebook). The time indicated below
/// was estimated by taking the total number of milliseconds until all threads completed and dividing by the 10,000
/// messages that were written.
///
/// - Cost per call 0.005 ms with no context (None)
/// - Cost per call 0.035 ms with calling method (LogImmediateCaller)
/// - Cost per call 0.060 ms with calling method & assembly info (LogImmediateCaller | LogAddAssemblyInfo)
/// - Cost per call 0.160 ms with calling method & file info (LogImmediateCaller | LogAddFileInfo)
/// - Cost per call 0.190 ms with calling method & assembly info & file info (LogImmediateCaller | LogAddAssemblyInfo | LogAddFileInfo)
///
///
public static LogOptions Options
{
get { return Configuration.LogOption; }
[MethodImpl(MethodImplOptions.NoInlining)]
set
{
if ((Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose) MessageQueue.Push(1, LogLevels.Verbose, null, String.Format("Log Options changed from {0} to {1}.", Configuration.LogOption, value), null);
Configuration.LogOption = value;
}
}
/// Gets or sets the format provider to use when formatting strings
public static IFormatProvider FormatProvider { set { if( value != null ) Configuration.InnerFormatter = value; } }
/// Changes the log level required to write to a specific output device.
[MethodImpl(MethodImplOptions.NoInlining)]
public static LogLevels SetOutputLevel(LogOutputs outputToConfigure, LogLevels newLevel)
{
LogLevels old = LogLevels.None;
switch(outputToConfigure)
{
case LogOutputs.AspNetTrace: { old = Configuration.LEVEL_ASPNET; Configuration.LEVEL_ASPNET = newLevel; break; }
case LogOutputs.Console: { old = Configuration.LEVEL_CONSOLE; Configuration.LEVEL_CONSOLE = newLevel; break; }
case LogOutputs.EventLog: { old = Configuration.LEVEL_EVENTLOG; Configuration.LEVEL_EVENTLOG = newLevel; break; }
case LogOutputs.LogFile: { old = Configuration.LEVEL_LOGFILE; Configuration.LEVEL_LOGFILE = newLevel; break; }
case LogOutputs.TraceWrite: { old = Configuration.LEVEL_TRACE; Configuration.LEVEL_TRACE = newLevel; break; }
case LogOutputs.All:
{
Configuration.LEVEL_ASPNET = newLevel;
Configuration.LEVEL_CONSOLE = newLevel;
Configuration.LEVEL_EVENTLOG = newLevel;
Configuration.LEVEL_LOGFILE = newLevel;
Configuration.LEVEL_TRACE = newLevel;
break;
}
}
MessageQueue.Push(1, LogLevels.Verbose, null, String.Format("Log level for {0} changed from {1} to {2}.", outputToConfigure, old, newLevel), null);
return old;
}
///
/// Changes the output format used to write to a specific output device. The format of this string behaves just like
/// EventData.ToString(). The string can contain any public field or property available for the CSharpTest.Net.Logging.EventData
/// class surrounded by braces {} and yes, properties/fields are case sensative. The input string should look something
/// like the following examples:
/// "[{ManagedThreadId:D2}] {Level,8} - {Message}{Location}{Exception}" -- this is the default format of ToString()
/// "{EventTime:o} [{ProcessId:D4},{ManagedThreadId:D2}] {Level,8} - {Message}{Location}{Exception}" -- This is the default log file format.
///
[MethodImpl(MethodImplOptions.NoInlining)]
public static void SetOutputFormat(LogOutputs outputToConfigure, string newFormat)
{
newFormat = LogUtils.PrepareFormatString(newFormat);
switch(outputToConfigure)
{
case LogOutputs.AspNetTrace: { Configuration.FORMAT_ASPNET = newFormat; break; }
case LogOutputs.Console: { Configuration.FORMAT_CONSOLE = newFormat; break; }
case LogOutputs.EventLog: { Configuration.FORMAT_EVENTLOG = newFormat; break; }
case LogOutputs.LogFile: { Configuration.FORMAT_LOGFILE = newFormat; break; }
case LogOutputs.TraceWrite: { Configuration.FORMAT_TRACE = newFormat; break; }
case LogOutputs.All:
{
Configuration.FORMAT_ASPNET = newFormat;
Configuration.FORMAT_CONSOLE = newFormat;
Configuration.FORMAT_EVENTLOG = newFormat;
Configuration.FORMAT_LOGFILE = newFormat;
Configuration.FORMAT_TRACE = newFormat;
break;
}
}
MessageQueue.Push(1, LogLevels.Verbose, null, String.Format("Log output for {0} changed to: '{1}'.", outputToConfigure, newFormat), null);
}
///
/// Gets or sets the current log file name, insert '{0}' in the file's name to allow log rolling
///
public static string LogFile
{
get { return Configuration.CurrentLogFile; }
[MethodImpl(MethodImplOptions.NoInlining)]
set
{
string newname = value;
try
{
if (!System.IO.Path.IsPathRooted(newname))
newname = System.IO.Path.GetFullPath(newname);
Configuration.CurrentLogFile = new System.IO.FileInfo(newname).FullName;
MessageQueue.LogFileChanged();
}
catch (ArgumentException ae)
{
MessageQueue.Push(1, LogLevels.Error, ae, String.Format("Unable to set log path to: {0}", value), null);
}
}
}
/// Gets or sets the maximum size in bytes the log file is allowed to be before rolling to history
public static int LogFileMaxSize { get { return Configuration.FILE_SIZE_THREASHOLD; } set { Configuration.FILE_SIZE_THREASHOLD = Math.Max(8192, value); } }
/// Gets or sets the maximum number of history log files to keep on the system
public static int LogFileMaxHistory { get { return Configuration.FILE_MAX_HISTORY_SIZE; } set { Configuration.FILE_MAX_HISTORY_SIZE = Math.Max(1, value); } }
/// Sets the event log name we will write events to.
public static string EventLogName { get { return Configuration.EventLogName; } set { Configuration.EventLogName = value; } }
/// Sets the event log source we will write events with, It's up to you to register this value.
public static string EventLogSource { get { return Configuration.EventLogSource; } set { Configuration.EventLogSource = value; } }
}
#endregion
/// Returns true if 'Info' messages are being recorded.
public static bool IsInfoEnabled { get { return (Configuration.LogLevel & LogLevels.Info) == LogLevels.Info; } }
/// Returns true if 'Verbose' messages are being recorded.
public static bool IsVerboseEnabled { get { return (Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose; } }
// FYI, these are only for the benifit of the following methods...
private static readonly Exception e = null;
private static readonly string format = String.Empty;
private static readonly object[] args = new object[0];
///
/// Enables calls to Log.xxx() to be processed on another thread to improve throughput...
/// Place this in a using() statement within Main: using( new Log.AppStart("Some Name") )
/// This call (and Disponse) IS Thread safe and can be called multiple times either
/// concurrently, sequentially, nested, or overlapping calls are all permitted and handled.
///
public static IDisposable AppStart(string format, params object[] args) { return new MessageQueue.ThreadingControl(LogUtils.Format(format, args)); }
///
/// Pushes a string into the trace stack so that log messages appear with the 'context'
/// set to the information provided. The operation named should be performed by the
/// current thread and the IDisposable object returned should be disposed when the
/// operation completes.
///
///
/// using( Log.Start("Reading File") )
/// {
/// ... do something ...
/// }
///
///
///
/// An IDisposable object that should be destroyed by calling the Dispose()
/// method when the activity is complete.
public static IDisposable Start(string format, params object[] args) { return new TraceStack(LogUtils.Format(format, args)); }
///
/// Forces any left-behind calls to Start() to be closed.
///
public static void ClearStack() { TraceStack.Clear(); }
///
/// Write directly to the log reguardless of the currently configured log-LogLevel
///
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Write(string format, params object[] args) { MessageQueue.Push(1, LogLevels.None, e, format, args); }
/// Logs a Critical error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Critical(Exception e) { if ((Configuration.LogLevel & LogLevels.Critical) == LogLevels.Critical) MessageQueue.Push(1, LogLevels.Critical, e, format, args); }
/// Logs a Critical error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Critical(Exception e, string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Critical) == LogLevels.Critical) MessageQueue.Push(1, LogLevels.Critical, e, format, args); }
/// Logs a Critical error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Critical(string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Critical) == LogLevels.Critical) MessageQueue.Push(1, LogLevels.Critical, e, format, args); }
/// Logs an Error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Error(Exception e) { if ((Configuration.LogLevel & LogLevels.Error) == LogLevels.Error) MessageQueue.Push(1, LogLevels.Error, e, format, args); }
/// Logs an Error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Error(Exception e, string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Error) == LogLevels.Error) MessageQueue.Push(1, LogLevels.Error, e, format, args); }
/// Logs an Error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Error(string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Error) == LogLevels.Error) MessageQueue.Push(1, LogLevels.Error, e, format, args); }
/// Logs a Warning
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Warning(Exception e) { if ((Configuration.LogLevel & LogLevels.Warning) == LogLevels.Warning) MessageQueue.Push(1, LogLevels.Warning, e, format, args); }
/// Logs a Warning
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Warning(Exception e, string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Warning) == LogLevels.Warning) MessageQueue.Push(1, LogLevels.Warning, e, format, args); }
/// Logs a Warning
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Warning(string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Warning) == LogLevels.Warning) MessageQueue.Push(1, LogLevels.Warning, e, format, args); }
/// Logs a Info error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Info(Exception e) { if ((Configuration.LogLevel & LogLevels.Info) == LogLevels.Info) MessageQueue.Push(1, LogLevels.Info, e, format, args); }
/// Logs a Info error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Info(Exception e, string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Info) == LogLevels.Info) MessageQueue.Push(1, LogLevels.Info, e, format, args); }
/// Logs an Informational message
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Info(string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Info) == LogLevels.Info) MessageQueue.Push(1, LogLevels.Info, e, format, args); }
/// Logs a Verbose error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Verbose(Exception e) { if ((Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose) MessageQueue.Push(1, LogLevels.Verbose, e, format, args); }
/// Logs a Verbose error
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Verbose(Exception e, string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose) MessageQueue.Push(1, LogLevels.Verbose, e, format, args); }
/// Logs a Verbose message
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Verbose(string format, params object[] args) { if ((Configuration.LogLevel & LogLevels.Verbose) == LogLevels.Verbose) MessageQueue.Push(1, LogLevels.Verbose, e, format, args); }
}