net-connector 0.4.0 → 0.4.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -65
  3. data/README.md +88 -95
  4. data/docs/VERIFICATION.md +2 -1
  5. data/docs/architecture.md +144 -248
  6. data/lib/net/connector/device/base.rb +1 -0
  7. data/lib/net/connector/device/interface_description.rb +3 -0
  8. data/lib/net/connector/device/profile/builder.rb +236 -0
  9. data/lib/net/connector/device/profile.rb +2 -227
  10. data/lib/net/connector/device/running_config/strategy.rb +5 -0
  11. data/lib/net/connector/device/running_config.rb +5 -0
  12. data/lib/net/connector/engine/dialogue.rb +1 -0
  13. data/lib/net/connector/engine/errors.rb +2 -0
  14. data/lib/net/connector/engine/log.rb +13 -9
  15. data/lib/net/connector/engine/session.rb +1 -0
  16. data/lib/net/connector/netdisco/cli.rb +20 -20
  17. data/lib/net/connector/netdisco/device.rb +4 -8
  18. data/lib/net/connector/netdisco/fleet.rb +2 -48
  19. data/lib/net/connector/netdisco/plan.rb +77 -0
  20. data/lib/net/connector/netdisco/planner.rb +12 -23
  21. data/lib/net/connector/netdisco/worker.rb +31 -24
  22. data/lib/net/connector/operations/tftp/file_upload.rb +42 -0
  23. data/lib/net/connector/operations/tftp/strategy.rb +9 -11
  24. data/lib/net/connector/operations/topology/strategy.rb +14 -0
  25. data/lib/net/connector/operations/topology.rb +1 -0
  26. data/lib/net/connector/vendor/cisco_ios/running_config.rb +1 -0
  27. data/lib/net/connector/vendor/cisco_ios/topology.rb +9 -0
  28. data/lib/net/connector/vendor/cisco_nxos/running_config.rb +1 -0
  29. data/lib/net/connector/vendor/h3c/tftp_backup.rb +2 -18
  30. data/lib/net/connector/vendor/h3c/topology.rb +9 -0
  31. data/lib/net/connector/vendor/hillstone/running_config.rb +1 -0
  32. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +4 -0
  33. data/lib/net/connector/vendor/hillstone/topology.rb +9 -0
  34. data/lib/net/connector/vendor/huawei/tftp_backup.rb +2 -18
  35. data/lib/net/connector/vendor/palo_alto/running_config.rb +4 -0
  36. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +7 -5
  37. data/lib/net/connector/vendor/palo_alto/topology.rb +10 -0
  38. data/lib/net/connector/vendor/radware/tftp_backup.rb +2 -2
  39. data/lib/net/connector/vendor/radware/topology.rb +2 -0
  40. data/lib/net/connector/version.rb +1 -1
  41. metadata +26 -24
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ module Connector
5
+ class Profile
6
+ # 构造器把声明块中的领域语言转换为设备档案字段。
7
+ class Builder
8
+ # 保存继承值并提供 commands、prompts 等有限的声明入口。
9
+ def initialize(parent)
10
+ @values = parent.to_h
11
+ end
12
+
13
+ # 声明运行配置和保存配置命令。
14
+ def commands(&definition)
15
+ check_block!(:commands, definition)
16
+ commands = Commands.new(
17
+ @values[:config_commands], @values[:save_commands]
18
+ )
19
+ commands.instance_eval(&definition)
20
+ @values[:config_commands] = commands.config_commands
21
+ @values[:save_commands] = commands.save_commands
22
+ self
23
+ end
24
+
25
+ # 声明登录、命令、凭据和提权提示符。
26
+ def prompts(&definition)
27
+ check_block!(:prompts, definition)
28
+ Prompts.new(@values).instance_eval(&definition)
29
+ self
30
+ end
31
+
32
+ # 声明分页模式及自动翻页响应。
33
+ def pager(&definition)
34
+ check_block!(:pager, definition)
35
+ Pager.new(@values).instance_eval(&definition)
36
+ self
37
+ end
38
+
39
+ # 声明认证失败和命令失败的输出模式。
40
+ def errors(&definition)
41
+ check_block!(:errors, definition)
42
+ Errors.new(@values).instance_eval(&definition)
43
+ self
44
+ end
45
+
46
+ # 声明登录挑战和命令确认交互。
47
+ def interactions(&definition)
48
+ check_block!(:interactions, definition)
49
+ interactions = Interactions.new(@values)
50
+ interactions.instance_eval(&definition)
51
+ @values[:login_interactions] = interactions.login_interactions
52
+ @values[:confirmation_interactions] = interactions.confirmation_interactions
53
+ self
54
+ end
55
+
56
+ # 声明进入特权模式所需的命令和提示符。
57
+ def privilege(&definition)
58
+ check_block!(:privilege, definition)
59
+ Privilege.new(@values).instance_eval(&definition)
60
+ self
61
+ end
62
+
63
+ # 声明旧版 SSH 协商参数。
64
+ def ssh(&definition)
65
+ check_block!(:ssh, definition)
66
+ Ssh.new(@values).instance_eval(&definition)
67
+ self
68
+ end
69
+
70
+ # 设置设备命令的默认超时秒数。
71
+ def command_timeout(seconds)
72
+ @values[:command_timeout] = seconds
73
+ self
74
+ end
75
+
76
+ # 设置避免设备自动换行的终端宽高。
77
+ def terminal_size(width, height = nil)
78
+ @values[:terminal_size] = height.nil? ? width : [width, height]
79
+ self
80
+ end
81
+
82
+ # 绑定已加载的策略类;nil 明确关闭继承的业务能力。
83
+ def tftp_strategy(klass)
84
+ @values[:tftp_strategy] = klass
85
+ self
86
+ end
87
+
88
+ # 指定运行配置采集策略,覆盖继承的厂商规则。
89
+ def running_config_strategy(klass)
90
+ @values[:running_config_strategy] = klass
91
+ self
92
+ end
93
+
94
+ # 指定邻居发现和描述下发策略,nil 表示关闭能力。
95
+ def topology_strategy(klass)
96
+ @values[:topology_strategy] = klass
97
+ self
98
+ end
99
+
100
+ # 将声明字段校验并构造成不可变档案。
101
+ def build = Profile.new(**@values)
102
+
103
+ private
104
+
105
+ # 块式声明入口必须有明确的子 DSL,避免静默忽略配置。
106
+ def check_block!(name, definition)
107
+ raise ArgumentError, "#{name} requires a block" unless definition
108
+ end
109
+
110
+ # 命令集合的声明对象。
111
+ class Commands
112
+ attr_reader :config_commands, :save_commands
113
+
114
+ # 复制继承的命令,后续修改不会污染父类档案。
115
+ def initialize(config_commands, save_commands)
116
+ @config_commands = config_commands&.dup
117
+ @save_commands = save_commands.dup
118
+ end
119
+
120
+ # 声明读取运行配置的命令序列。
121
+ def running_config(*commands)
122
+ @config_commands = commands.flatten
123
+ end
124
+
125
+ # 声明保存配置的命令序列。
126
+ def save_config(*commands)
127
+ @save_commands = commands.flatten
128
+ end
129
+ end
130
+
131
+ # 提示符的声明对象。
132
+ class Prompts
133
+ # 将登录、命令、用户名、密码和特权提示写入当前档案。
134
+ def initialize(values)
135
+ @values = values
136
+ end
137
+
138
+ # 声明登录结束提示符。
139
+ def login(pattern) = @values[:login_prompt] = pattern
140
+
141
+ # 声明普通命令结束提示符。
142
+ def command(pattern) = @values[:command_prompt] = pattern
143
+
144
+ # 声明用户名输入提示。
145
+ def username(pattern) = @values[:username_prompt] = pattern
146
+
147
+ # 声明密码输入提示。
148
+ def password(pattern) = @values[:password_prompt] = pattern
149
+
150
+ # 声明特权模式提示符。
151
+ def privilege(pattern) = @values[:privilege_prompt] = pattern
152
+ end
153
+
154
+ # 分页规则的声明对象。
155
+ class Pager
156
+ # 将分页模式和响应写入当前档案。
157
+ def initialize(values)
158
+ @values = values
159
+ end
160
+
161
+ # 声明分页提示匹配模式。
162
+ def pattern(value) = @values[:pager_pattern] = value
163
+
164
+ # 声明匹配分页提示后发送的字节。
165
+ def response(value) = @values[:pager_response] = value
166
+ end
167
+
168
+ # 设备错误模式的声明对象。
169
+ class Errors
170
+ # 将错误模式列表写入当前档案。
171
+ def initialize(values)
172
+ @values = values
173
+ end
174
+
175
+ # 替换认证失败模式列表。
176
+ def authentication(*patterns)
177
+ @values[:authentication_error_patterns] = patterns.flatten
178
+ end
179
+
180
+ # 替换命令失败模式列表。
181
+ def command(*patterns)
182
+ @values[:command_error_patterns] = patterns.flatten
183
+ end
184
+ end
185
+
186
+ # 登录和命令交互的声明对象。
187
+ class Interactions
188
+ attr_reader :login_interactions, :confirmation_interactions
189
+
190
+ # 复制继承的交互规则,子类可以在其上追加设备差异。
191
+ def initialize(values)
192
+ @login_interactions = values[:login_interactions].dup
193
+ @confirmation_interactions = values[:confirmation_interactions].dup
194
+ end
195
+
196
+ # 声明登录阶段的一次性应答。
197
+ def login(pattern, response:, sensitive: false, limit: 1, capture: true)
198
+ @login_interactions << Interaction.new(pattern, response, sensitive: sensitive,
199
+ limit: limit, capture: capture)
200
+ end
201
+
202
+ # 声明命令阶段的确认应答。
203
+ def confirm(pattern, response:, sensitive: false, limit: nil, capture: true)
204
+ @confirmation_interactions << Interaction.new(pattern, response, sensitive: sensitive,
205
+ limit: limit, capture: capture)
206
+ end
207
+ end
208
+
209
+ # 特权认证规则的声明对象。
210
+ class Privilege
211
+ # 将特权命令和结束提示写入当前档案。
212
+ def initialize(values)
213
+ @values = values
214
+ end
215
+
216
+ # 声明进入特权模式的设备命令。
217
+ def command(value) = @values[:privilege_command] = value
218
+
219
+ # 声明特权命令完成后的提示符。
220
+ def prompt(value) = @values[:privilege_prompt] = value
221
+ end
222
+
223
+ # SSH 兼容参数的声明对象。
224
+ class Ssh
225
+ # 将旧版 SSH 参数写入当前档案。
226
+ def initialize(values)
227
+ @values = values
228
+ end
229
+
230
+ # 声明连接旧版设备所需的参数序列。
231
+ def legacy_arguments(*arguments) = @values[:legacy_ssh_arguments] = arguments.flatten
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -110,233 +110,6 @@ module Net
110
110
  "save_commands=#{save_commands.size}>"
111
111
  end
112
112
 
113
- # 构造器把声明块中的领域语言转换为设备档案字段。
114
- class Builder
115
- # 保存继承值并提供 commands、prompts 等有限的声明入口。
116
- def initialize(parent)
117
- @values = parent.to_h
118
- end
119
-
120
- # 声明运行配置和保存配置命令。
121
- def commands(&definition)
122
- check_block!(:commands, definition)
123
- commands = Commands.new(
124
- @values[:config_commands], @values[:save_commands]
125
- )
126
- commands.instance_eval(&definition)
127
- @values[:config_commands] = commands.config_commands
128
- @values[:save_commands] = commands.save_commands
129
- self
130
- end
131
-
132
- # 声明登录、命令、凭据和提权提示符。
133
- def prompts(&definition)
134
- check_block!(:prompts, definition)
135
- Prompts.new(@values).instance_eval(&definition)
136
- self
137
- end
138
-
139
- # 声明分页模式及自动翻页响应。
140
- def pager(&definition)
141
- check_block!(:pager, definition)
142
- Pager.new(@values).instance_eval(&definition)
143
- self
144
- end
145
-
146
- # 声明认证失败和命令失败的输出模式。
147
- def errors(&definition)
148
- check_block!(:errors, definition)
149
- Errors.new(@values).instance_eval(&definition)
150
- self
151
- end
152
-
153
- # 声明登录挑战和命令确认交互。
154
- def interactions(&definition)
155
- check_block!(:interactions, definition)
156
- interactions = Interactions.new(@values)
157
- interactions.instance_eval(&definition)
158
- @values[:login_interactions] = interactions.login_interactions
159
- @values[:confirmation_interactions] = interactions.confirmation_interactions
160
- self
161
- end
162
-
163
- # 声明进入特权模式所需的命令和提示符。
164
- def privilege(&definition)
165
- check_block!(:privilege, definition)
166
- Privilege.new(@values).instance_eval(&definition)
167
- self
168
- end
169
-
170
- # 声明旧版 SSH 协商参数。
171
- def ssh(&definition)
172
- check_block!(:ssh, definition)
173
- Ssh.new(@values).instance_eval(&definition)
174
- self
175
- end
176
-
177
- # 设置设备命令的默认超时秒数。
178
- def command_timeout(seconds)
179
- @values[:command_timeout] = seconds
180
- self
181
- end
182
-
183
- # 设置避免设备自动换行的终端宽高。
184
- def terminal_size(width, height = nil)
185
- @values[:terminal_size] = height.nil? ? width : [width, height]
186
- self
187
- end
188
-
189
- # 绑定已加载的策略类;nil 明确关闭继承的业务能力。
190
- def tftp_strategy(klass)
191
- @values[:tftp_strategy] = klass
192
- self
193
- end
194
-
195
- def running_config_strategy(klass)
196
- @values[:running_config_strategy] = klass
197
- self
198
- end
199
-
200
- def topology_strategy(klass)
201
- @values[:topology_strategy] = klass
202
- self
203
- end
204
-
205
- # 将声明字段校验并构造成不可变档案。
206
- def build = Profile.new(**@values)
207
-
208
- private
209
-
210
- # 块式声明入口必须有明确的子 DSL,避免静默忽略配置。
211
- def check_block!(name, definition)
212
- raise ArgumentError, "#{name} requires a block" unless definition
213
- end
214
-
215
- # 命令集合的声明对象。
216
- class Commands
217
- attr_reader :config_commands, :save_commands
218
-
219
- # 复制继承的命令,后续修改不会污染父类档案。
220
- def initialize(config_commands, save_commands)
221
- @config_commands = config_commands&.dup
222
- @save_commands = save_commands.dup
223
- end
224
-
225
- # 声明读取运行配置的命令序列。
226
- def running_config(*commands)
227
- @config_commands = commands.flatten
228
- end
229
-
230
- # 声明保存配置的命令序列。
231
- def save_config(*commands)
232
- @save_commands = commands.flatten
233
- end
234
- end
235
-
236
- # 提示符的声明对象。
237
- class Prompts
238
- # 将登录、命令、用户名、密码和特权提示写入当前档案。
239
- def initialize(values)
240
- @values = values
241
- end
242
-
243
- # 声明登录结束提示符。
244
- def login(pattern) = @values[:login_prompt] = pattern
245
-
246
- # 声明普通命令结束提示符。
247
- def command(pattern) = @values[:command_prompt] = pattern
248
-
249
- # 声明用户名输入提示。
250
- def username(pattern) = @values[:username_prompt] = pattern
251
-
252
- # 声明密码输入提示。
253
- def password(pattern) = @values[:password_prompt] = pattern
254
-
255
- # 声明特权模式提示符。
256
- def privilege(pattern) = @values[:privilege_prompt] = pattern
257
- end
258
-
259
- # 分页规则的声明对象。
260
- class Pager
261
- # 将分页模式和响应写入当前档案。
262
- def initialize(values)
263
- @values = values
264
- end
265
-
266
- # 声明分页提示匹配模式。
267
- def pattern(value) = @values[:pager_pattern] = value
268
-
269
- # 声明匹配分页提示后发送的字节。
270
- def response(value) = @values[:pager_response] = value
271
- end
272
-
273
- # 设备错误模式的声明对象。
274
- class Errors
275
- # 将错误模式列表写入当前档案。
276
- def initialize(values)
277
- @values = values
278
- end
279
-
280
- # 追加认证失败模式。
281
- def authentication(*patterns)
282
- @values[:authentication_error_patterns] = patterns.flatten
283
- end
284
-
285
- # 追加命令失败模式。
286
- def command(*patterns)
287
- @values[:command_error_patterns] = patterns.flatten
288
- end
289
- end
290
-
291
- # 登录和命令交互的声明对象。
292
- class Interactions
293
- attr_reader :login_interactions, :confirmation_interactions
294
-
295
- # 复制继承的交互规则,子类可以在其上追加设备差异。
296
- def initialize(values)
297
- @login_interactions = values[:login_interactions].dup
298
- @confirmation_interactions = values[:confirmation_interactions].dup
299
- end
300
-
301
- # 声明登录阶段的一次性应答。
302
- def login(pattern, response:, sensitive: false, limit: 1, capture: true)
303
- @login_interactions << Interaction.new(pattern, response, sensitive: sensitive,
304
- limit: limit, capture: capture)
305
- end
306
-
307
- # 声明命令阶段的确认应答。
308
- def confirm(pattern, response:, sensitive: false, limit: nil, capture: true)
309
- @confirmation_interactions << Interaction.new(pattern, response, sensitive: sensitive,
310
- limit: limit, capture: capture)
311
- end
312
- end
313
-
314
- # 特权认证规则的声明对象。
315
- class Privilege
316
- # 将特权命令和结束提示写入当前档案。
317
- def initialize(values)
318
- @values = values
319
- end
320
-
321
- # 声明进入特权模式的设备命令。
322
- def command(value) = @values[:privilege_command] = value
323
-
324
- # 声明特权命令完成后的提示符。
325
- def prompt(value) = @values[:privilege_prompt] = value
326
- end
327
-
328
- # SSH 兼容参数的声明对象。
329
- class Ssh
330
- # 将旧版 SSH 参数写入当前档案。
331
- def initialize(values)
332
- @values = values
333
- end
334
-
335
- # 声明连接旧版设备所需的参数序列。
336
- def legacy_arguments(*arguments) = @values[:legacy_ssh_arguments] = arguments.flatten
337
- end
338
- end
339
-
340
113
  private
341
114
 
342
115
  # 校验静态接口,不构造策略实例或触发设备 I/O。
@@ -444,3 +217,5 @@ module Net
444
217
  end
445
218
  end
446
219
  end
220
+
221
+ require_relative "profile/builder"
@@ -6,10 +6,13 @@ module Net
6
6
  module Connector
7
7
  class RunningConfig
8
8
  class Strategy
9
+ # 保存策略所属设备,供提示符和厂商校验使用。
9
10
  def initialize(device) = @device = device
10
11
 
12
+ # 默认保留设备响应原文。
11
13
  def clean(text) = text
12
14
 
15
+ # 默认取脚本最后一个完成步骤作为运行配置。
13
16
  def result_step(result) = result.steps.last
14
17
 
15
18
  # 普通采集命令保持视图不变;需要切换视图的采集策略显式调整。
@@ -17,11 +20,13 @@ module Net
17
20
  TerminalRenderer.render(@device.current_prompt.to_s).lines.last.to_s.strip
18
21
  end
19
22
 
23
+ # 公共策略不增加逐命令响应检查。
20
24
  def check_response(_command, _response, _execution) end
21
25
  end
22
26
 
23
27
  # 使用终端行编辑规则渲染 H3C、Huawei 和 Radware 配置。
24
28
  class Rendered < Strategy
29
+ # 应用终端行编辑规则,还原设备最终显示的配置文本。
25
30
  def clean(text) = TerminalRenderer.render(text)
26
31
  end
27
32
  end
@@ -12,6 +12,7 @@ module Net
12
12
  @device = device
13
13
  end
14
14
 
15
+ # 从设备档案选定本次采集策略;无厂商规则时使用公共策略。
15
16
  def self.strategy(device)
16
17
  (device.profile.running_config_strategy || Strategy).new(device)
17
18
  end
@@ -33,6 +34,7 @@ module Net
33
34
 
34
35
  private
35
36
 
37
+ # 只从完成的配置步骤提取非空内容,并保留此前所有步骤。
36
38
  def finish(result)
37
39
  step = @device.send(:config_result_step, result)
38
40
  raise incomplete("configuration collection has no completed configuration step") unless step
@@ -44,6 +46,7 @@ module Net
44
46
  Result.new(steps: result.steps, config: content)
45
47
  end
46
48
 
49
+ # 用当前完整提示符构建结束匹配,避免配置正文中的单个 # 提前结束采集。
47
50
  def prompt_for(command, strategy)
48
51
  return command.prompt if command.prompt
49
52
 
@@ -54,6 +57,7 @@ module Net
54
57
  /(?:\A|(?<=[\r\n]))[ \t\x00]*#{Regexp.escape(prompt)}[ \t\x00]*[\r\n]*\z/
55
58
  end
56
59
 
60
+ # 排除命令回显和提示符,确认响应确实包含配置正文。
57
61
  def content?(step)
58
62
  body = TerminalRenderer.render(step.output.delete_suffix(step.prompt.to_s)).strip
59
63
  lines = body.lines
@@ -61,6 +65,7 @@ module Net
61
65
  !lines.join.strip.empty?
62
66
  end
63
67
 
68
+ # 将缺少配置的情况统一映射为可识别的设备错误码。
64
69
  def incomplete(message)
65
70
  DeviceError.new(message, code: :incomplete_configuration, host: @device.host, phase: :collect)
66
71
  end
@@ -86,6 +86,7 @@ module Net
86
86
 
87
87
  private
88
88
 
89
+ # 只截取命中错误模式的当前行,避免把整段设备配置写进诊断信息。
89
90
  def diagnostic_line(output)
90
91
  match = command_errors.lazy.filter_map { |pattern| pattern.match(output) }.first
91
92
  return unless match
@@ -112,6 +112,7 @@ module Net
112
112
  # 敏感命令和交互的异常可能只引用秘密的一部分,不能只依赖完整词匹配。
113
113
  def sensitive! = @sensitive = true
114
114
 
115
+ # 判断当前脱敏范围是否包含敏感交互。
115
116
  def sensitive? = @sensitive
116
117
 
117
118
  # 记住新的敏感字节,并按长度排序避免短词先匹配。
@@ -165,6 +166,7 @@ module Net
165
166
  @pattern ||= /(?=(#{Regexp.union((@secrets + ["[REDACTED]"]).uniq.sort_by { |secret| -secret.bytesize })}))/n
166
167
  end
167
168
 
169
+ # 合并重叠的秘密匹配区间,防止替换顺序露出部分凭据。
168
170
  def matches(text)
169
171
  ranges = []
170
172
  text.to_enum(:scan, pattern).each do
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support"
4
- require "active_support/logger"
5
- require "active_support/tagged_logging"
6
3
  require "fileutils"
4
+ require "logger"
7
5
  require_relative "terminal_renderer"
8
6
  require_relative "log_messages"
9
7
  require_relative "errors"
@@ -29,14 +27,16 @@ module Net
29
27
  if @configuration.log_format == :raw
30
28
  @writer = @output
31
29
  elsif @io || @configuration.logger
32
- base = @configuration.logger || ActiveSupport::Logger.new(@io)
30
+ base = @configuration.logger || ::Logger.new(@io)
33
31
  unless @configuration.logger
34
32
  base.formatter = proc do |severity, time, _program, message|
35
33
  "[#{time.getlocal.strftime("%Y-%m-%d %H:%M:%S %:z")}] #{severity} #{message}\n"
36
34
  end
37
35
  end
38
- @logger = ActiveSupport::TaggedLogging.new(base).tagged("host=#{@configuration.host}")
39
- @logger.level = [base.level, LEVELS.fetch(@configuration.log_level)].max
36
+ # 前缀直接写入消息,不克隆或修改调用方注入的日志器。
37
+ @logger = base
38
+ @event_level = [base.level, LEVELS.fetch(@configuration.log_level)].max
39
+ @tag = "[host=#{@configuration.host}] "
40
40
  if @io && @configuration.log_level == :debug
41
41
  @writer = TerminalRenderer.new(@output, max_line_bytes: @configuration.max_output_bytes)
42
42
  end
@@ -67,7 +67,7 @@ module Net
67
67
  end
68
68
  safe_name = @redactor.call(name.to_s).scrub.gsub(/[[:cntrl:]]+/, " ").strip
69
69
  finish_line
70
- @logger.public_send(level, LogMessages.format(safe_name, values))
70
+ @logger.public_send(level, "#{@tag}#{LogMessages.format(safe_name, values)}") if LEVELS.fetch(level) >= @event_level
71
71
  rescue Error
72
72
  raise
73
73
  rescue => error
@@ -92,7 +92,7 @@ module Net
92
92
  return unless detailed? && !@writer
93
93
 
94
94
  @redactor.call(TerminalRenderer.render(@redactor.call(bytes))).each_line do |line|
95
- @logger.debug(" #{line.chomp}")
95
+ @logger.debug("#{@tag} #{line.chomp}") if ::Logger::DEBUG >= @event_level
96
96
  end
97
97
  rescue Error
98
98
  raise
@@ -140,7 +140,7 @@ module Net
140
140
  begin
141
141
  @io&.close
142
142
  ensure
143
- @writer = @output = @logger = @io = @transport = nil
143
+ @writer = @output = @logger = @io = @transport = @tag = @event_level = nil
144
144
  @attached = false
145
145
  end
146
146
  end
@@ -169,12 +169,14 @@ module Net
169
169
 
170
170
  # 文件写入边界保留短尾部,避免分片或终端控制符拼出明文凭据。
171
171
  class RedactingWriter
172
+ # 保存目标流、脱敏器和等待下一分片的尾部。
172
173
  def initialize(target, redactor)
173
174
  @target = target
174
175
  @redactor = redactor
175
176
  @pending = "".b
176
177
  end
177
178
 
179
+ # 写入已确认安全的前缀,暂存可能与下一分片组成秘密的尾部。
178
180
  def write(bytes)
179
181
  @pending << bytes.b
180
182
  safe, @pending = @redactor.stream_chunk(@pending)
@@ -182,8 +184,10 @@ module Net
182
184
  bytes.bytesize
183
185
  end
184
186
 
187
+ # 刷新目标流,仍不提前写出待判断的尾部。
185
188
  def flush = @target.flush
186
189
 
190
+ # 在日志结束时脱敏并写出剩余尾部。
187
191
  def finish
188
192
  safe, @pending = @redactor.stream_chunk(@pending, final: true)
189
193
  @target.write(safe)
@@ -75,6 +75,7 @@ module Net
75
75
  end
76
76
  end
77
77
 
78
+ # 一次操作从连接到结果处理始终持锁;异常或 throw 中断都关闭未完成会话。
78
79
  def perform_locked(phase)
79
80
  @performing = true
80
81
  completed = false