smart_message 0.0.3 → 0.0.5

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,641 @@
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
+ description "Order processing messages with comprehensive multi-logger configuration"
289
+
290
+ property :order_id,
291
+ description: "Unique identifier for the customer order"
292
+ property :customer_id,
293
+ description: "Identifier of the customer placing the order"
294
+ property :amount,
295
+ description: "Total monetary amount of the order"
296
+ property :status,
297
+ description: "Current processing status of the order"
298
+ property :items,
299
+ description: "Array of items included in the order"
300
+
301
+ config do
302
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
303
+ serializer SmartMessage::Serializer::JSON.new
304
+
305
+ # Configure multi-logger to demonstrate different logging approaches
306
+ logger SmartMessage::Logger::MultiLogger.new(
307
+ SmartMessage::Logger::EmojiConsoleLogger.new,
308
+ SmartMessage::Logger::FileLogger.new('logs/order_processing.log', level: Logger::DEBUG),
309
+ SmartMessage::Logger::JSONLogger.new('logs/order_processing.json')
310
+ )
311
+ end
312
+
313
+ def self.process(message_header, message_payload)
314
+ # Simulate the logger being called during processing
315
+ if logger
316
+ logger.log_message_received(self, message_payload)
317
+ end
318
+
319
+ # Process the message
320
+ order_data = JSON.parse(message_payload)
321
+ result = "Order #{order_data['order_id']} processed successfully"
322
+
323
+ puts "šŸ’¼ OrderProcessing: #{result}"
324
+
325
+ # Log processing completion
326
+ if logger
327
+ logger.log_message_processed(self, result)
328
+ end
329
+
330
+ result
331
+ end
332
+
333
+ # Override publish to demonstrate logging hooks
334
+ def publish
335
+ # Log message creation
336
+ logger_instance = logger || self.class.logger
337
+ if logger_instance
338
+ logger_instance.log_message_created(self)
339
+ end
340
+
341
+ # Log publishing
342
+ transport_instance = transport || self.class.transport
343
+ if logger_instance
344
+ logger_instance.log_message_published(self, transport_instance)
345
+ end
346
+
347
+ # Call original publish method
348
+ super
349
+ rescue => error
350
+ # Log any errors during publishing
351
+ if logger_instance
352
+ logger_instance.log_error("Failed to publish #{self.class.name}", error)
353
+ end
354
+ raise
355
+ end
356
+ end
357
+
358
+ # Notification message with different logger configuration
359
+ class NotificationMessage < SmartMessage::Base
360
+ description "User notifications with file-based logging configuration"
361
+
362
+ property :recipient,
363
+ description: "Target recipient for the notification"
364
+ property :subject,
365
+ description: "Subject line or title of the notification"
366
+ property :body,
367
+ description: "Main content body of the notification"
368
+ property :priority,
369
+ description: "Priority level of the notification (low, normal, high, urgent)"
370
+
371
+ config do
372
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
373
+ serializer SmartMessage::Serializer::JSON.new
374
+
375
+ # Use only file logger for notifications
376
+ logger SmartMessage::Logger::FileLogger.new('logs/notifications.log', level: Logger::WARN)
377
+ end
378
+
379
+ def self.process(message_header, message_payload)
380
+ if logger
381
+ logger.log_message_received(self, message_payload)
382
+ end
383
+
384
+ notification_data = JSON.parse(message_payload)
385
+ result = "Notification sent to #{notification_data['recipient']}"
386
+
387
+ puts "šŸ“¬ Notification: #{result}"
388
+
389
+ if logger
390
+ logger.log_message_processed(self, result)
391
+ end
392
+
393
+ result
394
+ end
395
+ end
396
+
397
+ # Example: Message class using standard Ruby logger
398
+ # This demonstrates how to use Ruby's standard Logger in production code
399
+ class StandardLoggerMessage < SmartMessage::Base
400
+ description "Demonstrates integration with standard Ruby Logger for production logging"
401
+
402
+ property :content,
403
+ description: "Main content of the message to be logged"
404
+ property :level,
405
+ description: "Logging level for the message (debug, info, warn, error)"
406
+
407
+ config do
408
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
409
+ serializer SmartMessage::Serializer::JSON.new
410
+
411
+ # Example 1: Using Ruby's standard Logger directly
412
+ # Create a standard Ruby logger that logs to STDOUT
413
+ ruby_logger = Logger.new(STDOUT)
414
+ ruby_logger.level = Logger::INFO
415
+ ruby_logger.progname = 'SmartMessage'
416
+
417
+ # Wrap it in our adapter
418
+ logger SmartMessage::Logger::RubyLoggerWrapper.new(ruby_logger)
419
+
420
+ # Example 2: Using a file-based Ruby logger (commented out)
421
+ # file_logger = Logger.new('application.log', 'daily') # Rotate daily
422
+ # logger SmartMessage::Logger::RubyLoggerWrapper.new(file_logger)
423
+
424
+ # Example 3: In Rails, you would use Rails.logger (commented out)
425
+ # logger SmartMessage::Logger::RubyLoggerWrapper.new(Rails.logger)
426
+ end
427
+
428
+ def self.process(message_header, message_payload)
429
+ data = JSON.parse(message_payload)
430
+ puts "šŸ“ Processing: #{data['content']}"
431
+ "Processed"
432
+ end
433
+ end
434
+
435
+ # Example: Message using the built-in Default Logger
436
+ class DefaultLoggerMessage < SmartMessage::Base
437
+ description "Demonstrates SmartMessage's built-in default logger with auto-detection"
438
+
439
+ property :message,
440
+ description: "The message content to be logged using default logger"
441
+ property :level,
442
+ description: "Log level (debug, info, warn, error, fatal)"
443
+
444
+ config do
445
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
446
+ serializer SmartMessage::Serializer::JSON.new
447
+
448
+ # Use the built-in default logger - simplest option!
449
+ logger SmartMessage::Logger::Default.new
450
+ end
451
+
452
+ def self.process(message_header, message_payload)
453
+ data = JSON.parse(message_payload)
454
+ puts "šŸŽÆ DefaultLogger: Processing #{data['message']}"
455
+ "Processed with default logger"
456
+ end
457
+ end
458
+
459
+ # Service that uses instance-level logger override
460
+ class PriorityOrderService
461
+ def initialize
462
+ puts "šŸš€ PriorityOrderService: Starting with custom logger..."
463
+
464
+ # Create a priority-specific logger
465
+ @priority_logger = SmartMessage::Logger::FileLogger.new(
466
+ 'logs/priority_orders.log',
467
+ level: Logger::DEBUG
468
+ )
469
+ end
470
+
471
+ def process_priority_order(order_data)
472
+ # Create message with instance-level logger override
473
+ message = OrderProcessingMessage.new(**order_data)
474
+
475
+ # Override the logger at instance level
476
+ message.logger(@priority_logger)
477
+
478
+ puts "⚔ Processing priority order with dedicated logger"
479
+ message.publish
480
+
481
+ message
482
+ end
483
+ end
484
+
485
+ # Demo runner
486
+ class LoggerDemo
487
+ def run
488
+ puts "šŸš€ Starting Custom Logger Demo\n"
489
+
490
+ # Clean up any existing log files for a fresh demo
491
+ FileUtils.rm_rf('logs') if Dir.exist?('logs')
492
+
493
+ # Subscribe to messages
494
+ OrderProcessingMessage.subscribe
495
+ NotificationMessage.subscribe
496
+
497
+ puts "\n" + "="*70
498
+ puts "Demonstrating Different Logger Configurations"
499
+ puts "="*70
500
+
501
+ # Demo 1: Using the built-in Default Logger (NEW!)
502
+ puts "\n--- Demo 1: Using SmartMessage Default Logger ---"
503
+ puts "The Default logger automatically uses Rails.logger or Ruby Logger"
504
+
505
+ # Use the DefaultLoggerMessage class defined above
506
+ DefaultLoggerMessage.subscribe
507
+ default_msg = DefaultLoggerMessage.new(
508
+ message: "Testing the built-in default logger",
509
+ level: "info"
510
+ )
511
+ default_msg.publish
512
+ sleep(0.5)
513
+
514
+ # Demo 2: Standard order with multi-logger
515
+ puts "\n--- Demo 2: Standard Order (Multi-Logger) ---"
516
+ order1 = OrderProcessingMessage.new(
517
+ order_id: "ORD-001",
518
+ customer_id: "CUST-123",
519
+ amount: 99.99,
520
+ status: "pending",
521
+ items: ["Widget A", "Widget B"]
522
+ )
523
+ order1.publish
524
+ sleep(0.5)
525
+
526
+ # Demo 3: Notification with file-only logger
527
+ puts "\n--- Demo 3: Notification (File Logger Only) ---"
528
+ notification = NotificationMessage.new(
529
+ recipient: "customer@example.com",
530
+ subject: "Order Confirmation",
531
+ body: "Your order has been received",
532
+ priority: "normal"
533
+ )
534
+ notification.publish
535
+ sleep(0.5)
536
+
537
+ # Demo 4: Priority order with instance-level logger override
538
+ puts "\n--- Demo 4: Priority Order (Instance Logger Override) ---"
539
+ priority_service = PriorityOrderService.new
540
+ priority_order = priority_service.process_priority_order(
541
+ order_id: "ORD-PRIORITY-001",
542
+ customer_id: "VIP-456",
543
+ amount: 299.99,
544
+ status: "urgent",
545
+ items: ["Premium Widget", "Express Shipping"]
546
+ )
547
+ sleep(0.5)
548
+
549
+ # Demo 5: Using standard Ruby logger
550
+ puts "\n--- Demo 5: Using Standard Ruby Logger ---"
551
+
552
+ # Use the StandardLoggerMessage class that demonstrates Ruby's standard logger
553
+ StandardLoggerMessage.subscribe
554
+
555
+ # Create and send a message - watch for the Ruby logger output
556
+ msg = StandardLoggerMessage.new(
557
+ content: "Testing with Ruby's standard logger",
558
+ level: "info"
559
+ )
560
+
561
+ # The logger will output to STDOUT using Ruby's standard format
562
+ msg.publish
563
+ sleep(0.5)
564
+
565
+ puts "\nNote: The above used Ruby's standard Logger class wrapped for SmartMessage"
566
+ puts "You can use ANY Ruby logger this way: Logger.new, Rails.logger, etc."
567
+
568
+ # Demo 6: Error handling with logging
569
+ puts "\n--- Demo 6: Error Handling with Logging ---"
570
+ begin
571
+ # Create a message that will cause an error
572
+ faulty_order = OrderProcessingMessage.new(
573
+ order_id: nil, # This might cause issues
574
+ customer_id: "ERROR-TEST",
575
+ amount: "invalid_amount",
576
+ status: "error_demo"
577
+ )
578
+
579
+ # Simulate an error during processing
580
+ if OrderProcessingMessage.logger
581
+ OrderProcessingMessage.logger.log_error(
582
+ "Simulated error for demo",
583
+ StandardError.new("Invalid order data provided")
584
+ )
585
+ end
586
+
587
+ rescue => error
588
+ puts "šŸ” Caught demonstration error: #{error.message}"
589
+ end
590
+
591
+ # Show log file contents
592
+ puts "\n" + "="*70
593
+ puts "šŸ“‹ Log File Contents"
594
+ puts "="*70
595
+
596
+ show_log_contents
597
+
598
+ puts "\n✨ Demo completed!"
599
+ puts "\nThis example demonstrated:"
600
+ puts "• SmartMessage::Logger::Default - Built-in logger that auto-detects Rails/Ruby"
601
+ puts "• Integration with Ruby's standard Logger class"
602
+ puts "• How to wrap Rails.logger or any Ruby logger for SmartMessage"
603
+ puts "• Custom logger implementations (File, JSON, Console, Multi-logger)"
604
+ puts "• Class-level and instance-level logger configuration"
605
+ puts "• Different logging strategies for different message types"
606
+ puts "• Error logging and message lifecycle logging"
607
+ puts "• Log file management and structured logging formats"
608
+ puts "\nKEY TAKEAWAY: Use SmartMessage::Logger::Default.new for instant logging!"
609
+ puts "It automatically uses Rails.logger in Rails or creates a Ruby Logger otherwise."
610
+ end
611
+
612
+ private
613
+
614
+ def show_log_contents
615
+ log_files = Dir.glob('logs/*.log') + Dir.glob('logs/*.json')
616
+
617
+ log_files.each do |log_file|
618
+ next unless File.exist?(log_file)
619
+
620
+ puts "\nšŸ“ #{log_file}:"
621
+ puts "-" * 50
622
+
623
+ content = File.read(log_file)
624
+ if content.length > 500
625
+ puts content[0..500] + "\n... (truncated, #{content.length} total characters)"
626
+ else
627
+ puts content
628
+ end
629
+ end
630
+
631
+ if log_files.empty?
632
+ puts "āš ļø No log files found (they may not have been created yet)"
633
+ end
634
+ end
635
+ end
636
+
637
+ # Run the demo if this file is executed directly
638
+ if __FILE__ == $0
639
+ demo = LoggerDemo.new
640
+ demo.run
641
+ end