aws-xray-sdk 0.9.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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aws-xray-sdk.rb +10 -0
  3. data/lib/aws-xray-sdk/configuration.rb +158 -0
  4. data/lib/aws-xray-sdk/context/context.rb +26 -0
  5. data/lib/aws-xray-sdk/context/default_context.rb +81 -0
  6. data/lib/aws-xray-sdk/emitter/default_emitter.rb +53 -0
  7. data/lib/aws-xray-sdk/emitter/emitter.rb +24 -0
  8. data/lib/aws-xray-sdk/exceptions.rb +31 -0
  9. data/lib/aws-xray-sdk/facets/aws_sdk.rb +127 -0
  10. data/lib/aws-xray-sdk/facets/helper.rb +61 -0
  11. data/lib/aws-xray-sdk/facets/net_http.rb +61 -0
  12. data/lib/aws-xray-sdk/facets/rack.rb +87 -0
  13. data/lib/aws-xray-sdk/facets/rails/active_record.rb +66 -0
  14. data/lib/aws-xray-sdk/facets/rails/ex_middleware.rb +24 -0
  15. data/lib/aws-xray-sdk/facets/rails/railtie.rb +23 -0
  16. data/lib/aws-xray-sdk/facets/resources/aws_params_whitelist.rb +340 -0
  17. data/lib/aws-xray-sdk/facets/resources/aws_services_whitelist.rb +147 -0
  18. data/lib/aws-xray-sdk/logger.rb +19 -0
  19. data/lib/aws-xray-sdk/model/annotations.rb +97 -0
  20. data/lib/aws-xray-sdk/model/cause.rb +70 -0
  21. data/lib/aws-xray-sdk/model/dummy_entities.rb +72 -0
  22. data/lib/aws-xray-sdk/model/entity.rb +187 -0
  23. data/lib/aws-xray-sdk/model/metadata.rb +77 -0
  24. data/lib/aws-xray-sdk/model/segment.rb +63 -0
  25. data/lib/aws-xray-sdk/model/subsegment.rb +67 -0
  26. data/lib/aws-xray-sdk/model/trace_header.rb +54 -0
  27. data/lib/aws-xray-sdk/patcher.rb +21 -0
  28. data/lib/aws-xray-sdk/plugins/ec2.rb +39 -0
  29. data/lib/aws-xray-sdk/plugins/ecs.rb +23 -0
  30. data/lib/aws-xray-sdk/plugins/elastic_beanstalk.rb +25 -0
  31. data/lib/aws-xray-sdk/recorder.rb +209 -0
  32. data/lib/aws-xray-sdk/sampling/default_sampler.rb +105 -0
  33. data/lib/aws-xray-sdk/sampling/reservoir.rb +35 -0
  34. data/lib/aws-xray-sdk/sampling/sampler.rb +27 -0
  35. data/lib/aws-xray-sdk/sampling/sampling_rule.rb +57 -0
  36. data/lib/aws-xray-sdk/search_pattern.rb +82 -0
  37. data/lib/aws-xray-sdk/segment_naming/dynamic_naming.rb +26 -0
  38. data/lib/aws-xray-sdk/segment_naming/segment_naming.rb +10 -0
  39. data/lib/aws-xray-sdk/streaming/default_streamer.rb +53 -0
  40. data/lib/aws-xray-sdk/streaming/streamer.rb +17 -0
  41. data/lib/aws-xray-sdk/version.rb +3 -0
  42. metadata +224 -0
@@ -0,0 +1,147 @@
1
+ module XRay
2
+ # AWS Services listed below will be recorded as subsegments
3
+ module AwsServices
4
+ # exausted list can be tracked at http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Seahorse/Client/Base.html
5
+ @whitelist = %I[
6
+ ACM
7
+ APIGateway
8
+ AlexaForBusiness
9
+ AppStream
10
+ AppSync
11
+ ApplicationAutoScaling
12
+ ApplicationDiscoveryService
13
+ Athena
14
+ AutoScaling
15
+ Batch
16
+ Budgets
17
+ Cloud9
18
+ CloudDirectory
19
+ CloudFormatioin
20
+ CloudFront
21
+ CloudHSM
22
+ CloudHSMV2
23
+ CloudSearch
24
+ CloudSearchDomain
25
+ CloudTrail
26
+ CloudWatch
27
+ CloudWatchEvents
28
+ CloudWatchLogs
29
+ CodeBuild
30
+ CodeCommit
31
+ CodeDeploy
32
+ CodePipeline
33
+ CodeStar
34
+ CognitoIdentity
35
+ CognitoIdentityProvider
36
+ CognitoSync
37
+ Comprehend
38
+ ConfigService
39
+ CostExplore
40
+ CostandUsageReportService
41
+ DAX
42
+ DataPipeline
43
+ DatabaseMigrationService
44
+ DeviceFarm
45
+ DirectConnect
46
+ DirectoryService
47
+ DynamoDB
48
+ DynamoDBStreams
49
+ EC2
50
+ ECR
51
+ ECS
52
+ EFS
53
+ EMR
54
+ ElastiCache
55
+ ElasticBeanstalk
56
+ ElasticLoadBalancing
57
+ ElasticLoadBalancingV2
58
+ ElasticTranscoder
59
+ ElasticsearchService
60
+ Firehost
61
+ GameLift
62
+ Glacier
63
+ Glue
64
+ Greengrass
65
+ GuardDuty
66
+ Health
67
+ IAM
68
+ ImportExport
69
+ Inspector
70
+ IoT
71
+ IoTDataPlane
72
+ IoTJobsDataPlane
73
+ KMS
74
+ Kinesis
75
+ KinesisAnalytics
76
+ KinesisVideo
77
+ KinesisVideoArchiveMedia
78
+ KinesisVideoMedia
79
+ Lambda
80
+ LambdaPreview
81
+ Lex
82
+ LexModelBuildingService
83
+ LexRuntimeService
84
+ Lightsail
85
+ MQ
86
+ MTurk
87
+ MachineLearning
88
+ MarketplaceCommerceAnalytics
89
+ MarketplaceEntitlementService
90
+ MarketplaceMetering
91
+ MediaConvert
92
+ MediaLive
93
+ MediaPackage
94
+ MediaStore
95
+ MediaStoreData
96
+ MigrationHub
97
+ Mobile
98
+ OpsWorks
99
+ OpsWorksCM
100
+ Organizations
101
+ Pinpoint
102
+ Polly
103
+ Pricing
104
+ RDS
105
+ Redshift
106
+ Rekognition
107
+ ResourceGroups
108
+ ResourceGroupsTaggingAPI
109
+ Route53
110
+ Route53Domains
111
+ S3
112
+ SES
113
+ SFN
114
+ SMS
115
+ SNS
116
+ SQS
117
+ SSM
118
+ STS
119
+ SWF
120
+ SageMaker
121
+ SageMakerRuntime
122
+ ServerlessApplicationRepository
123
+ ServiceCatalog
124
+ ServiceDiscovery
125
+ Shield
126
+ SimpleDB
127
+ Snowball
128
+ States
129
+ StorageGateway
130
+ Support
131
+ Translate
132
+ WAF
133
+ WAFRegional
134
+ WorkDocs
135
+ WorkSpaces
136
+ XRay
137
+ ]
138
+
139
+ def self.whitelist
140
+ @whitelist
141
+ end
142
+
143
+ def self.whitelist=(v)
144
+ @whitelist = v
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,19 @@
1
+ require 'logger'
2
+
3
+ module XRay
4
+ # Provide global logger for classes that include this module.
5
+ # It serves as a proxy to global recorder's logger.
6
+ module Logging
7
+ def logger
8
+ Logging.logger
9
+ end
10
+
11
+ def self.logger
12
+ @logger ||= Logger.new($stdout).tap { |l| l.level = Logger::INFO }
13
+ end
14
+
15
+ def self.logger=(v)
16
+ @logger = v
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,97 @@
1
+ require 'aws-xray-sdk/logger'
2
+ require 'aws-xray-sdk/exceptions'
3
+
4
+ module XRay
5
+ # Annotations are simple key-value pairs that are indexed for use with filter expressions.
6
+ # Use annotations to record data that you want to use to group traces in the console,
7
+ # or when calling the GetTraceSummaries API.
8
+ class Annotations
9
+ include Logging
10
+
11
+ def initialize(entity)
12
+ @entity = entity
13
+ @data = {}
14
+ end
15
+
16
+ def [](key)
17
+ @data[key]
18
+ end
19
+
20
+ # @param [Symbol] k Only characters in `A-Za-z0-9_` are supported.
21
+ # @param v Only `Numeric`, `String` true/false is supported.
22
+ def []=(k, v)
23
+ raise EntityClosedError if @entity.closed?
24
+ if key_supported?(k) && value_supported?(v)
25
+ @data[k] = v
26
+ else
27
+ logger.warn %(Dropping annotation with key #{k} due to invalid characters.)
28
+ end
29
+ end
30
+
31
+ # @param [Hash] h Update annotations with a single input hash.
32
+ def update(h)
33
+ raise EntityClosedError if @entity.closed?
34
+ filtered = filter_annotations(h)
35
+ @data.merge!(filtered)
36
+ end
37
+
38
+ def to_h
39
+ sanitize_values(@data)
40
+ end
41
+
42
+ private
43
+
44
+ def filter_annotations(h)
45
+ h.delete_if do |k, v|
46
+ drop = !key_supported?(k) || !value_supported?(v)
47
+ logger.warn %(Dropping annotation with key #{k} due to invalid characters.) if drop
48
+ drop
49
+ end
50
+ end
51
+
52
+ def sanitize_values(h)
53
+ h.each_pair do |k, v|
54
+ if v.is_a?(Float)
55
+ h[k] = v.to_s if v.nan? || v.infinite? == 1 || v.infinite? == -1
56
+ end
57
+ end
58
+ end
59
+
60
+ def key_supported?(k)
61
+ k.match(/[A-Za-z0-9_]+/)
62
+ end
63
+
64
+ def value_supported?(v)
65
+ case v
66
+ when Numeric
67
+ true
68
+ when true, false
69
+ true
70
+ else
71
+ v.is_a?(String)
72
+ end
73
+ end
74
+ end
75
+
76
+ # Singleton facade annotations class doing no-op for performance
77
+ # in case of not sampled X-Ray entities.
78
+ module FacadeAnnotations
79
+ class << self
80
+ def [](key)
81
+ # no-op
82
+ end
83
+
84
+ def []=(k, v)
85
+ # no-op
86
+ end
87
+
88
+ def update(h)
89
+ # no-op
90
+ end
91
+
92
+ def to_h
93
+ # no-op
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,70 @@
1
+ require 'oj'
2
+
3
+ module XRay
4
+ # Represents cause section in segment and subsegment document.
5
+ # It records information about application runtime exceptions.
6
+ class Cause
7
+ attr_reader :id
8
+ @@depth = 15
9
+
10
+ def initialize(exception: nil, id: nil, remote: false)
11
+ if exception
12
+ @exception_h = normalize e: exception, remote: remote
13
+ end
14
+ @id = id
15
+ end
16
+
17
+ def to_h
18
+ return id if id
19
+ h = {
20
+ working_directory: Dir.pwd,
21
+ paths: Gem.paths.path,
22
+ exceptions: @exception_h
23
+ }
24
+ h
25
+ end
26
+
27
+ def to_json
28
+ @to_json ||= begin
29
+ Oj.dump to_h, mode: :compat, use_as_json: true
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def normalize(e:, remote: false)
36
+ exceptions = []
37
+ exceptions << normalize_exception(e: e, remote: remote)
38
+
39
+ # don't propagate remote flag
40
+ while e.cause
41
+ exceptions << normalize_exception(e: e.cause)
42
+ e = e.cause
43
+ end
44
+
45
+ exceptions
46
+ end
47
+
48
+ def normalize_exception(e:, remote: false)
49
+ h = {
50
+ message: e.to_s,
51
+ type: e.class.to_s
52
+ }
53
+ h[:remote] = true if remote
54
+
55
+ backtrace = e.backtrace_locations
56
+ return h unless backtrace
57
+ h[:stack] = backtrace.first(@@depth).collect do |t|
58
+ {
59
+ path: t.path,
60
+ line: t.lineno,
61
+ label: t.label
62
+ }
63
+ end
64
+
65
+ truncated = backtrace.size - @@depth
66
+ h[:truncated] = truncated if truncated > 0
67
+ h
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,72 @@
1
+ require 'aws-xray-sdk/model/segment'
2
+ require 'aws-xray-sdk/model/subsegment'
3
+ require 'aws-xray-sdk/model/annotations'
4
+ require 'aws-xray-sdk/model/metadata'
5
+
6
+ module XRay
7
+ # defines common no-op methods for dummy segments/subsegments
8
+ module DummyEntity
9
+ def sampled
10
+ false
11
+ end
12
+
13
+ def annotations
14
+ FacadeAnnotations
15
+ end
16
+
17
+ def metadata(namespace: :default)
18
+ FacadeMetadata
19
+ end
20
+
21
+ def apply_status_code(status:)
22
+ # no-op
23
+ end
24
+
25
+ def merge_http_request(request:)
26
+ # no-op
27
+ end
28
+
29
+ def merge_http_response(response:)
30
+ # no-op
31
+ end
32
+
33
+ def add_exception(exception:, remote: false)
34
+ # no-op
35
+ end
36
+
37
+ def aws=(v)
38
+ # no-op
39
+ end
40
+
41
+ def to_h
42
+ # no-op
43
+ end
44
+
45
+ def to_json
46
+ # no-op
47
+ end
48
+ end
49
+
50
+ # A dummy segment is created when ``xray_recorder`` decides to not sample
51
+ # the segment based on sampling decisions.
52
+ # Adding data to a dummy segment becomes a no-op except for
53
+ # subsegments. This is to reduce the memory footprint of the SDK.
54
+ # A dummy segment will not be sent to the X-Ray daemon by the default emitter.
55
+ # Manually create dummy segments is not recommended.
56
+ class DummySegment < Segment
57
+ include DummyEntity
58
+ end
59
+
60
+ # A dummy subsegment will be created when ``xray_recorder`` tries
61
+ # to create a subsegment under a not sampled segment. Adding data
62
+ # to a dummy subsegment becomes no-op except for child subsegments.
63
+ # Dummy subsegment will not be sent to the X-Ray daemon by the default emitter.
64
+ # Manually create dummy subsegments is not recommended.
65
+ class DummySubsegment < Subsegment
66
+ include DummyEntity
67
+
68
+ def sql=(v)
69
+ # no-op
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,187 @@
1
+ require 'securerandom'
2
+ require 'bigdecimal'
3
+ require 'oj'
4
+ require 'aws-xray-sdk/exceptions'
5
+ require 'aws-xray-sdk/model/cause'
6
+ require 'aws-xray-sdk/model/annotations'
7
+ require 'aws-xray-sdk/model/metadata'
8
+
9
+ module XRay
10
+ # This module contains common properties and methods
11
+ # used by segment and subsegment class.
12
+ module Entity
13
+ attr_reader :name, :exception, :cause, :namespace,
14
+ :http_request, :http_response
15
+ attr_accessor :parent, :throttle, :error, :fault, :sampled, :aws,
16
+ :start_time, :end_time
17
+
18
+ HTTP_REQUEST_KEY = %I[url method user_agent client_ip x_forwarded_for].freeze
19
+ HTTP_RESPONSE_KEY = %I[status content_length].freeze
20
+
21
+ # Generates a random 8-digit hex number as the entity id and returns it.
22
+ def id
23
+ @id ||= begin
24
+ SecureRandom.hex(8)
25
+ end
26
+ end
27
+
28
+ def closed?
29
+ @closed ||= false
30
+ end
31
+
32
+ # @param [Float] end_time End time on epoch.
33
+ def close(end_time: nil)
34
+ raise EntityClosedError if closed?
35
+ @end_time = end_time || Time.now.to_f
36
+ @closed = true
37
+ end
38
+
39
+ # @return [Array] The children subsegments of this entity.
40
+ def subsegments
41
+ @subsegments ||= []
42
+ end
43
+
44
+ # @param [Subsegment] subsegment Append the provided subsegment to children subsegments.
45
+ def add_subsegment(subsegment:)
46
+ raise EntityClosedError if closed?
47
+ subsegment.sampled = sampled
48
+ subsegment.parent = self
49
+ subsegments << subsegment
50
+ nil
51
+ end
52
+
53
+ # @param [Subsegment] subsegment Remove the provided subsegment from children subsegments.
54
+ # @return [Subsegment] The deleted subsegment if the deletion is successful.
55
+ def remove_subsegment(subsegment:)
56
+ subsegments.delete(subsegment)
57
+ subsegment
58
+ end
59
+
60
+ def annotations
61
+ @annotations ||= Annotations.new(self)
62
+ end
63
+
64
+ def metadata(namespace: :default)
65
+ @metadata ||= Metadata.new(self)
66
+ @metadata.sub_meta(namespace)
67
+ end
68
+
69
+ # Set error/fault/throttle flags based on http status code.
70
+ # This method is idempotent.
71
+ # @param [Integer] status
72
+ def apply_status_code(status:)
73
+ raise EntityClosedError if closed?
74
+ case status.to_i
75
+ when 429
76
+ @throttle = true
77
+ @error = true
78
+ @fault = false
79
+ when 400..499
80
+ @error = true
81
+ @throttle = false
82
+ @fault = false
83
+ when 500..599
84
+ @fault = true
85
+ @error = false
86
+ @throttle = false
87
+ end
88
+
89
+ @http_response ||= {}
90
+ @http_response[:status] = status.to_i
91
+ end
92
+
93
+ # @param [Hash] request Supported keys are `:url`, `:user_agent`, `:client_ip`,
94
+ # `:x_forwarded_for`, `:method`. Value can be one of
95
+ # String or Integer or Boolean types depend on the key.
96
+ def merge_http_request(request:)
97
+ raise EntityClosedError if closed?
98
+ request.delete_if { |k| !HTTP_REQUEST_KEY.include?(k) }
99
+ @http_request ||= {}
100
+ @http_request.merge!(request)
101
+ end
102
+
103
+ # @param [Hash] response Supported keys are `:status`, `:content_length`.
104
+ # Value can be one of String or Integer types depend on the key.
105
+ def merge_http_response(response:)
106
+ raise EntityClosedError if closed?
107
+ response.delete_if { |k| !HTTP_RESPONSE_KEY.include?(k) }
108
+ @http_response ||= {}
109
+ @http_response.merge!(response)
110
+ apply_status_code status: response[:status] if response.key?(:status)
111
+ end
112
+
113
+ # @param [Exception] exception The exception object to capture.
114
+ # @param remote A boolean flag indicates whether the exception is
115
+ # returned from the downstream service.
116
+ def add_exception(exception:, remote: false)
117
+ raise EntityClosedError if closed?
118
+ @fault = true
119
+ @exception = exception
120
+ if cause_id = find_root_cause(exception)
121
+ @cause = Cause.new id: cause_id
122
+ else
123
+ @cause = Cause.new exception: exception, remote: remote
124
+ end
125
+ end
126
+
127
+ # @return [String] Cause id is the id of the subsegment where
128
+ # the exception originally comes from.
129
+ def cause_id
130
+ return @cause.id if @cause
131
+ end
132
+
133
+ # @return [Hash] The hash that contains all attributes that will
134
+ # be later serialized and sent out.
135
+ def to_h
136
+ h = {
137
+ name: name,
138
+ id: id,
139
+ start_time: start_time
140
+ }
141
+ if closed?
142
+ h[:end_time] = end_time
143
+ else
144
+ h[:in_progress] = true
145
+ end
146
+ h[:subsegments] = subsegments unless subsegments.empty?
147
+ h[:aws] = aws if aws
148
+ if http_request || http_response
149
+ h[:http] = {}
150
+ h[:http][:request] = http_request if http_request
151
+ h[:http][:response] = http_response if http_response
152
+ end
153
+ if (a = annotations.to_h) && !a.empty?
154
+ h[:annotations] = a
155
+ end
156
+ if (m = @metadata) && !m.to_h.empty?
157
+ h[:metadata] = m.to_h
158
+ end
159
+
160
+ h[:parent_id] = parent.id if parent
161
+ # make sure the value in hash can only be boolean true
162
+ h[:fault] = !!fault if fault
163
+ h[:error] = !!error if error
164
+ h[:throttle] = !!throttle if throttle
165
+ h[:cause] = cause.to_h if cause
166
+ h
167
+ end
168
+
169
+ def to_json
170
+ @to_json ||= begin
171
+ Oj.dump to_h, mode: :compat, use_as_json: true
172
+ end
173
+ end
174
+
175
+ private
176
+
177
+ def find_root_cause(e)
178
+ subsegment = subsegments.find { |i| i.exception.hash == e.hash }
179
+ return nil unless subsegment
180
+ if cause_id = subsegment.cause_id
181
+ cause_id
182
+ else
183
+ subsegment.id
184
+ end
185
+ end
186
+ end
187
+ end