Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions sentry-api-client/Public/Get-SentrySpans.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
function Get-SentrySpans {
<#
.SYNOPSIS
Retrieves spans from Sentry.

.DESCRIPTION
Fetches Sentry spans matching specified criteria.
Supports filtering by query, trace ID, and time range.
Uses the Sentry Discover API with the 'spans' dataset.
Transactions are spans with is_transaction=true.

.PARAMETER Query
Search query string using Sentry search syntax (e.g., 'span.op:http.client', 'is_transaction:true').

.PARAMETER TraceId
Filter spans by specific trace ID.

.PARAMETER StatsPeriod
Relative time period (e.g., '24h', '7d', '14d'). Default is '24h'.

.PARAMETER Limit
Maximum number of spans to return. Default is 100.

.PARAMETER Cursor
Pagination cursor for retrieving subsequent pages of results.

.PARAMETER Fields
Specific fields to return. Default includes: id, trace, span.op, span.description, span.duration, is_transaction, timestamp, transaction.event_id.

.EXAMPLE
Get-SentrySpans -TraceId 'abc123def456789012345678901234ab'

.EXAMPLE
Get-SentrySpans -TraceId 'abc123def456' -Query 'is_transaction:true'

.EXAMPLE
Get-SentrySpans -Query 'span.op:http.client' -StatsPeriod '7d'
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$Query,

[Parameter(Mandatory = $false)]
[string]$TraceId,

[Parameter(Mandatory = $false)]
[string]$StatsPeriod = '24h',

[Parameter(Mandatory = $false)]
[int]$Limit = 100,

[Parameter(Mandatory = $false)]
[string]$Cursor,

[Parameter(Mandatory = $false)]
[string[]]$Fields
)

# Build the query string combining Query and TraceId if provided
$QueryParts = @()
if ($Query) {
$QueryParts += $Query
}
if ($TraceId) {
$QueryParts += "trace:$TraceId"
}
$FinalQuery = $QueryParts -join ' '

# Default fields for spans if not specified
if (-not $Fields -or $Fields.Count -eq 0) {
$Fields = @(
'id',
'trace',
'span.op',
'span.description',
'span.duration',
'is_transaction',
'timestamp',
'transaction.event_id'
)
}

$QueryParams = @{
dataset = 'spans'
statsPeriod = $StatsPeriod
per_page = $Limit
field = $Fields
}

if ($FinalQuery) {
$QueryParams.query = $FinalQuery
}

if ($Cursor) {
$QueryParams.cursor = $Cursor
}

$QueryString = Build-QueryString -Parameters $QueryParams
$Uri = Get-SentryOrganizationUrl -Resource "events/" -QueryString $QueryString

try {
$Response = Invoke-SentryApiRequest -Uri $Uri -Method 'GET'
return $Response
}
catch {
Write-Error "Failed to retrieve spans - $_"
throw
}
}
60 changes: 60 additions & 0 deletions sentry-api-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ Get-SentryLogs -Query 'severity:error' -StatsPeriod '24h'
# Get logs by attribute
Get-SentryLogsByAttribute -AttributeName 'test_id' -AttributeValue 'abc123'

# Get metrics
Get-SentryMetrics -Query 'metric.name:my.counter'

# Get metrics by attribute
Get-SentryMetricsByAttribute -MetricName 'my.counter' -AttributeName 'test_id' -AttributeValue 'abc123'

# Get spans by trace ID
Get-SentrySpans -TraceId 'abc123def456789012345678901234ab'

# Download Sentry CLI
Get-SentryCLI -Version 'latest' -DownloadDirectory './bin'

Expand Down Expand Up @@ -133,6 +142,57 @@ Get-SentryLogsByAttribute -AttributeName 'test_id' -AttributeValue 'integration-
Get-SentryLogsByAttribute -AttributeName 'user_id' -AttributeValue '12345' -StatsPeriod '7d'
```

### Get-SentryMetrics

Retrieves metrics from Sentry using the `tracemetrics` dataset.

```powershell
# Query metrics by name
Get-SentryMetrics -Query 'metric.name:my.counter'

# With custom time period and fields
Get-SentryMetrics -Query 'metric.name:my.counter' -StatsPeriod '7d' -Fields @('id', 'metric.name', 'value')
```

Parameters:

- `Query`: Search query using Sentry search syntax
- `StatsPeriod`: Time period (default: '24h')
- `Limit`: Maximum metrics to return (default: 100)
- `Fields`: Fields to include in response (default: id, metric.name, metric.type, value, timestamp)

### Get-SentryMetricsByAttribute

Convenience wrapper for filtering metrics by metric name and a specific attribute.

```powershell
# Filter by metric name and test ID
Get-SentryMetricsByAttribute -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue 'abc-123'
```

### Get-SentrySpans

Retrieves spans from Sentry using the `spans` dataset. Transactions are spans with `is_transaction=true`.

```powershell
# Get all spans for a trace
Get-SentrySpans -TraceId 'abc123def456789012345678901234ab'

# Get only transactions for a trace
Get-SentrySpans -TraceId 'abc123def456' -Query 'is_transaction:true'

# Query by span operation
Get-SentrySpans -Query 'span.op:http.client' -StatsPeriod '7d'
```

Parameters:

- `Query`: Search query using Sentry search syntax
- `TraceId`: Filter spans by specific trace ID
- `StatsPeriod`: Time period (default: '24h')
- `Limit`: Maximum spans to return (default: 100)
- `Fields`: Fields to include in response (default: id, trace, span.op, span.description, span.duration, is_transaction, timestamp, transaction.event_id)

### Get-SentryCLI

Downloads the Sentry CLI executable for the current platform.
Expand Down
1 change: 1 addition & 0 deletions sentry-api-client/SentryApiClient.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
'Get-SentryLogsByAttribute',
'Get-SentryMetrics',
'Get-SentryMetricsByAttribute',
'Get-SentrySpans',
'Invoke-SentryCLI'
)

Expand Down
48 changes: 48 additions & 0 deletions sentry-api-client/Tests/Fixtures/SentrySpansResponses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"spans_list": {
"data": [
{
"id": "3b17400e50f24a27",
"trace": "140df6870c0f406faf87cff1f0d9e280",
"span.op": "perform-checkout",
"span.description": "Mac checkout",
"span.duration": 404.76,
"is_transaction": true,
"timestamp": "2025-02-17T07:56:06+00:00",
"transaction.event_id": "f4a523482c8a427ab7fc831e3ddca6b0"
},
{
"id": "0a7ca53c31b547c6",
"trace": "140df6870c0f406faf87cff1f0d9e280",
"span.op": "validation",
"span.description": "validating shopping cart",
"span.duration": 188.99,
"is_transaction": false,
"timestamp": "2025-02-17T07:56:06+00:00",
"transaction.event_id": "f4a523482c8a427ab7fc831e3ddca6b0"
}
],
"meta": {
"fields": {
"id": "string",
"trace": "string",
"span.op": "string",
"span.description": "string",
"span.duration": "duration",
"is_transaction": "boolean",
"timestamp": "string",
"transaction.event_id": "string"
},
"units": {
"span.duration": "millisecond"
}
}
},
"spans_empty": {
"data": [],
"meta": {
"fields": {},
"units": {}
}
}
}
116 changes: 116 additions & 0 deletions sentry-api-client/Tests/SentryApiClient.Spans.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
BeforeAll {
$ModulePath = Join-Path $PSScriptRoot '..' 'SentryApiClient.psd1'
Import-Module $ModulePath -Force

# Load test fixtures
$FixturesPath = Join-Path $PSScriptRoot 'Fixtures' 'SentrySpansResponses.json'
$Script:SpansFixtures = Get-Content $FixturesPath | ConvertFrom-Json -AsHashtable
}

AfterAll {
Remove-Module SentryApiClient -Force
}

Describe 'SentryApiClient Spans Functions' {
Context 'Module Export' {
It 'Should export Get-SentrySpans function' {
Get-Command Get-SentrySpans -Module SentryApiClient | Should -Not -BeNullOrEmpty
}
}

Context 'Get-SentrySpans' {
BeforeAll {
Mock -ModuleName SentryApiClient Invoke-WebRequest {
return @{ Content = ($Script:SpansFixtures.spans_list | ConvertTo-Json -Depth 10) }
}

Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
}

It 'Should retrieve spans from spans dataset' {
$result = Get-SentrySpans -TraceId '140df6870c0f406faf87cff1f0d9e280'

$result | Should -Not -BeNullOrEmpty
$result.data | Should -HaveCount 2

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'dataset=spans' -and
$Uri -match 'organizations/test-org/events/'
}
}

It 'Should include default fields when none specified' {
Get-SentrySpans -TraceId '140df6870c0f406faf87cff1f0d9e280'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'field=id' -and
$Uri -match 'field=trace' -and
$Uri -match 'field=span\.op' -and
$Uri -match 'field=span\.description' -and
$Uri -match 'field=span\.duration' -and
$Uri -match 'field=is_transaction' -and
$Uri -match 'field=timestamp' -and
$Uri -match 'field=transaction\.event_id'
}
}

It 'Should use custom fields when specified' {
Get-SentrySpans -Query 'span.op:http.client' -Fields @('id', 'trace', 'custom_field')

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'field=id' -and
$Uri -match 'field=trace' -and
$Uri -match 'field=custom_field'
}
}

It 'Should append trace filter to query' {
Get-SentrySpans -TraceId '140df6870c0f406faf87cff1f0d9e280'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'query=trace%3A140df6870c0f406faf87cff1f0d9e280'
}
}

It 'Should combine query and trace ID' {
Get-SentrySpans -Query 'is_transaction:true' -TraceId '140df6870c0f406faf87cff1f0d9e280'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'query=is_transaction%3Atrue' -and
$Uri -match 'trace%3A140df6870c0f406faf87cff1f0d9e280'
}
}

It 'Should pass stats period parameter' {
Get-SentrySpans -TraceId '140df6870c0f406faf87cff1f0d9e280' -StatsPeriod '7d'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'statsPeriod=7d'
}
}
}

Context 'Error Handling' {
BeforeAll {
Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
}

It 'Should handle API errors gracefully' {
Mock -ModuleName SentryApiClient Invoke-WebRequest {
throw [System.Net.WebException]::new('401 Unauthorized')
}

{ Get-SentrySpans } | Should -Throw '*Sentry API request*failed*'
}
}

Context 'Connection Validation' {
BeforeEach {
Disconnect-SentryApi
}

It 'Should throw when organization is not configured' {
{ Get-SentrySpans } | Should -Throw '*Organization not configured*'
}
}
}
Loading