Skip to content

Commit 0f3a63a

Browse files
authored
feat: Add utilities to query metrics from Sentry (#38)
* feat: Add utilities to query metrics from Sentry * Add fields param * Add tests
1 parent f82cd9c commit 0f3a63a

File tree

6 files changed

+426
-1
lines changed

6 files changed

+426
-1
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
function Get-SentryMetrics {
2+
<#
3+
.SYNOPSIS
4+
Retrieves metrics from Sentry.
5+
6+
.DESCRIPTION
7+
Fetches Sentry metrics matching specified criteria.
8+
Supports filtering by query, time range, and custom fields.
9+
Uses the Sentry Discover API with the 'tracemetrics' dataset.
10+
11+
.PARAMETER Query
12+
Search query string using Sentry search syntax (e.g., 'metric.name:my.counter', 'metric.type:counter').
13+
14+
.PARAMETER StatsPeriod
15+
Relative time period (e.g., '24h', '7d', '14d'). Default is '24h'.
16+
17+
.PARAMETER Limit
18+
Maximum number of metrics to return. Default is 100.
19+
20+
.PARAMETER Cursor
21+
Pagination cursor for retrieving subsequent pages of results.
22+
23+
.PARAMETER Fields
24+
Specific fields to return. Default includes: id, metric.name, metric.type, value, timestamp.
25+
26+
.EXAMPLE
27+
Get-SentryMetrics -Query 'metric.name:my.counter'
28+
29+
.EXAMPLE
30+
Get-SentryMetrics -Query 'metric.name:my.counter test_id:abc123' -StatsPeriod '7d'
31+
#>
32+
[CmdletBinding()]
33+
param(
34+
[Parameter(Mandatory = $false)]
35+
[string]$Query,
36+
37+
[Parameter(Mandatory = $false)]
38+
[string]$StatsPeriod = '24h',
39+
40+
[Parameter(Mandatory = $false)]
41+
[int]$Limit = 100,
42+
43+
[Parameter(Mandatory = $false)]
44+
[string]$Cursor,
45+
46+
[Parameter(Mandatory = $false)]
47+
[string[]]$Fields
48+
)
49+
50+
# Default fields for metrics if not specified
51+
if (-not $Fields -or $Fields.Count -eq 0) {
52+
$Fields = @(
53+
'id',
54+
'metric.name',
55+
'metric.type',
56+
'value',
57+
'timestamp'
58+
)
59+
}
60+
61+
$QueryParams = @{
62+
dataset = 'tracemetrics'
63+
statsPeriod = $StatsPeriod
64+
per_page = $Limit
65+
field = $Fields
66+
}
67+
68+
if ($Query) {
69+
$QueryParams.query = $Query
70+
}
71+
72+
if ($Cursor) {
73+
$QueryParams.cursor = $Cursor
74+
}
75+
76+
$QueryString = Build-QueryString -Parameters $QueryParams
77+
$Uri = Get-SentryOrganizationUrl -Resource "events/" -QueryString $QueryString
78+
79+
try {
80+
$Response = Invoke-SentryApiRequest -Uri $Uri -Method 'GET'
81+
return $Response
82+
}
83+
catch {
84+
Write-Error "Failed to retrieve metrics - $_"
85+
throw
86+
}
87+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
function Get-SentryMetricsByAttribute {
2+
<#
3+
.SYNOPSIS
4+
Retrieves metrics filtered by metric name and a specific attribute.
5+
6+
.DESCRIPTION
7+
Fetches Sentry metrics that match a specific metric name and attribute
8+
name/value pair. This is a convenience wrapper around Get-SentryMetrics
9+
for common use cases like filtering by test_id for integration testing.
10+
11+
.PARAMETER MetricName
12+
The name of the metric to filter by (e.g., 'test.integration.counter').
13+
14+
.PARAMETER AttributeName
15+
The name of the attribute to filter by (e.g., 'test_id').
16+
17+
.PARAMETER AttributeValue
18+
The value of the attribute to match.
19+
20+
.PARAMETER Limit
21+
Maximum number of metrics to return. Default is 100.
22+
23+
.PARAMETER StatsPeriod
24+
Relative time period (e.g., '24h', '7d'). Default is '24h'.
25+
26+
.PARAMETER Fields
27+
Additional fields to include in the response. These are merged with default fields
28+
(id, metric.name, metric.type, value, timestamp) and the filter attribute.
29+
30+
.EXAMPLE
31+
Get-SentryMetricsByAttribute -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue 'abc-123'
32+
33+
.EXAMPLE
34+
Get-SentryMetricsByAttribute -MetricName 'my.counter' -AttributeName 'user_id' -AttributeValue '12345' -StatsPeriod '7d'
35+
#>
36+
[CmdletBinding()]
37+
param(
38+
[Parameter(Mandatory = $true)]
39+
[string]$MetricName,
40+
41+
[Parameter(Mandatory = $true)]
42+
[string]$AttributeName,
43+
44+
[Parameter(Mandatory = $true)]
45+
[string]$AttributeValue,
46+
47+
[Parameter(Mandatory = $false)]
48+
[int]$Limit = 100,
49+
50+
[Parameter(Mandatory = $false)]
51+
[string]$StatsPeriod = '24h',
52+
53+
[Parameter(Mandatory = $false)]
54+
[string[]]$Fields
55+
)
56+
57+
$Query = "metric.name:$MetricName $AttributeName`:$AttributeValue"
58+
59+
# Include default fields plus the attribute we're filtering by
60+
$DefaultFields = @(
61+
'id',
62+
'metric.name',
63+
'metric.type',
64+
'value',
65+
'timestamp',
66+
$AttributeName
67+
)
68+
69+
if ($Fields) {
70+
$AllFields = @($DefaultFields + $Fields) | Select-Object -Unique
71+
} else {
72+
$AllFields = $DefaultFields
73+
}
74+
75+
return Get-SentryMetrics -Query $Query -Limit $Limit -StatsPeriod $StatsPeriod -Fields $AllFields
76+
}

sentry-api-client/SentryApiClient.psd1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
'Get-SentryEventsByTag',
3838
'Get-SentryLogs',
3939
'Get-SentryLogsByAttribute',
40+
'Get-SentryMetrics',
41+
'Get-SentryMetricsByAttribute',
4042
'Invoke-SentryCLI'
4143
)
4244

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"metrics_list": {
3+
"data": [
4+
{
5+
"id": "019bbce4569e7f5f81466757e8f84001",
6+
"metric.name": "test.integration.counter",
7+
"metric.type": "counter",
8+
"value": 42.0,
9+
"timestamp": "2025-01-14T14:32:45+00:00",
10+
"test_id": "metrics-test-001"
11+
},
12+
{
13+
"id": "019bbce4569e7f5f81466757e8f84002",
14+
"metric.name": "test.integration.counter",
15+
"metric.type": "counter",
16+
"value": 7.0,
17+
"timestamp": "2025-01-14T14:32:46+00:00",
18+
"test_id": "metrics-test-001"
19+
}
20+
],
21+
"meta": {
22+
"fields": {
23+
"id": "string",
24+
"metric.name": "string",
25+
"metric.type": "string",
26+
"value": "number",
27+
"timestamp": "date",
28+
"test_id": "string"
29+
},
30+
"units": {}
31+
}
32+
},
33+
"metrics_empty": {
34+
"data": [],
35+
"meta": {
36+
"fields": {},
37+
"units": {}
38+
}
39+
}
40+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
BeforeAll {
2+
$ModulePath = Join-Path $PSScriptRoot '..' 'SentryApiClient.psd1'
3+
Import-Module $ModulePath -Force
4+
5+
# Load test fixtures
6+
$FixturesPath = Join-Path $PSScriptRoot 'Fixtures' 'SentryMetricsResponses.json'
7+
$Script:MetricsFixtures = Get-Content $FixturesPath | ConvertFrom-Json -AsHashtable
8+
}
9+
10+
AfterAll {
11+
Remove-Module SentryApiClient -Force
12+
}
13+
14+
Describe 'SentryApiClient Metrics Functions' {
15+
Context 'Module Export' {
16+
It 'Should export Get-SentryMetrics function' {
17+
Get-Command Get-SentryMetrics -Module SentryApiClient | Should -Not -BeNullOrEmpty
18+
}
19+
20+
It 'Should export Get-SentryMetricsByAttribute function' {
21+
Get-Command Get-SentryMetricsByAttribute -Module SentryApiClient | Should -Not -BeNullOrEmpty
22+
}
23+
}
24+
25+
Context 'Get-SentryMetrics' {
26+
BeforeAll {
27+
Mock -ModuleName SentryApiClient Invoke-WebRequest {
28+
return @{ Content = ($Script:MetricsFixtures.metrics_list | ConvertTo-Json -Depth 10) }
29+
}
30+
31+
Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
32+
}
33+
34+
It 'Should retrieve metrics from tracemetrics dataset' {
35+
$result = Get-SentryMetrics -Query 'metric.name:test.integration.counter'
36+
37+
$result | Should -Not -BeNullOrEmpty
38+
$result.data | Should -HaveCount 2
39+
40+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
41+
$Uri -match 'dataset=tracemetrics' -and
42+
$Uri -match 'organizations/test-org/events/'
43+
}
44+
}
45+
46+
It 'Should include default fields when none specified' {
47+
Get-SentryMetrics -Query 'metric.name:test.integration.counter'
48+
49+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
50+
$Uri -match 'field=id' -and
51+
$Uri -match 'field=metric\.name' -and
52+
$Uri -match 'field=metric\.type' -and
53+
$Uri -match 'field=value' -and
54+
$Uri -match 'field=timestamp'
55+
}
56+
}
57+
58+
It 'Should use custom fields when specified' {
59+
Get-SentryMetrics -Query 'metric.name:test.counter' -Fields @('id', 'value', 'custom_field')
60+
61+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
62+
$Uri -match 'field=id' -and
63+
$Uri -match 'field=value' -and
64+
$Uri -match 'field=custom_field'
65+
}
66+
}
67+
68+
It 'Should pass stats period parameter' {
69+
Get-SentryMetrics -Query 'metric.name:test.counter' -StatsPeriod '7d'
70+
71+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
72+
$Uri -match 'statsPeriod=7d'
73+
}
74+
}
75+
}
76+
77+
Context 'Get-SentryMetricsByAttribute' {
78+
BeforeAll {
79+
Mock -ModuleName SentryApiClient Invoke-WebRequest {
80+
return @{ Content = ($Script:MetricsFixtures.metrics_list | ConvertTo-Json -Depth 10) }
81+
}
82+
83+
Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
84+
}
85+
86+
It 'Should query by metric name and attribute' {
87+
$result = Get-SentryMetricsByAttribute -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue 'metrics-test-001'
88+
89+
$result | Should -Not -BeNullOrEmpty
90+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
91+
$Uri -match 'query=metric\.name%3Atest\.integration\.counter' -and
92+
$Uri -match 'test_id%3Ametrics-test-001'
93+
}
94+
}
95+
96+
It 'Should include the filter attribute in response fields' {
97+
Get-SentryMetricsByAttribute -MetricName 'test.counter' -AttributeName 'test_id' -AttributeValue 'abc-123'
98+
99+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
100+
$Uri -match 'field=test_id'
101+
}
102+
}
103+
104+
It 'Should merge additional fields with defaults' {
105+
Get-SentryMetricsByAttribute -MetricName 'test.counter' -AttributeName 'test_id' -AttributeValue 'abc-123' -Fields @('extra_field')
106+
107+
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
108+
$Uri -match 'field=extra_field' -and
109+
$Uri -match 'field=metric\.name' -and
110+
$Uri -match 'field=test_id'
111+
}
112+
}
113+
}
114+
115+
Context 'Error Handling' {
116+
BeforeAll {
117+
Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
118+
}
119+
120+
It 'Should handle API errors gracefully' {
121+
Mock -ModuleName SentryApiClient Invoke-WebRequest {
122+
throw [System.Net.WebException]::new('401 Unauthorized')
123+
}
124+
125+
{ Get-SentryMetrics } | Should -Throw '*Sentry API request*failed*'
126+
}
127+
}
128+
129+
Context 'Connection Validation' {
130+
BeforeEach {
131+
Disconnect-SentryApi
132+
}
133+
134+
It 'Should throw when organization is not configured' {
135+
{ Get-SentryMetrics } | Should -Throw '*Organization not configured*'
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)