chargebee 2.74.0 → 2.76.0

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +53 -0
  5. data/VERSION +1 -1
  6. data/chargebee.gemspec +12 -2
  7. data/lib/chargebee/environment.rb +3 -2
  8. data/lib/chargebee/models/addon.rb +7 -7
  9. data/lib/chargebee/models/address.rb +2 -2
  10. data/lib/chargebee/models/alert.rb +6 -6
  11. data/lib/chargebee/models/alert_status.rb +2 -2
  12. data/lib/chargebee/models/attached_item.rb +5 -5
  13. data/lib/chargebee/models/business_entity.rb +2 -2
  14. data/lib/chargebee/models/card.rb +5 -5
  15. data/lib/chargebee/models/comment.rb +4 -4
  16. data/lib/chargebee/models/configuration.rb +1 -1
  17. data/lib/chargebee/models/coupon.rb +9 -9
  18. data/lib/chargebee/models/coupon_code.rb +4 -4
  19. data/lib/chargebee/models/coupon_set.rb +7 -7
  20. data/lib/chargebee/models/credit_note.rb +14 -14
  21. data/lib/chargebee/models/csv_tax_rule.rb +6 -1
  22. data/lib/chargebee/models/currency.rb +6 -6
  23. data/lib/chargebee/models/customer.rb +26 -26
  24. data/lib/chargebee/models/customer_entitlement.rb +1 -1
  25. data/lib/chargebee/models/differential_price.rb +5 -5
  26. data/lib/chargebee/models/entitlement.rb +2 -2
  27. data/lib/chargebee/models/entitlement_override.rb +2 -2
  28. data/lib/chargebee/models/estimate.rb +20 -20
  29. data/lib/chargebee/models/event.rb +2 -2
  30. data/lib/chargebee/models/export.rb +18 -18
  31. data/lib/chargebee/models/feature.rb +8 -8
  32. data/lib/chargebee/models/gift.rb +7 -7
  33. data/lib/chargebee/models/grant_block.rb +1 -1
  34. data/lib/chargebee/models/hosted_page.rb +22 -22
  35. data/lib/chargebee/models/in_app_subscription.rb +4 -4
  36. data/lib/chargebee/models/invoice.rb +40 -40
  37. data/lib/chargebee/models/item.rb +5 -5
  38. data/lib/chargebee/models/item_entitlement.rb +4 -4
  39. data/lib/chargebee/models/item_family.rb +5 -5
  40. data/lib/chargebee/models/item_price.rb +7 -7
  41. data/lib/chargebee/models/ledger_account_balance.rb +1 -1
  42. data/lib/chargebee/models/ledger_operation.rb +6 -6
  43. data/lib/chargebee/models/non_subscription.rb +1 -1
  44. data/lib/chargebee/models/offer_event.rb +1 -1
  45. data/lib/chargebee/models/offer_fulfillment.rb +3 -3
  46. data/lib/chargebee/models/omnichannel_one_time_order.rb +2 -2
  47. data/lib/chargebee/models/omnichannel_subscription.rb +4 -4
  48. data/lib/chargebee/models/omnichannel_subscription_item.rb +1 -1
  49. data/lib/chargebee/models/order.rb +12 -12
  50. data/lib/chargebee/models/payment_intent.rb +3 -3
  51. data/lib/chargebee/models/payment_schedule_scheme.rb +3 -3
  52. data/lib/chargebee/models/payment_source.rb +16 -16
  53. data/lib/chargebee/models/payment_voucher.rb +4 -4
  54. data/lib/chargebee/models/personalized_offer.rb +1 -1
  55. data/lib/chargebee/models/plan.rb +7 -7
  56. data/lib/chargebee/models/portal_session.rb +4 -4
  57. data/lib/chargebee/models/price_variant.rb +5 -5
  58. data/lib/chargebee/models/pricing_page_session.rb +2 -2
  59. data/lib/chargebee/models/promotional_credit.rb +5 -5
  60. data/lib/chargebee/models/promotional_grant.rb +1 -1
  61. data/lib/chargebee/models/purchase.rb +2 -2
  62. data/lib/chargebee/models/quote.rb +26 -26
  63. data/lib/chargebee/models/ramp.rb +5 -5
  64. data/lib/chargebee/models/recorded_purchase.rb +2 -2
  65. data/lib/chargebee/models/resource_migration.rb +1 -1
  66. data/lib/chargebee/models/rule.rb +1 -1
  67. data/lib/chargebee/models/site_migration_detail.rb +1 -1
  68. data/lib/chargebee/models/subscription.rb +37 -37
  69. data/lib/chargebee/models/subscription_entitlement.rb +2 -2
  70. data/lib/chargebee/models/time_machine.rb +3 -3
  71. data/lib/chargebee/models/transaction.rb +11 -11
  72. data/lib/chargebee/models/unbilled_charge.rb +6 -6
  73. data/lib/chargebee/models/usage.rb +5 -5
  74. data/lib/chargebee/models/usage_charge.rb +1 -1
  75. data/lib/chargebee/models/usage_event.rb +2 -2
  76. data/lib/chargebee/models/usage_file.rb +2 -2
  77. data/lib/chargebee/models/usage_summary.rb +1 -1
  78. data/lib/chargebee/models/virtual_bank_account.rb +6 -6
  79. data/lib/chargebee/models/webhook_endpoint.rb +5 -5
  80. data/lib/chargebee/request.rb +37 -8
  81. data/lib/chargebee/telemetry/request_telemetry_context.rb +29 -0
  82. data/lib/chargebee/telemetry/request_telemetry_error.rb +20 -0
  83. data/lib/chargebee/telemetry/request_telemetry_result.rb +20 -0
  84. data/lib/chargebee/telemetry/telemetry_adapter.rb +32 -0
  85. data/lib/chargebee/telemetry/telemetry_attribute_keys.rb +33 -0
  86. data/lib/chargebee/telemetry/telemetry_support.rb +123 -0
  87. data/lib/chargebee/telemetry_executor.rb +95 -0
  88. data/lib/chargebee.rb +9 -1
  89. data/spec/chargebee/request_telemetry_spec.rb +75 -0
  90. data/spec/chargebee/telemetry_executor_spec.rb +147 -0
  91. data/spec/chargebee/telemetry_support_spec.rb +29 -0
  92. metadata +15 -2
@@ -14,7 +14,7 @@ module ChargeBee
14
14
  options = {
15
15
  :isIdempotent => true
16
16
  }
17
- Request.send('post', uri_path("virtual_bank_accounts","create_using_permanent_token"), params, env, headers,nil, false, jsonKeys, options)
17
+ Request.send('post', uri_path("virtual_bank_accounts","create_using_permanent_token"), params, env, headers,nil, false, jsonKeys, options, telemetry_resource: "virtualBankAccount", telemetry_operation: "createUsingPermanentToken")
18
18
  end
19
19
 
20
20
  def self.create(params, env=nil, headers={})
@@ -23,21 +23,21 @@ module ChargeBee
23
23
  options = {
24
24
  :isIdempotent => true
25
25
  }
26
- Request.send('post', uri_path("virtual_bank_accounts"), params, env, headers,nil, false, jsonKeys, options)
26
+ Request.send('post', uri_path("virtual_bank_accounts"), params, env, headers,nil, false, jsonKeys, options, telemetry_resource: "virtualBankAccount", telemetry_operation: "create")
27
27
  end
28
28
 
29
29
  def self.retrieve(id, env=nil, headers={})
30
30
  jsonKeys = {
31
31
  }
32
32
  options = {}
33
- Request.send('get', uri_path("virtual_bank_accounts",id.to_s), {}, env, headers,nil, false, jsonKeys, options)
33
+ Request.send('get', uri_path("virtual_bank_accounts",id.to_s), {}, env, headers,nil, false, jsonKeys, options, telemetry_resource: "virtualBankAccount", telemetry_operation: "retrieve")
34
34
  end
35
35
 
36
36
  def self.list(params={}, env=nil, headers={})
37
37
  jsonKeys = {
38
38
  }
39
39
  options = {}
40
- Request.send_list_request('get', uri_path("virtual_bank_accounts"), params, env, headers,nil, false, jsonKeys, options)
40
+ Request.send_list_request('get', uri_path("virtual_bank_accounts"), params, env, headers,nil, false, jsonKeys, options, telemetry_resource: "virtualBankAccount", telemetry_operation: "list")
41
41
  end
42
42
 
43
43
  def self.delete(id, env=nil, headers={})
@@ -46,7 +46,7 @@ module ChargeBee
46
46
  options = {
47
47
  :isIdempotent => true
48
48
  }
49
- Request.send('post', uri_path("virtual_bank_accounts",id.to_s,"delete"), {}, env, headers,nil, false, jsonKeys, options)
49
+ Request.send('post', uri_path("virtual_bank_accounts",id.to_s,"delete"), {}, env, headers,nil, false, jsonKeys, options, telemetry_resource: "virtualBankAccount", telemetry_operation: "delete")
50
50
  end
51
51
 
52
52
  def self.delete_local(id, env=nil, headers={})
@@ -55,7 +55,7 @@ module ChargeBee
55
55
  options = {
56
56
  :isIdempotent => true
57
57
  }
58
- Request.send('post', uri_path("virtual_bank_accounts",id.to_s,"delete_local"), {}, env, headers,nil, false, jsonKeys, options)
58
+ Request.send('post', uri_path("virtual_bank_accounts",id.to_s,"delete_local"), {}, env, headers,nil, false, jsonKeys, options, telemetry_resource: "virtualBankAccount", telemetry_operation: "deleteLocal")
59
59
  end
60
60
 
61
61
  end # ~VirtualBankAccount
@@ -13,7 +13,7 @@ module ChargeBee
13
13
  options = {
14
14
  :isIdempotent => true
15
15
  }
16
- Request.send('post', uri_path("webhook_endpoints"), params, env, headers,nil, false, jsonKeys, options)
16
+ Request.send('post', uri_path("webhook_endpoints"), params, env, headers,nil, false, jsonKeys, options, telemetry_resource: "webhookEndpoint", telemetry_operation: "create")
17
17
  end
18
18
 
19
19
  def self.update(id, params={}, env=nil, headers={})
@@ -22,14 +22,14 @@ module ChargeBee
22
22
  options = {
23
23
  :isIdempotent => true
24
24
  }
25
- Request.send('post', uri_path("webhook_endpoints",id.to_s), params, env, headers,nil, false, jsonKeys, options)
25
+ Request.send('post', uri_path("webhook_endpoints",id.to_s), params, env, headers,nil, false, jsonKeys, options, telemetry_resource: "webhookEndpoint", telemetry_operation: "update")
26
26
  end
27
27
 
28
28
  def self.retrieve(id, env=nil, headers={})
29
29
  jsonKeys = {
30
30
  }
31
31
  options = {}
32
- Request.send('get', uri_path("webhook_endpoints",id.to_s), {}, env, headers,nil, false, jsonKeys, options)
32
+ Request.send('get', uri_path("webhook_endpoints",id.to_s), {}, env, headers,nil, false, jsonKeys, options, telemetry_resource: "webhookEndpoint", telemetry_operation: "retrieve")
33
33
  end
34
34
 
35
35
  def self.delete(id, env=nil, headers={})
@@ -38,14 +38,14 @@ module ChargeBee
38
38
  options = {
39
39
  :isIdempotent => true
40
40
  }
41
- Request.send('post', uri_path("webhook_endpoints",id.to_s,"delete"), {}, env, headers,nil, false, jsonKeys, options)
41
+ Request.send('post', uri_path("webhook_endpoints",id.to_s,"delete"), {}, env, headers,nil, false, jsonKeys, options, telemetry_resource: "webhookEndpoint", telemetry_operation: "delete")
42
42
  end
43
43
 
44
44
  def self.list(params={}, env=nil, headers={})
45
45
  jsonKeys = {
46
46
  }
47
47
  options = {}
48
- Request.send_list_request('get', uri_path("webhook_endpoints"), params, env, headers,nil, false, jsonKeys, options)
48
+ Request.send_list_request('get', uri_path("webhook_endpoints"), params, env, headers,nil, false, jsonKeys, options, telemetry_resource: "webhookEndpoint", telemetry_operation: "list")
49
49
  end
50
50
 
51
51
  end # ~WebhookEndpoint
@@ -1,7 +1,7 @@
1
1
  module ChargeBee
2
2
  class Request
3
3
 
4
- def self.send_list_request(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={}, options={})
4
+ def self.send_list_request(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={}, options={}, telemetry_resource=nil, telemetry_operation=nil)
5
5
  serialized = {}
6
6
  params.each do |k, v|
7
7
  if(v.kind_of? Array)
@@ -9,19 +9,48 @@ module ChargeBee
9
9
  end
10
10
  serialized["#{k}"] = v
11
11
  end
12
- self.send(method, url, serialized, env, headers, sub_domain, isJsonRequest=nil, jsonKeys={}, options)
12
+ self.send(method, url, serialized, env, headers, sub_domain, isJsonRequest, jsonKeys, options, telemetry_resource, telemetry_operation)
13
13
  end
14
14
 
15
- def self.send(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={}, options={})
15
+ def self.send(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={}, options={}, telemetry_resource=nil, telemetry_operation=nil)
16
+ telemetry_resource, telemetry_operation = normalize_telemetry_args(telemetry_resource, telemetry_operation)
16
17
  env ||= ChargeBee.default_env
17
18
  ser_params = isJsonRequest ? params : Util.serialize(params, nil, nil, jsonKeys)
18
- resp, rheaders, rcode = NativeRequest.request(method, url, env, ser_params||={}, headers, sub_domain, isJsonRequest, options)
19
- if resp&.has_key?(:list)
20
- ListResult.new(resp[:list], resp[:next_offset], rheaders, rcode)
21
- else
22
- Result.new(resp, rheaders, rcode)
19
+ http_url = TelemetryExecutor.build_http_url(env, sub_domain, url)
20
+ normalized_headers = headers || {}
21
+
22
+ TelemetryExecutor.execute(
23
+ env,
24
+ telemetry_resource: telemetry_resource,
25
+ telemetry_operation: telemetry_operation,
26
+ method: method,
27
+ http_url: http_url,
28
+ request_headers: normalized_headers,
29
+ ) do |telemetry_headers|
30
+ merged_headers = normalized_headers.dup
31
+ if telemetry_headers
32
+ telemetry_headers.each { |key, value| merged_headers[key] = value }
33
+ end
34
+ resp, rheaders, rcode = NativeRequest.request(method, url, env, ser_params||={}, merged_headers, sub_domain, isJsonRequest, options)
35
+ result = if resp && resp.has_key?(:list)
36
+ ListResult.new(resp[:list], resp[:next_offset], rheaders, rcode)
37
+ else
38
+ Result.new(resp, rheaders, rcode)
39
+ end
40
+ [rcode.to_i, result]
23
41
  end
24
42
  end
25
43
 
44
+ def self.normalize_telemetry_args(telemetry_resource, telemetry_operation)
45
+ if telemetry_resource.is_a?(Hash) && telemetry_operation.nil? &&
46
+ (telemetry_resource.key?(:telemetry_resource) || telemetry_resource.key?(:telemetry_operation))
47
+ h = telemetry_resource
48
+ return [h[:telemetry_resource], h[:telemetry_operation]]
49
+ end
50
+
51
+ [telemetry_resource, telemetry_operation]
52
+ end
53
+ private_class_method :normalize_telemetry_args
54
+
26
55
  end
27
56
  end
@@ -0,0 +1,29 @@
1
+ # This file is auto-generated by Chargebee.
2
+ # For more information on how to make changes to this file, please see the README.
3
+ # Reach out to dx@chargebee.com for any questions.
4
+ # Copyright 2026 Chargebee Inc.
5
+
6
+ module ChargeBee
7
+ module Telemetry
8
+ # Context passed to TelemetryAdapter#on_request_start.
9
+ class RequestTelemetryContext
10
+ attr_reader :span_name, :resource, :operation, :http_method, :http_url, :server_address,
11
+ :chargebee_site, :chargebee_api_version, :sdk_name, :sdk_version, :start_attributes
12
+
13
+ def initialize(span_name:, resource:, operation:, http_method:, http_url:, server_address:,
14
+ chargebee_site:, chargebee_api_version:, sdk_name:, sdk_version:, start_attributes:)
15
+ @span_name = span_name
16
+ @resource = resource
17
+ @operation = operation
18
+ @http_method = http_method
19
+ @http_url = http_url
20
+ @server_address = server_address
21
+ @chargebee_site = chargebee_site
22
+ @chargebee_api_version = chargebee_api_version
23
+ @sdk_name = sdk_name
24
+ @sdk_version = sdk_version
25
+ @start_attributes = start_attributes.freeze
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ # This file is auto-generated by Chargebee.
2
+ # For more information on how to make changes to this file, please see the README.
3
+ # Reach out to dx@chargebee.com for any questions.
4
+ # Copyright 2026 Chargebee Inc.
5
+
6
+ module ChargeBee
7
+ module Telemetry
8
+ # Error details attached to RequestTelemetryResult on failed requests.
9
+ class RequestTelemetryError
10
+ attr_reader :message, :chargebee_error_code, :chargebee_api_error_type, :chargebee_error_param
11
+
12
+ def initialize(message:, chargebee_error_code: nil, chargebee_api_error_type: nil, chargebee_error_param: nil)
13
+ @message = message
14
+ @chargebee_error_code = chargebee_error_code
15
+ @chargebee_api_error_type = chargebee_api_error_type
16
+ @chargebee_error_param = chargebee_error_param
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # This file is auto-generated by Chargebee.
2
+ # For more information on how to make changes to this file, please see the README.
3
+ # Reach out to dx@chargebee.com for any questions.
4
+ # Copyright 2026 Chargebee Inc.
5
+
6
+ module ChargeBee
7
+ module Telemetry
8
+ # Result passed to TelemetryAdapter#on_request_end.
9
+ class RequestTelemetryResult
10
+ attr_reader :http_status_code, :duration_ms, :error, :end_attributes
11
+
12
+ def initialize(http_status_code:, duration_ms:, error:, end_attributes:)
13
+ @http_status_code = http_status_code
14
+ @duration_ms = duration_ms
15
+ @error = error
16
+ @end_attributes = end_attributes.freeze
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ # This file is auto-generated by Chargebee.
2
+ # For more information on how to make changes to this file, please see the README.
3
+ # Reach out to dx@chargebee.com for any questions.
4
+ # Copyright 2026 Chargebee Inc.
5
+
6
+ module ChargeBee
7
+ module Telemetry
8
+ # Optional telemetry adapter for observability integrations (e.g. OpenTelemetry).
9
+ #
10
+ # When not configured, the SDK skips all telemetry work — zero overhead. The SDK stores the
11
+ # adapter by reference and never clones or deep-copies it.
12
+ #
13
+ # Implementations may inject W3C trace context headers (traceparent, tracestate) into
14
+ # request_headers during on_request_start.
15
+ module TelemetryAdapter
16
+ # Called once per SDK API call before the request is sent (including before retries).
17
+ #
18
+ # context contains prebuilt start attributes. request_headers is a mutable map for trace propagation.
19
+ # Returns an opaque handle passed back to on_request_end, or nil if span creation was skipped.
20
+ def on_request_start(context, request_headers)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Called once per SDK API call after the final response or terminal failure.
25
+ #
26
+ # handle is the value returned from on_request_start and may be nil. result contains prebuilt end attributes.
27
+ def on_request_end(handle, result)
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # This file is auto-generated by Chargebee.
2
+ # For more information on how to make changes to this file, please see the README.
3
+ # Reach out to dx@chargebee.com for any questions.
4
+ # Copyright 2026 Chargebee Inc.
5
+
6
+ module ChargeBee
7
+ module Telemetry
8
+ # Span attribute keys shared across Chargebee SDKs.
9
+ module TelemetryAttributeKeys
10
+ SDK_NAME = 'chargebee-ruby'.freeze
11
+ TELEMETRY_SPAN_NAME_PREFIX = 'chargebee'.freeze
12
+
13
+ HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX = 'http.request.header.'.freeze
14
+ CHARGEBEE_TELEMETRY_HEADER_PREFIX = 'chargebee-'.freeze
15
+ CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX = 'chargebee-request-origin-'.freeze
16
+
17
+ URL_FULL = 'url.full'.freeze
18
+ HTTP_REQUEST_METHOD = 'http.request.method'.freeze
19
+ HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code'.freeze
20
+ SERVER_ADDRESS = 'server.address'.freeze
21
+ ERROR_TYPE = 'error.type'.freeze
22
+ CHARGEBEE_SITE = 'chargebee.site'.freeze
23
+ CHARGEBEE_API_VERSION = 'chargebee.api_version'.freeze
24
+ CHARGEBEE_RESOURCE = 'chargebee.resource'.freeze
25
+ CHARGEBEE_OPERATION = 'chargebee.operation'.freeze
26
+ CHARGEBEE_SDK_NAME = 'chargebee.sdk.name'.freeze
27
+ CHARGEBEE_SDK_VERSION = 'chargebee.sdk.version'.freeze
28
+ CHARGEBEE_ERROR_CODE = 'chargebee.error.code'.freeze
29
+ CHARGEBEE_ERROR_TYPE = 'chargebee.error.type'.freeze
30
+ CHARGEBEE_ERROR_PARAM = 'chargebee.error.param'.freeze
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,123 @@
1
+ # This file is auto-generated by Chargebee.
2
+ # For more information on how to make changes to this file, please see the README.
3
+ # Reach out to dx@chargebee.com for any questions.
4
+ # Copyright 2026 Chargebee Inc.
5
+
6
+ module ChargeBee
7
+ module Telemetry
8
+ # Helpers for building standardized Chargebee telemetry context and results.
9
+ class TelemetrySupport
10
+ class << self
11
+ def build_span_name(resource, operation)
12
+ "#{TelemetryAttributeKeys::TELEMETRY_SPAN_NAME_PREFIX}.#{resource}.#{operation}"
13
+ end
14
+
15
+ def resolve_chargebee_api_version(api_path)
16
+ api_path == '/api/v1' ? 'v1' : 'v2'
17
+ end
18
+
19
+ def build_request_start_span_attributes(resource, operation, http_method, http_url, server_address,
20
+ chargebee_site, chargebee_api_version, sdk_version, request_headers = {})
21
+ {
22
+ TelemetryAttributeKeys::URL_FULL => http_url,
23
+ TelemetryAttributeKeys::HTTP_REQUEST_METHOD => http_method,
24
+ TelemetryAttributeKeys::SERVER_ADDRESS => server_address,
25
+ TelemetryAttributeKeys::CHARGEBEE_SITE => chargebee_site,
26
+ TelemetryAttributeKeys::CHARGEBEE_API_VERSION => chargebee_api_version,
27
+ TelemetryAttributeKeys::CHARGEBEE_RESOURCE => resource,
28
+ TelemetryAttributeKeys::CHARGEBEE_OPERATION => operation,
29
+ TelemetryAttributeKeys::CHARGEBEE_SDK_NAME => TelemetryAttributeKeys::SDK_NAME,
30
+ TelemetryAttributeKeys::CHARGEBEE_SDK_VERSION => sdk_version,
31
+ }.merge(build_request_header_span_attributes(request_headers))
32
+ end
33
+
34
+ # Promotes chargebee-* request headers to http.request.header.* attributes; excludes the chargebee-request-origin-* PII family.
35
+ def build_request_header_span_attributes(request_headers)
36
+ attributes = {}
37
+ return attributes if request_headers.nil?
38
+
39
+ request_headers.each do |name, value|
40
+ next if name.nil? || value.nil?
41
+
42
+ lower_name = name.to_s.downcase
43
+ next unless lower_name.start_with?(TelemetryAttributeKeys::CHARGEBEE_TELEMETRY_HEADER_PREFIX)
44
+ next if lower_name.start_with?(TelemetryAttributeKeys::CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX)
45
+
46
+ attributes["#{TelemetryAttributeKeys::HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX}#{lower_name}"] = value.to_s
47
+ end
48
+
49
+ attributes
50
+ end
51
+
52
+ def build_request_end_span_attributes(http_status_code, error)
53
+ attributes = {
54
+ TelemetryAttributeKeys::HTTP_RESPONSE_STATUS_CODE => http_status_code,
55
+ }
56
+
57
+ if error
58
+ if error.chargebee_api_error_type
59
+ attributes[TelemetryAttributeKeys::ERROR_TYPE] = error.chargebee_api_error_type
60
+ attributes[TelemetryAttributeKeys::CHARGEBEE_ERROR_TYPE] = error.chargebee_api_error_type
61
+ end
62
+ attributes[TelemetryAttributeKeys::CHARGEBEE_ERROR_CODE] = error.chargebee_error_code if error.chargebee_error_code
63
+ attributes[TelemetryAttributeKeys::CHARGEBEE_ERROR_PARAM] = error.chargebee_error_param if error.chargebee_error_param
64
+ end
65
+
66
+ attributes
67
+ end
68
+
69
+ def build_request_telemetry_context(resource, operation, http_method, http_url, server_address,
70
+ chargebee_site, chargebee_api_version, sdk_version, request_headers = {})
71
+ RequestTelemetryContext.new(
72
+ span_name: build_span_name(resource, operation),
73
+ resource: resource,
74
+ operation: operation,
75
+ http_method: http_method,
76
+ http_url: http_url,
77
+ server_address: server_address,
78
+ chargebee_site: chargebee_site,
79
+ chargebee_api_version: chargebee_api_version,
80
+ sdk_name: TelemetryAttributeKeys::SDK_NAME,
81
+ sdk_version: sdk_version,
82
+ start_attributes: build_request_start_span_attributes(
83
+ resource, operation, http_method, http_url, server_address, chargebee_site, chargebee_api_version, sdk_version, request_headers
84
+ ),
85
+ )
86
+ end
87
+
88
+ def build_request_telemetry_result(http_status_code, duration_ms, error)
89
+ RequestTelemetryResult.new(
90
+ http_status_code: http_status_code,
91
+ duration_ms: duration_ms,
92
+ error: error,
93
+ end_attributes: build_request_end_span_attributes(http_status_code, error),
94
+ )
95
+ end
96
+
97
+ def extract_request_telemetry_error(err)
98
+ return nil if err.nil?
99
+
100
+ message = err.message.to_s
101
+ message = 'Chargebee API request failed' if message.empty?
102
+
103
+ if err.is_a?(ChargeBee::APIError)
104
+ return RequestTelemetryError.new(
105
+ message: message,
106
+ chargebee_error_code: err.api_error_code,
107
+ chargebee_api_error_type: err.type.to_s,
108
+ chargebee_error_param: err.param,
109
+ )
110
+ end
111
+
112
+ RequestTelemetryError.new(message: message)
113
+ end
114
+
115
+ def extract_http_status_code(err)
116
+ return err.http_status_code if err.is_a?(ChargeBee::APIError)
117
+
118
+ nil
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,95 @@
1
+ require 'uri'
2
+
3
+ module ChargeBee
4
+ class TelemetryExecutor
5
+ class << self
6
+ def execute(env, telemetry_resource:, telemetry_operation:, method:, http_url:, request_headers: {})
7
+ adapter = env.telemetry_adapter
8
+ if adapter.nil? || telemetry_resource.to_s.empty? || telemetry_operation.to_s.empty?
9
+ _status, result = yield(nil)
10
+ return result
11
+ end
12
+
13
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
+ headers = {}
15
+ handle = start_telemetry(
16
+ env, adapter, telemetry_resource, telemetry_operation, method, http_url, request_headers, headers
17
+ )
18
+
19
+ begin
20
+ telemetry_headers = headers.empty? ? nil : headers
21
+ status_code, result = yield(telemetry_headers)
22
+ end_telemetry_success(adapter, handle, start, status_code)
23
+ result
24
+ rescue => err
25
+ end_telemetry_failure(adapter, handle, start, err)
26
+ raise
27
+ end
28
+ end
29
+
30
+ def build_http_url(env, sub_domain, url)
31
+ path = url.start_with?('/') ? url : "/#{url}"
32
+ full = env.api_url(path, sub_domain)
33
+ uri = URI.parse(full)
34
+ "#{uri.scheme}://#{uri.host}#{uri.path}"
35
+ end
36
+
37
+ private
38
+
39
+ def start_telemetry(env, adapter, resource, operation, method, http_url, request_headers, headers)
40
+ uri = URI.parse(http_url)
41
+ host = uri.host || ''
42
+ api_path = "/api/#{Environment::API_VERSION}"
43
+ context = Telemetry::TelemetrySupport.build_request_telemetry_context(
44
+ resource,
45
+ operation,
46
+ method.to_s.upcase,
47
+ http_url,
48
+ host,
49
+ env.site,
50
+ Telemetry::TelemetrySupport.resolve_chargebee_api_version(api_path),
51
+ ChargeBee::VERSION,
52
+ request_headers,
53
+ )
54
+ safe_on_request_start(env, adapter, context, headers)
55
+ end
56
+
57
+ def end_telemetry_success(adapter, handle, start, http_status_code)
58
+ duration_ms = elapsed_ms(start)
59
+ result = Telemetry::TelemetrySupport.build_request_telemetry_result(http_status_code, duration_ms, nil)
60
+ safe_on_request_end(adapter, handle, result)
61
+ end
62
+
63
+ def end_telemetry_failure(adapter, handle, start, err)
64
+ status_code = Telemetry::TelemetrySupport.extract_http_status_code(err) || 500
65
+ duration_ms = elapsed_ms(start)
66
+ telemetry_error = Telemetry::TelemetrySupport.extract_request_telemetry_error(err)
67
+ result = Telemetry::TelemetrySupport.build_request_telemetry_result(status_code, duration_ms, telemetry_error)
68
+ safe_on_request_end(adapter, handle, result)
69
+ end
70
+
71
+ def safe_on_request_start(env, adapter, context, headers)
72
+ adapter.on_request_start(context, headers)
73
+ rescue => err
74
+ log_telemetry_error(env, 'on_request_start', err)
75
+ nil
76
+ end
77
+
78
+ def safe_on_request_end(adapter, handle, result)
79
+ adapter.on_request_end(handle, result)
80
+ rescue => err
81
+ warn "[ChargeBee] Telemetry adapter on_request_end failed: #{err}"
82
+ end
83
+
84
+ def log_telemetry_error(env, phase, err)
85
+ return unless env.enable_debug_logs
86
+
87
+ warn "[ChargeBee] Telemetry adapter #{phase} failed: #{err}. Continuing without telemetry."
88
+ end
89
+
90
+ def elapsed_ms(start)
91
+ ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
92
+ end
93
+ end
94
+ end
95
+ end
data/lib/chargebee.rb CHANGED
@@ -7,6 +7,14 @@ require File.dirname(__FILE__) + '/chargebee/list_result'
7
7
 
8
8
  require File.dirname(__FILE__) + '/chargebee/errors'
9
9
 
10
+ require File.dirname(__FILE__) + '/chargebee/telemetry/telemetry_attribute_keys'
11
+ require File.dirname(__FILE__) + '/chargebee/telemetry/request_telemetry_context'
12
+ require File.dirname(__FILE__) + '/chargebee/telemetry/request_telemetry_error'
13
+ require File.dirname(__FILE__) + '/chargebee/telemetry/request_telemetry_result'
14
+ require File.dirname(__FILE__) + '/chargebee/telemetry/telemetry_adapter'
15
+ require File.dirname(__FILE__) + '/chargebee/telemetry/telemetry_support'
16
+ require File.dirname(__FILE__) + '/chargebee/telemetry_executor'
17
+
10
18
  require File.dirname(__FILE__) + '/chargebee/models/model'
11
19
  require File.dirname(__FILE__) + '/chargebee/models/addon'
12
20
  require File.dirname(__FILE__) + '/chargebee/models/address'
@@ -121,7 +129,7 @@ require File.dirname(__FILE__) + '/chargebee/models/webhook_endpoint'
121
129
 
122
130
  module ChargeBee
123
131
 
124
- VERSION = '2.74.0'
132
+ VERSION = '2.76.0'
125
133
 
126
134
  @@default_env = nil
127
135
  @@verify_ca_certs = true
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChargeBee::Request do
4
+ class InjectTraceAdapter
5
+ include ChargeBee::Telemetry::TelemetryAdapter
6
+
7
+ def on_request_start(_context, request_headers)
8
+ request_headers['traceparent'] = '00-test-trace'
9
+ nil
10
+ end
11
+
12
+ def on_request_end(_handle, _result)
13
+ end
14
+ end
15
+
16
+ it 'does not mutate caller headers when telemetry injects trace headers' do
17
+ env = ChargeBee::Environment.new(
18
+ api_key: 'test_key',
19
+ site: 'acme',
20
+ telemetry_adapter: InjectTraceAdapter.new,
21
+ )
22
+ original_headers = { 'chargebee-foo' => 'bar' }
23
+ headers = original_headers.dup
24
+
25
+ ChargeBee::NativeRequest.expects(:request).with do |_method, _url, _env, _params, merged_headers, *_rest|
26
+ expect(merged_headers['traceparent']).to eq('00-test-trace')
27
+ expect(merged_headers['chargebee-foo']).to eq('bar')
28
+ true
29
+ end.returns([{}, {}, '200'])
30
+
31
+ ChargeBee::Request.send(
32
+ 'get',
33
+ '/customers',
34
+ {},
35
+ env,
36
+ headers,
37
+ nil,
38
+ false,
39
+ {},
40
+ {},
41
+ telemetry_resource: 'customer',
42
+ telemetry_operation: 'list',
43
+ )
44
+
45
+ expect(headers).to eq(original_headers)
46
+ expect(headers).not_to have_key('traceparent')
47
+ end
48
+
49
+ it 'accepts nil headers when telemetry injects trace headers' do
50
+ env = ChargeBee::Environment.new(
51
+ api_key: 'test_key',
52
+ site: 'acme',
53
+ telemetry_adapter: InjectTraceAdapter.new,
54
+ )
55
+
56
+ ChargeBee::NativeRequest.expects(:request).with do |_method, _url, _env, _params, merged_headers, *_rest|
57
+ expect(merged_headers['traceparent']).to eq('00-test-trace')
58
+ true
59
+ end.returns([{}, {}, '200'])
60
+
61
+ ChargeBee::Request.send(
62
+ 'get',
63
+ '/customers',
64
+ {},
65
+ env,
66
+ nil,
67
+ nil,
68
+ false,
69
+ {},
70
+ {},
71
+ 'customer',
72
+ 'list',
73
+ )
74
+ end
75
+ end