bug_bunny 4.9.0 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,135 +0,0 @@
1
- ---
2
- name: sentry
3
- description: Experto en la instancia self-hosted de Sentry de Wispro. Úsala SIEMPRE que necesites buscar errores, listar issues, ver stacktraces, resolver o asignar issues en Sentry. Funciona con cualquier proyecto del ecosistema.
4
- ---
5
-
6
- # Sentry Expert
7
-
8
- Skill de conocimiento completo sobre la instancia de Sentry self-hosted de Wispro. Consultame para buscar errores, analizar stacktraces, gestionar issues o diagnosticar problemas en producción.
9
-
10
- ## Configuración
11
-
12
- ```bash
13
- SENTRY_TOKEN=$SENTRY_TOKEN
14
- SENTRY_URL="https://sentry.cloud.wispro.co"
15
- SENTRY_ORG="wispro"
16
- ```
17
-
18
- **Importante:** Siempre usar `-k` en curl (certificado self-hosted). Nunca exponer el token en las respuestas.
19
-
20
- ## Glosario
21
-
22
- **Issue** — Agrupación de eventos similares. Tiene un ID numérico y un shortId legible (ej: `PROJECT-123`).
23
-
24
- **Event** — Ocurrencia individual de un error. Contiene stacktrace, contexto, usuario, breadcrumbs.
25
-
26
- **Project slug** — Identificador del proyecto en Sentry. Cada microservicio tiene uno. Si no lo sabés, listá los proyectos primero.
27
-
28
- **statsPeriod** — Período de estadísticas: `24h`, `14d` o vacío para todo el historial.
29
-
30
- ## Flujos de Trabajo
31
-
32
- ### Buscar errores en un proyecto
33
-
34
- 1. Si no tenés el project slug, listá los proyectos primero (ver API).
35
- 2. Consultá issues del proyecto filtrando por período y query.
36
- 3. Para cada issue relevante, consultá los eventos para ver el stacktrace.
37
-
38
- ### Diagnosticar un error desde el código
39
-
40
- 1. Tomá el mensaje de error o excepción del código/logs.
41
- 2. Buscá en Sentry con `query=<mensaje>` en el proyecto correspondiente.
42
- 3. Si hay match, mostrá el stacktrace y la frecuencia.
43
- 4. Si no hay match, indicá que el error no está reportado en Sentry.
44
-
45
- ### Gestionar issues
46
-
47
- - **Resolver**: `status=resolved`
48
- - **Ignorar**: `status=ignored`
49
- - **Asignar**: `assignedTo=<username>`
50
- - **Marcar como visto**: `hasSeen=true`
51
-
52
- ### Análisis de tendencias
53
-
54
- 1. Consultá issues con `statsPeriod=14d` para ver tendencias.
55
- 2. Ordená por `count` o `userCount` para priorizar.
56
- 3. Revisá `firstSeen` y `lastSeen` para detectar regresiones.
57
-
58
- ## API — Endpoints principales
59
-
60
- Todos los endpoints usan base URL `$SENTRY_URL/api/0/` y header `Authorization: Bearer $SENTRY_TOKEN`.
61
-
62
- Ver catálogo completo en [references/api-endpoints.md](references/api-endpoints.md).
63
-
64
- ### Listar proyectos
65
- ```bash
66
- curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
67
- "$SENTRY_URL/api/0/organizations/$SENTRY_ORG/projects/" | jq '.[] | {slug, name}'
68
- ```
69
-
70
- ### Listar issues de un proyecto
71
- ```bash
72
- curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
73
- "$SENTRY_URL/api/0/projects/$SENTRY_ORG/{PROJECT_SLUG}/issues/?statsPeriod=24h&query=is:unresolved"
74
- ```
75
-
76
- ### Ver detalle de un issue
77
- ```bash
78
- curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
79
- "$SENTRY_URL/api/0/organizations/$SENTRY_ORG/issues/{ISSUE_ID}/"
80
- ```
81
-
82
- ### Ver eventos de un issue (con stacktrace)
83
- ```bash
84
- curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
85
- "$SENTRY_URL/api/0/organizations/$SENTRY_ORG/issues/{ISSUE_ID}/events/?full=true&limit=1"
86
- ```
87
-
88
- ### Buscar por mensaje de error
89
- ```bash
90
- curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
91
- "$SENTRY_URL/api/0/projects/$SENTRY_ORG/{PROJECT_SLUG}/issues/?query={MENSAJE}&statsPeriod=24h"
92
- ```
93
-
94
- ### Resolver un issue
95
- ```bash
96
- curl -sk -X PUT -H "Authorization: Bearer $SENTRY_TOKEN" \
97
- -H "Content-Type: application/json" \
98
- -d '{"status":"resolved"}' \
99
- "$SENTRY_URL/api/0/organizations/$SENTRY_ORG/issues/{ISSUE_ID}/"
100
- ```
101
-
102
- ## FAQ
103
-
104
- ### ¿Cómo encuentro el project slug de mi servicio?
105
- Ejecutá el endpoint de listar proyectos. El campo `slug` es lo que necesitás. Generalmente coincide con el nombre del repo en minúsculas.
106
-
107
- ### ¿Cómo veo el stacktrace completo?
108
- Usá el endpoint de eventos con `full=true`. El stacktrace está en `entries[].data.values[].stacktrace.frames[]`. Cada frame tiene `filename`, `lineNo`, `function` y `context`.
109
-
110
- ### ¿Cómo filtro por nivel de error?
111
- Agregá `query=level:error` o `query=level:warning` al endpoint de issues.
112
-
113
- ### ¿Cómo pagino resultados?
114
- La API devuelve un header `Link` con cursores. Usá el parámetro `cursor` del link `next` para la siguiente página.
115
-
116
- ## Antipatrones
117
-
118
- **Buscar sin project slug** — No consultes issues a nivel organización si sabés el proyecto. Es más lento y devuelve ruido de otros servicios.
119
-
120
- **Ignorar la paginación** — Por defecto la API devuelve pocos resultados. Si necesitás más, paginá con el cursor.
121
-
122
- **Hardcodear issue IDs** — Los IDs cambian. Siempre buscá por query o filtrá por período.
123
-
124
- ## Errores comunes
125
-
126
- **401 Unauthorized** — Token inválido o expirado. Verificá que `$SENTRY_TOKEN` esté en el entorno.
127
-
128
- **404 Not Found** — Project slug incorrecto. Listá los proyectos para verificar.
129
-
130
- **SSL Certificate Error** — Falta el flag `-k` en curl. Es necesario porque es self-hosted.
131
-
132
- ## Referencias
133
-
134
- - [API Endpoints](references/api-endpoints.md) — Catálogo completo de endpoints con parámetros
135
- - [sentry.rb](scripts/sentry.rb) — Script helper para queries comunes
@@ -1,147 +0,0 @@
1
- # Sentry API — Catálogo de Endpoints
2
-
3
- Base URL: `$SENTRY_URL/api/0/`
4
- Auth: `Authorization: Bearer $SENTRY_TOKEN`
5
- Siempre usar `-k` en curl (certificado self-hosted).
6
-
7
- ## Proyectos
8
-
9
- ### Listar proyectos de la organización
10
- ```
11
- GET /organizations/{org}/projects/
12
- ```
13
- Respuesta: array de objetos con `slug`, `name`, `id`, `platform`, `dateCreated`.
14
-
15
- ### Obtener detalle de un proyecto
16
- ```
17
- GET /projects/{org}/{project_slug}/
18
- ```
19
-
20
- ## Issues
21
-
22
- ### Listar issues de un proyecto
23
- ```
24
- GET /projects/{org}/{project_slug}/issues/
25
- ```
26
- Query params:
27
- - `statsPeriod` — `24h`, `14d` o vacío (default: `24h`)
28
- - `query` — Búsqueda estructurada (default: `is:unresolved`)
29
- - `shortIdLookup` — `true` para buscar por shortId
30
- - `cursor` — Paginación
31
-
32
- Respuesta: array de issues con `id`, `title`, `culprit`, `count`, `userCount`, `firstSeen`, `lastSeen`, `level`, `status`, `shortId`, `project`.
33
-
34
- ### Listar issues de la organización
35
- ```
36
- GET /organizations/{org}/issues/
37
- ```
38
- Mismos query params. Devuelve issues de todos los proyectos.
39
-
40
- ### Obtener detalle de un issue
41
- ```
42
- GET /organizations/{org}/issues/{issue_id}/
43
- ```
44
- Respuesta: issue completo con `activity`, `assignedTo`, `count`, `firstSeen`, `lastSeen`, `firstRelease`, `lastRelease`, `participants`, `userReportCount`, `stats`.
45
-
46
- ### Actualizar un issue
47
- ```
48
- PUT /organizations/{org}/issues/{issue_id}/
49
- ```
50
- Body (JSON, todos opcionales):
51
- - `status` — `resolved`, `resolvedInNextRelease`, `unresolved`, `ignored`
52
- - `assignedTo` — username o team
53
- - `hasSeen` — boolean
54
- - `isBookmarked` — boolean
55
- - `isSubscribed` — boolean
56
- - `isPublic` — boolean
57
-
58
- ### Eliminar un issue
59
- ```
60
- DELETE /organizations/{org}/issues/{issue_id}/
61
- ```
62
-
63
- ### Bulk update issues de un proyecto
64
- ```
65
- PUT /projects/{org}/{project_slug}/issues/
66
- ```
67
- Query params:
68
- - `id` — lista de issue IDs a actualizar
69
-
70
- Body: mismos campos que update individual.
71
-
72
- ## Eventos
73
-
74
- ### Listar eventos de un issue
75
- ```
76
- GET /organizations/{org}/issues/{issue_id}/events/
77
- ```
78
- Query params:
79
- - `full` — `true` para incluir body completo con stacktrace
80
- - `statsPeriod` — período de filtro
81
- - `environment` — filtrar por entorno
82
- - `query` — búsqueda dentro de eventos
83
- - `cursor` — paginación
84
-
85
- Respuesta: array de eventos con `id`, `eventID`, `groupID`, `title`, `message`, `dateCreated`, `tags`, `user`, `location`, `culprit`.
86
-
87
- ### Listar eventos de error de un proyecto
88
- ```
89
- GET /projects/{org}/{project_slug}/events/
90
- ```
91
-
92
- ### Obtener evento específico de un proyecto
93
- ```
94
- GET /projects/{org}/{project_slug}/events/{event_id}/
95
- ```
96
- Respuesta completa con stacktrace en `entries[].data.values[].stacktrace.frames[]`.
97
-
98
- Cada frame contiene:
99
- - `filename` — archivo
100
- - `lineNo` — línea
101
- - `function` — método
102
- - `context` — líneas de código alrededor
103
- - `inApp` — boolean, si es código de la app o dependencia
104
-
105
- ## Tags
106
-
107
- ### Valores de un tag en un issue
108
- ```
109
- GET /organizations/{org}/issues/{issue_id}/tags/{tag_key}/values/
110
- ```
111
- Tags comunes: `environment`, `server_name`, `browser`, `os`, `release`.
112
-
113
- ### Detalle de un tag en un issue
114
- ```
115
- GET /organizations/{org}/issues/{issue_id}/tags/{tag_key}/
116
- ```
117
-
118
- ## Hashes
119
-
120
- ### Listar hashes de un issue
121
- ```
122
- GET /organizations/{org}/issues/{issue_id}/hashes/
123
- ```
124
- Útil para entender qué variantes de stacktrace se agrupan en el mismo issue.
125
-
126
- ## Paginación
127
-
128
- La API usa cursor-based pagination. El header `Link` de la respuesta contiene:
129
-
130
- ```
131
- Link: <url>; rel="previous"; results="false"; cursor="...",
132
- <url>; rel="next"; results="true"; cursor="..."
133
- ```
134
-
135
- Usar el valor de `cursor` del link `next` como query param para la siguiente página. Cuando `results="false"` en `next`, no hay más páginas.
136
-
137
- ## Queries estructuradas
138
-
139
- El parámetro `query` soporta:
140
- - `is:unresolved` / `is:resolved` / `is:ignored`
141
- - `level:error` / `level:warning` / `level:info`
142
- - `assigned:username` / `assigned:me`
143
- - `bookmarks:me`
144
- - `firstSeen:>2024-01-01`
145
- - `lastSeen:<24h`
146
- - `times_seen:>100`
147
- - Texto libre para buscar en título/mensaje
@@ -1,194 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Helper para interactuar con la API de Sentry self-hosted de Wispro.
5
- # Uso: ruby sentry.rb <comando> [opciones]
6
- #
7
- # Comandos:
8
- # projects — Lista todos los proyectos
9
- # issues <project_slug> [opciones] — Lista issues de un proyecto
10
- # issue <issue_id> — Detalle de un issue
11
- # events <issue_id> [--full] — Eventos de un issue
12
- # search <project_slug> <query> — Busca issues por texto
13
- # resolve <issue_id> — Resuelve un issue
14
- # ignore <issue_id> — Ignora un issue
15
- # assign <issue_id> <username> — Asigna un issue
16
-
17
- require 'net/http'
18
- require 'uri'
19
- require 'json'
20
- require 'openssl'
21
-
22
- module Sentry
23
- URL = ENV['SENTRY_URL'] || 'https://sentry.cloud.wispro.co'
24
- TOKEN = ENV['SENTRY_TOKEN']
25
- ORG = ENV['SENTRY_ORG'] || 'wispro'
26
-
27
- class << self
28
- def run(args)
29
- unless TOKEN
30
- puts "ERROR: SENTRY_TOKEN no configurado en el entorno."
31
- exit 1
32
- end
33
-
34
- command = args.shift
35
- case command
36
- when 'projects' then projects
37
- when 'issues' then issues(args)
38
- when 'issue' then issue(args.first)
39
- when 'events' then events(args)
40
- when 'search' then search(args)
41
- when 'resolve' then update_status(args.first, 'resolved')
42
- when 'ignore' then update_status(args.first, 'ignored')
43
- when 'assign' then assign(args[0], args[1])
44
- else
45
- puts USAGE
46
- end
47
- end
48
-
49
- private
50
-
51
- USAGE = <<~TEXT
52
- Uso: ruby sentry.rb <comando> [opciones]
53
-
54
- Comandos:
55
- projects Lista todos los proyectos
56
- issues <project_slug> [--period=24h] Lista issues (default: 24h, unresolved)
57
- issue <issue_id> Detalle de un issue
58
- events <issue_id> [--full] Eventos de un issue
59
- search <project_slug> <query> Busca issues por texto
60
- resolve <issue_id> Resuelve un issue
61
- ignore <issue_id> Ignora un issue
62
- assign <issue_id> <username> Asigna un issue
63
- TEXT
64
-
65
- def projects
66
- data = get("/organizations/#{ORG}/projects/")
67
- data.each do |p|
68
- puts " #{p['slug'].ljust(30)} #{p['name']}"
69
- end
70
- end
71
-
72
- def issues(args)
73
- slug = args.shift
74
- period = extract_flag(args, '--period') || '24h'
75
-
76
- data = get("/projects/#{ORG}/#{slug}/issues/?statsPeriod=#{period}&query=is:unresolved")
77
- print_issues(data)
78
- end
79
-
80
- def issue(issue_id)
81
- data = get("/organizations/#{ORG}/issues/#{issue_id}/")
82
- puts " ##{data['shortId']} — #{data['title']}"
83
- puts " Level: #{data['level']} | Count: #{data['count']} | Users: #{data['userCount']}"
84
- puts " First: #{data['firstSeen']} | Last: #{data['lastSeen']}"
85
- puts " Status: #{data['status']}"
86
- puts " Assigned: #{data.dig('assignedTo', 'name') || 'nadie'}"
87
- puts " Link: #{data['permalink']}"
88
- end
89
-
90
- def events(args)
91
- issue_id = args.shift
92
- full = args.include?('--full')
93
- params = full ? '?full=true&limit=3' : '?limit=5'
94
-
95
- data = get("/organizations/#{ORG}/issues/#{issue_id}/events/#{params}")
96
- data.each do |event|
97
- puts "\n Event #{event['eventID'][0..7]} — #{event['dateCreated']}"
98
- puts " #{event['title']}"
99
-
100
- next unless full && event['entries']
101
-
102
- event['entries'].each do |entry|
103
- next unless entry['type'] == 'exception'
104
-
105
- entry.dig('data', 'values')&.each do |exc|
106
- puts " Exception: #{exc['type']}: #{exc['value']}"
107
- frames = exc.dig('stacktrace', 'frames') || []
108
- frames.select { |f| f['inApp'] }.last(5).each do |frame|
109
- puts " #{frame['filename']}:#{frame['lineNo']} in #{frame['function']}"
110
- end
111
- end
112
- end
113
- end
114
- end
115
-
116
- def search(args)
117
- slug = args.shift
118
- query = args.join(' ')
119
- data = get("/projects/#{ORG}/#{slug}/issues/?query=#{URI.encode_www_form_component(query)}&statsPeriod=24h")
120
- print_issues(data)
121
- end
122
-
123
- def update_status(issue_id, status)
124
- data = put("/organizations/#{ORG}/issues/#{issue_id}/", { status: status })
125
- puts " Issue ##{issue_id} → #{data['status']}"
126
- end
127
-
128
- def assign(issue_id, username)
129
- data = put("/organizations/#{ORG}/issues/#{issue_id}/", { assignedTo: username })
130
- puts " Issue ##{issue_id} → asignado a #{data.dig('assignedTo', 'name') || username}"
131
- end
132
-
133
- def print_issues(data)
134
- if data.empty?
135
- puts " Sin issues encontrados."
136
- return
137
- end
138
-
139
- data.each do |i|
140
- level = i['level'].upcase.ljust(7)
141
- count = "x#{i['count']}".ljust(6)
142
- puts " #{level} #{count} ##{i['shortId'].ljust(15)} #{i['title'][0..80]}"
143
- end
144
- end
145
-
146
- # --- HTTP ---
147
-
148
- def get(path)
149
- request(:get, path)
150
- end
151
-
152
- def put(path, body)
153
- request(:put, path, body)
154
- end
155
-
156
- def request(method, path, body = nil)
157
- uri = URI.parse("#{URL}/api/0#{path}")
158
- http = Net::HTTP.new(uri.host, uri.port)
159
- http.use_ssl = uri.scheme == 'https'
160
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
161
-
162
- req = case method
163
- when :get then Net::HTTP::Get.new(uri)
164
- when :put then Net::HTTP::Put.new(uri)
165
- end
166
-
167
- req['Authorization'] = "Bearer #{TOKEN}"
168
- req['Content-Type'] = 'application/json'
169
- req.body = JSON.generate(body) if body
170
-
171
- response = http.request(req)
172
-
173
- unless response.code.start_with?('2')
174
- puts " ERROR: HTTP #{response.code} — #{response.body[0..200]}"
175
- exit 1
176
- end
177
-
178
- JSON.parse(response.body)
179
- rescue StandardError => e
180
- puts " ERROR: #{e.message}"
181
- exit 1
182
- end
183
-
184
- def extract_flag(args, flag)
185
- idx = args.index { |a| a.start_with?(flag) }
186
- return nil unless idx
187
-
188
- value = args.delete_at(idx)
189
- value.include?('=') ? value.split('=', 2).last : args.delete_at(idx)
190
- end
191
- end
192
- end
193
-
194
- Sentry.run(ARGV.dup) if __FILE__ == $PROGRAM_NAME