smart_message 0.0.13 → 0.0.16
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/.gitignore +1 -0
- data/CHANGELOG.md +120 -0
- data/Gemfile.lock +3 -3
- data/README.md +71 -25
- data/docs/index.md +2 -0
- data/docs/reference/transports.md +46 -21
- data/docs/transports/memory-transport.md +2 -1
- data/docs/transports/multi-transport.md +484 -0
- data/examples/file/00_run_all_file_demos.rb +260 -0
- data/examples/file/01_basic_file_transport_demo.rb +237 -0
- data/examples/file/02_fifo_transport_demo.rb +289 -0
- data/examples/file/03_file_watching_demo.rb +332 -0
- data/examples/file/04_multi_transport_file_demo.rb +432 -0
- data/examples/file/README.md +257 -0
- data/examples/memory/00_run_all_demos.rb +317 -0
- data/examples/memory/01_message_deduplication_demo.rb +18 -30
- data/examples/memory/02_dead_letter_queue_demo.rb +9 -9
- data/examples/memory/03_point_to_point_orders.rb +3 -3
- data/examples/memory/04_publish_subscribe_events.rb +15 -15
- data/examples/memory/05_many_to_many_chat.rb +19 -19
- data/examples/memory/06_stdout_publish_only.rb +145 -0
- data/examples/memory/07_proc_handlers_demo.rb +13 -13
- data/examples/memory/08_custom_logger_demo.rb +136 -136
- data/examples/memory/09_error_handling_demo.rb +7 -7
- data/examples/memory/10_entity_addressing_basic.rb +25 -25
- data/examples/memory/11_entity_addressing_with_filtering.rb +32 -32
- data/examples/memory/12_regex_filtering_microservices.rb +10 -10
- data/examples/memory/14_global_configuration_demo.rb +12 -12
- data/examples/memory/README.md +34 -17
- data/examples/memory/log/demo_app.log.1 +100 -0
- data/examples/memory/log/demo_app.log.2 +100 -0
- data/examples/multi_transport_example.rb +114 -0
- data/examples/redis/01_smart_home_iot_demo.rb +20 -20
- data/examples/utilities/box_it.rb +12 -0
- data/examples/utilities/doing.rb +19 -0
- data/examples/utilities/temp.md +28 -0
- data/lib/smart_message/base.rb +5 -7
- data/lib/smart_message/errors.rb +3 -0
- data/lib/smart_message/header.rb +1 -1
- data/lib/smart_message/logger/default.rb +1 -1
- data/lib/smart_message/messaging.rb +36 -6
- data/lib/smart_message/plugins.rb +46 -4
- data/lib/smart_message/serializer/base.rb +1 -1
- data/lib/smart_message/serializer.rb +3 -2
- data/lib/smart_message/subscription.rb +18 -20
- data/lib/smart_message/transport/async_publish_queue.rb +284 -0
- data/lib/smart_message/transport/fifo_operations.rb +264 -0
- data/lib/smart_message/transport/file_operations.rb +200 -0
- data/lib/smart_message/transport/file_transport.rb +149 -0
- data/lib/smart_message/transport/file_watching.rb +72 -0
- data/lib/smart_message/transport/partitioned_files.rb +46 -0
- data/lib/smart_message/transport/stdout_transport.rb +50 -36
- data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
- data/lib/smart_message/version.rb +1 -1
- metadata +24 -10
- data/ideas/README.md +0 -41
- data/ideas/agents.md +0 -1001
- data/ideas/database_transport.md +0 -980
- data/ideas/improvement.md +0 -359
- data/ideas/meshage.md +0 -1788
- data/ideas/message_discovery.md +0 -178
- data/ideas/message_schema.md +0 -1381
- data/lib/smart_message/wrapper.rb.bak +0 -132
- /data/examples/memory/{06_pretty_print_demo.rb → 16_pretty_print_demo.rb} +0 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# FIFO Transport Demo
|
6
|
+
#
|
7
|
+
# This example demonstrates SmartMessage's FileTransport with FIFO (named pipes).
|
8
|
+
# FIFOs enable real-time, process-to-process communication through the filesystem.
|
9
|
+
|
10
|
+
require_relative '../../lib/smart_message'
|
11
|
+
require 'tempfile'
|
12
|
+
require 'fileutils'
|
13
|
+
|
14
|
+
# Define message classes
|
15
|
+
Dir.mkdir('messages') unless Dir.exist?('messages')
|
16
|
+
|
17
|
+
class PipeMessage < SmartMessage::Base
|
18
|
+
property :sender, required: true
|
19
|
+
property :content, required: true
|
20
|
+
property :timestamp, default: -> { Time.now.iso8601 }
|
21
|
+
property :priority, default: 'normal'
|
22
|
+
|
23
|
+
from 'fifo_transport_demo'
|
24
|
+
end
|
25
|
+
|
26
|
+
class StatusUpdate < SmartMessage::Base
|
27
|
+
property :service, required: true
|
28
|
+
property :status, required: true, valid: %w[starting running stopping stopped error]
|
29
|
+
property :details, default: {}
|
30
|
+
property :timestamp, default: -> { Time.now.iso8601 }
|
31
|
+
|
32
|
+
from 'fifo_transport_demo'
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "=== SmartMessage FIFO Transport Demo ==="
|
36
|
+
puts
|
37
|
+
|
38
|
+
# Check if FIFOs are supported on this system
|
39
|
+
def fifo_supported?
|
40
|
+
return @fifo_supported if defined?(@fifo_supported)
|
41
|
+
|
42
|
+
test_dir = Dir.mktmpdir('fifo_test')
|
43
|
+
test_fifo = File.join(test_dir, 'test.fifo')
|
44
|
+
|
45
|
+
result = system("mkfifo #{test_fifo} 2>/dev/null")
|
46
|
+
@fifo_supported = result && File.exist?(test_fifo) && File.ftype(test_fifo) == "fifo"
|
47
|
+
|
48
|
+
FileUtils.rm_rf(test_dir)
|
49
|
+
@fifo_supported
|
50
|
+
rescue
|
51
|
+
@fifo_supported = false
|
52
|
+
end
|
53
|
+
|
54
|
+
unless fifo_supported?
|
55
|
+
puts "❌ FIFOs are not supported on this system"
|
56
|
+
puts "This demo requires Unix-like systems with mkfifo support"
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
|
60
|
+
demo_dir = Dir.mktmpdir('fifo_demo')
|
61
|
+
puts "Demo directory: #{demo_dir}"
|
62
|
+
|
63
|
+
begin
|
64
|
+
# Example 1: Basic FIFO Communication
|
65
|
+
puts "\n1. Basic FIFO Communication"
|
66
|
+
puts "=" * 35
|
67
|
+
|
68
|
+
fifo_path = File.join(demo_dir, 'message_pipe.fifo')
|
69
|
+
system("mkfifo #{fifo_path}")
|
70
|
+
|
71
|
+
puts "✓ Created FIFO: #{fifo_path}"
|
72
|
+
puts "FIFO type: #{File.ftype(fifo_path)}"
|
73
|
+
|
74
|
+
# Configure FIFO transport
|
75
|
+
fifo_transport = SmartMessage::Transport::FileTransport.new(
|
76
|
+
file_path: fifo_path,
|
77
|
+
file_type: :fifo,
|
78
|
+
format: :json,
|
79
|
+
enable_subscriptions: true,
|
80
|
+
subscription_mode: :fifo_blocking
|
81
|
+
)
|
82
|
+
|
83
|
+
PipeMessage.class_eval do
|
84
|
+
transport fifo_transport
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set up a reader in a separate process
|
88
|
+
reader_pid = fork do
|
89
|
+
puts "[Reader] Starting FIFO reader process..."
|
90
|
+
|
91
|
+
# Subscribe to messages
|
92
|
+
received_messages = []
|
93
|
+
fifo_transport.subscribe('PipeMessage', ->(msg) {
|
94
|
+
received_messages << msg
|
95
|
+
puts "[Reader] Received: #{msg.inspect}"
|
96
|
+
}, {})
|
97
|
+
|
98
|
+
# Keep reading for a short time
|
99
|
+
sleep 3
|
100
|
+
|
101
|
+
puts "[Reader] Received #{received_messages.length} messages"
|
102
|
+
exit 0
|
103
|
+
end
|
104
|
+
|
105
|
+
# Give reader time to start
|
106
|
+
sleep 0.5
|
107
|
+
|
108
|
+
# Send messages from parent process
|
109
|
+
puts "[Writer] Sending messages through FIFO..."
|
110
|
+
|
111
|
+
PipeMessage.new(
|
112
|
+
sender: 'process_1',
|
113
|
+
content: 'Hello from the writer process!'
|
114
|
+
).publish
|
115
|
+
|
116
|
+
PipeMessage.new(
|
117
|
+
sender: 'process_1',
|
118
|
+
content: 'This is message #2',
|
119
|
+
priority: 'high'
|
120
|
+
).publish
|
121
|
+
|
122
|
+
PipeMessage.new(
|
123
|
+
sender: 'process_1',
|
124
|
+
content: 'Final message before closing'
|
125
|
+
).publish
|
126
|
+
|
127
|
+
# Wait for reader to finish
|
128
|
+
Process.wait(reader_pid)
|
129
|
+
puts "✓ FIFO communication completed"
|
130
|
+
|
131
|
+
# Example 2: Non-blocking FIFO Operations
|
132
|
+
puts "\n2. Non-blocking FIFO Operations"
|
133
|
+
puts "=" * 35
|
134
|
+
|
135
|
+
nb_fifo_path = File.join(demo_dir, 'nonblocking.fifo')
|
136
|
+
system("mkfifo #{nb_fifo_path}")
|
137
|
+
|
138
|
+
# Configure non-blocking transport
|
139
|
+
nb_transport = SmartMessage::Transport::FileTransport.new(
|
140
|
+
file_path: nb_fifo_path,
|
141
|
+
file_type: :fifo,
|
142
|
+
format: :json,
|
143
|
+
fifo_mode: :non_blocking
|
144
|
+
)
|
145
|
+
|
146
|
+
StatusUpdate.class_eval do
|
147
|
+
transport nb_transport
|
148
|
+
end
|
149
|
+
|
150
|
+
puts "✓ Created non-blocking FIFO: #{nb_fifo_path}"
|
151
|
+
|
152
|
+
# Start a background reader
|
153
|
+
reader_thread = Thread.new do
|
154
|
+
sleep 0.2 # Let writer get ahead
|
155
|
+
|
156
|
+
begin
|
157
|
+
File.open(nb_fifo_path, 'r') do |fifo|
|
158
|
+
puts "[Reader] Opened FIFO for reading"
|
159
|
+
while line = fifo.gets
|
160
|
+
puts "[Reader] Read: #{line.chomp}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
rescue => e
|
164
|
+
puts "[Reader] Error: #{e.message}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Send status updates
|
169
|
+
puts "[Writer] Sending status updates..."
|
170
|
+
|
171
|
+
%w[starting running stopping stopped].each_with_index do |status, i|
|
172
|
+
StatusUpdate.new(
|
173
|
+
service: 'web_server',
|
174
|
+
status: status,
|
175
|
+
details: { pid: 1234 + i, memory: "#{50 + i * 10}MB" }
|
176
|
+
).publish
|
177
|
+
|
178
|
+
sleep 0.1 # Small delay between messages
|
179
|
+
end
|
180
|
+
|
181
|
+
# Close the FIFO from writer side to signal EOF
|
182
|
+
nb_transport.disconnect
|
183
|
+
|
184
|
+
reader_thread.join(2) # Wait up to 2 seconds
|
185
|
+
puts "✓ Non-blocking FIFO operations completed"
|
186
|
+
|
187
|
+
# Example 3: FIFO Concepts Demonstration (Non-blocking)
|
188
|
+
puts "\n3. FIFO Concepts Demonstration (Non-blocking)"
|
189
|
+
puts "=" * 50
|
190
|
+
|
191
|
+
simple_fifo_path = File.join(demo_dir, 'simple.fifo')
|
192
|
+
system("mkfifo #{simple_fifo_path}")
|
193
|
+
|
194
|
+
puts "✓ Created FIFO: #{simple_fifo_path}"
|
195
|
+
puts "✓ Verified FIFO type: #{File.ftype(simple_fifo_path)}"
|
196
|
+
|
197
|
+
puts "\nFIFO Characteristics:"
|
198
|
+
puts " - Named pipes for inter-process communication"
|
199
|
+
puts " - Blocking by default (writers wait for readers)"
|
200
|
+
puts " - First In, First Out data flow"
|
201
|
+
puts " - Persistent in filesystem until deleted"
|
202
|
+
|
203
|
+
puts "\nDemonstrating FIFO creation and properties..."
|
204
|
+
puts " - FIFO exists: #{File.exist?(simple_fifo_path)}"
|
205
|
+
puts " - FIFO permissions: #{File.stat(simple_fifo_path).mode.to_s(8)}"
|
206
|
+
puts " - FIFO size: #{File.size(simple_fifo_path)} bytes (always 0 for FIFOs)"
|
207
|
+
|
208
|
+
# Show transport configuration without actual message sending
|
209
|
+
simple_transport = SmartMessage::Transport::FileTransport.new(
|
210
|
+
file_path: simple_fifo_path,
|
211
|
+
file_type: :fifo,
|
212
|
+
format: :json
|
213
|
+
)
|
214
|
+
|
215
|
+
puts "\nFileTransport FIFO Configuration:"
|
216
|
+
puts " - File path: #{simple_transport.instance_variable_get(:@file_path) rescue 'configured'}"
|
217
|
+
puts " - Format: JSON"
|
218
|
+
puts " - Type: FIFO (named pipe)"
|
219
|
+
puts " - Note: Actual message sending requires concurrent reader process"
|
220
|
+
|
221
|
+
puts "\n✓ FIFO concepts demonstration completed"
|
222
|
+
|
223
|
+
# Example 4: Message Priority Classification (File-based)
|
224
|
+
puts "\n4. Message Priority Classification (File-based)"
|
225
|
+
puts "=" * 55
|
226
|
+
|
227
|
+
# Use regular files instead of FIFOs to avoid blocking
|
228
|
+
priority_file = File.join(demo_dir, 'priority_messages.log')
|
229
|
+
|
230
|
+
priority_transport = SmartMessage::Transport::FileTransport.new(
|
231
|
+
file_path: priority_file,
|
232
|
+
format: :json
|
233
|
+
)
|
234
|
+
|
235
|
+
# Create a priority message class
|
236
|
+
class PriorityMessage < SmartMessage::Base
|
237
|
+
property :level, required: true, valid: %w[low normal high critical]
|
238
|
+
property :message, required: true
|
239
|
+
property :component, required: true
|
240
|
+
|
241
|
+
from 'fifo_transport_demo'
|
242
|
+
end
|
243
|
+
|
244
|
+
PriorityMessage.class_eval do
|
245
|
+
transport priority_transport
|
246
|
+
end
|
247
|
+
|
248
|
+
puts "Demonstrating priority message classification and logging..."
|
249
|
+
|
250
|
+
# Send mixed priority messages
|
251
|
+
messages = [
|
252
|
+
{ level: 'low', message: 'Debug information', component: 'logger' },
|
253
|
+
{ level: 'normal', message: 'User login successful', component: 'auth' },
|
254
|
+
{ level: 'high', message: 'Database connection slow', component: 'db' },
|
255
|
+
{ level: 'normal', message: 'Processing request', component: 'api' },
|
256
|
+
{ level: 'critical', message: 'Out of memory!', component: 'system' },
|
257
|
+
{ level: 'high', message: 'Security alert detected', component: 'security' },
|
258
|
+
{ level: 'low', message: 'Cache miss', component: 'cache' }
|
259
|
+
]
|
260
|
+
|
261
|
+
puts "Processing and classifying priority messages..."
|
262
|
+
high_priority_count = 0
|
263
|
+
messages.each_with_index do |msg_data, i|
|
264
|
+
msg = PriorityMessage.new(**msg_data)
|
265
|
+
msg.publish
|
266
|
+
|
267
|
+
if %w[high critical].include?(msg_data[:level])
|
268
|
+
high_priority_count += 1
|
269
|
+
puts " → [HIGH PRIORITY] #{msg_data[:level].upcase}: #{msg_data[:component]} - #{msg_data[:message]}"
|
270
|
+
else
|
271
|
+
puts " → [#{msg_data[:level]}]: #{msg_data[:component]} - #{msg_data[:message]}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
puts "\nPriority Log Contents:"
|
276
|
+
puts File.read(priority_file)
|
277
|
+
|
278
|
+
puts "✓ Processed #{messages.length} messages (#{high_priority_count} high priority)"
|
279
|
+
puts "✓ Message priority classification completed"
|
280
|
+
|
281
|
+
rescue => e
|
282
|
+
puts "Error: #{e.message}"
|
283
|
+
puts e.backtrace.first(3)
|
284
|
+
ensure
|
285
|
+
# Cleanup files
|
286
|
+
FileUtils.rm_rf(demo_dir) if Dir.exist?(demo_dir)
|
287
|
+
FileUtils.rm_rf('messages') if Dir.exist?('messages')
|
288
|
+
puts "\n✓ FIFO demo completed and cleaned up"
|
289
|
+
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# File Watching Demo
|
6
|
+
#
|
7
|
+
# This example demonstrates SmartMessage's FileTransport file watching capabilities.
|
8
|
+
# It shows how to monitor files for changes and automatically process new content.
|
9
|
+
|
10
|
+
require_relative '../../lib/smart_message'
|
11
|
+
require 'tempfile'
|
12
|
+
require 'fileutils'
|
13
|
+
|
14
|
+
# Define message classes
|
15
|
+
Dir.mkdir('messages') unless Dir.exist?('messages')
|
16
|
+
|
17
|
+
class LogEntry < SmartMessage::Base
|
18
|
+
property :timestamp, required: true
|
19
|
+
property :level, required: true
|
20
|
+
property :message, required: true
|
21
|
+
property :source_file, required: true
|
22
|
+
|
23
|
+
from 'file_watching_demo'
|
24
|
+
end
|
25
|
+
|
26
|
+
class ConfigChange < SmartMessage::Base
|
27
|
+
property :setting, required: true
|
28
|
+
property :old_value
|
29
|
+
property :new_value, required: true
|
30
|
+
property :changed_at, default: -> { Time.now.iso8601 }
|
31
|
+
|
32
|
+
from 'file_watching_demo'
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "=== SmartMessage File Watching Demo ==="
|
36
|
+
puts
|
37
|
+
|
38
|
+
demo_dir = Dir.mktmpdir('file_watching_demo')
|
39
|
+
puts "Demo directory: #{demo_dir}"
|
40
|
+
|
41
|
+
begin
|
42
|
+
# Example 1: Basic File Watching
|
43
|
+
puts "\n1. Basic File Watching"
|
44
|
+
puts "=" * 30
|
45
|
+
|
46
|
+
watch_file = File.join(demo_dir, 'application.log')
|
47
|
+
File.write(watch_file, "") # Create empty file
|
48
|
+
|
49
|
+
# Configure file watching transport
|
50
|
+
watching_transport = SmartMessage::Transport::FileTransport.new(
|
51
|
+
file_path: watch_file,
|
52
|
+
enable_subscriptions: true,
|
53
|
+
subscription_mode: :poll_changes,
|
54
|
+
poll_interval: 0.5, # Check every 500ms
|
55
|
+
read_from: :end # Only read new content
|
56
|
+
)
|
57
|
+
|
58
|
+
received_entries = []
|
59
|
+
|
60
|
+
# Subscribe to file changes
|
61
|
+
watching_transport.subscribe('LogEntry', ->(entry) {
|
62
|
+
received_entries << entry
|
63
|
+
puts "[Watcher] New log entry: #{entry.message}"
|
64
|
+
}, {})
|
65
|
+
|
66
|
+
puts "✓ Started watching: #{watch_file}"
|
67
|
+
puts "✓ Polling interval: 500ms"
|
68
|
+
|
69
|
+
# Simulate log entries being written to the file
|
70
|
+
Thread.new do
|
71
|
+
sleep 1 # Let watcher start
|
72
|
+
|
73
|
+
log_entries = [
|
74
|
+
"2024-01-15T10:00:00Z INFO Application started",
|
75
|
+
"2024-01-15T10:00:01Z DEBUG Loading configuration",
|
76
|
+
"2024-01-15T10:00:02Z INFO Database connected",
|
77
|
+
"2024-01-15T10:00:03Z WARN Deprecated API used",
|
78
|
+
"2024-01-15T10:00:04Z ERROR Connection timeout"
|
79
|
+
]
|
80
|
+
|
81
|
+
log_entries.each do |entry|
|
82
|
+
File.open(watch_file, 'a') do |f|
|
83
|
+
f.puts entry
|
84
|
+
f.flush
|
85
|
+
end
|
86
|
+
puts "[Writer] Added: #{entry}"
|
87
|
+
sleep 0.8 # Slower than poll interval
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Let the watcher run for a few seconds
|
92
|
+
sleep 6
|
93
|
+
watching_transport.disconnect
|
94
|
+
|
95
|
+
puts "✓ Watched file changes, received #{received_entries.length} entries"
|
96
|
+
|
97
|
+
# Example 2: Configuration File Monitoring
|
98
|
+
puts "\n2. Configuration File Monitoring"
|
99
|
+
puts "=" * 40
|
100
|
+
|
101
|
+
config_file = File.join(demo_dir, 'app_config.ini')
|
102
|
+
|
103
|
+
# Initial configuration
|
104
|
+
initial_config = <<~CONFIG
|
105
|
+
[database]
|
106
|
+
host=localhost
|
107
|
+
port=5432
|
108
|
+
pool_size=10
|
109
|
+
|
110
|
+
[logging]
|
111
|
+
level=INFO
|
112
|
+
format=json
|
113
|
+
CONFIG
|
114
|
+
|
115
|
+
File.write(config_file, initial_config)
|
116
|
+
|
117
|
+
config_transport = SmartMessage::Transport::FileTransport.new(
|
118
|
+
file_path: config_file,
|
119
|
+
enable_subscriptions: true,
|
120
|
+
subscription_mode: :poll_changes,
|
121
|
+
poll_interval: 0.3,
|
122
|
+
read_from: :beginning # Re-read entire file on changes
|
123
|
+
)
|
124
|
+
|
125
|
+
current_config = {}
|
126
|
+
|
127
|
+
# Monitor configuration changes
|
128
|
+
config_transport.subscribe('ConfigChange', ->(change) {
|
129
|
+
puts "[Config Monitor] Setting '#{change.setting}' changed: #{change.old_value} → #{change.new_value}"
|
130
|
+
current_config[change.setting] = change.new_value
|
131
|
+
}, {})
|
132
|
+
|
133
|
+
puts "✓ Started monitoring config file: #{config_file}"
|
134
|
+
|
135
|
+
# Simulate configuration changes
|
136
|
+
Thread.new do
|
137
|
+
sleep 1
|
138
|
+
|
139
|
+
changes = [
|
140
|
+
{ file: config_file, content: initial_config.gsub('pool_size=10', 'pool_size=20') },
|
141
|
+
{ file: config_file, content: initial_config.gsub('pool_size=10', 'pool_size=20').gsub('level=INFO', 'level=DEBUG') },
|
142
|
+
{ file: config_file, content: initial_config.gsub('pool_size=10', 'pool_size=15').gsub('level=INFO', 'level=WARN').gsub('format=json', 'format=text') }
|
143
|
+
]
|
144
|
+
|
145
|
+
changes.each_with_index do |change, i|
|
146
|
+
File.write(change[:file], change[:content])
|
147
|
+
puts "[Config Writer] Applied configuration change #{i + 1}"
|
148
|
+
sleep 1.2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
sleep 5
|
153
|
+
config_transport.disconnect
|
154
|
+
|
155
|
+
puts "✓ Configuration monitoring completed"
|
156
|
+
|
157
|
+
# Example 3: Log Rotation Handling
|
158
|
+
puts "\n3. Log Rotation Handling"
|
159
|
+
puts "=" * 30
|
160
|
+
|
161
|
+
rotating_log = File.join(demo_dir, 'rotating.log')
|
162
|
+
File.write(rotating_log, "")
|
163
|
+
|
164
|
+
rotation_transport = SmartMessage::Transport::FileTransport.new(
|
165
|
+
file_path: rotating_log,
|
166
|
+
enable_subscriptions: true,
|
167
|
+
subscription_mode: :poll_changes,
|
168
|
+
poll_interval: 0.4,
|
169
|
+
read_from: :end,
|
170
|
+
handle_rotation: true # Handle file rotation
|
171
|
+
)
|
172
|
+
|
173
|
+
rotation_messages = []
|
174
|
+
|
175
|
+
rotation_transport.subscribe('LogEntry', ->(entry) {
|
176
|
+
rotation_messages << entry
|
177
|
+
puts "[Rotation Watcher] #{entry.message}"
|
178
|
+
}, {})
|
179
|
+
|
180
|
+
puts "✓ Started rotation-aware watching: #{rotating_log}"
|
181
|
+
|
182
|
+
# Simulate log rotation
|
183
|
+
Thread.new do
|
184
|
+
sleep 1
|
185
|
+
|
186
|
+
# Write some initial entries
|
187
|
+
3.times do |i|
|
188
|
+
File.open(rotating_log, 'a') { |f| f.puts "Entry #{i + 1} before rotation" }
|
189
|
+
sleep 0.5
|
190
|
+
end
|
191
|
+
|
192
|
+
# Simulate rotation
|
193
|
+
puts "[Rotator] Rotating log file..."
|
194
|
+
FileUtils.mv(rotating_log, "#{rotating_log}.1")
|
195
|
+
File.write(rotating_log, "") # New file
|
196
|
+
|
197
|
+
# Write entries to new file
|
198
|
+
3.times do |i|
|
199
|
+
File.open(rotating_log, 'a') { |f| f.puts "Entry #{i + 1} after rotation" }
|
200
|
+
sleep 0.5
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
sleep 5
|
205
|
+
rotation_transport.disconnect
|
206
|
+
|
207
|
+
puts "✓ Log rotation handling completed, processed #{rotation_messages.length} messages"
|
208
|
+
|
209
|
+
# Example 4: Multiple File Watching
|
210
|
+
puts "\n4. Multiple File Watching"
|
211
|
+
puts "=" * 30
|
212
|
+
|
213
|
+
files_to_watch = %w[app1.log app2.log app3.log].map { |name| File.join(demo_dir, name) }
|
214
|
+
|
215
|
+
# Create empty files
|
216
|
+
files_to_watch.each { |file| File.write(file, "") }
|
217
|
+
|
218
|
+
watchers = []
|
219
|
+
all_messages = []
|
220
|
+
|
221
|
+
files_to_watch.each_with_index do |file, index|
|
222
|
+
transport = SmartMessage::Transport::FileTransport.new(
|
223
|
+
file_path: file,
|
224
|
+
enable_subscriptions: true,
|
225
|
+
subscription_mode: :poll_changes,
|
226
|
+
poll_interval: 0.3,
|
227
|
+
read_from: :end
|
228
|
+
)
|
229
|
+
|
230
|
+
transport.subscribe('LogEntry', ->(entry) {
|
231
|
+
all_messages << entry
|
232
|
+
puts "[Multi-Watcher] #{File.basename(entry.source_file)}: #{entry.message}"
|
233
|
+
}, {})
|
234
|
+
|
235
|
+
watchers << transport
|
236
|
+
end
|
237
|
+
|
238
|
+
puts "✓ Started watching #{files_to_watch.length} files"
|
239
|
+
|
240
|
+
# Simulate activity on multiple files
|
241
|
+
Thread.new do
|
242
|
+
sleep 1
|
243
|
+
|
244
|
+
10.times do |i|
|
245
|
+
# Randomly pick a file to write to
|
246
|
+
file = files_to_watch.sample
|
247
|
+
message = "Message #{i + 1} from #{File.basename(file)}"
|
248
|
+
|
249
|
+
File.open(file, 'a') { |f| f.puts message }
|
250
|
+
puts "[Multi-Writer] → #{File.basename(file)}: #{message}"
|
251
|
+
|
252
|
+
sleep rand(0.2..0.6)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
sleep 8
|
257
|
+
watchers.each(&:disconnect)
|
258
|
+
|
259
|
+
puts "✓ Multiple file watching completed, total messages: #{all_messages.length}"
|
260
|
+
|
261
|
+
# Example 5: File Content Processing
|
262
|
+
puts "\n5. File Content Processing with Patterns"
|
263
|
+
puts "=" * 45
|
264
|
+
|
265
|
+
pattern_file = File.join(demo_dir, 'structured.log')
|
266
|
+
File.write(pattern_file, "")
|
267
|
+
|
268
|
+
# Custom processor that parses log lines
|
269
|
+
pattern_transport = SmartMessage::Transport::FileTransport.new(
|
270
|
+
file_path: pattern_file,
|
271
|
+
enable_subscriptions: true,
|
272
|
+
subscription_mode: :poll_changes,
|
273
|
+
poll_interval: 0.4,
|
274
|
+
read_from: :end,
|
275
|
+
line_processor: ->(line) {
|
276
|
+
# Parse structured log lines: "TIMESTAMP LEVEL MESSAGE"
|
277
|
+
if match = line.match(/^(\S+)\s+(\S+)\s+(.+)$/)
|
278
|
+
{
|
279
|
+
timestamp: match[1],
|
280
|
+
level: match[2],
|
281
|
+
message: match[3],
|
282
|
+
source_file: pattern_file
|
283
|
+
}
|
284
|
+
else
|
285
|
+
nil # Skip malformed lines
|
286
|
+
end
|
287
|
+
}
|
288
|
+
)
|
289
|
+
|
290
|
+
processed_logs = []
|
291
|
+
|
292
|
+
pattern_transport.subscribe('LogEntry', ->(entry) {
|
293
|
+
processed_logs << entry
|
294
|
+
puts "[Pattern Processor] [#{entry.level}] #{entry.message}"
|
295
|
+
}, {})
|
296
|
+
|
297
|
+
puts "✓ Started pattern-based processing: #{pattern_file}"
|
298
|
+
|
299
|
+
# Write structured log entries
|
300
|
+
Thread.new do
|
301
|
+
sleep 1
|
302
|
+
|
303
|
+
entries = [
|
304
|
+
"2024-01-15T10:00:00Z INFO Application started successfully",
|
305
|
+
"2024-01-15T10:00:01Z DEBUG Loading user preferences",
|
306
|
+
"invalid log line that will be skipped",
|
307
|
+
"2024-01-15T10:00:02Z WARN Database connection slow",
|
308
|
+
"2024-01-15T10:00:03Z ERROR Failed to process request",
|
309
|
+
"another invalid line",
|
310
|
+
"2024-01-15T10:00:04Z INFO Request processed successfully"
|
311
|
+
]
|
312
|
+
|
313
|
+
entries.each do |entry|
|
314
|
+
File.open(pattern_file, 'a') { |f| f.puts entry }
|
315
|
+
sleep 0.6
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
sleep 5
|
320
|
+
pattern_transport.disconnect
|
321
|
+
|
322
|
+
puts "✓ Pattern processing completed, valid entries: #{processed_logs.length}"
|
323
|
+
|
324
|
+
rescue => e
|
325
|
+
puts "Error: #{e.message}"
|
326
|
+
puts e.backtrace.first(3)
|
327
|
+
ensure
|
328
|
+
# Cleanup
|
329
|
+
FileUtils.rm_rf(demo_dir) if Dir.exist?(demo_dir)
|
330
|
+
FileUtils.rm_rf('messages') if Dir.exist?('messages')
|
331
|
+
puts "\n✓ File watching demo completed and cleaned up"
|
332
|
+
end
|