diff --git a/.gitignore b/.gitignore index 3eb5a821..2515b681 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ $tf*/ #Nuget downloaded files packages/ deps/ + +# Visual Studio 2017 Solution files +.vs/ diff --git a/QtSharp.CLI/ConsoleLogger.cs b/QtSharp.CLI/ConsoleLogger.cs deleted file mode 100644 index bcf8864e..00000000 --- a/QtSharp.CLI/ConsoleLogger.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; - -namespace QtSharp.CLI -{ - public class ConsoleLogger - { - - /// Original Console Output Stream. - /// Original Console Output Stream. - public TextWriter ConsoleStdOutput { get; set; } - - /// Original Console Output Stream. - /// Original Console Output Stream. - public TextWriter ConsoleErrOutput { get; set; } - - /// Path to the Log File. - /// Path to the Log File. - public string LogFilePath - { - get - { - return logFilePath; - } - } - protected string logFilePath; - - /// Filestream used for output. - protected FileStream fstream { get; set; } - - /// Filestream Writer used for output. - protected StreamWriter fstreamwriter { get; set; } - - /// Default constructor. - public ConsoleLogger() - { - ConsoleStdOutput = Console.Out; - ConsoleErrOutput = Console.Error; - logFilePath = null; - } - - /// Sets the log file output. - /// Filename of the log file to use. - public void SetLogFile(string filename) - { - logFilePath = Path.Combine("logs", filename); - } - - /// Starts console redirection. - public void Start() - { - Stop(); - fstream = new FileStream(logFilePath, FileMode.Create); - fstreamwriter = new StreamWriter(fstream); - Console.SetOut(fstreamwriter); - Console.SetError(fstreamwriter); - } - - /// Stops console redirection. - public void Stop() - { - Console.SetOut(ConsoleStdOutput); - Console.SetError(ConsoleErrOutput); - if (fstreamwriter != null) fstreamwriter.Close(); - if (fstream != null) fstream.Close(); - } - - /// Creates log directory. - public void CreateLogDirectory() - { - if (Directory.Exists("logs") == false) Directory.CreateDirectory("logs"); - } - } -} diff --git a/QtSharp.CLI/Helpers/CppSharpLog.cs b/QtSharp.CLI/Helpers/CppSharpLog.cs new file mode 100644 index 00000000..f7899386 --- /dev/null +++ b/QtSharp.CLI/Helpers/CppSharpLog.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using CppSharp; +using QtSharp.CLI.Logging; + +namespace QtSharp.CLI.Helpers +{ + /// Used to override CppSharp's default Logging output. + public class CppSharpLog : IDiagnostics + { + /// Gets the LibLog logger instance. + /// LibLog Logger. + private static ILog Log => _Log ?? (_Log = LogProvider.GetCurrentClassLogger()); + private static ILog _Log; + + /// Default constructor. + public CppSharpLog() + { + Indents = new Stack(); + Level = DiagnosticKind.Message; + } + + /// The number of indents to add to the message. + public Stack Indents; + + /// Normally a cutoff for the logging level, not used here since SerilOg handles that. + public DiagnosticKind Level { get; set; } + + /// Handle the logging message. + /// The log information. + public void Emit(DiagnosticInfo info) { + var currentIndent = Indents.Sum(); + var message = new string(' ', currentIndent) + info.Message; + var level = DiagnosticKind_To_LibLog(info.Kind); + Log.Log(level, () => message); + } + + /// Increases the indent of the log output. + /// amount to increase the indent. + public void PushIndent(int level = 4) { + Indents.Push(level); + } + + /// Decrease the indent of the log output. + public void PopIndent() { + Indents.Pop(); + } + + /// Convert DiagnosticKind loglevel to LibLog loglevel + /// CppSharp DiagnosticKind loglevel + /// LibLog LogLevel. + private static LogLevel DiagnosticKind_To_LibLog(DiagnosticKind level) + { + switch (level) + { + case DiagnosticKind.Error: + return LogLevel.Error; + case DiagnosticKind.Warning: + return LogLevel.Warn; + case DiagnosticKind.Message: + return LogLevel.Info; + case DiagnosticKind.Debug: + return LogLevel.Debug; + default: + return LogLevel.Info; + } + } + } +} diff --git a/QtSharp.CLI/Helpers/LibLog.cs b/QtSharp.CLI/Helpers/LibLog.cs new file mode 100644 index 00000000..d41a139e --- /dev/null +++ b/QtSharp.CLI/Helpers/LibLog.cs @@ -0,0 +1,2450 @@ +// +//=============================================================================== +// LibLog +// +// https://github.com/damianh/LibLog +//=============================================================================== +// Copyright © 2011-2017 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 = "QtSharp.CLI.Logging")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "QtSharp.CLI.Logging.Logger.#Invoke(QtSharp.CLI.Logging.LogLevel,System.Func`1,System.Exception,System.Object[])")] + +// If you copied this file manually, you need to change all "YourRootNameSpace" so not to clash with other libraries +// that use LibLog +#if LIBLOG_PROVIDERS_ONLY +namespace QtSharp.CLI.LibLog +#else +namespace QtSharp.CLI.Logging +#endif +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; +#if LIBLOG_PROVIDERS_ONLY + using LogProviders; +#else + using 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 messageFunc, Exception exception = null, params object[] formatParameters); + +#if !LIBLOG_PROVIDERS_ONLY + /// + /// Simple interface that represent a logger. + /// +#if LIBLOG_PUBLIC + public +#else + internal +#endif + interface ILog + { + /// + /// Log a message the specified log level. + /// + /// The log level. + /// The message function. + /// An optional exception. + /// Optional format parameters for the message generated by the messagefunc. + /// true if the message was logged. Otherwise false. + /// + /// 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. + /// + bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters); + } +#endif + + /// + /// The log level. + /// +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + enum LogLevel + { + Trace, + Debug, + Info, + Warn, + Error, + Fatal + } + +#if !LIBLOG_PROVIDERS_ONLY +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif +#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 messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Debug, WrapLogInternal(messageFunc)); + } + + public static void Debug(this ILog logger, string message) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc()); + } + } + + public static void Debug(this ILog logger, string message, params object[] args) + { + logger.DebugFormat(message, args); + } + + public static void Debug(this ILog logger, Exception exception, string message, params object[] args) + { + logger.DebugException(message, exception, args); + } + + 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 messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Error, WrapLogInternal(messageFunc)); + } + + public static void Error(this ILog logger, string message) + { + if (logger.IsErrorEnabled()) + { + logger.Log(LogLevel.Error, message.AsFunc()); + } + } + + public static void Error(this ILog logger, string message, params object[] args) + { + logger.ErrorFormat(message, args); + } + + public static void Error(this ILog logger, Exception exception, string message, params object[] args) + { + logger.ErrorException(message, exception, args); + } + + 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 messageFunc) + { + logger.Log(LogLevel.Fatal, WrapLogInternal(messageFunc)); + } + + public static void Fatal(this ILog logger, string message) + { + if (logger.IsFatalEnabled()) + { + logger.Log(LogLevel.Fatal, message.AsFunc()); + } + } + + public static void Fatal(this ILog logger, string message, params object[] args) + { + logger.FatalFormat(message, args); + } + + public static void Fatal(this ILog logger, Exception exception, string message, params object[] args) + { + logger.FatalException(message, exception, args); + } + + 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 messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Info, WrapLogInternal(messageFunc)); + } + + public static void Info(this ILog logger, string message) + { + if (logger.IsInfoEnabled()) + { + logger.Log(LogLevel.Info, message.AsFunc()); + } + } + + public static void Info(this ILog logger, string message, params object[] args) + { + logger.InfoFormat(message, args); + } + + public static void Info(this ILog logger, Exception exception, string message, params object[] args) + { + logger.InfoException(message, exception, args); + } + + 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 messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Trace, WrapLogInternal(messageFunc)); + } + + public static void Trace(this ILog logger, string message) + { + if (logger.IsTraceEnabled()) + { + logger.Log(LogLevel.Trace, message.AsFunc()); + } + } + + public static void Trace(this ILog logger, string message, params object[] args) + { + logger.TraceFormat(message, args); + } + + public static void Trace(this ILog logger, Exception exception, string message, params object[] args) + { + logger.TraceException(message, exception, args); + } + + 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 messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Warn, WrapLogInternal(messageFunc)); + } + + public static void Warn(this ILog logger, string message) + { + if (logger.IsWarnEnabled()) + { + logger.Log(LogLevel.Warn, message.AsFunc()); + } + } + + public static void Warn(this ILog logger, string message, params object[] args) + { + logger.WarnFormat(message, args); + } + + public static void Warn(this ILog logger, Exception exception, string message, params object[] args) + { + logger.WarnException(message, exception, args); + } + + 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 AsFunc(this T value) where T : class + { + return value.Return; + } + + private static T Return(this T value) + { + return value; + } + + // Allow passing callsite-logger-type to LogProviderBase using messageFunc + internal static Func WrapLogSafeInternal(LoggerExecutionWrapper logger, Func messageFunc) + { + Func wrappedMessageFunc = () => + { + try + { + return messageFunc(); + } + catch (Exception ex) + { + logger.WrappedLogger(LogLevel.Error, () => LoggerExecutionWrapper.FailedToGenerateLogMessage, ex); + } + return null; + }; + return wrappedMessageFunc; + } + + // Allow passing callsite-logger-type to LogProviderBase using messageFunc + private static Func WrapLogInternal(Func messageFunc) + { + Func wrappedMessageFunc = () => + { + return messageFunc(); + }; + return wrappedMessageFunc; + } + } +#endif + + /// + /// Represents a way to get a + /// +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + interface ILogProvider + { + /// + /// Gets the specified named logger. + /// + /// Name of the logger. + /// The logger reference. + Logger GetLogger(string name); + + /// + /// Opens a nested diagnostics context. Not supported in EntLib logging. + /// + /// The message to add to the diagnostics context. + /// A disposable that when disposed removes the message from the context. + IDisposable OpenNestedContext(string message); + + /// + /// Opens a mapped diagnostics context. Not supported in EntLib logging. + /// + /// A key. + /// A value. + /// A disposable that when disposed removes the map from the context. + IDisposable OpenMappedContext(string key, object value, bool destructure = false); + } + + /// + /// Provides a mechanism to create instances of objects. + /// +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + static class LogProvider + { +#if !LIBLOG_PROVIDERS_ONLY + 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 s_onCurrentLogProviderSet; + private static Lazy s_resolvedLogProvider = new Lazy(() => ForceResolveLogProvider()); + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static LogProvider() + { + IsDisabled = false; + } + + /// + /// Sets the current log provider. + /// + /// The log provider. + public static void SetCurrentLogProvider(ILogProvider logProvider) + { + s_currentLogProvider = logProvider; + + RaiseOnCurrentLogProviderSet(); + } + + /// + /// Gets or sets a value indicating whether this is logging is disabled. + /// + /// + /// true if logging is disabled; otherwise, false. + /// + public static bool IsDisabled { get; set; } + + /// + /// 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. + /// + /// + internal static Action OnCurrentLogProviderSet + { + set + { + s_onCurrentLogProviderSet = value; + RaiseOnCurrentLogProviderSet(); + } + } + + internal static ILogProvider CurrentLogProvider + { + get + { + return s_currentLogProvider; + } + } + + /// + /// Gets a logger for the specified type. + /// + /// The type whose name will be used for the logger. + /// An instance of +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog For() + { + return GetLogger(typeof(T)); + } + +#if !LIBLOG_PORTABLE + /// + /// Gets a logger for the current class. + /// + /// An instance of + [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 + + /// + /// Gets a logger for the specified type. + /// + /// The type whose name will be used for the logger. + /// If the type is null then this name will be used as the log name instead + /// An instance of +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog GetLogger(Type type, string fallbackTypeName = "System.Object") + { + // If the type passed in is null then fallback to the type name specified + return GetLogger(type != null ? type.FullName : fallbackTypeName); + } + + /// + /// Gets a logger with the specified name. + /// + /// The name. + /// An instance of +#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); + } + + /// + /// Opens a nested diagnostics context. + /// + /// A message. + /// An that closes context when disposed. + [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); + } + + /// + /// Opens a mapped diagnostics context. + /// + /// A key. + /// A value. + /// An that closes context when disposed. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")] +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static IDisposable OpenMappedContext(string key, object value, bool destructure = false) + { + ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider(); + + return logProvider == null + ? new DisposableAction(() => { }) + : logProvider.OpenMappedContext(key, value, destructure); + } +#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> LogProviderResolvers = + new List> + { + new Tuple(SerilogLogProvider.IsLoggerAvailable, () => new SerilogLogProvider()), + new Tuple(NLogLogProvider.IsLoggerAvailable, () => new NLogLogProvider()), + new Tuple(Log4NetLogProvider.IsLoggerAvailable, () => new Log4NetLogProvider()), + new Tuple(EntLibLogProvider.IsLoggerAvailable, () => new EntLibLogProvider()), + new Tuple(LoupeLogProvider.IsLoggerAvailable, () => new LoupeLogProvider()), + }; + +#if !LIBLOG_PROVIDERS_ONLY + private static void RaiseOnCurrentLogProviderSet() + { + if (s_onCurrentLogProviderSet != null) + { + s_onCurrentLogProviderSet(s_currentLogProvider); + } + } +#endif + + internal static ILogProvider ResolveLogProvider() + { + return s_resolvedLogProvider.Value; + } + + [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 ForceResolveLogProvider() + { + 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 +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class NoOpLogger : ILog + { + internal static readonly NoOpLogger Instance = new NoOpLogger(); + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + return false; + } + } +#endif + } + +#if !LIBLOG_PROVIDERS_ONLY +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class LoggerExecutionWrapper : ILog + { + private readonly Logger _logger; + private readonly ICallSiteExtension _callsiteLogger; + private readonly Func _getIsDisabled; + internal const string FailedToGenerateLogMessage = "Failed to generate log message"; + + Func _lastExtensionMethod; + + internal LoggerExecutionWrapper(Logger logger, Func getIsDisabled = null) + { + _logger = logger; + _callsiteLogger = new CallSiteExtension(); + _getIsDisabled = getIsDisabled ?? (() => false); + } + + internal Logger WrappedLogger + { + get { return _logger; } + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters) + { + if (_getIsDisabled()) + { + return false; + } + if (messageFunc == null) + { + return _logger(logLevel, null); + } + +#if !LIBLOG_PORTABLE + // Callsite HACK - Using the messageFunc to provide the callsite-logger-type + var lastExtensionMethod = _lastExtensionMethod; + if (lastExtensionMethod == null || !lastExtensionMethod.Equals(messageFunc)) + { + // Callsite HACK - Cache the last validated messageFunc as Equals is faster than type-check + lastExtensionMethod = null; + var methodType = messageFunc.Method.DeclaringType; + if (methodType == typeof(LogExtensions) || (methodType != null && methodType.DeclaringType == typeof(LogExtensions))) + { + lastExtensionMethod = messageFunc; + } + } + + if (lastExtensionMethod != null) + { + // Callsite HACK - LogExtensions has called virtual ILog interface method to get here, callsite-stack is good + _lastExtensionMethod = lastExtensionMethod; + return _logger(logLevel, LogExtensions.WrapLogSafeInternal(this, messageFunc), exception, formatParameters); + } + else +#endif + { + Func wrappedMessageFunc = () => + { + try + { + return messageFunc(); + } + catch (Exception ex) + { + _logger(LogLevel.Error, () => FailedToGenerateLogMessage, ex); + } + return null; + }; + + // Callsite HACK - Need to ensure proper callsite stack without inlining, so calling the logger within a virtual interface method + return _callsiteLogger.Log(_logger, logLevel, wrappedMessageFunc, exception, formatParameters); + } + } + + interface ICallSiteExtension + { + bool Log(Logger logger, LogLevel logLevel, Func messageFunc, Exception exception, object[] formatParameters); + } + + class CallSiteExtension : ICallSiteExtension + { + bool ICallSiteExtension.Log(Logger logger, LogLevel logLevel, Func messageFunc, Exception exception, object[] formatParameters) + { + return logger(logLevel, messageFunc, exception, formatParameters); + } + } + } +#endif +} + +#if LIBLOG_PROVIDERS_ONLY +namespace QtSharp.CLI.LibLog.LogProviders +#else +namespace QtSharp.CLI.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; + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal abstract class LogProviderBase : ILogProvider + { + protected delegate IDisposable OpenNdc(string message); + protected delegate IDisposable OpenMdc(string key, object value, bool destructure); + + private readonly Lazy _lazyOpenNdcMethod; + private readonly Lazy _lazyOpenMdcMethod; + private static readonly IDisposable NoopDisposableInstance = new DisposableAction(); + + protected LogProviderBase() + { + _lazyOpenNdcMethod + = new Lazy(GetOpenNdcMethod); + _lazyOpenMdcMethod + = new Lazy(GetOpenMdcMethod); + } + + public abstract Logger GetLogger(string name); + + public IDisposable OpenNestedContext(string message) + { + return _lazyOpenNdcMethod.Value(message); + } + + public IDisposable OpenMappedContext(string key, object value, bool destructure = false) + { + return _lazyOpenMdcMethod.Value(key, value, destructure); + } + + protected virtual OpenNdc GetOpenNdcMethod() + { + return _ => NoopDisposableInstance; + } + + protected virtual OpenMdc GetOpenMdcMethod() + { + return (_, __, ___) => NoopDisposableInstance; + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class NLogLogProvider : LogProviderBase + { + private readonly Func _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"); + MethodInfo pushMethod = ndcContextType.GetMethodPortable("Push", typeof(string)); + ParameterExpression messageParam = Expression.Parameter(typeof(string), "message"); + MethodCallExpression pushMethodCall = Expression.Call(null, pushMethod, messageParam); + return Expression.Lambda(pushMethodCall, messageParam).Compile(); + } + + protected override OpenMdc GetOpenMdcMethod() + { + Type mdcContextType = Type.GetType("NLog.MappedDiagnosticsContext, NLog"); + + 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 set = Expression + .Lambda>(setMethodCall, keyParam, valueParam) + .Compile(); + Action remove = Expression + .Lambda>(removeMethodCall, keyParam) + .Compile(); + + return (key, value, _) => + { + set(key, value.ToString()); + return new DisposableAction(() => remove(key)); + }; + } + + private static Type GetLogManagerType() + { + return Type.GetType("NLog.LogManager, NLog"); + } + + private static Func 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>(methodCall, nameParam).Compile(); + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class NLogLogger + { + private readonly dynamic _logger; + + private static Func _logEventInfoFact; + + private static readonly object _levelTrace; + private static readonly object _levelDebug; + private static readonly object _levelInfo; + private static readonly object _levelWarn; + private static readonly object _levelError; + private static readonly object _levelFatal; + + static NLogLogger() + { + try + { + var logEventLevelType = Type.GetType("NLog.LogLevel, NLog"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type NLog.LogLevel was not found."); + } + + var levelFields = logEventLevelType.GetFieldsPortable().ToList(); + _levelTrace = levelFields.First(x => x.Name == "Trace").GetValue(null); + _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); + + var logEventInfoType = Type.GetType("NLog.LogEventInfo, NLog"); + if (logEventInfoType == null) + { + throw new InvalidOperationException("Type NLog.LogEventInfo was not found."); + } + + ConstructorInfo loggingEventConstructor = + logEventInfoType.GetConstructorPortable(logEventLevelType, typeof(string), typeof(IFormatProvider), typeof(string), typeof(object[]), typeof(Exception)); + + ParameterExpression loggerNameParam = Expression.Parameter(typeof(string)); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + + NewExpression newLoggingEventExpression = + Expression.New(loggingEventConstructor, + levelCast, + loggerNameParam, + Expression.Constant(null, typeof(IFormatProvider)), + messageParam, + Expression.Constant(null, typeof(object[])), + exceptionParam + ); + + _logEventInfoFact = Expression.Lambda>(newLoggingEventExpression, + loggerNameParam, levelParam, messageParam, exceptionParam).Compile(); + } + catch { } + } + + internal NLogLogger(dynamic logger) + { + _logger = logger; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsLogLevelEnable(logLevel); + } + + var callsiteMessageFunc = messageFunc; + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + if (_logEventInfoFact != null) + { + if (IsLogLevelEnable(logLevel)) + { + Type callsiteLoggerType = typeof(NLogLogger); +#if !LIBLOG_PORTABLE + // Callsite HACK - Extract the callsite-logger-type from the messageFunc + var methodType = callsiteMessageFunc.Method.DeclaringType; + if (methodType == typeof(LogExtensions) || (methodType != null && methodType.DeclaringType == typeof(LogExtensions))) + { + callsiteLoggerType = typeof(LogExtensions); + } + else if (methodType == typeof(LoggerExecutionWrapper) || (methodType != null && methodType.DeclaringType == typeof(LoggerExecutionWrapper))) + { + callsiteLoggerType = typeof(LoggerExecutionWrapper); + } +#endif + var nlogLevel = this.TranslateLevel(logLevel); + var nlogEvent = _logEventInfoFact(_logger.Name, nlogLevel, messageFunc(), exception); + _logger.Log(callsiteLoggerType, nlogEvent); + return true; + } + return false; + } + + 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 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; + } + } + + private object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + return _levelTrace; + 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); + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class Log4NetLogProvider : LogProviderBase + { + private readonly Func _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"); + 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(callPushBody, messageParameter) + .Compile(); + + return result; + } + + protected override OpenMdc GetOpenMdcMethod() + { + Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net"); + 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 set = Expression + .Lambda>(setProperties, keyParam, valueParam) + .Compile(); + + Action remove = Expression + .Lambda>(removeMethodCall, keyParam) + .Compile(); + + return (key, value, _) => + { + set(key, value.ToString()); + return new DisposableAction(() => remove(key)); + }; + } + + private static Type GetLogManagerType() + { + return Type.GetType("log4net.LogManager, log4net"); + } + + private static Func 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>(methodCall, nameParam).Compile(); + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class Log4NetLogger + { + private readonly dynamic _logger; + private static Type s_callerStackBoundaryType; + private static readonly object CallerStackBoundaryTypeSync = new object(); + + private static readonly object _levelDebug; + private static readonly object _levelInfo; + private static readonly object _levelWarn; + private static readonly object _levelError; + private static readonly object _levelFatal; + private static readonly Func _isEnabledForDelegate; + private static readonly Action _logDelegate; + private static readonly Func _createLoggingEvent; + private static readonly Action _loggingEventPropertySetter; + + static Log4NetLogger() + { + var logEventLevelType = Type.GetType("log4net.Core.Level, log4net"); + 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 isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("log4net.Core.ILogger, log4net"); + if (loggerType == null) + { + throw new InvalidOperationException("Type log4net.Core.ILogger, was not found."); + } + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + _isEnabledForDelegate = GetIsEnabledFor(loggerType, logEventLevelType, instanceCast, levelCast, instanceParam, levelParam); + + Type loggingEventType = Type.GetType("log4net.Core.LoggingEvent, log4net"); + + _createLoggingEvent = GetCreateLoggingEvent(instanceParam, instanceCast, levelParam, levelCast, loggingEventType); + + _logDelegate = GetLogDelegate(loggerType, loggingEventType, instanceCast, instanceParam); + + _loggingEventPropertySetter = GetLoggingEventPropertySetter(loggingEventType); + } + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + internal Log4NetLogger(dynamic logger) + { + _logger = logger.Logger; + } + + private static Action GetLogDelegate(Type loggerType, Type loggingEventType, UnaryExpression instanceCast, + ParameterExpression instanceParam) + { + //Action Log = + //(logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Log(new LoggingEvent(callerStackBoundaryDeclaringType, logger.Repository, logger.Name, level, message, exception)); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log", + loggingEventType); + + ParameterExpression loggingEventParameter = + Expression.Parameter(typeof(object), "loggingEvent"); + + UnaryExpression loggingEventCasted = + Expression.Convert(loggingEventParameter, loggingEventType); + + var writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + loggingEventCasted); + + var logDelegate = Expression.Lambda>( + writeMethodExp, + instanceParam, + loggingEventParameter).Compile(); + + return logDelegate; + } + + private static Func GetCreateLoggingEvent(ParameterExpression instanceParam, UnaryExpression instanceCast, ParameterExpression levelParam, UnaryExpression levelCast, Type loggingEventType) + { + ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + + PropertyInfo repositoryProperty = loggingEventType.GetPropertyPortable("Repository"); + PropertyInfo levelProperty = loggingEventType.GetPropertyPortable("Level"); + + ConstructorInfo loggingEventConstructor = + loggingEventType.GetConstructorPortable(typeof(Type), repositoryProperty.PropertyType, typeof(string), levelProperty.PropertyType, typeof(object), typeof(Exception)); + + //Func Log = + //(logger, callerStackBoundaryDeclaringType, level, message, exception) => new LoggingEvent(callerStackBoundaryDeclaringType, ((ILogger)logger).Repository, ((ILogger)logger).Name, (Level)level, message, exception); } + NewExpression newLoggingEventExpression = + Expression.New(loggingEventConstructor, + callerStackBoundaryDeclaringTypeParam, + Expression.Property(instanceCast, "Repository"), + Expression.Property(instanceCast, "Name"), + levelCast, + messageParam, + exceptionParam); + + var createLoggingEvent = + Expression.Lambda>( + newLoggingEventExpression, + instanceParam, + callerStackBoundaryDeclaringTypeParam, + levelParam, + messageParam, + exceptionParam) + .Compile(); + + return createLoggingEvent; + } + + private static Func GetIsEnabledFor(Type loggerType, Type logEventLevelType, + UnaryExpression instanceCast, + UnaryExpression levelCast, + ParameterExpression instanceParam, + ParameterExpression levelParam) + { + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + + Func result = + Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam) + .Compile(); + + return result; + } + + private static Action GetLoggingEventPropertySetter(Type loggingEventType) + { + ParameterExpression loggingEventParameter = Expression.Parameter(typeof(object), "loggingEvent"); + ParameterExpression keyParameter = Expression.Parameter(typeof(string), "key"); + ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value"); + + PropertyInfo propertiesProperty = loggingEventType.GetPropertyPortable("Properties"); + PropertyInfo item = propertiesProperty.PropertyType.GetPropertyPortable("Item"); + + // ((LoggingEvent)loggingEvent).Properties[key] = value; + var body = + Expression.Assign( + Expression.Property( + Expression.Property(Expression.Convert(loggingEventParameter, loggingEventType), + propertiesProperty), item, keyParameter), valueParameter); + + Action result = + Expression.Lambda> + (body, loggingEventParameter, keyParameter, + valueParameter) + .Compile(); + + return result; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsLogLevelEnable(logLevel); + } + + if (!IsLogLevelEnable(logLevel)) + { + return false; + } + + string message = messageFunc(); + + IEnumerable patternMatches; + + string formattedMessage = + LogMessageFormatter.FormatStructuredMessage(message, + formatParameters, + out patternMatches); + + // 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); + + object loggingEvent = _createLoggingEvent(_logger, s_callerStackBoundaryType, translatedLevel, formattedMessage, exception); + + PopulateProperties(loggingEvent, patternMatches, formatParameters); + + _logDelegate(_logger, loggingEvent); + + return true; + } + + private void PopulateProperties(object loggingEvent, IEnumerable patternMatches, object[] formatParameters) + { + IEnumerable> keyToValue = + patternMatches.Zip(formatParameters, + (key, value) => new KeyValuePair(key, value)); + + foreach (KeyValuePair keyValuePair in keyToValue) + { + _loggingEventPropertySetter(loggingEvent, keyValuePair.Key, keyValuePair.Value); + } + } + + 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); + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class EntLibLogProvider : LogProviderBase + { + private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging"; + 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 WriteLogEntry; + private static readonly Func ShouldLogEntry; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static EntLibLogProvider() + { + LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")); + LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "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 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>( + writeLogEntryExpression, + logNameParameter, + messageParameter, + severityParameter).Compile(); + } + + private static Func 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>( + 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)), + typeof(List).GetMethodPortable("Add", typeof(string)), + logNameParameter))); + return memberInit; + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class EntLibLogger + { + private readonly string _loggerName; + private readonly Action _writeLog; + private readonly Func _shouldLog; + + internal EntLibLogger(string loggerName, Action writeLog, Func shouldLog) + { + _loggerName = loggerName; + _writeLog = writeLog; + _shouldLog = shouldLog; + } + + public bool Log(LogLevel logLevel, Func 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 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; + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class SerilogLogProvider : LogProviderBase + { + private readonly Func _getLoggerByNameDelegate; + private static bool s_providerIsAvailableOverride = true; + private static Func _pushProperty; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + public SerilogLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Serilog.Log not found"); + } + _getLoggerByNameDelegate = GetForContextMethodCall(); + _pushProperty = GetPushProperty(); + } + + 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 => _pushProperty("NDC", message, false); + } + + protected override OpenMdc GetOpenMdcMethod() + { + return (key, value, destructure) => _pushProperty(key, value, destructure); + } + + private static Func GetPushProperty() + { + Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog") ?? + Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); + + 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>( + pushPropertyMethodCall, + nameParam, + valueParam, + destructureObjectParam) + .Compile(); + + return (key, value, destructure) => pushProperty(key, value, destructure); + } + + private static Type GetLogManagerType() + { + return Type.GetType("Serilog.Log, Serilog"); + } + + private static Func 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>( + methodCall, + propertyNameParam, + valueParam, + destructureObjectsParam) + .Compile(); + return name => func("SourceContext", name, false); + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + 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 IsEnabled; + private static readonly Action Write; + private static readonly Action 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"); + 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 isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("Serilog.ILogger, Serilog"); + 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>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + + // Action 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>( + writeMethodExp, + instanceParam, + levelParam, + messageParam, + propertyValuesParam); + Write = expression.Compile(); + + // Action 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>( + writeMethodExp, + instanceParam, + levelParam, + exceptionParam, + messageParam, + propertyValuesParam).Compile(); + } + + internal SerilogLogger(object logger) + { + _logger = logger; + } + + public bool Log(LogLevel logLevel, Func 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 messageFunc, object[] formatParameters) + { + Write(_logger, translatedLevel, messageFunc(), formatParameters); + } + + private void LogException(object logLevel, Func 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; + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class LoupeLogProvider : LogProviderBase + { + /// + /// The form of the Loupe Log.Write method we're using + /// + 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(); + } + + /// + /// Gets or sets a value indicating whether [provider is available override]. Used in tests. + /// + /// + /// true if [provider is available override]; otherwise, false. + /// + 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"); + } + + private static WriteDelegate GetLogWriteDelegate() + { + Type logManagerType = GetLogManagerType(); + Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent"); + Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent"); + + 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; + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + 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 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"); + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + 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); + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal static class LogMessageFormatter + { + //private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); +#if LIBLOG_PORTABLE + private static readonly Regex Pattern = new Regex(@"(?[^\d{][^ }]*)}"); +#else + private static readonly Regex Pattern = new Regex(@"(?[^ :{}]+)(?:[^}]+)?}", RegexOptions.Compiled); +#endif + + /// + /// 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. + /// + /// The message builder. + /// The format parameters. + /// + public static Func SimulateStructuredLogging(Func messageBuilder, object[] formatParameters) + { + if (formatParameters == null || formatParameters.Length == 0) + { + return messageBuilder; + } + + return () => + { + string targetMessage = messageBuilder(); + IEnumerable patternMatches; + return FormatStructuredMessage(targetMessage, formatParameters, out patternMatches); + }; + } + + 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); + } + + public static string FormatStructuredMessage(string targetMessage, object[] formatParameters, out IEnumerable patternMatches) + { + if (formatParameters.Length == 0) + { + patternMatches = Enumerable.Empty(); + return targetMessage; + } + + List processedArguments = new List(); + patternMatches = processedArguments; + + foreach (Match match in Pattern.Matches(targetMessage)) + { + var arg = match.Groups["arg"].Value; + + int notUsed; + if (!int.TryParse(arg, out notUsed)) + { + int argumentIndex = processedArguments.IndexOf(arg); + if (argumentIndex == -1) + { + argumentIndex = processedArguments.Count; + processedArguments.Add(arg); + } + + targetMessage = ReplaceFirst(targetMessage, match.Value, + "{" + argumentIndex + match.Groups["format"].Value + "}"); + } + } + 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); + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal static class TypeExtensions + { + internal static ConstructorInfo GetConstructorPortable(this Type type, params Type[] types) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().DeclaredConstructors.FirstOrDefault + (constructor => + constructor.GetParameters() + .Select(parameter => parameter.ParameterType) + .SequenceEqual(types)); +#else + return type.GetConstructor(types); +#endif + } + + 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 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 + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class DisposableAction : IDisposable + { + private readonly Action _onDispose; + + public DisposableAction(Action onDispose = null) + { + _onDispose = onDispose; + } + + public void Dispose() + { + if (_onDispose != null) + { + _onDispose(); + } + } + } +} diff --git a/QtSharp.CLI/Helpers/SerilogSetup.cs b/QtSharp.CLI/Helpers/SerilogSetup.cs new file mode 100644 index 00000000..7b01d847 --- /dev/null +++ b/QtSharp.CLI/Helpers/SerilogSetup.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using Serilog; +using Serilog.Sinks.SystemConsole.Themes; + +namespace QtSharp.CLI.Helpers +{ + /// The setup of Serilog for logging. + public class SerilogSetup + { + /// Full pathname of the log file. + public static string LogFilePath = @"logs\qtsharp1.txt"; + + /// Setup logging without a Dttm. + public static Serilog.Core.Logger GetLogger_Nodttm() + { + var outputTemplate = "{Message:lj}{NewLine}{Exception}"; + var theme = AnsiConsoleTheme.Code; + + // Setup Serilog Configuration + var serilogcfg = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Console(outputTemplate: outputTemplate, theme: theme); + + return serilogcfg.CreateLogger(); + } + + /// Setup logging with a Dttm. + public static Serilog.Core.Logger GetLogger_dttm() + { + var outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"; + var theme = AnsiConsoleTheme.Code; + + // Setup Serilog Configuration + var serilogcfg = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Console(outputTemplate: outputTemplate, theme: theme); + + // Optionally add in file logging + SetupFileLogging(serilogcfg); + + return serilogcfg.CreateLogger(); + } + + /// Sets up logging for file output. + /// The serilog configuration. + private static void SetupFileLogging(LoggerConfiguration serilogcfg) { + // Note logging is limited to 1Gb by default - https://github.com/serilog/serilog-sinks-file + + // Get full path + string fullpath = Path.Combine(Directory.GetCurrentDirectory(), LogFilePath); + // Create the logging output directory + string logdir = Path.GetDirectoryName(fullpath); + if (Directory.Exists(logdir) == false) + if (logdir != null) + Directory.CreateDirectory(logdir); + // Setup Serilog + serilogcfg.WriteTo.File(fullpath); + } + } +} diff --git a/QtSharp.CLI/Program.cs b/QtSharp.CLI/Program.cs index d183a2dc..00a99618 100644 --- a/QtSharp.CLI/Program.cs +++ b/QtSharp.CLI/Program.cs @@ -1,19 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; using CppSharp; -using CppSharp.Utils; +using QtSharp.CLI.Helpers; +using QtSharp.CLI.Logging; namespace QtSharp.CLI { public class Program { - static int ParseArgs(string[] args, out string qmake, out string make, out bool debug) + /// Gets the LibLog logger instance. + /// LibLog Logger. + private static ILog Log => _Log ?? (_Log = LogProvider.GetCurrentClassLogger()); + private static ILog _Log; + + private static int ParseArgs(string[] args, out string qmake, out string make, out bool debug) { qmake = null; make = null; @@ -21,21 +23,21 @@ static int ParseArgs(string[] args, out string qmake, out string make, out bool if (args.Length < 2) { - Console.WriteLine("Please enter the paths to qmake and make."); + Log.Error("Please enter the paths to qmake and make."); return 1; } qmake = args [0]; if (!File.Exists(qmake)) { - Console.WriteLine("The specified qmake does not exist."); + Log.Error("The specified qmake does not exist."); return 1; } make = args [1]; if (!File.Exists(make)) { - Console.WriteLine("The specified make does not exist."); + Log.Error("The specified make does not exist."); return 1; } @@ -44,117 +46,17 @@ static int ParseArgs(string[] args, out string qmake, out string make, out bool return 0; } - static List FindQt() - { - var home = Environment.GetFolderPath(Environment.SpecialFolder.Personal); - var qts = new List(); - - var qtPath = Path.Combine(home, "Qt"); - if (!Directory.Exists(qtPath)) - { - return new List(); - } - - foreach (var path in Directory.EnumerateDirectories(qtPath)) - { - var dir = Path.GetFileName(path); - bool isNumber = dir.All(c => char.IsDigit(c) || c == '.'); - if (!isNumber) - continue; - var qt = new QtInfo { Path = path }; - var match = Regex.Match(dir, @"([0-9]+)\.([0-9]+)"); - if (!match.Success) - continue; - qt.MajorVersion = int.Parse(match.Groups[1].Value); - qt.MinorVersion = int.Parse(match.Groups[2].Value); - qts.Add(qt); - } - - return qts; - } - - static bool QueryQt(QtInfo qt, bool debug) - { - // check for OS X - if (string.IsNullOrWhiteSpace(qt.QMake)) - { - qt.QMake = Path.Combine(qt.Path, "clang_64/bin/qmake"); - } - if (string.IsNullOrWhiteSpace(qt.Make)) - { - qt.Make = "/usr/bin/make"; - } - - string path = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); - path = Path.GetDirectoryName(qt.Make) + Path.PathSeparator + path; - Environment.SetEnvironmentVariable("Path", path, EnvironmentVariableTarget.Process); - - int error; - string errorMessage; - qt.Bins = ProcessHelper.Run(qt.QMake, "-query QT_INSTALL_BINS", out error, out errorMessage); - if (!string.IsNullOrEmpty(errorMessage)) - { - Console.WriteLine(errorMessage); - return false; - } - - qt.Libs = ProcessHelper.Run(qt.QMake, "-query QT_INSTALL_LIBS", out error, out errorMessage); - if (!string.IsNullOrEmpty(errorMessage)) - { - Console.WriteLine(errorMessage); - return false; - } - - DirectoryInfo libsInfo = new DirectoryInfo(Platform.IsWindows ? qt.Bins : qt.Libs); - if (!libsInfo.Exists) - { - Console.WriteLine( - "The directory \"{0}\" that qmake returned as the lib directory of the Qt installation, does not exist.", - libsInfo.Name); - return false; - } - qt.LibFiles = GetLibFiles(libsInfo, debug); - qt.Headers = ProcessHelper.Run(qt.QMake, "-query QT_INSTALL_HEADERS", out error, out errorMessage); - if (!string.IsNullOrEmpty(errorMessage)) - { - Console.WriteLine(errorMessage); - return false; - } - DirectoryInfo headersInfo = new DirectoryInfo(qt.Headers); - if (!headersInfo.Exists) - { - Console.WriteLine( - "The directory \"{0}\" that qmake returned as the header direcory of the Qt installation, does not exist.", - headersInfo.Name); - return false; - } - qt.Docs = ProcessHelper.Run(qt.QMake, "-query QT_INSTALL_DOCS", out error, out errorMessage); - - string emptyFile = Platform.IsWindows ? "NUL" : "/dev/null"; - string output; - ProcessHelper.Run("gcc", $"-v -E -x c++ {emptyFile}", out error, out output); - qt.Target = Regex.Match(output, @"Target:\s*(?[^\r\n]+)").Groups["target"].Value; - - const string includeDirsRegex = @"#include <\.\.\.> search starts here:(?.+)End of search list"; - string allIncludes = Regex.Match(output, includeDirsRegex, RegexOptions.Singleline).Groups["includes"].Value; - var includeDirs = allIncludes.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .Select(s => s.Trim()).ToList(); - - const string frameworkDirectory = "(framework directory)"; - qt.SystemIncludeDirs = includeDirs.Where(s => !s.Contains(frameworkDirectory)) - .Select(Path.GetFullPath); - - if (Platform.IsMacOS) - qt.FrameworkDirs = includeDirs.Where(s => s.Contains(frameworkDirectory)) - .Select(s => s.Replace(frameworkDirectory, string.Empty).Trim()).Select(Path.GetFullPath); - - return true; - } - public static int Main(string[] args) { + // Setup Logging with SeriLog + // remove timestamp etc for while we're parsing command line args / input etc + var logger = SerilogSetup.GetLogger_Nodttm(); + Serilog.Log.Logger = logger; + // Setup CppSharp Log + Diagnostics.Implementation = new CppSharpLog(); + Stopwatch s = Stopwatch.StartNew(); - var qts = FindQt(); + var qts = QtInfo.FindQt(); bool found = qts.Count != 0; bool debug = false; QtInfo qt; @@ -173,12 +75,16 @@ public static int Main(string[] args) qt = qts.Last(); } - bool log = false; - ConsoleLogger logredirect = log ? new ConsoleLogger() : null; - if (logredirect != null) - logredirect.CreateLogDirectory(); + // Re-setup logging with default template including timestamp / log level + logger.Dispose(); + logger = SerilogSetup.GetLogger_dttm(); + Serilog.Log.Logger = logger; + _Log = LogProvider.GetCurrentClassLogger(); + // Setup CppSharp Log + Diagnostics.Implementation = new CppSharpLog(); - if (!QueryQt(qt, debug)) + Log.Info("QtSharp Starting"); + if (!qt.Query(debug)) return 1; for (int i = qt.LibFiles.Count - 1; i >= 0; i--) @@ -198,15 +104,13 @@ public static int Main(string[] args) if (wrappedModules.Count == 0) { - Console.WriteLine("Generation failed."); + Log.Error("Generation failed."); return 1; } const string qtSharpZip = "QtSharp.zip"; - if (File.Exists(qtSharpZip)) - { - File.Delete(qtSharpZip); - } + if (File.Exists(qtSharpZip)) File.Delete(qtSharpZip); + using (var zip = File.Create(qtSharpZip)) { using (var zipArchive = new ZipArchive(zip, ZipArchiveMode.Create)) @@ -221,38 +125,9 @@ public static int Main(string[] args) zipArchive.CreateEntryFromFile("CppSharp.Runtime.dll", "CppSharp.Runtime.dll"); } } - Console.WriteLine("Done in: " + s.Elapsed); + Log.Info("Done in: " + s.Elapsed); + Log.Info("QtSharp Finished"); return 0; } - - private static IList GetLibFiles(DirectoryInfo libsInfo, bool debug) - { - List modules; - - if (Platform.IsMacOS) - { - modules = libsInfo.EnumerateDirectories("*.framework").Select(dir => Path.GetFileNameWithoutExtension(dir.Name)).ToList(); - } - else - { - modules = (from file in libsInfo.EnumerateFiles() - where Regex.IsMatch(file.Name, @"^Qt\d?\w+\.\w+$") - select file.Name).ToList(); - } - - for (var i = modules.Count - 1; i >= 0; i--) - { - var module = Path.GetFileNameWithoutExtension(modules[i]); - if (debug && module != null && !module.EndsWith("d", StringComparison.Ordinal)) - { - modules.Remove(module + Path.GetExtension(modules[i])); - } - else - { - modules.Remove(module + "d" + Path.GetExtension(modules[i])); - } - } - return modules; - } } } diff --git a/QtSharp.CLI/QtSharp.CLI.csproj b/QtSharp.CLI/QtSharp.CLI.csproj index a8ddd595..5c418e3f 100644 --- a/QtSharp.CLI/QtSharp.CLI.csproj +++ b/QtSharp.CLI/QtSharp.CLI.csproj @@ -41,7 +41,7 @@ true bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG full AnyCPU prompt @@ -80,21 +80,27 @@ ..\packages\CppSharp.0.8.14\lib\CppSharp.Runtime.dll - - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll - True + + + ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.dll + + + ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Mdb.dll + + + ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Pdb.dll + + + ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Rocks.dll - - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll - True + + ..\packages\Serilog.2.6.0\lib\net46\Serilog.dll - - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll - True + + ..\packages\Serilog.Sinks.Console.3.1.1\lib\net45\Serilog.Sinks.Console.dll - - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll - True + + ..\packages\Serilog.Sinks.File.4.0.0\lib\net45\Serilog.Sinks.File.dll @@ -106,7 +112,9 @@ - + + + @@ -120,13 +128,14 @@ + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - +