Skip to content

Commit

Permalink
Changed logger provider to return IDisposable
Browse files Browse the repository at this point in the history
  • Loading branch information
sandrohanea committed Dec 20, 2024
1 parent 618faae commit 717f948
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 64 deletions.
8 changes: 4 additions & 4 deletions Whisper.net/LibraryLoader/CudaHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal static class CudaHelper
{
public static bool IsCudaAvailable()
{
LogProvider.Log(WhisperLogLevel.Debug, "Checking for CUDA availability.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Checking for CUDA availability.");
INativeCuda? nativeCuda = null;
var cudaDevices = 0;
try
Expand All @@ -22,7 +22,7 @@ public static bool IsCudaAvailable()

if (!NativeLibrary.TryLoad(libName, out var library))
{
LogProvider.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
return false;
}
nativeCuda = new NativeLibraryCuda(library);
Expand All @@ -37,11 +37,11 @@ public static bool IsCudaAvailable()
}
catch
{
LogProvider.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
return false;
}
#endif
LogProvider.Log(WhisperLogLevel.Debug, $"NUmber of CUDA devices found: {cudaDevices}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"NUmber of CUDA devices found: {cudaDevices}");
return cudaDevices > 0;
}
finally
Expand Down
36 changes: 18 additions & 18 deletions Whisper.net/LibraryLoader/NativeLibraryLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ public static class NativeLibraryLoader
internal static LoadResult LoadNativeLibrary()
{
#if IOS || MACCATALYST || TVOS
LogProvider.Log(WhisperLogLevel.Debug, "Using LibraryImportInternalWhisper for whisper librar for ios.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using LibraryImportInternalWhisper for whisper librar for ios.");
return LoadResult.Success(new LibraryImportInternalWhisper());
#elif ANDROID
LogProvider.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper librar for Android.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper librar for Android.");
return LoadResult.Success(new LibraryImportLibWhisper());
#else
// If the user has handled loading the library themselves, we don't need to do anything.
if (RuntimeOptions.LoadedLibrary.HasValue
|| RuntimeInformation.OSArchitecture.ToString().Equals("wasm", StringComparison.OrdinalIgnoreCase))
{
#if NET8_0_OR_GREATER
LogProvider.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper library with bypassed loading.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper library with bypassed loading.");
return LoadResult.Success(new LibraryImportLibWhisper());
#else
LogProvider.Log(WhisperLogLevel.Debug, "Using DllImportsNativeLibWhisper for whisper library with bypassed loading.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using DllImportsNativeLibWhisper for whisper library with bypassed loading.");
return LoadResult.Success(new DllImportsNativeLibWhisper());
#endif
}
Expand Down Expand Up @@ -87,30 +87,30 @@ _ when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) => "macos",

var whisperPath = GetLibraryPath(platform, "whisper", runtimePath);

LogProvider.Log(WhisperLogLevel.Debug, $"Trying to load ggml library from {ggmlPath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Trying to load ggml library from {ggmlPath}");
if (!libraryLoader.TryOpenLibrary(ggmlPath, out var ggmlLibraryHandle))
{
lastError = libraryLoader.GetLastError();
LogProvider.Log(WhisperLogLevel.Debug, $"Failed to load ggml library from {ggmlPath}. Error: {lastError}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Failed to load ggml library from {ggmlPath}. Error: {lastError}");
continue;
}

LogProvider.Log(WhisperLogLevel.Debug, $"Trying to load whisper library from {whisperPath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Trying to load whisper library from {whisperPath}");
// Ggml was loaded, for this runtimePath, we need to load whisper as well
if (!libraryLoader.TryOpenLibrary(whisperPath, out var whisperHandle))
{
lastError = libraryLoader.GetLastError();
LogProvider.Log(WhisperLogLevel.Debug, $"Failed to load whisper library from {whisperPath}. Error: {lastError}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Failed to load whisper library from {whisperPath}. Error: {lastError}");
continue;
}

LogProvider.Log(WhisperLogLevel.Debug, $"Successfully loaded whisper library from {whisperPath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Successfully loaded whisper library from {whisperPath}");
RuntimeOptions.LoadedLibrary = runtimeLibrary;
#if NETSTANDARD
LogProvider.Log(WhisperLogLevel.Debug, $"Using DllImportsNativeWhisper for whisper library");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Using DllImportsNativeWhisper for whisper library");
var nativeWhisper = new DllImportsNativeWhisper();
#else
LogProvider.Log(WhisperLogLevel.Debug, $"Using NativeLibraryWhisper for whisper library");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Using NativeLibraryWhisper for whisper library");
var nativeWhisper = new NativeLibraryWhisper(whisperHandle, ggmlLibraryHandle);
#endif

Expand Down Expand Up @@ -143,15 +143,15 @@ private static string GetLibraryPath(string platform, string libraryName, string

private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform, string architecture, List<RuntimeLibrary> runtimeLibraries)
{
LogProvider.Log(WhisperLogLevel.Debug, $"Checking if runtime {runtime} is supported on the platform: {platform}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Checking if runtime {runtime} is supported on the platform: {platform}");
#if !NETSTANDARD
// If AVX is not supported, we can't use CPU runtime on Windows and linux (we should use noavx runtime instead).
if (runtime == RuntimeLibrary.Cpu
&& (platform == "win" || platform == "linux")
&& (architecture == "x86" || architecture == "x64")
&& (!Avx.IsSupported || !Avx2.IsSupported || !Fma.IsSupported))
{
LogProvider.Log(WhisperLogLevel.Debug, $"No AVX, AVX2 or Fma support is identified on this host. AVX: {Avx.IsSupported} AVX2: {Avx2.IsSupported} FMA: {Fma.IsSupported}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"No AVX, AVX2 or Fma support is identified on this host. AVX: {Avx.IsSupported} AVX2: {Avx2.IsSupported} FMA: {Fma.IsSupported}");
// If noavx runtime is not available, we should throw an exception, because we can't use CPU runtime without AVX support.
if (!runtimeLibraries.Contains(RuntimeLibrary.CpuNoAvx))
{
Expand All @@ -173,12 +173,12 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
// + override the default RuntimeLibraryOrder to have only [ Cuda ].
// This way, the user can use Cuda if it's available, otherwise, the CPU runtime will be used.
// However, the cudart library should be available in the system.
LogProvider.Log(WhisperLogLevel.Debug, "Cuda runtime is not available, but it's the last runtime in the list. " +
WhisperLogger.Log(WhisperLogLevel.Debug, "Cuda runtime is not available, but it's the last runtime in the list. " +
"It will be used as a fallback to the CPU runtime.");
return true;
}

LogProvider.Log(WhisperLogLevel.Debug, "Cuda driver is not available or no cuda device is identified.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Cuda driver is not available or no cuda device is identified.");
return false;
}

Expand Down Expand Up @@ -217,15 +217,15 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
RuntimeLibrary.OpenVino => Path.Combine(runtimesPath, "openvino", $"{platform}-{architecture}"),
_ => throw new InvalidOperationException("Unknown runtime library")
};
LogProvider.Log(WhisperLogLevel.Debug, $"Searching for runtime directory {library} in {runtimePath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Searching for runtime directory {library} in {runtimePath}");

if (Directory.Exists(runtimePath))
{
yield return (runtimePath, library);
}
else
{
LogProvider.Log(WhisperLogLevel.Debug, $"Runtime directory for {library} not found in {runtimePath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Runtime directory for {library} not found in {runtimePath}");
}
}

Expand All @@ -245,7 +245,7 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
}
catch (Exception ex)
{
LogProvider.Log(WhisperLogLevel.Debug, $"Failed to get directory name from path: {path}. Error: {ex.Message}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Failed to get directory name from path: {path}. Error: {ex.Message}");
return null;
}
#endif
Expand Down
30 changes: 19 additions & 11 deletions Whisper.net/Logger/LogProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,35 @@
namespace Whisper.net.Logger;
public static class LogProvider
{
public static event Action<WhisperLogLevel, string?>? OnLog;

/// <summary>
/// Adds a console logger that logs messages with a severity greater than or equal to the specified level.
/// </summary>
/// <param name="minLevel">The minimum severity level to log.</param>
public static void AddConsoleLogging(WhisperLogLevel minLevel = WhisperLogLevel.Info)
/// <returns>
/// Returns a disposable object that can be used to remove the logger.
/// </returns>
public static IDisposable AddConsoleLogging(WhisperLogLevel minLevel = WhisperLogLevel.Info)
{
OnLog += (level, message) =>
return new WhisperLogger((level, message) =>
{
// Higher values are less severe
if (level < minLevel)
{
Console.WriteLine($"[{level}] {message}");
}
};
});
}

/// <summary>
/// Adds a logger that logs messages with a custom action.
/// </summary>
/// <param name="logAction">The action to log.</param>
/// <returns>
/// Returns a disposable object that can be used to remove the logger.
/// </returns>
public static IDisposable AddLogger(Action<WhisperLogLevel, string?> logAction)
{
return new WhisperLogger(logAction);
}

internal static void InitializeLogging(INativeWhisper nativeWhisper)
Expand Down Expand Up @@ -56,11 +69,6 @@ internal static void LogUnmanaged(GgmlLogLevel level, IntPtr message, IntPtr use
_ => WhisperLogLevel.Info
};

Log(managedLevel, messageString);
}

internal static void Log(WhisperLogLevel level, string? message)
{
OnLog?.Invoke(level, message);
WhisperLogger.Log(managedLevel, messageString);
}
}
26 changes: 26 additions & 0 deletions Whisper.net/Logger/WhisperLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed under the MIT license: https://opensource.org/licenses/MIT

namespace Whisper.net.Logger;

internal class WhisperLogger : IDisposable
{
private readonly Action<WhisperLogLevel, string?> logAction;

public WhisperLogger(Action<WhisperLogLevel, string?> logAction)
{
this.logAction = logAction;
OnLog += logAction;
}

public static event Action<WhisperLogLevel, string?>? OnLog;

public void Dispose()
{
OnLog -= logAction;
}

public static void Log(WhisperLogLevel level, string? message)
{
OnLog?.Invoke(level, message);
}
}
5 changes: 3 additions & 2 deletions examples/CoreML/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ await WhisperGgmlDownloader.GetEncoderCoreMLModelAsync(ggmlType)
}

// Optional logging from the native library
LogProvider.OnLog += (level, message) =>

using var whisperLogger = LogProvider.AddLogger((level, message) =>
{
Console.Write($"{level}: {message}");
};
});

// This section creates the whisperFactory object which is used to create the processor object.
using var whisperFactory = WhisperFactory.FromPath(modelFileName);
Expand Down
4 changes: 2 additions & 2 deletions examples/MultiRuntime/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public static async Task Main(string[] args)
var modelFileName = "ggml-base.bin";
var wavFileName = "kennedy.wav";

LogProvider.OnLog += (level, message) =>
using var whisperLogger = LogProvider.AddLogger((level, message) =>
{
Console.Write($"{level}: {message}");
};
});

// Optional set the order of the runtimes:
RuntimeOptions.RuntimeLibraryOrder = [RuntimeLibrary.Cuda, RuntimeLibrary.Cpu];
Expand Down
5 changes: 1 addition & 4 deletions examples/NvidiaCuda/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ public static async Task Main(string[] args)
var modelFileName = "ggml-base.bin";
var wavFileName = "kennedy.wav";

LogProvider.OnLog += (level, message) =>
{
Console.Write($"{level}: {message}");
};
using var whisperLogger = LogProvider.AddConsoleLogging(WhisperLogLevel.Debug);

// This section detects whether the "ggml-base.bin" file exists in our project disk. If it doesn't, it downloads it from the internet
if (!File.Exists(modelFileName))
Expand Down
5 changes: 1 addition & 4 deletions examples/OpenVinoExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ public static async Task Main(string[] args)
var wavFileName = "kennedy.wav";
var encoderDirectoryName = "ggml-small-encoder";

LogProvider.OnLog += (level, message) =>
{
Console.Write($"{level}: {message}");
};
using var whisperLogger = LogProvider.AddConsoleLogging(WhisperLogLevel.Debug);

// This section detects whether the "ggml-small" file exists in our project disk. If it doesn't, it downloads it from the internet
if (!File.Exists(modelFileName))
Expand Down
5 changes: 1 addition & 4 deletions examples/ParallelExecution/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ public static async Task Main(string[] args)
}

// Optional logging from the native library
LogProvider.OnLog += (level, message) =>
{
Console.Write($"{level}: {message}");
};
using var whisperLogger = LogProvider.AddConsoleLogging(WhisperLogLevel.Debug);

// This section creates the whisperFactory object which is used to create the processor object.
using var whisperFactory = WhisperFactory.FromPath("ggml-base.bin");
Expand Down
5 changes: 1 addition & 4 deletions examples/Simple/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ public static async Task Main(string[] args)
}

// Optional logging from the native library
LogProvider.OnLog += (level, message) =>
{
Console.Write($"{level}: {message}");
};
using var whisperLogger = LogProvider.AddConsoleLogging(WhisperLogLevel.Debug);

// This section creates the whisperFactory object which is used to create the processor object.
using var whisperFactory = WhisperFactory.FromPath("ggml-base.bin");
Expand Down
5 changes: 1 addition & 4 deletions examples/SimpleSync/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ public static async Task Main(string[] args)
}

// Optional logging from the native library
LogProvider.OnLog += (level, message) =>
{
Console.WriteLine($"{level}: {message}");
};
using var whisperLogger = LogProvider.AddConsoleLogging(WhisperLogLevel.Debug);

// This section creates the whisperFactory object which is used to create the processor object.
using var whisperFactory = WhisperFactory.FromPath("ggml-base.bin");
Expand Down
5 changes: 1 addition & 4 deletions examples/Vulkan/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ public static async Task Main(string[] args)
var modelFileName = "ggml-largev3.bin";
var wavFileName = "kennedy.wav";

LogProvider.OnLog += (level, message) =>
{
Console.Write($"{level}: {message}");
};
using var whisperLogger = LogProvider.AddConsoleLogging(WhisperLogLevel.Debug);

// This section detects whether the "ggml-largev3.bin" file exists in our project disk. If it doesn't, it downloads it from the internet
if (!File.Exists(modelFileName))
Expand Down
11 changes: 8 additions & 3 deletions tests/Whisper.net.Tests/FactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@ public sealed class FactoryTests : IClassFixture<TinyModelFixture>, IDisposable
{
private readonly TinyModelFixture model;
private readonly ITestOutputHelper output;

private readonly List<IDisposable> loggers = [];

public FactoryTests(TinyModelFixture model, ITestOutputHelper output)
{
LogProvider.AddConsoleLogging(minLevel: WhisperLogLevel.Debug);
LogProvider.OnLog += OnLog;
loggers.Add(LogProvider.AddConsoleLogging(minLevel: WhisperLogLevel.Debug));
loggers.Add(LogProvider.AddLogger(OnLog));
this.model = model;
this.output = output;
}

public void Dispose()
{
LogProvider.OnLog -= OnLog;
foreach (var logger in loggers)
{
logger.Dispose();
}
}

[Fact]
Expand Down

0 comments on commit 717f948

Please sign in to comment.