cw-datadog 2.23.0.2 → 2.23.0.4

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
  3. data/ext/libdatadog_api/library_config.c +12 -11
  4. data/ext/libdatadog_extconf_helpers.rb +1 -1
  5. data/lib/datadog/appsec/api_security/route_extractor.rb +20 -5
  6. data/lib/datadog/appsec/api_security/sampler.rb +3 -1
  7. data/lib/datadog/appsec/assets/blocked.html +8 -0
  8. data/lib/datadog/appsec/assets/blocked.json +1 -1
  9. data/lib/datadog/appsec/assets/blocked.text +3 -1
  10. data/lib/datadog/appsec/assets.rb +1 -1
  11. data/lib/datadog/appsec/remote.rb +4 -0
  12. data/lib/datadog/appsec/response.rb +18 -4
  13. data/lib/datadog/core/cloudwise/client.rb +412 -25
  14. data/lib/datadog/core/cloudwise/component.rb +195 -52
  15. data/lib/datadog/core/cloudwise/docc_heartbeat_worker.rb +105 -0
  16. data/lib/datadog/core/cloudwise/docc_operation_worker.rb +191 -0
  17. data/lib/datadog/core/cloudwise/docc_registration_worker.rb +89 -0
  18. data/lib/datadog/core/cloudwise/license_worker.rb +90 -4
  19. data/lib/datadog/core/cloudwise/probe_state.rb +134 -12
  20. data/lib/datadog/core/configuration/components.rb +10 -9
  21. data/lib/datadog/core/configuration/settings.rb +43 -0
  22. data/lib/datadog/core/configuration/supported_configurations.rb +6 -2
  23. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  24. data/lib/datadog/core/remote/component.rb +2 -2
  25. data/lib/datadog/core/remote/transport/config.rb +2 -10
  26. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  27. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  28. data/lib/datadog/core/remote/transport/http.rb +2 -0
  29. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  30. data/lib/datadog/core/remote/worker.rb +23 -35
  31. data/lib/datadog/core/telemetry/component.rb +26 -13
  32. data/lib/datadog/core/telemetry/event/app_started.rb +67 -49
  33. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  34. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  35. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  36. data/lib/datadog/core/telemetry/worker.rb +51 -6
  37. data/lib/datadog/core/transport/http/adapters/net.rb +2 -0
  38. data/lib/datadog/core/transport/http/client.rb +69 -0
  39. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  40. data/lib/datadog/data_streams/transport/http/client.rb +4 -32
  41. data/lib/datadog/data_streams/transport/stats.rb +1 -1
  42. data/lib/datadog/di/probe_notification_builder.rb +35 -13
  43. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  44. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  45. data/lib/datadog/di/transport/http/input.rb +2 -4
  46. data/lib/datadog/di/transport/input.rb +2 -2
  47. data/lib/datadog/open_feature/component.rb +60 -0
  48. data/lib/datadog/open_feature/configuration.rb +27 -0
  49. data/lib/datadog/open_feature/evaluation_engine.rb +59 -0
  50. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  51. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  52. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  53. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  54. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  55. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  56. data/lib/datadog/open_feature/ext.rb +13 -0
  57. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  58. data/lib/datadog/open_feature/provider.rb +134 -0
  59. data/lib/datadog/open_feature/remote.rb +74 -0
  60. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  61. data/lib/datadog/open_feature/transport.rb +72 -0
  62. data/lib/datadog/open_feature.rb +19 -0
  63. data/lib/datadog/profiling/component.rb +6 -0
  64. data/lib/datadog/profiling/profiler.rb +4 -0
  65. data/lib/datadog/profiling.rb +1 -2
  66. data/lib/datadog/single_step_instrument.rb +1 -1
  67. data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +164 -7
  68. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  69. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  70. data/lib/datadog/tracing/contrib/karafka/patcher.rb +14 -0
  71. data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -2
  72. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  73. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  74. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  75. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  76. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  77. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  78. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  79. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  80. data/lib/datadog/tracing/contrib.rb +1 -0
  81. data/lib/datadog/tracing/transport/http/api.rb +73 -1
  82. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  83. data/lib/datadog/tracing/transport/http/traces.rb +4 -2
  84. data/lib/datadog/tracing/transport/trace_formatter.rb +16 -0
  85. data/lib/datadog/version.rb +2 -2
  86. data/lib/datadog.rb +1 -0
  87. metadata +38 -15
  88. data/lib/datadog/core/cloudwise/IMPLEMENTATION_V2.md +0 -517
  89. data/lib/datadog/core/cloudwise/QUICKSTART.md +0 -398
  90. data/lib/datadog/core/cloudwise/README.md +0 -722
  91. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  92. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  93. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -10,22 +10,36 @@ module Datadog
10
10
  module Cloudwise
11
11
  # HTTP client for Cloudwise API calls
12
12
  class Client
13
- attr_reader :base_url, :server_name, :license_key, :logger, :account_id
13
+ attr_reader :base_url, :server_name, :license_key, :logger, :account_id,
14
+ :integrated_mode, :token, :token_account_id, :token_user_id,
15
+ :agent_instance_id
14
16
 
15
17
  # Response codes
16
18
  CODE_SUCCESS = 1000
17
19
  CODE_PROBE_SUSPENDED = 1001 # 探针熔断
18
20
  CODE_PROBE_ACTIVE = 1002 # 探针活跃/恢复
19
21
  CODE_LICENSE_INVALID = 2001 # License 校验失败
22
+ # DOCC response codes
23
+ DOCC_CODE_SUCCESS = 100000
20
24
 
21
- def initialize(base_url:, server_name:, license_key:, logger:)
25
+ def initialize(base_url:, server_name:, license_key:, logger:, integrated_mode: true, token: nil, api_prefix: nil, api_prefix_mode: nil)
22
26
  @base_url = normalize_url(base_url)
23
27
  @server_name = server_name
24
28
  @license_key = license_key
25
29
  @logger = logger
30
+ @integrated_mode = integrated_mode
31
+ @token = token
32
+ @api_prefix = api_prefix # 自定义 API 路径前缀
33
+ @api_prefix_mode = api_prefix_mode # API 前缀模式(简化配置)
26
34
  @host_id = nil
27
35
  @account_id = nil
28
36
  @is_first_heartbeat = true
37
+
38
+ # Parse token if provided
39
+ parse_token if @token && !@token.empty?
40
+
41
+ # Generate agent instance ID for DOCC
42
+ @agent_instance_id = generate_agent_id(get_local_ip) if use_integrated_mode?
29
43
  end
30
44
 
31
45
  # 工厂方法:从 Datadog settings 创建 Client
@@ -47,7 +61,11 @@ module Datadog
47
61
  base_url: base_url,
48
62
  server_name: settings.service || 'unknown-service',
49
63
  license_key: settings.cloudwise.license_key,
50
- logger: logger
64
+ logger: logger,
65
+ integrated_mode: settings.cloudwise.integrated_mode,
66
+ token: settings.cloudwise.token,
67
+ api_prefix: settings.cloudwise.api_prefix,
68
+ api_prefix_mode: settings.cloudwise.api_prefix_mode
51
69
  )
52
70
  end
53
71
 
@@ -181,6 +199,22 @@ module Datadog
181
199
  })
182
200
  end
183
201
 
202
+ # 熔断状态上报接口
203
+ # @param fuse_state [Integer] 熔断状态码
204
+ # @param fuse_describe [String] 熔断描述
205
+ def report_fuse_state(fuse_state:, fuse_describe:)
206
+ host_ip = get_local_ip
207
+
208
+ post('/api/v1/agent/fuseHeart', {
209
+ version: Datadog::VERSION::STRING,
210
+ accountId: @account_id,
211
+ agentId: generate_agent_id(host_ip),
212
+ fuseState: fuse_state,
213
+ fuseDescribe: fuse_describe,
214
+ appId: generate_app_id(server_name)
215
+ })
216
+ end
217
+
184
218
  # 应用注册接口
185
219
  def register_application
186
220
  host_ip = get_local_ip
@@ -210,11 +244,94 @@ module Datadog
210
244
  })
211
245
  end
212
246
 
247
+ # Check if should use integrated mode (DOCC interfaces)
248
+ # 判断是否需要使用融合模式
249
+ # token不为空且integrated_mode=true时启用
250
+ def use_integrated_mode?
251
+ @integrated_mode && @token && !@token.empty? && @token_account_id && @token_user_id
252
+ end
253
+
254
+ # ====================================================================
255
+ # DOCC Interface Methods (Integrated Mode)
256
+ # ====================================================================
257
+
258
+ # DOCC 注册纳管接口
259
+ # 接口路径: api/ext/gaia/daemon/registExt
260
+ # 调用频率: 每10分钟一次
261
+ def docc_register_ext
262
+ host_ip = get_local_ip
263
+
264
+ data = {
265
+ agent_instance_id: generate_agent_id(host_ip),
266
+ agent_id: 'rubyagent',
267
+ version: 'v' + Datadog::VERSION::STRING,
268
+ agent_pid: Process.pid,
269
+ agent_run_user: get_run_user,
270
+ log_path: get_log_path,
271
+ ip_address: host_ip,
272
+ host_name: Socket.gethostname,
273
+ system_uuid: get_system_uuid,
274
+ os: get_os_name,
275
+ arch: get_architecture,
276
+ meta_info: {
277
+ app_name: @server_name,
278
+ service_path: get_process_path,
279
+ sample: calculate_sample_rate,
280
+ physical_ip: detect_container? ? get_host_real_ip : '',
281
+ container_id: detect_container? ? get_container_id : '',
282
+ sys: ENV['CW_SYS'] || 'default',
283
+ env_tag: Datadog.configuration.env || 'default'
284
+ }
285
+ }
286
+
287
+ post_docc('/api/ext/gaia/daemon/registExt', data)
288
+ end
289
+
290
+ # DOCC 心跳纳管接口
291
+ # 接口路径: api/ext/gaia/daemon/heartbeatExt
292
+ # 调用频率: 每30秒一次
293
+ def docc_heartbeat_ext(fusing: false, fusing_condition: nil, fusing_start_time: nil, fusing_end_time: nil)
294
+ host_ip = get_local_ip
295
+
296
+ data = {
297
+ agent_id: 'rubyagent',
298
+ agent_instance_id: generate_agent_id(host_ip),
299
+ agent_pid: Process.pid,
300
+ agent_run_user: get_run_user,
301
+ fusing: fusing,
302
+ fusing_condition: fusing_condition,
303
+ fusing_start_time: fusing_start_time,
304
+ fusing_end_time: fusing_end_time
305
+ }
306
+
307
+ post_docc('/api/ext/gaia/daemon/heartbeatExt', data)
308
+ end
309
+
310
+ # DOCC 操作拉取接口
311
+ # 接口路径: api/ext/gaia/daemon/fetchExt/${agentInstanceId}
312
+ # 调用频率: 每30秒一次
313
+ def docc_fetch_operation
314
+ post_docc("/api/ext/gaia/daemon/fetchExt/#{@agent_instance_id}", {})
315
+ end
316
+
317
+ # DOCC 操作结果上报接口
318
+ # 接口路径: api/ext/gaia/daemon/report/${agentInstanceId}
319
+ # 上报操作结果(支持批量上报)
320
+ # @param results [Array<Hash>, Hash] 单个结果或结果数组
321
+ def docc_report_operation(results)
322
+ # 如果传入的是数组,直接使用;否则包装成数组
323
+ data = results.is_a?(Array) ? results : [results]
324
+
325
+ post_docc("/api/ext/gaia/daemon/report/#{@agent_instance_id}", data)
326
+ end
327
+
213
328
  private
214
329
 
215
330
  def post(path, data)
216
- uri = URI.join(base_url, path)
331
+ # 统一处理 API 路径前缀
332
+ path = apply_api_prefix(path)
217
333
 
334
+ uri = URI.join(base_url, path)
218
335
  http = Net::HTTP.new(uri.host, uri.port)
219
336
  http.use_ssl = (uri.scheme == 'https')
220
337
  http.open_timeout = 5
@@ -248,7 +365,7 @@ module Datadog
248
365
  success: true,
249
366
  data: body['data'] || body,
250
367
  code: code,
251
- message: body['message']
368
+ message: body['msg']
252
369
  }
253
370
  when 400..499
254
371
  Cloudwise.log_warn { "Cloudwise API client error for #{path}: #{response.code} #{response.body}" }
@@ -262,17 +379,6 @@ module Datadog
262
379
  end
263
380
  end
264
381
 
265
- def detect_framework
266
- if defined?(::Rails)
267
- 'rails'
268
- elsif defined?(::Sinatra)
269
- 'sinatra'
270
- elsif defined?(::Rack)
271
- 'rack'
272
- else
273
- 'unknown'
274
- end
275
- end
276
382
 
277
383
  # 获取本机 IP 地址
278
384
  # 优先获取外网 IP(通过创建 UDP 连接,不实际发送数据)
@@ -287,7 +393,6 @@ module Datadog
287
393
  udp_socket.connect('8.8.8.8', 1)
288
394
  local_ip = udp_socket.addr.last
289
395
  udp_socket.close
290
-
291
396
  return local_ip
292
397
  rescue => e
293
398
  Cloudwise.log_debug { "Cloudwise: Failed to get IP via UDP: #{e.message}" }
@@ -416,16 +521,34 @@ module Datadog
416
521
  ''
417
522
  end
418
523
 
419
- # 计算采样率(借鉴 Propagation 的规则,不引用 tracing 组件)
524
+ # 计算采样率(读取 Datadog 配置或环境变量)
525
+ # 返回 0-100 的整数值
420
526
  def calculate_sample_rate
421
- # 默认采样率为 1(100% 采样)
422
- # 可以从环境变量读取自定义采样率
423
- rate = ENV['CLOUDWISE_SAMPLE_RATE']&.to_i
424
- return rate if rate && rate >= 0 && rate <= 100
527
+ rate = nil
528
+
529
+ # 1. 尝试从 Datadog 配置读取(0.0-1.0 的浮点数)
530
+ if defined?(Datadog.configuration) &&
531
+ Datadog.configuration.respond_to?(:tracing) &&
532
+ Datadog.configuration.tracing.respond_to?(:sampling) &&
533
+ Datadog.configuration.tracing.sampling.respond_to?(:default_rate)
534
+ configured_rate = Datadog.configuration.tracing.sampling.default_rate
535
+ rate = configured_rate if configured_rate && configured_rate >= 0.0 && configured_rate <= 1.0
536
+ end
425
537
 
426
- 1 # 默认值
427
- rescue
428
- 1
538
+ # 2. 尝试从环境变量读取(DD_TRACE_SAMPLE_RATE,0.0-1.0 的浮点数)
539
+ if rate.nil? && ENV['DD_TRACE_SAMPLE_RATE']
540
+ env_rate = ENV['DD_TRACE_SAMPLE_RATE'].to_f
541
+ rate = env_rate if env_rate >= 0.0 && env_rate <= 1.0
542
+ end
543
+
544
+ # 3. 如果没有配置,默认使用 100%
545
+ rate = 1.0 if rate.nil?
546
+
547
+ # 转换为 0-100 的整数
548
+ (rate * 100).to_i
549
+ rescue => e
550
+ Cloudwise.log_debug { "Failed to calculate sample rate: #{e.message}" }
551
+ 100 # 出错时默认 100%
429
552
  end
430
553
 
431
554
  # 生成标识(基于多个因素的唯一标识)
@@ -484,6 +607,270 @@ module Datadog
484
607
  url.chomp('/')
485
608
  end
486
609
 
610
+ # 统一处理 API 路径前缀
611
+ # 优先级:api_prefix_mode > api_prefix > integrated_mode (/apm) > 无前缀
612
+ # @param path [String] 原始路径
613
+ # @return [String] 处理后的路径
614
+ def apply_api_prefix(path)
615
+ # DOCC 接口不需要添加前缀(它们有自己的路径)
616
+ return path if path.start_with?('/api/ext/gaia/daemon/')
617
+
618
+ # 获取需要应用前缀的路径
619
+ # 这些路径是 Cloudwise 特定的API,不是 DOCC 接口
620
+ cloudwise_api_paths = [
621
+ '/v2/app/generateHostId',
622
+ '/v2/app/registerHost',
623
+ '/api/v1/agent/heartbeat',
624
+ '/api/v1/agent/fuseHeart',
625
+ '/v2/app/create',
626
+ '/v2/licence/verification'
627
+ ]
628
+
629
+ # 检查是否是需要添加前缀的路径
630
+ needs_prefix = cloudwise_api_paths.any? { |p| path.start_with?(p) }
631
+ return path unless needs_prefix
632
+
633
+ # 优先级 1: api_prefix_mode 模式配置(最高优先级,简化配置)
634
+ if @api_prefix_mode && !@api_prefix_mode.empty?
635
+ prefix = get_prefix_by_mode(@api_prefix_mode)
636
+ return "#{prefix}#{path}" if prefix
637
+ end
638
+
639
+ # 优先级 2: 显式配置的 api_prefix(自定义前缀)
640
+ if @api_prefix && !@api_prefix.empty?
641
+ prefix = @api_prefix.start_with?('/') ? @api_prefix : "/#{@api_prefix}"
642
+ return "#{prefix}#{path}"
643
+ end
644
+
645
+ # 优先级 3: integrated_mode 使用 /apm 前缀
646
+ if use_integrated_mode?
647
+ return "/apm#{path}"
648
+ end
649
+
650
+ # 优先级 4: 无前缀(默认)
651
+ path
652
+ end
653
+
654
+ # 根据模式获取前缀
655
+ # @param mode [String] 前缀模式:'doop', 'apm', 'custom'
656
+ # @return [String, nil] 前缀字符串,如果模式无效返回 nil
657
+ def get_prefix_by_mode(mode)
658
+ case mode.to_s.downcase
659
+ when 'doop'
660
+ '/doop-agent-api'
661
+ when 'apm'
662
+ '/apm'
663
+ when 'custom'
664
+ # 使用 api_prefix 配置的自定义前缀
665
+ if @api_prefix && !@api_prefix.empty?
666
+ @api_prefix.start_with?('/') ? @api_prefix : "/#{@api_prefix}"
667
+ else
668
+ Cloudwise.log_warn { "Cloudwise: api_prefix_mode='custom' but api_prefix is not set" }
669
+ nil
670
+ end
671
+ else
672
+ Cloudwise.log_warn { "Cloudwise: Unknown api_prefix_mode '#{mode}', ignoring" }
673
+ nil
674
+ end
675
+ end
676
+
677
+ # Parse token to extract account_id and user_id
678
+ # Token format: base64("account_id@user_id")
679
+ def parse_token
680
+ require 'base64'
681
+
682
+ begin
683
+ decoded = Base64.decode64(@token)
684
+ parts = decoded.split('@')
685
+
686
+ if parts.length == 2 && !parts[0].empty? && !parts[1].empty?
687
+ @token_account_id = parts[0]
688
+ @token_user_id = parts[1]
689
+ @account_id = @token_account_id
690
+ Cloudwise.log_debug { "Cloudwise: Token parsed - account_id: #{@token_account_id}, user_id: #{@token_user_id}" }
691
+
692
+ #account_id
693
+ ENV['CLOUDWISE_ACCOUNT_ID'] = @token_account_id.to_s
694
+ else
695
+ Cloudwise.log_warn { 'Cloudwise: Invalid token format (expected base64 of account_id@user_id)' }
696
+ @token_account_id = nil
697
+ @token_user_id = nil
698
+ end
699
+ rescue => e
700
+ Cloudwise.log_error { "Cloudwise: Failed to parse token: #{e.message}" }
701
+ @token_account_id = nil
702
+ @token_user_id = nil
703
+ end
704
+ end
705
+
706
+
707
+ # Get current run user
708
+ def get_run_user
709
+ ENV['USER'] || ENV['USERNAME'] || 'cloudwise'
710
+ rescue
711
+ 'cloudwise'
712
+ end
713
+
714
+ # Get log path
715
+ def get_log_path
716
+ if defined?(Datadog.logger) && Datadog.logger.respond_to?(:logdev)
717
+ logdev = Datadog.logger.logdev
718
+ return logdev.filename if logdev.respond_to?(:filename) && logdev.filename
719
+ end
720
+ ''
721
+ rescue
722
+ ''
723
+ end
724
+
725
+
726
+ # Get system UUID
727
+ # Try multiple methods to get a unique system identifier
728
+ def get_system_uuid
729
+ # Method 1: Try to read from /etc/machine-id (Linux)
730
+ if File.exist?('/etc/machine-id')
731
+ uuid = File.read('/etc/machine-id').strip
732
+ return uuid unless uuid.empty?
733
+ end
734
+
735
+ # Method 2: Try to read from /var/lib/dbus/machine-id (Linux)
736
+ if File.exist?('/var/lib/dbus/machine-id')
737
+ uuid = File.read('/var/lib/dbus/machine-id').strip
738
+ return uuid unless uuid.empty?
739
+ end
740
+
741
+ # Method 3: Try macOS system_profiler
742
+ if RUBY_PLATFORM =~ /darwin/
743
+ uuid = `system_profiler SPHardwareDataType 2>/dev/null | awk '/UUID/ { print $3; }'`.strip
744
+ return uuid unless uuid.empty?
745
+ end
746
+
747
+ # Method 4: Generate a persistent UUID based on hostname and mac address
748
+ generate_uuid_from_system_info
749
+ rescue => e
750
+ Cloudwise.log_debug { "Failed to get system UUID: #{e.message}" }
751
+ # Fallback: generate a UUID from hostname
752
+ generate_fallback_uuid
753
+ end
754
+
755
+ # Generate UUID from system information
756
+ def generate_uuid_from_system_info
757
+ require 'digest/md5'
758
+ hostname = get_safe_hostname
759
+ mac = get_mac_address
760
+ combined = "#{hostname}-#{mac}"
761
+ Digest::MD5.hexdigest(combined)
762
+ end
763
+
764
+ # Generate fallback UUID
765
+ def generate_fallback_uuid
766
+ require 'digest/md5'
767
+ hostname = get_safe_hostname
768
+ Digest::MD5.hexdigest(hostname)
769
+ end
770
+
771
+ # Get hostname safely
772
+ def get_safe_hostname
773
+ Socket.gethostname
774
+ rescue
775
+ 'unknown'
776
+ end
777
+
778
+ # Get MAC address
779
+ def get_mac_address
780
+ # Try to get MAC address from network interfaces
781
+ if File.exist?('/sys/class/net')
782
+ Dir.glob('/sys/class/net/*/address').each do |addr_file|
783
+ next if addr_file.include?('lo') # Skip loopback
784
+ mac = File.read(addr_file).strip
785
+ return mac unless mac.empty? || mac == '00:00:00:00:00:00'
786
+ end
787
+ end
788
+
789
+ # Fallback: try ifconfig/ip command
790
+ output = `ifconfig 2>/dev/null || ip link show 2>/dev/null`.strip
791
+ match = output.match(/(?:[0-9a-f]{2}:){5}[0-9a-f]{2}/i)
792
+ return match[0] if match
793
+
794
+ ''
795
+ rescue
796
+ ''
797
+ end
798
+
799
+ # Get OS name
800
+ def get_os_name
801
+ case RUBY_PLATFORM
802
+ when /linux/
803
+ 'Linux'
804
+ when /darwin/
805
+ 'Darwin'
806
+ when /freebsd/
807
+ 'FreeBSD'
808
+ when /netbsd/
809
+ 'NetBSD'
810
+ when /openbsd/
811
+ 'OpenBSD'
812
+ when /sunos|solaris/
813
+ 'Solaris'
814
+ when /aix/
815
+ 'AIX'
816
+ when /win|mingw|mswin/
817
+ 'Windows'
818
+ else
819
+ RUBY_PLATFORM
820
+ end
821
+ rescue
822
+ 'Unknown'
823
+ end
824
+
825
+ # Get system architecture
826
+ def get_architecture
827
+ case RbConfig::CONFIG['host_cpu']
828
+ when /x86_64|amd64/
829
+ 'x86_64'
830
+ when /i[3-6]86|x86/
831
+ 'x86'
832
+ when /aarch64|arm64/
833
+ 'aarch_64'
834
+ when /arm/
835
+ 'arm'
836
+ when /ppc64le/
837
+ 'ppc64le'
838
+ when /ppc64/
839
+ 'ppc64'
840
+ when /s390x/
841
+ 's390x'
842
+ else
843
+ RbConfig::CONFIG['host_cpu']
844
+ end
845
+ rescue
846
+ 'unknown'
847
+ end
848
+
849
+ # POST request for DOCC interfaces with token authentication
850
+ def post_docc(path, data)
851
+ uri = URI.join(base_url, path)
852
+
853
+ http = Net::HTTP.new(uri.host, uri.port)
854
+ http.use_ssl = (uri.scheme == 'https')
855
+ http.open_timeout = 5
856
+ http.read_timeout = 5
857
+
858
+ request = Net::HTTP::Post.new(uri.path)
859
+ request['Content-Type'] = 'application/json'
860
+ request['token'] = @token
861
+ request['User-Agent'] = "Datadog-Ruby-Agent/#{Datadog::VERSION::STRING}"
862
+ request['DD-Internal-Untraced-Request'] = '1'
863
+ request.body = data.to_json
864
+
865
+ Cloudwise.log_debug { "Cloudwise DOCC API request: #{request.method} #{uri} with data: #{data.inspect}" }
866
+
867
+ response = http.request(request)
868
+ handle_response(response, path)
869
+ rescue => e
870
+ Cloudwise.log_error { "Cloudwise DOCC API error for #{path}: #{e.class.name} #{e.message}" }
871
+ { success: false, error: e.message, code: nil }
872
+ end
873
+
487
874
  end
488
875
  end
489
876
  end