From 35747253b8065dbde4d860e15afd9a4d2860f68d Mon Sep 17 00:00:00 2001 From: RMBGAME <12234359+rmbadmin@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:33:18 +0800 Subject: [PATCH] Feature/ipc console log (#3463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎨 LogConsole * 🐛 ref * 💄 ProxyLog UI * 🐛 WebApplication Logging * 🐛 Misc * 🎨 Log format * 🎨 LogConsoleService.ConsoleMaxCharCount * 🐛 Misc --------- Co-authored-by: Aigio Liu --- ...nt.Plugins.Accelerator.ReverseProxy.csproj | 4 + .../Program.cs | 1 + .../LazyReverseProxyServiceImpl.cs | 2 + .../YarpReverseProxyServiceImpl.cs | 8 + .../Services/IReverseProxyService.cs | 6 + .../UI/Views/Controls/ProxyChartView.axaml.cs | 4 - .../UI/Views/Controls/ProxyLog.axaml | 20 + .../UI/Views/Controls/ProxyLog.axaml.cs | 92 +++++ .../UI/Views/Pages/AcceleratorPage2.axaml | 3 + src/BD.WTTS.Client/App/IApplication.Log.cs | 16 + src/BD.WTTS.Client/BD.WTTS.Client.csproj | 1 + src/BD.WTTS.Client/Logging/ClientLogger.cs | 10 +- .../Resources/Strings.Designer.cs | 9 + src/BD.WTTS.Client/Resources/Strings.resx | 3 + .../IPC/IPCMainProcessServiceImpl.cs | 7 + .../Services/IPC/IPCMainProcessService.cs | 2 + .../Services/Mvvm/LogConsoleService.Logger.cs | 362 ++++++++++++++++++ .../Services/Mvvm/LogConsoleService.cs | 98 +++++ 18 files changed, 636 insertions(+), 12 deletions(-) create mode 100644 src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml create mode 100644 src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml.cs create mode 100644 src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.Logger.cs create mode 100644 src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.cs diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj index 7350d4985a5..560ba13e352 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy.csproj @@ -76,6 +76,9 @@ Services\Platform + + Services\Mvvm + @@ -104,6 +107,7 @@ + diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Program.cs b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Program.cs index 6e5cd83f416..7e1d39267ba 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Program.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Program.cs @@ -81,6 +81,7 @@ static void ConfigureServices(IServiceCollection services) #if DEBUG l.AddConsole(); #endif + l.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(moduleName)); }); // 设置仓储层数据库文件存放路径 diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/LazyReverseProxyServiceImpl.cs b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/LazyReverseProxyServiceImpl.cs index fdd785a6169..2550649d406 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/LazyReverseProxyServiceImpl.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/LazyReverseProxyServiceImpl.cs @@ -23,6 +23,8 @@ public IReadOnlyCollection? Scripts public byte[]? GetFlowStatistics_Bytes() => impl().GetFlowStatistics_Bytes(); + public string? GetLogAllMessage() => impl().GetLogAllMessage(); + public async Task StartProxyAsync(byte[] reverseProxySettings) { var result = await impl().StartProxyAsync(reverseProxySettings); diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/YarpReverseProxyServiceImpl.cs b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/YarpReverseProxyServiceImpl.cs index 7569f2c2bce..e9a96665706 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/YarpReverseProxyServiceImpl.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/YarpReverseProxyServiceImpl.cs @@ -65,6 +65,8 @@ StartProxyResult StartProxyCore() WebRootPath = RootPath, }); + builder.Logging.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(AssemblyInfo.Accelerator)); + builder.Services.Configure(static o => { o.AllowEmptyHosts = true; @@ -167,6 +169,12 @@ public async Task StopProxyAsync() return bytes; } + public string? GetLogAllMessage() + { + var result = LogConsoleService.Builder; + return result.ToString(); + } + // IDisposable protected override void DisposeCore() diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services/IReverseProxyService.cs b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services/IReverseProxyService.cs index 92ca8bf7e15..f071c5e7a30 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services/IReverseProxyService.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services/IReverseProxyService.cs @@ -15,6 +15,12 @@ partial interface IReverseProxyService /// /// byte[]? GetFlowStatistics_Bytes(); + + /// + /// 获取所有日志信息 + /// + /// + string? GetLogAllMessage(); } public static partial class ReverseProxyServiceExtensions diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyChartView.axaml.cs b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyChartView.axaml.cs index 670b0c29100..34d863b48b2 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyChartView.axaml.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyChartView.axaml.cs @@ -1,14 +1,10 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Data; using Avalonia.Threading; using Avalonia.VisualTree; using LiveChartsCore; -using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Avalonia; -using LiveChartsCore.SkiaSharpView.Drawing; -using SkiaSharp; namespace BD.WTTS.UI.Views.Controls; diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml new file mode 100644 index 00000000000..41a3103f18c --- /dev/null +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml @@ -0,0 +1,20 @@ + + + + + diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml.cs b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml.cs new file mode 100644 index 00000000000..5b295ccb7b7 --- /dev/null +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Controls/ProxyLog.axaml.cs @@ -0,0 +1,92 @@ +using Avalonia.Controls; +using Avalonia.Threading; +using Avalonia.VisualTree; +using AvaloniaEdit; +using AvaloniaEdit.TextMate; +using Org.BouncyCastle.Math; +using TextMateSharp.Grammars; + +namespace BD.WTTS.UI.Views.Controls; + +public partial class ProxyLog : UserControl +{ + CancellationTokenSource cancellation = new(); + + public ProxyLog() + { + InitializeComponent(); + + var logTextbox = this.FindControl("LogTextbox")!; + + //Here we initialize RegistryOptions with the theme we want to use. + var registryOptions = new TextMateSharp.Grammars.RegistryOptions(ThemeName.Dark); + + //Initial setup of TextMate. + var textMateInstallation = logTextbox.InstallTextMate(registryOptions); + + //Here we are getting the language by the extension and right after that we are initializing grammar with this language. + //And that's all 😀, you are ready to use AvaloniaEdit with syntax highlighting! + textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".log").Id)); + + //logTextbox.Text = LogText; + //logTextbox.ScrollToLine(logTextbox.Document.LineCount); + + logTextbox.TextChanged += LogTextbox_TextChanged; + + ProxyService.Current.WhenAnyValue(x => x.ProxyStatus) + .Subscribe(x => + { + if (x) + { + cancellation = new CancellationTokenSource(); + Task2.InBackground(FlushLogsAsync, true); + } + else + { + if (cancellation != null) + { + cancellation.Cancel(); + cancellation.Dispose(); + } + } + }); + } + + private void LogTextbox_TextChanged(object? sender, EventArgs e) + { + LogTextbox.ScrollToLine(LogTextbox.Document.LineCount); + } + + void FlushLogsAsync() + { + while (!cancellation.IsCancellationRequested) + { + try + { + var isAttachedToVisualTree = this.IsAttachedToVisualTree(); + if (isAttachedToVisualTree) + { + var logtext = IReverseProxyService.Constants.Instance.GetLogAllMessage(); + if (string.IsNullOrEmpty(logtext)) + { + Thread.Sleep(1000); + continue; + } + + Dispatcher.UIThread.Post(() => + { + if (LogTextbox.Text.Length != logtext.Length) + LogTextbox.Text = logtext; + }); + } + } + catch + { + } + finally + { + Thread.Sleep(1000); + } + } + } +} \ No newline at end of file diff --git a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml index decab56d4ab..38fd421f5b0 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml +++ b/src/BD.WTTS.Client.Plugins.Accelerator/UI/Views/Pages/AcceleratorPage2.axaml @@ -449,6 +449,7 @@ --> + @@ -557,6 +558,8 @@ + + diff --git a/src/BD.WTTS.Client/App/IApplication.Log.cs b/src/BD.WTTS.Client/App/IApplication.Log.cs index 206b1d90965..a6252f00715 100644 --- a/src/BD.WTTS.Client/App/IApplication.Log.cs +++ b/src/BD.WTTS.Client/App/IApplication.Log.cs @@ -86,6 +86,22 @@ public static Action ConfigureLogging(LogLevel minLevel = Defau //#elif MACOS || MACCATALYST || (IOS && DEBUG) // builder.AddProvider(Logging.OSLogLoggerProvider.Instance); //#endif + + //#if !ANDROID && !IOS + // var isBackend = false; + // try + // { + // isBackend = Startup.Instance.HasIPCRoot; + // } + // catch + // { + // } + + // if (isBackend) + // { + // builder.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(IPlatformService.IPCRoot.moduleName)); + // } + //#endif }; } diff --git a/src/BD.WTTS.Client/BD.WTTS.Client.csproj b/src/BD.WTTS.Client/BD.WTTS.Client.csproj index 7c4c31f2c50..aafb0a9ac8f 100644 --- a/src/BD.WTTS.Client/BD.WTTS.Client.csproj +++ b/src/BD.WTTS.Client/BD.WTTS.Client.csproj @@ -122,6 +122,7 @@ + diff --git a/src/BD.WTTS.Client/Logging/ClientLogger.cs b/src/BD.WTTS.Client/Logging/ClientLogger.cs index 0378a64afd9..a4c68cf154b 100644 --- a/src/BD.WTTS.Client/Logging/ClientLogger.cs +++ b/src/BD.WTTS.Client/Logging/ClientLogger.cs @@ -7,14 +7,8 @@ namespace BD.WTTS.Logging; /// public abstract class ClientLogger : ILogger { - static readonly string _messagePadding; - static readonly string _newLineWithMessagePadding; - - static ClientLogger() - { - _messagePadding = new string(' ', 6); - _newLineWithMessagePadding = Environment.NewLine + _messagePadding; - } + static readonly string _messagePadding = new string(' ', 6); + static readonly string _newLineWithMessagePadding = Environment.NewLine + _messagePadding; protected readonly string name; diff --git a/src/BD.WTTS.Client/Resources/Strings.Designer.cs b/src/BD.WTTS.Client/Resources/Strings.Designer.cs index 03593c2cf9d..2def77249b0 100644 --- a/src/BD.WTTS.Client/Resources/Strings.Designer.cs +++ b/src/BD.WTTS.Client/Resources/Strings.Designer.cs @@ -1358,6 +1358,15 @@ public static string CommunityFix_OpenHostsDir { } } + /// + /// 查找类似 加速日志 的本地化字符串。 + /// + public static string CommunityFix_ProxyLog { + get { + return ResourceManager.GetString("CommunityFix_ProxyLog", resourceCulture); + } + } + /// /// 查找类似 加速模式 的本地化字符串。 /// diff --git a/src/BD.WTTS.Client/Resources/Strings.resx b/src/BD.WTTS.Client/Resources/Strings.resx index e6ffdd80b77..1d162ace81f 100644 --- a/src/BD.WTTS.Client/Resources/Strings.resx +++ b/src/BD.WTTS.Client/Resources/Strings.resx @@ -2611,6 +2611,9 @@ 已加速 Comment=末尾带空格; + + + 加速日志 流量统计 diff --git a/src/BD.WTTS.Client/Services.Implementation/IPC/IPCMainProcessServiceImpl.cs b/src/BD.WTTS.Client/Services.Implementation/IPC/IPCMainProcessServiceImpl.cs index 1debf99f2a3..d924b78fc78 100644 --- a/src/BD.WTTS.Client/Services.Implementation/IPC/IPCMainProcessServiceImpl.cs +++ b/src/BD.WTTS.Client/Services.Implementation/IPC/IPCMainProcessServiceImpl.cs @@ -450,6 +450,13 @@ public async Task ExitModules(IEnumerable moduleNames) return result.All(static x => x.result); } + public void WriteMessage(string? moduleName, byte[] bytes) + { +#if !ANDROID && !IOS + LogConsoleService.Current.WriteMessage(moduleName, bytes); +#endif + } + /// /// 配置服务 /// diff --git a/src/BD.WTTS.Client/Services/IPC/IPCMainProcessService.cs b/src/BD.WTTS.Client/Services/IPC/IPCMainProcessService.cs index 38ccf093447..5568c19f205 100644 --- a/src/BD.WTTS.Client/Services/IPC/IPCMainProcessService.cs +++ b/src/BD.WTTS.Client/Services/IPC/IPCMainProcessService.cs @@ -67,4 +67,6 @@ public interface IPCMainProcessService : IAsyncDisposable /// /// ValueTask GetServiceAsync(string moduleName) where T : class; + + void WriteMessage(string? moduleName, byte[] bytes); } \ No newline at end of file diff --git a/src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.Logger.cs b/src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.Logger.cs new file mode 100644 index 00000000000..cc504205b19 --- /dev/null +++ b/src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.Logger.cs @@ -0,0 +1,362 @@ +#if !ANDROID && !IOS +using Utf8StringInterpolation; + +// ReSharper disable once CheckNamespace +namespace BD.WTTS.Services; + +partial class LogConsoleService +{ + /// + /// 控制台显示最大字符数 + /// + const int ConsoleMaxCharCount = 10_0000; + + static readonly int ConsoleMaxByteCount = Encoding.UTF8.GetMaxByteCount(ConsoleMaxCharCount); + + /// + /// 日志源 + /// + [DebuggerDisplay("{DebuggerDisplay(),nq}")] + internal sealed partial class Source + { + List? chunk; + (string? str, long byteLength)? message = null; + +#if DEBUG + readonly string moduleName; + + public Source(string moduleName) + { + this.moduleName = moduleName; + } + + string DebuggerDisplay() => +$""" +moduleName: {moduleName} +ByteLength: {ByteLength} +{ToString()} +"""; +#endif + + public long ByteLength { get; private set; } + + public void BuilderAppend(byte[] value) + { + (chunk ??= new()).Add(value); + ByteLength += value.Length; + } + + void BuilderClear() + { + if (chunk != null && chunk.Count > 1) + { + // 保留最后 21.3% 的日志 + var keppLength = (int)MathF.Floor(chunk.Count * .213f); + if (keppLength <= 0 || keppLength >= chunk.Count) + { + keppLength = 1; + } + chunk = chunk.TakeLast(keppLength).ToList(); + } + + message = null; + ByteLength = 0; + } + + /// + /// 追加收到的日志 UTF-8 字节 + /// + public void Append(byte[]? value) + { + if (value != null && value.Length > 0) + { + if (ByteLength > ConsoleMaxByteCount) + { + BuilderClear(); + } + + // 追加收到的日志 UTF-8 字节 + BuilderAppend(value); + } + } + + public override string? ToString() + { + if (message != null) + { + if (ByteLength == message.Value.byteLength) + { + // 根据 len 判断是否使用缓存 + return message.Value.str; + } + } + + if (chunk == null) + { + return null; + } + else if (chunk.Count <= 0) + { + return string.Empty; + } + + using var buffer = Utf8String.CreateWriter(out var writer); + + foreach (var it in chunk) + { + // 拼接字符串 + writer.AppendUtf8(it); + } + + writer.Flush(); + + var u8_bytes = buffer.ToArray(); + + chunk = [u8_bytes]; + + var result = Encoding.UTF8.GetString(u8_bytes); + + // 设置缓存值 + message = new(result, ByteLength); + return result; + } + } + + #region 服务进程部分 + + static readonly string _messagePadding = new string(' ', 6); + static readonly string _newLineWithMessagePadding = Environment.NewLine + _messagePadding; + + static byte[] CreateDefaultLogMessage(string logName, int eventId, string? message, Exception? exception) + { + // https://github.com/dotnet/extensions/blob/v3.1.5/src/Logging/Logging.Console/src/ConsoleLogger.cs + // TODO: 对比 SimpleConsoleFormatter 实现带 Color 的版本 + // https://github.com/dotnet/runtime/blob/v9.0.0-preview.7.24405.7/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs + + if (!string.IsNullOrEmpty(message)) + { + StringBuilder builder = new(); + CreateDefaultLogMessage(builder, logName, eventId, message, exception); + + var str = builder.ToString(); + return Encoding.UTF8.GetBytes(str); + } + else + { + using var buffer = Utf8String.CreateWriter(out var logBuilder); + + // Example: + // INFO: ConsoleApp.Program[10] + // Request received + + // category and event id + logBuilder.Append(logName); + logBuilder.Append('['); + logBuilder.Append(eventId.ToString()); + logBuilder.AppendLine("]"); + + // Example: + // System.InvalidOperationException + // at Namespace.Class.Function() in File:line X + if (exception != null) + // exception message + logBuilder.AppendLine(exception.ToString()); + + logBuilder.Flush(); + + var result = buffer.ToArray(); + return result; + } + } + + static void CreateDefaultLogMessage(StringBuilder logBuilder, string logName, int eventId, string? message, Exception? exception) + { + // Example: + // INFO: ConsoleApp.Program[10] + // Request received + + // category and event id + logBuilder.Append(logName); + logBuilder.Append('['); + logBuilder.Append(eventId); + logBuilder.AppendLine("]"); + + if (!string.IsNullOrEmpty(message)) + { + // message + logBuilder.Append(_messagePadding); + + var len = logBuilder.Length; + logBuilder.AppendLine(message); + logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length); + } + + // Example: + // System.InvalidOperationException + // at Namespace.Class.Function() in File:line X + if (exception != null) + // exception message + logBuilder.AppendLine(exception.ToString()); + } + + static byte[] CreateDefaultLogMessageWithNLogFileLayout(LogLevel logLevel, string logName, int eventId, string? message, Exception? exception) + { + // Layout = " + // ${longdate}| + // ${level}| + // ${logger}|${message} |${all-event-properties} ${exception:format=tostring}", + + using var buffer = Utf8String.CreateWriter(out var logBuilder); + + logBuilder.Append(DateTime.Now.ToString( + "yyyy-MM-dd HH:mm:ss.fff", + CultureInfo.InvariantCulture)); + logBuilder.AppendUtf8("|"u8); + + switch (logLevel) // see NLog.LogLevel + { + case LogLevel.Trace: + logBuilder.AppendUtf8("Trace"u8); + break; + case LogLevel.Debug: + logBuilder.AppendUtf8("Debug"u8); + break; + case LogLevel.Information: + logBuilder.AppendUtf8("Info"u8); + break; + case LogLevel.Warning: + logBuilder.AppendUtf8("Warn"u8); + break; + case LogLevel.Error: + logBuilder.AppendUtf8("Error"u8); + break; + case LogLevel.Critical: + logBuilder.AppendUtf8("Fatal"u8); + break; + case LogLevel.None: + logBuilder.AppendUtf8("Off"u8); + break; + default: + logBuilder.Append(logLevel.ToString()); + break; + } + logBuilder.AppendUtf8("|"u8); + + logBuilder.Append(logName); + logBuilder.AppendUtf8("|"u8); + + logBuilder.Append(message); + logBuilder.AppendUtf8("| "u8); + + logBuilder.Append(exception?.ToString()); + logBuilder.AppendLine(); + + logBuilder.Flush(); + + var result = buffer.ToArray(); + return result; + } + +#if APP_REVERSE_PROXY + internal static readonly Source Builder = new( +#if DEBUG + AssemblyInfo.Accelerator +#endif + ); +#endif + + internal sealed class Utf8StringLogger(string name) : ILogger + { + readonly string name = name; + + public IDisposable? BeginScope(TState state) where TState : notnull + { + return NullScope.Instance; + } + + public bool IsEnabled(LogLevel logLevel) + { + // TODO: 可选加入设置项 +#if DEBUG + return true; +#else + return logLevel >= LogLevel.Information; +#endif + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) + return; + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + var message = formatter(state, exception); + + if (!string.IsNullOrEmpty(message) || exception != null) + { + +#if APP_REVERSE_PROXY + var logMessageU8 = CreateDefaultLogMessageWithNLogFileLayout(logLevel, name, eventId.Id, message, exception); + Builder.Append(logMessageU8); +#else + try + { +#if APP_REVERSE_PROXY + //var s = IReverseProxyService.Instance; + // TODO Ipc +#else + var s = IPCMainProcessService.Instance; +#endif + var logMessage = CreateDefaultLogMessage(name, eventId.Id, message, exception); + s.WriteMessage(Utf8StringLoggerProvider.ModuleName, logMessage); + } + catch + { + } +#endif + } + } + } + + /// + /// An empty scope without any logic + /// https://github.com/dotnet/extensions/blob/v3.1.5/src/Logging/shared/NullScope.cs + /// https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Common/src/Extensions/Logging/NullScope.cs + /// + sealed class NullScope : IDisposable + { + public static NullScope Instance { get; } = new(); + + NullScope() + { + } + + public void Dispose() + { + } + } + + internal sealed class Utf8StringLoggerProvider : ILoggerProvider + { + internal static string? ModuleName { get; private set; } + + public Utf8StringLoggerProvider(string moduleName) + { + ModuleName = moduleName; + } + + public ILogger CreateLogger(string name) + { + return new Utf8StringLogger(name); + } + + void IDisposable.Dispose() + { + } + } + + #endregion +} +#endif diff --git a/src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.cs b/src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.cs new file mode 100644 index 00000000000..11f4c36329a --- /dev/null +++ b/src/BD.WTTS.Client/Services/Mvvm/LogConsoleService.cs @@ -0,0 +1,98 @@ +#if !ANDROID && !IOS +using Avalonia.Threading; +using Utf8StringInterpolation; + +// ReSharper disable once CheckNamespace +namespace BD.WTTS.Services; + +public sealed partial class LogConsoleService : ReactiveObject +{ + static readonly Lazy mCurrent = new(() => new(), LazyThreadSafetyMode.ExecutionAndPublication); + + public static LogConsoleService Current => mCurrent.Value; + + LogConsoleService() + { + + } + + #region UI 进程部分 + + readonly string?[] logMessages = new string?[2]; + + /// + /// 加速进程的日志 + /// + public string? LogMessageAccelerator => logMessages[0]; + + /// + /// 后端进程的日志 + /// + public string? LogMessageBackEnd => logMessages[1]; + + /// + /// 日志源 + /// + readonly Dictionary sources = new(); + + partial class Source + { + /// + /// 追加收到的日志 UTF-8 字节并触发属性通知 + /// + public void AppendWithPropertyChanged(IReactiveObject o, byte[]? value, + string propertyName, ref string? propertyValue) + { + if (value != null && value.Length > 0) + { + Append(value); + + var logAllMessage = ToString(); + propertyValue = logAllMessage; + + Dispatcher.UIThread.Invoke(() => + { + o.RaisePropertyChanged(propertyName); + }); + + } + } + } + + /// + /// 追加收到的日志 UTF-8 字节 + /// + /// + /// + internal void WriteMessage(string? moduleName, byte[]? value) + { + if (!string.IsNullOrWhiteSpace(moduleName) && + value != null && value.Length > 0) + { + if (!sources.TryGetValue(moduleName, out var source)) + { + source = new( +#if DEBUG + moduleName +#endif + ); + sources.Add(moduleName, source); + } + + switch (moduleName) + { + case AssemblyInfo.Accelerator: + source.AppendWithPropertyChanged(this, value, + nameof(LogMessageAccelerator), ref logMessages[0]); + break; + case IPlatformService.IPCRoot.moduleName: + source.AppendWithPropertyChanged(this, value, + nameof(LogMessageBackEnd), ref logMessages[1]); + break; + } + } + } + + #endregion +} +#endif \ No newline at end of file