172 lines
4.1 KiB
Go
172 lines
4.1 KiB
Go
package irontelemetry
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Transport handles HTTP communication with the server
|
|
type Transport struct {
|
|
apiBaseURL string
|
|
publicKey string
|
|
debug bool
|
|
client *http.Client
|
|
}
|
|
|
|
// NewTransport creates a new Transport
|
|
func NewTransport(parsedDSN *ParsedDSN, apiBaseURL string, debug bool) *Transport {
|
|
return &Transport{
|
|
apiBaseURL: apiBaseURL,
|
|
publicKey: parsedDSN.PublicKey,
|
|
debug: debug,
|
|
client: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Send sends an event to the server
|
|
func (t *Transport) Send(ctx context.Context, event *TelemetryEvent) SendResult {
|
|
url := t.apiBaseURL + "/api/v1/events"
|
|
|
|
body, err := json.Marshal(t.serializeEvent(event))
|
|
if err != nil {
|
|
return SendResult{Success: false, Error: err.Error()}
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
|
|
if err != nil {
|
|
return SendResult{Success: false, Error: err.Error()}
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-Public-Key", t.publicKey)
|
|
|
|
resp, err := t.client.Do(req)
|
|
if err != nil {
|
|
if t.debug {
|
|
fmt.Printf("[IronTelemetry] Failed to send event: %v\n", err)
|
|
}
|
|
return SendResult{Success: false, Error: err.Error()}
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 400 {
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
errMsg := fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(bodyBytes))
|
|
if t.debug {
|
|
fmt.Printf("[IronTelemetry] Failed to send event: %s\n", errMsg)
|
|
}
|
|
return SendResult{Success: false, Error: errMsg}
|
|
}
|
|
|
|
var result struct {
|
|
EventID string `json:"eventId"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
// Even if we can't decode, the request succeeded
|
|
result.EventID = event.EventID
|
|
}
|
|
|
|
if t.debug {
|
|
fmt.Printf("[IronTelemetry] Event sent successfully: %s\n", event.EventID)
|
|
}
|
|
|
|
return SendResult{
|
|
Success: true,
|
|
EventID: result.EventID,
|
|
}
|
|
}
|
|
|
|
// IsOnline checks if the server is reachable
|
|
func (t *Transport) IsOnline(ctx context.Context) bool {
|
|
url := t.apiBaseURL + "/api/v1/health"
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
req.Header.Set("X-Public-Key", t.publicKey)
|
|
|
|
resp, err := t.client.Do(req)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode == 200
|
|
}
|
|
|
|
func (t *Transport) serializeEvent(event *TelemetryEvent) map[string]any {
|
|
breadcrumbs := make([]map[string]any, len(event.Breadcrumbs))
|
|
for i, b := range event.Breadcrumbs {
|
|
breadcrumbs[i] = map[string]any{
|
|
"timestamp": b.Timestamp.Format(time.RFC3339),
|
|
"category": b.Category,
|
|
"message": b.Message,
|
|
"level": b.Level,
|
|
"data": b.Data,
|
|
}
|
|
}
|
|
|
|
result := map[string]any{
|
|
"eventId": event.EventID,
|
|
"timestamp": event.Timestamp.Format(time.RFC3339),
|
|
"level": event.Level,
|
|
"message": event.Message,
|
|
"tags": event.Tags,
|
|
"extra": event.Extra,
|
|
"breadcrumbs": breadcrumbs,
|
|
"environment": event.Environment,
|
|
"appVersion": event.AppVersion,
|
|
"platform": map[string]any{
|
|
"name": event.Platform.Name,
|
|
"version": event.Platform.Version,
|
|
"os": event.Platform.OS,
|
|
},
|
|
}
|
|
|
|
if event.Exception != nil {
|
|
stacktrace := make([]map[string]any, len(event.Exception.Stacktrace))
|
|
for i, f := range event.Exception.Stacktrace {
|
|
stacktrace[i] = map[string]any{
|
|
"function": f.Function,
|
|
"filename": f.Filename,
|
|
"lineno": f.Lineno,
|
|
}
|
|
}
|
|
result["exception"] = map[string]any{
|
|
"type": event.Exception.Type,
|
|
"message": event.Exception.Message,
|
|
"stacktrace": stacktrace,
|
|
}
|
|
}
|
|
|
|
if event.User != nil {
|
|
result["user"] = map[string]any{
|
|
"id": event.User.ID,
|
|
"email": event.User.Email,
|
|
"name": event.User.Name,
|
|
"data": event.User.Data,
|
|
}
|
|
}
|
|
|
|
if event.Journey != nil {
|
|
result["journey"] = map[string]any{
|
|
"journeyId": event.Journey.JourneyID,
|
|
"name": event.Journey.Name,
|
|
"currentStep": event.Journey.CurrentStep,
|
|
"startedAt": event.Journey.StartedAt.Format(time.RFC3339),
|
|
"metadata": event.Journey.Metadata,
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|