Add a telemetry to WelsonJS Launcher

Add a telemetry to WelsonJS Launcher, To improve debugging experience.
This commit is contained in:
Namhyeon Go 2025-12-03 14:48:30 +09:00
parent bf4b2214bc
commit c4e6acc8cd
10 changed files with 282 additions and 4 deletions

View File

@ -11,6 +11,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using WelsonJS.Launcher.Telemetry;
namespace WelsonJS.Launcher namespace WelsonJS.Launcher
{ {
@ -21,6 +22,7 @@ namespace WelsonJS.Launcher
public static Mutex _mutex; public static Mutex _mutex;
public static ResourceServer _resourceServer; public static ResourceServer _resourceServer;
public static string _dateTimeFormat; public static string _dateTimeFormat;
public static TelemetryClient _telemetryClient;
static Program() static Program()
{ {
@ -32,10 +34,10 @@ namespace WelsonJS.Launcher
// load native libraries // load native libraries
string appDataSubDirectory = "WelsonJS"; string appDataSubDirectory = "WelsonJS";
bool requireSigned = string.Equals( bool requireSigned = string.Equals(
GetAppConfig("NativeRequireSigned"), GetAppConfig("NativeRequireSigned"),
"true", "true",
StringComparison.OrdinalIgnoreCase); StringComparison.OrdinalIgnoreCase);
NativeBootstrap.Init( NativeBootstrap.Init(
dllNames: new[] { "ChakraCore.dll" }, dllNames: new[] { "ChakraCore.dll" },
@ -43,6 +45,17 @@ namespace WelsonJS.Launcher
logger: _logger, logger: _logger,
requireSigned: requireSigned requireSigned: requireSigned
); );
// telemetry
var telemetryProvider = GetAppConfig("TelemetryProvider");
var telemetryOptions = new TelemetryOptions
{
ApiKey = GetAppConfig("TelemetryApiKey"),
BaseUrl = GetAppConfig("TelemetryBaseUrl"),
DistinctId = Environment.MachineName,
Disabled = string.Equals(GetAppConfig("TelemetryDisabled"), "true", StringComparison.OrdinalIgnoreCase)
};
_telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger);
} }
[STAThread] [STAThread]
@ -70,6 +83,9 @@ namespace WelsonJS.Launcher
return; return;
} }
// send event to the telemetry server
_telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", "0.2.7.57");
// draw the main form // draw the main form
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);

View File

@ -378,6 +378,42 @@ namespace WelsonJS.Launcher.Properties {
} }
} }
/// <summary>
/// phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryApiKey {
get {
return ResourceManager.GetString("TelemetryApiKey", resourceCulture);
}
}
/// <summary>
/// https://us.i.posthog.com과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryBaseUrl {
get {
return ResourceManager.GetString("TelemetryBaseUrl", resourceCulture);
}
}
/// <summary>
/// false과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryDisabled {
get {
return ResourceManager.GetString("TelemetryDisabled", resourceCulture);
}
}
/// <summary>
/// posthog과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryProvider {
get {
return ResourceManager.GetString("TelemetryProvider", resourceCulture);
}
}
/// <summary> /// <summary>
/// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다. /// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>

View File

@ -229,4 +229,16 @@
<data name="icon_editor_32" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="icon_editor_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\icon_editor_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\icon_editor_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="TelemetryApiKey" xml:space="preserve">
<value>phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8</value>
</data>
<data name="TelemetryBaseUrl" xml:space="preserve">
<value>https://us.i.posthog.com</value>
</data>
<data name="TelemetryProvider" xml:space="preserve">
<value>posthog</value>
</data>
<data name="TelemetryDisabled" xml:space="preserve">
<value>false</value>
</data>
</root> </root>

View File

@ -0,0 +1,20 @@
// ITelemetryProvider.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.Telemetry
{
public interface ITelemetryProvider
{
Task TrackEventAsync(
string eventName,
IDictionary<string, object> properties = null,
CancellationToken cancellationToken = default
);
}
}

View File

@ -0,0 +1,85 @@
// PosthogTelemetryProvider.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.Telemetry
{
public sealed class PosthogTelemetryProvider : ITelemetryProvider, IDisposable
{
private readonly TelemetryOptions _options;
private readonly HttpClient _httpClient;
private bool _disposed;
public PosthogTelemetryProvider(TelemetryOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
if (string.IsNullOrWhiteSpace(_options.ApiKey))
throw new ArgumentException("PostHog API key is missing.");
if (string.IsNullOrWhiteSpace(_options.BaseUrl))
throw new ArgumentException("PostHog BaseUrl is missing.");
if (string.IsNullOrWhiteSpace(_options.DistinctId))
_options.DistinctId = $"anon-{Guid.NewGuid():N}";
_httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
}
public async Task TrackEventAsync(
string eventName,
IDictionary<string, object> properties = null,
CancellationToken cancellationToken = default)
{
if (_disposed) throw new ObjectDisposedException(nameof(PosthogTelemetryProvider));
if (_options.Disabled) return;
if (string.IsNullOrWhiteSpace(eventName))
return;
string json;
using (var ser = new JsSerializer())
{
var payload = new Dictionary<string, object>
{
["api_key"] = _options.ApiKey,
["distinct_id"] = _options.DistinctId,
["event"] = eventName,
["properties"] = properties ?? new Dictionary<string, object>()
};
json = ser.Serialize(payload, 0);
}
var url = $"{_options.BaseUrl.TrimEnd('/')}/i/v0/e";
try
{
await _httpClient.PostAsync(
url,
new StringContent(json, Encoding.UTF8, "application/json"),
cancellationToken
);
}
catch
{
// swallow for fire-and-forget telemetry
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_httpClient.Dispose();
}
}
}

View File

@ -0,0 +1,51 @@
// TelemetryClient.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.Telemetry
{
public sealed class TelemetryClient : IDisposable
{
private readonly ITelemetryProvider _provider;
private readonly ICompatibleLogger _logger;
public TelemetryClient(string providerName, TelemetryOptions options, ICompatibleLogger logger = null)
{
_provider = TelemetryProviderFactory.Create(providerName, options);
_logger = logger;
}
public Task TrackEventAsync(
string eventName,
IDictionary<string, object> properties = null,
CancellationToken cancellationToken = default)
{
return _provider.TrackEventAsync(eventName, properties, cancellationToken);
}
public Task TrackAppStartedAsync(string appName, string appVersion)
{
var props = new Dictionary<string, object>
{
{ "app_name", appName },
{ "app_version", appVersion },
{ "os_platform", Environment.OSVersion.Platform.ToString() },
{ "os_version", Environment.OSVersion.Version.ToString() },
{ "timestamp_utc", DateTime.UtcNow.ToString("o") }
};
return TrackEventAsync("app_started", props);
}
public void Dispose()
{
(_provider as IDisposable)?.Dispose();
}
}
}

View File

@ -0,0 +1,15 @@
// TelemetryOptions.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
namespace WelsonJS.Launcher.Telemetry
{
public class TelemetryOptions
{
public string ApiKey { get; set; }
public string BaseUrl { get; set; }
public string DistinctId { get; set; }
public bool Disabled { get; set; } = false;
}
}

View File

@ -0,0 +1,34 @@
// TelemetryProviderFactory.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System;
namespace WelsonJS.Launcher.Telemetry
{
public static class TelemetryProviderFactory
{
public static ITelemetryProvider Create(string provider, TelemetryOptions options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
if (provider == null)
throw new ArgumentNullException(nameof(provider));
provider = provider.ToLowerInvariant();
switch (provider)
{
case "posthog":
return new PosthogTelemetryProvider(options);
default:
throw new NotSupportedException(
"Unknown telemetry provider: " + provider
);
}
}
}
}

View File

@ -128,6 +128,11 @@
<DependentUpon>GlobalSettingsForm.cs</DependentUpon> <DependentUpon>GlobalSettingsForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="ResourceServer.cs" /> <Compile Include="ResourceServer.cs" />
<Compile Include="Telemetry\ITelemetryProvider.cs" />
<Compile Include="Telemetry\PosthogTelemetryProvider.cs" />
<Compile Include="Telemetry\TelemetryClient.cs" />
<Compile Include="Telemetry\TelemetryOptions.cs" />
<Compile Include="Telemetry\TelemetryProviderFactory.cs" />
<Compile Include="TraceLogger.cs" /> <Compile Include="TraceLogger.cs" />
<Compile Include="WebSocketManager.cs" /> <Compile Include="WebSocketManager.cs" />
<EmbeddedResource Include="EnvForm.resx"> <EmbeddedResource Include="EnvForm.resx">

View File

@ -26,6 +26,10 @@
<add key="IpQueryApiPrefix2" value="https://api.abuseipdb.com/api/v2/"/> <add key="IpQueryApiPrefix2" value="https://api.abuseipdb.com/api/v2/"/>
<add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/> <add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/>
<add key="NativeRequireSigned" value="false"/> <add key="NativeRequireSigned" value="false"/>
<add key="TelemetryProvider" value="posthog"/>
<add key="TelemetryApiKey" value="phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8"/>
<add key="TelemetryBaseUrl" value="https://us.i.posthog.com"/>
<add key="TelemetryDisabled" value="false"/>
</appSettings> </appSettings>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>