210 lines
8.1 KiB
C#
210 lines
8.1 KiB
C#
using Sentry;
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Ink_Canvas.Helpers
|
|
{
|
|
internal static class TelemetryUploader
|
|
{
|
|
private static readonly Regex EmailRegex = new Regex(
|
|
@"(?i)\b[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}\b",
|
|
RegexOptions.Compiled);
|
|
|
|
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()
|
|
{
|
|
return Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
var settings = MainWindow.Settings;
|
|
if (settings == null || settings.Startup == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var level = settings.Startup.TelemetryUploadLevel;
|
|
if (level == TelemetryUploadLevel.None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string deviceId = DeviceIdentifier.GetDeviceId();
|
|
if (string.IsNullOrWhiteSpace(deviceId) || deviceId.Length < 5)
|
|
{
|
|
LogHelper.WriteLogToFile("TelemetryUploader | 设备ID无效,取消遥测上传", LogHelper.LogType.Warning);
|
|
return;
|
|
}
|
|
|
|
// Basic 和 Extended 均上传崩溃日志(脱敏)
|
|
object crashFile = TryGetLatestSanitizedFile(
|
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Crashes"),
|
|
"Crash_*.txt",
|
|
"崩溃日志");
|
|
|
|
// Extended 额外上传运行日志(脱敏)
|
|
object runtimeLogFile = null;
|
|
if (level == TelemetryUploadLevel.Extended)
|
|
{
|
|
runtimeLogFile = TryGetLatestSanitizedFile(
|
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"),
|
|
"Log_*.txt",
|
|
"运行日志");
|
|
}
|
|
|
|
var telemetryData = new
|
|
{
|
|
telemetry_level = level.ToString(),
|
|
device_id = deviceId,
|
|
update_channel = settings.Startup.UpdateChannel.ToString(),
|
|
app_version = Assembly.GetExecutingAssembly().GetName().Version.ToString(),
|
|
os_version = Environment.OSVersion.VersionString,
|
|
has_crash_log = crashFile != null,
|
|
has_runtime_log = runtimeLogFile != null
|
|
};
|
|
|
|
// 通过 Sentry 上报一个包含遥测信息的事件
|
|
string userName = Environment.UserName;
|
|
SentrySdk.ConfigureScope(scope =>
|
|
{
|
|
scope.User = new SentryUser
|
|
{
|
|
Id = deviceId,
|
|
Username = userName,
|
|
Email = $"{userName}",
|
|
IpAddress = "{{auto}}"
|
|
};
|
|
});
|
|
|
|
var evt = new SentryEvent
|
|
{
|
|
Message = "ICC CE Telemetry",
|
|
Level = SentryLevel.Info
|
|
};
|
|
|
|
evt.User = new SentryUser
|
|
{
|
|
Id = deviceId,
|
|
Username = userName,
|
|
Email = $"{userName}",
|
|
IpAddress = "{{auto}}"
|
|
};
|
|
|
|
evt.SetTag("telemetry_level", level.ToString());
|
|
evt.SetTag("device_id", deviceId);
|
|
evt.SetTag("update_channel", settings.Startup.UpdateChannel.ToString());
|
|
evt.SetTag("app_version", Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
|
evt.SetTag("os_version", Environment.OSVersion.VersionString);
|
|
evt.SetExtra("telemetry_data", telemetryData);
|
|
|
|
if (crashFile != null)
|
|
{
|
|
evt.SetExtra("crash_file", crashFile);
|
|
}
|
|
|
|
if (runtimeLogFile != null)
|
|
{
|
|
evt.SetExtra("runtime_log_file", runtimeLogFile);
|
|
}
|
|
|
|
SentrySdk.CaptureEvent(evt);
|
|
LogHelper.WriteLogToFile("TelemetryUploader | 遥测数据已通过 Sentry 上报", LogHelper.LogType.Event);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"TelemetryUploader | 遥测上传失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
}
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|