smart_message 0.0.8 → 0.0.9
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/.irbrc +24 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile.lock +6 -1
- data/README.md +289 -15
- data/docs/README.md +3 -1
- data/docs/addressing.md +119 -13
- data/docs/architecture.md +68 -0
- data/docs/dead_letter_queue.md +673 -0
- data/docs/dispatcher.md +87 -0
- data/docs/examples.md +59 -1
- data/docs/getting-started.md +8 -1
- data/docs/logging.md +382 -326
- data/docs/message_filtering.md +451 -0
- data/examples/01_point_to_point_orders.rb +54 -53
- data/examples/02_publish_subscribe_events.rb +14 -10
- data/examples/03_many_to_many_chat.rb +16 -8
- data/examples/04_redis_smart_home_iot.rb +20 -10
- data/examples/05_proc_handlers.rb +12 -11
- data/examples/06_custom_logger_example.rb +95 -100
- data/examples/07_error_handling_scenarios.rb +4 -2
- data/examples/08_entity_addressing_basic.rb +18 -6
- data/examples/08_entity_addressing_with_filtering.rb +27 -9
- data/examples/09_dead_letter_queue_demo.rb +559 -0
- data/examples/09_regex_filtering_microservices.rb +407 -0
- data/examples/10_header_block_configuration.rb +263 -0
- data/examples/11_global_configuration_example.rb +219 -0
- data/examples/README.md +102 -0
- data/examples/dead_letters.jsonl +12 -0
- data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
- data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
- data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
- data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
- data/examples/performance_metrics/compare_benchmarks.rb +519 -0
- data/examples/performance_metrics/dead_letters.jsonl +3100 -0
- data/examples/performance_metrics/performance_benchmark.rb +344 -0
- data/examples/show_logger.rb +367 -0
- data/examples/show_me.rb +145 -0
- data/examples/temp.txt +94 -0
- data/examples/tmux_chat/bot_agent.rb +4 -2
- data/examples/tmux_chat/human_agent.rb +4 -2
- data/examples/tmux_chat/room_monitor.rb +4 -2
- data/examples/tmux_chat/shared_chat_system.rb +6 -3
- data/lib/smart_message/addressing.rb +259 -0
- data/lib/smart_message/base.rb +121 -599
- data/lib/smart_message/circuit_breaker.rb +2 -1
- data/lib/smart_message/configuration.rb +199 -0
- data/lib/smart_message/dead_letter_queue.rb +27 -10
- data/lib/smart_message/dispatcher.rb +90 -49
- data/lib/smart_message/header.rb +5 -0
- data/lib/smart_message/logger/base.rb +21 -1
- data/lib/smart_message/logger/default.rb +88 -138
- data/lib/smart_message/logger/lumberjack.rb +324 -0
- data/lib/smart_message/logger/null.rb +81 -0
- data/lib/smart_message/logger.rb +17 -9
- data/lib/smart_message/messaging.rb +100 -0
- data/lib/smart_message/plugins.rb +132 -0
- data/lib/smart_message/serializer/base.rb +25 -8
- data/lib/smart_message/serializer/json.rb +5 -4
- data/lib/smart_message/subscription.rb +193 -0
- data/lib/smart_message/transport/base.rb +72 -41
- data/lib/smart_message/transport/memory_transport.rb +7 -5
- data/lib/smart_message/transport/redis_transport.rb +15 -45
- data/lib/smart_message/transport/stdout_transport.rb +18 -8
- data/lib/smart_message/transport.rb +1 -34
- data/lib/smart_message/utilities.rb +142 -0
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message/versioning.rb +85 -0
- data/lib/smart_message/wrapper.rb.bak +132 -0
- data/lib/smart_message.rb +74 -28
- data/smart_message.gemspec +3 -0
- metadata +76 -3
- data/lib/smart_message/serializer.rb +0 -10
- data/lib/smart_message/wrapper.rb +0 -43
@@ -9,170 +9,141 @@ require 'stringio'
|
|
9
9
|
module SmartMessage
|
10
10
|
module Logger
|
11
11
|
# Default logger implementation for SmartMessage
|
12
|
-
#
|
13
|
-
# This logger
|
14
|
-
#
|
15
|
-
#
|
12
|
+
#
|
13
|
+
# This logger provides a simple Ruby Logger wrapper with enhanced formatting.
|
14
|
+
# Applications can easily configure Rails.logger or other loggers through
|
15
|
+
# the global configuration system instead.
|
16
16
|
#
|
17
17
|
# Usage:
|
18
|
-
# #
|
19
|
-
#
|
20
|
-
# logger SmartMessage::Logger::Default.new
|
18
|
+
# # Use default file logging
|
19
|
+
# SmartMessage.configure do |config|
|
20
|
+
# config.logger = SmartMessage::Logger::Default.new
|
21
21
|
# end
|
22
22
|
#
|
23
|
-
# #
|
24
|
-
#
|
25
|
-
# logger SmartMessage::Logger::Default.new(
|
26
|
-
# log_file: 'custom/path.log',
|
23
|
+
# # Use custom options
|
24
|
+
# SmartMessage.configure do |config|
|
25
|
+
# config.logger = SmartMessage::Logger::Default.new(
|
26
|
+
# log_file: 'custom/path.log',
|
27
27
|
# level: Logger::DEBUG
|
28
28
|
# )
|
29
29
|
# end
|
30
30
|
#
|
31
|
-
# #
|
32
|
-
#
|
33
|
-
# logger SmartMessage::Logger::Default.new(
|
34
|
-
# log_file: STDOUT,
|
31
|
+
# # Log to STDOUT
|
32
|
+
# SmartMessage.configure do |config|
|
33
|
+
# config.logger = SmartMessage::Logger::Default.new(
|
34
|
+
# log_file: STDOUT,
|
35
35
|
# level: Logger::INFO
|
36
36
|
# )
|
37
37
|
# end
|
38
|
+
#
|
39
|
+
# # Use Rails logger instead
|
40
|
+
# SmartMessage.configure do |config|
|
41
|
+
# config.logger = Rails.logger
|
42
|
+
# end
|
38
43
|
class Default < Base
|
39
44
|
attr_reader :logger, :log_file, :level
|
40
|
-
|
45
|
+
|
41
46
|
def initialize(log_file: nil, level: nil)
|
42
|
-
@log_file = log_file ||
|
43
|
-
@level = level ||
|
44
|
-
|
47
|
+
@log_file = log_file || 'log/smart_message.log'
|
48
|
+
@level = level || ::Logger::INFO
|
49
|
+
|
45
50
|
@logger = setup_logger
|
46
51
|
end
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
+
|
53
|
+
|
86
54
|
# General purpose logging methods matching Ruby's Logger interface
|
87
|
-
|
55
|
+
# These methods capture caller information and embed it in the log message
|
56
|
+
|
88
57
|
def debug(message = nil, &block)
|
89
|
-
|
58
|
+
enhanced_log(:debug, message, caller_locations(1, 1).first, &block)
|
90
59
|
end
|
91
|
-
|
60
|
+
|
92
61
|
def info(message = nil, &block)
|
93
|
-
|
62
|
+
enhanced_log(:info, message, caller_locations(1, 1).first, &block)
|
94
63
|
end
|
95
|
-
|
64
|
+
|
96
65
|
def warn(message = nil, &block)
|
97
|
-
|
66
|
+
enhanced_log(:warn, message, caller_locations(1, 1).first, &block)
|
98
67
|
end
|
99
|
-
|
68
|
+
|
100
69
|
def error(message = nil, &block)
|
101
|
-
|
70
|
+
enhanced_log(:error, message, caller_locations(1, 1).first, &block)
|
102
71
|
end
|
103
|
-
|
72
|
+
|
104
73
|
def fatal(message = nil, &block)
|
105
|
-
|
74
|
+
enhanced_log(:fatal, message, caller_locations(1, 1).first, &block)
|
106
75
|
end
|
107
|
-
|
76
|
+
|
108
77
|
private
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
78
|
+
|
79
|
+
# Enhanced logging method that embeds caller information
|
80
|
+
def enhanced_log(level, message, caller_location, &block)
|
81
|
+
if caller_location
|
82
|
+
file_path = caller_location.path
|
83
|
+
line_number = caller_location.lineno
|
84
|
+
|
85
|
+
# If a block is provided, call it to get the message
|
86
|
+
if block_given?
|
87
|
+
actual_message = block.call
|
88
|
+
else
|
89
|
+
actual_message = message
|
90
|
+
end
|
91
|
+
|
92
|
+
# Embed caller info in the message
|
93
|
+
enhanced_message = "[#{file_path}:#{line_number}] #{actual_message}"
|
94
|
+
logger.send(level, enhanced_message)
|
114
95
|
else
|
115
|
-
#
|
116
|
-
|
96
|
+
# Fallback if caller information is not available
|
97
|
+
if block_given?
|
98
|
+
logger.send(level, &block)
|
99
|
+
else
|
100
|
+
logger.send(level, message)
|
101
|
+
end
|
117
102
|
end
|
118
103
|
end
|
119
|
-
|
120
|
-
def
|
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
|
104
|
+
|
105
|
+
def setup_logger
|
126
106
|
# Handle IO objects (STDOUT, STDERR) vs file paths
|
127
107
|
if @log_file.is_a?(IO) || @log_file.is_a?(StringIO)
|
128
108
|
# For STDOUT/STDERR, don't use rotation
|
129
109
|
ruby_logger = ::Logger.new(@log_file)
|
130
110
|
else
|
131
111
|
# For file paths, ensure directory exists and use rotation
|
132
|
-
|
133
|
-
|
112
|
+
log_dir = File.dirname(@log_file)
|
113
|
+
FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
|
114
|
+
|
134
115
|
ruby_logger = ::Logger.new(
|
135
116
|
@log_file,
|
136
117
|
10, # Keep 10 old log files
|
137
118
|
10_485_760 # Rotate when file reaches 10MB
|
138
119
|
)
|
139
120
|
end
|
140
|
-
|
121
|
+
|
141
122
|
ruby_logger.level = @level
|
142
|
-
|
143
|
-
# Set a
|
123
|
+
|
124
|
+
# Set a formatter that includes file and line number
|
144
125
|
ruby_logger.formatter = proc do |severity, datetime, progname, msg|
|
145
126
|
timestamp = datetime.strftime('%Y-%m-%d %H:%M:%S.%3N')
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
127
|
+
|
128
|
+
# Extract caller information if it's embedded in the message
|
129
|
+
if msg.is_a?(String) && msg.match(/\A\[(.+?):(\d+)\] (.+)\z/)
|
130
|
+
file_path = $1
|
131
|
+
line_number = $2
|
132
|
+
actual_msg = $3
|
133
|
+
|
134
|
+
# Get just the filename from the full path
|
135
|
+
filename = File.basename(file_path)
|
136
|
+
|
137
|
+
"[#{timestamp}] #{severity.ljust(5)} -- #{filename}:#{line_number} : #{actual_msg}\n"
|
167
138
|
else
|
168
|
-
|
139
|
+
"[#{timestamp}] #{severity.ljust(5)} -- : #{msg}\n"
|
169
140
|
end
|
170
|
-
else
|
171
|
-
# Default to INFO for non-Rails environments
|
172
|
-
::Logger::INFO
|
173
141
|
end
|
142
|
+
|
143
|
+
ruby_logger
|
174
144
|
end
|
175
|
-
|
145
|
+
|
146
|
+
|
176
147
|
def message_summary(message)
|
177
148
|
# Create a brief summary of the message for logging
|
178
149
|
if message.respond_to?(:to_h)
|
@@ -185,33 +156,12 @@ module SmartMessage
|
|
185
156
|
truncate(message.inspect, 200)
|
186
157
|
end
|
187
158
|
end
|
188
|
-
|
159
|
+
|
189
160
|
def truncate(string, max_length)
|
190
161
|
return string if string.length <= max_length
|
191
162
|
"#{string[0...max_length]}..."
|
192
163
|
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
|
164
|
+
|
215
165
|
end
|
216
166
|
end
|
217
|
-
end
|
167
|
+
end
|
@@ -0,0 +1,324 @@
|
|
1
|
+
# lib/smart_message/logger/lumberjack.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'lumberjack'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'colorize'
|
8
|
+
|
9
|
+
module SmartMessage
|
10
|
+
module Logger
|
11
|
+
# Lumberjack-based logger implementation for SmartMessage
|
12
|
+
#
|
13
|
+
# This logger uses the Lumberjack gem to provide enhanced structured logging
|
14
|
+
# with automatic source location tracking, structured data support, and
|
15
|
+
# better performance characteristics.
|
16
|
+
#
|
17
|
+
# Features:
|
18
|
+
# - Automatic source location tracking (file:line) for every log entry
|
19
|
+
# - Structured data logging for message headers and payloads
|
20
|
+
# - Better performance than standard Ruby Logger
|
21
|
+
# - JSON output support for machine-readable logs
|
22
|
+
# - Customizable formatters and devices
|
23
|
+
#
|
24
|
+
# Usage:
|
25
|
+
# # Basic usage with file logging
|
26
|
+
# config do
|
27
|
+
# logger SmartMessage::Logger::Lumberjack.new
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # Custom configuration
|
31
|
+
# config do
|
32
|
+
# logger SmartMessage::Logger::Lumberjack.new(
|
33
|
+
# log_file: 'custom/smart_message.log',
|
34
|
+
# level: :debug,
|
35
|
+
# format: :json, # :text or :json
|
36
|
+
# include_source: true, # Include source location
|
37
|
+
# structured_data: true, # Log structured message data
|
38
|
+
# colorize: true, # Enable colorized output (console only)
|
39
|
+
# roll_by_date: true, # Enable date-based log rolling
|
40
|
+
# max_file_size: 50 * 1024 * 1024 # Max file size before rolling (50 MB)
|
41
|
+
# )
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # Log to STDOUT with colorized JSON format
|
45
|
+
# config do
|
46
|
+
# logger SmartMessage::Logger::Lumberjack.new(
|
47
|
+
# log_file: STDOUT,
|
48
|
+
# format: :json,
|
49
|
+
# colorize: true
|
50
|
+
# )
|
51
|
+
# end
|
52
|
+
class Lumberjack < Base
|
53
|
+
attr_reader :logger, :log_file, :level, :format, :include_source, :structured_data, :colorize
|
54
|
+
|
55
|
+
def initialize(log_file: nil, level: nil, format: :text, include_source: true, structured_data: true, colorize: false, **options)
|
56
|
+
@log_file = log_file || default_log_file
|
57
|
+
@level = level || default_log_level
|
58
|
+
@format = format
|
59
|
+
@include_source = include_source
|
60
|
+
@structured_data = structured_data
|
61
|
+
@options = options
|
62
|
+
|
63
|
+
# Set colorize after @log_file is set so console_output? works correctly
|
64
|
+
@colorize = colorize && console_output?
|
65
|
+
|
66
|
+
@logger = setup_lumberjack_logger
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
# General purpose logging methods matching Ruby's Logger interface
|
71
|
+
# These methods capture caller information and add structured data
|
72
|
+
|
73
|
+
def debug(message = nil, **structured_data, &block)
|
74
|
+
return unless message || block_given?
|
75
|
+
structured_data[:message] = message if message
|
76
|
+
log_with_caller(:debug, 1, structured_data, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def info(message = nil, **structured_data, &block)
|
80
|
+
return unless message || block_given?
|
81
|
+
structured_data[:message] = message if message
|
82
|
+
log_with_caller(:info, 1, structured_data, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def warn(message = nil, **structured_data, &block)
|
86
|
+
return unless message || block_given?
|
87
|
+
structured_data[:message] = message if message
|
88
|
+
log_with_caller(:warn, 1, structured_data, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def error(message = nil, **structured_data, &block)
|
92
|
+
return unless message || block_given?
|
93
|
+
structured_data[:message] = message if message
|
94
|
+
log_with_caller(:error, 1, structured_data, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
def fatal(message = nil, **structured_data, &block)
|
98
|
+
return unless message || block_given?
|
99
|
+
structured_data[:message] = message if message
|
100
|
+
log_with_caller(:fatal, 1, structured_data, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if output is going to console (STDOUT/STDERR)
|
104
|
+
def console_output?
|
105
|
+
@log_file == STDOUT || @log_file == STDERR
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def setup_lumberjack_logger
|
111
|
+
# Create log directory if needed
|
112
|
+
if @log_file.is_a?(String) && !@log_file.start_with?('/dev/')
|
113
|
+
log_dir = File.dirname(@log_file)
|
114
|
+
FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
|
115
|
+
end
|
116
|
+
|
117
|
+
# For STDOUT/STDERR, use Device::Writer with template for colorization
|
118
|
+
if console_output?
|
119
|
+
device = ::Lumberjack::Device::Writer.new(@log_file, template: create_template)
|
120
|
+
::Lumberjack::Logger.new(device, level: normalize_level(@level))
|
121
|
+
else
|
122
|
+
# Build Lumberjack options for file-based logging
|
123
|
+
lumberjack_options = {
|
124
|
+
level: normalize_level(@level),
|
125
|
+
formatter: create_formatter,
|
126
|
+
buffer_size: 0 # Disable buffering for immediate output
|
127
|
+
}
|
128
|
+
|
129
|
+
# Add log rolling options if specified
|
130
|
+
if @options[:roll_by_date]
|
131
|
+
lumberjack_options[:date_pattern] = @options[:date_pattern] || '%Y-%m-%d'
|
132
|
+
end
|
133
|
+
|
134
|
+
if @options[:roll_by_size]
|
135
|
+
lumberjack_options[:max_size] = @options[:max_file_size] || (10 * 1024 * 1024) # 10 MB
|
136
|
+
lumberjack_options[:keep] = @options[:keep_files] || 5
|
137
|
+
end
|
138
|
+
|
139
|
+
# Configure the Lumberjack logger
|
140
|
+
::Lumberjack::Logger.new(@log_file, lumberjack_options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_template
|
145
|
+
# Template for Device::Writer (used for console output)
|
146
|
+
colorize_enabled = @colorize
|
147
|
+
include_source = @include_source
|
148
|
+
|
149
|
+
case @format
|
150
|
+
when :json
|
151
|
+
# JSON template - return lambda that formats as JSON
|
152
|
+
lambda do |entry|
|
153
|
+
data = {
|
154
|
+
timestamp: entry.time.strftime('%Y-%m-%d %H:%M:%S.%3N'),
|
155
|
+
level: entry.severity_label,
|
156
|
+
message: entry.message
|
157
|
+
}
|
158
|
+
|
159
|
+
# Add source location if available
|
160
|
+
if include_source && entry.tags[:source]
|
161
|
+
data[:source] = entry.tags[:source]
|
162
|
+
end
|
163
|
+
|
164
|
+
# Add any structured data
|
165
|
+
entry.tags.each do |key, value|
|
166
|
+
next if key == :source
|
167
|
+
data[key] = value
|
168
|
+
end
|
169
|
+
|
170
|
+
data.to_json + "\n"
|
171
|
+
end
|
172
|
+
else
|
173
|
+
# Text template with optional colorization
|
174
|
+
lambda do |entry|
|
175
|
+
require 'colorize' if colorize_enabled
|
176
|
+
|
177
|
+
timestamp = entry.time.strftime('%Y-%m-%d %H:%M:%S.%3N')
|
178
|
+
level = entry.severity_label.ljust(5)
|
179
|
+
source_info = include_source && entry.tags[:source] ? " #{entry.tags[:source]}" : ""
|
180
|
+
|
181
|
+
line = "[#{timestamp}] #{level} --#{source_info} : #{entry.message}"
|
182
|
+
|
183
|
+
if colorize_enabled
|
184
|
+
# Apply colorization with custom color scheme
|
185
|
+
case entry.severity_label.downcase.to_sym
|
186
|
+
when :debug
|
187
|
+
# Debug: dark green background, white foreground, bold
|
188
|
+
line.white.bold.on_green
|
189
|
+
when :info
|
190
|
+
# Info: bright white foreground (no background)
|
191
|
+
line.light_white
|
192
|
+
when :warn
|
193
|
+
# Warn: yellow background, white foreground, bold
|
194
|
+
line.white.bold.on_yellow
|
195
|
+
when :error
|
196
|
+
# Error: red background, white foreground, bold
|
197
|
+
line.white.bold.on_red
|
198
|
+
when :fatal
|
199
|
+
# Fatal: bright red background, yellow foreground, bold
|
200
|
+
line.yellow.bold.on_light_red
|
201
|
+
else
|
202
|
+
line
|
203
|
+
end
|
204
|
+
else
|
205
|
+
line
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def create_formatter
|
212
|
+
case @format
|
213
|
+
when :json
|
214
|
+
# JSON formatter with structured data support
|
215
|
+
::Lumberjack::Formatter.new do |entry|
|
216
|
+
data = {
|
217
|
+
timestamp: entry.time.strftime('%Y-%m-%d %H:%M:%S.%3N'),
|
218
|
+
level: entry.severity_label,
|
219
|
+
message: entry.message
|
220
|
+
}
|
221
|
+
|
222
|
+
# Add source location if available
|
223
|
+
if @include_source && entry.tags[:source]
|
224
|
+
data[:source] = entry.tags[:source]
|
225
|
+
end
|
226
|
+
|
227
|
+
# Add any structured data
|
228
|
+
entry.tags.each do |key, value|
|
229
|
+
next if key == :source
|
230
|
+
data[key] = value
|
231
|
+
end
|
232
|
+
|
233
|
+
data.to_json + "\n"
|
234
|
+
end
|
235
|
+
else
|
236
|
+
# Text formatter for file output (no colorization)
|
237
|
+
::Lumberjack::Formatter.new do |entry|
|
238
|
+
timestamp = entry.time.strftime('%Y-%m-%d %H:%M:%S.%3N')
|
239
|
+
level = entry.severity_label.ljust(5)
|
240
|
+
source_info = @include_source && entry.tags[:source] ? " #{entry.tags[:source]}" : ""
|
241
|
+
|
242
|
+
"[#{timestamp}] #{level} --#{source_info} : #{entry.message}\n"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def log_with_caller(level, caller_depth, structured_data = {}, &block)
|
248
|
+
return unless @logger.send("#{level}?")
|
249
|
+
|
250
|
+
caller_info = get_caller_info(caller_depth + 1)
|
251
|
+
tags = structured_data.dup
|
252
|
+
tags[:source] = caller_info if @include_source && caller_info
|
253
|
+
|
254
|
+
if block_given?
|
255
|
+
message = block.call(caller_info)
|
256
|
+
else
|
257
|
+
message = structured_data.delete(:message) || ""
|
258
|
+
end
|
259
|
+
|
260
|
+
if tags.empty?
|
261
|
+
@logger.send(level, message)
|
262
|
+
else
|
263
|
+
@logger.tag(tags) do
|
264
|
+
@logger.send(level, message)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
def get_caller_info(depth)
|
271
|
+
return nil unless @include_source
|
272
|
+
|
273
|
+
caller_location = caller_locations(depth + 1, 1).first
|
274
|
+
return nil unless caller_location
|
275
|
+
|
276
|
+
filename = File.basename(caller_location.path)
|
277
|
+
line_number = caller_location.lineno
|
278
|
+
"#{filename}:#{line_number}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def normalize_level(level)
|
282
|
+
case level
|
283
|
+
when Symbol
|
284
|
+
case level
|
285
|
+
when :debug then ::Lumberjack::Severity::DEBUG
|
286
|
+
when :info then ::Lumberjack::Severity::INFO
|
287
|
+
when :warn then ::Lumberjack::Severity::WARN
|
288
|
+
when :error then ::Lumberjack::Severity::ERROR
|
289
|
+
when :fatal then ::Lumberjack::Severity::FATAL
|
290
|
+
else ::Lumberjack::Severity::INFO
|
291
|
+
end
|
292
|
+
when Integer
|
293
|
+
level
|
294
|
+
else
|
295
|
+
::Lumberjack::Severity::INFO
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def default_log_file
|
300
|
+
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
301
|
+
Rails.root.join('log', 'smart_message.log').to_s
|
302
|
+
else
|
303
|
+
'log/smart_message.log'
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def default_log_level
|
308
|
+
if defined?(Rails) && Rails.respond_to?(:env)
|
309
|
+
case Rails.env
|
310
|
+
when 'development', 'test'
|
311
|
+
:info
|
312
|
+
when 'production'
|
313
|
+
:info
|
314
|
+
else
|
315
|
+
:info
|
316
|
+
end
|
317
|
+
else
|
318
|
+
:info
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# lib/smart_message/logger/null.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module Logger
|
7
|
+
# Null logger implementation that discards all log messages
|
8
|
+
#
|
9
|
+
# This logger provides a no-op implementation of the standard logging
|
10
|
+
# interface. All log messages are silently discarded, making it useful
|
11
|
+
# for applications that want to completely disable SmartMessage logging.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
# # Disable all SmartMessage logging
|
15
|
+
# SmartMessage.configure do |config|
|
16
|
+
# config.logger = SmartMessage::Logger::Null.new
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Or set logger to nil (framework will use Null logger automatically)
|
20
|
+
# SmartMessage.configure do |config|
|
21
|
+
# config.logger = nil
|
22
|
+
# end
|
23
|
+
class Null < Base
|
24
|
+
def initialize
|
25
|
+
# No setup needed for null logger
|
26
|
+
end
|
27
|
+
|
28
|
+
# All logging methods are no-ops that accept any arguments
|
29
|
+
# and return nil immediately without processing
|
30
|
+
|
31
|
+
def debug(*args, &block)
|
32
|
+
# Silently discard
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def info(*args, &block)
|
37
|
+
# Silently discard
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def warn(*args, &block)
|
42
|
+
# Silently discard
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def error(*args, &block)
|
47
|
+
# Silently discard
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def fatal(*args, &block)
|
52
|
+
# Silently discard
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# Additional methods that might be called on loggers
|
57
|
+
|
58
|
+
def level
|
59
|
+
::Logger::FATAL + 1 # Higher than FATAL to ensure nothing logs
|
60
|
+
end
|
61
|
+
|
62
|
+
def level=(value)
|
63
|
+
# Ignore level changes
|
64
|
+
end
|
65
|
+
|
66
|
+
def close
|
67
|
+
# Nothing to close
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond_to_missing?(method_name, include_private = false)
|
71
|
+
# Pretend to respond to any logging method
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(method_name, *args, &block)
|
76
|
+
# Silently handle any other logging calls
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|