diff --git a/K4AdotNet.Samples.Wpf.BackgroundRemover/App.cs b/K4AdotNet.Samples.Wpf.BackgroundRemover/App.cs index 91bd530..2b88c84 100644 --- a/K4AdotNet.Samples.Wpf.BackgroundRemover/App.cs +++ b/K4AdotNet.Samples.Wpf.BackgroundRemover/App.cs @@ -7,7 +7,15 @@ internal class App : AppBase { [STAThread] public static void Main() - => new App().Run(); + { +#if DEBUG + Sdk.TraceLevel = System.Diagnostics.TraceLevel.Info; +#else + Sdk.TraceLevel = System.Diagnostics.TraceLevel.Warning; +#endif + + new App().Run(); + } protected override Window CreateMainWindow(StartupEventArgs e) { diff --git a/K4AdotNet.Samples.Wpf.BodyTracker/App.cs b/K4AdotNet.Samples.Wpf.BodyTracker/App.cs index 350328e..57ab72c 100644 --- a/K4AdotNet.Samples.Wpf.BodyTracker/App.cs +++ b/K4AdotNet.Samples.Wpf.BodyTracker/App.cs @@ -7,7 +7,15 @@ internal sealed class App : AppBase { [STAThread] public static void Main() - => new App().Run(); + { +#if DEBUG + Sdk.TraceLevel = System.Diagnostics.TraceLevel.Info; +#else + Sdk.TraceLevel = System.Diagnostics.TraceLevel.Warning; +#endif + + new App().Run(); + } protected override Window CreateMainWindow(StartupEventArgs e) { diff --git a/K4AdotNet.Samples.Wpf.Viewer/App.cs b/K4AdotNet.Samples.Wpf.Viewer/App.cs index aaad541..244e827 100644 --- a/K4AdotNet.Samples.Wpf.Viewer/App.cs +++ b/K4AdotNet.Samples.Wpf.Viewer/App.cs @@ -7,7 +7,15 @@ internal sealed class App : AppBase { [STAThread] public static void Main() - => new App().Run(); + { +#if DEBUG + Sdk.TraceLevel = System.Diagnostics.TraceLevel.Info; +#else + Sdk.TraceLevel = System.Diagnostics.TraceLevel.Warning; +#endif + + new App().Run(); + } protected override Window CreateMainWindow(StartupEventArgs e) => new MainWindow(new MainModel(this)); diff --git a/K4AdotNet/K4AdotNet.xml b/K4AdotNet/K4AdotNet.xml index e6a7e69..d3a7509 100644 --- a/K4AdotNet/K4AdotNet.xml +++ b/K4AdotNet/K4AdotNet.xml @@ -1258,6 +1258,79 @@ This method cannot be called for disposed objects. + + + Internal helper class which implements logging-related logic. + + + + Verbosity levels of debug messaging. + + + The most severe level of debug messaging. + + + The second most severe level of debug messaging. + + + The third most severe level of debug messaging. + + + The second least severe level of debug messaging. + + + The lest severe level of debug messaging. This is the most verbose messaging possible. + + + No logging is performed. + + + DLL imports from k4a.h header file for native functions that are connected with logging. + + + Callback function for debug messages being generated by the Azure Kinect SDK. + The context of the callback function. This is the context that was supplied by the caller to . + The level of the message that has been created. + The file name of the source file that generated the message. + The line number of the source file that generated the message. + The messaged generated by the Azure Kinect SDK. + + The callback is called asynchronously when the Azure Kinext SDK generates a message at a that is equal to + or more critical than the level specified when calling to register the callback. + + This callback can occur from any thread and blocks the calling thread.This callback user + must protect it's logging resources from concurrent calls. All care should be made to minimize the amount of time + locks are held. + + + + Sets and clears the callback function to receive debug messages from the Azure Kinect device. + The callback function to receive messages from. Set to to unregister the callback function. + The callback functions context. + The least critical error the user wants to be notified about. + + if the callback function was set or cleared successfully. + if an error is encountered or the callback function has already been set. + + + Call this function to set or clear the callback function that is used to deliver debug messages to the caller. This + callback may be called concurrently, it is up to the implementation of the callback function to ensure the + parallelization is handled. + + Clearing the callback function will block until all pending calls to the callback function have completed. + + To update , this method can be called with the same value and by + specifying a new . + + Logging provided via this API is independent of the logging controlled by the environmental variable controls + K4A_ENABLE_LOG_TO_STDOUT, K4A_ENABLE_LOG_TO_A_FILE, and K4A_LOG_LEVEL. However there is a slight change in + default behavior when using this function.By default, when \p k4a_set_debug_message_handler() has not been used to + register a message callback, the default for environmental variable controls is to send debug messages as if + K4A_ENABLE_LOG_TO_STDOUT= 1 were set. If this method registers a callback function before + is called, then the default for environmental controls + is as if K4A_ENABLE_LOG_TO_STDOUT= 0 was specified. Physically specifying the environmental control will override the default. + + 32-bit time value in microseconds. Used for timestamps and delays. @@ -4144,6 +4217,13 @@ Extension of Dynamic Link Libraries (DLL) under Windows. + + + The K4A.Net can log data to a regular .Net Trace. + Use this property to choose the level of such logging, or set it to to turn it off. + Default value is . + + The Sensor SDK can log data to the console, files, or to a custom handler. Level of logging. @@ -7691,149 +7771,3 @@ -System.Diagnostics.CodeAnalysis.AllowNullAttribute"> - - Specifies that is allowed as an input even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that is disallowed as an input even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that a method that will never return under any circumstance. - - - - - Initializes a new instance of the class. - - - - - - Specifies that the method will not return if the associated - parameter is passed the specified value. - - - - - Gets the condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Initializes a new instance of the - class with the specified parameter value. - - - The condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Specifies that an output may be even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that when a method returns , - the parameter may be even if the corresponding type disallows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter may be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter may be . - - - - - Specifies that an output is not even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that the output will be non- if the - named parameter is non-. - - - - - Gets the associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Initializes the attribute with the associated parameter name. - - - The associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Specifies that when a method returns , - the parameter will not be even if the corresponding type allows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter will not be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter will not be . - - - - diff --git a/K4AdotNet/Logging/LogImpl.cs b/K4AdotNet/Logging/LogImpl.cs new file mode 100644 index 0000000..44dece8 --- /dev/null +++ b/K4AdotNet/Logging/LogImpl.cs @@ -0,0 +1,116 @@ +using System; +using System.Diagnostics; + +namespace K4AdotNet.Logging +{ + /// + /// Internal helper class which implements logging-related logic. + /// + internal static class LogImpl + { + private static readonly NativeApi.LoggingMessageCallback debugMessageHandler = OnDebugMessage; + private static TraceLevel traceLevel = TraceLevel.Off; + private static readonly object traceLevelSync = new object(); + + public static TraceLevel TraceLevel + { + get + { + lock (traceLevelSync) return traceLevel; + } + + set + { + lock (traceLevelSync) + { + if (value == traceLevel) + return; + + if (traceLevel != TraceLevel.Off) + { + var res = NativeApi.SetDebugMessageHandler(null, IntPtr.Zero, traceLevel.ToLogLevel()); + if (res != NativeCallResults.Result.Succeeded) + throw new InvalidOperationException("Failed to clear the debug message handler."); + } + + if (value != TraceLevel.Off) + { + var res = NativeApi.SetDebugMessageHandler(debugMessageHandler, IntPtr.Zero, value.ToLogLevel()); + if (res != NativeCallResults.Result.Succeeded) + throw new InvalidOperationException("Failed to set the debug message handler."); + if (traceLevel == TraceLevel.Off) + { + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_Exit; + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_Exit; + } + } + else + { + AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_Exit; + AppDomain.CurrentDomain.DomainUnload -= CurrentDomain_Exit; + } + + traceLevel = value; + } + } + } + + private static void OnDebugMessage(IntPtr _, LogLevel level, string file, int line, string message) + { + var text = $"#K4A [{System.IO.Path.GetFileName(file)}:{line}] {message}"; + + switch (level) + { + case LogLevel.Critical: + case LogLevel.Error: + Trace.TraceError(text); + break; + case LogLevel.Warning: + Trace.TraceWarning(text); + break; + case LogLevel.Information: + Trace.TraceInformation(text); + break; + default: + Trace.WriteLine(text); + break; + } + } + + private static void CurrentDomain_Exit(object sender, EventArgs e) + { + var res = NativeApi.SetDebugMessageHandler(null, IntPtr.Zero, traceLevel.ToLogLevel()); + if (res != NativeCallResults.Result.Succeeded) + Trace.TraceWarning("Failed to clear the debug message handler"); + } + + private static LogLevel ToLogLevel(this TraceLevel level) + => level switch + { + TraceLevel.Off => LogLevel.Off, + TraceLevel.Error => LogLevel.Error, + TraceLevel.Warning => LogLevel.Warning, + TraceLevel.Info => LogLevel.Information, + TraceLevel.Verbose => LogLevel.Trace, + _ => throw new ArgumentOutOfRangeException(nameof(level)), + }; + + public static void ConfigureLogging(string variableNamePrefix, TraceLevel level, bool logToStdout, string? logToFile) + { + Environment.SetEnvironmentVariable(variableNamePrefix + "LOG_LEVEL", level.ToSdkLogLevelLetter(), EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(variableNamePrefix + "ENABLE_LOG_TO_STDOUT", logToStdout ? "1" : "0", EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(variableNamePrefix + "ENABLE_LOG_TO_A_FILE", string.IsNullOrWhiteSpace(logToFile) ? "0" : logToFile, EnvironmentVariableTarget.Process); + } + + private static string ToSdkLogLevelLetter(this TraceLevel level) + => level switch + { + TraceLevel.Off => "c", + TraceLevel.Error => "e", + TraceLevel.Warning => "w", + TraceLevel.Info => "i", + TraceLevel.Verbose => "t", + _ => throw new ArgumentOutOfRangeException(nameof(level)), + }; + } +} diff --git a/K4AdotNet/Logging/LogLevel.cs b/K4AdotNet/Logging/LogLevel.cs new file mode 100644 index 0000000..c6eb931 --- /dev/null +++ b/K4AdotNet/Logging/LogLevel.cs @@ -0,0 +1,31 @@ +namespace K4AdotNet.Logging +{ + // k4a_log_level_t + /// Verbosity levels of debug messaging. + internal enum LogLevel + { + // K4A_LOG_LEVEL_CRITICAL + /// The most severe level of debug messaging. + Critical = 0, + + // K4A_LOG_LEVEL_ERROR + /// The second most severe level of debug messaging. + Error, + + // K4A_LOG_LEVEL_WARNING + /// The third most severe level of debug messaging. + Warning, + + // K4A_LOG_LEVEL_INFO + /// The second least severe level of debug messaging. + Information, + + // K4A_LOG_LEVEL_TRACE + /// The lest severe level of debug messaging. This is the most verbose messaging possible. + Trace, + + // K4A_LOG_LEVEL_OFF + /// No logging is performed. + Off, + } +} diff --git a/K4AdotNet/Logging/NativeApi.cs b/K4AdotNet/Logging/NativeApi.cs new file mode 100644 index 0000000..aa393d8 --- /dev/null +++ b/K4AdotNet/Logging/NativeApi.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; + +namespace K4AdotNet.Logging +{ + /// DLL imports from k4a.h header file for native functions that are connected with logging. + internal static class NativeApi + { + // typedef void (k4a_logging_message_cb_t) (void* context, + // k4a_log_level_t level, + // const char* file, + // const int line, + // const char* message); + /// Callback function for debug messages being generated by the Azure Kinect SDK. + /// The context of the callback function. This is the context that was supplied by the caller to . + /// The level of the message that has been created. + /// The file name of the source file that generated the message. + /// The line number of the source file that generated the message. + /// The messaged generated by the Azure Kinect SDK. + /// + /// The callback is called asynchronously when the Azure Kinext SDK generates a message at a that is equal to + /// or more critical than the level specified when calling to register the callback. + /// + /// This callback can occur from any thread and blocks the calling thread.This callback user + /// must protect it's logging resources from concurrent calls. All care should be made to minimize the amount of time + /// locks are held. + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void LoggingMessageCallback( + IntPtr context, + LogLevel level, + [MarshalAs(UnmanagedType.LPStr)] string file, + int line, + [MarshalAs(UnmanagedType.LPStr)] string message); + + // K4A_EXPORT k4a_result_t k4a_set_debug_message_handler(k4a_logging_message_cb_t* message_cb, + // void* message_cb_context, + // k4a_log_level_t min_level); + /// Sets and clears the callback function to receive debug messages from the Azure Kinect device. + /// The callback function to receive messages from. Set to to unregister the callback function. + /// The callback functions context. + /// The least critical error the user wants to be notified about. + /// + /// if the callback function was set or cleared successfully. + /// if an error is encountered or the callback function has already been set. + /// + /// + /// Call this function to set or clear the callback function that is used to deliver debug messages to the caller. This + /// callback may be called concurrently, it is up to the implementation of the callback function to ensure the + /// parallelization is handled. + /// + /// Clearing the callback function will block until all pending calls to the callback function have completed. + /// + /// To update , this method can be called with the same value and by + /// specifying a new . + /// + /// Logging provided via this API is independent of the logging controlled by the environmental variable controls + /// K4A_ENABLE_LOG_TO_STDOUT, K4A_ENABLE_LOG_TO_A_FILE, and K4A_LOG_LEVEL. However there is a slight change in + /// default behavior when using this function.By default, when \p k4a_set_debug_message_handler() has not been used to + /// register a message callback, the default for environmental variable controls is to send debug messages as if + /// K4A_ENABLE_LOG_TO_STDOUT= 1 were set. If this method registers a callback function before + /// is called, then the default for environmental controls + /// is as if K4A_ENABLE_LOG_TO_STDOUT= 0 was specified. Physically specifying the environmental control will override the default. + /// + [DllImport(Sdk.SENSOR_DLL_NAME, EntryPoint = "k4a_set_debug_message_handler", CallingConvention = CallingConvention.Cdecl)] + public static extern NativeCallResults.Result SetDebugMessageHandler( + LoggingMessageCallback? messageCallback, + IntPtr messageCallbackContext, + LogLevel minLevel); + } +} diff --git a/K4AdotNet/Sdk.cs b/K4AdotNet/Sdk.cs index 34ccf8f..a6f1238 100644 --- a/K4AdotNet/Sdk.cs +++ b/K4AdotNet/Sdk.cs @@ -71,6 +71,17 @@ public static class Sdk #region Logging + /// + /// The K4A.Net can log data to a regular .Net Trace. + /// Use this property to choose the level of such logging, or set it to to turn it off. + /// Default value is . + /// + public static TraceLevel TraceLevel + { + get => Logging.LogImpl.TraceLevel; + set => Logging.LogImpl.TraceLevel = value; + } + /// The Sensor SDK can log data to the console, files, or to a custom handler. /// Level of logging. /// Log messages to STDOUT? @@ -88,7 +99,7 @@ public static class Sdk public static void ConfigureLogging(TraceLevel level, bool logToStdout = false, string? logToFile = null) { const string PREFIX = "K4A_"; - ConfigureLogging(PREFIX, level, logToStdout, logToFile); + Logging.LogImpl.ConfigureLogging(PREFIX, level, logToStdout, logToFile); } /// Record part of Sensor SDK can log data to the console, files, or to a custom handler. @@ -108,7 +119,7 @@ public static void ConfigureLogging(TraceLevel level, bool logToStdout = false, public static void ConfigureRecordLogging(TraceLevel level, bool logToStdout = false, string? logToFile = null) { const string PREFIX = "K4A_RECORD_"; - ConfigureLogging(PREFIX, level, logToStdout, logToFile); + Logging.LogImpl.ConfigureLogging(PREFIX, level, logToStdout, logToFile); } /// The Body Tracking SDK can log data to the console, files, or to a custom handler. @@ -128,27 +139,7 @@ public static void ConfigureRecordLogging(TraceLevel level, bool logToStdout = f public static void ConfigureBodyTrackingLogging(TraceLevel level, bool logToStdout = false, string? logToFile = null) { const string PREFIX = "K4ABT_"; - ConfigureLogging(PREFIX, level, logToStdout, logToFile); - } - - private static void ConfigureLogging(string variableNamePrefix, TraceLevel level, bool logToStdout, string? logToFile) - { - Environment.SetEnvironmentVariable(variableNamePrefix + "LOG_LEVEL", level.ToSdkLogLevelLetter(), EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable(variableNamePrefix + "ENABLE_LOG_TO_STDOUT", logToStdout ? "1" : "0", EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable(variableNamePrefix + "ENABLE_LOG_TO_A_FILE", string.IsNullOrWhiteSpace(logToFile) ? "0" : logToFile, EnvironmentVariableTarget.Process); - } - - private static string ToSdkLogLevelLetter(this TraceLevel level) - { - switch (level) - { - case TraceLevel.Off: return "c"; - case TraceLevel.Error: return "e"; - case TraceLevel.Warning: return "w"; - case TraceLevel.Info: return "i"; - case TraceLevel.Verbose: return "t"; - default: throw new ArgumentOutOfRangeException(nameof(level)); - } + Logging.LogImpl.ConfigureLogging(PREFIX, level, logToStdout, logToFile); } #endregion