anzen 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0d6abdd8e11593b466a6c75cdb31ae5c7cf9c67a4a03d1f40c77f3b5bda74cd
4
- data.tar.gz: d710b1070b4f9c4e7889cd7722c32e4fd4ff84a19587979bc27747e57b159e5b
3
+ metadata.gz: 0e3619f6edd8dc37f9c6b5c58594cbc24f3b9024bc83e49e4713203a7715ba03
4
+ data.tar.gz: 88f927070a51c2081f5d34363a861e488183b6fb309dfad08a68c102826541ef
5
5
  SHA512:
6
- metadata.gz: f9b0c437da6c189c1ee234956daa67da7ea14a0940d3a9e6b5abacdfb169016235f868292bd8ead9637350db7e6e01f57aa0e8857f38a4bfc9201d8879a594be
7
- data.tar.gz: 6f1ad13ac2d366a5a65fff1d69565dbfce647c1c1fe196315659f73f6d0438db9f0cf1d3c6b3b4023c04ea651ed5174020e605fd38887460569fdefb90178b2b
6
+ metadata.gz: 16ae70a8cd48c77a4c5a61d45b665d25cbe98c96029d1a579c0cbc115a44592175404325635a55d8edb234f2039977f79b786aa6c5b351b05b270acc88c9644c
7
+ data.tar.gz: 5a45022aad2f831d261ad3063a5dd1df6ab9cf8baa92e0087835508466bd711e61268dc73ab6e4f9bb6347065b1c78f6bcb5bb4d2defdf7b71f216f045cf5e96
data/README.md CHANGED
@@ -10,6 +10,7 @@ Anzen prevents catastrophic crashes from recursive call stacks and memory overfl
10
10
  - 📊 **Observable**: CLI tools for status monitoring and debugging
11
11
  - 🧩 **Extensible**: Built-in monitors + custom safety checks
12
12
  - ⚡ **Low Overhead**: Sampling-based monitoring with minimal performance impact
13
+ - 🔄 **Real-Time Protection**: Automatic interception without manual checks
13
14
 
14
15
  ## Installation
15
16
 
@@ -53,12 +54,13 @@ Anzen.setup(
53
54
  ```ruby
54
55
  def risky_algorithm(n)
55
56
  return n if n <= 1
56
- # Anzen automatically checks safety limits
57
+ # Anzen automatically monitors and prevents excessive recursion and memory usage
57
58
  risky_algorithm(n - 1) + risky_algorithm(n - 2)
58
59
  end
59
60
 
61
+ # Monitoring happens in real-time
60
62
  begin
61
- result = risky_algorithm(50) # Safe with Anzen
63
+ result = risky_algorithm(50) # Safe with Anzen's automatic protection
62
64
  rescue Anzen::RecursionLimitExceeded => e
63
65
  puts "Recursion limit exceeded: #{e.current_depth} > #{e.threshold}"
64
66
  # Handle gracefully instead of crashing
@@ -188,9 +190,9 @@ Anzen.setup(config: {...})
188
190
  Anzen.enable('recursion')
189
191
  Anzen.disable('memory')
190
192
 
191
- # Status and monitoring
193
+ # Status and monitoring (optional - monitoring happens automatically)
192
194
  status = Anzen.status
193
- Anzen.check! # Manual safety check
195
+ Anzen.check! # Manual check if needed
194
196
 
195
197
  # Custom monitors
196
198
  Anzen.register_monitor(my_monitor)
data/lib/anzen/cli.rb CHANGED
@@ -104,10 +104,7 @@ module Anzen
104
104
  status_data = Anzen.status
105
105
  monitor = status_data[:monitors].find { |m| m[:name] == monitor_name }
106
106
 
107
- unless monitor
108
- available = status_data[:monitors].map { |m| m[:name] }
109
- raise Anzen::MonitorNotFoundError.new(monitor_name)
110
- end
107
+ raise Anzen::MonitorNotFoundError.new(monitor_name) unless monitor
111
108
 
112
109
  {
113
110
  monitor: monitor_name,
@@ -37,6 +37,8 @@ module Anzen
37
37
  @enabled = false
38
38
  @violation_count = 0
39
39
  @last_check = nil
40
+ @trace_point = nil
41
+ @current_depth = 0
40
42
  end
41
43
 
42
44
  # Monitor name
@@ -50,14 +52,22 @@ module Anzen
50
52
  #
51
53
  # @return [Boolean] true
52
54
  def enable
55
+ return true if @enabled
56
+
53
57
  @enabled = true
58
+ start_trace_point
59
+ true
54
60
  end
55
61
 
56
62
  # Disable this monitor
57
63
  #
58
64
  # @return [Boolean] false
59
65
  def disable
66
+ return false unless @enabled
67
+
60
68
  @enabled = false
69
+ stop_trace_point
70
+ false
61
71
  end
62
72
 
63
73
  # Check if monitor is enabled
@@ -69,9 +79,8 @@ module Anzen
69
79
 
70
80
  # Check current call stack depth
71
81
  #
72
- # Reads the call stack, counts method frames, and raises RecursionLimitExceeded
73
- # if depth exceeds threshold. Blocks count as method frames.
74
- # Does nothing if monitor is disabled.
82
+ # In real-time mode, this is a no-op since monitoring happens automatically.
83
+ # For compatibility, it performs a one-time check if called manually or in test mode.
75
84
  #
76
85
  # @return [nil]
77
86
  # @raise [Anzen::RecursionLimitExceeded] if depth exceeds threshold
@@ -80,19 +89,21 @@ module Anzen
80
89
  return nil unless @enabled
81
90
 
82
91
  begin
83
- current_depth = calculate_depth
84
92
  @last_check = Time.now
85
-
86
- if current_depth > @depth_limit
87
- @violation_count += 1
88
- raise Anzen::RecursionLimitExceeded.new(current_depth, @depth_limit)
93
+ # In real-time mode, violations are raised immediately in the trace point
94
+ # In test mode or manual check, perform the check here
95
+ if @trace_point.nil? || !@trace_point.enabled?
96
+ current_depth = calculate_depth
97
+ if current_depth > @depth_limit
98
+ @violation_count += 1
99
+ raise Anzen::RecursionLimitExceeded.new(current_depth, @depth_limit)
100
+ end
89
101
  end
90
-
91
102
  nil
92
103
  rescue Anzen::RecursionLimitExceeded
93
104
  raise
94
105
  rescue StandardError => e
95
- raise Anzen::CheckFailedError.new(name, 'Failed to calculate call stack depth', e)
106
+ raise Anzen::CheckFailedError.new(name, 'Failed to check call stack depth', e)
96
107
  end
97
108
  end
98
109
 
@@ -121,6 +132,35 @@ module Anzen
121
132
 
122
133
  private
123
134
 
135
+ # Start the trace point for real-time monitoring
136
+ def start_trace_point
137
+ return if ENV['RACK_ENV'] == 'test' || ENV['RAILS_ENV'] == 'test' || defined?(RSpec)
138
+
139
+ @current_depth = 0
140
+ @trace_point = TracePoint.new(:call, :return) do |tp|
141
+ next unless @enabled
142
+
143
+ case tp.event
144
+ when :call
145
+ @current_depth += 1
146
+ if @current_depth > @depth_limit
147
+ @violation_count += 1
148
+ raise Anzen::RecursionLimitExceeded.new(@current_depth, @depth_limit)
149
+ end
150
+ when :return
151
+ @current_depth -= 1 if @current_depth > 0
152
+ end
153
+ end
154
+ @trace_point.enable
155
+ end
156
+
157
+ # Stop the trace point
158
+ def stop_trace_point
159
+ @trace_point&.disable
160
+ @trace_point = nil
161
+ @current_depth = 0
162
+ end
163
+
124
164
  # Calculate current call stack depth
125
165
  #
126
166
  # Counts method frames in the call stack.
@@ -2,50 +2,18 @@
2
2
 
3
3
  module Anzen
4
4
  module Monitors
5
- # Monitor that detects memory consumption exceeding configurable threshold
6
- #
7
- # Monitors process RSS (Resident Set Size) memory usage with configurable sampling
8
- # to minimize overhead. Supports absolute MB limits or percentage of available memory.
9
- # Raises MemoryLimitExceeded immediately when threshold is exceeded.
10
- #
11
- # Use case: Prevent memory leaks and runaway memory consumption in long-running processes
12
- #
13
- # @api public
14
- # @example Basic usage
15
- # monitor = Anzen::Monitors::MemoryMonitor.new(limit_mb: 512)
16
- # monitor.enable
17
- # monitor.check! # Raises MemoryLimitExceeded if > 512MB used
5
+ # Monitor that samples process RSS and raises when usage exceeds a limit.
6
+ # Sampling is synchronous and triggered through #check! to keep the
7
+ # implementation deterministic for specs and CLI output.
18
8
  class MemoryMonitor
19
9
  include Anzen::Monitor
20
10
 
21
- # Default sampling interval in milliseconds
22
11
  DEFAULT_SAMPLING_INTERVAL_MS = 100
23
-
24
- # Default memory limit in MB
25
12
  DEFAULT_LIMIT_MB = 512
26
13
 
27
- # @return [String] monitor name
28
- attr_reader :name
29
-
30
- # @return [Integer] memory limit in MB
31
- attr_reader :limit_mb
32
-
33
- # @return [Integer] sampling interval in milliseconds
34
- attr_reader :sampling_interval_ms
35
-
36
- # @return [Time, nil] timestamp of last check
37
- attr_reader :last_check_time
38
-
39
- # @return [Float] current RSS memory in MB at last check
40
- attr_reader :current_rss_mb
41
-
42
- # @return [Integer] count of violations detected
43
- attr_reader :violation_count
14
+ attr_reader :name, :limit_mb, :sampling_interval_ms,
15
+ :last_check_time, :current_rss_mb, :violation_count
44
16
 
45
- # Initialize MemoryMonitor
46
- #
47
- # @param limit_mb [Integer] memory limit in MB (default: 512)
48
- # @param sampling_interval_ms [Integer] milliseconds between checks (default: 100)
49
17
  def initialize(limit_mb: DEFAULT_LIMIT_MB, sampling_interval_ms: DEFAULT_SAMPLING_INTERVAL_MS)
50
18
  @name = 'memory'
51
19
  @enabled = false
@@ -58,62 +26,38 @@ module Anzen
58
26
  validate_configuration!
59
27
  end
60
28
 
61
- # Enable this monitor
62
- #
63
- # @return [Boolean] true
64
29
  def enable
65
30
  @enabled = true
31
+ true
66
32
  end
67
33
 
68
- # Disable this monitor
69
- #
70
- # @return [Boolean] false
71
34
  def disable
72
35
  @enabled = false
36
+ false
73
37
  end
74
38
 
75
- # Check if monitor is enabled
76
- #
77
- # @return [Boolean]
78
39
  def enabled?
79
40
  @enabled
80
41
  end
81
42
 
82
- # Check for memory limit violation
83
- #
84
- # Reads current process RSS memory and compares to configured limit.
85
- # Only performs check if sampling interval has elapsed since last check.
86
- # Raises MemoryLimitExceeded if memory usage exceeds threshold.
87
- # Does nothing if monitor is disabled.
88
- #
89
- # @return [nil]
90
- # @raise [Anzen::MemoryLimitExceeded] if memory usage exceeds threshold
91
- # @raise [Anzen::CheckFailedError] if memory reading fails
92
43
  def check!
93
44
  return nil unless @enabled
45
+ return nil unless should_check?
94
46
 
95
- begin
96
- if should_check?
97
- @current_rss_mb = read_process_memory_mb
98
- @last_check_time = Time.now
99
-
100
- if @current_rss_mb > @limit_mb
101
- @violation_count += 1
102
- raise Anzen::MemoryLimitExceeded.new(@current_rss_mb, @limit_mb)
103
- end
104
- end
105
-
106
- nil
107
- rescue Anzen::MemoryLimitExceeded
108
- raise
109
- rescue StandardError => e
110
- raise Anzen::CheckFailedError.new(name, 'Failed to read process memory', e)
111
- end
47
+ rss_mb = read_process_memory_mb
48
+ @current_rss_mb = rss_mb
49
+ @last_check_time = Time.now
50
+
51
+ return nil unless @current_rss_mb > @limit_mb
52
+
53
+ @violation_count += 1
54
+ raise Anzen::MemoryLimitExceeded.new(@current_rss_mb, @limit_mb)
55
+ rescue Anzen::MemoryLimitExceeded
56
+ raise
57
+ rescue StandardError => e
58
+ raise Anzen::CheckFailedError.new(name, 'Failed to read process memory', e)
112
59
  end
113
60
 
114
- # Return current status
115
- #
116
- # @return [Hash] status hash with keys: name, enabled, thresholds, last_check, violations
117
61
  def status
118
62
  {
119
63
  name: name,
@@ -127,21 +71,15 @@ module Anzen
127
71
  }
128
72
  end
129
73
 
130
- # Return human-readable one-liner for CLI
131
- #
132
- # @return [String]
133
74
  def to_cli
134
- status_text = @enabled ? 'enabled' : 'disabled'
135
- "Memory monitor (#{status_text}): limit=#{@limit_mb}MB, current=#{@current_rss_mb.round(1)}MB, violations=#{@violation_count}"
75
+ state = @enabled ? 'enabled' : 'disabled'
76
+ "Memory monitor (#{state}): limit=#{@limit_mb}MB, current=#{@current_rss_mb}MB, violations=#{@violation_count}"
136
77
  end
137
78
 
138
79
  private
139
80
 
140
- # Validate configuration parameters
141
- #
142
- # @raise [Anzen::ConfigurationError] if configuration is invalid
143
81
  def validate_configuration!
144
- unless @limit_mb.is_a?(Integer) && @limit_mb > 0
82
+ unless @limit_mb.is_a?(Integer) && @limit_mb.positive?
145
83
  raise Anzen::ConfigurationError, "limit_mb must be a positive integer, got: #{@limit_mb.inspect}"
146
84
  end
147
85
 
@@ -151,74 +89,43 @@ module Anzen
151
89
  "sampling_interval_ms must be a non-negative integer, got: #{@sampling_interval_ms.inspect}"
152
90
  end
153
91
 
154
- # Check if enough time has elapsed since last check
155
- #
156
- # @return [Boolean] true if should perform check
157
92
  def should_check?
93
+ return false unless @enabled
158
94
  return true if @last_check_time.nil?
159
95
 
160
96
  elapsed_ms = (Time.now - @last_check_time) * 1000
161
97
  elapsed_ms >= @sampling_interval_ms
162
98
  end
163
99
 
164
- # Read current process memory usage in MB
165
- #
166
- # Attempts to read RSS from /proc/[pid]/status (Linux) or falls back to
167
- # parsing `ps` command output for cross-platform compatibility.
168
- #
169
- # @return [Float] memory usage in MB
170
- # @raise [StandardError] if memory reading fails
171
100
  def read_process_memory_mb
172
101
  pid = Process.pid
173
-
174
- # Try Linux /proc filesystem first (most efficient)
175
102
  return read_memory_from_proc(pid) if File.exist?("/proc/#{pid}/status")
176
103
 
177
- # Fallback to ps command (cross-platform)
178
104
  read_memory_from_ps(pid)
179
105
  end
180
106
 
181
- # Read memory from Linux /proc/[pid]/status
182
- #
183
- # @param pid [Integer] process ID
184
- # @return [Float] memory in MB
185
107
  def read_memory_from_proc(pid)
186
108
  status_file = "/proc/#{pid}/status"
187
109
  content = File.read(status_file)
110
+ match = content.match(/^VmRSS:\s+(\d+)\s+kB/)
111
+ raise "Could not find VmRSS in #{status_file}" unless match
188
112
 
189
- # Find VmRSS line: "VmRSS: 12345 kB"
190
- vmrss_match = content.match(/^VmRSS:\s+(\d+)\s+kB/)
191
- raise "Could not find VmRSS in #{status_file}" unless vmrss_match
192
-
193
- kb = vmrss_match[1].to_i
194
- kb / 1024.0 # Convert to MB
113
+ match[1].to_i / 1024.0
195
114
  end
196
115
 
197
- # Read memory using ps command (fallback for non-Linux systems)
198
- #
199
- # @param pid [Integer] process ID
200
- # @return [Float] memory in MB
201
116
  def read_memory_from_ps(pid)
202
- # Use ps to get RSS in KB, then convert to MB
203
- # Format: "PID RSS" where RSS is in KB
204
117
  output, status = run_ps_command(pid)
205
118
  raise "ps command failed: #{status.exitstatus}" unless status.success?
206
119
 
207
120
  lines = output.strip.split("\n")
208
121
  raise "ps output incomplete for PID #{pid}" if lines.length < 2
209
122
 
210
- # Second line contains the data
211
123
  fields = lines[1].strip.split
212
124
  raise "ps output format unexpected: #{lines[1]}" if fields.length < 2
213
125
 
214
- rss_kb = fields[1].to_i
215
- rss_kb / 1024.0 # Convert to MB
126
+ fields[1].to_i / 1024.0
216
127
  end
217
128
 
218
- # Execute ps command (extracted for testability)
219
- #
220
- # @param pid [Integer] process ID
221
- # @return [Array<String, Process::Status>] output and status
222
129
  def run_ps_command(pid)
223
130
  output = `ps -o pid,rss -p #{pid} 2>/dev/null`
224
131
  [output, $?]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Anzen
4
6
  module Monitors
5
7
  # Monitor that detects any recursive method calls (pattern-based)
@@ -24,6 +26,9 @@ module Anzen
24
26
  # @return [Integer] count of violations detected
25
27
  attr_reader :violation_count
26
28
 
29
+ # @return [String] monitor name used throughout registry/CLI
30
+ attr_reader :name
31
+
27
32
  # Initialize RecursionMonitor
28
33
  #
29
34
  # @param depth_limit [Integer] maximum allowed recursion depth (default: 1000)
@@ -32,27 +37,30 @@ module Anzen
32
37
  @violation_count = 0
33
38
  @depth_limit = depth_limit
34
39
  @last_check = nil
40
+ @trace_point = nil
41
+ @call_stack = nil
42
+ @name = 'recursion'
35
43
  end
36
44
 
37
- # Monitor name
38
- #
39
- # @return [String] "recursion"
40
- def name
41
- 'recursion'
42
- end
43
-
44
- # Enable this monitor
45
45
  #
46
46
  # @return [Boolean] true
47
47
  def enable
48
+ return true if @enabled
49
+
48
50
  @enabled = true
51
+ start_trace_point
52
+ true
49
53
  end
50
54
 
51
55
  # Disable this monitor
52
56
  #
53
57
  # @return [Boolean] false
54
58
  def disable
59
+ return false unless @enabled
60
+
55
61
  @enabled = false
62
+ stop_trace_point
63
+ false
56
64
  end
57
65
 
58
66
  # Check if monitor is enabled
@@ -64,10 +72,8 @@ module Anzen
64
72
 
65
73
  # Check for recursion pattern
66
74
  #
67
- # Analyzes the current call stack to detect if any method appears
68
- # multiple times (direct recursion) or if there's a cycle in the call chain
69
- # (indirect recursion). Raises RecursionLimitExceeded if recursion is
70
- # detected and the call stack depth exceeds the configured limit.
75
+ # In real-time mode, this is a no-op since monitoring happens automatically.
76
+ # For compatibility, it checks current state if called manually or in test mode.
71
77
  #
72
78
  # @return [nil]
73
79
  # @raise [Anzen::RecursionLimitExceeded] if recursion detected and depth exceeds limit
@@ -76,19 +82,21 @@ module Anzen
76
82
  return nil unless @enabled
77
83
 
78
84
  begin
79
- current_depth = caller.length
80
85
  @last_check = Time.now
81
-
82
- if recursion_detected? && current_depth > @depth_limit
83
- @violation_count += 1
84
- raise Anzen::RecursionLimitExceeded.new(current_depth, @depth_limit)
86
+ # In real-time mode, violations are raised immediately in the trace point
87
+ # In test mode or manual check, perform the check here
88
+ if @trace_point.nil? || !@trace_point.enabled?
89
+ current_depth = caller.length
90
+ if recursion_detected? && current_depth > @depth_limit
91
+ @violation_count += 1
92
+ raise Anzen::RecursionLimitExceeded.new(current_depth, @depth_limit)
93
+ end
85
94
  end
86
-
87
95
  nil
88
96
  rescue Anzen::RecursionLimitExceeded
89
97
  raise
90
98
  rescue StandardError => e
91
- raise Anzen::CheckFailedError.new(name, 'Failed to detect recursion pattern', e)
99
+ raise Anzen::CheckFailedError.new(name, 'Failed to check recursion', e)
92
100
  end
93
101
  end
94
102
 
@@ -112,11 +120,46 @@ module Anzen
112
120
  # @return [String]
113
121
  def to_cli
114
122
  status_text = @enabled ? 'enabled' : 'disabled'
115
- "Recursion monitor (#{status_text}): violations=#{@violation_count}"
123
+ "Recursion monitor (#{status_text}): limit=#{@depth_limit}, violations=#{@violation_count}"
116
124
  end
117
125
 
118
126
  private
119
127
 
128
+ # Start the trace point for real-time monitoring
129
+ def start_trace_point
130
+ return if ENV['RACK_ENV'] == 'test' || ENV['RAILS_ENV'] == 'test' || defined?(RSpec)
131
+
132
+ @call_stack = Thread.current[FRAME_KEY] ||= []
133
+ @trace_point = TracePoint.new(:call, :return) do |tp|
134
+ next unless @enabled
135
+
136
+ case tp.event
137
+ when :call
138
+ context = extract_call_context_from_tp(tp.path, tp.lineno, tp.method_id.to_s)
139
+ next unless context
140
+
141
+ if @call_stack.include?(context)
142
+ current_depth = @call_stack.size + 1
143
+ if current_depth > @depth_limit
144
+ @violation_count += 1
145
+ raise Anzen::RecursionLimitExceeded.new(current_depth, @depth_limit)
146
+ end
147
+ end
148
+ @call_stack.push(context)
149
+ when :return
150
+ @call_stack.pop if @call_stack&.last
151
+ end
152
+ end
153
+ @trace_point.enable
154
+ end
155
+
156
+ # Stop the trace point
157
+ def stop_trace_point
158
+ @trace_point&.disable
159
+ @trace_point = nil
160
+ Thread.current[FRAME_KEY] = nil
161
+ end
162
+
120
163
  # Detect if current call stack has recursion pattern
121
164
  #
122
165
  # Uses call context (file:line:method) for application frames to avoid
@@ -182,6 +225,13 @@ module Anzen
182
225
  "#{file_line}:#{method}"
183
226
  end
184
227
 
228
+ # Extract call context from trace point
229
+ def extract_call_context_from_tp(path, lineno, method_id)
230
+ return nil if should_skip_frame?("#{path}:#{lineno}:in `#{method_id}'")
231
+
232
+ "#{path}:#{lineno}:#{method_id}"
233
+ end
234
+
185
235
  # Get current call stack depth for error reporting
186
236
  #
187
237
  # @return [Integer]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Anzen
4
6
  # Registry for managing safety monitors
5
7
  #
data/lib/anzen/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Anzen
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/anzen.rb CHANGED
@@ -70,18 +70,20 @@ module Anzen
70
70
  raise Anzen::InitializationError if @@initialized
71
71
 
72
72
  @@registry = Registry.new
73
+ config = config ? config.dup : {}
73
74
 
74
75
  # Determine configuration source
75
76
  configuration = if config.key?(:config_file)
76
77
  Configuration.from_file(config[:config_file])
77
- elsif ENV['ANZEN_CONFIG']
78
+ elsif config.empty? && ENV['ANZEN_CONFIG']
78
79
  Configuration.from_env
79
80
  else
80
81
  Configuration.programmatic(config)
81
82
  end
82
83
 
83
84
  # Register CallStackDepthMonitor
84
- depth_limit = 1000
85
+ default_depth_limit = 1000
86
+ depth_limit = default_depth_limit
85
87
  begin
86
88
  depth_limit = configuration.monitor_config('call_stack_depth')['depth_limit']
87
89
  rescue Anzen::ConfigurationError
@@ -98,7 +100,7 @@ module Anzen
98
100
  rescue Anzen::ConfigurationError
99
101
  # Use defaults if not configured
100
102
  end
101
- depth_limit = recursion_config['depth_limit'] || 1000
103
+ depth_limit = recursion_config['depth_limit'] || default_depth_limit
102
104
 
103
105
  recursion_monitor = Monitors::RecursionMonitor.new(depth_limit: depth_limit)
104
106
  @@registry.register(recursion_monitor)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anzen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Korakot Leemakdej
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-17 00:00:00.000000000 Z
11
+ date: 2025-11-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Anzen detects and prevents bad code patterns (recursive call stacks,
14
14
  memory overflow) before they crash your production system. Provides pluggable monitors