Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2004 lines (1779 sloc) 76.2 KB
//===============================================================================
// LibLog
//
// https://github.com/damianh/LibLog
//===============================================================================
// Copyright © 2011-2015 Damian Hickey. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//===============================================================================
// ReSharper disable PossibleNullReferenceException
// Define LIBLOG_PORTABLE conditional compilation symbol for PCL compatibility
//
// Define LIBLOG_PUBLIC to enable ability to GET a logger (LogProvider.For<>() etc) from outside this library. NOTE:
// this can have unintended consequences of consumers of your library using your library to resolve a logger. If the
// reason is because you want to open this functionality to other projects within your solution,
// consider [InternalsVisibleTo] instead.
//
// Define LIBLOG_PROVIDERS_ONLY if your library provides its own logging API and you just want to use the
// LibLog providers internally to provide built in support for popular logging frameworks.
#pragma warning disable 1591
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Dropcraft.Common.Logging")]
[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Dropcraft.Common.Logging.Logger.#Invoke(Dropcraft.Common.Logging.LogLevel,System.Func`1<System.String>,System.Exception,System.Object[])")]
// If you copied this file manually, you need to change all "Dropcraft.Common" so not to clash with other libraries
// that use LibLog
#if LIBLOG_PROVIDERS_ONLY
namespace Dropcraft.Common.LibLog
#else
namespace Dropcraft.Common.Logging
#endif
{
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#if LIBLOG_PROVIDERS_ONLY
using Dropcraft.Common.LibLog.LogProviders;
#else
using Dropcraft.Common.Logging.LogProviders;
#endif
using System;
#if !LIBLOG_PROVIDERS_ONLY
using System.Diagnostics;
#if !LIBLOG_PORTABLE
using System.Runtime.CompilerServices;
#endif
#endif
#if LIBLOG_PROVIDERS_ONLY
internal
#else
public
#endif
delegate bool Logger(LogLevel logLevel, Func<string> messageFunc, Exception exception = null, params object[] formatParameters);
#if !LIBLOG_PROVIDERS_ONLY
/// <summary>
/// Simple interface that represent a logger.
/// </summary>
#if LIBLOG_PUBLIC
public
#else
internal
#endif
interface ILog
{
/// <summary>
/// Log a message the specified log level.
/// </summary>
/// <param name="logLevel">The log level.</param>
/// <param name="messageFunc">The message function.</param>
/// <param name="exception">An optional exception.</param>
/// <param name="formatParameters">Optional format parameters for the message generated by the messagefunc. </param>
/// <returns>true if the message was logged. Otherwise false.</returns>
/// <remarks>
/// Note to implementers: the message func should not be called if the loglevel is not enabled
/// so as not to incur performance penalties.
///
/// To check IsEnabled call Log with only LogLevel and check the return value, no event will be written.
/// </remarks>
bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception = null, params object[] formatParameters);
}
#endif
/// <summary>
/// The log level.
/// </summary>
#if LIBLOG_PROVIDERS_ONLY
internal
#else
public
#endif
enum LogLevel
{
Trace,
Debug,
Info,
Warn,
Error,
Fatal
}
#if !LIBLOG_PROVIDERS_ONLY
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static partial class LogExtensions
{
public static bool IsDebugEnabled(this ILog logger)
{
GuardAgainstNullLogger(logger);
return logger.Log(LogLevel.Debug, null);
}
public static bool IsErrorEnabled(this ILog logger)
{
GuardAgainstNullLogger(logger);
return logger.Log(LogLevel.Error, null);
}
public static bool IsFatalEnabled(this ILog logger)
{
GuardAgainstNullLogger(logger);
return logger.Log(LogLevel.Fatal, null);
}
public static bool IsInfoEnabled(this ILog logger)
{
GuardAgainstNullLogger(logger);
return logger.Log(LogLevel.Info, null);
}
public static bool IsTraceEnabled(this ILog logger)
{
GuardAgainstNullLogger(logger);
return logger.Log(LogLevel.Trace, null);
}
public static bool IsWarnEnabled(this ILog logger)
{
GuardAgainstNullLogger(logger);
return logger.Log(LogLevel.Warn, null);
}
public static void Debug(this ILog logger, Func<string> messageFunc)
{
GuardAgainstNullLogger(logger);
logger.Log(LogLevel.Debug, messageFunc);
}
public static void Debug(this ILog logger, string message)
{
if (logger.IsDebugEnabled())
{
logger.Log(LogLevel.Debug, message.AsFunc());
}
}
public static void DebugFormat(this ILog logger, string message, params object[] args)
{
if (logger.IsDebugEnabled())
{
logger.LogFormat(LogLevel.Debug, message, args);
}
}
public static void DebugException(this ILog logger, string message, Exception exception)
{
if (logger.IsDebugEnabled())
{
logger.Log(LogLevel.Debug, message.AsFunc(), exception);
}
}
public static void DebugException(this ILog logger, string message, Exception exception, params object[] formatParams)
{
if (logger.IsDebugEnabled())
{
logger.Log(LogLevel.Debug, message.AsFunc(), exception, formatParams);
}
}
public static void Error(this ILog logger, Func<string> messageFunc)
{
GuardAgainstNullLogger(logger);
logger.Log(LogLevel.Error, messageFunc);
}
public static void Error(this ILog logger, string message)
{
if (logger.IsErrorEnabled())
{
logger.Log(LogLevel.Error, message.AsFunc());
}
}
public static void ErrorFormat(this ILog logger, string message, params object[] args)
{
if (logger.IsErrorEnabled())
{
logger.LogFormat(LogLevel.Error, message, args);
}
}
public static void ErrorException(this ILog logger, string message, Exception exception, params object[] formatParams)
{
if (logger.IsErrorEnabled())
{
logger.Log(LogLevel.Error, message.AsFunc(), exception, formatParams);
}
}
public static void Fatal(this ILog logger, Func<string> messageFunc)
{
logger.Log(LogLevel.Fatal, messageFunc);
}
public static void Fatal(this ILog logger, string message)
{
if (logger.IsFatalEnabled())
{
logger.Log(LogLevel.Fatal, message.AsFunc());
}
}
public static void FatalFormat(this ILog logger, string message, params object[] args)
{
if (logger.IsFatalEnabled())
{
logger.LogFormat(LogLevel.Fatal, message, args);
}
}
public static void FatalException(this ILog logger, string message, Exception exception, params object[] formatParams)
{
if (logger.IsFatalEnabled())
{
logger.Log(LogLevel.Fatal, message.AsFunc(), exception, formatParams);
}
}
public static void Info(this ILog logger, Func<string> messageFunc)
{
GuardAgainstNullLogger(logger);
logger.Log(LogLevel.Info, messageFunc);
}
public static void Info(this ILog logger, string message)
{
if (logger.IsInfoEnabled())
{
logger.Log(LogLevel.Info, message.AsFunc());
}
}
public static void InfoFormat(this ILog logger, string message, params object[] args)
{
if (logger.IsInfoEnabled())
{
logger.LogFormat(LogLevel.Info, message, args);
}
}
public static void InfoException(this ILog logger, string message, Exception exception, params object[] formatParams)
{
if (logger.IsInfoEnabled())
{
logger.Log(LogLevel.Info, message.AsFunc(), exception, formatParams);
}
}
public static void Trace(this ILog logger, Func<string> messageFunc)
{
GuardAgainstNullLogger(logger);
logger.Log(LogLevel.Trace, messageFunc);
}
public static void Trace(this ILog logger, string message)
{
if (logger.IsTraceEnabled())
{
logger.Log(LogLevel.Trace, message.AsFunc());
}
}
public static void TraceFormat(this ILog logger, string message, params object[] args)
{
if (logger.IsTraceEnabled())
{
logger.LogFormat(LogLevel.Trace, message, args);
}
}
public static void TraceException(this ILog logger, string message, Exception exception, params object[] formatParams)
{
if (logger.IsTraceEnabled())
{
logger.Log(LogLevel.Trace, message.AsFunc(), exception, formatParams);
}
}
public static void Warn(this ILog logger, Func<string> messageFunc)
{
GuardAgainstNullLogger(logger);
logger.Log(LogLevel.Warn, messageFunc);
}
public static void Warn(this ILog logger, string message)
{
if (logger.IsWarnEnabled())
{
logger.Log(LogLevel.Warn, message.AsFunc());
}
}
public static void WarnFormat(this ILog logger, string message, params object[] args)
{
if (logger.IsWarnEnabled())
{
logger.LogFormat(LogLevel.Warn, message, args);
}
}
public static void WarnException(this ILog logger, string message, Exception exception, params object[] formatParams)
{
if (logger.IsWarnEnabled())
{
logger.Log(LogLevel.Warn, message.AsFunc(), exception, formatParams);
}
}
// ReSharper disable once UnusedParameter.Local
private static void GuardAgainstNullLogger(ILog logger)
{
if (logger == null)
{
throw new ArgumentNullException("logger");
}
}
private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args)
{
logger.Log(logLevel, message.AsFunc(), null, args);
}
// Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b
private static Func<T> AsFunc<T>(this T value) where T : class
{
return value.Return;
}
private static T Return<T>(this T value)
{
return value;
}
}
#endif
/// <summary>
/// Represents a way to get a <see cref="ILog"/>
/// </summary>
#if LIBLOG_PROVIDERS_ONLY
internal
#else
public
#endif
interface ILogProvider
{
/// <summary>
/// Gets the specified named logger.
/// </summary>
/// <param name="name">Name of the logger.</param>
/// <returns>The logger reference.</returns>
Logger GetLogger(string name);
/// <summary>
/// Opens a nested diagnostics context. Not supported in EntLib logging.
/// </summary>
/// <param name="message">The message to add to the diagnostics context.</param>
/// <returns>A disposable that when disposed removes the message from the context.</returns>
IDisposable OpenNestedContext(string message);
/// <summary>
/// Opens a mapped diagnostics context. Not supported in EntLib logging.
/// </summary>
/// <param name="key">A key.</param>
/// <param name="value">A value.</param>
/// <returns>A disposable that when disposed removes the map from the context.</returns>
IDisposable OpenMappedContext(string key, string value);
}
/// <summary>
/// Provides a mechanism to create instances of <see cref="ILog" /> objects.
/// </summary>
#if LIBLOG_PROVIDERS_ONLY
internal
#else
public
#endif
static class LogProvider
{
#if !LIBLOG_PROVIDERS_ONLY
/// <summary>
/// The disable logging environment variable. If the environment variable is set to 'true', then logging
/// will be disabled.
/// </summary>
public const string DisableLoggingEnvironmentVariable = "$rootnamespace$_LIBLOG_DISABLE";
private const string NullLogProvider = "Current Log Provider is not set. Call SetCurrentLogProvider " +
"with a non-null value first.";
private static dynamic s_currentLogProvider;
private static Action<ILogProvider> s_onCurrentLogProviderSet;
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static LogProvider()
{
IsDisabled = false;
}
/// <summary>
/// Sets the current log provider.
/// </summary>
/// <param name="logProvider">The log provider.</param>
public static void SetCurrentLogProvider(ILogProvider logProvider)
{
s_currentLogProvider = logProvider;
RaiseOnCurrentLogProviderSet();
}
/// <summary>
/// Gets or sets a value indicating whether this is logging is disabled.
/// </summary>
/// <value>
/// <c>true</c> if logging is disabled; otherwise, <c>false</c>.
/// </value>
public static bool IsDisabled { get; set; }
/// <summary>
/// Sets an action that is invoked when a consumer of your library has called SetCurrentLogProvider. It is
/// important that hook into this if you are using child libraries (especially ilmerged ones) that are using
/// LibLog (or other logging abstraction) so you adapt and delegate to them.
/// <see cref="SetCurrentLogProvider"/>
/// </summary>
internal static Action<ILogProvider> OnCurrentLogProviderSet
{
set
{
s_onCurrentLogProviderSet = value;
RaiseOnCurrentLogProviderSet();
}
}
internal static ILogProvider CurrentLogProvider
{
get
{
return s_currentLogProvider;
}
}
/// <summary>
/// Gets a logger for the specified type.
/// </summary>
/// <typeparam name="T">The type whose name will be used for the logger.</typeparam>
/// <returns>An instance of <see cref="ILog"/></returns>
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static ILog For<T>()
{
return GetLogger(typeof(T));
}
#if !LIBLOG_PORTABLE
/// <summary>
/// Gets a logger for the current class.
/// </summary>
/// <returns>An instance of <see cref="ILog"/></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static ILog GetCurrentClassLogger()
{
var stackFrame = new StackFrame(1, false);
return GetLogger(stackFrame.GetMethod().DeclaringType);
}
#endif
/// <summary>
/// Gets a logger for the specified type.
/// </summary>
/// <param name="type">The type whose name will be used for the logger.</param>
/// <returns>An instance of <see cref="ILog"/></returns>
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static ILog GetLogger(Type type)
{
return GetLogger(type.FullName);
}
/// <summary>
/// Gets a logger with the specified name.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>An instance of <see cref="ILog"/></returns>
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static ILog GetLogger(string name)
{
ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
return logProvider == null
? NoOpLogger.Instance
: (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name), () => IsDisabled);
}
/// <summary>
/// Opens a nested diagnostics context.
/// </summary>
/// <param name="message">A message.</param>
/// <returns>An <see cref="IDisposable"/> that closes context when disposed.</returns>
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")]
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static IDisposable OpenNestedContext(string message)
{
ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
return logProvider == null
? new DisposableAction(() => { })
: logProvider.OpenNestedContext(message);
}
/// <summary>
/// Opens a mapped diagnostics context.
/// </summary>
/// <param name="key">A key.</param>
/// <param name="value">A value.</param>
/// <returns>An <see cref="IDisposable"/> that closes context when disposed.</returns>
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")]
#if LIBLOG_PUBLIC
public
#else
internal
#endif
static IDisposable OpenMappedContext(string key, string value)
{
ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
return logProvider == null
? new DisposableAction(() => { })
: logProvider.OpenMappedContext(key, value);
}
#endif
#if LIBLOG_PROVIDERS_ONLY
private
#else
internal
#endif
delegate bool IsLoggerAvailable();
#if LIBLOG_PROVIDERS_ONLY
private
#else
internal
#endif
delegate ILogProvider CreateLogProvider();
#if LIBLOG_PROVIDERS_ONLY
private
#else
internal
#endif
static readonly List<Tuple<IsLoggerAvailable, CreateLogProvider>> LogProviderResolvers =
new List<Tuple<IsLoggerAvailable, CreateLogProvider>>
{
new Tuple<IsLoggerAvailable, CreateLogProvider>(SerilogLogProvider.IsLoggerAvailable, () => new SerilogLogProvider()),
new Tuple<IsLoggerAvailable, CreateLogProvider>(NLogLogProvider.IsLoggerAvailable, () => new NLogLogProvider()),
new Tuple<IsLoggerAvailable, CreateLogProvider>(Log4NetLogProvider.IsLoggerAvailable, () => new Log4NetLogProvider()),
new Tuple<IsLoggerAvailable, CreateLogProvider>(EntLibLogProvider.IsLoggerAvailable, () => new EntLibLogProvider()),
new Tuple<IsLoggerAvailable, CreateLogProvider>(LoupeLogProvider.IsLoggerAvailable, () => new LoupeLogProvider()),
};
#if !LIBLOG_PROVIDERS_ONLY
private static void RaiseOnCurrentLogProviderSet()
{
if (s_onCurrentLogProviderSet != null)
{
s_onCurrentLogProviderSet(s_currentLogProvider);
}
}
#endif
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
internal static ILogProvider ResolveLogProvider()
{
try
{
foreach (var providerResolver in LogProviderResolvers)
{
if (providerResolver.Item1())
{
return providerResolver.Item2();
}
}
}
catch (Exception ex)
{
#if LIBLOG_PORTABLE
Debug.WriteLine(
#else
Console.WriteLine(
#endif
"Exception occurred resolving a log provider. Logging for this assembly {0} is disabled. {1}",
typeof(LogProvider).GetAssemblyPortable().FullName,
ex);
}
return null;
}
#if !LIBLOG_PROVIDERS_ONLY
internal class NoOpLogger : ILog
{
internal static readonly NoOpLogger Instance = new NoOpLogger();
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
{
return false;
}
}
#endif
}
#if !LIBLOG_PROVIDERS_ONLY
internal class LoggerExecutionWrapper : ILog
{
private readonly Logger _logger;
private readonly Func<bool> _getIsDisabled;
internal const string FailedToGenerateLogMessage = "Failed to generate log message";
internal LoggerExecutionWrapper(Logger logger, Func<bool> getIsDisabled = null)
{
_logger = logger;
_getIsDisabled = getIsDisabled ?? (() => false);
}
internal Logger WrappedLogger
{
get { return _logger; }
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception = null, params object[] formatParameters)
{
if (_getIsDisabled())
{
return false;
}
#if !LIBLOG_PORTABLE
var envVar = Environment.GetEnvironmentVariable(LogProvider.DisableLoggingEnvironmentVariable);
if (envVar != null && envVar.Equals("true", StringComparison.OrdinalIgnoreCase))
{
return false;
}
#endif
if (messageFunc == null)
{
return _logger(logLevel, null);
}
Func<string> wrappedMessageFunc = () =>
{
try
{
return messageFunc();
}
catch (Exception ex)
{
Log(LogLevel.Error, () => FailedToGenerateLogMessage, ex);
}
return null;
};
return _logger(logLevel, wrappedMessageFunc, exception, formatParameters);
}
}
#endif
}
#if LIBLOG_PROVIDERS_ONLY
namespace Dropcraft.Common.LibLog.LogProviders
#else
namespace Dropcraft.Common.Logging.LogProviders
#endif
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#if !LIBLOG_PORTABLE
using System.Diagnostics;
#endif
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#if !LIBLOG_PORTABLE
using System.Text;
#endif
using System.Text.RegularExpressions;
internal abstract class LogProviderBase : ILogProvider
{
protected delegate IDisposable OpenNdc(string message);
protected delegate IDisposable OpenMdc(string key, string value);
private readonly Lazy<OpenNdc> _lazyOpenNdcMethod;
private readonly Lazy<OpenMdc> _lazyOpenMdcMethod;
private static readonly IDisposable NoopDisposableInstance = new DisposableAction();
protected LogProviderBase()
{
_lazyOpenNdcMethod
= new Lazy<OpenNdc>(GetOpenNdcMethod);
_lazyOpenMdcMethod
= new Lazy<OpenMdc>(GetOpenMdcMethod);
}
public abstract Logger GetLogger(string name);
public IDisposable OpenNestedContext(string message)
{
return _lazyOpenNdcMethod.Value(message);
}
public IDisposable OpenMappedContext(string key, string value)
{
return _lazyOpenMdcMethod.Value(key, value);
}
protected virtual OpenNdc GetOpenNdcMethod()
{
return _ => NoopDisposableInstance;
}
protected virtual OpenMdc GetOpenMdcMethod()
{
return (_, __) => NoopDisposableInstance;
}
}
internal class NLogLogProvider : LogProviderBase
{
private readonly Func<string, object> _getLoggerByNameDelegate;
private static bool s_providerIsAvailableOverride = true;
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "NLog")]
public NLogLogProvider()
{
if (!IsLoggerAvailable())
{
throw new InvalidOperationException("NLog.LogManager not found");
}
_getLoggerByNameDelegate = GetGetLoggerMethodCall();
}
public static bool ProviderIsAvailableOverride
{
get { return s_providerIsAvailableOverride; }
set { s_providerIsAvailableOverride = value; }
}
public override Logger GetLogger(string name)
{
return new NLogLogger(_getLoggerByNameDelegate(name)).Log;
}
public static bool IsLoggerAvailable()
{
return ProviderIsAvailableOverride && GetLogManagerType() != null;
}
protected override OpenNdc GetOpenNdcMethod()
{
Type ndcContextType = Type.GetType("NLog.NestedDiagnosticsContext, NLog") ?? Type.GetType("NLog.NestedDiagnosticsContext");
MethodInfo pushMethod = ndcContextType.GetMethodPortable("Push", typeof(string));
ParameterExpression messageParam = Expression.Parameter(typeof(string), "message");
MethodCallExpression pushMethodCall = Expression.Call(null, pushMethod, messageParam);
return Expression.Lambda<OpenNdc>(pushMethodCall, messageParam).Compile();
}
protected override OpenMdc GetOpenMdcMethod()
{
Type mdcContextType = Type.GetType("NLog.MappedDiagnosticsContext, NLog") ?? Type.GetType("NLog.MappedDiagnosticsContext");
MethodInfo setMethod = mdcContextType.GetMethodPortable("Set", typeof(string), typeof(string));
MethodInfo removeMethod = mdcContextType.GetMethodPortable("Remove", typeof(string));
ParameterExpression keyParam = Expression.Parameter(typeof(string), "key");
ParameterExpression valueParam = Expression.Parameter(typeof(string), "value");
MethodCallExpression setMethodCall = Expression.Call(null, setMethod, keyParam, valueParam);
MethodCallExpression removeMethodCall = Expression.Call(null, removeMethod, keyParam);
Action<string, string> set = Expression
.Lambda<Action<string, string>>(setMethodCall, keyParam, valueParam)
.Compile();
Action<string> remove = Expression
.Lambda<Action<string>>(removeMethodCall, keyParam)
.Compile();
return (key, value) =>
{
set(key, value);
return new DisposableAction(() => remove(key));
};
}
private static Type GetLogManagerType()
{
return Type.GetType("NLog.LogManager, NLog") ?? Type.GetType("NLog.LogManager");
}
private static Func<string, object> GetGetLoggerMethodCall()
{
Type logManagerType = GetLogManagerType();
MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string));
ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
MethodCallExpression methodCall = Expression.Call(null, method, nameParam);
return Expression.Lambda<Func<string, object>>(methodCall, nameParam).Compile();
}
internal class NLogLogger
{
private readonly dynamic _logger;
internal NLogLogger(dynamic logger)
{
_logger = logger;
}
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
{
if (messageFunc == null)
{
return IsLogLevelEnable(logLevel);
}
messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);
if (exception != null)
{
return LogException(logLevel, messageFunc, exception);
}
switch (logLevel)
{
case LogLevel.Debug:
if (_logger.IsDebugEnabled)
{
_logger.Debug(messageFunc());
return true;
}
break;
case LogLevel.Info:
if (_logger.IsInfoEnabled)
{
_logger.Info(messageFunc());
return true;
}
break;
case LogLevel.Warn:
if (_logger.IsWarnEnabled)
{
_logger.Warn(messageFunc());
return true;
}
break;
case LogLevel.Error:
if (_logger.IsErrorEnabled)
{
_logger.Error(messageFunc());
return true;
}
break;
case LogLevel.Fatal:
if (_logger.IsFatalEnabled)
{
_logger.Fatal(messageFunc());
return true;
}
break;
default:
if (_logger.IsTraceEnabled)
{
_logger.Trace(messageFunc());
return true;
}
break;
}
return false;
}
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
private bool LogException(LogLevel logLevel, Func<string> messageFunc, Exception exception)
{
switch (logLevel)
{
case LogLevel.Debug:
if (_logger.IsDebugEnabled)
{
_logger.DebugException(messageFunc(), exception);
return true;
}
break;
case LogLevel.Info:
if (_logger.IsInfoEnabled)
{
_logger.InfoException(messageFunc(), exception);
return true;
}
break;
case LogLevel.Warn:
if (_logger.IsWarnEnabled)
{
_logger.WarnException(messageFunc(), exception);
return true;
}
break;
case LogLevel.Error:
if (_logger.IsErrorEnabled)
{
_logger.ErrorException(messageFunc(), exception);
return true;
}
break;
case LogLevel.Fatal:
if (_logger.IsFatalEnabled)
{
_logger.FatalException(messageFunc(), exception);
return true;
}
break;
default:
if (_logger.IsTraceEnabled)
{
_logger.TraceException(messageFunc(), exception);
return true;
}
break;
}
return false;
}
private bool IsLogLevelEnable(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Debug:
return _logger.IsDebugEnabled;
case LogLevel.Info:
return _logger.IsInfoEnabled;
case LogLevel.Warn:
return _logger.IsWarnEnabled;
case LogLevel.Error:
return _logger.IsErrorEnabled;
case LogLevel.Fatal:
return _logger.IsFatalEnabled;
default:
return _logger.IsTraceEnabled;
}
}
}
}
internal class Log4NetLogProvider : LogProviderBase
{
private readonly Func<string, object> _getLoggerByNameDelegate;
private static bool s_providerIsAvailableOverride = true;
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")]
public Log4NetLogProvider()
{
if (!IsLoggerAvailable())
{
throw new InvalidOperationException("log4net.LogManager not found");
}
_getLoggerByNameDelegate = GetGetLoggerMethodCall();
}
public static bool ProviderIsAvailableOverride
{
get { return s_providerIsAvailableOverride; }
set { s_providerIsAvailableOverride = value; }
}
public override Logger GetLogger(string name)
{
return new Log4NetLogger(_getLoggerByNameDelegate(name)).Log;
}
internal static bool IsLoggerAvailable()
{
return ProviderIsAvailableOverride && GetLogManagerType() != null;
}
protected override OpenNdc GetOpenNdcMethod()
{
Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net") ?? Type.GetType("log4net.LogicalThreadContext");
PropertyInfo stacksProperty = logicalThreadContextType.GetPropertyPortable("Stacks");
Type logicalThreadContextStacksType = stacksProperty.PropertyType;
PropertyInfo stacksIndexerProperty = logicalThreadContextStacksType.GetPropertyPortable("Item");
Type stackType = stacksIndexerProperty.PropertyType;
MethodInfo pushMethod = stackType.GetMethodPortable("Push");
ParameterExpression messageParameter =
Expression.Parameter(typeof(string), "message");
// message => LogicalThreadContext.Stacks.Item["NDC"].Push(message);
MethodCallExpression callPushBody =
Expression.Call(
Expression.Property(Expression.Property(null, stacksProperty),
stacksIndexerProperty,
Expression.Constant("NDC")),
pushMethod,
messageParameter);
OpenNdc result =
Expression.Lambda<OpenNdc>(callPushBody, messageParameter)
.Compile();
return result;
}
protected override OpenMdc GetOpenMdcMethod()
{
Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net") ?? Type.GetType("log4net.LogicalThreadContext");
PropertyInfo propertiesProperty = logicalThreadContextType.GetPropertyPortable("Properties");
Type logicalThreadContextPropertiesType = propertiesProperty.PropertyType;
PropertyInfo propertiesIndexerProperty = logicalThreadContextPropertiesType.GetPropertyPortable("Item");
MethodInfo removeMethod = logicalThreadContextPropertiesType.GetMethodPortable("Remove");
ParameterExpression keyParam = Expression.Parameter(typeof(string), "key");
ParameterExpression valueParam = Expression.Parameter(typeof(string), "value");
MemberExpression propertiesExpression = Expression.Property(null, propertiesProperty);
// (key, value) => LogicalThreadContext.Properties.Item[key] = value;
BinaryExpression setProperties = Expression.Assign(Expression.Property(propertiesExpression, propertiesIndexerProperty, keyParam), valueParam);
// key => LogicalThreadContext.Properties.Remove(key);
MethodCallExpression removeMethodCall = Expression.Call(propertiesExpression, removeMethod, keyParam);
Action<string, string> set = Expression
.Lambda<Action<string, string>>(setProperties, keyParam, valueParam)
.Compile();
Action<string> remove = Expression
.Lambda<Action<string>>(removeMethodCall, keyParam)
.Compile();
return (key, value) =>
{
set(key, value);
return new DisposableAction(() => remove(key));
};
}
private static Type GetLogManagerType()
{
return Type.GetType("log4net.LogManager, log4net") ?? Type.GetType("log4net.LogManager");
}
private static Func<string, object> GetGetLoggerMethodCall()
{
Type logManagerType = GetLogManagerType();
MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string));
ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
MethodCallExpression methodCall = Expression.Call(null, method, nameParam);
return Expression.Lambda<Func<string, object>>(methodCall, nameParam).Compile();
}
internal class Log4NetLogger
{
private readonly dynamic _logger;
private static Type s_callerStackBoundaryType;
private static readonly object CallerStackBoundaryTypeSync = new object();
private readonly object _levelDebug;
private readonly object _levelInfo;
private readonly object _levelWarn;
private readonly object _levelError;
private readonly object _levelFatal;
private readonly Func<object, object, bool> _isEnabledForDelegate;
private readonly Action<object, Type, object, string, Exception> _logDelegate;
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")]
internal Log4NetLogger(dynamic logger)
{
_logger = logger.Logger;
var logEventLevelType = Type.GetType("log4net.Core.Level, log4net") ?? Type.GetType("log4net.Core.Level");
if (logEventLevelType == null)
{
throw new InvalidOperationException("Type log4net.Core.Level was not found.");
}
var levelFields = logEventLevelType.GetFieldsPortable().ToList();
_levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null);
_levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null);
_levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null);
_levelError = levelFields.First(x => x.Name == "Error").GetValue(null);
_levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null);
// Func<object, object, bool> isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); }
var loggerType = Type.GetType("log4net.Core.ILogger, log4net") ?? Type.GetType("log4net.Core.ILogger");
if (loggerType == null)
{
throw new InvalidOperationException("Type log4net.Core.ILogger, was not found.");
}
MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType);
ParameterExpression instanceParam = Expression.Parameter(typeof(object));
UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType);
ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type));
ParameterExpression levelParam = Expression.Parameter(typeof(object));
ParameterExpression messageParam = Expression.Parameter(typeof(string));
UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType);
MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast);
_isEnabledForDelegate = Expression.Lambda<Func<object, object, bool>>(isEnabledMethodCall, instanceParam, levelParam).Compile();
// Action<object, object, string, Exception> Log =
// (logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Write(callerStackBoundaryDeclaringType, level, message, exception); }
MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log",
typeof(Type),
logEventLevelType,
typeof(string),
typeof(Exception));
ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception));
var writeMethodExp = Expression.Call(
instanceCast,
writeExceptionMethodInfo,
callerStackBoundaryDeclaringTypeParam,
levelCast,
messageParam,
exceptionParam);
_logDelegate = Expression.Lambda<Action<object, Type, object, string, Exception>>(
writeMethodExp,
instanceParam,
callerStackBoundaryDeclaringTypeParam,
levelParam,
messageParam,
exceptionParam).Compile();
}
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
{
if (messageFunc == null)
{
return IsLogLevelEnable(logLevel);
}
if (!IsLogLevelEnable(logLevel))
{
return false;
}
messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);
// determine correct caller - this might change due to jit optimizations with method inlining
if (s_callerStackBoundaryType == null)
{
lock (CallerStackBoundaryTypeSync)
{
#if !LIBLOG_PORTABLE
StackTrace stack = new StackTrace();
Type thisType = GetType();
s_callerStackBoundaryType = Type.GetType("LoggerExecutionWrapper");
for (var i = 1; i < stack.FrameCount; i++)
{
if (!IsInTypeHierarchy(thisType, stack.GetFrame(i).GetMethod().DeclaringType))
{
s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType;
break;
}
}
#else
s_callerStackBoundaryType = typeof (LoggerExecutionWrapper);
#endif
}
}
var translatedLevel = TranslateLevel(logLevel);
_logDelegate(_logger, s_callerStackBoundaryType, translatedLevel, messageFunc(), exception);
return true;
}
private static bool IsInTypeHierarchy(Type currentType, Type checkType)
{
while (currentType != null && currentType != typeof(object))
{
if (currentType == checkType)
{
return true;
}
currentType = currentType.GetBaseTypePortable();
}
return false;
}
private bool IsLogLevelEnable(LogLevel logLevel)
{
var level = TranslateLevel(logLevel);
return _isEnabledForDelegate(_logger, level);
}
private object TranslateLevel(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace:
case LogLevel.Debug:
return _levelDebug;
case LogLevel.Info:
return _levelInfo;
case LogLevel.Warn:
return _levelWarn;
case LogLevel.Error:
return _levelError;
case LogLevel.Fatal:
return _levelFatal;
default:
throw new ArgumentOutOfRangeException("logLevel", logLevel, null);
}
}
}
}
internal class EntLibLogProvider : LogProviderBase
{
private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging";
private const string TypeTemplateFallback = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}";
private static bool s_providerIsAvailableOverride = true;
private static readonly Type LogEntryType;
private static readonly Type LoggerType;
private static readonly Type TraceEventTypeType;
private static readonly Action<string, string, int> WriteLogEntry;
private static readonly Func<string, int, bool> ShouldLogEntry;
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static EntLibLogProvider()
{
LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")) ??
Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplateFallback, "LogEntry"));
LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger")) ??
Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplateFallback, "Logger"));
TraceEventTypeType = TraceEventTypeValues.Type;
if (LogEntryType == null
|| TraceEventTypeType == null
|| LoggerType == null)
{
return;
}
WriteLogEntry = GetWriteLogEntry();
ShouldLogEntry = GetShouldLogEntry();
}
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnterpriseLibrary")]
public EntLibLogProvider()
{
if (!IsLoggerAvailable())
{
throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found");
}
}
public static bool ProviderIsAvailableOverride
{
get { return s_providerIsAvailableOverride; }
set { s_providerIsAvailableOverride = value; }
}
public override Logger GetLogger(string name)
{
return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry).Log;
}
internal static bool IsLoggerAvailable()
{
return ProviderIsAvailableOverride
&& TraceEventTypeType != null
&& LogEntryType != null;
}
private static Action<string, string, int> GetWriteLogEntry()
{
// new LogEntry(...)
var logNameParameter = Expression.Parameter(typeof(string), "logName");
var messageParameter = Expression.Parameter(typeof(string), "message");
var severityParameter = Expression.Parameter(typeof(int), "severity");
MemberInitExpression memberInit = GetWriteLogExpression(
messageParameter,
Expression.Convert(severityParameter, TraceEventTypeType),
logNameParameter);
//Logger.Write(new LogEntry(....));
MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("Write", LogEntryType);
var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit);
return Expression.Lambda<Action<string, string, int>>(
writeLogEntryExpression,
logNameParameter,
messageParameter,
severityParameter).Compile();
}
private static Func<string, int, bool> GetShouldLogEntry()
{
// new LogEntry(...)
var logNameParameter = Expression.Parameter(typeof(string), "logName");
var severityParameter = Expression.Parameter(typeof(int), "severity");
MemberInitExpression memberInit = GetWriteLogExpression(
Expression.Constant("***dummy***"),
Expression.Convert(severityParameter, TraceEventTypeType),
logNameParameter);
//Logger.Write(new LogEntry(....));
MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("ShouldLog", LogEntryType);
var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit);
return Expression.Lambda<Func<string, int, bool>>(
writeLogEntryExpression,
logNameParameter,
severityParameter).Compile();
}
private static MemberInitExpression GetWriteLogExpression(Expression message,
Expression severityParameter, ParameterExpression logNameParameter)
{
var entryType = LogEntryType;
MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType),
Expression.Bind(entryType.GetPropertyPortable("Message"), message),
Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter),
Expression.Bind(
entryType.GetPropertyPortable("TimeStamp"),
Expression.Property(null, typeof(DateTime).GetPropertyPortable("UtcNow"))),
Expression.Bind(
entryType.GetPropertyPortable("Categories"),
Expression.ListInit(
Expression.New(typeof(List<string>)),
typeof(List<string>).GetMethodPortable("Add", typeof(string)),
logNameParameter)));
return memberInit;
}
internal class EntLibLogger
{
private readonly string _loggerName;
private readonly Action<string, string, int> _writeLog;
private readonly Func<string, int, bool> _shouldLog;
internal EntLibLogger(string loggerName, Action<string, string, int> writeLog, Func<string, int, bool> shouldLog)
{
_loggerName = loggerName;
_writeLog = writeLog;
_shouldLog = shouldLog;
}
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
{
var severity = MapSeverity(logLevel);
if (messageFunc == null)
{
return _shouldLog(_loggerName, severity);
}
messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);
if (exception != null)
{
return LogException(logLevel, messageFunc, exception);
}
_writeLog(_loggerName, messageFunc(), severity);
return true;
}
public bool LogException(LogLevel logLevel, Func<string> messageFunc, Exception exception)
{
var severity = MapSeverity(logLevel);
var message = messageFunc() + Environment.NewLine + exception;
_writeLog(_loggerName, message, severity);
return true;
}
private static int MapSeverity(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Fatal:
return TraceEventTypeValues.Critical;
case LogLevel.Error:
return TraceEventTypeValues.Error;
case LogLevel.Warn:
return TraceEventTypeValues.Warning;
case LogLevel.Info:
return TraceEventTypeValues.Information;
default:
return TraceEventTypeValues.Verbose;
}
}
}
}
internal class SerilogLogProvider : LogProviderBase
{
private readonly Func<string, object> _getLoggerByNameDelegate;
private static bool s_providerIsAvailableOverride = true;
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")]
public SerilogLogProvider()
{
if (!IsLoggerAvailable())
{
throw new InvalidOperationException("Serilog.Log not found");
}
_getLoggerByNameDelegate = GetForContextMethodCall();
}
public static bool ProviderIsAvailableOverride
{
get { return s_providerIsAvailableOverride; }
set { s_providerIsAvailableOverride = value; }
}
public override Logger GetLogger(string name)
{
return new SerilogLogger(_getLoggerByNameDelegate(name)).Log;
}
internal static bool IsLoggerAvailable()
{
return ProviderIsAvailableOverride && GetLogManagerType() != null;
}
protected override OpenNdc GetOpenNdcMethod()
{
return message => GetPushProperty()("NDC", message);
}
protected override OpenMdc GetOpenMdcMethod()
{
return (key, value) => GetPushProperty()(key, value);
}
private static Func<string, string, IDisposable> GetPushProperty()
{
Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx") ?? Type.GetType("Serilog.Context.LogContext");
MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable(
"PushProperty",
typeof(string),
typeof(object),
typeof(bool));
ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");
ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects");
MethodCallExpression pushPropertyMethodCall = Expression
.Call(null, pushPropertyMethod, nameParam, valueParam, destructureObjectParam);
var pushProperty = Expression
.Lambda<Func<string, object, bool, IDisposable>>(
pushPropertyMethodCall,
nameParam,
valueParam,
destructureObjectParam)
.Compile();
return (key, value) => pushProperty(key, value, false);
}
private static Type GetLogManagerType()
{
return Type.GetType("Serilog.Log, Serilog") ?? Type.GetType("Serilog.Log");
}
private static Func<string, object> GetForContextMethodCall()
{
Type logManagerType = GetLogManagerType();
MethodInfo method = logManagerType.GetMethodPortable("ForContext", typeof(string), typeof(object), typeof(bool));
ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName");
ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");
ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects");
MethodCallExpression methodCall = Expression.Call(null, method, new Expression[]
{
propertyNameParam,
valueParam,
destructureObjectsParam
});
var func = Expression.Lambda<Func<string, object, bool, object>>(
methodCall,
propertyNameParam,
valueParam,
destructureObjectsParam)
.Compile();
return name => func("SourceContext", name, false);
}
internal class SerilogLogger
{
private readonly object _logger;
private static readonly object DebugLevel;
private static readonly object ErrorLevel;
private static readonly object FatalLevel;
private static readonly object InformationLevel;
private static readonly object VerboseLevel;
private static readonly object WarningLevel;
private static readonly Func<object, object, bool> IsEnabled;
private static readonly Action<object, object, string, object[]> Write;
private static readonly Action<object, object, Exception, string, object[]> WriteException;
[SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogEventLevel")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")]
static SerilogLogger()
{
var logEventLevelType = Type.GetType("Serilog.Events.LogEventLevel, Serilog") ?? Type.GetType("Serilog.Events.LogEventLevel");
if (logEventLevelType == null)
{
throw new InvalidOperationException("Type Serilog.Events.LogEventLevel was not found.");
}
DebugLevel = Enum.Parse(logEventLevelType, "Debug", false);
ErrorLevel = Enum.Parse(logEventLevelType, "Error", false);
FatalLevel = Enum.Parse(logEventLevelType, "Fatal", false);
InformationLevel = Enum.Parse(logEventLevelType, "Information", false);
VerboseLevel = Enum.Parse(logEventLevelType, "Verbose", false);
WarningLevel = Enum.Parse(logEventLevelType, "Warning", false);
// Func<object, object, bool> isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); }
var loggerType = Type.GetType("Serilog.ILogger, Serilog") ?? Type.GetType("Serilog.ILogger");
if (loggerType == null)
{
throw new InvalidOperationException("Type Serilog.ILogger was not found.");
}
MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabled", logEventLevelType);
ParameterExpression instanceParam = Expression.Parameter(typeof(object));
UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType);
ParameterExpression levelParam = Expression.Parameter(typeof(object));
UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType);
MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast);
IsEnabled = Expression.Lambda<Func<object, object, bool>>(isEnabledMethodCall, instanceParam, levelParam).Compile();
// Action<object, object, string> Write =
// (logger, level, message, params) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, params); }
MethodInfo writeMethodInfo = loggerType.GetMethodPortable("Write", logEventLevelType, typeof(string), typeof(object[]));
ParameterExpression messageParam = Expression.Parameter(typeof(string));
ParameterExpression propertyValuesParam = Expression.Parameter(typeof(object[]));
MethodCallExpression writeMethodExp = Expression.Call(
instanceCast,
writeMethodInfo,
levelCast,
messageParam,
propertyValuesParam);
var expression = Expression.Lambda<Action<object, object, string, object[]>>(
writeMethodExp,
instanceParam,
levelParam,
messageParam,
propertyValuesParam);
Write = expression.Compile();
// Action<object, object, string, Exception> WriteException =
// (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); }
MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Write",
logEventLevelType,
typeof(Exception),
typeof(string),
typeof(object[]));
ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception));
writeMethodExp = Expression.Call(
instanceCast,
writeExceptionMethodInfo,
levelCast,
exceptionParam,
messageParam,
propertyValuesParam);
WriteException = Expression.Lambda<Action<object, object, Exception, string, object[]>>(
writeMethodExp,
instanceParam,
levelParam,
exceptionParam,
messageParam,
propertyValuesParam).Compile();
}
internal SerilogLogger(object logger)
{
_logger = logger;
}
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
{
var translatedLevel = TranslateLevel(logLevel);
if (messageFunc == null)
{
return IsEnabled(_logger, translatedLevel);
}
if (!IsEnabled(_logger, translatedLevel))
{
return false;
}
if (exception != null)
{
LogException(translatedLevel, messageFunc, exception, formatParameters);
}
else
{
LogMessage(translatedLevel, messageFunc, formatParameters);
}
return true;
}
private void LogMessage(object translatedLevel, Func<string> messageFunc, object[] formatParameters)
{
Write(_logger, translatedLevel, messageFunc(), formatParameters);
}
private void LogException(object logLevel, Func<string> messageFunc, Exception exception, object[] formatParams)
{
WriteException(_logger, logLevel, exception, messageFunc(), formatParams);
}
private static object TranslateLevel(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Fatal:
return FatalLevel;
case LogLevel.Error:
return ErrorLevel;
case LogLevel.Warn:
return WarningLevel;
case LogLevel.Info:
return InformationLevel;
case LogLevel.Trace:
return VerboseLevel;
default:
return DebugLevel;
}
}
}
}
internal class LoupeLogProvider : LogProviderBase
{
/// <summary>
/// The form of the Loupe Log.Write method we're using
/// </summary>
internal delegate void WriteDelegate(
int severity,
string logSystem,
int skipFrames,
Exception exception,
bool attributeToException,
int writeMode,
string detailsXml,
string category,
string caption,
string description,
params object[] args
);
private static bool s_providerIsAvailableOverride = true;
private readonly WriteDelegate _logWriteDelegate;
public LoupeLogProvider()
{
if (!IsLoggerAvailable())
{
throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found");
}
_logWriteDelegate = GetLogWriteDelegate();
}
/// <summary>
/// Gets or sets a value indicating whether [provider is available override]. Used in tests.
/// </summary>
/// <value>
/// <c>true</c> if [provider is available override]; otherwise, <c>false</c>.
/// </value>
public static bool ProviderIsAvailableOverride
{
get { return s_providerIsAvailableOverride; }
set { s_providerIsAvailableOverride = value; }
}
public override Logger GetLogger(string name)
{
return new LoupeLogger(name, _logWriteDelegate).Log;
}
public static bool IsLoggerAvailable()
{
return ProviderIsAvailableOverride && GetLogManagerType() != null;
}
private static Type GetLogManagerType()
{
return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent") ?? Type.GetType("Gibraltar.Agent.Log");
}
private static WriteDelegate GetLogWriteDelegate()
{
Type logManagerType = GetLogManagerType();
Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent") ?? Type.GetType("Gibraltar.Agent.LogMessageSeverity");
Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent") ?? Type.GetType("Gibraltar.Agent.LogWriteMode");
MethodInfo method = logManagerType.GetMethodPortable(
"Write",
logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool),
logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[]));
var callDelegate = (WriteDelegate)method.CreateDelegate(typeof(WriteDelegate));
return callDelegate;
}
internal class LoupeLogger
{
private const string LogSystem = "LibLog";
private readonly string _category;
private readonly WriteDelegate _logWriteDelegate;
private readonly int _skipLevel;
internal LoupeLogger(string category, WriteDelegate logWriteDelegate)
{
_category = category;
_logWriteDelegate = logWriteDelegate;
#if DEBUG
_skipLevel = 2;
#else
_skipLevel = 1;
#endif
}
public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
{
if (messageFunc == null)
{
//nothing to log..
return true;
}
messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);
_logWriteDelegate(ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null,
_category, null, messageFunc.Invoke());
return true;
}
private static int ToLogMessageSeverity(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace:
return TraceEventTypeValues.Verbose;
case LogLevel.Debug:
return TraceEventTypeValues.Verbose;
case LogLevel.Info:
return TraceEventTypeValues.Information;
case LogLevel.Warn:
return TraceEventTypeValues.Warning;
case LogLevel.Error:
return TraceEventTypeValues.Error;
case LogLevel.Fatal:
return TraceEventTypeValues.Critical;
default:
throw new ArgumentOutOfRangeException("logLevel");
}
}
}
}
internal static class TraceEventTypeValues
{
internal static readonly Type Type;
internal static readonly int Verbose;
internal static readonly int Information;
internal static readonly int Warning;
internal static readonly int Error;
internal static readonly int Critical;
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static TraceEventTypeValues()
{
var assembly = typeof(Uri).GetAssemblyPortable(); // This is to get to the System.dll assembly in a PCL compatible way.
if (assembly == null)
{
return;
}
Type = assembly.GetType("System.Diagnostics.TraceEventType");
if (Type == null) return;
Verbose = (int)Enum.Parse(Type, "Verbose", false);
Information = (int)Enum.Parse(Type, "Information", false);
Warning = (int)Enum.Parse(Type, "Warning", false);
Error = (int)Enum.Parse(Type, "Error", false);
Critical = (int)Enum.Parse(Type, "Critical", false);
}
}
internal static class LogMessageFormatter
{
private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}");
/// <summary>
/// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string:
/// For example: Log("Log message to {user}", user). This only works with serilog, but as the user of LibLog, you don't know if serilog is actually
/// used. So, this class simulates that. it will replace any text in {curly braces} with an index number.
///
/// "Log {message} to {user}" would turn into => "Log {0} to {1}". Then the format parameters are handled using regular .net string.Format.
/// </summary>
/// <param name="messageBuilder">The message builder.</param>
/// <param name="formatParameters">The format parameters.</param>
/// <returns></returns>
public static Func<string> SimulateStructuredLogging(Func<string> messageBuilder, object[] formatParameters)
{
if (formatParameters == null || formatParameters.Length == 0)
{
return messageBuilder;
}
return () =>
{
string targetMessage = messageBuilder();
int argumentIndex = 0;
foreach (Match match in Pattern.Matches(targetMessage))
{
int notUsed;
if (!int.TryParse(match.Value.Substring(1, match.Value.Length - 2), out notUsed))
{
targetMessage = ReplaceFirst(targetMessage, match.Value,
"{" + argumentIndex++ + "}");
}
}
try
{
return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters);
}
catch (FormatException ex)
{
throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex);
}
};
}
private static string ReplaceFirst(string text, string search, string replace)
{
int pos = text.IndexOf(search, StringComparison.Ordinal);
if (pos < 0)
{
return text;
}
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
}
}
internal static class TypeExtensions
{
internal static MethodInfo GetMethodPortable(this Type type, string name)
{
#if LIBLOG_PORTABLE
return type.GetRuntimeMethods().SingleOrDefault(m => m.Name == name);
#else
return type.GetMethod(name);
#endif
}
internal static MethodInfo GetMethodPortable(this Type type, string name, params Type[] types)
{
#if LIBLOG_PORTABLE
return type.GetRuntimeMethod(name, types);
#else
return type.GetMethod(name, types);
#endif
}
internal static PropertyInfo GetPropertyPortable(this Type type, string name)
{
#if LIBLOG_PORTABLE
return type.GetRuntimeProperty(name);
#else
return type.GetProperty(name);
#endif
}
internal static IEnumerable<FieldInfo> GetFieldsPortable(this Type type)
{
#if LIBLOG_PORTABLE
return type.GetRuntimeFields();
#else
return type.GetFields();
#endif
}
internal static Type GetBaseTypePortable(this Type type)
{
#if LIBLOG_PORTABLE
return type.GetTypeInfo().BaseType;
#else
return type.BaseType;
#endif
}
#if LIBLOG_PORTABLE
internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo)
{
return propertyInfo.GetMethod;
}
internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo)
{
return propertyInfo.SetMethod;
}
#endif
#if !LIBLOG_PORTABLE
internal static object CreateDelegate(this MethodInfo methodInfo, Type delegateType)
{
return Delegate.CreateDelegate(delegateType, methodInfo);
}
#endif
internal static Assembly GetAssemblyPortable(this Type type)
{
#if LIBLOG_PORTABLE
return type.GetTypeInfo().Assembly;
#else
return type.Assembly;
#endif
}
}
internal class DisposableAction : IDisposable
{
private readonly Action _onDispose;
public DisposableAction(Action onDispose = null)
{
_onDispose = onDispose;
}
public void Dispose()
{
if (_onDispose != null)
{
_onDispose();
}
}
}
}