aws-xray-sdk 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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