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">
|
<Compile Include="..\BD.WTTS.Client\Services\Platform\IPlatformService.OS.Linux.Etc.Os_Release.cs">
|
||||||
<LinkBase>Services\Platform</LinkBase>
|
<LinkBase>Services\Platform</LinkBase>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\BD.WTTS.Client\Services\Mvvm\LogConsoleService.Logger.cs">
|
||||||
|
<LinkBase>Services\Mvvm</LinkBase>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -104,6 +107,7 @@
|
|||||||
<PackageReference Include="System.Drawing.Common" />
|
<PackageReference Include="System.Drawing.Common" />
|
||||||
<PackageReference Include="System.Runtime.Caching" />
|
<PackageReference Include="System.Runtime.Caching" />
|
||||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||||
|
<PackageReference Include="Utf8StringInterpolation" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
|
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('windows')) AND ($(RuntimeIdentifier.StartsWith('win-')) OR $(RuntimeIdentifier) == '')">
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ static void ConfigureServices(IServiceCollection services)
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
l.AddConsole();
|
l.AddConsole();
|
||||||
#endif
|
#endif
|
||||||
|
l.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(moduleName));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置仓储层数据库文件存放路径
|
// 设置仓储层数据库文件存放路径
|
||||||
|
|||||||
+2
@@ -23,6 +23,8 @@ sealed class LazyReverseProxyServiceImpl : IReverseProxyService
|
|||||||
|
|
||||||
public byte[]? GetFlowStatistics_Bytes() => impl().GetFlowStatistics_Bytes();
|
public byte[]? GetFlowStatistics_Bytes() => impl().GetFlowStatistics_Bytes();
|
||||||
|
|
||||||
|
public string? GetLogAllMessage() => impl().GetLogAllMessage();
|
||||||
|
|
||||||
public async Task<StartProxyResult> StartProxyAsync(byte[] reverseProxySettings)
|
public async Task<StartProxyResult> StartProxyAsync(byte[] reverseProxySettings)
|
||||||
{
|
{
|
||||||
var result = await impl().StartProxyAsync(reverseProxySettings);
|
var result = await impl().StartProxyAsync(reverseProxySettings);
|
||||||
|
|||||||
+8
@@ -65,6 +65,8 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
|
|||||||
WebRootPath = RootPath,
|
WebRootPath = RootPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Logging.AddProvider(new LogConsoleService.Utf8StringLoggerProvider(AssemblyInfo.Accelerator));
|
||||||
|
|
||||||
builder.Services.Configure<HostFilteringOptions>(static o =>
|
builder.Services.Configure<HostFilteringOptions>(static o =>
|
||||||
{
|
{
|
||||||
o.AllowEmptyHosts = true;
|
o.AllowEmptyHosts = true;
|
||||||
@@ -167,6 +169,12 @@ sealed partial class YarpReverseProxyServiceImpl : ReverseProxyServiceImpl, IRev
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string? GetLogAllMessage()
|
||||||
|
{
|
||||||
|
var result = LogConsoleService.Builder;
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
// IDisposable
|
// IDisposable
|
||||||
|
|
||||||
protected override void DisposeCore()
|
protected override void DisposeCore()
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ partial interface IReverseProxyService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
byte[]? GetFlowStatistics_Bytes();
|
byte[]? GetFlowStatistics_Bytes();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有日志信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
string? GetLogAllMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static partial class ReverseProxyServiceExtensions
|
public static partial class ReverseProxyServiceExtensions
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Data;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
using LiveChartsCore;
|
using LiveChartsCore;
|
||||||
using LiveChartsCore.Motion;
|
|
||||||
using LiveChartsCore.SkiaSharpView;
|
using LiveChartsCore.SkiaSharpView;
|
||||||
using LiveChartsCore.SkiaSharpView.Avalonia;
|
using LiveChartsCore.SkiaSharpView.Avalonia;
|
||||||
using LiveChartsCore.SkiaSharpView.Drawing;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace BD.WTTS.UI.Views.Controls;
|
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>
|
</Border>
|
||||||
</WrapPanel>-->
|
</WrapPanel>-->
|
||||||
</TabStripItem>
|
</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_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 Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||||
<TabStripItem Name="GameAccTab">
|
<TabStripItem Name="GameAccTab">
|
||||||
@@ -557,6 +558,8 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</spp:ContentLoader>
|
</spp:ContentLoader>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
<!-- 加速日志 -->
|
||||||
|
<spp:ProxyLog Margin="5,0" />
|
||||||
|
|
||||||
<!-- 流量统计 -->
|
<!-- 流量统计 -->
|
||||||
<spp:ProxyChartView Margin="5,0" />
|
<spp:ProxyChartView Margin="5,0" />
|
||||||
|
|||||||
@@ -86,6 +86,22 @@ partial interface IApplication
|
|||||||
//#elif MACOS || MACCATALYST || (IOS && DEBUG)
|
//#elif MACOS || MACCATALYST || (IOS && DEBUG)
|
||||||
// builder.AddProvider(Logging.OSLogLoggerProvider.Instance);
|
// builder.AddProvider(Logging.OSLogLoggerProvider.Instance);
|
||||||
//#endif
|
//#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" />
|
||||||
<PackageReference Include="SkiaSharp.HarfBuzz" />
|
<PackageReference Include="SkiaSharp.HarfBuzz" />
|
||||||
<PackageReference Include="HarfBuzzSharp" />
|
<PackageReference Include="HarfBuzzSharp" />
|
||||||
|
<PackageReference Include="Utf8StringInterpolation" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
|
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
|
||||||
|
|||||||
@@ -7,14 +7,8 @@ namespace BD.WTTS.Logging;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ClientLogger : ILogger
|
public abstract class ClientLogger : ILogger
|
||||||
{
|
{
|
||||||
static readonly string _messagePadding;
|
static readonly string _messagePadding = new string(' ', 6);
|
||||||
static readonly string _newLineWithMessagePadding;
|
static readonly string _newLineWithMessagePadding = Environment.NewLine + _messagePadding;
|
||||||
|
|
||||||
static ClientLogger()
|
|
||||||
{
|
|
||||||
_messagePadding = new string(' ', 6);
|
|
||||||
_newLineWithMessagePadding = Environment.NewLine + _messagePadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly string name;
|
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>
|
||||||
/// 查找类似 加速模式 的本地化字符串。
|
/// 查找类似 加速模式 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2611,6 +2611,9 @@
|
|||||||
<data name="CommunityFix_AlreadyProxy" xml:space="preserve">
|
<data name="CommunityFix_AlreadyProxy" xml:space="preserve">
|
||||||
<value>已加速 </value>
|
<value>已加速 </value>
|
||||||
<comment>Comment=末尾带空格;</comment>
|
<comment>Comment=末尾带空格;</comment>
|
||||||
|
</data>
|
||||||
|
<data name="CommunityFix_ProxyLog" xml:space="preserve">
|
||||||
|
<value>加速日志</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommunityFix_FlowStatistics" xml:space="preserve">
|
<data name="CommunityFix_FlowStatistics" xml:space="preserve">
|
||||||
<value>流量统计</value>
|
<value>流量统计</value>
|
||||||
|
|||||||
@@ -450,6 +450,13 @@ public sealed partial class IPCMainProcessServiceImpl : IPCMainProcessService
|
|||||||
return result.All(static x => x.result);
|
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>
|
||||||
/// 配置服务
|
/// 配置服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -67,4 +67,6 @@ public interface IPCMainProcessService : IAsyncDisposable
|
|||||||
/// <param name="moduleName"></param>
|
/// <param name="moduleName"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<T?> GetServiceAsync<T>(string moduleName) where T : class;
|
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