ddtrace 0.34.2 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +58 -9
  3. data/.circleci/images/primary/Dockerfile-jruby-9.2 +77 -0
  4. data/Appraisals +1 -1
  5. data/CHANGELOG.md +33 -1
  6. data/Rakefile +1 -1
  7. data/ddtrace.gemspec +5 -3
  8. data/docs/DevelopmentGuide.md +1 -1
  9. data/docs/GettingStarted.md +89 -36
  10. data/lib/ddtrace.rb +1 -1
  11. data/lib/ddtrace/buffer.rb +9 -9
  12. data/lib/ddtrace/chunker.rb +34 -0
  13. data/lib/ddtrace/configuration.rb +28 -5
  14. data/lib/ddtrace/configuration/components.rb +154 -0
  15. data/lib/ddtrace/configuration/settings.rb +131 -63
  16. data/lib/ddtrace/context.rb +6 -6
  17. data/lib/ddtrace/context_flush.rb +1 -1
  18. data/lib/ddtrace/contrib/action_cable/instrumentation.rb +1 -1
  19. data/lib/ddtrace/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  20. data/lib/ddtrace/contrib/action_view/events/render_partial.rb +1 -1
  21. data/lib/ddtrace/contrib/action_view/events/render_template.rb +1 -1
  22. data/lib/ddtrace/contrib/action_view/instrumentation/partial_renderer.rb +1 -1
  23. data/lib/ddtrace/contrib/action_view/instrumentation/template_renderer.rb +2 -2
  24. data/lib/ddtrace/contrib/action_view/patcher.rb +1 -1
  25. data/lib/ddtrace/contrib/active_record/events/instantiation.rb +1 -1
  26. data/lib/ddtrace/contrib/active_record/events/sql.rb +1 -1
  27. data/lib/ddtrace/contrib/active_support/cache/instrumentation.rb +2 -2
  28. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +2 -2
  29. data/lib/ddtrace/contrib/analytics.rb +1 -1
  30. data/lib/ddtrace/contrib/dalli/patcher.rb +1 -1
  31. data/lib/ddtrace/contrib/dalli/quantize.rb +1 -1
  32. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +1 -1
  33. data/lib/ddtrace/contrib/excon/middleware.rb +2 -2
  34. data/lib/ddtrace/contrib/faraday/patcher.rb +1 -1
  35. data/lib/ddtrace/contrib/grape/endpoint.rb +5 -5
  36. data/lib/ddtrace/contrib/grape/patcher.rb +1 -1
  37. data/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +1 -1
  38. data/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb +2 -2
  39. data/lib/ddtrace/contrib/grpc/patcher.rb +1 -1
  40. data/lib/ddtrace/contrib/http/instrumentation.rb +1 -1
  41. data/lib/ddtrace/contrib/mongodb/subscribers.rb +2 -2
  42. data/lib/ddtrace/contrib/patchable.rb +1 -1
  43. data/lib/ddtrace/contrib/patcher.rb +3 -3
  44. data/lib/ddtrace/contrib/presto/instrumentation.rb +3 -3
  45. data/lib/ddtrace/contrib/presto/patcher.rb +1 -1
  46. data/lib/ddtrace/contrib/rack/middlewares.rb +2 -2
  47. data/lib/ddtrace/contrib/rack/patcher.rb +2 -2
  48. data/lib/ddtrace/contrib/rack/request_queue.rb +1 -1
  49. data/lib/ddtrace/contrib/rake/instrumentation.rb +2 -2
  50. data/lib/ddtrace/contrib/redis/quantize.rb +1 -1
  51. data/lib/ddtrace/contrib/resque/resque_job.rb +2 -2
  52. data/lib/ddtrace/contrib/sidekiq/tracing.rb +1 -1
  53. data/lib/ddtrace/contrib/sinatra/env.rb +20 -0
  54. data/lib/ddtrace/contrib/sinatra/ext.rb +6 -0
  55. data/lib/ddtrace/contrib/sinatra/patcher.rb +1 -0
  56. data/lib/ddtrace/contrib/sinatra/tracer.rb +98 -35
  57. data/lib/ddtrace/contrib/sinatra/tracer_middleware.rb +16 -13
  58. data/lib/ddtrace/correlation.rb +9 -6
  59. data/lib/ddtrace/diagnostics/health.rb +2 -6
  60. data/lib/ddtrace/encoding.rb +13 -39
  61. data/lib/ddtrace/event.rb +1 -1
  62. data/lib/ddtrace/ext/correlation.rb +1 -0
  63. data/lib/ddtrace/ext/diagnostics.rb +2 -0
  64. data/lib/ddtrace/ext/environment.rb +1 -0
  65. data/lib/ddtrace/ext/forced_tracing.rb +1 -1
  66. data/lib/ddtrace/logger.rb +3 -44
  67. data/lib/ddtrace/metrics.rb +5 -5
  68. data/lib/ddtrace/monkey.rb +1 -1
  69. data/lib/ddtrace/opentracer/global_tracer.rb +1 -1
  70. data/lib/ddtrace/pin.rb +1 -1
  71. data/lib/ddtrace/pipeline.rb +1 -1
  72. data/lib/ddtrace/propagation/http_propagator.rb +2 -2
  73. data/lib/ddtrace/runtime/cgroup.rb +1 -1
  74. data/lib/ddtrace/runtime/container.rb +1 -1
  75. data/lib/ddtrace/runtime/metrics.rb +5 -2
  76. data/lib/ddtrace/sampler.rb +2 -2
  77. data/lib/ddtrace/sampling/rule.rb +1 -1
  78. data/lib/ddtrace/sampling/rule_sampler.rb +1 -1
  79. data/lib/ddtrace/span.rb +4 -4
  80. data/lib/ddtrace/sync_writer.rb +3 -8
  81. data/lib/ddtrace/tracer.rb +26 -31
  82. data/lib/ddtrace/transport/http.rb +1 -1
  83. data/lib/ddtrace/transport/http/api/instance.rb +4 -0
  84. data/lib/ddtrace/transport/http/builder.rb +3 -5
  85. data/lib/ddtrace/transport/http/client.rb +7 -64
  86. data/lib/ddtrace/transport/http/response.rb +1 -1
  87. data/lib/ddtrace/transport/http/statistics.rb +1 -1
  88. data/lib/ddtrace/transport/http/traces.rb +10 -7
  89. data/lib/ddtrace/transport/io.rb +1 -1
  90. data/lib/ddtrace/transport/io/client.rb +2 -2
  91. data/lib/ddtrace/transport/io/response.rb +3 -1
  92. data/lib/ddtrace/transport/io/traces.rb +50 -3
  93. data/lib/ddtrace/transport/parcel.rb +0 -4
  94. data/lib/ddtrace/transport/statistics.rb +2 -2
  95. data/lib/ddtrace/transport/traces.rb +160 -10
  96. data/lib/ddtrace/utils.rb +1 -1
  97. data/lib/ddtrace/version.rb +2 -2
  98. data/lib/ddtrace/workers.rb +5 -13
  99. data/lib/ddtrace/workers/async.rb +2 -2
  100. data/lib/ddtrace/workers/runtime_metrics.rb +47 -0
  101. data/lib/ddtrace/workers/trace_writer.rb +199 -0
  102. data/lib/ddtrace/writer.rb +20 -27
  103. metadata +22 -32
@@ -19,7 +19,7 @@ module Datadog
19
19
 
20
20
  # Builds a new Transport::HTTP::Client
21
21
  def new(&block)
22
- Builder.new(&block).to_client
22
+ Builder.new(&block).to_transport
23
23
  end
24
24
 
25
25
  # Builds a new Transport::HTTP::Client with default settings
@@ -15,6 +15,10 @@ module Datadog
15
15
  @headers = options.fetch(:headers, {})
16
16
  end
17
17
 
18
+ def encoder
19
+ spec.encoder
20
+ end
21
+
18
22
  def call(env)
19
23
  # Add headers to request env, unless empty.
20
24
  env.headers.merge!(headers) unless headers.empty?
@@ -70,13 +70,11 @@ module Datadog
70
70
  @default_api = key
71
71
  end
72
72
 
73
- def to_client
73
+ def to_transport
74
74
  raise NoDefaultApiError if @default_api.nil?
75
75
 
76
- @client ||= Client.new(
77
- to_api_instances,
78
- @default_api
79
- )
76
+ # DEV: Should not be specific to traces
77
+ Transport::Traces::Transport.new(to_api_instances, @default_api)
80
78
  end
81
79
 
82
80
  def to_api_instances
@@ -8,42 +8,31 @@ module Datadog
8
8
  class Client
9
9
  include Transport::HTTP::Statistics
10
10
 
11
- attr_reader \
12
- :apis,
13
- :current_api_id
11
+ attr_reader :api
14
12
 
15
- def initialize(apis, current_api_id)
16
- @apis = apis
17
-
18
- # Activate initial API
19
- change_api!(current_api_id)
13
+ def initialize(api)
14
+ @api = api
20
15
  end
21
16
 
22
17
  def send_request(request, &block)
23
18
  # Build request into env
24
19
  env = build_env(request)
25
20
 
26
- # Get response from API
27
- response = yield(current_api, env)
21
+ # Get responses from API
22
+ response = yield(api, env)
28
23
 
29
24
  # Update statistics
30
25
  update_stats_from_response!(response)
31
26
 
32
- # If API should be downgraded, downgrade and try again.
33
- if downgrade?(response)
34
- downgrade!
35
- response = send_request(request, &block)
36
- end
37
-
38
27
  response
39
28
  rescue StandardError => e
40
29
  message = "Internal error during HTTP transport request. Cause: #{e.message} Location: #{e.backtrace.first}"
41
30
 
42
31
  # Log error
43
32
  if stats.consecutive_errors > 0
44
- Datadog::Logger.log.debug(message)
33
+ Datadog.logger.debug(message)
45
34
  else
46
- Datadog::Logger.log.error(message)
35
+ Datadog.logger.error(message)
47
36
  end
48
37
 
49
38
  # Update statistics
@@ -55,52 +44,6 @@ module Datadog
55
44
  def build_env(request)
56
45
  Env.new(request)
57
46
  end
58
-
59
- def downgrade?(response)
60
- return false unless apis.fallbacks.key?(current_api_id)
61
- response.not_found? || response.unsupported?
62
- end
63
-
64
- def current_api
65
- apis[current_api_id]
66
- end
67
-
68
- def change_api!(api_id)
69
- raise UnknownApiVersionError, api_id unless apis.key?(api_id)
70
- @current_api_id = api_id
71
- end
72
-
73
- def downgrade!
74
- downgrade_api_id = apis.fallbacks[current_api_id]
75
- raise NoDowngradeAvailableError, current_api_id if downgrade_api_id.nil?
76
- change_api!(downgrade_api_id)
77
- end
78
-
79
- # Raised when configured with an unknown API version
80
- class UnknownApiVersionError < StandardError
81
- attr_reader :version
82
-
83
- def initialize(version)
84
- @version = version
85
- end
86
-
87
- def message
88
- "No matching transport API for version #{version}!"
89
- end
90
- end
91
-
92
- # Raised when configured with an unknown API version
93
- class NoDowngradeAvailableError < StandardError
94
- attr_reader :version
95
-
96
- def initialize(version)
97
- @version = version
98
- end
99
-
100
- def message
101
- "No downgrade from transport API version #{version} is available!"
102
- end
103
- end
104
47
  end
105
48
  end
106
49
  end
@@ -15,7 +15,7 @@ module Datadog
15
15
  @http_response = http_response
16
16
  end
17
17
 
18
- def_delegators :@http_response, *Transport::Response.instance_methods
18
+ def_delegators :@http_response, *Datadog::Transport::Response.instance_methods
19
19
 
20
20
  def code
21
21
  @http_response.respond_to?(:code) ? @http_response.code : nil
@@ -6,7 +6,7 @@ module Datadog
6
6
  # Tracks statistics for HTTP transports
7
7
  module Statistics
8
8
  def self.included(base)
9
- base.send(:include, Transport::Statistics)
9
+ base.send(:include, Datadog::Transport::Statistics)
10
10
  base.send(:include, InstanceMethods)
11
11
  end
12
12
 
@@ -12,19 +12,18 @@ module Datadog
12
12
  # Response from HTTP transport for traces
13
13
  class Response
14
14
  include HTTP::Response
15
- include Transport::Traces::Response
15
+ include Datadog::Transport::Traces::Response
16
16
 
17
17
  def initialize(http_response, options = {})
18
18
  super(http_response)
19
19
  @service_rates = options.fetch(:service_rates, nil)
20
+ @trace_count = options.fetch(:trace_count, 0)
20
21
  end
21
22
  end
22
23
 
23
24
  # Extensions for HTTP client
24
25
  module Client
25
- def send_traces(traces)
26
- request = Transport::Traces::Request.new(traces)
27
-
26
+ def send_payload(request)
28
27
  send_request(request) do |api, env|
29
28
  api.send_traces(env)
30
29
  end
@@ -45,6 +44,10 @@ module Datadog
45
44
  traces.call(env, &block)
46
45
  end
47
46
 
47
+ def encoder
48
+ traces.encoder
49
+ end
50
+
48
51
  # Raised when traces sent but no traces endpoint is defined
49
52
  class NoTraceEndpointDefinedError < StandardError
50
53
  attr_reader :spec
@@ -104,17 +107,17 @@ module Datadog
104
107
 
105
108
  def call(env, &block)
106
109
  # Add trace count header
107
- env.headers[HEADER_TRACE_COUNT] = env.request.parcel.count.to_s
110
+ env.headers[HEADER_TRACE_COUNT] = env.request.parcel.trace_count.to_s
108
111
 
109
112
  # Encode body & type
110
113
  env.headers[HEADER_CONTENT_TYPE] = encoder.content_type
111
- env.body = env.request.parcel.encode_with(encoder)
114
+ env.body = env.request.parcel.data
112
115
 
113
116
  # Query for response
114
117
  http_response = super(env, &block)
115
118
 
116
119
  # Process the response
117
- response_options = {}.tap do |options|
120
+ response_options = { trace_count: env.request.parcel.trace_count }.tap do |options|
118
121
  # Parse service rates, if configured to do so.
119
122
  if service_rates? && !http_response.payload.to_s.empty?
120
123
  body = JSON.parse(http_response.payload)
@@ -18,7 +18,7 @@ module Datadog
18
18
  def default(options = {})
19
19
  new(
20
20
  options.fetch(:out, STDOUT),
21
- options.fetch(:encoder, Encoding::JSONEncoder::V2)
21
+ options.fetch(:encoder, Encoding::JSONEncoder)
22
22
  )
23
23
  end
24
24
  end
@@ -37,9 +37,9 @@ module Datadog
37
37
 
38
38
  # Log error
39
39
  if stats.consecutive_errors > 0
40
- Datadog::Logger.log.debug(message)
40
+ Datadog.logger.debug(message)
41
41
  else
42
- Datadog::Logger.log.error(message)
42
+ Datadog.logger.error(message)
43
43
  end
44
44
 
45
45
  # Update statistics
@@ -6,12 +6,14 @@ module Datadog
6
6
  # Response from HTTP transport for traces
7
7
  class Response
8
8
  include Transport::Response
9
+ include Transport::Traces::Response
9
10
 
10
11
  attr_reader \
11
12
  :result
12
13
 
13
- def initialize(result)
14
+ def initialize(result, trace_count = 1)
14
15
  @result = result
16
+ @trace_count = trace_count
15
17
  end
16
18
 
17
19
  def ok?
@@ -10,16 +10,15 @@ module Datadog
10
10
  module Traces
11
11
  # Response from HTTP transport for traces
12
12
  class Response < IO::Response
13
- include Transport::Traces::Response
14
13
  end
15
14
 
16
15
  # Extensions for HTTP client
17
16
  module Client
18
17
  def send_traces(traces)
19
18
  # Build a request
20
- req = Transport::Traces::Request.new(traces)
19
+ req = Transport::Traces::Request.new(Parcel.new(traces))
21
20
 
22
- send_request(req) do |out, request|
21
+ [send_request(req) do |out, request|
23
22
  # Encode trace data
24
23
  data = encode_data(encoder, request)
25
24
 
@@ -32,7 +31,55 @@ module Datadog
32
31
 
33
32
  # Generate response
34
33
  Traces::Response.new(result)
34
+ end]
35
+ end
36
+ end
37
+
38
+ # Encoder for IO-specific trace encoding
39
+ # API compliant when used with {JSONEncoder}.
40
+ module Encoder
41
+ ENCODED_IDS = [
42
+ :trace_id,
43
+ :span_id,
44
+ :parent_id
45
+ ].freeze
46
+
47
+ # Encodes a list of traces
48
+ def encode_traces(encoder, traces)
49
+ trace_hashes = traces.map do |trace|
50
+ encode_trace(trace)
35
51
  end
52
+
53
+ # Wrap traces & encode them
54
+ encoder.encode(traces: trace_hashes)
55
+ end
56
+
57
+ private
58
+
59
+ def encode_trace(trace)
60
+ # Convert each trace to hash
61
+ trace.map(&:to_hash).tap do |spans|
62
+ # Convert IDs to hexadecimal
63
+ spans.each do |span|
64
+ ENCODED_IDS.each do |id|
65
+ span[id] = span[id].to_s(16) if span.key?(id)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # Transfer object for list of traces
73
+ class Parcel
74
+ include Transport::Parcel
75
+ include Encoder
76
+
77
+ def count
78
+ data.length
79
+ end
80
+
81
+ def encode_with(encoder)
82
+ encode_traces(encoder, data)
36
83
  end
37
84
  end
38
85
 
@@ -8,10 +8,6 @@ module Datadog
8
8
  def initialize(data)
9
9
  @data = data
10
10
  end
11
-
12
- def encode_with(encoder)
13
- encoder.encode(data)
14
- end
15
11
  end
16
12
  end
17
13
  end
@@ -20,7 +20,7 @@ module Datadog
20
20
  end
21
21
 
22
22
  # Send health metrics
23
- Diagnostics::Health.metrics.send_metrics(
23
+ Datadog.health_metrics.send_metrics(
24
24
  metrics_for_response(response).values
25
25
  )
26
26
  end
@@ -37,7 +37,7 @@ module Datadog
37
37
  stats.consecutive_errors += 1
38
38
 
39
39
  # Send health metrics
40
- Diagnostics::Health.metrics.send_metrics(
40
+ Datadog.health_metrics.send_metrics(
41
41
  metrics_for_exception(exception).values
42
42
  )
43
43
  end
@@ -1,32 +1,182 @@
1
1
  require 'ddtrace/transport/parcel'
2
2
  require 'ddtrace/transport/request'
3
+ require 'ddtrace/chunker'
3
4
 
4
5
  module Datadog
5
6
  module Transport
6
7
  module Traces
7
- # Data transfer object for trace data
8
- class Parcel
8
+ # Data transfer object for encoded traces
9
+ class EncodedParcel
9
10
  include Transport::Parcel
10
11
 
11
- def count
12
- data.length
12
+ attr_reader :trace_count
13
+
14
+ def initialize(data, trace_count)
15
+ super(data)
16
+ @trace_count = trace_count
13
17
  end
14
18
 
15
- def encode_with(encoder)
16
- encoder.encode_traces(data)
19
+ def count
20
+ data.length
17
21
  end
18
22
  end
19
23
 
20
24
  # Traces request
21
25
  class Request < Transport::Request
22
- def initialize(traces)
23
- super(Parcel.new(traces))
24
- end
25
26
  end
26
27
 
27
28
  # Traces response
28
29
  module Response
29
- attr_reader :service_rates
30
+ attr_reader :service_rates, :trace_count
31
+ end
32
+
33
+ # Traces chunker
34
+ class Chunker
35
+ # Trace agent limit payload size of 10 MiB (since agent v5.11.0):
36
+ # https://github.com/DataDog/datadog-agent/blob/6.14.1/pkg/trace/api/api.go#L46
37
+ #
38
+ # We set the value to a conservative 5 MiB, in case network speed is slow.
39
+ DEFAULT_MAX_PAYLOAD_SIZE = 5 * 1024 * 1024
40
+
41
+ attr_reader :encoder, :max_size
42
+
43
+ #
44
+ # Single traces larger than +max_size+ will be discarded.
45
+ #
46
+ # @param encoder [Datadog::Encoding::Encoder]
47
+ # @param max_size [String] maximum acceptable payload size
48
+ def initialize(encoder, max_size: DEFAULT_MAX_PAYLOAD_SIZE)
49
+ @encoder = encoder
50
+ @max_size = max_size
51
+ end
52
+
53
+ # Encodes a list of traces in chunks.
54
+ # Before serializing, all traces are normalized. Trace nesting is not changed.
55
+ #
56
+ # @param traces [Enumerable<Trace>] list of traces
57
+ # @return [Enumerable[Array[Bytes,Integer]]] list of encoded chunks: each containing a byte array and
58
+ # number of traces
59
+ def encode_in_chunks(traces)
60
+ encoded_traces = traces.map { |t| encode_one(t) }.reject(&:nil?)
61
+
62
+ Datadog::Chunker.chunk_by_size(encoded_traces, max_size).map do |chunk|
63
+ [encoder.join(chunk), chunk.size]
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def encode_one(trace)
70
+ encoded = Encoder.encode_trace(encoder, trace)
71
+
72
+ if encoded.size > max_size
73
+ # This single trace is too large, we can't flush it
74
+ Datadog.logger.debug { "Dropping trace. Payload too large: '#{trace.map(&:to_hash)}'" }
75
+ Datadog.health_metrics.transport_trace_too_large(1)
76
+
77
+ return nil
78
+ end
79
+
80
+ encoded
81
+ end
82
+ end
83
+
84
+ # Encodes traces using {Datadog::Encoding::Encoder} instances.
85
+ module Encoder
86
+ module_function
87
+
88
+ def encode_trace(encoder, trace)
89
+ encoder.encode(trace.map(&:to_hash))
90
+ end
91
+ end
92
+
93
+ # Sends traces based on transport API configuration.
94
+ #
95
+ # This class initializes the HTTP client, breaks down large
96
+ # batches of traces into smaller chunks and handles
97
+ # API version downgrade handshake.
98
+ class Transport
99
+ attr_reader :client, :apis, :default_api, :current_api_id
100
+
101
+ def initialize(apis, default_api)
102
+ @apis = apis
103
+ @default_api = default_api
104
+
105
+ change_api!(default_api)
106
+ end
107
+
108
+ def send_traces(traces)
109
+ encoder = current_api.encoder
110
+ chunker = Datadog::Transport::Traces::Chunker.new(encoder)
111
+
112
+ responses = chunker.encode_in_chunks(traces.lazy).map do |encoded_traces, trace_count|
113
+ request = Request.new(EncodedParcel.new(encoded_traces, trace_count))
114
+
115
+ client.send_payload(request).tap do |response|
116
+ if downgrade?(response)
117
+ downgrade!
118
+ return send_traces(traces)
119
+ end
120
+ end
121
+ end.force
122
+
123
+ Datadog.health_metrics.transport_chunked(responses.size)
124
+
125
+ responses
126
+ end
127
+
128
+ def stats
129
+ @client.stats
130
+ end
131
+
132
+ def current_api
133
+ apis[@current_api_id]
134
+ end
135
+
136
+ private
137
+
138
+ def downgrade?(response)
139
+ return false unless apis.fallbacks.key?(@current_api_id)
140
+ response.not_found? || response.unsupported?
141
+ end
142
+
143
+ def downgrade!
144
+ downgrade_api_id = apis.fallbacks[@current_api_id]
145
+ raise NoDowngradeAvailableError, @current_api_id if downgrade_api_id.nil?
146
+ change_api!(downgrade_api_id)
147
+ end
148
+
149
+ def change_api!(api_id)
150
+ raise UnknownApiVersionError, api_id unless apis.key?(api_id)
151
+ @current_api_id = api_id
152
+ @client = HTTP::Client.new(current_api)
153
+ end
154
+
155
+ # Raised when configured with an unknown API version
156
+ class UnknownApiVersionError < StandardError
157
+ attr_reader :version
158
+
159
+ def initialize(version)
160
+ @version = version
161
+ end
162
+
163
+ def message
164
+ "No matching transport API for version #{version}!"
165
+ end
166
+ end
167
+
168
+ # Raised when configured with an unknown API version
169
+ class NoDowngradeAvailableError < StandardError
170
+ attr_reader :version
171
+
172
+ def initialize(version)
173
+ @version = version
174
+ end
175
+
176
+ def message
177
+ "No downgrade from transport API version #{version} is available!"
178
+ end
179
+ end
30
180
  end
31
181
  end
32
182
  end