improve:遥测
This commit is contained in:
@@ -1,21 +1,47 @@
|
|||||||
using Sentry;
|
using Sentry;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 遥测上传:根据用户设置,通过 Sentry 上传 usage_stats 和 Crashes 目录的摘要信息。
|
|
||||||
/// </summary>
|
|
||||||
internal static class TelemetryUploader
|
internal static class TelemetryUploader
|
||||||
{
|
{
|
||||||
/// <summary>
|
private static readonly Regex EmailRegex = new Regex(
|
||||||
/// 根据当前设置决定是否上传遥测数据。
|
@"(?i)\b[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}\b",
|
||||||
/// 在主窗口加载完成后调用一次即可。
|
RegexOptions.Compiled);
|
||||||
/// </summary>
|
|
||||||
|
private static readonly Regex PhoneRegex = new Regex(
|
||||||
|
@"\b1[3-9]\d{9}\b",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex IPv4Regex = new Regex(
|
||||||
|
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex WindowsPathRegex = new Regex(
|
||||||
|
@"\b[A-Za-z]:\\[^\s<>|]+\b",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex UncPathRegex = new Regex(
|
||||||
|
@"\\\\[^\s]+",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex KeyValueSecretRegex = new Regex(
|
||||||
|
@"(?i)(\b(?:access[_-]?token|refresh[_-]?token|token|password|passwd|pwd|secret|authorization)\b\s*[:=]\s*)([^\s,;]+)",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex JsonSecretRegex = new Regex(
|
||||||
|
"(?i)(\"(?:access_token|refresh_token|token|password|passwd|pwd|secret|authorization)\"\\s*:\\s*\")([^\"]*)(\")",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex UrlSecretRegex = new Regex(
|
||||||
|
@"(?i)([?&](?:access_token|token|password|pwd|secret)=)[^&\s]+",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
public static Task UploadTelemetryIfNeededAsync()
|
public static Task UploadTelemetryIfNeededAsync()
|
||||||
{
|
{
|
||||||
return Task.Run(() =>
|
return Task.Run(() =>
|
||||||
@@ -31,10 +57,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
var level = settings.Startup.TelemetryUploadLevel;
|
var level = settings.Startup.TelemetryUploadLevel;
|
||||||
if (level == TelemetryUploadLevel.None)
|
if (level == TelemetryUploadLevel.None)
|
||||||
{
|
{
|
||||||
return; // 用户未开启
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取并校验设备ID
|
|
||||||
string deviceId = DeviceIdentifier.GetDeviceId();
|
string deviceId = DeviceIdentifier.GetDeviceId();
|
||||||
if (string.IsNullOrWhiteSpace(deviceId) || deviceId.Length < 5)
|
if (string.IsNullOrWhiteSpace(deviceId) || deviceId.Length < 5)
|
||||||
{
|
{
|
||||||
@@ -42,65 +67,20 @@ namespace Ink_Canvas.Helpers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选:Crashes 目录下的崩溃日志
|
// Basic 和 Extended 均上传崩溃日志(脱敏)
|
||||||
List<object> crashFiles = null;
|
object crashFile = TryGetLatestSanitizedFile(
|
||||||
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Crashes"),
|
||||||
|
"Crash_*.txt",
|
||||||
|
"崩溃日志");
|
||||||
|
|
||||||
|
// Extended 额外上传运行日志(脱敏)
|
||||||
|
object runtimeLogFile = null;
|
||||||
if (level == TelemetryUploadLevel.Extended)
|
if (level == TelemetryUploadLevel.Extended)
|
||||||
{
|
{
|
||||||
try
|
runtimeLogFile = TryGetLatestSanitizedFile(
|
||||||
{
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"),
|
||||||
string crashDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Crashes");
|
"Log_*.txt",
|
||||||
if (Directory.Exists(crashDir))
|
"运行日志");
|
||||||
{
|
|
||||||
crashFiles = new List<object>();
|
|
||||||
var files = Directory.GetFiles(crashDir);
|
|
||||||
|
|
||||||
FileInfo latestInfo = null;
|
|
||||||
string latestContent = null;
|
|
||||||
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var info = new FileInfo(file);
|
|
||||||
|
|
||||||
// 避免一次上传过大,单文件限制为 8192KB
|
|
||||||
if (info.Length > 8192 * 1024)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string content = File.ReadAllText(file);
|
|
||||||
|
|
||||||
if (latestInfo == null || info.LastWriteTime > latestInfo.LastWriteTime)
|
|
||||||
{
|
|
||||||
latestInfo = info;
|
|
||||||
latestContent = content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exFile)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile(
|
|
||||||
$"TelemetryUploader | 读取崩溃日志失败: {exFile.Message}",
|
|
||||||
LogHelper.LogType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (latestInfo != null && latestContent != null)
|
|
||||||
{
|
|
||||||
crashFiles.Add(new
|
|
||||||
{
|
|
||||||
file_name = latestInfo.Name,
|
|
||||||
content = latestContent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile(
|
|
||||||
$"TelemetryUploader | 收集崩溃日志失败: {ex.Message}",
|
|
||||||
LogHelper.LogType.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var telemetryData = new
|
var telemetryData = new
|
||||||
@@ -110,10 +90,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
update_channel = settings.Startup.UpdateChannel.ToString(),
|
update_channel = settings.Startup.UpdateChannel.ToString(),
|
||||||
app_version = Assembly.GetExecutingAssembly().GetName().Version.ToString(),
|
app_version = Assembly.GetExecutingAssembly().GetName().Version.ToString(),
|
||||||
os_version = Environment.OSVersion.VersionString,
|
os_version = Environment.OSVersion.VersionString,
|
||||||
crash_files = crashFiles
|
has_crash_log = crashFile != null,
|
||||||
|
has_runtime_log = runtimeLogFile != null
|
||||||
};
|
};
|
||||||
|
|
||||||
// 通过 Sentry 上报一个包含遥测信息的事件
|
// 按你的要求保留现有用户字段行为
|
||||||
string userName = Environment.UserName;
|
string userName = Environment.UserName;
|
||||||
SentrySdk.ConfigureScope(scope =>
|
SentrySdk.ConfigureScope(scope =>
|
||||||
{
|
{
|
||||||
@@ -147,9 +128,14 @@ namespace Ink_Canvas.Helpers
|
|||||||
evt.SetTag("os_version", Environment.OSVersion.VersionString);
|
evt.SetTag("os_version", Environment.OSVersion.VersionString);
|
||||||
evt.SetExtra("telemetry_data", telemetryData);
|
evt.SetExtra("telemetry_data", telemetryData);
|
||||||
|
|
||||||
if (crashFiles != null)
|
if (crashFile != null)
|
||||||
{
|
{
|
||||||
evt.SetExtra("crash_files", crashFiles);
|
evt.SetExtra("crash_file", crashFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtimeLogFile != null)
|
||||||
|
{
|
||||||
|
evt.SetExtra("runtime_log_file", runtimeLogFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
SentrySdk.CaptureEvent(evt);
|
SentrySdk.CaptureEvent(evt);
|
||||||
@@ -161,7 +147,63 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static object TryGetLatestSanitizedFile(string directory, string pattern, string fileType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var latest = new DirectoryInfo(directory)
|
||||||
|
.GetFiles(pattern)
|
||||||
|
.OrderByDescending(file => file.LastWriteTime)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (latest == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string content = File.ReadAllText(latest.FullName);
|
||||||
|
string sanitizedContent = SanitizeLogContent(content);
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
file_type = fileType,
|
||||||
|
file_name = latest.Name,
|
||||||
|
last_write_time = latest.LastWriteTime.ToString("o"),
|
||||||
|
content = sanitizedContent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile(
|
||||||
|
$"TelemetryUploader | 收集{fileType}失败: {ex.Message}",
|
||||||
|
LogHelper.LogType.Warning);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string SanitizeLogContent(string content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
string sanitized = content;
|
||||||
|
sanitized = EmailRegex.Replace(sanitized, "[REDACTED_EMAIL]");
|
||||||
|
sanitized = PhoneRegex.Replace(sanitized, "[REDACTED_PHONE]");
|
||||||
|
sanitized = IPv4Regex.Replace(sanitized, "[REDACTED_IP]");
|
||||||
|
sanitized = WindowsPathRegex.Replace(sanitized, "[REDACTED_PATH]");
|
||||||
|
sanitized = UncPathRegex.Replace(sanitized, "[REDACTED_PATH]");
|
||||||
|
sanitized = UrlSecretRegex.Replace(sanitized, "$1[REDACTED]");
|
||||||
|
sanitized = KeyValueSecretRegex.Replace(sanitized, "$1[REDACTED]");
|
||||||
|
sanitized = JsonSecretRegex.Replace(sanitized, "$1[REDACTED]$3");
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user