cc 1.2.0 → 1.2.1

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/cc.rb +1 -0
  3. data/trace.rb +327 -0
  4. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d516f93659f22c9c1cc96949b6b28a18f9b0a4488df88d0534b93e02d384ec1
4
- data.tar.gz: cacf6949dfacd5676274f8189eb6f103ab10bbd8449e97d5ed719647c39fbb83
3
+ metadata.gz: 4fc0484d6d2387f8043520b7981b52be8968f1a233cf5e110395a2dcc9ab5fa8
4
+ data.tar.gz: 88f19e9a08e517ccdebb8d9f5d8dbb8afec0dd675d6b0c6df057c40703d75993
5
5
  SHA512:
6
- metadata.gz: 439bf5ca21f7ebc3ae75e9f15864e739554aed7d1020a686a1890bf75defcf7e2ce338ae9f47266eda782c8cb8589ae79b9c9581aa0bef7b9bebdebb3305a0d6
7
- data.tar.gz: 82b49a5413e171490cb09f9ba0057c5efa39642c7c4814b1463fd90b272225d52e547bb23b87188797358f42f14d92a5d953aa11af6acf9e6141f816fde196ea
6
+ metadata.gz: 371bdf3d14c78623c029bd58aff0002383f256dc9346ab5f6586b0ad7920690c9698338d1d73ccbffcab07019d0c9a132ced0365d6f7198c3c7040371329900e
7
+ data.tar.gz: 874c41883cb25ddf44790b37d8b7589ef5ff87bb07e25d5d7908f2553dedb3b9cc96af751a7886902839e3e7084931cdd1a30b547c8c6c43a63721bf0b4af6b8
data/cc.rb CHANGED
@@ -15,6 +15,7 @@ $modlist = [
15
15
  'regexp',
16
16
  'string',
17
17
  'tree',
18
+ 'trace',
18
19
  'logic'
19
20
  ]
20
21
 
data/trace.rb ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # encoding: UTF-8
4
+
5
+ =begin
6
+ | 关键特性 | 说明                                |
7
+ |---------|---------------------------------------------------------------|
8
+ | 追踪功能 | TracePoint 是 Ruby 内置的追踪 API,性能比 `set_trace_func` 更好 |
9
+ | 层级缩进 | 通过 `call_depth` 显示调用层级,便于阅读 |
10
+ | 对象信息 | 记录类名、object_id、inspect 结果、集合 size 等 |
11
+ | 局部变量 | 在每一行记录当前的局部变量状态 |
12
+ | 参数追踪 | 方法调用时记录传入的参数 |
13
+ | 源码显示 | 显示当前执行的源代码行 |
14
+ | 安全截断 | 超长字符串自动截断,防止日志爆炸 |
15
+
16
+ ※注意: `trace_lines: true` 会追踪每一行代码,性能开销较大,适合调试小段代码;只追踪方法调用(默认)性能更好,适合生产环境追踪。
17
+
18
+ # 使用方法:
19
+ require 'cc'
20
+ CC.use 'trace'
21
+ ExecutionTracer.enable! # log_path: 'execution.log', trace_lines: false, max_depth: 100
22
+ hello(:world)
23
+ ExecutionTracer.disable!
24
+
25
+ # 设置环境变量调用:
26
+ `EXECUTION_TRACER_AUTO_START=true EXECUTION_TRACER_LOG=my_trace.log EXECUTION_TRACER_TRACE_LINES=true ruby 自定义脚本.rb`
27
+ =end
28
+
29
+ require 'json'
30
+ require 'date'
31
+
32
+ module ExecutionTracer
33
+ class << self
34
+ attr_reader :logger, :enabled, :log_file, :call_depth
35
+
36
+ def enable!(log_path: 'execution.log', trace_lines: false, max_depth: 100)
37
+ return if @enabled
38
+
39
+ time = Time.now
40
+ @logger = []
41
+ @logger << {
42
+ 'time' => time,
43
+ 'event' => "Execution Trace Start",
44
+ 'version' => RUBY_VERSION,
45
+ 'process-id' => Process.pid,
46
+ 'log-path' => log_path
47
+ }
48
+
49
+ @log_file = File.open(log_path, 'a')
50
+ @log_file.puts "\n" + "="*80
51
+ @log_file.puts "[#{time}] Execution Trace Start"
52
+ @log_file.puts "Ruby Version: #{RUBY_VERSION}"
53
+ @log_file.puts "Process PID: #{Process.pid}"
54
+ @log_file.puts "="*80 + "\n"
55
+ @log_file.flush
56
+
57
+ @enabled = true
58
+ @call_depth = 0
59
+ @max_depth = max_depth
60
+
61
+ setup_tracepoint(trace_lines)
62
+
63
+ puts "[ExecutionTracer] 已启用,日志写入: #{File.absolute_path(log_path)}"
64
+ end
65
+
66
+ def disable!
67
+ return unless @enabled
68
+
69
+ time = Time.now
70
+ @logger ||= []
71
+ @logger << {
72
+ 'time' => time,
73
+ 'event' => "Execution Trace End",
74
+ 'process-id' => Process.pid
75
+ }
76
+
77
+ @tracepoint&.disable
78
+ @enabled = false
79
+
80
+ @log_file.puts "\n" + "="*80
81
+ @log_file.puts "[#{time}] Execution Trace End"
82
+ @log_file.puts "="*80 + "\n"
83
+ @log_file.close
84
+
85
+ puts "[ExecutionTracer] 已禁用,日志已保存"
86
+ end
87
+
88
+ def log(message)
89
+ return unless @enabled && @log_file
90
+ @log_file.puts(message.encode("UTF-8"))
91
+ @log_file.flush
92
+ end
93
+
94
+ private
95
+
96
+ def setup_tracepoint(trace_lines)
97
+ events = [:call, :return, :c_call, :c_return]
98
+ events << :line if trace_lines
99
+
100
+ @tracepoint = TracePoint.new(*events) do |tp|
101
+ next if internal_file?(tp.path)
102
+ next if @call_depth > @max_depth
103
+
104
+ case tp.event
105
+ when :call, :c_call
106
+ @call_depth += 1
107
+ log_method_call(tp)
108
+ when :return, :c_return
109
+ log_method_return(tp)
110
+ @call_depth -= 1
111
+ when :line
112
+ log_line_execution(tp)
113
+ end
114
+ end
115
+
116
+ @tracepoint.enable
117
+ end
118
+
119
+ def log_method_call(tp)
120
+ receiver = tp.self
121
+ method_name = tp.method_id
122
+ file = tp.path
123
+ line = tp.lineno
124
+ defined_class = tp.defined_class
125
+
126
+ # 收集对象信息
127
+ object_info = extract_object_info(receiver)
128
+
129
+ # 收集参数信息(如果可能)
130
+ args_info = extract_arguments(tp.binding) if tp.binding
131
+
132
+ indent = " " * [@call_depth - 1, 0].max
133
+ log_message = format_log_entry(
134
+ event: "CALL",
135
+ indent: indent,
136
+ file: file,
137
+ line: line,
138
+ method: "#{defined_class}##{method_name}",
139
+ object: object_info,
140
+ args: args_info
141
+ )
142
+
143
+ log(log_message)
144
+ end
145
+
146
+ def log_method_return(tp)
147
+ receiver = tp.self
148
+ method_name = tp.method_id
149
+ file = tp.path
150
+ line = tp.lineno
151
+
152
+ object_info = extract_object_info(receiver)
153
+ return_value = safe_inspect(tp.return_value) if tp.respond_to?(:return_value)
154
+
155
+ indent = " " * [@call_depth - 1, 0].max
156
+ log_message = format_log_entry(
157
+ event: "RETURN",
158
+ indent: indent,
159
+ file: file,
160
+ line: line,
161
+ method: "#{tp.defined_class}##{method_name}",
162
+ object: object_info,
163
+ return: return_value
164
+ )
165
+
166
+ log(log_message)
167
+ end
168
+
169
+ def log_line_execution(tp)
170
+ file = tp.path
171
+ line = tp.lineno
172
+
173
+ # 获取当前行的代码(如果文件可读)
174
+ source_line = read_source_line(file, line)
175
+
176
+ # 获取局部变量信息
177
+ local_vars = extract_local_variables(tp.binding) if tp.binding
178
+
179
+ log_message = format_log_entry(
180
+ event: "LINE",
181
+ indent: " " * (@call_depth < 0 ? @call_depth*(-1) : @call_depth),
182
+ file: file,
183
+ line: line,
184
+ source: source_line,
185
+ locals: local_vars
186
+ )
187
+
188
+ log(log_message)
189
+ end
190
+
191
+ def extract_object_info(obj)
192
+ info = {
193
+ class: obj.class.name,
194
+ object_id: obj.object_id,
195
+ inspect: safe_inspect(obj)
196
+ }
197
+
198
+ # 如果是集合类型,记录大小
199
+ if obj.respond_to?(:size)
200
+ info[:size] = obj.size rescue "N/A"
201
+ end
202
+
203
+ # 如果是字符串,记录长度
204
+ if obj.is_a?(String)
205
+ info[:length] = obj.length
206
+ info[:encoding] = obj.encoding.name
207
+ end
208
+
209
+ # 如果是数字,记录值
210
+ if obj.is_a?(Numeric)
211
+ info[:value] = obj
212
+ end
213
+
214
+ info
215
+ rescue => e
216
+ { error: "无法提取对象信息: #{e.message}" }
217
+ end
218
+
219
+ def extract_arguments(binding_context)
220
+ return nil unless binding_context
221
+
222
+ # 尝试获取方法参数(Ruby 2.7+ 支持)
223
+ if binding_context.respond_to?(:local_variables)
224
+ vars = binding_context.local_variables
225
+ args = {}
226
+ vars.each do |var|
227
+ begin
228
+ args[var] = safe_inspect(binding_context.local_variable_get(var))
229
+ rescue
230
+ args[var] = "<unreadable>"
231
+ end
232
+ end
233
+ args
234
+ end
235
+ rescue => e
236
+ { error: e.message }
237
+ end
238
+
239
+ def extract_local_variables(binding_context)
240
+ return nil unless binding_context
241
+
242
+ vars = {}
243
+ binding_context.local_variables.each do |var|
244
+ begin
245
+ value = binding_context.local_variable_get(var)
246
+ vars[var] = {
247
+ class: value.class.name,
248
+ inspect: safe_inspect(value)[0..100] # 限制长度
249
+ }
250
+ rescue => e
251
+ vars[var] = { error: e.message }
252
+ end
253
+ end
254
+ vars
255
+ rescue => e
256
+ { error: e.message }
257
+ end
258
+
259
+ def format_log_entry(**data)
260
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
261
+ indent = data.delete(:indent) || ""
262
+ event = data.delete(:event)
263
+
264
+ @logger ||= []
265
+ record = {"time"=>timestamp, "indent"=>indent.strip, "event"=>event}
266
+
267
+ # 构建格式化的日志行
268
+ lines = ["[#{timestamp}] #{indent}[#{event}]"]
269
+
270
+ data.each do |key, value|
271
+ next if value.nil?
272
+
273
+ formatted_value = case value
274
+ when Hash
275
+ value.map { |k, v| "#{k}=#{truncate(v.to_s, 80)}" }.join(", ")
276
+ when String
277
+ truncate(value, 100)
278
+ else
279
+ truncate(value.to_s, 100)
280
+ end
281
+
282
+ record[key] = formatted_value
283
+ lines << " #{key}: #{formatted_value}"
284
+ end
285
+
286
+ @logger << record
287
+ lines.map{|line|line.encode("UTF-8")}.join("\n")
288
+ end
289
+
290
+ def read_source_line(file, line_num)
291
+ return nil unless File.exist?(file)
292
+
293
+ File.readlines(file)[line_num - 1]&.strip
294
+ rescue => e
295
+ "<无法读取: #{e.message}>"
296
+ end
297
+
298
+ def safe_inspect(obj)
299
+ obj.inspect
300
+ rescue => e
301
+ "<inspect error: #{e.message}>"
302
+ end
303
+
304
+ def truncate(str, max_len)
305
+ str.length > max_len ? str[0...max_len] + "..." : str
306
+ end
307
+
308
+ def internal_file?(path)
309
+ return true if path.nil? || path.empty?
310
+ return true if path.start_with?('<internal:')
311
+ return true if path.include?('/ruby/')
312
+ return true if path.include?('execution_tracer.rb') # 忽略追踪器本身
313
+ false
314
+ end
315
+ end
316
+ end
317
+
318
+ # 自动启动(如果设置了环境变量)
319
+ if ENV['EXECUTION_TRACER_AUTO_START']
320
+ ExecutionTracer.enable!(
321
+ log_path: ENV['EXECUTION_TRACER_LOG'] || 'execution.log',
322
+ trace_lines: ENV['EXECUTION_TRACER_TRACE_LINES'] == 'true'
323
+ )
324
+ end
325
+
326
+ # 注册退出钩子
327
+ at_exit { ExecutionTracer.disable! if ExecutionTracer.enabled }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt
@@ -34,6 +34,7 @@ files:
34
34
  - regexp.rb
35
35
  - shell-tools.rb
36
36
  - string.rb
37
+ - trace.rb
37
38
  - tree.rb
38
39
  homepage: https://github.com/ChenMeng1365/custom-core
39
40
  licenses: