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.
- checksums.yaml +4 -4
- data/cc.rb +1 -0
- data/trace.rb +327 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4fc0484d6d2387f8043520b7981b52be8968f1a233cf5e110395a2dcc9ab5fa8
|
|
4
|
+
data.tar.gz: 88f19e9a08e517ccdebb8d9f5d8dbb8afec0dd675d6b0c6df057c40703d75993
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 371bdf3d14c78623c029bd58aff0002383f256dc9346ab5f6586b0ad7920690c9698338d1d73ccbffcab07019d0c9a132ced0365d6f7198c3c7040371329900e
|
|
7
|
+
data.tar.gz: 874c41883cb25ddf44790b37d8b7589ef5ff87bb07e25d5d7908f2553dedb3b9cc96af751a7886902839e3e7084931cdd1a30b547c8c6c43a63721bf0b4af6b8
|
data/cc.rb
CHANGED
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.
|
|
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:
|