Skip to content

Commit 8a2c407

Browse files
wgutmannclaude
andcommitted
feat: expand Sentry error capture across plugin and dashboards
Switch deploy cron check-ins to DSN-based relay endpoint, capture previously swallowed exceptions in dashboard WebSocket handlers, and add SentrySdk.CaptureException calls to all plugin catch blocks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 02bed5e commit 8a2c407

5 files changed

Lines changed: 47 additions & 15 deletions

File tree

deploy.ps1

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,21 @@ $sentryAuthToken = if (-not [string]::IsNullOrWhiteSpace($env:SENTRY_AUTH_TOKEN)
420420
else { $null }
421421
$sentryRelease = if (-not [string]::IsNullOrWhiteSpace($script:SimStewardPluginVersionDeployed)) { $script:SimStewardPluginVersionDeployed } else { $null }
422422

423+
function Parse-SentryDsn {
424+
param([string]$Dsn)
425+
# https://<public_key>@<ingest_domain>/<project_id>
426+
if ($Dsn -match '^https://([^@]+)@([^/]+)/(\d+)$') {
427+
return @{
428+
PublicKey = $Matches[1]
429+
IngestDomain = $Matches[2]
430+
ProjectId = $Matches[3]
431+
}
432+
}
433+
return $null
434+
}
435+
436+
$script:sentryDsn = Parse-SentryDsn $env:SIMSTEWARD_SENTRY_DSN
437+
423438
function Push-SentryApi {
424439
param([string]$Path, [hashtable]$Body)
425440
if ([string]::IsNullOrWhiteSpace($sentryAuthToken) -or [string]::IsNullOrWhiteSpace($sentryRelease)) { return }
@@ -436,12 +451,12 @@ function Push-SentryApi {
436451

437452
function Push-SentryCheckIn {
438453
param([string]$MonitorSlug, [string]$Status, [string]$CheckInId)
439-
if ([string]::IsNullOrWhiteSpace($sentryAuthToken)) { return $null }
454+
if (-not $script:sentryDsn) { return $null }
440455
try {
441-
$headers = @{ Authorization = "Bearer $sentryAuthToken"; 'Content-Type' = 'application/json' }
456+
$baseUrl = "https://$($script:sentryDsn.IngestDomain)/api/$($script:sentryDsn.ProjectId)/cron/$MonitorSlug/$($script:sentryDsn.PublicKey)/"
457+
$headers = @{ 'Content-Type' = 'application/json' }
442458
if ([string]::IsNullOrWhiteSpace($CheckInId)) {
443-
# Initial check-in: POST with monitor_config for auto-creation
444-
$url = "https://sentry.io/api/0/organizations/$sentryOrg/monitors/$MonitorSlug/checkins/"
459+
# Initial check-in: POST with monitor_config for upsert/auto-creation
445460
$body = @{
446461
status = $Status
447462
monitor_config = @{
@@ -452,7 +467,7 @@ function Push-SentryCheckIn {
452467
}
453468
}
454469
$json = $body | ConvertTo-Json -Compress -Depth 5
455-
$resp = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $json -ErrorAction Stop
470+
$resp = Invoke-RestMethod -Uri $baseUrl -Method Post -Headers $headers -Body $json -ErrorAction Stop
456471
$newId = $resp.id
457472
Push-LokiEvent 'deploy_sentry_checkin' 'INFO' "Sentry cron check-in started: $MonitorSlug" @{
458473
monitor_slug = $MonitorSlug
@@ -461,11 +476,11 @@ function Push-SentryCheckIn {
461476
}
462477
return $newId
463478
} else {
464-
# Completion check-in: PUT to update existing
465-
$url = "https://sentry.io/api/0/organizations/$sentryOrg/monitors/$MonitorSlug/checkins/$CheckInId/"
479+
# Completion check-in: POST with check_in_id to update existing
480+
$url = "${baseUrl}?check_in_id=$CheckInId"
466481
$body = @{ status = $Status }
467482
$json = $body | ConvertTo-Json -Compress -Depth 5
468-
Invoke-RestMethod -Uri $url -Method Put -Headers $headers -Body $json -ErrorAction Stop | Out-Null
483+
Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $json -ErrorAction Stop | Out-Null
469484
Push-LokiEvent 'deploy_sentry_checkin' 'INFO' "Sentry cron check-in completed: $MonitorSlug ($Status)" @{
470485
monitor_slug = $MonitorSlug
471486
status = $Status
@@ -489,17 +504,20 @@ if (-not [string]::IsNullOrWhiteSpace($sentryAuthToken) -and -not [string]::IsNu
489504
Push-SentryApi "releases/" @{
490505
version = $sentryRelease
491506
projects = $sentryProjects
492-
refs = @(@{ repository = "simsteward/simhub-plugin"; commit = $fullSha })
507+
ref = $fullSha
493508
}
494509

510+
# URL-encode release version for path segments ('+' in SemVer breaks URLs)
511+
$encodedRelease = [System.Uri]::EscapeDataString($sentryRelease)
512+
495513
# Deploy: simhub-plugin (C# DLLs)
496-
Push-SentryApi "releases/$sentryRelease/deploys/" @{
514+
Push-SentryApi "releases/$encodedRelease/deploys/" @{
497515
environment = 'local'
498516
name = 'simhub-plugin'
499517
}
500518

501519
# Deploy: web-dashboards (all HTML/JS dashboards)
502-
Push-SentryApi "releases/$sentryRelease/deploys/" @{
520+
Push-SentryApi "releases/$encodedRelease/deploys/" @{
503521
environment = 'local'
504522
name = 'web-dashboards'
505523
}

src/SimSteward.Dashboard/index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,8 @@
799799
try { onMsg(JSON.parse(e.data)); }
800800
catch (err) { if (typeof Sentry !== 'undefined') Sentry.captureException(err); }
801801
};
802-
} catch {
802+
} catch (err) {
803+
if (typeof Sentry !== 'undefined') Sentry.captureException(err);
803804
setWs('disconnected');
804805
setTimeout(connectWs, 3000);
805806
}
@@ -827,7 +828,8 @@
827828
try {
828829
ws.send(JSON.stringify(o));
829830
return true;
830-
} catch {
831+
} catch (err) {
832+
if (typeof Sentry !== 'undefined') Sentry.captureException(err);
831833
return false;
832834
}
833835
}

src/SimSteward.Dashboard/replay-incident-index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,9 @@
155155
setTimeout(connectWs, 3000);
156156
};
157157
ws.onerror = () => {};
158-
ws.onmessage = (e) => { try { onMsg(JSON.parse(e.data)); } catch (_) {} };
159-
} catch (_) {
158+
ws.onmessage = (e) => { try { onMsg(JSON.parse(e.data)); } catch (err) { if (typeof Sentry !== 'undefined') Sentry.captureException(err); } };
159+
} catch (err) {
160+
if (typeof Sentry !== 'undefined') Sentry.captureException(err);
160161
setTimeout(connectWs, 3000);
161162
}
162163
}

src/SimSteward.Plugin/SimStewardPlugin.DataCaptureSuite.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ private void TickT0_Rewind()
770770
}
771771
catch (Exception ex)
772772
{
773+
SentrySdk.CaptureException(ex);
773774
_logger?.Warn("DataCaptureSuite T0 rewind: " + ex.Message);
774775
}
775776

src/SimSteward.Plugin/SimStewardPlugin.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ private void OnLogWriteError(string eventType, Exception ex)
218218

219219
private void WriteBroadcastError(string context, Exception ex)
220220
{
221+
if (ex != null) SentrySdk.CaptureException(ex);
221222
if (string.IsNullOrEmpty(_pluginDataPath)) return;
222223
var path = Path.Combine(_pluginDataPath, "broadcast-errors.log");
223224
var line = DateTime.UtcNow.ToString("o") + " " + context + (ex != null ? " " + ex.Message : "") + Environment.NewLine;
@@ -659,6 +660,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
659660
}
660661
catch (Exception ex)
661662
{
663+
SentrySdk.CaptureException(ex);
662664
var err = ex.Message ?? "replay_session_failed";
663665
LogActionResult(action, arg, correlationId, false, err);
664666
return (false, null, err);
@@ -712,6 +714,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
712714
}
713715
catch (Exception ex)
714716
{
717+
SentrySdk.CaptureException(ex);
715718
var err = ex.Message ?? "replay_speed_failed";
716719
LogActionResult(action, arg, correlationId, false, err);
717720
return (false, null, err);
@@ -746,6 +749,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
746749
}
747750
catch (Exception ex)
748751
{
752+
SentrySdk.CaptureException(ex);
749753
var err = ex.Message ?? "replay_seek_failed";
750754
LogActionResult(action, arg, correlationId, false, err);
751755
return (false, null, err);
@@ -780,6 +784,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
780784
}
781785
catch (Exception ex)
782786
{
787+
SentrySdk.CaptureException(ex);
783788
var err = ex.Message ?? "replay_jump_failed";
784789
LogActionResult(action, arg, correlationId, false, err);
785790
return (false, null, err);
@@ -810,6 +815,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
810815
}
811816
catch (Exception ex)
812817
{
818+
SentrySdk.CaptureException(ex);
813819
var err = ex.Message ?? "seek_to_incident_failed";
814820
LogActionResult(action, arg, correlationId, false, err);
815821
return (false, null, err);
@@ -843,6 +849,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
843849
}
844850
catch (Exception ex)
845851
{
852+
SentrySdk.CaptureException(ex);
846853
LogActionResult(action, arg, correlationId, false, ex.Message);
847854
return (false, null, ex.Message);
848855
}
@@ -889,6 +896,7 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
889896
catch (Exception ex)
890897
{
891898
sw.Stop();
899+
SentrySdk.CaptureException(ex);
892900
LogActionResult(action, arg, correlationId, false, ex.Message, BuildCaptureIncidentSupplement(parsed, sw.ElapsedMilliseconds));
893901
return (false, null, ex.Message);
894902
}
@@ -1070,6 +1078,7 @@ private void RefreshDependencyChecks()
10701078
}
10711079
catch (Exception ex)
10721080
{
1081+
SentrySdk.CaptureException(ex);
10731082
_dashboardPingStatus = "Error: " + ex.Message;
10741083
// #region agent log
10751084
WriteAgentHttpDebug("H1,H3,H5", "dashboard_ping_error", new Dictionary<string, object>
@@ -1491,6 +1500,7 @@ public void End(PluginManager pluginManager)
14911500
}
14921501
catch (Exception ex)
14931502
{
1503+
SentrySdk.CaptureException(ex);
14941504
_logger?.Warn($"iRacing SDK Stop failed: {ex.Message}");
14951505
}
14961506
_irsdk = null;

0 commit comments

Comments
 (0)