smart_message 0.0.2 → 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.
@@ -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