flow_chat 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.cliff.toml +74 -0
  3. data/.github/workflows/ci.yml +2 -3
  4. data/.github/workflows/release.yml +56 -0
  5. data/.standard.yml +4 -0
  6. data/CHANGELOG.md +22 -0
  7. data/CLAUDE.md +327 -0
  8. data/CONTRIBUTING.md +134 -0
  9. data/Gemfile +1 -0
  10. data/README.md +290 -105
  11. data/Rakefile +5 -1
  12. data/SECURITY.md +42 -349
  13. data/docs/architecture.md +510 -0
  14. data/docs/async-background-processing.md +298 -0
  15. data/docs/configuration.md +556 -226
  16. data/docs/factory-pattern.md +355 -0
  17. data/docs/gateway-context-variables.md +171 -0
  18. data/docs/gateway-development.md +723 -0
  19. data/docs/getting-started.md +429 -0
  20. data/docs/instrumentation.md +264 -153
  21. data/docs/platforms/telegram.md +1013 -0
  22. data/docs/platforms/ussd.md +693 -0
  23. data/docs/platforms/whatsapp.md +1395 -0
  24. data/docs/testing.md +243 -365
  25. data/examples/custom_session_id_example.rb +119 -0
  26. data/examples/http_controller.rb +154 -0
  27. data/examples/intercom_configuration_example.rb +118 -0
  28. data/examples/intercom_controller.rb +194 -0
  29. data/examples/multi_tenant_whatsapp_controller.rb +4 -4
  30. data/examples/simulator_controller.rb +21 -1
  31. data/examples/ussd_controller.rb +10 -10
  32. data/examples/whatsapp_controller.rb +2 -2
  33. data/flow_chat.gemspec +4 -0
  34. data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
  35. data/lib/flow_chat/async_job.rb +176 -0
  36. data/lib/flow_chat/config.rb +23 -28
  37. data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
  38. data/lib/flow_chat/factory.rb +94 -0
  39. data/lib/flow_chat/gateway_async_support.rb +106 -0
  40. data/lib/flow_chat/generic_async_job.rb +30 -0
  41. data/lib/flow_chat/http/gateway/simple.rb +125 -0
  42. data/lib/flow_chat/http/renderer.rb +41 -0
  43. data/lib/flow_chat/instrumentation/setup.rb +1 -1
  44. data/lib/flow_chat/instrumentation.rb +25 -0
  45. data/lib/flow_chat/intercom/client.rb +155 -0
  46. data/lib/flow_chat/intercom/configuration.rb +149 -0
  47. data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
  48. data/lib/flow_chat/intercom/renderer.rb +71 -0
  49. data/lib/flow_chat/phone_number_util.rb +49 -0
  50. data/lib/flow_chat/processor.rb +188 -0
  51. data/lib/flow_chat/renderers/markdown_support.rb +58 -0
  52. data/lib/flow_chat/session/cache_session_store.rb +1 -17
  53. data/lib/flow_chat/session/middleware.rb +43 -26
  54. data/lib/flow_chat/simulator/controller.rb +17 -5
  55. data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
  56. data/lib/flow_chat/telegram/client.rb +240 -0
  57. data/lib/flow_chat/telegram/configuration.rb +118 -0
  58. data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
  59. data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
  60. data/lib/flow_chat/telegram/renderer.rb +125 -0
  61. data/lib/flow_chat/telegram.rb +7 -0
  62. data/lib/flow_chat/ussd/gateway/nalo.rb +27 -11
  63. data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
  64. data/lib/flow_chat/ussd/renderer.rb +1 -1
  65. data/lib/flow_chat/version.rb +1 -1
  66. data/lib/flow_chat/whatsapp/client.rb +144 -13
  67. data/lib/flow_chat/whatsapp/configuration.rb +1 -1
  68. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +144 -106
  69. data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
  70. data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
  71. data/lib/flow_chat/whatsapp/renderer.rb +145 -11
  72. data/lib/flow_chat.rb +11 -1
  73. data/lib/tasks/release.rake +165 -0
  74. metadata +88 -21
  75. data/docs/flows.md +0 -320
  76. data/docs/images/simulator.png +0 -0
  77. data/docs/media.md +0 -153
  78. data/docs/sessions.md +0 -433
  79. data/docs/ussd-setup.md +0 -322
  80. data/docs/whatsapp-setup.md +0 -162
  81. data/examples/whatsapp_message_job.rb +0 -113
  82. data/lib/flow_chat/base_processor.rb +0 -145
  83. data/lib/flow_chat/session/rails_session_store.rb +0 -68
  84. data/lib/flow_chat/ussd/app.rb +0 -6
  85. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -98
  86. data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
  87. data/lib/flow_chat/ussd/processor.rb +0 -39
  88. data/lib/flow_chat/whatsapp/app.rb +0 -29
  89. data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
  90. data/lib/flow_chat/whatsapp/processor.rb +0 -32
  91. data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
@@ -1,216 +1,327 @@
1
- # Instrumentation & Monitoring
1
+ # Instrumentation
2
2
 
3
- FlowChat includes a comprehensive instrumentation system for observability, monitoring, and logging.
3
+ FlowChat provides comprehensive instrumentation via ActiveSupport::Notifications, enabling monitoring, logging, metrics collection, and error tracking across all platforms.
4
4
 
5
- ## Quick Setup
6
-
7
- Enable instrumentation in your Rails application:
5
+ ## Quick Start
8
6
 
9
7
  ```ruby
10
- # config/initializers/flowchat.rb
11
- FlowChat.setup_instrumentation!
8
+ # Subscribe to all FlowChat events
9
+ ActiveSupport::Notifications.subscribe(/\.flow_chat$/) do |event|
10
+ Rails.logger.info "FlowChat: #{event.name} - #{event.payload}"
11
+ end
12
+
13
+ # Subscribe to specific events
14
+ ActiveSupport::Notifications.subscribe("api.error.flow_chat") do |event|
15
+ Sentry.capture_message("API Error", extra: event.payload)
16
+ end
12
17
  ```
13
18
 
14
- This sets up:
15
- - 📊 **Metrics Collection** - Performance and usage metrics
16
- - 📝 **Structured Logging** - Event-driven logs with context
17
- - 🔍 **Event Tracking** - All framework events instrumented
18
- - ⚡ **Performance Monitoring** - Execution timing and bottleneck detection
19
+ ## Event Reference
20
+
21
+ All events are namespaced with `.flow_chat` suffix. Use `FlowChat::Instrumentation::Events` constants for consistency.
19
22
 
20
- ## Features
23
+ ### Core Framework Events
21
24
 
22
- **🎯 Zero Configuration**
23
- - Works out of the box with Rails applications
24
- - Automatic ActiveSupport::Notifications integration
25
- - Thread-safe metrics collection
25
+ | Event | Description | Key Payload |
26
+ |-------|-------------|-------------|
27
+ | `flow.execution.start` | Flow method begins executing | `flow_name`, `action`, `session_id` |
28
+ | `flow.execution.end` | Flow method completes | `flow_name`, `action`, `duration` |
29
+ | `flow.execution.error` | Unhandled error in flow | `flow_name`, `error`, `backtrace` |
30
+ | `context.created` | New request context created | `request_id`, `platform` |
26
31
 
27
- **📈 Comprehensive Metrics**
28
- - Flow execution counts and timing
29
- - Session creation/destruction rates
30
- - WhatsApp/USSD message volumes
31
- - Cache hit/miss ratios
32
- - Error tracking by type and flow
32
+ ### Session Events
33
33
 
34
- **🔍 Rich Event Tracking**
35
- - 20+ predefined event types (see [Event Types](#event-types))
36
- - Automatic context enrichment (session ID, flow name, gateway)
37
- - Structured event payloads
34
+ | Event | Description | Key Payload |
35
+ |-------|-------------|-------------|
36
+ | `session.created` | New session started | `session_id`, `boundaries` |
37
+ | `session.destroyed` | Session ended/cleared | `session_id` |
38
+ | `session.data.get` | Session data read | `session_id`, `key` |
39
+ | `session.data.set` | Session data written | `session_id`, `key` |
40
+ | `session.cache.hit` | Session found in cache | `session_id` |
41
+ | `session.cache.miss` | Session not in cache | `session_id` |
38
42
 
39
- **📊 Production Ready**
40
- - Minimal performance overhead
41
- - Thread-safe operations
42
- - Graceful error handling
43
+ ### Messaging Events
43
44
 
44
- ## Event Types
45
+ | Event | Description | Key Payload |
46
+ |-------|-------------|-------------|
47
+ | `message.received` | Inbound message from user | `platform`, `from`, `message_type` |
48
+ | `message.sent` | Outbound message to user | `platform`, `to`, `message_type`, `content_length` |
49
+ | `media.upload` | Media file uploaded (WhatsApp) | `platform`, `filename`, `mime_type`, `size` |
50
+ | `pagination.triggered` | USSD pagination activated | `session_id`, `page`, `total_pages` |
45
51
 
46
- FlowChat instruments the following events:
52
+ ### Webhook Events
47
53
 
48
- ### Flow Events
49
- - `flow.execution.start.flow_chat`
50
- - `flow.execution.end.flow_chat`
51
- - `flow.execution.error.flow_chat`
54
+ | Event | Description | Key Payload |
55
+ |-------|-------------|-------------|
56
+ | `webhook.verified` | Webhook signature valid (WhatsApp) | `platform`, `gateway` |
57
+ | `webhook.failed` | Webhook validation failed (WhatsApp) | `platform`, `reason` |
52
58
 
53
- ### Session Events
54
- - `session.created.flow_chat`
55
- - `session.destroyed.flow_chat`
56
- - `session.data.get.flow_chat`
57
- - `session.data.set.flow_chat`
58
- - `session.cache.hit.flow_chat`
59
- - `session.cache.miss.flow_chat`
59
+ ### API Events
60
60
 
61
- ### WhatsApp Events
62
- - `whatsapp.message.received.flow_chat`
63
- - `whatsapp.message.sent.flow_chat`
64
- - `whatsapp.webhook.verified.flow_chat`
65
- - `whatsapp.api.request.flow_chat`
66
- - `whatsapp.media.upload.flow_chat`
61
+ | Event | Description | Key Payload |
62
+ |-------|-------------|-------------|
63
+ | `api.error` | API call failed | `platform`, `message`, error details |
67
64
 
68
- ### USSD Events
69
- - `ussd.message.received.flow_chat`
70
- - `ussd.message.sent.flow_chat`
71
- - `ussd.pagination.triggered.flow_chat`
65
+ ### Events for Custom Use
72
66
 
73
- ## Usage Examples
67
+ The following event constants are provided for use in your own instrumentation but are not emitted by FlowChat internally:
74
68
 
75
- ### Access Metrics
69
+ | Event | Suggested Use | Key Payload |
70
+ |-------|---------------|-------------|
71
+ | `api.request` | Wrap outbound API calls | `platform`, `endpoint` |
72
+ | `middleware.before` | Custom middleware entry | `middleware_class` |
73
+ | `middleware.after` | Custom middleware exit | `middleware_class`, `duration` |
74
+ | `conversation.assigned` | Intercom assignment tracking | `conversation_id`, `admin_id` |
75
+ | `conversation.tagged` | Intercom tag tracking | `conversation_id`, `tag` |
76
+ | `conversation.state_changed` | Intercom state tracking | `conversation_id`, `state` |
76
77
 
78
+ ## API Error Instrumentation
79
+
80
+ The `api.error` event provides detailed information when API calls fail. This is useful for monitoring, alerting, and debugging integration issues.
81
+
82
+ ### Event Payload by Platform
83
+
84
+ **Telegram:**
77
85
  ```ruby
78
- # Get current metrics snapshot
79
- metrics = FlowChat.metrics.snapshot
80
-
81
- # Flow execution metrics
82
- flow_metrics = FlowChat.metrics.get_category("flows")
83
- puts flow_metrics["flows.executed"] # Total flows executed
84
- puts flow_metrics["flows.execution_time"] # Average execution time
85
-
86
- # Session metrics
87
- session_metrics = FlowChat.metrics.get_category("sessions")
88
- puts session_metrics["sessions.created"] # Total sessions created
89
- puts session_metrics["sessions.cache.hits"] # Cache hit count
86
+ {
87
+ platform: :telegram,
88
+ message: "Telegram API error: Unauthorized",
89
+ bot_id: "123456789",
90
+ api_method: "sendMessage",
91
+ error_code: 401,
92
+ error_description: "Unauthorized",
93
+ chat_id: 987654321
94
+ }
90
95
  ```
91
96
 
92
- ### Custom Instrumentation in Flows
97
+ **WhatsApp:**
98
+ ```ruby
99
+ {
100
+ platform: :whatsapp,
101
+ message: "WhatsApp API request failed",
102
+ phone_number_id: "123456789",
103
+ recipient: "+1234567890",
104
+ message_type: "text",
105
+ response_code: "401",
106
+ error_type: "OAuthException",
107
+ error_code: 190,
108
+ error_subcode: 463,
109
+ error_message: "Error validating access token"
110
+ }
111
+ ```
93
112
 
113
+ **Intercom:**
94
114
  ```ruby
95
- class PaymentFlow < FlowChat::Flow
96
- def process_payment
97
- # Instrument custom events in your flows
98
- instrument("payment.started", {
99
- amount: payment_amount,
100
- currency: "USD",
101
- payment_method: "mobile_money"
102
- }) do
103
- # Payment processing logic
104
- result = process_mobile_money_payment
105
-
106
- # Event automatically includes session_id, flow_name, gateway
107
- result
108
- end
109
- end
110
- end
115
+ {
116
+ platform: :intercom,
117
+ message: "Intercom authentication failed",
118
+ app_id: "abc123def",
119
+ conversation_id: "conv_123",
120
+ admin_id: "admin_456"
121
+ }
111
122
  ```
112
123
 
113
- ### Event Subscribers
124
+ ### Error Handling Behavior
114
125
 
115
- ```ruby
116
- # config/initializers/flowchat_instrumentation.rb
117
- FlowChat.setup_instrumentation!
126
+ | Error Type | Telegram | WhatsApp | Intercom |
127
+ |------------|----------|----------|----------|
128
+ | Invalid credentials | Returns `{"ok"=>false}` | Returns `nil` | Raises `ConfigurationError` |
129
+ | API error response | Returns error hash | Returns `nil` | Returns `nil` |
130
+ | Network timeout | Re-raises exception | Re-raises exception | Re-raises exception |
131
+ | Connection refused | Returns `{"ok"=>false}` | Returns `nil` | Returns `nil` |
118
132
 
119
- # Subscribe to specific events
120
- ActiveSupport::Notifications.subscribe("flow.execution.end.flow_chat") do |event|
121
- duration = event.duration
122
- flow_name = event.payload[:flow_name]
123
-
124
- # Send to external monitoring service
125
- ExternalMonitoring.track_flow_execution(flow_name, duration)
126
- end
133
+ **Note:** Network timeouts (`Net::OpenTimeout`, `Net::ReadTimeout`) are intentionally re-raised without instrumentation, allowing callers to implement retry logic at a higher level.
127
134
 
128
- # Subscribe to all FlowChat events
129
- ActiveSupport::Notifications.subscribe(/\.flow_chat$/) do |name, start, finish, id, payload|
130
- CustomLogger.log_event(name, payload.merge(duration: finish - start))
135
+ ### Example: Error Monitoring
136
+
137
+ ```ruby
138
+ # config/initializers/flow_chat_monitoring.rb
139
+
140
+ ActiveSupport::Notifications.subscribe("api.error.flow_chat") do |event|
141
+ payload = event.payload
142
+
143
+ # Log with structured data
144
+ Rails.logger.error({
145
+ event: "flow_chat.api_error",
146
+ platform: payload[:platform],
147
+ message: payload[:message],
148
+ error_code: payload[:error_code],
149
+ recipient: payload[:recipient] || payload[:chat_id] || payload[:conversation_id]
150
+ }.to_json)
151
+
152
+ # Send to error tracking service
153
+ Sentry.capture_message(
154
+ "FlowChat API Error: #{payload[:message]}",
155
+ level: :error,
156
+ extra: payload
157
+ )
158
+
159
+ # Increment metrics
160
+ StatsD.increment("flow_chat.api_error", tags: ["platform:#{payload[:platform]}"])
131
161
  end
132
162
  ```
133
163
 
134
- ### Integration with Monitoring Services
164
+ ### Example: Rails Error Reporting
165
+
166
+ FlowChat automatically reports errors to `Rails.error` when available (Rails 7+):
135
167
 
136
168
  ```ruby
137
- # config/initializers/flowchat_monitoring.rb
138
- FlowChat.setup_instrumentation!
139
-
140
- # Export metrics to Prometheus, StatsD, etc.
141
- ActiveSupport::Notifications.subscribe("flow.execution.end.flow_chat") do |event|
142
- StatsD.increment("flowchat.flows.executed")
143
- StatsD.timing("flowchat.flows.duration", event.duration)
144
- StatsD.increment("flowchat.flows.#{event.payload[:flow_name]}.executed")
145
- end
169
+ # Errors are automatically reported with context:
170
+ Rails.error.report(exception, handled: true, context: {
171
+ platform: :whatsapp,
172
+ recipient: "+1234567890",
173
+ message_type: "text"
174
+ })
175
+ ```
176
+
177
+ You can subscribe to these in your error reporting configuration:
178
+
179
+ ```ruby
180
+ # config/initializers/error_reporting.rb
181
+ Rails.error.subscribe(MyErrorReporter.new)
182
+ ```
183
+
184
+ ## Custom Instrumentation
185
+
186
+ ### In Flows
146
187
 
147
- # Track error rates
148
- ActiveSupport::Notifications.subscribe("flow.execution.error.flow_chat") do |event|
149
- StatsD.increment("flowchat.flows.errors")
150
- StatsD.increment("flowchat.flows.errors.#{event.payload[:error_class]}")
188
+ ```ruby
189
+ class PaymentFlow < FlowChat::Flow
190
+ def process_payment
191
+ amount = app.screen(:amount) { |p| p.ask "Enter amount:" }
192
+
193
+ instrument(Events::API_REQUEST, { endpoint: "payment_gateway" }) do
194
+ result = PaymentGateway.charge(amount)
195
+
196
+ if result.success?
197
+ app.say "Payment successful!"
198
+ else
199
+ instrument(Events::API_ERROR, {
200
+ message: "Payment failed",
201
+ error_code: result.error_code
202
+ })
203
+ app.say "Payment failed: #{result.error}"
204
+ end
205
+ end
206
+ end
151
207
  end
152
208
  ```
153
209
 
154
- ## Performance Impact
210
+ ### In Custom Middleware
155
211
 
156
- The instrumentation system is designed for production use with minimal overhead:
212
+ ```ruby
213
+ class MetricsMiddleware
214
+ include FlowChat::Instrumentation
215
+
216
+ def initialize(app)
217
+ @app = app
218
+ end
157
219
 
158
- - **Event Publishing**: ~0.1ms per event
159
- - **Metrics Collection**: Thread-safe atomic operations
160
- - **Memory Usage**: <1MB for typical applications
161
- - **Storage**: Events are ephemeral, metrics are kept in memory
220
+ def call(context)
221
+ @context = context
162
222
 
163
- ## Debugging & Troubleshooting
223
+ instrument(Events::MIDDLEWARE_BEFORE, { middleware_class: self.class.name })
164
224
 
165
- ### Enable Debug Logging
225
+ start_time = Time.current
226
+ result = @app.call(context)
227
+ duration = Time.current - start_time
166
228
 
167
- ```ruby
168
- # config/environments/development.rb
169
- config.log_level = :debug
229
+ instrument(Events::MIDDLEWARE_AFTER, {
230
+ middleware_class: self.class.name,
231
+ duration: duration
232
+ })
233
+
234
+ result
235
+ end
236
+
237
+ # Required for context enrichment
238
+ attr_reader :context
239
+ end
170
240
  ```
171
241
 
172
- ### Reset Metrics
242
+ ### Module-Level Instrumentation
173
243
 
174
244
  ```ruby
175
- # Clear all metrics (useful for testing)
176
- FlowChat.metrics.reset!
245
+ # Direct instrumentation without including the module
246
+ FlowChat::Instrumentation.instrument("custom.event", {
247
+ custom_key: "custom_value"
248
+ })
177
249
  ```
178
250
 
179
- ### Check Event Subscribers
251
+ ## Payload Enrichment
252
+
253
+ When instrumenting from objects with a `context` accessor, payloads are automatically enriched with:
254
+
255
+ - `request_id` - Unique request identifier
256
+ - `session_id` - Current session ID
257
+ - `flow_name` - Active flow class name
258
+ - `gateway` - Gateway handling the request
259
+ - `platform` - Platform (`:ussd`, `:whatsapp`, `:telegram`, `:intercom`)
260
+
261
+ All payloads also include:
262
+ - `timestamp` - Event timestamp (`Time.current`)
263
+
264
+ ## Subscribing to Events
265
+
266
+ ### Pattern Matching
180
267
 
181
268
  ```ruby
182
- # See all active subscribers
183
- ActiveSupport::Notifications.notifier.listeners_for("flow.execution.end.flow_chat")
269
+ # All FlowChat events
270
+ ActiveSupport::Notifications.subscribe(/\.flow_chat$/) { |event| ... }
271
+
272
+ # All error events
273
+ ActiveSupport::Notifications.subscribe(/error\.flow_chat$/) { |event| ... }
274
+
275
+ # All session events
276
+ ActiveSupport::Notifications.subscribe(/^session\..*\.flow_chat$/) { |event| ... }
184
277
  ```
185
278
 
186
- ## Testing Instrumentation
279
+ ### Block vs Callable
187
280
 
188
281
  ```ruby
189
- # test/test_helper.rb
190
- class ActiveSupport::TestCase
191
- setup do
192
- # Reset metrics before each test
193
- FlowChat::Instrumentation::Setup.reset! if FlowChat::Instrumentation::Setup.setup?
282
+ # Block form (simple)
283
+ ActiveSupport::Notifications.subscribe("message.sent.flow_chat") do |event|
284
+ puts event.payload
285
+ end
286
+
287
+ # Callable form (for complex subscribers)
288
+ class MessageLogger
289
+ def call(event)
290
+ # Access timing info
291
+ puts "Duration: #{event.duration}ms"
292
+ puts "Payload: #{event.payload}"
194
293
  end
195
294
  end
196
295
 
197
- # In your tests
296
+ ActiveSupport::Notifications.subscribe("message.sent.flow_chat", MessageLogger.new)
297
+ ```
298
+
299
+ ## Testing
300
+
301
+ ```ruby
198
302
  class FlowInstrumentationTest < ActiveSupport::TestCase
199
- test "flow execution is instrumented" do
303
+ def test_api_error_instrumentation
200
304
  events = []
201
-
202
- # Capture events
203
- ActiveSupport::Notifications.subscribe(/flow_chat$/) do |name, start, finish, id, payload|
204
- events << { name: name, payload: payload, duration: (finish - start) * 1000 }
305
+
306
+ ActiveSupport::Notifications.subscribe("api.error.flow_chat") do |event|
307
+ events << event
205
308
  end
206
-
207
- # Execute flow
208
- processor.run(WelcomeFlow, :main_page)
209
-
210
- # Verify events
211
- assert_equal 2, events.size
212
- assert_equal "flow.execution.start.flow_chat", events[0][:name]
213
- assert_equal "flow.execution.end.flow_chat", events[1][:name]
214
- assert_equal "welcome_flow", events[0][:payload][:flow_name]
309
+
310
+ # Trigger the error condition
311
+ client.send_message_with_invalid_token
312
+
313
+ assert_equal 1, events.size
314
+ assert_equal :whatsapp, events.first.payload[:platform]
315
+ ensure
316
+ ActiveSupport::Notifications.unsubscribe("api.error.flow_chat")
215
317
  end
216
- end
318
+ end
319
+ ```
320
+
321
+ ## Production Recommendations
322
+
323
+ 1. **Subscribe early** - Set up subscriptions in initializers before requests arrive
324
+ 2. **Keep handlers fast** - Use async processing for slow operations (logging to external services)
325
+ 3. **Filter events** - Only subscribe to events you need to avoid overhead
326
+ 4. **Use structured logging** - Log payloads as JSON for easier querying
327
+ 5. **Set up alerting** - Configure alerts on `api.error` events for proactive monitoring