diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 02d578912..b631e0275 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -124,6 +124,7 @@ + diff --git a/LibGit2Sharp.Tests/LogFixture.cs b/LibGit2Sharp.Tests/LogFixture.cs new file mode 100644 index 000000000..76654e3d3 --- /dev/null +++ b/LibGit2Sharp.Tests/LogFixture.cs @@ -0,0 +1,40 @@ +using System; +using LibGit2Sharp; +using LibGit2Sharp.Core; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class LogFixture + { + [Fact] + public void CanEnableAndDisableLogging() + { + // Setting logging produces a log message at level Info, + // ensure that we catch it. + LogLevel level = LogLevel.None; + string message = null; + + GlobalSettings.LogConfiguration = new LogConfiguration(LogLevel.Trace, (l, m) => { level = l; message = m; }); + + Assert.Equal(LogLevel.Info, level); + Assert.Equal("Logging enabled at level Trace", message); + + // Configuring at Warning and higher means that the + // message at level Info should not be produced. + level = LogLevel.None; + message = null; + + GlobalSettings.LogConfiguration = new LogConfiguration(LogLevel.Warning, (l, m) => { level = l; message = m; }); + + Assert.Equal(LogLevel.None, level); + Assert.Equal(null, message); + + // Similarly, turning logging off should produce no messages. + GlobalSettings.LogConfiguration = LogConfiguration.None; + + Assert.Equal(LogLevel.None, level); + Assert.Equal(null, message); + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 113e91f15..ab47bb327 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1496,6 +1496,11 @@ internal static extern int git_tag_delete( [DllImport(libgit2)] internal static extern void git_threads_shutdown(); + internal delegate void git_trace_cb(LogLevel level, IntPtr message); + + [DllImport(libgit2)] + internal static extern int git_trace_set(LogLevel level, git_trace_cb trace_cb); + internal delegate int git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload); internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index a04bb0bef..e87ce58de 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2951,6 +2951,19 @@ public static GitObjectType git_tag_target_type(GitObjectSafeHandle tag) #endregion + #region git_trace_ + + public static void git_trace_set(LogLevel level, NativeMethods.git_trace_cb callback) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_trace_set(level, callback); + Ensure.ZeroResult(res); + } + } + + #endregion + #region git_transport_ public static void git_transport_register(String prefix, IntPtr transport_cb, IntPtr param) diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs index 08f7ad53d..190cfadf4 100644 --- a/LibGit2Sharp/GlobalSettings.cs +++ b/LibGit2Sharp/GlobalSettings.cs @@ -1,5 +1,6 @@ using System; using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; namespace LibGit2Sharp { @@ -10,6 +11,8 @@ public static class GlobalSettings { private static readonly Lazy version = new Lazy(Version.Build); + private static LogConfiguration logConfiguration = LogConfiguration.None; + /// /// Returns all the optional features that were compiled into /// libgit2. @@ -84,5 +87,38 @@ public static void UnregisterSmartSubtransport(SmartSubtransportRegistration< Proxy.git_transport_unregister(registration.Scheme); registration.Free(); } + + /// + /// Registers a new to receive + /// information logging information from libgit2 and LibGit2Sharp. + /// + /// Note that this configuration is global to an entire process + /// and does not honor application domains. + /// + public static LogConfiguration LogConfiguration + { + set + { + Ensure.ArgumentNotNull(value, "value"); + + logConfiguration = value; + + if (logConfiguration.Level == LogLevel.None) + { + Proxy.git_trace_set(0, null); + } + else + { + Proxy.git_trace_set(value.Level, value.GitTraceHandler); + + Log.Write(LogLevel.Info, "Logging enabled at level {0}", value.Level); + } + } + + get + { + return logConfiguration; + } + } } } diff --git a/LibGit2Sharp/Handlers.cs b/LibGit2Sharp/Handlers.cs index ef688c550..74c746f1a 100644 --- a/LibGit2Sharp/Handlers.cs +++ b/LibGit2Sharp/Handlers.cs @@ -111,4 +111,12 @@ public enum PackBuilderStage /// Deltafying } + + /// + /// Delegate definition for logging. This callback will be used to + /// write logging messages in libgit2 and LibGit2Sharp. + /// + /// The level of the log message. + /// The log message. + public delegate void LogHandler(LogLevel level, string message); } diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index ba00f56df..4214e22bc 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -139,6 +139,9 @@ + + + diff --git a/LibGit2Sharp/Log.cs b/LibGit2Sharp/Log.cs new file mode 100644 index 000000000..23f79d93e --- /dev/null +++ b/LibGit2Sharp/Log.cs @@ -0,0 +1,30 @@ +using System.Runtime.CompilerServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + internal class Log + { + private static bool IsEnabled(LogConfiguration configuration, LogLevel level) + { + return (configuration.Level != LogLevel.None && configuration.Level >= level); + } + + internal static bool IsEnabled(LogLevel level) + { + return IsEnabled(GlobalSettings.LogConfiguration, level); + } + + internal static void Write(LogLevel level, string message, params object[] args) + { + LogConfiguration configuration = GlobalSettings.LogConfiguration; + + if (!IsEnabled(configuration, level)) + { + return; + } + + configuration.Handler(level, string.Format(message, args)); + } + } +} diff --git a/LibGit2Sharp/LogConfiguration.cs b/LibGit2Sharp/LogConfiguration.cs new file mode 100644 index 000000000..f31eeac18 --- /dev/null +++ b/LibGit2Sharp/LogConfiguration.cs @@ -0,0 +1,45 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Logging and tracing configuration for libgit2 and LibGit2Sharp. + /// + public sealed class LogConfiguration + { + /// + /// The default logging configuration, which performs no logging at all. + /// + public static readonly LogConfiguration None = new LogConfiguration { Level = LogLevel.None }; + + /// + /// Creates a new logging configuration to call the given + /// delegate when logging occurs at the given level. + /// + /// Level to log at + /// Handler to call when logging occurs + public LogConfiguration(LogLevel level, LogHandler handler) + { + Ensure.ArgumentConformsTo(level, (t) => { return (level != LogLevel.None); }, "level"); + Ensure.ArgumentNotNull(handler, "handler"); + + Level = level; + Handler = handler; + } + + private LogConfiguration() + { + } + + internal LogLevel Level { get; private set; } + internal LogHandler Handler { get; private set; } + + internal void GitTraceHandler(LogLevel level, IntPtr msg) + { + string message = LaxUtf8Marshaler.FromNative(msg); + Handler(level, message); + } + } +} diff --git a/LibGit2Sharp/LogLevel.cs b/LibGit2Sharp/LogLevel.cs new file mode 100644 index 000000000..472bd5aa5 --- /dev/null +++ b/LibGit2Sharp/LogLevel.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Available logging levels. When logging is enabled at a particular + /// level, callers will be provided logging at the given level and all + /// lower levels. + /// + public enum LogLevel + { + /// + /// No logging will be provided. + /// + None = 0, + + /// + /// Severe errors that may impact the program's execution. + /// + Fatal = 1, + + /// + /// Errors that do not impact the program's execution. + /// + Error = 2, + + /// + /// Warnings that suggest abnormal data. + /// + Warning = 3, + + /// + /// Informational messages about program execution. + /// + Info = 4, + + /// + /// Detailed data that allows for debugging. + /// + Debug = 5, + + /// + /// Tracing is exceptionally detailed debugging data. + /// + Trace = 6, + } +}