CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
228 lines
8.1 KiB
C#
228 lines
8.1 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Win32;
|
|
using PCL.Core.App.IoC;
|
|
using PCL.Core.IO.Net;
|
|
using PCL.Core.IO.Net.Dns;
|
|
using PCL.Core.Logging;
|
|
using PCL.Core.Utils.OS;
|
|
using STUN.Client;
|
|
using Sentry;
|
|
using Sentry.Extensibility;
|
|
|
|
namespace PCL.Core.App.Essentials;
|
|
|
|
[LifecycleScope("telemetry", "遥测")]
|
|
[LifecycleService(LifecycleState.Running)]
|
|
public sealed partial class TelemetryService
|
|
{
|
|
private static void _InitSentry()
|
|
{
|
|
Context.Info("开始初始化 Sentry SDK");
|
|
var dsn = EnvironmentInterop.GetSecret("SENTRY_DSN");
|
|
if (dsn is null)
|
|
{
|
|
Context.Warn("未找到 Sentry DSN");
|
|
return;
|
|
}
|
|
|
|
var release = $"{Basics.VersionName}";
|
|
|
|
#if DEBUG
|
|
var environment = "Debug";
|
|
#else
|
|
var environment = "Production";
|
|
#endif
|
|
|
|
SentrySdk.Init(options =>
|
|
{
|
|
options.Dsn = dsn;
|
|
#if DEBUG
|
|
options.Debug = true;
|
|
#else
|
|
options.Debug = false;
|
|
#endif
|
|
options.SendDefaultPii = false;
|
|
options.IsGlobalModeEnabled = true;
|
|
options.AutoSessionTracking = true;
|
|
options.Release = release;
|
|
options.Environment = environment;
|
|
|
|
// 应该被直接丢弃不上报的异常类型
|
|
options.AddExceptionFilterForType<TimeoutException>();
|
|
options.AddExceptionFilterForType<HttpRequestException>();
|
|
options.AddExceptionFilterForType<WebException>();
|
|
options.AddExceptionFilterForType<TaskCanceledException>();
|
|
options.AddExceptionFilterForType<DirectoryNotFoundException>();
|
|
options.AddExceptionFilterForType<UnauthorizedAccessException>();
|
|
options.AddExceptionFilterForType<FileNotFoundException>();
|
|
|
|
// 细分类型的过滤器
|
|
options.AddExceptionFilter(new SocketExceptionFilter());
|
|
|
|
options.SetBeforeSend(@event => @event.Level is SentryLevel.Debug ? null : @event);
|
|
});
|
|
|
|
SentrySdk.ConfigureScope(scope =>
|
|
{
|
|
scope.User = new SentryUser
|
|
{
|
|
Id = Utils.Secret.Identify.LauncherId
|
|
};
|
|
});
|
|
|
|
Context.Info("Sentry SDK 初始化完成");
|
|
}
|
|
|
|
// 错误上报
|
|
public static void ReportException(Exception ex, string plain, LogLevel level)
|
|
{
|
|
var sentryEvent = new SentryEvent(ex)
|
|
{
|
|
Level = level.RealLevel() switch
|
|
{
|
|
LogLevel.Fatal => SentryLevel.Fatal,
|
|
LogLevel.Error => SentryLevel.Error,
|
|
LogLevel.Warning => SentryLevel.Warning,
|
|
LogLevel.Info => SentryLevel.Info,
|
|
LogLevel.Debug or LogLevel.Trace => SentryLevel.Debug,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(level))
|
|
}
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(plain))
|
|
{
|
|
sentryEvent.Message = new SentryMessage { Formatted = plain };
|
|
}
|
|
|
|
SentrySdk.CaptureEvent(sentryEvent);
|
|
}
|
|
|
|
// 设备环境上报
|
|
private static void _ReportDeviceEnvironment(TelemetryDeviceEnvironment content)
|
|
{
|
|
Context.Info("正在上报设备环境调查数据");
|
|
|
|
SentrySdk.ConfigureScope(scope =>
|
|
{
|
|
scope.Contexts["Telemetry"] = content;
|
|
});
|
|
|
|
try
|
|
{
|
|
SentrySdk.CaptureMessage("设备环境调查");
|
|
Context.Info("已发送设备环境调查数据");
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Context.Error("设备环境调查数据发送失败,请检查网络连接以及使用的版本", ex);
|
|
}
|
|
}
|
|
|
|
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
|
|
|
private class TelemetryDeviceEnvironment
|
|
{
|
|
public required string Tag { get; set; }
|
|
public required string Id { get; set; }
|
|
[JsonPropertyName("OS")] public required int Os { get; set; }
|
|
public required bool Is64Bit { get; set; }
|
|
[JsonPropertyName("IsARM64")] public required bool IsArm64 { get; set; }
|
|
public required string Launcher { get; set; }
|
|
public required string LauncherBranch {get; set; }
|
|
[JsonPropertyName("UsedOfficialPCL")] public required bool UsedOfficialPcl { get; set; }
|
|
[JsonPropertyName("UsedHMCL")] public required bool UsedHmcl { get; set; }
|
|
[JsonPropertyName("UsedBakaXL")] public required bool UsedBakaXl { get; set; }
|
|
public required ulong Memory { get; set; }
|
|
public required string? NatMapBehaviour { get; set; }
|
|
public required string? NatFilterBehaviour { get; set; }
|
|
[JsonPropertyName("IPv6Status")] public required string Ipv6Status { get; set; }
|
|
}
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
private const string STUN_SERVER_ADDR = "stun.miwifi.com";
|
|
|
|
// ReSharper restore UnusedAutoPropertyAccessor.Local
|
|
[LifecycleStart]
|
|
private static async Task _StartAsync()
|
|
{
|
|
if (!Config.System.Telemetry) return;
|
|
_InitSentry();
|
|
|
|
var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
|
|
|
// stun test
|
|
StunClient5389UDP? natTest = null;
|
|
var miWifiIps = await DnsQuery.Instance.QueryForIpAsync(STUN_SERVER_ADDR).ConfigureAwait(false);
|
|
try
|
|
{
|
|
miWifiIps ??= await Dns.GetHostAddressesAsync(STUN_SERVER_ADDR).ConfigureAwait(false);
|
|
} catch(Exception) { /* Ignore dns error */ }
|
|
|
|
if (miWifiIps is not null && miWifiIps.Length != 0)
|
|
{
|
|
natTest = new StunClient5389UDP(new IPEndPoint(miWifiIps.First(), 3478),
|
|
new IPEndPoint(IPAddress.Any, 0));
|
|
await natTest.QueryAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
var telemetry = new TelemetryDeviceEnvironment
|
|
{
|
|
Tag = "Telemetry",
|
|
Id = Utils.Secret.Identify.LauncherId,
|
|
Os = Environment.OSVersion.Version.Build,
|
|
Is64Bit = Environment.Is64BitOperatingSystem,
|
|
IsArm64 = RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64),
|
|
Launcher = Basics.VersionName,
|
|
LauncherBranch = Config.Update.UpdateChannel switch
|
|
{
|
|
UpdateChannel.Release => "Release",
|
|
UpdateChannel.Beta => "Beta",
|
|
UpdateChannel.Dev => "Dev",
|
|
_ => "Unknown"
|
|
},
|
|
UsedOfficialPcl =
|
|
bool.TryParse(Registry.GetValue(@"HKEY_CURRENT_USER\Software\PCL", "SystemEula", "false") as string,
|
|
out var officialPcl) && officialPcl,
|
|
UsedHmcl = Directory.Exists(Path.Combine(appDataFolder, ".hmcl")),
|
|
UsedBakaXl = Directory.Exists(Path.Combine(appDataFolder, "BakaXL")),
|
|
Memory = KernelInterop.GetPhysicalMemoryBytes().Total,
|
|
NatMapBehaviour = natTest?.State.MappingBehavior.ToString(),
|
|
NatFilterBehaviour = natTest?.State.FilteringBehavior.ToString(),
|
|
Ipv6Status = NetworkInterfaceUtils.GetIPv6Status().ToString()
|
|
};
|
|
|
|
_ReportDeviceEnvironment(telemetry);
|
|
}
|
|
|
|
// 用来细分过滤 SocketException 的过滤器,我觉得应该除了遥测服务之外没有其他东西会用到这破玩意儿
|
|
private sealed class SocketExceptionFilter : IExceptionFilter
|
|
{
|
|
public bool Filter(Exception ex)
|
|
{
|
|
if (ex is SocketException socketEx)
|
|
{
|
|
return socketEx.SocketErrorCode is
|
|
SocketError.ConnectionRefused or
|
|
SocketError.TimedOut or
|
|
SocketError.HostNotFound or
|
|
SocketError.NetworkUnreachable or
|
|
SocketError.ConnectionReset;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[LifecycleStop]
|
|
private static void _StopAsync()
|
|
{
|
|
SentrySdk.Close();
|
|
}
|
|
} |