FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: add Sentry PHP SDK v2 and v4 compatibility · buggregator/server@ef7ec3b · GitHub

Commit ef7ec3b

Browse files
andcommitted
fix: add Sentry PHP SDK v2 and v4 compatibility
Previously only SDK v3 was fully supported. This fixes three issues: 1. SDK v4 always uses envelope format and omits event_id from the item payload (only in envelope header). Events were invisible in the UI because the frontend couldn't find event_id in the payload. Fix: inject event_id from envelope header when missing. 2. SDK v2 sends timestamps as ISO 8601 strings ("2026-03-31T12:44:44Z") instead of numeric epoch. json.Number rejected these, causing the entire ErrorEvent unmarshal to fail silently — structured storage was skipped. Fix: FlexibleTS type accepts both formats. 3. SDK v3/v4 parameterized messages use object form {"message":"...", "params":[...], "formatted":"..."} which broke string unmarshal. Fix: FlexibleMessage type accepts both forms. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 522d554 commit ef7ec3b

6 files changed

Lines changed: 603 additions & 11 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
name: sentry-sdk-testing
3+
description: Test Buggregator's Sentry module compatibility with different Sentry PHP SDK versions. Triggers when the user asks to test Sentry compatibility, verify a new SDK version works, check Sentry transport changes, or mentions "sentry sdk", "sentry version", "sentry compatibility". Also triggers on requests to prepare for a new Sentry SDK release.
4+
---
5+
6+
# Sentry SDK Compatibility Testing
7+
8+
## Role
9+
10+
You are a protocol compatibility engineer. Your job is to verify that Buggregator's Sentry ingestion pipeline correctly handles payloads from all supported Sentry PHP SDK versions (v2, v3, v4, and future releases).
11+
12+
## When to Use
13+
14+
- A new major/minor Sentry PHP SDK version is released
15+
- A user reports that events from a specific SDK version are not appearing in the UI
16+
- Before a Buggregator release, to verify no regressions in Sentry support
17+
18+
## Workflow
19+
20+
### Phase 1 — Install SDK Versions
21+
22+
Create isolated temporary directories for each SDK version and install them via Composer:
23+
24+
```bash
25+
# For each version you need to test:
26+
mkdir -p /tmp/sentry-vN
27+
cd /tmp/sentry-vN
28+
composer require sentry/sentry:"^N.0" --no-interaction
29+
30+
# If PHP version constraint fails:
31+
composer require sentry/sentry:"^N.0" --no-interaction --ignore-platform-reqs
32+
33+
# Verify installed version:
34+
cd /tmp/sentry-vN && composer show sentry/sentry | head -5
35+
```
36+
37+
### Phase 2 — Analyze Transport Layer
38+
39+
For each SDK version, examine these key files:
40+
41+
| What to Find | Where to Look |
42+
|---|---|
43+
| Endpoint paths | `src/Dsn.php``getStoreApiEndpointUrl()`, `getEnvelopeApiEndpointUrl()` |
44+
| Transport logic | `src/Transport/HttpTransport.php``send()` method, which endpoint is chosen |
45+
| Headers | `src/HttpClient/Authentication/`, `src/Util/Http.php` |
46+
| Serialization | `src/Serializer/PayloadSerializer.php` (v2/v3) or `src/Serializer/EnvelopItems/*.php` (v4+) |
47+
| Compression | `src/HttpClient/Plugin/GzipEncoderPlugin.php` or `src/HttpClient/HttpClient.php` |
48+
| Event structure | `src/Event.php``toArray()` method (v2/v3) or `EnvelopItems/EventItem.php` (v4+) |
49+
50+
Document findings in a comparison table covering:
51+
1. **Endpoint**`/store/` vs `/envelope/` vs both
52+
2. **Content-Type**`application/json` vs `application/x-sentry-envelope`
53+
3. **Body format** — plain JSON vs envelope (header + items)
54+
4. **`event_id` location** — in payload, in envelope header, or both
55+
5. **Timestamp format** — ISO 8601 string vs numeric epoch float
56+
6. **Message format** — plain string vs `{"message","params","formatted"}` object
57+
7. **New/removed fields** in event payload
58+
8. **New envelope item types** (e.g., `log`, `trace_metric`, `client_report`)
59+
9. **Compression** — gzip, deflate, or none by default
60+
61+
### Phase 3 — Generate Real Payloads
62+
63+
Write a PHP script in each SDK directory to produce real serialized output:
64+
65+
```php
66+
<?php
67+
require_once 'vendor/autoload.php';
68+
69+
use Sentry\Options;
70+
use Sentry\Serializer\PayloadSerializer;
71+
use Sentry\Event;
72+
use Sentry\Severity;
73+
use Sentry\ExceptionDataBag;
74+
75+
$options = new Options([
76+
'dsn' => 'http://test@localhost:8000/1',
77+
'http_compression' => false,
78+
// For v3 with tracing: 'traces_sample_rate' => 1.0,
79+
]);
80+
81+
$serializer = new PayloadSerializer($options);
82+
83+
$event = Event::createEvent(); // v3/v4
84+
// $event = new Event(); // v2
85+
$event->setLevel(Severity::error());
86+
$event->setEnvironment('production');
87+
$event->setRelease('1.0.0');
88+
$event->setServerName('web-01');
89+
90+
$exception = new ExceptionDataBag(new \RuntimeException('Test error'));
91+
$event->setExceptions([$exception]);
92+
93+
echo $serializer->serialize($event);
94+
```
95+
96+
Save generated payloads for use in Go tests.
97+
98+
### Phase 4 — Write Go Tests
99+
100+
Add tests in `modules/sentry/handler_test.go` using real payloads from Phase 3.
101+
102+
Each test must verify:
103+
104+
1. **`Handle()` returns non-nil** — the event is recognized
105+
2. **`inc.UUID`** — matches the expected event_id
106+
3. **`inc.Type == "sentry"`** — correct event type
107+
4. **`inc.Project`** — extracted from URL path
108+
5. **`event_id` in payload** — present in `inc.Payload` JSON (critical for frontend rendering)
109+
6. **Exception/message data preserved** — key fields survive parsing
110+
111+
For structured storage tests (with DB), also verify:
112+
- Row exists in `sentry_error_events`
113+
- Exceptions stored in `sentry_exceptions`
114+
- Breadcrumbs stored in `sentry_breadcrumbs`
115+
116+
Test naming convention:
117+
```
118+
TestHandler_SDKvN_Format (e.g., TestHandler_SDKv4_Envelope)
119+
TestHandler_SDKvN_Format_WithDB (e.g., TestHandler_SDKv2_PlainJSON_WithDB)
120+
```
121+
122+
### Phase 5 — Fix Compatibility Issues
123+
124+
Common patterns that break between SDK versions:
125+
126+
| Problem | Symptom | Fix Pattern |
127+
|---|---|---|
128+
| `event_id` missing from item payload | Events invisible in UI | Inject from envelope header via `injectEventID()` |
129+
| Timestamp format change (string vs float) | `json.Unmarshal` fails silently | Use `FlexibleTS` type that accepts both |
130+
| Field type change (string vs object) | Structured storage skipped | Use flexible unmarshaler (e.g., `FlexibleMessage`) |
131+
| New envelope item type | Unknown items silently dropped | Add case to `handleEnvelope()` switch |
132+
| New fields in payload | Data loss in structured tables | Add fields to Go structs, update store functions |
133+
| Endpoint change | Handler doesn't match request | Update `Match()` path checks |
134+
135+
### Phase 6 — Verify All Tests Pass
136+
137+
```bash
138+
# Sentry module tests only:
139+
cd /home/butschster/repos/buggregator/server
140+
go test ./modules/sentry/ -v
141+
142+
# Full project:
143+
go test ./...
144+
```
145+
146+
## Known Version Differences (Reference)
147+
148+
### SDK v2 (2.x)
149+
- Plain JSON only, `/api/{id}/store/`
150+
- Timestamp: ISO 8601 string
151+
- No envelope support
152+
- No tracing/spans
153+
154+
### SDK v3 (3.x)
155+
- Plain JSON to `/store/` (no tracing) or Envelope to `/envelope/` (with tracing)
156+
- Timestamp: float epoch
157+
- `event_id` present in both envelope header and item payload
158+
159+
### SDK v4 (4.x)
160+
- Always Envelope to `/api/{id}/envelope/`
161+
- Timestamp: float epoch
162+
- `event_id` ONLY in envelope header (not in item payload)
163+
- New item types: `log`, `trace_metric`, `client_report`
164+
- SDK payload includes `packages` array
165+
- Span `origin` field added
166+
167+
## Key Files in Buggregator
168+
169+
| File | Responsibility |
170+
|---|---|
171+
| `modules/sentry/handler.go` | Request matching, decompression, JSON vs envelope routing, event_id injection |
172+
| `modules/sentry/envelope.go` | Envelope parsing (header + item pairs) |
173+
| `modules/sentry/types.go` | `ErrorEvent`, `FlexibleTS`, `FlexibleMessage`, `Transaction`, `RawSpan`, etc. |
174+
| `modules/sentry/store_error.go` | Structured storage for error events, exceptions, breadcrumbs |
175+
| `modules/sentry/store_transaction.go` | Structured storage for transactions and spans |
176+
| `modules/sentry/preview.go` | Preview mapper for WebSocket broadcast |
177+
| `modules/sentry/handler_test.go` | SDK compatibility tests |
178+
179+
## Cleanup
180+
181+
After testing, remove temporary directories:
182+
183+
```bash
184+
rm -rf /tmp/sentry-v2 /tmp/sentry-v3 /tmp/sentry-v4 /tmp/sentry-v5
185+
```

‎modules/sentry/handler.go‎

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func (h *handler) handleEnvelope(body []byte, project string) (*event.Incoming,
113113
for _, item := range items {
114114
switch item.Type {
115115
case "event":
116-
uuid, payload := h.handleEventItem(item, project)
116+
uuid, payload := h.handleEventItem(item, envHeader.EventID, project)
117117
if uuid != "" {
118118
primaryUUID = uuid
119119
}
@@ -177,22 +177,33 @@ func (h *handler) handleEnvelope(body []byte, project string) (*event.Incoming,
177177
}
178178

179179
// handleEventItem processes an "event" envelope item (error/message event).
180-
func (h *handler) handleEventItem(item EnvelopeItem, project string) (string, json.RawMessage) {
180+
// envelopeEventID is the event_id from the envelope header — Sentry SDK v4+
181+
// omits event_id from the item payload, so we inject it when missing.
182+
func (h *handler) handleEventItem(item EnvelopeItem, envelopeEventID string, project string) (string, json.RawMessage) {
181183
var ev ErrorEvent
182184
if err := json.Unmarshal(item.Payload, &ev); err != nil {
183185
slog.Warn("sentry: failed to parse error event", "err", err)
184186
return "", item.Payload
185187
}
186188

189+
// Sentry SDK v4+ sends event_id only in the envelope header, not in
190+
// the item payload. Inject it so downstream consumers (frontend, API)
191+
// always see it in the payload.
187192
uuid := ev.EventID
193+
payload := item.Payload
194+
if uuid == "" && envelopeEventID != "" {
195+
uuid = envelopeEventID
196+
ev.EventID = envelopeEventID
197+
payload = injectEventID(payload, envelopeEventID)
198+
}
188199

189200
if h.db != nil {
190-
if _, err := storeErrorEvent(h.db, &ev, item.Payload, project); err != nil {
201+
if _, err := storeErrorEvent(h.db, &ev, payload, project); err != nil {
191202
slog.Warn("sentry: failed to store structured error event", "err", err)
192203
}
193204
}
194205

195-
return uuid, item.Payload
206+
return uuid, payload
196207
}
197208

198209
// handleTransactionItem processes a "transaction" envelope item.
@@ -261,6 +272,21 @@ func (h *handler) handleLogItem(item EnvelopeItem) {
261272
}
262273
}
263274

275+
// injectEventID adds "event_id" to a JSON payload that lacks it.
276+
// Used to normalize Sentry SDK v4+ payloads which omit event_id from item bodies.
277+
func injectEventID(payload json.RawMessage, eventID string) json.RawMessage {
278+
var obj map[string]json.RawMessage
279+
if err := json.Unmarshal(payload, &obj); err != nil {
280+
return payload
281+
}
282+
obj["event_id"] = json.RawMessage(`"` + eventID + `"`)
283+
result, err := json.Marshal(obj)
284+
if err != nil {
285+
return payload
286+
}
287+
return result
288+
}
289+
264290
// decompress handles gzip and deflate Content-Encoding.
265291
// Also auto-detects gzip/zlib by magic bytes if header is missing.
266292
func decompress(data []byte, encoding string) []byte {

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL