mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-20 00:11:04 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e813d4d24 | |||
| 7b7ed945d4 | |||
| 585a7f01d0 | |||
| 803d17a5f8 | |||
| c6a2cd67b4 | |||
| e3ef74f08e | |||
| b43cc93215 | |||
| 6149625373 | |||
| c6f6dbd7ef |
+4
@@ -76,6 +76,9 @@
|
||||
<Compile Include="..\BD.WTTS.Client\Services\Platform\IPlatformService.OS.Linux.Etc.Os_Release.cs">
|
||||
<LinkBase>Services\Platform</LinkBase>
|
||||
</Compile>
|
||||
<Compile Include="..\BD.WTTS.Client\Services\Mvvm\LogConsoleService.Logger.cs">
|
||||
<LinkBase>Services\Mvvm</LinkBase>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -104,6 +107,7 @@
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="System.Runtime.Caching" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
<PackageReference Include="Utf8StringInterpolation" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
|
||||
|
||||
@@ -81,6 +81,7 @@ static void ConfigureServices(IServiceCollection services)
|
||||
#if DEBUG
|
||||
l.AddConsole();
|
||||
#endif
|
||||
l.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(moduleName));
|
||||
});
|
||||
|
||||
// 设置仓储层数据库文件存放路径
|
||||
|
||||
+2
@@ -23,6 +23,8 @@ sealed class LazyReverseProxyServiceImpl : IReverseProxyService
|
||||
|
||||
public byte[]? GetFlowStatistics_Bytes() => impl().GetFlowStatistics_Bytes();
|
||||
|
||||
public string? GetLogAllMessage() => impl().GetLogAllMessage();
|
||||
|
||||
public async Task<StartProxyResult> StartProxyAsync(byte[] reverseProxySettings)
|
||||
{
|
||||
var result = await impl().StartProxyAsync(reverseProxySettings);
|
||||
|
||||
+8
@@ -65,6 +65,8 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
|
||||
WebRootPath = RootPath,
|
||||
});
|
||||
|
||||
builder.Logging.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(AssemblyInfo.Accelerator));
|
||||
|
||||
builder.Services.Configure<HostFilteringOptions>(static o =>
|
||||
{
|
||||
o.AllowEmptyHosts = true;
|
||||
@@ -167,6 +169,12 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public string? GetLogAllMessage()
|
||||
{
|
||||
var result = LogConsoleService.Builder;
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
// IDisposable
|
||||
|
||||
protected override void DisposeCore()
|
||||
|
||||
@@ -15,6 +15,12 @@ partial interface IReverseProxyService
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
byte[]? GetFlowStatistics_Bytes();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有日志信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string? GetLogAllMessage();
|
||||
}
|
||||
|
||||
public static partial class ReverseProxyServiceExtensions
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<UserControl
|
||||
x:Class="BD.WTTS.UI.Views.Controls.ProxyLog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:AvaloniaEdit="using:AvaloniaEdit"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
<AvaloniaEdit:TextEditor
|
||||
x:Name="LogTextbox"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
IsReadOnly="True"
|
||||
ShowLineNumbers="True" />
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -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<TextEditor>("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,6 +449,7 @@
|
||||
</Border>
|
||||
</WrapPanel>-->
|
||||
</TabStripItem>
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_ProxyLog}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_FlowStatistics}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Name="GameAccTab">
|
||||
@@ -557,6 +558,8 @@
|
||||
</ItemsControl>
|
||||
</spp:ContentLoader>
|
||||
</ScrollViewer>
|
||||
<!-- 加速日志 -->
|
||||
<spp:ProxyLog Margin="5,0" />
|
||||
|
||||
<!-- 流量统计 -->
|
||||
<spp:ProxyChartView Margin="5,0" />
|
||||
|
||||
@@ -86,6 +86,22 @@ partial interface IApplication
|
||||
//#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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
<PackageReference Include="SkiaSharp" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" />
|
||||
<PackageReference Include="HarfBuzzSharp" />
|
||||
<PackageReference Include="Utf8StringInterpolation" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
|
||||
|
||||
@@ -7,14 +7,8 @@ namespace BD.WTTS.Logging;
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
|
||||
@@ -1358,6 +1358,15 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 加速日志 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string CommunityFix_ProxyLog {
|
||||
get {
|
||||
return ResourceManager.GetString("CommunityFix_ProxyLog", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 加速模式 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -2611,6 +2611,9 @@
|
||||
<data name="CommunityFix_AlreadyProxy" xml:space="preserve">
|
||||
<value>已加速 </value>
|
||||
<comment>Comment=末尾带空格;</comment>
|
||||
</data>
|
||||
<data name="CommunityFix_ProxyLog" xml:space="preserve">
|
||||
<value>加速日志</value>
|
||||
</data>
|
||||
<data name="CommunityFix_FlowStatistics" xml:space="preserve">
|
||||
<value>流量统计</value>
|
||||
|
||||
@@ -450,6 +450,13 @@ public sealed partial class IPCMainProcessServiceImpl : IPCMainProcessService
|
||||
return result.All(static x => x.result);
|
||||
}
|
||||
|
||||
public void WriteMessage(string? moduleName, byte[] bytes)
|
||||
{
|
||||
#if !ANDROID && !IOS
|
||||
LogConsoleService.Current.WriteMessage(moduleName, bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置服务
|
||||
/// </summary>
|
||||
|
||||
@@ -67,4 +67,6 @@ public interface IPCMainProcessService : IAsyncDisposable
|
||||
/// <param name="moduleName"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<T?> GetServiceAsync<T>(string moduleName) where T : class;
|
||||
|
||||
void WriteMessage(string? moduleName, byte[] bytes);
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
#if !ANDROID && !IOS
|
||||
using Utf8StringInterpolation;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS.Services;
|
||||
|
||||
partial class LogConsoleService
|
||||
{
|
||||
/// <summary>
|
||||
/// 控制台显示最大字符数
|
||||
/// </summary>
|
||||
const int ConsoleMaxCharCount = 10_0000;
|
||||
|
||||
static readonly int ConsoleMaxByteCount = Encoding.UTF8.GetMaxByteCount(ConsoleMaxCharCount);
|
||||
|
||||
/// <summary>
|
||||
/// 日志源
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
|
||||
internal sealed partial class Source
|
||||
{
|
||||
List<byte[]>? 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追加收到的日志 UTF-8 字节
|
||||
/// </summary>
|
||||
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>(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<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An empty scope without any logic
|
||||
/// <para>https://github.com/dotnet/extensions/blob/v3.1.5/src/Logging/shared/NullScope.cs</para>
|
||||
/// <para>https://github.com/dotnet/runtime/blob/v5.0.0-rtm.20519.4/src/libraries/Common/src/Extensions/Logging/NullScope.cs</para>
|
||||
/// </summary>
|
||||
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
|
||||
@@ -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<LogConsoleService> mCurrent = new(() => new(), LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
public static LogConsoleService Current => mCurrent.Value;
|
||||
|
||||
LogConsoleService()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#region UI 进程部分
|
||||
|
||||
readonly string?[] logMessages = new string?[2];
|
||||
|
||||
/// <summary>
|
||||
/// 加速进程的日志
|
||||
/// </summary>
|
||||
public string? LogMessageAccelerator => logMessages[0];
|
||||
|
||||
/// <summary>
|
||||
/// 后端进程的日志
|
||||
/// </summary>
|
||||
public string? LogMessageBackEnd => logMessages[1];
|
||||
|
||||
/// <summary>
|
||||
/// 日志源
|
||||
/// </summary>
|
||||
readonly Dictionary<string, Source> sources = new();
|
||||
|
||||
partial class Source
|
||||
{
|
||||
/// <summary>
|
||||
/// 追加收到的日志 UTF-8 字节并触发属性通知
|
||||
/// </summary>
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追加收到的日志 UTF-8 字节
|
||||
/// </summary>
|
||||
/// <param name="moduleName"></param>
|
||||
/// <param name="value"></param>
|
||||
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
|
||||
Reference in New Issue
Block a user