zabbix_manager 5.1.4 → 5.1.6
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/CHANGELOG.md +25 -9
- data/LICENSE +1 -1
- data/README.md +202 -8
- data/lib/zabbix_manager/basic/basic_alias.rb +25 -3
- data/lib/zabbix_manager/basic/basic_func.rb +32 -61
- data/lib/zabbix_manager/basic/basic_init.rb +28 -13
- data/lib/zabbix_manager/basic/basic_logic.rb +172 -128
- data/lib/zabbix_manager/classes/actions.rb +21 -11
- data/lib/zabbix_manager/classes/applications.rb +12 -17
- data/lib/zabbix_manager/classes/configurations.rb +20 -5
- data/lib/zabbix_manager/classes/drules.rb +11 -23
- data/lib/zabbix_manager/classes/errors.rb +16 -28
- data/lib/zabbix_manager/classes/events.rb +6 -2
- data/lib/zabbix_manager/classes/graphs.rb +41 -34
- data/lib/zabbix_manager/classes/hostgroups.rb +53 -3
- data/lib/zabbix_manager/classes/hostinterfaces.rb +172 -12
- data/lib/zabbix_manager/classes/hosts.rb +197 -68
- data/lib/zabbix_manager/classes/httptests.rb +18 -20
- data/lib/zabbix_manager/classes/items.rb +199 -72
- data/lib/zabbix_manager/classes/maintenance.rb +6 -2
- data/lib/zabbix_manager/classes/mediatypes.rb +7 -63
- data/lib/zabbix_manager/classes/problems.rb +75 -57
- data/lib/zabbix_manager/classes/proxies.rb +48 -7
- data/lib/zabbix_manager/classes/roles.rb +47 -36
- data/lib/zabbix_manager/classes/screens.rb +45 -14
- data/lib/zabbix_manager/classes/scripts.rb +17 -7
- data/lib/zabbix_manager/classes/server.rb +7 -1
- data/lib/zabbix_manager/classes/templates.rb +59 -21
- data/lib/zabbix_manager/classes/triggers.rb +253 -66
- data/lib/zabbix_manager/classes/usergroups.rb +28 -6
- data/lib/zabbix_manager/classes/usermacros.rb +119 -33
- data/lib/zabbix_manager/classes/users.rb +39 -7
- data/lib/zabbix_manager/classes/valuemaps.rb +13 -19
- data/lib/zabbix_manager/client.rb +311 -135
- data/lib/zabbix_manager/http_transport.rb +192 -0
- data/lib/zabbix_manager/log_sanitizer.rb +68 -0
- data/lib/zabbix_manager/monitoring.rb +691 -0
- data/lib/zabbix_manager/version.rb +1 -1
- data/lib/zabbix_manager.rb +118 -40
- data/zabbix_manager.gemspec +21 -8
- metadata +208 -22
- data/lib/zabbix_manager/classes/unusable.rb +0 -11
|
@@ -1,189 +1,365 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "net/http"
|
|
4
3
|
require "json"
|
|
5
|
-
require "
|
|
4
|
+
require "logger"
|
|
6
5
|
|
|
7
6
|
class ZabbixManager
|
|
8
7
|
class Client
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
SUPPORTED_MAJOR_VERSIONS = (4..7).freeze
|
|
9
|
+
UNAUTHENTICATED_METHODS = %w[apiinfo.version user.login].freeze
|
|
10
|
+
BLANK_NORMALIZED_OPTIONS = %i[api_token username user password http_user http_password].freeze
|
|
11
|
+
UPSERT_MUTEX_STRIPES = 64
|
|
12
|
+
DEFAULT_UNCERTAIN_WRITE_DELAYS = [0, 0.25, 1, 2].freeze
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
def id
|
|
14
|
-
@id ||= rand 100_00
|
|
15
|
-
end
|
|
14
|
+
attr_reader :options, :sanitized_options, :logger
|
|
16
15
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
# 构建按服务端版本选择认证方式并复用 HTTP 会话的客户端。
|
|
17
|
+
# @return [Client]
|
|
18
|
+
# @api public
|
|
19
|
+
def initialize(options = {})
|
|
20
|
+
@raw_options = normalize_options(options)
|
|
21
|
+
validate_credentials!
|
|
22
|
+
validate_uncertain_write_delays!
|
|
23
|
+
@sanitized_options = sanitize_options(@raw_options).freeze
|
|
24
|
+
@options = @sanitized_options
|
|
25
|
+
@logger = build_logger
|
|
26
|
+
@id_mutex = Mutex.new
|
|
27
|
+
@next_id = 0
|
|
28
|
+
@upsert_mutexes = Array.new(UPSERT_MUTEX_STRIPES) { Mutex.new }
|
|
29
|
+
validate_upsert_lock!
|
|
30
|
+
@transport = HttpTransport.new(@raw_options)
|
|
31
|
+
validate_token_transport!
|
|
32
|
+
@api_version = api_version
|
|
33
|
+
validate_api_version!
|
|
34
|
+
validate_http_auth_compatibility!
|
|
35
|
+
@credential_type = @raw_options[:api_token] ? :api_token : :user_session
|
|
36
|
+
@auth_token = @raw_options[:api_token] || auth
|
|
37
|
+
log(:info, "client.connected", version: @api_version, credential_type: @credential_type)
|
|
38
|
+
log(:warn, "tls.verification_disabled", url: @transport.safe_url) if insecure_https?
|
|
39
|
+
log(:warn, "token.sent_over_http", url: @transport.safe_url) if insecure_token_transport?
|
|
40
|
+
rescue StandardError
|
|
41
|
+
@transport&.close
|
|
42
|
+
raise
|
|
20
43
|
end
|
|
21
44
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
# 分配线程安全的 JSON-RPC 请求编号。
|
|
46
|
+
# @return [Integer]
|
|
47
|
+
# @api semipublic
|
|
48
|
+
def id
|
|
49
|
+
@id_mutex.synchronize { @next_id += 1 }
|
|
25
50
|
end
|
|
26
51
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
# 返回首次查询后缓存的远端 Zabbix API 版本。
|
|
53
|
+
# @return [String]
|
|
54
|
+
# @api public
|
|
55
|
+
def api_version
|
|
56
|
+
@api_version ||= api_request(method: "apiinfo.version", params: {})
|
|
30
57
|
end
|
|
31
58
|
|
|
32
|
-
#
|
|
59
|
+
# 使用用户名和密码创建 Zabbix 会话。
|
|
60
|
+
# @return [String] Zabbix session token
|
|
61
|
+
# @api semipublic
|
|
33
62
|
def auth
|
|
34
|
-
# 发起 "user.login" 方法的 API 请求, 并传递用户信息(用户名和密码)作为参数
|
|
35
63
|
api_request(
|
|
36
64
|
method: "user.login",
|
|
37
65
|
params: {
|
|
38
|
-
|
|
39
|
-
password: @
|
|
66
|
+
login_parameter => username,
|
|
67
|
+
password: @raw_options[:password]
|
|
40
68
|
}
|
|
41
69
|
)
|
|
42
70
|
end
|
|
43
71
|
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# 检查必要的配置属性是否存在
|
|
50
|
-
raise ZbxError, "必须正确提供 :url 属性" unless @options.key?(:url)
|
|
51
|
-
raise ZbxError, "必须正确提供 :user 属性" unless @options.key?(:user)
|
|
52
|
-
raise ZbxError, "必须正确提供 :password 属性" unless @options.key?(:password)
|
|
53
|
-
|
|
54
|
-
# 配置代理信息
|
|
55
|
-
if !ENV["http_proxy"].nil? && options[:no_proxy] != true
|
|
56
|
-
@proxy_uri = URI.parse(ENV["http_proxy"])
|
|
57
|
-
@proxy_host = @proxy_uri.host
|
|
58
|
-
@proxy_port = @proxy_uri.port
|
|
59
|
-
@proxy_user, @proxy_pass = @proxy_uri.userinfo&.split(/:/) if @proxy_uri.userinfo
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# 检查 Zabbix API 版本是否受支持
|
|
63
|
-
unless api_version.match?(/^(234567)\.\d+\.\d+$/)
|
|
64
|
-
message = "Zabbix API版本:#{api_version} 不受此版本的 zabbix_manager 支持"
|
|
65
|
-
if @options[:ignore_version]
|
|
66
|
-
log "[WARNING] #{message}"
|
|
67
|
-
else
|
|
68
|
-
raise ZbxError, message
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# 进行用户鉴权,获取令牌
|
|
73
|
-
@auth_hash = auth
|
|
74
|
-
log "[DEBUG] Auth token: #{@auth_hash}"
|
|
72
|
+
# 判断是否启用调试日志。
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
# @api semipublic
|
|
75
|
+
def debug?
|
|
76
|
+
@raw_options[:debug] == true
|
|
75
77
|
end
|
|
76
78
|
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# 序列化 JSON-RPC 请求并按版本加入请求体认证。
|
|
80
|
+
# @return [String]
|
|
81
|
+
# @api semipublic
|
|
82
|
+
def message_json(body)
|
|
83
|
+
method = fetch_body_value(body, :method)
|
|
84
|
+
message = {
|
|
85
|
+
method: method,
|
|
86
|
+
params: fetch_body_value(body, :params) || {},
|
|
87
|
+
id: id,
|
|
84
88
|
jsonrpc: "2.0"
|
|
85
89
|
}
|
|
90
|
+
message[:auth] = @auth_token if @auth_token && body_authentication?(method)
|
|
91
|
+
JSON.generate(message)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 通过持久连接发送请求,并按版本加入 Bearer 令牌。
|
|
95
|
+
# @return [String] raw response body
|
|
96
|
+
# @api semipublic
|
|
97
|
+
def http_request(body)
|
|
98
|
+
method = JSON.parse(body).fetch("method")
|
|
99
|
+
@transport.request(body, bearer_token: bearer_token_for(method))
|
|
100
|
+
end
|
|
86
101
|
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
# 解析 JSON-RPC 响应并统一转换协议错误。
|
|
103
|
+
# @return [Object]
|
|
104
|
+
# @api semipublic
|
|
105
|
+
def _request(body)
|
|
106
|
+
parsed = JSON.parse(http_request(body))
|
|
107
|
+
raise_api_error(parsed, body) if parsed["error"]
|
|
108
|
+
unless parsed.key?("result")
|
|
109
|
+
raise ApiError.new("Invalid JSON-RPC response: missing result", LogSanitizer.sanitize(parsed))
|
|
110
|
+
end
|
|
89
111
|
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
parsed["result"]
|
|
113
|
+
rescue JSON::ParserError => e
|
|
114
|
+
raise ApiError, "Invalid JSON response: #{e.message}"
|
|
92
115
|
end
|
|
93
116
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
117
|
+
# 格式化请求摘要并隐藏全部参数值。
|
|
118
|
+
# @return [String]
|
|
119
|
+
# @api semipublic
|
|
120
|
+
def pretty_body(body)
|
|
121
|
+
parsed = JSON.parse(body)
|
|
122
|
+
parsed["params"] = LogSanitizer::REDACTED if parsed.key?("params")
|
|
123
|
+
JSON.pretty_generate(LogSanitizer.sanitize(parsed))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# 执行 Zabbix JSON-RPC 方法并记录脱敏耗时日志。
|
|
127
|
+
# @return [Object] decoded result value
|
|
128
|
+
# @api public
|
|
129
|
+
def api_request(body)
|
|
130
|
+
method = fetch_body_value(body, :method)
|
|
131
|
+
started_at = monotonic_time
|
|
132
|
+
log(:debug, "request.started", method: method)
|
|
133
|
+
result = _request(message_json(body))
|
|
134
|
+
log(:info, "request.completed", method: method, duration_ms: elapsed_ms(started_at))
|
|
135
|
+
result
|
|
136
|
+
rescue StandardError => e
|
|
137
|
+
log(:warn, "request.failed", method: method, duration_ms: elapsed_ms(started_at), error: e.class.name)
|
|
138
|
+
raise
|
|
139
|
+
end
|
|
140
|
+
alias manager_request api_request
|
|
141
|
+
|
|
142
|
+
# 注销用户名会话并关闭持久连接;API 令牌无需远端注销。
|
|
143
|
+
# @return [Object] logout result
|
|
144
|
+
# @api public
|
|
145
|
+
def logout
|
|
146
|
+
result = @credential_type == :user_session && @auth_token ? api_request(method: "user.logout", params: []) : true
|
|
147
|
+
result
|
|
148
|
+
ensure
|
|
149
|
+
@auth_token = nil
|
|
150
|
+
close
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# 关闭持久 HTTP 连接。
|
|
154
|
+
# @return [Object]
|
|
155
|
+
# @api public
|
|
156
|
+
def close
|
|
157
|
+
@transport.close
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# 输出结构化且已脱敏的客户端事件。
|
|
161
|
+
# @return [void]
|
|
162
|
+
# @api semipublic
|
|
163
|
+
def log(level, event, data = {})
|
|
164
|
+
return unless @logger
|
|
165
|
+
|
|
166
|
+
sanitized = LogSanitizer.sanitize(data)
|
|
167
|
+
details = sanitized.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
|
|
168
|
+
@logger.public_send(level, ["[zabbix_manager]", event, details].reject(&:empty?).join(" "))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# 返回不包含凭据的客户端摘要,避免对象检查时泄漏配置
|
|
172
|
+
# @return [String]
|
|
173
|
+
# @api public
|
|
174
|
+
def inspect
|
|
175
|
+
"#<#{self.class} options=#{@sanitized_options.inspect}>"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# 以业务键串行执行幂等写入,并可接入跨进程锁实现
|
|
179
|
+
# @return [Object] 代码块结果
|
|
180
|
+
# @api semipublic
|
|
181
|
+
def with_upsert_lock(key, &block)
|
|
182
|
+
mutex = @upsert_mutexes[key.hash % UPSERT_MUTEX_STRIPES]
|
|
183
|
+
mutex.synchronize do
|
|
184
|
+
coordinator = @raw_options[:upsert_lock]
|
|
185
|
+
coordinator ? coordinator.call(key, &block) : yield
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
# 统一配置键并把页面提交的空凭据归一化为 nil
|
|
192
|
+
# @return [Hash]
|
|
193
|
+
# @api private
|
|
194
|
+
def normalize_options(options)
|
|
195
|
+
unless options.respond_to?(:each_pair)
|
|
196
|
+
raise Invalid, "options must be a hash-like object"
|
|
197
|
+
end
|
|
97
198
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
199
|
+
normalized = options.to_h.transform_keys(&:to_sym)
|
|
200
|
+
BLANK_NORMALIZED_OPTIONS.each do |key|
|
|
201
|
+
value = normalized[key].presence
|
|
202
|
+
value ? normalized[key] = value : normalized.delete(key)
|
|
203
|
+
end
|
|
204
|
+
normalized
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# 脱敏普通凭据,并清理 URL 和代理地址中的 userinfo 与查询串。
|
|
208
|
+
def sanitize_options(options)
|
|
209
|
+
sanitized = LogSanitizer.sanitize(options)
|
|
210
|
+
%i[url proxy].each do |key|
|
|
211
|
+
sanitized[key] = LogSanitizer.sanitize_url(options[key]) if options[key]
|
|
212
|
+
end
|
|
213
|
+
sanitized
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# 校验页面配置只能选择 API 令牌或用户名密码之一。
|
|
217
|
+
# @return [void]
|
|
218
|
+
# @api private
|
|
219
|
+
def validate_credentials!
|
|
220
|
+
raise Invalid, "url is required" if @raw_options[:url].nil? || @raw_options[:url].to_s.empty?
|
|
221
|
+
|
|
222
|
+
token_present = @raw_options[:api_token].present?
|
|
223
|
+
username_present = username.present?
|
|
224
|
+
password_present = @raw_options[:password].present?
|
|
225
|
+
if token_present && (username_present || password_present)
|
|
226
|
+
raise Invalid, "api_token cannot be combined with username/password"
|
|
227
|
+
end
|
|
228
|
+
return if token_present || (username_present && password_present)
|
|
229
|
+
|
|
230
|
+
raise Invalid, "provide api_token or both username and password"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# 校验传输结果不确定时的只读回查退避窗口。
|
|
234
|
+
def validate_uncertain_write_delays!
|
|
235
|
+
values = @raw_options.fetch(:uncertain_write_delays, DEFAULT_UNCERTAIN_WRITE_DELAYS)
|
|
236
|
+
delays = Array(values).map { |value| Float(value) }
|
|
237
|
+
unless delays.any? && delays.length <= 10 && delays.all? { |delay| delay >= 0 } && delays.sum <= 60
|
|
238
|
+
raise Invalid, "uncertain_write_delays must contain 1 to 10 non-negative seconds totaling at most 60"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
@raw_options[:uncertain_write_delays] = delays.freeze
|
|
242
|
+
rescue ArgumentError, TypeError
|
|
243
|
+
raise Invalid, "uncertain_write_delays must contain numeric seconds"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# 校验可选的跨进程幂等锁适配器。
|
|
247
|
+
# @return [void]
|
|
248
|
+
# @api private
|
|
249
|
+
def validate_upsert_lock!
|
|
250
|
+
lock = @raw_options[:upsert_lock]
|
|
251
|
+
return if lock.nil? || lock.respond_to?(:call)
|
|
252
|
+
|
|
253
|
+
raise Invalid, "upsert_lock must respond to call(key, &block)"
|
|
254
|
+
end
|
|
101
255
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
256
|
+
# 校验服务端版本格式及支持范围。
|
|
257
|
+
# @return [void]
|
|
258
|
+
# @api private
|
|
259
|
+
def validate_api_version!
|
|
260
|
+
match = @api_version.to_s.match(/\A(\d+)\.(\d+)\.(\d+)\z/)
|
|
261
|
+
supported = match && SUPPORTED_MAJOR_VERSIONS.cover?(match[1].to_i)
|
|
262
|
+
return if supported
|
|
263
|
+
|
|
264
|
+
message = "Zabbix API version #{@api_version.inspect} is not supported"
|
|
265
|
+
if @raw_options[:ignore_version]
|
|
266
|
+
log(:warn, "version.unsupported", version: @api_version)
|
|
106
267
|
else
|
|
107
|
-
|
|
268
|
+
raise ApiError, message
|
|
108
269
|
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# 拒绝 Zabbix 7 Bearer 认证与 HTTP Basic 复用同一认证头。
|
|
273
|
+
# @return [void]
|
|
274
|
+
# @api private
|
|
275
|
+
def validate_http_auth_compatibility!
|
|
276
|
+
return unless api_major_version >= 7 && @raw_options[:http_user].present?
|
|
109
277
|
|
|
110
|
-
|
|
111
|
-
if uri.scheme == "https"
|
|
112
|
-
http.use_ssl = true
|
|
113
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
278
|
+
raise Invalid, "HTTP Basic authentication cannot be combined with Zabbix 7 Bearer authentication"
|
|
114
279
|
end
|
|
115
280
|
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
281
|
+
# 默认阻止 API 令牌通过明文 HTTP 发送
|
|
282
|
+
# @return [void]
|
|
283
|
+
# @api private
|
|
284
|
+
def validate_token_transport!
|
|
285
|
+
return unless @raw_options[:api_token] && insecure_token_transport?
|
|
286
|
+
return if @raw_options[:allow_insecure_http] == true
|
|
119
287
|
|
|
120
|
-
|
|
121
|
-
|
|
288
|
+
raise Invalid, "api_token requires HTTPS unless allow_insecure_http is true"
|
|
289
|
+
end
|
|
122
290
|
|
|
123
|
-
#
|
|
124
|
-
|
|
291
|
+
# 判断当前令牌连接是否使用明文 HTTP
|
|
292
|
+
# @return [Boolean]
|
|
293
|
+
# @api private
|
|
294
|
+
def insecure_token_transport?
|
|
295
|
+
@raw_options[:api_token].present? && @transport.uri.scheme == "http"
|
|
296
|
+
end
|
|
125
297
|
|
|
126
|
-
#
|
|
127
|
-
|
|
298
|
+
# 提取已缓存 API 版本的主版本号。
|
|
299
|
+
def api_major_version
|
|
300
|
+
@api_version.to_s.split(".", 2).first.to_i
|
|
301
|
+
end
|
|
128
302
|
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
303
|
+
# 根据服务端版本选择登录用户名字段。
|
|
304
|
+
def login_parameter
|
|
305
|
+
api_major_version >= 6 ? :username : :user
|
|
306
|
+
end
|
|
132
307
|
|
|
133
|
-
#
|
|
134
|
-
|
|
308
|
+
# 返回统一后的登录用户名。
|
|
309
|
+
def username
|
|
310
|
+
@raw_options[:username] || @raw_options[:user]
|
|
311
|
+
end
|
|
135
312
|
|
|
136
|
-
#
|
|
137
|
-
|
|
313
|
+
# 判断当前方法是否应在 JSON-RPC 请求体中携带认证值。
|
|
314
|
+
def body_authentication?(method)
|
|
315
|
+
authenticated_method?(method) && api_major_version < 7
|
|
316
|
+
end
|
|
138
317
|
|
|
139
|
-
|
|
318
|
+
# 判断当前方法是否应使用 Bearer 认证头。
|
|
319
|
+
def bearer_token_for(method)
|
|
320
|
+
@auth_token if authenticated_method?(method) && api_major_version >= 7
|
|
321
|
+
end
|
|
140
322
|
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
-
|
|
323
|
+
# 排除版本查询和登录方法,判断请求是否需要认证。
|
|
324
|
+
def authenticated_method?(method)
|
|
325
|
+
!UNAUTHENTICATED_METHODS.include?(method.to_s)
|
|
326
|
+
end
|
|
144
327
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
result = JSON.parse(http_request(body))
|
|
149
|
-
|
|
150
|
-
# 如果响应结果中包含错误信息,则抛出 ZbxError 异常
|
|
151
|
-
if result["error"]
|
|
152
|
-
# 构建错误消息,包括服务器返回的错误信息和请求的详细信息
|
|
153
|
-
msg = <<~MOJO
|
|
154
|
-
---------------------------------------------------------------
|
|
155
|
-
Server answer API error: #{JSON.pretty_unparse(result["error"])}
|
|
156
|
-
on request: #{pretty_body(body)}
|
|
157
|
-
---------------------------------------------------------------
|
|
158
|
-
MOJO
|
|
159
|
-
|
|
160
|
-
# 抛出自定义异常 ZbxError,并传递错误消息和完整的响应结果
|
|
161
|
-
raise ZbxError.new(msg, result)
|
|
162
|
-
else
|
|
163
|
-
# 如果没有错误信息,则返回响应结果中的 "result" 部分
|
|
164
|
-
result["result"]
|
|
328
|
+
# 同时读取符号键和字符串键的请求字段。
|
|
329
|
+
def fetch_body_value(body, key)
|
|
330
|
+
body[key] || body[key.to_s]
|
|
165
331
|
end
|
|
166
|
-
end
|
|
167
332
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
333
|
+
# 使用脱敏响应和请求摘要构造 API 异常。
|
|
334
|
+
def raise_api_error(parsed, request_body)
|
|
335
|
+
safe_response = LogSanitizer.sanitize(parsed)
|
|
336
|
+
error = safe_response["error"]
|
|
337
|
+
message = "Zabbix API error #{error["code"]}: #{error["message"]}"
|
|
338
|
+
message = "#{message} (#{error["data"]})" if error["data"]
|
|
339
|
+
raise ApiError.new("#{message}\nRequest:\n#{pretty_body(request_body)}", safe_response)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# 优先使用调用方日志器,调试模式下才创建默认日志器。
|
|
343
|
+
def build_logger
|
|
344
|
+
return @raw_options[:logger] if @raw_options[:logger]
|
|
345
|
+
return unless debug?
|
|
172
346
|
|
|
173
|
-
|
|
174
|
-
if data["params"].is_a?(Hash) && data["params"].key?("password")
|
|
175
|
-
data["params"]["password"] = "***"
|
|
347
|
+
Logger.new($stdout).tap { |value| value.level = Logger::DEBUG }
|
|
176
348
|
end
|
|
177
349
|
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
350
|
+
# 判断 HTTPS 是否按项目要求关闭证书校验。
|
|
351
|
+
def insecure_https?
|
|
352
|
+
@transport.uri.scheme == "https" && @raw_options[:verify_ssl] != true
|
|
353
|
+
end
|
|
181
354
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
355
|
+
# 读取不受系统时间校准影响的单调时钟。
|
|
356
|
+
def monotonic_time
|
|
357
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# 计算并格式化请求耗时毫秒数。
|
|
361
|
+
def elapsed_ms(started_at)
|
|
362
|
+
((monotonic_time - started_at) * 1_000).round(1)
|
|
363
|
+
end
|
|
188
364
|
end
|
|
189
365
|
end
|