-
-
Notifications
You must be signed in to change notification settings - Fork 4
Add Add-SentryEventProcessor cmdlet #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| using System; | ||
| using System.Management.Automation; | ||
| using Sentry; | ||
| using Sentry.Extensibility; | ||
|
|
||
| // Wraps a PowerShell ScriptBlock as an ISentryEventProcessor so the public | ||
| // Add-SentryEventProcessor cmdlet can register user-supplied script blocks with | ||
| // the Sentry pipeline. Implementing the interface in C# (rather than asking users | ||
| // to author a PowerShell class deriving from an internal base) keeps the public | ||
| // API a single scriptblock and avoids the `Process` keyword conflict in Windows | ||
| // PowerShell that would otherwise require a Process_ / DoProcess workaround. | ||
| public sealed class ScriptBlockEventProcessor : ISentryEventProcessor | ||
| { | ||
| private readonly ScriptBlock _scriptBlock; | ||
| private readonly IDiagnosticLogger _logger; | ||
|
|
||
| public ScriptBlockEventProcessor(ScriptBlock scriptBlock, IDiagnosticLogger logger) | ||
| { | ||
| if (scriptBlock == null) throw new ArgumentNullException("scriptBlock"); | ||
| _scriptBlock = scriptBlock; | ||
| _logger = logger; | ||
| } | ||
|
|
||
| public SentryEvent Process(SentryEvent @event) | ||
| { | ||
| try | ||
| { | ||
| var results = _scriptBlock.Invoke(@event); | ||
| if (results == null || results.Count == 0) | ||
| { | ||
| return @event; | ||
| } | ||
|
|
||
| var last = results[results.Count - 1]; | ||
| if (last == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return (last.BaseObject as SentryEvent) ?? @event; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth warning if the user returns anything but SentryEvent. This line defaults to |
||
| } | ||
| catch (Exception ex) | ||
| { | ||
| if (_logger != null) | ||
| { | ||
| _logger.Log( | ||
| SentryLevel.Warning, | ||
| "Event processor scriptblock failed for event {0}: {1}", | ||
| ex, | ||
| new object[] { @event.EventId, ex.Message }); | ||
| } | ||
| return @event; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| . "$privateDir/Get-CurrentOptions.ps1" | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Registers a global event processor that runs on every event before it is sent to Sentry. | ||
| .DESCRIPTION | ||
| The script block receives the Sentry event via the automatic variable $_ (matching the | ||
| convention used by Edit-SentryScope). Return the event to send it, or $null to drop it. | ||
| .EXAMPLE | ||
| PS> Add-SentryEventProcessor { $_.SetTag('host', $env:COMPUTERNAME); $_ } | ||
| .EXAMPLE | ||
| PS> Add-SentryEventProcessor { | ||
| if ($_.Message -match 'secret') { return $null } | ||
| $_ | ||
| } | ||
| #> | ||
| function Add-SentryEventProcessor { | ||
| param( | ||
| [Parameter(Mandatory, Position = 0)] | ||
| [scriptblock] $ScriptBlock | ||
| ) | ||
|
|
||
| $options = Get-CurrentOptions | ||
| if ($null -eq $options) { | ||
| throw 'Sentry is not initialized. Call Start-Sentry before adding an event processor.' | ||
| } | ||
|
|
||
| # Wrap the user's script block in a pipeline so that $_ is bound to the event, | ||
| # matching Edit-SentryScope's convention. | ||
| $wrapped = { | ||
| param([Sentry.SentryEvent] $event_) | ||
| $event_ | ForEach-Object $ScriptBlock | ||
| }.GetNewClosure() | ||
|
|
||
| $options.AddEventProcessor( | ||
| [ScriptBlockEventProcessor]::new($wrapped, $options.DiagnosticLogger)) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| BeforeAll { | ||
| . "$PSScriptRoot/utils.ps1" | ||
| } | ||
|
|
||
| Describe 'Add-SentryEventProcessor' { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Pester suite covers the happy paths well, but I'd add at least:
|
||
| BeforeEach { | ||
| $events = [System.Collections.Generic.List[Sentry.SentryEvent]]::new(); | ||
| $transport = [RecordingTransport]::new() | ||
| StartSentryForEventTests ([ref] $events) ([ref] $transport) | ||
| } | ||
|
|
||
| AfterEach { | ||
| $events.Clear() | ||
| Stop-Sentry | ||
| } | ||
|
|
||
| It 'Mutates events via $_' { | ||
| Add-SentryEventProcessor { $_.SetTag('custom', 'value'); $_ } | ||
| 'msg' | Out-Sentry | ||
|
|
||
| $events[0].Tags['custom'] | Should -Be 'value' | ||
| } | ||
|
|
||
| It 'Drops events when the script block returns $null' { | ||
| Add-SentryEventProcessor { | ||
| if ($_.Message.Message -match 'drop-me') { return $null } | ||
| $_ | ||
| } | ||
| 'drop-me please' | Out-Sentry | ||
| 'keep this one' | Out-Sentry | ||
|
|
||
| $events.Count | Should -Be 1 | ||
| $events[0].Message.Message | Should -Be 'keep this one' | ||
| } | ||
|
|
||
| It 'Chains multiple processors in registration order' { | ||
| Add-SentryEventProcessor { $_.SetTag('first', '1'); $_ } | ||
| Add-SentryEventProcessor { $_.SetTag('second', '2'); $_ } | ||
| 'msg' | Out-Sentry | ||
|
|
||
| $events[0].Tags['first'] | Should -Be '1' | ||
| $events[0].Tags['second'] | Should -Be '2' | ||
| } | ||
|
|
||
| It 'Throws when Sentry is not initialized' { | ||
| Stop-Sentry | ||
| { Add-SentryEventProcessor { $_ } } | Should -Throw '*Sentry is not initialized*' | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is contrary to the usual way event processors work right? In case user returns null, the event should be dropped AFAIR