status.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package status
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "sync"
  7. "time"
  8. "github.com/sst/opencode/internal/pubsub"
  9. )
  10. type Level string
  11. const (
  12. LevelInfo Level = "info"
  13. LevelWarn Level = "warn"
  14. LevelError Level = "error"
  15. LevelDebug Level = "debug"
  16. )
  17. type StatusMessage struct {
  18. Level Level `json:"level"`
  19. Message string `json:"message"`
  20. Timestamp time.Time `json:"timestamp"`
  21. Critical bool `json:"critical"`
  22. Duration time.Duration `json:"duration"`
  23. }
  24. // StatusOption is a function that configures a status message
  25. type StatusOption func(*StatusMessage)
  26. // WithCritical marks a status message as critical, causing it to be displayed immediately
  27. func WithCritical(critical bool) StatusOption {
  28. return func(msg *StatusMessage) {
  29. msg.Critical = critical
  30. }
  31. }
  32. // WithDuration sets a custom display duration for a status message
  33. func WithDuration(duration time.Duration) StatusOption {
  34. return func(msg *StatusMessage) {
  35. msg.Duration = duration
  36. }
  37. }
  38. const (
  39. EventStatusPublished pubsub.EventType = "status_published"
  40. )
  41. type Service interface {
  42. pubsub.Subscriber[StatusMessage]
  43. Info(message string, opts ...StatusOption)
  44. Warn(message string, opts ...StatusOption)
  45. Error(message string, opts ...StatusOption)
  46. Debug(message string, opts ...StatusOption)
  47. }
  48. type service struct {
  49. broker *pubsub.Broker[StatusMessage]
  50. mu sync.RWMutex
  51. }
  52. var globalStatusService *service
  53. func InitService() error {
  54. if globalStatusService != nil {
  55. return fmt.Errorf("status service already initialized")
  56. }
  57. broker := pubsub.NewBroker[StatusMessage]()
  58. globalStatusService = &service{
  59. broker: broker,
  60. }
  61. return nil
  62. }
  63. func GetService() Service {
  64. if globalStatusService == nil {
  65. panic("status service not initialized. Call status.InitService() at application startup.")
  66. }
  67. return globalStatusService
  68. }
  69. func (s *service) Info(message string, opts ...StatusOption) {
  70. s.publish(LevelInfo, message, opts...)
  71. slog.Info(message)
  72. }
  73. func (s *service) Warn(message string, opts ...StatusOption) {
  74. s.publish(LevelWarn, message, opts...)
  75. slog.Warn(message)
  76. }
  77. func (s *service) Error(message string, opts ...StatusOption) {
  78. s.publish(LevelError, message, opts...)
  79. slog.Error(message)
  80. }
  81. func (s *service) Debug(message string, opts ...StatusOption) {
  82. s.publish(LevelDebug, message, opts...)
  83. slog.Debug(message)
  84. }
  85. func (s *service) publish(level Level, messageText string, opts ...StatusOption) {
  86. statusMsg := StatusMessage{
  87. Level: level,
  88. Message: messageText,
  89. Timestamp: time.Now(),
  90. }
  91. // Apply all options
  92. for _, opt := range opts {
  93. opt(&statusMsg)
  94. }
  95. s.broker.Publish(EventStatusPublished, statusMsg)
  96. }
  97. func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[StatusMessage] {
  98. return s.broker.Subscribe(ctx)
  99. }
  100. func Info(message string, opts ...StatusOption) {
  101. GetService().Info(message, opts...)
  102. }
  103. func Warn(message string, opts ...StatusOption) {
  104. GetService().Warn(message, opts...)
  105. }
  106. func Error(message string, opts ...StatusOption) {
  107. GetService().Error(message, opts...)
  108. }
  109. func Debug(message string, opts ...StatusOption) {
  110. GetService().Debug(message, opts...)
  111. }
  112. func Subscribe(ctx context.Context) <-chan pubsub.Event[StatusMessage] {
  113. return GetService().Subscribe(ctx)
  114. }