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);
}
}