cc 1.1.5 → 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 +2 -0
- data/shell-tools.rb +264 -0
- data/trace.rb +327 -0
- metadata +3 -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/shell-tools.rb
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
require 'io/console'
|
|
5
|
+
|
|
6
|
+
class ProgressBar
|
|
7
|
+
# 预设主题
|
|
8
|
+
THEMES = {
|
|
9
|
+
classic: { fill: '█', empty: '░', left: '[', right: ']', arrow: '>' },
|
|
10
|
+
modern: { fill: '●', empty: '○', left: '', right: '', arrow: '▶' },
|
|
11
|
+
blocks: { fill: '■', empty: '□', left: '│', right: '│', arrow: '▶' },
|
|
12
|
+
dots: { fill: '◉', empty: '◎', left: '⟨', right: '⟩', arrow: '→' },
|
|
13
|
+
minimal: { fill: '=', empty: '-', left: '[', right: ']', arrow: '>' },
|
|
14
|
+
hearts: { fill: '♥', empty: '♡', left: ' ', right: ' ', arrow: '💕' }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
attr_reader :total, :current, :start_time
|
|
18
|
+
|
|
19
|
+
def initialize(total, title: "Processing", theme: :modern, width: 40, show_percentage: true, show_eta: true, color: true)
|
|
20
|
+
@total = total
|
|
21
|
+
@current = 0
|
|
22
|
+
@title = title
|
|
23
|
+
@theme = THEMES[theme] || THEMES[:modern]
|
|
24
|
+
@width = width
|
|
25
|
+
@show_percentage = show_percentage
|
|
26
|
+
@show_eta = show_eta
|
|
27
|
+
@color = color
|
|
28
|
+
@start_time = Time.now
|
|
29
|
+
@last_update = 0
|
|
30
|
+
@mutex = Mutex.new
|
|
31
|
+
|
|
32
|
+
# ANSI 颜色代码
|
|
33
|
+
@colors = {
|
|
34
|
+
reset: "\e[0m",
|
|
35
|
+
bold: "\e[1m",
|
|
36
|
+
green: "\e[32m",
|
|
37
|
+
yellow: "\e[33m",
|
|
38
|
+
blue: "\e[34m",
|
|
39
|
+
magenta: "\e[35m",
|
|
40
|
+
cyan: "\e[36m",
|
|
41
|
+
gray: "\e[90m"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# 隐藏光标
|
|
45
|
+
print "\e[?25l"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 更新进度
|
|
49
|
+
def update(step = 1)
|
|
50
|
+
@mutex.synchronize do
|
|
51
|
+
@current += step
|
|
52
|
+
@current = @total if @current > @total
|
|
53
|
+
refresh if should_refresh?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 直接设置进度
|
|
58
|
+
def set(value)
|
|
59
|
+
@mutex.synchronize do
|
|
60
|
+
@current = [[value, @total].min, 0].max
|
|
61
|
+
refresh
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 完成进度条
|
|
66
|
+
def finish(message: "Done!")
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
@current = @total
|
|
69
|
+
refresh
|
|
70
|
+
puts # 换行
|
|
71
|
+
show_cursor
|
|
72
|
+
puts colorize(message, :green) unless message.empty?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# 显示统计信息
|
|
77
|
+
def stats
|
|
78
|
+
elapsed = Time.now - @start_time
|
|
79
|
+
rate = @current / elapsed rescue 0
|
|
80
|
+
{
|
|
81
|
+
total: @total,
|
|
82
|
+
current: @current,
|
|
83
|
+
percentage: (@current.to_f / @total * 100).round(1),
|
|
84
|
+
elapsed: format_time(elapsed),
|
|
85
|
+
eta: format_time(eta),
|
|
86
|
+
rate: rate.round(2)
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# 清理资源
|
|
91
|
+
def dispose
|
|
92
|
+
show_cursor
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def should_refresh?
|
|
98
|
+
now = Time.now.to_f
|
|
99
|
+
return false if now - @last_update < 0.05 # 限制刷新率 20fps
|
|
100
|
+
@last_update = now
|
|
101
|
+
true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def refresh
|
|
105
|
+
percentage = @current.to_f / @total
|
|
106
|
+
filled = (percentage * @width).round
|
|
107
|
+
empty = @width - filled
|
|
108
|
+
|
|
109
|
+
bar = @theme[:fill] * filled +
|
|
110
|
+
(@current < @total ? @theme[:arrow] : @theme[:fill]) +
|
|
111
|
+
@theme[:empty] * [empty - 1, 0].max
|
|
112
|
+
|
|
113
|
+
# 构建输出字符串
|
|
114
|
+
parts = []
|
|
115
|
+
|
|
116
|
+
# 标题
|
|
117
|
+
parts << colorize(sprintf("%-15s", @title), :bold)
|
|
118
|
+
|
|
119
|
+
# 进度条
|
|
120
|
+
bar_color = percentage >= 1.0 ? :green : (percentage > 0.5 ? :cyan : :yellow)
|
|
121
|
+
parts << "#{@theme[:left]}#{colorize(bar, bar_color)}#{@theme[:right]}"
|
|
122
|
+
|
|
123
|
+
# 百分比
|
|
124
|
+
if @show_percentage
|
|
125
|
+
parts << colorize(sprintf("%6.1f%%", percentage * 100), :magenta)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# 计数
|
|
129
|
+
parts << colorize("[#{@current}/#{@total}]", :gray)
|
|
130
|
+
|
|
131
|
+
# ETA
|
|
132
|
+
if @show_eta && @current > 0 && @current < @total
|
|
133
|
+
parts << colorize("ETA: #{format_time(eta)}", :blue)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# 清除行并输出
|
|
137
|
+
print "\r\e[K" + parts.join(' ')
|
|
138
|
+
$stdout.flush
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def eta
|
|
142
|
+
return 0 if @current == 0
|
|
143
|
+
elapsed = Time.now - @start_time
|
|
144
|
+
remaining = @total - @current
|
|
145
|
+
(elapsed / @current) * remaining
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def format_time(seconds)
|
|
149
|
+
return "0s" if seconds < 1
|
|
150
|
+
if seconds < 60
|
|
151
|
+
"#{seconds.round}s"
|
|
152
|
+
elsif seconds < 3600
|
|
153
|
+
"#{(seconds / 60).floor}m #{(seconds % 60).round}s"
|
|
154
|
+
else
|
|
155
|
+
"#{(seconds / 3600).floor}h #{((seconds % 3600) / 60).floor}m"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def colorize(text, color)
|
|
160
|
+
return text unless @color
|
|
161
|
+
"#{@colors[color]}#{text}#{@colors[:reset]}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def show_cursor
|
|
165
|
+
print "\e[?25h"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# 辅助方法(用于示例中的颜色输出)
|
|
170
|
+
def colorize(text, color)
|
|
171
|
+
colors = { green: "\e[32m", yellow: "\e[33m", gray: "\e[90m", reset: "\e[0m" }
|
|
172
|
+
"#{colors[color]}#{text}#{colors[:reset]}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
=begin
|
|
176
|
+
# ==================== 使用示例 ====================
|
|
177
|
+
puts "🚀 Ruby Fancy Progress Bar Demo"
|
|
178
|
+
puts "=" * 50
|
|
179
|
+
|
|
180
|
+
# 示例 1: 基础用法
|
|
181
|
+
puts "\n📊 示例 1: 基础文件处理"
|
|
182
|
+
bar = ProgressBar.new(100, title: "Downloading", theme: :modern)
|
|
183
|
+
100.times do |i|
|
|
184
|
+
sleep(0.03) # 模拟工作
|
|
185
|
+
bar.update
|
|
186
|
+
end
|
|
187
|
+
bar.finish(message: "✅ 下载完成!")
|
|
188
|
+
|
|
189
|
+
# 示例 2: 不同主题展示
|
|
190
|
+
puts "\n🎨 示例 2: 主题展示"
|
|
191
|
+
[:classic, :blocks, :dots, :minimal].each do |theme_name|
|
|
192
|
+
bar = ProgressBar.new(50, title: theme_name.to_s, theme: theme_name, width: 30)
|
|
193
|
+
50.times do
|
|
194
|
+
sleep(0.02)
|
|
195
|
+
bar.update
|
|
196
|
+
end
|
|
197
|
+
bar.finish(message: "")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# 示例 3: 批量任务处理(带错误处理)
|
|
201
|
+
puts "\n📁 示例 3: 批量文件处理"
|
|
202
|
+
files = (1..20).map { |n| "file_#{n}.txt" }
|
|
203
|
+
bar = ProgressBar.new(files.size, title: "Processing", theme: :hearts, color: true)
|
|
204
|
+
processed = []
|
|
205
|
+
files.each do |file|
|
|
206
|
+
sleep(0.1) # 模拟处理时间
|
|
207
|
+
processed << file
|
|
208
|
+
bar.update
|
|
209
|
+
# 模拟偶尔的错误
|
|
210
|
+
if rand < 0.1
|
|
211
|
+
print "\n⚠️ #{colorize("警告: #{file} 处理异常", :yellow)}"
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
bar.finish(message: "✨ 处理了 #{processed.size} 个文件")
|
|
215
|
+
|
|
216
|
+
# 示例 4: 嵌套进度条(多阶段任务)
|
|
217
|
+
puts "\n🏗️ 示例 4: 多阶段构建任务"
|
|
218
|
+
phases = [
|
|
219
|
+
{ name: "Compiling", items: 30 },
|
|
220
|
+
{ name: "Testing", items: 20 },
|
|
221
|
+
{ name: "Packaging", items: 15 }
|
|
222
|
+
]
|
|
223
|
+
phases.each do |phase|
|
|
224
|
+
puts "\n阶段: #{phase[:name]}"
|
|
225
|
+
bar = ProgressBar.new(phase[:items], title: phase[:name], theme: :modern)
|
|
226
|
+
phase[:items].times do
|
|
227
|
+
sleep(0.05)
|
|
228
|
+
bar.update
|
|
229
|
+
end
|
|
230
|
+
bar.finish(message: "")
|
|
231
|
+
end
|
|
232
|
+
puts colorize("🎉 所有阶段完成!", :green)
|
|
233
|
+
|
|
234
|
+
# 示例 5: 实时速度显示(大数据处理)
|
|
235
|
+
puts "\n⚡ 示例 5: 大数据处理(带实时统计)"
|
|
236
|
+
total_items = 1000
|
|
237
|
+
bar = ProgressBar.new(total_items, title: "BigData", theme: :blocks, width: 50)
|
|
238
|
+
total_items.times do |i|
|
|
239
|
+
sleep(0.01) # 模拟快速处理
|
|
240
|
+
# 每 100 个显示一次统计
|
|
241
|
+
if i % 100 == 0 && i > 0
|
|
242
|
+
s = bar.stats
|
|
243
|
+
print "\n #{colorize("速度: #{s[:rate]} items/s | 已用: #{s[:elapsed]} | 剩余: #{s[:eta]}", :gray)}"
|
|
244
|
+
end
|
|
245
|
+
bar.update
|
|
246
|
+
end
|
|
247
|
+
bar.finish
|
|
248
|
+
|
|
249
|
+
# 示例 6: 手动控制进度(非均匀任务)
|
|
250
|
+
puts "\n🎮 示例 6: 手动进度控制(非均匀任务)"
|
|
251
|
+
bar = ProgressBar.new(100, title: "Uploading", theme: :dots)
|
|
252
|
+
# 模拟不同大小的文件上传
|
|
253
|
+
uploads = [10, 25, 5, 30, 20, 10]
|
|
254
|
+
uploads.each_with_index do |size, idx|
|
|
255
|
+
sleep(0.3) # 模拟上传时间
|
|
256
|
+
bar.set(bar.current + size)
|
|
257
|
+
print "\n 上传了 chunk_#{idx + 1}.bin (#{size}MB)"
|
|
258
|
+
end
|
|
259
|
+
bar.finish(message: "上传完成")
|
|
260
|
+
# 确保光标显示
|
|
261
|
+
print "\e[?25h"
|
|
262
|
+
puts "\n" + "=" * 50
|
|
263
|
+
puts "✨ 所有演示完成! 你可以复制 ProgressBar 类到你的项目中使用。"
|
|
264
|
+
=end
|
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.1
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt
|
|
@@ -32,7 +32,9 @@ files:
|
|
|
32
32
|
- monkey-patch.rb
|
|
33
33
|
- number.rb
|
|
34
34
|
- regexp.rb
|
|
35
|
+
- shell-tools.rb
|
|
35
36
|
- string.rb
|
|
37
|
+
- trace.rb
|
|
36
38
|
- tree.rb
|
|
37
39
|
homepage: https://github.com/ChenMeng1365/custom-core
|
|
38
40
|
licenses:
|