smart_message 0.0.3 → 0.0.4
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/docs/README.md +1 -0
- data/docs/logging.md +452 -0
- data/examples/.gitignore +2 -0
- data/examples/06_custom_logger_example.rb +620 -0
- data/lib/smart_message/logger/default.rb +217 -0
- data/lib/smart_message/logger.rb +9 -1
- data/lib/smart_message/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de5fce8e9cb793850301df5fdb9ad8a6954cf3a481cadc2deb625c373706723a
|
4
|
+
data.tar.gz: 0cc8067011377f0d574bc7f9bf1168ac2176fad7c7981cbd43f708e622a98fa0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf7d1fc90d41bdbd3a8661f1f3e294915d29f267a68c13869926a4f829a00338f3878b03e14f26a7ccc9b248dc55b566e9988cb67aca7f6de596b94a51b5e885
|
7
|
+
data.tar.gz: 51c784d4183fe12862ff9250389a57233718604442e50303cdb941265971978e291fe7c3c1fec92509e6fa32bcc67b6b2fe8813b3cc1d06fa9d1f16645b181f3
|
data/Gemfile.lock
CHANGED
data/docs/README.md
CHANGED
@@ -18,6 +18,7 @@ Welcome to the comprehensive documentation for SmartMessage, a Ruby gem that abs
|
|
18
18
|
- [Property System](properties.md)
|
19
19
|
- [Transport Layer](transports.md)
|
20
20
|
- [Serializers](serializers.md)
|
21
|
+
- [Logging System](logging.md)
|
21
22
|
- [Dispatcher & Routing](dispatcher.md)
|
22
23
|
- [Message Headers](headers.md)
|
23
24
|
|
data/docs/logging.md
ADDED
@@ -0,0 +1,452 @@
|
|
1
|
+
# Logging in SmartMessage
|
2
|
+
|
3
|
+
SmartMessage provides flexible logging capabilities through its plugin architecture. This document covers the built-in default logger as well as how to create custom loggers.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Default Logger](#default-logger)
|
8
|
+
- [Configuration Options](#configuration-options)
|
9
|
+
- [Output Destinations](#output-destinations)
|
10
|
+
- [Log Levels](#log-levels)
|
11
|
+
- [Message Lifecycle Logging](#message-lifecycle-logging)
|
12
|
+
- [Rails Integration](#rails-integration)
|
13
|
+
- [Custom Loggers](#custom-loggers)
|
14
|
+
- [Examples](#examples)
|
15
|
+
|
16
|
+
## Default Logger
|
17
|
+
|
18
|
+
SmartMessage includes a built-in `SmartMessage::Logger::Default` class that automatically detects your environment and chooses the best logging approach:
|
19
|
+
|
20
|
+
- **Rails Applications**: Uses `Rails.logger` with tagged logging
|
21
|
+
- **Standalone Ruby**: Uses Ruby's standard `Logger` class with file output
|
22
|
+
|
23
|
+
### Quick Start
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class MyMessage < SmartMessage::Base
|
27
|
+
property :content
|
28
|
+
|
29
|
+
config do
|
30
|
+
transport SmartMessage::Transport::StdoutTransport.new
|
31
|
+
serializer SmartMessage::Serializer::JSON.new
|
32
|
+
logger SmartMessage::Logger::Default.new # Zero configuration!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Configuration Options
|
38
|
+
|
39
|
+
The default logger accepts several configuration options:
|
40
|
+
|
41
|
+
### Basic Configuration
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# Use defaults (Rails.logger or log/smart_message.log)
|
45
|
+
logger SmartMessage::Logger::Default.new
|
46
|
+
|
47
|
+
# Custom log file path
|
48
|
+
logger SmartMessage::Logger::Default.new(
|
49
|
+
log_file: '/var/log/my_app/messages.log'
|
50
|
+
)
|
51
|
+
|
52
|
+
# Custom log level
|
53
|
+
logger SmartMessage::Logger::Default.new(
|
54
|
+
level: Logger::DEBUG
|
55
|
+
)
|
56
|
+
|
57
|
+
# Both custom file and level
|
58
|
+
logger SmartMessage::Logger::Default.new(
|
59
|
+
log_file: 'logs/custom.log',
|
60
|
+
level: Logger::WARN
|
61
|
+
)
|
62
|
+
```
|
63
|
+
|
64
|
+
## Output Destinations
|
65
|
+
|
66
|
+
The default logger supports multiple output destinations:
|
67
|
+
|
68
|
+
### File Logging (Default)
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# Default file location (Rails convention)
|
72
|
+
logger SmartMessage::Logger::Default.new
|
73
|
+
# → log/smart_message.log
|
74
|
+
|
75
|
+
# Custom file path
|
76
|
+
logger SmartMessage::Logger::Default.new(
|
77
|
+
log_file: '/var/log/application/messages.log'
|
78
|
+
)
|
79
|
+
```
|
80
|
+
|
81
|
+
**Features:**
|
82
|
+
- Automatic log rotation (10 files, 10MB each)
|
83
|
+
- Directory creation if needed
|
84
|
+
- Timestamped entries with clean formatting
|
85
|
+
|
86
|
+
### STDOUT Logging
|
87
|
+
|
88
|
+
Perfect for containerized applications (Docker, Kubernetes):
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
logger SmartMessage::Logger::Default.new(
|
92
|
+
log_file: STDOUT,
|
93
|
+
level: Logger::INFO
|
94
|
+
)
|
95
|
+
```
|
96
|
+
|
97
|
+
### STDERR Logging
|
98
|
+
|
99
|
+
For error-focused logging:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
logger SmartMessage::Logger::Default.new(
|
103
|
+
log_file: STDERR,
|
104
|
+
level: Logger::WARN
|
105
|
+
)
|
106
|
+
```
|
107
|
+
|
108
|
+
### In-Memory Logging (Testing)
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
require 'stringio'
|
112
|
+
|
113
|
+
logger SmartMessage::Logger::Default.new(
|
114
|
+
log_file: StringIO.new,
|
115
|
+
level: Logger::DEBUG
|
116
|
+
)
|
117
|
+
```
|
118
|
+
|
119
|
+
## Log Levels
|
120
|
+
|
121
|
+
The default logger supports all standard Ruby log levels:
|
122
|
+
|
123
|
+
| Level | Numeric Value | Description |
|
124
|
+
|-------|--------------|-------------|
|
125
|
+
| `Logger::DEBUG` | 0 | Detailed debugging information |
|
126
|
+
| `Logger::INFO` | 1 | General information messages |
|
127
|
+
| `Logger::WARN` | 2 | Warning messages |
|
128
|
+
| `Logger::ERROR` | 3 | Error messages |
|
129
|
+
| `Logger::FATAL` | 4 | Fatal error messages |
|
130
|
+
|
131
|
+
### Environment-Based Defaults
|
132
|
+
|
133
|
+
The default logger automatically sets appropriate log levels based on your environment:
|
134
|
+
|
135
|
+
- **Rails Production**: `Logger::INFO`
|
136
|
+
- **Rails Test**: `Logger::ERROR`
|
137
|
+
- **Rails Development**: `Logger::DEBUG`
|
138
|
+
- **Non-Rails**: `Logger::INFO`
|
139
|
+
|
140
|
+
## Message Lifecycle Logging
|
141
|
+
|
142
|
+
The default logger automatically logs key events in the message lifecycle:
|
143
|
+
|
144
|
+
### Message Creation
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# Logged at DEBUG level
|
148
|
+
message = MyMessage.new(content: "Hello")
|
149
|
+
# → [DEBUG] [SmartMessage] Created: MyMessage - {content: "Hello"}
|
150
|
+
```
|
151
|
+
|
152
|
+
### Message Publishing
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
# Logged at INFO level
|
156
|
+
message.publish
|
157
|
+
# → [INFO] [SmartMessage] Published: MyMessage via StdoutTransport
|
158
|
+
```
|
159
|
+
|
160
|
+
### Message Reception
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
# Logged at INFO level when message is received
|
164
|
+
# → [INFO] [SmartMessage] Received: MyMessage (45 bytes)
|
165
|
+
```
|
166
|
+
|
167
|
+
### Message Processing
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
# Logged at INFO level after processing
|
171
|
+
# → [INFO] [SmartMessage] Processed: MyMessage - Success
|
172
|
+
```
|
173
|
+
|
174
|
+
### Subscription Management
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
# Logged at INFO level
|
178
|
+
MyMessage.subscribe
|
179
|
+
# → [INFO] [SmartMessage] Subscribed: MyMessage
|
180
|
+
|
181
|
+
MyMessage.unsubscribe
|
182
|
+
# → [INFO] [SmartMessage] Unsubscribed: MyMessage
|
183
|
+
```
|
184
|
+
|
185
|
+
### Error Logging
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# Logged at ERROR level with full stack trace (DEBUG level)
|
189
|
+
# → [ERROR] [SmartMessage] Error in message processing: RuntimeError - Something went wrong
|
190
|
+
# → [DEBUG] [SmartMessage] Backtrace: ...
|
191
|
+
```
|
192
|
+
|
193
|
+
## Rails Integration
|
194
|
+
|
195
|
+
When running in a Rails application, the default logger provides enhanced integration:
|
196
|
+
|
197
|
+
### Automatic Detection
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
# Automatically uses Rails.logger when available
|
201
|
+
logger SmartMessage::Logger::Default.new
|
202
|
+
```
|
203
|
+
|
204
|
+
### Tagged Logging
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
# In Rails, all SmartMessage logs are tagged
|
208
|
+
Rails.logger.tagged('SmartMessage') do
|
209
|
+
# All SmartMessage logging happens here
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
### Rails Log File Location
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
# Uses Rails.root/log/smart_message.log when Rails is detected
|
217
|
+
logger SmartMessage::Logger::Default.new
|
218
|
+
# → Rails.root.join('log', 'smart_message.log')
|
219
|
+
```
|
220
|
+
|
221
|
+
### Rails Environment Handling
|
222
|
+
|
223
|
+
The logger respects Rails environment settings:
|
224
|
+
|
225
|
+
- **Production**: INFO level, structured logging
|
226
|
+
- **Development**: DEBUG level, verbose output
|
227
|
+
- **Test**: ERROR level, minimal output
|
228
|
+
|
229
|
+
## Custom Loggers
|
230
|
+
|
231
|
+
You can create custom loggers by inheriting from `SmartMessage::Logger::Base`:
|
232
|
+
|
233
|
+
### Basic Custom Logger
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class SmartMessage::Logger::MyCustomLogger < SmartMessage::Logger::Base
|
237
|
+
def initialize(external_logger)
|
238
|
+
@logger = external_logger
|
239
|
+
end
|
240
|
+
|
241
|
+
def log_message_created(message)
|
242
|
+
@logger.debug "Created message: #{message.class.name}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def log_message_published(message, transport)
|
246
|
+
@logger.info "Published #{message.class.name} via #{transport.class.name}"
|
247
|
+
end
|
248
|
+
|
249
|
+
# Implement other lifecycle methods as needed...
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
### Wrapper for Third-Party Loggers
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
# Semantic Logger example
|
257
|
+
class SmartMessage::Logger::SemanticLogger < SmartMessage::Logger::Base
|
258
|
+
def initialize(semantic_logger = nil)
|
259
|
+
@logger = semantic_logger || SemanticLogger['SmartMessage']
|
260
|
+
end
|
261
|
+
|
262
|
+
def log_message_created(message)
|
263
|
+
@logger.debug "Message created", message_class: message.class.name
|
264
|
+
end
|
265
|
+
|
266
|
+
def log_error(context, error)
|
267
|
+
@logger.error "Error in #{context}", exception: error
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
### Multi-Logger (Broadcast)
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
class SmartMessage::Logger::MultiLogger < SmartMessage::Logger::Base
|
276
|
+
def initialize(*loggers)
|
277
|
+
@loggers = loggers
|
278
|
+
end
|
279
|
+
|
280
|
+
def log_message_created(message)
|
281
|
+
@loggers.each { |logger| logger.log_message_created(message) }
|
282
|
+
end
|
283
|
+
|
284
|
+
# Other methods follow same pattern...
|
285
|
+
end
|
286
|
+
|
287
|
+
# Usage
|
288
|
+
logger SmartMessage::Logger::MultiLogger.new(
|
289
|
+
SmartMessage::Logger::Default.new(log_file: 'app.log'),
|
290
|
+
SmartMessage::Logger::Default.new(log_file: STDOUT),
|
291
|
+
SmartMessage::Logger::MyCustomLogger.new(external_system)
|
292
|
+
)
|
293
|
+
```
|
294
|
+
|
295
|
+
## Examples
|
296
|
+
|
297
|
+
### Development Setup
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
class OrderMessage < SmartMessage::Base
|
301
|
+
property :order_id, String, required: true
|
302
|
+
property :customer_id, String, required: true
|
303
|
+
property :amount, Float, required: true
|
304
|
+
|
305
|
+
config do
|
306
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
307
|
+
serializer SmartMessage::Serializer::JSON.new
|
308
|
+
|
309
|
+
# Verbose logging for development
|
310
|
+
logger SmartMessage::Logger::Default.new(
|
311
|
+
log_file: STDOUT,
|
312
|
+
level: Logger::DEBUG
|
313
|
+
)
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.process(message_header, message_payload)
|
317
|
+
data = JSON.parse(message_payload)
|
318
|
+
|
319
|
+
# Logger is available in process method
|
320
|
+
logger.info "Processing order #{data['order_id']}"
|
321
|
+
|
322
|
+
# Business logic here
|
323
|
+
result = process_order(data)
|
324
|
+
|
325
|
+
logger.info "Order #{data['order_id']} completed: #{result}"
|
326
|
+
result
|
327
|
+
end
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
### Production Setup
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
class NotificationMessage < SmartMessage::Base
|
335
|
+
property :recipient, String, required: true
|
336
|
+
property :subject, String, required: true
|
337
|
+
property :body, String, required: true
|
338
|
+
|
339
|
+
config do
|
340
|
+
transport SmartMessage::Transport::RedisTransport.new
|
341
|
+
serializer SmartMessage::Serializer::JSON.new
|
342
|
+
|
343
|
+
# Production logging with file rotation
|
344
|
+
logger SmartMessage::Logger::Default.new(
|
345
|
+
log_file: '/var/log/app/notifications.log',
|
346
|
+
level: Logger::INFO
|
347
|
+
)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
### Docker/Kubernetes Setup
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
class EventMessage < SmartMessage::Base
|
356
|
+
property :event_type, String, required: true
|
357
|
+
property :data, Hash, required: true
|
358
|
+
|
359
|
+
config do
|
360
|
+
transport SmartMessage::Transport::RedisTransport.new(
|
361
|
+
redis_url: ENV['REDIS_URL']
|
362
|
+
)
|
363
|
+
serializer SmartMessage::Serializer::JSON.new
|
364
|
+
|
365
|
+
# Container-friendly STDOUT logging
|
366
|
+
logger SmartMessage::Logger::Default.new(
|
367
|
+
log_file: STDOUT,
|
368
|
+
level: ENV['LOG_LEVEL']&.upcase&.to_sym || Logger::INFO
|
369
|
+
)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
```
|
373
|
+
|
374
|
+
### Testing Setup
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
# In test_helper.rb or similar
|
378
|
+
class TestMessage < SmartMessage::Base
|
379
|
+
property :test_data, Hash
|
380
|
+
|
381
|
+
config do
|
382
|
+
transport SmartMessage::Transport::MemoryTransport.new
|
383
|
+
serializer SmartMessage::Serializer::JSON.new
|
384
|
+
|
385
|
+
# Minimal logging for tests
|
386
|
+
logger SmartMessage::Logger::Default.new(
|
387
|
+
log_file: StringIO.new,
|
388
|
+
level: Logger::FATAL # Only fatal errors in tests
|
389
|
+
)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
```
|
393
|
+
|
394
|
+
### Instance-Level Logger Override
|
395
|
+
|
396
|
+
```ruby
|
397
|
+
# Different logger for specific instances
|
398
|
+
class PriorityMessage < SmartMessage::Base
|
399
|
+
property :priority, String
|
400
|
+
property :data, Hash
|
401
|
+
|
402
|
+
config do
|
403
|
+
transport SmartMessage::Transport::RedisTransport.new
|
404
|
+
serializer SmartMessage::Serializer::JSON.new
|
405
|
+
logger SmartMessage::Logger::Default.new # Default logger
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Override logger for high-priority messages
|
410
|
+
priority_logger = SmartMessage::Logger::Default.new(
|
411
|
+
log_file: '/var/log/priority.log',
|
412
|
+
level: Logger::DEBUG
|
413
|
+
)
|
414
|
+
|
415
|
+
urgent_message = PriorityMessage.new(priority: 'urgent', data: {...})
|
416
|
+
urgent_message.logger(priority_logger) # Override for this instance
|
417
|
+
urgent_message.publish
|
418
|
+
```
|
419
|
+
|
420
|
+
## Best Practices
|
421
|
+
|
422
|
+
1. **Use the default logger** unless you have specific requirements
|
423
|
+
2. **Log to STDOUT** in containerized environments
|
424
|
+
3. **Use appropriate log levels** - avoid DEBUG in production
|
425
|
+
4. **Tag your logs** for better searchability
|
426
|
+
5. **Consider structured logging** for production systems
|
427
|
+
6. **Test your logging** - ensure logs are helpful for debugging
|
428
|
+
7. **Monitor log volume** - excessive logging can impact performance
|
429
|
+
8. **Rotate log files** to prevent disk space issues (default logger handles this)
|
430
|
+
|
431
|
+
## Troubleshooting
|
432
|
+
|
433
|
+
### No logs appearing
|
434
|
+
- Check log level settings
|
435
|
+
- Verify file permissions
|
436
|
+
- Ensure logger is configured
|
437
|
+
|
438
|
+
### Too much logging
|
439
|
+
- Increase log level (DEBUG → INFO → WARN → ERROR)
|
440
|
+
- Consider filtering in production
|
441
|
+
|
442
|
+
### Performance issues
|
443
|
+
- Lower log level in production
|
444
|
+
- Use asynchronous logging for high-volume systems
|
445
|
+
- Consider structured logging formats
|
446
|
+
|
447
|
+
### Rails integration not working
|
448
|
+
- Ensure Rails is loaded before SmartMessage
|
449
|
+
- Check that `Rails.logger` is available
|
450
|
+
- Verify Rails environment is set correctly
|
451
|
+
|
452
|
+
For more troubleshooting tips, see the [Troubleshooting Guide](troubleshooting.md).
|
data/examples/.gitignore
ADDED
@@ -0,0 +1,620 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/06_custom_logger_example.rb
|
3
|
+
#
|
4
|
+
# Custom Logger Example: Comprehensive Message Logging
|
5
|
+
#
|
6
|
+
# This example demonstrates how to implement and use custom loggers in the SmartMessage
|
7
|
+
# framework. It shows different logging strategies including file logging, structured
|
8
|
+
# logging, and audit trails for message processing workflows.
|
9
|
+
#
|
10
|
+
# IMPORTANT: Using the Default Logger or Custom Loggers
|
11
|
+
# ====================================================================
|
12
|
+
# SmartMessage now includes a DEFAULT LOGGER that automatically detects your environment:
|
13
|
+
# - Uses Rails.logger if running in a Rails application
|
14
|
+
# - Uses Ruby's standard Logger otherwise (logs to log/smart_message.log)
|
15
|
+
#
|
16
|
+
# To use the default logger (EASIEST OPTION):
|
17
|
+
# config do
|
18
|
+
# logger SmartMessage::Logger::Default.new
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# To use the default logger with custom settings:
|
22
|
+
# config do
|
23
|
+
# logger SmartMessage::Logger::Default.new(
|
24
|
+
# log_file: 'custom/path.log',
|
25
|
+
# level: Logger::DEBUG
|
26
|
+
# )
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# To log to STDOUT (useful for Docker/Kubernetes):
|
30
|
+
# config do
|
31
|
+
# logger SmartMessage::Logger::Default.new(
|
32
|
+
# log_file: STDOUT,
|
33
|
+
# level: Logger::INFO
|
34
|
+
# )
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# To create your own custom logger:
|
38
|
+
# 1. Create a wrapper class that inherits from SmartMessage::Logger::Base
|
39
|
+
# 2. Store the Ruby logger instance in your wrapper
|
40
|
+
# 3. Implement the SmartMessage logging methods using your Ruby logger
|
41
|
+
#
|
42
|
+
# To use Rails.logger directly (without the default logger):
|
43
|
+
# 1. Create a wrapper that delegates to Rails.logger
|
44
|
+
# 2. Configure it at the class level: logger SmartMessage::Logger::RailsLogger.new
|
45
|
+
# 3. All messages will be logged to your Rails application logs
|
46
|
+
|
47
|
+
require_relative '../lib/smart_message'
|
48
|
+
require 'logger'
|
49
|
+
require 'json'
|
50
|
+
require 'fileutils'
|
51
|
+
|
52
|
+
puts "=== SmartMessage Example: Custom Logger Implementation ==="
|
53
|
+
puts
|
54
|
+
|
55
|
+
# Custom File Logger Implementation
|
56
|
+
# This wrapper shows how to integrate Ruby's standard Logger class with SmartMessage.
|
57
|
+
# The same pattern works for Rails.logger, Semantic Logger, or any Ruby logger.
|
58
|
+
class SmartMessage::Logger::FileLogger < SmartMessage::Logger::Base
|
59
|
+
attr_reader :log_file_path, :logger
|
60
|
+
|
61
|
+
def initialize(log_file_path, level: Logger::INFO)
|
62
|
+
@log_file_path = log_file_path
|
63
|
+
|
64
|
+
# Ensure log directory exists
|
65
|
+
FileUtils.mkdir_p(File.dirname(@log_file_path))
|
66
|
+
|
67
|
+
# This is Ruby's standard Logger class from the 'logger' library
|
68
|
+
# You could replace this with Rails.logger or any other logger:
|
69
|
+
# @logger = Rails.logger # For Rails applications
|
70
|
+
# @logger = SemanticLogger['SmartMessage'] # For Semantic Logger
|
71
|
+
# @logger = $stdout.sync = Logger.new($stdout) # For stdout logging
|
72
|
+
@logger = Logger.new(@log_file_path)
|
73
|
+
@logger.level = level
|
74
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
75
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S.%3N')}] #{severity}: #{msg}\n"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def log_message_created(message)
|
80
|
+
@logger.info("MESSAGE_CREATED: #{message.class.name} - #{message.to_h}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def log_message_published(message, transport)
|
84
|
+
@logger.info("MESSAGE_PUBLISHED: #{message.class.name} via #{transport.class.name}")
|
85
|
+
end
|
86
|
+
|
87
|
+
def log_message_received(message_class, payload)
|
88
|
+
@logger.info("MESSAGE_RECEIVED: #{message_class.name} - #{payload}")
|
89
|
+
end
|
90
|
+
|
91
|
+
def log_message_processed(message_class, result)
|
92
|
+
@logger.info("MESSAGE_PROCESSED: #{message_class.name} - Result: #{result}")
|
93
|
+
end
|
94
|
+
|
95
|
+
def log_error(message, error)
|
96
|
+
@logger.error("MESSAGE_ERROR: #{message} - #{error.class.name}: #{error.message}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Structured JSON Logger Implementation
|
101
|
+
class SmartMessage::Logger::JSONLogger < SmartMessage::Logger::Base
|
102
|
+
attr_reader :log_file_path
|
103
|
+
|
104
|
+
def initialize(log_file_path)
|
105
|
+
@log_file_path = log_file_path
|
106
|
+
|
107
|
+
# Ensure log directory exists
|
108
|
+
FileUtils.mkdir_p(File.dirname(@log_file_path))
|
109
|
+
end
|
110
|
+
|
111
|
+
def log_message_created(message)
|
112
|
+
write_log_entry({
|
113
|
+
event: 'message_created',
|
114
|
+
message_class: message.class.name,
|
115
|
+
message_id: message._sm_header&.message_id,
|
116
|
+
timestamp: Time.now.iso8601,
|
117
|
+
data: message.to_h
|
118
|
+
})
|
119
|
+
end
|
120
|
+
|
121
|
+
def log_message_published(message, transport)
|
122
|
+
write_log_entry({
|
123
|
+
event: 'message_published',
|
124
|
+
message_class: message.class.name,
|
125
|
+
message_id: message._sm_header&.message_id,
|
126
|
+
transport: transport.class.name,
|
127
|
+
timestamp: Time.now.iso8601
|
128
|
+
})
|
129
|
+
end
|
130
|
+
|
131
|
+
def log_message_received(message_class, payload)
|
132
|
+
write_log_entry({
|
133
|
+
event: 'message_received',
|
134
|
+
message_class: message_class.name,
|
135
|
+
payload_size: payload.length,
|
136
|
+
timestamp: Time.now.iso8601
|
137
|
+
})
|
138
|
+
end
|
139
|
+
|
140
|
+
def log_message_processed(message_class, result)
|
141
|
+
write_log_entry({
|
142
|
+
event: 'message_processed',
|
143
|
+
message_class: message_class.name,
|
144
|
+
result: result.to_s,
|
145
|
+
timestamp: Time.now.iso8601
|
146
|
+
})
|
147
|
+
end
|
148
|
+
|
149
|
+
def log_error(message, error)
|
150
|
+
write_log_entry({
|
151
|
+
event: 'error',
|
152
|
+
message: message.to_s,
|
153
|
+
error_class: error.class.name,
|
154
|
+
error_message: error.message,
|
155
|
+
backtrace: error.backtrace&.first(5),
|
156
|
+
timestamp: Time.now.iso8601
|
157
|
+
})
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def write_log_entry(data)
|
163
|
+
File.open(@log_file_path, 'a') do |file|
|
164
|
+
file.puts(JSON.generate(data))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Console Logger with Emoji Implementation
|
170
|
+
class SmartMessage::Logger::EmojiConsoleLogger < SmartMessage::Logger::Base
|
171
|
+
def log_message_created(message)
|
172
|
+
puts "🏗️ Created: #{message.class.name} with data: #{message.to_h}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def log_message_published(message, transport)
|
176
|
+
puts "📤 Published: #{message.class.name} via #{transport.class.name}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def log_message_received(message_class, payload)
|
180
|
+
puts "📥 Received: #{message_class.name} (#{payload.length} bytes)"
|
181
|
+
end
|
182
|
+
|
183
|
+
def log_message_processed(message_class, result)
|
184
|
+
puts "⚙️ Processed: #{message_class.name} → #{result}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def log_error(message, error)
|
188
|
+
puts "❌ Error: #{message} → #{error.class.name}: #{error.message}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Example: Simple Ruby Logger Wrapper
|
193
|
+
# This demonstrates the minimal wrapper needed for Ruby's standard Logger.
|
194
|
+
# Use this pattern when you want to integrate with existing logging infrastructure.
|
195
|
+
class SmartMessage::Logger::RubyLoggerWrapper < SmartMessage::Logger::Base
|
196
|
+
def initialize(ruby_logger = nil)
|
197
|
+
# Accept any Ruby logger instance, or create a default one
|
198
|
+
@logger = ruby_logger || Logger.new(STDOUT)
|
199
|
+
end
|
200
|
+
|
201
|
+
def log_message_created(message)
|
202
|
+
@logger.debug { "SmartMessage created: #{message.class.name} ID: #{message._sm_header&.message_id}" }
|
203
|
+
end
|
204
|
+
|
205
|
+
def log_message_published(message, transport)
|
206
|
+
@logger.info { "SmartMessage published: #{message.class.name} via #{transport.class.name}" }
|
207
|
+
end
|
208
|
+
|
209
|
+
def log_message_received(message_class, payload)
|
210
|
+
@logger.info { "SmartMessage received: #{message_class.name}" }
|
211
|
+
end
|
212
|
+
|
213
|
+
def log_message_processed(message_class, result)
|
214
|
+
@logger.info { "SmartMessage processed: #{message_class.name}" }
|
215
|
+
end
|
216
|
+
|
217
|
+
def log_error(message, error)
|
218
|
+
@logger.error { "SmartMessage error: #{message} - #{error.message}" }
|
219
|
+
@logger.debug { error.backtrace.join("\n") }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Example: Rails Logger Wrapper (for Rails applications)
|
224
|
+
# Uncomment and use this in your Rails application
|
225
|
+
# class SmartMessage::Logger::RailsLogger < SmartMessage::Logger::Base
|
226
|
+
# def log_message_created(message)
|
227
|
+
# Rails.logger.tagged('SmartMessage', message.class.name) do
|
228
|
+
# Rails.logger.info "Message created with ID: #{message._sm_header&.message_id}"
|
229
|
+
# end
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# def log_message_published(message, transport)
|
233
|
+
# Rails.logger.tagged('SmartMessage', message.class.name) do
|
234
|
+
# Rails.logger.info "Message published via #{transport.class.name}"
|
235
|
+
# end
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# def log_message_received(message_class, payload)
|
239
|
+
# Rails.logger.tagged('SmartMessage', message_class.name) do
|
240
|
+
# Rails.logger.info "Message received (#{payload.length} bytes)"
|
241
|
+
# end
|
242
|
+
# end
|
243
|
+
#
|
244
|
+
# def log_message_processed(message_class, result)
|
245
|
+
# Rails.logger.tagged('SmartMessage', message_class.name) do
|
246
|
+
# Rails.logger.info "Message processed successfully"
|
247
|
+
# end
|
248
|
+
# end
|
249
|
+
#
|
250
|
+
# def log_error(message, error)
|
251
|
+
# Rails.logger.tagged('SmartMessage') do
|
252
|
+
# Rails.logger.error "Error: #{message}"
|
253
|
+
# Rails.logger.error "#{error.class.name}: #{error.message}"
|
254
|
+
# Rails.logger.debug error.backtrace.join("\n")
|
255
|
+
# end
|
256
|
+
# end
|
257
|
+
# end
|
258
|
+
|
259
|
+
# Multi-logger that broadcasts to multiple loggers
|
260
|
+
class SmartMessage::Logger::MultiLogger < SmartMessage::Logger::Base
|
261
|
+
def initialize(*loggers)
|
262
|
+
@loggers = loggers
|
263
|
+
end
|
264
|
+
|
265
|
+
def log_message_created(message)
|
266
|
+
@loggers.each { |logger| logger.log_message_created(message) }
|
267
|
+
end
|
268
|
+
|
269
|
+
def log_message_published(message, transport)
|
270
|
+
@loggers.each { |logger| logger.log_message_published(message, transport) }
|
271
|
+
end
|
272
|
+
|
273
|
+
def log_message_received(message_class, payload)
|
274
|
+
@loggers.each { |logger| logger.log_message_received(message_class, payload) }
|
275
|
+
end
|
276
|
+
|
277
|
+
def log_message_processed(message_class, result)
|
278
|
+
@loggers.each { |logger| logger.log_message_processed(message_class, result) }
|
279
|
+
end
|
280
|
+
|
281
|
+
def log_error(message, error)
|
282
|
+
@loggers.each { |logger| logger.log_error(message, error) }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Sample message class with comprehensive logging
|
287
|
+
class OrderProcessingMessage < SmartMessage::Base
|
288
|
+
property :order_id
|
289
|
+
property :customer_id
|
290
|
+
property :amount
|
291
|
+
property :status
|
292
|
+
property :items
|
293
|
+
|
294
|
+
config do
|
295
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
296
|
+
serializer SmartMessage::Serializer::JSON.new
|
297
|
+
|
298
|
+
# Configure multi-logger to demonstrate different logging approaches
|
299
|
+
logger SmartMessage::Logger::MultiLogger.new(
|
300
|
+
SmartMessage::Logger::EmojiConsoleLogger.new,
|
301
|
+
SmartMessage::Logger::FileLogger.new('logs/order_processing.log', level: Logger::DEBUG),
|
302
|
+
SmartMessage::Logger::JSONLogger.new('logs/order_processing.json')
|
303
|
+
)
|
304
|
+
end
|
305
|
+
|
306
|
+
def self.process(message_header, message_payload)
|
307
|
+
# Simulate the logger being called during processing
|
308
|
+
if logger
|
309
|
+
logger.log_message_received(self, message_payload)
|
310
|
+
end
|
311
|
+
|
312
|
+
# Process the message
|
313
|
+
order_data = JSON.parse(message_payload)
|
314
|
+
result = "Order #{order_data['order_id']} processed successfully"
|
315
|
+
|
316
|
+
puts "💼 OrderProcessing: #{result}"
|
317
|
+
|
318
|
+
# Log processing completion
|
319
|
+
if logger
|
320
|
+
logger.log_message_processed(self, result)
|
321
|
+
end
|
322
|
+
|
323
|
+
result
|
324
|
+
end
|
325
|
+
|
326
|
+
# Override publish to demonstrate logging hooks
|
327
|
+
def publish
|
328
|
+
# Log message creation
|
329
|
+
logger_instance = logger || self.class.logger
|
330
|
+
if logger_instance
|
331
|
+
logger_instance.log_message_created(self)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Log publishing
|
335
|
+
transport_instance = transport || self.class.transport
|
336
|
+
if logger_instance
|
337
|
+
logger_instance.log_message_published(self, transport_instance)
|
338
|
+
end
|
339
|
+
|
340
|
+
# Call original publish method
|
341
|
+
super
|
342
|
+
rescue => error
|
343
|
+
# Log any errors during publishing
|
344
|
+
if logger_instance
|
345
|
+
logger_instance.log_error("Failed to publish #{self.class.name}", error)
|
346
|
+
end
|
347
|
+
raise
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# Notification message with different logger configuration
|
352
|
+
class NotificationMessage < SmartMessage::Base
|
353
|
+
property :recipient
|
354
|
+
property :subject
|
355
|
+
property :body
|
356
|
+
property :priority
|
357
|
+
|
358
|
+
config do
|
359
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
360
|
+
serializer SmartMessage::Serializer::JSON.new
|
361
|
+
|
362
|
+
# Use only file logger for notifications
|
363
|
+
logger SmartMessage::Logger::FileLogger.new('logs/notifications.log', level: Logger::WARN)
|
364
|
+
end
|
365
|
+
|
366
|
+
def self.process(message_header, message_payload)
|
367
|
+
if logger
|
368
|
+
logger.log_message_received(self, message_payload)
|
369
|
+
end
|
370
|
+
|
371
|
+
notification_data = JSON.parse(message_payload)
|
372
|
+
result = "Notification sent to #{notification_data['recipient']}"
|
373
|
+
|
374
|
+
puts "📬 Notification: #{result}"
|
375
|
+
|
376
|
+
if logger
|
377
|
+
logger.log_message_processed(self, result)
|
378
|
+
end
|
379
|
+
|
380
|
+
result
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Example: Message class using standard Ruby logger
|
385
|
+
# This demonstrates how to use Ruby's standard Logger in production code
|
386
|
+
class StandardLoggerMessage < SmartMessage::Base
|
387
|
+
property :content
|
388
|
+
property :level
|
389
|
+
|
390
|
+
config do
|
391
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
392
|
+
serializer SmartMessage::Serializer::JSON.new
|
393
|
+
|
394
|
+
# Example 1: Using Ruby's standard Logger directly
|
395
|
+
# Create a standard Ruby logger that logs to STDOUT
|
396
|
+
ruby_logger = Logger.new(STDOUT)
|
397
|
+
ruby_logger.level = Logger::INFO
|
398
|
+
ruby_logger.progname = 'SmartMessage'
|
399
|
+
|
400
|
+
# Wrap it in our adapter
|
401
|
+
logger SmartMessage::Logger::RubyLoggerWrapper.new(ruby_logger)
|
402
|
+
|
403
|
+
# Example 2: Using a file-based Ruby logger (commented out)
|
404
|
+
# file_logger = Logger.new('application.log', 'daily') # Rotate daily
|
405
|
+
# logger SmartMessage::Logger::RubyLoggerWrapper.new(file_logger)
|
406
|
+
|
407
|
+
# Example 3: In Rails, you would use Rails.logger (commented out)
|
408
|
+
# logger SmartMessage::Logger::RubyLoggerWrapper.new(Rails.logger)
|
409
|
+
end
|
410
|
+
|
411
|
+
def self.process(message_header, message_payload)
|
412
|
+
data = JSON.parse(message_payload)
|
413
|
+
puts "📝 Processing: #{data['content']}"
|
414
|
+
"Processed"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# Example: Message using the built-in Default Logger
|
419
|
+
class DefaultLoggerMessage < SmartMessage::Base
|
420
|
+
property :message
|
421
|
+
property :level
|
422
|
+
|
423
|
+
config do
|
424
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
425
|
+
serializer SmartMessage::Serializer::JSON.new
|
426
|
+
|
427
|
+
# Use the built-in default logger - simplest option!
|
428
|
+
logger SmartMessage::Logger::Default.new
|
429
|
+
end
|
430
|
+
|
431
|
+
def self.process(message_header, message_payload)
|
432
|
+
data = JSON.parse(message_payload)
|
433
|
+
puts "🎯 DefaultLogger: Processing #{data['message']}"
|
434
|
+
"Processed with default logger"
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
# Service that uses instance-level logger override
|
439
|
+
class PriorityOrderService
|
440
|
+
def initialize
|
441
|
+
puts "🚀 PriorityOrderService: Starting with custom logger..."
|
442
|
+
|
443
|
+
# Create a priority-specific logger
|
444
|
+
@priority_logger = SmartMessage::Logger::FileLogger.new(
|
445
|
+
'logs/priority_orders.log',
|
446
|
+
level: Logger::DEBUG
|
447
|
+
)
|
448
|
+
end
|
449
|
+
|
450
|
+
def process_priority_order(order_data)
|
451
|
+
# Create message with instance-level logger override
|
452
|
+
message = OrderProcessingMessage.new(**order_data)
|
453
|
+
|
454
|
+
# Override the logger at instance level
|
455
|
+
message.logger(@priority_logger)
|
456
|
+
|
457
|
+
puts "⚡ Processing priority order with dedicated logger"
|
458
|
+
message.publish
|
459
|
+
|
460
|
+
message
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
# Demo runner
|
465
|
+
class LoggerDemo
|
466
|
+
def run
|
467
|
+
puts "🚀 Starting Custom Logger Demo\n"
|
468
|
+
|
469
|
+
# Clean up any existing log files for a fresh demo
|
470
|
+
FileUtils.rm_rf('logs') if Dir.exist?('logs')
|
471
|
+
|
472
|
+
# Subscribe to messages
|
473
|
+
OrderProcessingMessage.subscribe
|
474
|
+
NotificationMessage.subscribe
|
475
|
+
|
476
|
+
puts "\n" + "="*70
|
477
|
+
puts "Demonstrating Different Logger Configurations"
|
478
|
+
puts "="*70
|
479
|
+
|
480
|
+
# Demo 1: Using the built-in Default Logger (NEW!)
|
481
|
+
puts "\n--- Demo 1: Using SmartMessage Default Logger ---"
|
482
|
+
puts "The Default logger automatically uses Rails.logger or Ruby Logger"
|
483
|
+
|
484
|
+
# Use the DefaultLoggerMessage class defined above
|
485
|
+
DefaultLoggerMessage.subscribe
|
486
|
+
default_msg = DefaultLoggerMessage.new(
|
487
|
+
message: "Testing the built-in default logger",
|
488
|
+
level: "info"
|
489
|
+
)
|
490
|
+
default_msg.publish
|
491
|
+
sleep(0.5)
|
492
|
+
|
493
|
+
# Demo 2: Standard order with multi-logger
|
494
|
+
puts "\n--- Demo 2: Standard Order (Multi-Logger) ---"
|
495
|
+
order1 = OrderProcessingMessage.new(
|
496
|
+
order_id: "ORD-001",
|
497
|
+
customer_id: "CUST-123",
|
498
|
+
amount: 99.99,
|
499
|
+
status: "pending",
|
500
|
+
items: ["Widget A", "Widget B"]
|
501
|
+
)
|
502
|
+
order1.publish
|
503
|
+
sleep(0.5)
|
504
|
+
|
505
|
+
# Demo 3: Notification with file-only logger
|
506
|
+
puts "\n--- Demo 3: Notification (File Logger Only) ---"
|
507
|
+
notification = NotificationMessage.new(
|
508
|
+
recipient: "customer@example.com",
|
509
|
+
subject: "Order Confirmation",
|
510
|
+
body: "Your order has been received",
|
511
|
+
priority: "normal"
|
512
|
+
)
|
513
|
+
notification.publish
|
514
|
+
sleep(0.5)
|
515
|
+
|
516
|
+
# Demo 4: Priority order with instance-level logger override
|
517
|
+
puts "\n--- Demo 4: Priority Order (Instance Logger Override) ---"
|
518
|
+
priority_service = PriorityOrderService.new
|
519
|
+
priority_order = priority_service.process_priority_order(
|
520
|
+
order_id: "ORD-PRIORITY-001",
|
521
|
+
customer_id: "VIP-456",
|
522
|
+
amount: 299.99,
|
523
|
+
status: "urgent",
|
524
|
+
items: ["Premium Widget", "Express Shipping"]
|
525
|
+
)
|
526
|
+
sleep(0.5)
|
527
|
+
|
528
|
+
# Demo 5: Using standard Ruby logger
|
529
|
+
puts "\n--- Demo 5: Using Standard Ruby Logger ---"
|
530
|
+
|
531
|
+
# Use the StandardLoggerMessage class that demonstrates Ruby's standard logger
|
532
|
+
StandardLoggerMessage.subscribe
|
533
|
+
|
534
|
+
# Create and send a message - watch for the Ruby logger output
|
535
|
+
msg = StandardLoggerMessage.new(
|
536
|
+
content: "Testing with Ruby's standard logger",
|
537
|
+
level: "info"
|
538
|
+
)
|
539
|
+
|
540
|
+
# The logger will output to STDOUT using Ruby's standard format
|
541
|
+
msg.publish
|
542
|
+
sleep(0.5)
|
543
|
+
|
544
|
+
puts "\nNote: The above used Ruby's standard Logger class wrapped for SmartMessage"
|
545
|
+
puts "You can use ANY Ruby logger this way: Logger.new, Rails.logger, etc."
|
546
|
+
|
547
|
+
# Demo 6: Error handling with logging
|
548
|
+
puts "\n--- Demo 6: Error Handling with Logging ---"
|
549
|
+
begin
|
550
|
+
# Create a message that will cause an error
|
551
|
+
faulty_order = OrderProcessingMessage.new(
|
552
|
+
order_id: nil, # This might cause issues
|
553
|
+
customer_id: "ERROR-TEST",
|
554
|
+
amount: "invalid_amount",
|
555
|
+
status: "error_demo"
|
556
|
+
)
|
557
|
+
|
558
|
+
# Simulate an error during processing
|
559
|
+
if OrderProcessingMessage.logger
|
560
|
+
OrderProcessingMessage.logger.log_error(
|
561
|
+
"Simulated error for demo",
|
562
|
+
StandardError.new("Invalid order data provided")
|
563
|
+
)
|
564
|
+
end
|
565
|
+
|
566
|
+
rescue => error
|
567
|
+
puts "🔍 Caught demonstration error: #{error.message}"
|
568
|
+
end
|
569
|
+
|
570
|
+
# Show log file contents
|
571
|
+
puts "\n" + "="*70
|
572
|
+
puts "📋 Log File Contents"
|
573
|
+
puts "="*70
|
574
|
+
|
575
|
+
show_log_contents
|
576
|
+
|
577
|
+
puts "\n✨ Demo completed!"
|
578
|
+
puts "\nThis example demonstrated:"
|
579
|
+
puts "• SmartMessage::Logger::Default - Built-in logger that auto-detects Rails/Ruby"
|
580
|
+
puts "• Integration with Ruby's standard Logger class"
|
581
|
+
puts "• How to wrap Rails.logger or any Ruby logger for SmartMessage"
|
582
|
+
puts "• Custom logger implementations (File, JSON, Console, Multi-logger)"
|
583
|
+
puts "• Class-level and instance-level logger configuration"
|
584
|
+
puts "• Different logging strategies for different message types"
|
585
|
+
puts "• Error logging and message lifecycle logging"
|
586
|
+
puts "• Log file management and structured logging formats"
|
587
|
+
puts "\nKEY TAKEAWAY: Use SmartMessage::Logger::Default.new for instant logging!"
|
588
|
+
puts "It automatically uses Rails.logger in Rails or creates a Ruby Logger otherwise."
|
589
|
+
end
|
590
|
+
|
591
|
+
private
|
592
|
+
|
593
|
+
def show_log_contents
|
594
|
+
log_files = Dir.glob('logs/*.log') + Dir.glob('logs/*.json')
|
595
|
+
|
596
|
+
log_files.each do |log_file|
|
597
|
+
next unless File.exist?(log_file)
|
598
|
+
|
599
|
+
puts "\n📁 #{log_file}:"
|
600
|
+
puts "-" * 50
|
601
|
+
|
602
|
+
content = File.read(log_file)
|
603
|
+
if content.length > 500
|
604
|
+
puts content[0..500] + "\n... (truncated, #{content.length} total characters)"
|
605
|
+
else
|
606
|
+
puts content
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
if log_files.empty?
|
611
|
+
puts "⚠️ No log files found (they may not have been created yet)"
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
# Run the demo if this file is executed directly
|
617
|
+
if __FILE__ == $0
|
618
|
+
demo = LoggerDemo.new
|
619
|
+
demo.run
|
620
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# lib/smart_message/logger/default.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'stringio'
|
8
|
+
|
9
|
+
module SmartMessage
|
10
|
+
module Logger
|
11
|
+
# Default logger implementation for SmartMessage
|
12
|
+
#
|
13
|
+
# This logger automatically detects and uses the best available logging option:
|
14
|
+
# - Rails.logger if running in a Rails application
|
15
|
+
# - Standard Ruby Logger writing to log/smart_message.log otherwise
|
16
|
+
#
|
17
|
+
# Usage:
|
18
|
+
# # In your message class
|
19
|
+
# config do
|
20
|
+
# logger SmartMessage::Logger::Default.new
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # Or with custom options
|
24
|
+
# config do
|
25
|
+
# logger SmartMessage::Logger::Default.new(
|
26
|
+
# log_file: 'custom/path.log', # File path
|
27
|
+
# level: Logger::DEBUG
|
28
|
+
# )
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # To log to STDOUT instead of a file
|
32
|
+
# config do
|
33
|
+
# logger SmartMessage::Logger::Default.new(
|
34
|
+
# log_file: STDOUT, # STDOUT or STDERR
|
35
|
+
# level: Logger::INFO
|
36
|
+
# )
|
37
|
+
# end
|
38
|
+
class Default < Base
|
39
|
+
attr_reader :logger, :log_file, :level
|
40
|
+
|
41
|
+
def initialize(log_file: nil, level: nil)
|
42
|
+
@log_file = log_file || default_log_file
|
43
|
+
@level = level || default_log_level
|
44
|
+
|
45
|
+
@logger = setup_logger
|
46
|
+
end
|
47
|
+
|
48
|
+
# Message lifecycle logging methods
|
49
|
+
|
50
|
+
def log_message_created(message)
|
51
|
+
logger.debug { "[SmartMessage] Created: #{message.class.name} - #{message_summary(message)}" }
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_message_published(message, transport)
|
55
|
+
logger.info { "[SmartMessage] Published: #{message.class.name} via #{transport.class.name.split('::').last}" }
|
56
|
+
end
|
57
|
+
|
58
|
+
def log_message_received(message_class, payload)
|
59
|
+
logger.info { "[SmartMessage] Received: #{message_class.name} (#{payload.bytesize} bytes)" }
|
60
|
+
end
|
61
|
+
|
62
|
+
def log_message_processed(message_class, result)
|
63
|
+
logger.info { "[SmartMessage] Processed: #{message_class.name} - #{truncate(result.to_s, 100)}" }
|
64
|
+
end
|
65
|
+
|
66
|
+
def log_message_subscribe(message_class, handler = nil)
|
67
|
+
handler_desc = handler ? " with handler: #{handler}" : ""
|
68
|
+
logger.info { "[SmartMessage] Subscribed: #{message_class.name}#{handler_desc}" }
|
69
|
+
end
|
70
|
+
|
71
|
+
def log_message_unsubscribe(message_class)
|
72
|
+
logger.info { "[SmartMessage] Unsubscribed: #{message_class.name}" }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Error logging
|
76
|
+
|
77
|
+
def log_error(context, error)
|
78
|
+
logger.error { "[SmartMessage] Error in #{context}: #{error.class.name} - #{error.message}" }
|
79
|
+
logger.debug { "[SmartMessage] Backtrace:\n#{error.backtrace.join("\n")}" } if error.backtrace
|
80
|
+
end
|
81
|
+
|
82
|
+
def log_warning(message)
|
83
|
+
logger.warn { "[SmartMessage] Warning: #{message}" }
|
84
|
+
end
|
85
|
+
|
86
|
+
# General purpose logging methods matching Ruby's Logger interface
|
87
|
+
|
88
|
+
def debug(message = nil, &block)
|
89
|
+
logger.debug(message, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def info(message = nil, &block)
|
93
|
+
logger.info(message, &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def warn(message = nil, &block)
|
97
|
+
logger.warn(message, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def error(message = nil, &block)
|
101
|
+
logger.error(message, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def fatal(message = nil, &block)
|
105
|
+
logger.fatal(message, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def setup_logger
|
111
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
112
|
+
# Use Rails logger if available
|
113
|
+
setup_rails_logger
|
114
|
+
else
|
115
|
+
# Use standard Ruby logger
|
116
|
+
setup_ruby_logger
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def setup_rails_logger
|
121
|
+
# Wrap Rails.logger to ensure our messages are properly tagged
|
122
|
+
RailsLoggerWrapper.new(Rails.logger, level: @level)
|
123
|
+
end
|
124
|
+
|
125
|
+
def setup_ruby_logger
|
126
|
+
# Handle IO objects (STDOUT, STDERR) vs file paths
|
127
|
+
if @log_file.is_a?(IO) || @log_file.is_a?(StringIO)
|
128
|
+
# For STDOUT/STDERR, don't use rotation
|
129
|
+
ruby_logger = ::Logger.new(@log_file)
|
130
|
+
else
|
131
|
+
# For file paths, ensure directory exists and use rotation
|
132
|
+
FileUtils.mkdir_p(File.dirname(@log_file))
|
133
|
+
|
134
|
+
ruby_logger = ::Logger.new(
|
135
|
+
@log_file,
|
136
|
+
10, # Keep 10 old log files
|
137
|
+
10_485_760 # Rotate when file reaches 10MB
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
ruby_logger.level = @level
|
142
|
+
|
143
|
+
# Set a clean formatter
|
144
|
+
ruby_logger.formatter = proc do |severity, datetime, progname, msg|
|
145
|
+
timestamp = datetime.strftime('%Y-%m-%d %H:%M:%S.%3N')
|
146
|
+
"[#{timestamp}] #{severity.ljust(5)} -- : #{msg}\n"
|
147
|
+
end
|
148
|
+
|
149
|
+
ruby_logger
|
150
|
+
end
|
151
|
+
|
152
|
+
def default_log_file
|
153
|
+
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
154
|
+
Rails.root.join('log', 'smart_message.log').to_s
|
155
|
+
else
|
156
|
+
'log/smart_message.log'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def default_log_level
|
161
|
+
if defined?(Rails) && Rails.respond_to?(:env)
|
162
|
+
case Rails.env
|
163
|
+
when 'production'
|
164
|
+
::Logger::INFO
|
165
|
+
when 'test'
|
166
|
+
::Logger::ERROR
|
167
|
+
else
|
168
|
+
::Logger::DEBUG
|
169
|
+
end
|
170
|
+
else
|
171
|
+
# Default to INFO for non-Rails environments
|
172
|
+
::Logger::INFO
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def message_summary(message)
|
177
|
+
# Create a brief summary of the message for logging
|
178
|
+
if message.respond_to?(:to_h)
|
179
|
+
data = message.to_h
|
180
|
+
# Remove internal header for cleaner logs
|
181
|
+
data.delete(:_sm_header)
|
182
|
+
data.delete('_sm_header')
|
183
|
+
truncate(data.inspect, 200)
|
184
|
+
else
|
185
|
+
truncate(message.inspect, 200)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def truncate(string, max_length)
|
190
|
+
return string if string.length <= max_length
|
191
|
+
"#{string[0...max_length]}..."
|
192
|
+
end
|
193
|
+
|
194
|
+
# Internal wrapper for Rails.logger to handle tagged logging
|
195
|
+
class RailsLoggerWrapper
|
196
|
+
def initialize(rails_logger, level: nil)
|
197
|
+
@rails_logger = rails_logger
|
198
|
+
@rails_logger.level = level if level
|
199
|
+
end
|
200
|
+
|
201
|
+
def method_missing(method, *args, &block)
|
202
|
+
if @rails_logger.respond_to?(:tagged)
|
203
|
+
@rails_logger.tagged('SmartMessage') do
|
204
|
+
@rails_logger.send(method, *args, &block)
|
205
|
+
end
|
206
|
+
else
|
207
|
+
@rails_logger.send(method, *args, &block)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def respond_to_missing?(method, include_private = false)
|
212
|
+
@rails_logger.respond_to?(method, include_private)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
data/lib/smart_message/logger.rb
CHANGED
@@ -3,5 +3,13 @@
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
5
|
module SmartMessage::Logger
|
6
|
-
#
|
6
|
+
# Logger module provides logging capabilities for SmartMessage
|
7
|
+
# The Default logger automatically uses Rails.logger if available,
|
8
|
+
# otherwise falls back to a standard Ruby Logger
|
7
9
|
end # module SmartMessage::Logger
|
10
|
+
|
11
|
+
# Load the base class first
|
12
|
+
require_relative 'logger/base'
|
13
|
+
|
14
|
+
# Load the default logger implementation
|
15
|
+
require_relative 'logger/default'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_message
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dewayne VanHoozer
|
@@ -204,17 +204,20 @@ files:
|
|
204
204
|
- docs/examples.md
|
205
205
|
- docs/getting-started.md
|
206
206
|
- docs/ideas_to_think_about.md
|
207
|
+
- docs/logging.md
|
207
208
|
- docs/message_processing.md
|
208
209
|
- docs/proc_handlers_summary.md
|
209
210
|
- docs/properties.md
|
210
211
|
- docs/serializers.md
|
211
212
|
- docs/transports.md
|
212
213
|
- docs/troubleshooting.md
|
214
|
+
- examples/.gitignore
|
213
215
|
- examples/01_point_to_point_orders.rb
|
214
216
|
- examples/02_publish_subscribe_events.rb
|
215
217
|
- examples/03_many_to_many_chat.rb
|
216
218
|
- examples/04_redis_smart_home_iot.rb
|
217
219
|
- examples/05_proc_handlers.rb
|
220
|
+
- examples/06_custom_logger_example.rb
|
218
221
|
- examples/README.md
|
219
222
|
- examples/smart_home_iot_dataflow.md
|
220
223
|
- examples/tmux_chat/README.md
|
@@ -233,6 +236,7 @@ files:
|
|
233
236
|
- lib/smart_message/header.rb
|
234
237
|
- lib/smart_message/logger.rb
|
235
238
|
- lib/smart_message/logger/base.rb
|
239
|
+
- lib/smart_message/logger/default.rb
|
236
240
|
- lib/smart_message/property_descriptions.rb
|
237
241
|
- lib/smart_message/serializer.rb
|
238
242
|
- lib/smart_message/serializer/base.rb
|