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 }