package irontelemetry import ( "sync" "time" "github.com/google/uuid" ) // JourneyManager manages journey context type JourneyManager struct { mu sync.RWMutex current *JourneyContext client *Client } // NewJourneyManager creates a new JourneyManager func NewJourneyManager(client *Client) *JourneyManager { return &JourneyManager{ client: client, } } // StartJourney starts a new journey func (jm *JourneyManager) StartJourney(name string) *Journey { jm.mu.Lock() defer jm.mu.Unlock() ctx := &JourneyContext{ JourneyID: uuid.New().String(), Name: name, StartedAt: time.Now(), Metadata: make(map[string]any), } jm.current = ctx // Add breadcrumb for journey start if jm.client != nil { jm.client.AddBreadcrumb("Started journey: "+name, CategoryBusiness) } return &Journey{ manager: jm, context: ctx, } } // GetCurrent returns the current journey context func (jm *JourneyManager) GetCurrent() *JourneyContext { jm.mu.RLock() defer jm.mu.RUnlock() return jm.current } // Clear clears the current journey func (jm *JourneyManager) Clear() { jm.mu.Lock() defer jm.mu.Unlock() jm.current = nil } // Journey represents an active journey type Journey struct { manager *JourneyManager context *JourneyContext } // SetUser sets the user for the journey func (j *Journey) SetUser(id, email, name string) *Journey { j.manager.mu.Lock() defer j.manager.mu.Unlock() j.context.Metadata["userId"] = id if email != "" { j.context.Metadata["userEmail"] = email } if name != "" { j.context.Metadata["userName"] = name } return j } // SetMetadata sets metadata for the journey func (j *Journey) SetMetadata(key string, value any) *Journey { j.manager.mu.Lock() defer j.manager.mu.Unlock() j.context.Metadata[key] = value return j } // StartStep starts a new step in the journey func (j *Journey) StartStep(name string, category BreadcrumbCategory) *Step { j.manager.mu.Lock() j.context.CurrentStep = name j.manager.mu.Unlock() // Add breadcrumb for step start if j.manager.client != nil { j.manager.client.AddBreadcrumbWithData( "Started step: "+name, category, SeverityInfo, map[string]any{ "journeyId": j.context.JourneyID, "journeyName": j.context.Name, }, ) } return &Step{ journey: j, name: name, category: category, startedAt: time.Now(), data: make(map[string]any), } } // Complete completes the journey successfully func (j *Journey) Complete() { if j.manager.client != nil { j.manager.client.AddBreadcrumbWithData( "Completed journey: "+j.context.Name, CategoryBusiness, SeverityInfo, map[string]any{ "journeyId": j.context.JourneyID, "duration": time.Since(j.context.StartedAt).Milliseconds(), }, ) } j.manager.Clear() } // Fail marks the journey as failed func (j *Journey) Fail(err error) { if j.manager.client != nil { data := map[string]any{ "journeyId": j.context.JourneyID, "duration": time.Since(j.context.StartedAt).Milliseconds(), } if err != nil { data["error"] = err.Error() } j.manager.client.AddBreadcrumbWithData( "Failed journey: "+j.context.Name, CategoryBusiness, SeverityError, data, ) } j.manager.Clear() } // Step represents a step within a journey type Step struct { journey *Journey name string category BreadcrumbCategory startedAt time.Time data map[string]any } // SetData sets data for the step func (s *Step) SetData(key string, value any) *Step { s.data[key] = value return s } // Complete completes the step successfully func (s *Step) Complete() { if s.journey.manager.client != nil { data := map[string]any{ "journeyId": s.journey.context.JourneyID, "journeyName": s.journey.context.Name, "duration": time.Since(s.startedAt).Milliseconds(), } for k, v := range s.data { data[k] = v } s.journey.manager.client.AddBreadcrumbWithData( "Completed step: "+s.name, s.category, SeverityInfo, data, ) } } // Fail marks the step as failed func (s *Step) Fail(err error) { if s.journey.manager.client != nil { data := map[string]any{ "journeyId": s.journey.context.JourneyID, "journeyName": s.journey.context.Name, "duration": time.Since(s.startedAt).Milliseconds(), } for k, v := range s.data { data[k] = v } if err != nil { data["error"] = err.Error() } s.journey.manager.client.AddBreadcrumbWithData( "Failed step: "+s.name, s.category, SeverityError, data, ) } } // RunStep is a helper that runs a function as a step func (j *Journey) RunStep(name string, category BreadcrumbCategory, fn func() error) error { step := j.StartStep(name, category) err := fn() if err != nil { step.Fail(err) return err } step.Complete() return nil }