irontelemetry-go/journey.go

228 lines
4.7 KiB
Go

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
}