using LLama.Native; using System; using System.Diagnostics; using System.IO; using static LLama.Common.ILLamaLogger; namespace LLama.Common; /// /// receives log messages from LLamaSharp /// public interface ILLamaLogger { /// /// Severity level of a log message /// public enum LogLevel { /// /// Logs that are used for interactive investigation during development. /// Debug = 1, /// /// Logs that highlight when the current flow of execution is stopped due to a failure. /// Error = 2, /// /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop. /// Warning = 3, /// /// Logs that track the general flow of the application. /// Info = 4 } /// /// Write the log in customized way /// /// The source of the log. It may be a method name or class name. /// The message. /// The log level. void Log(string source, string message, LogLevel level); } /// /// The default logger of LLamaSharp. On default it write to console. Use methods of `LLamaLogger.Default` to change the behavior. /// It's recommended to inherit `ILLamaLogger` to customize the behavior. /// public sealed class LLamaDefaultLogger : ILLamaLogger { private static readonly Lazy _instance = new Lazy(() => new LLamaDefaultLogger()); private bool _toConsole = true; private bool _toFile; private FileStream? _fileStream; private StreamWriter? _fileWriter; /// /// Get the default logger instance /// public static LLamaDefaultLogger Default => _instance.Value; private LLamaDefaultLogger() { } /// /// Enable logging output from llama.cpp /// /// public LLamaDefaultLogger EnableNative() { EnableNativeLogCallback(); return this; } /// /// Enable writing log messages to console /// /// public LLamaDefaultLogger EnableConsole() { _toConsole = true; return this; } /// /// Disable writing messages to console /// /// public LLamaDefaultLogger DisableConsole() { _toConsole = false; return this; } /// /// Enable writing log messages to file /// /// /// /// public LLamaDefaultLogger EnableFile(string filename, FileMode mode = FileMode.Append) { _fileStream = new FileStream(filename, mode, FileAccess.Write); _fileWriter = new StreamWriter(_fileStream); _toFile = true; return this; } /// /// Disable writing log messages to file /// /// unused! /// [Obsolete("Use DisableFile method without 'filename' parameter")] public LLamaDefaultLogger DisableFile(string filename) { return DisableFile(); } /// /// Disable writing log messages to file /// /// public LLamaDefaultLogger DisableFile() { if (_fileWriter is not null) { _fileWriter.Close(); _fileWriter = null; } if (_fileStream is not null) { _fileStream.Close(); _fileStream = null; } _toFile = false; return this; } /// /// Log a message /// /// The source of this message (e.g. class name) /// The message to log /// Severity level of this message public void Log(string source, string message, LogLevel level) { if (level == LogLevel.Info) { Info(message); } else if (level == LogLevel.Debug) { } else if (level == LogLevel.Warning) { Warn(message); } else if (level == LogLevel.Error) { Error(message); } } /// /// Write a log message with "Info" severity /// /// public void Info(string message) { message = MessageFormat("info", message); if (_toConsole) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(message); Console.ResetColor(); } if (_toFile) { Debug.Assert(_fileStream is not null); Debug.Assert(_fileWriter is not null); _fileWriter.WriteLine(message); } } /// /// Write a log message with "Warn" severity /// /// public void Warn(string message) { message = MessageFormat("warn", message); if (_toConsole) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine(message); Console.ResetColor(); } if (_toFile) { Debug.Assert(_fileStream is not null); Debug.Assert(_fileWriter is not null); _fileWriter.WriteLine(message); } } /// /// Write a log message with "Error" severity /// /// public void Error(string message) { message = MessageFormat("error", message); if (_toConsole) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(message); Console.ResetColor(); } if (_toFile) { Debug.Assert(_fileStream is not null); Debug.Assert(_fileWriter is not null); _fileWriter.WriteLine(message); } } private static string MessageFormat(string level, string message) { var now = DateTime.Now; return $"[{now:yyyy.MM.dd HH:mm:ss}][{level}]: {message}"; } /// /// Register native logging callback /// private void EnableNativeLogCallback() { // TODO: Move to a more appropriate place once we have a intitialize method NativeApi.llama_log_set(NativeLogCallback); } /// /// Callback for native logging function /// /// The log level /// The log message private void NativeLogCallback(LogLevel level, string message) { if (string.IsNullOrEmpty(message)) return; // Note that text includes the new line character at the end for most events. // If your logging mechanism cannot handle that, check if the last character is '\n' and strip it // if it exists. // It might not exist for progress report where '.' is output repeatedly. Log(default!, message.TrimEnd('\n'), level); } }