google-cloud-trace 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ # Copyright 2014 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/credentials"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Trace
21
+ ##
22
+ # @private Represents the OAuth 2.0 signing logic for Trace.
23
+ class Credentials < Google::Cloud::Credentials
24
+ SCOPE = ["https://www.googleapis.com/auth/cloud-platform"]
25
+ PATH_ENV_VARS = %w(TRACE_KEYFILE GOOGLE_CLOUD_KEYFILE GCLOUD_KEYFILE)
26
+ JSON_ENV_VARS = %w(TRACE_KEYFILE_JSON GOOGLE_CLOUD_KEYFILE_JSON
27
+ GCLOUD_KEYFILE_JSON)
28
+
29
+ ##
30
+ # @private Create credentials with given scope and/or keyfile
31
+ def self.credentials_with_scope keyfile, scope = nil
32
+ if keyfile.nil?
33
+ default(scope: scope)
34
+ else
35
+ new(keyfile, scope: scope)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,130 @@
1
+ # Copyright 2014 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "json"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Trace
21
+ ##
22
+ # A collection of well-known label keys for trace spans.
23
+ #
24
+ module LabelKey
25
+ AGENT = "/agent"
26
+ COMPONENT = "/component"
27
+ ERROR_MESSAGE = "/error/message"
28
+ ERROR_NAME = "/error/name"
29
+ HTTP_CLIENT_CITY = "/http/client_city"
30
+ HTTP_CLIENT_COUNTRY = "/http/client_country"
31
+ HTTP_CLIENT_PROTOCOL = "/http/client_protocol"
32
+ HTTP_CLIENT_REGION = "/http/client_region"
33
+ HTTP_HOST = "/http/host"
34
+ HTTP_METHOD = "/http/method"
35
+ HTTP_REDIRECTED_URL = "/http/redirected_url"
36
+ HTTP_REQUEST_SIZE = "/http/request/size"
37
+ HTTP_RESPONSE_SIZE = "/http/response/size"
38
+ HTTP_STATUS_CODE = "/http/status_code"
39
+ HTTP_URL = "/http/url"
40
+ HTTP_USER_AGENT = "/http/user_agent"
41
+ PID = "/pid"
42
+ STACKTRACE = "/stacktrace"
43
+ TID = "/tid"
44
+
45
+ GAE_APPLICATION_ERROR = "g.co/gae/application_error"
46
+ GAE_APP_MODULE = "g.co/gae/app/module"
47
+ GAE_APP_MODULE_VERSION = "g.co/gae/app/module_version"
48
+ GAE_APP_VERSION = "g.co/gae/app/version"
49
+ GAE_DATASTORE_COUNT = "g.co/gae/datastore/count"
50
+ GAE_DATASTORE_CURSOR = "g.co/gae/datastore/cursor"
51
+ GAE_DATASTORE_ENTITY_WRITES = "g.co/gae/datastore/entity_writes"
52
+ GAE_DATASTORE_HAS_ANCESTOR = "g.co/gae/datastore/has_ancestor"
53
+ GAE_DATASTORE_HAS_CURSOR = "g.co/gae/datastore/has_cursor"
54
+ GAE_DATASTORE_HAS_TRANSACTION = "g.co/gae/datastore/has_transaction"
55
+ GAE_DATASTORE_INDEX_WRITES = "g.co/gae/datastore/index_writes"
56
+ GAE_DATASTORE_KIND = "g.co/gae/datastore/kind"
57
+ GAE_DATASTORE_LIMIT = "g.co/gae/datastore/limit"
58
+ GAE_DATASTORE_MORE_RESULTS = "g.co/gae/datastore/more_results"
59
+ GAE_DATASTORE_OFFSET = "g.co/gae/datastore/offset"
60
+ GAE_DATASTORE_REQUESTED_ENTITY_DELETES =
61
+ "g.co/gae/datastore/requested_entity_deletes"
62
+ GAE_DATASTORE_REQUESTED_ENTITY_PUTS =
63
+ "g.co/gae/datastore/requested_entity_puts"
64
+ GAE_DATASTORE_SIZE = "g.co/gae/datastore/size"
65
+ GAE_DATASTORE_SKIPPED = "g.co/gae/datastore/skipped"
66
+ GAE_DATASTORE_TRANSACTION_HANDLE =
67
+ "g.co/gae/datastore/transaction_handle"
68
+ GAE_ERROR_MESSAGE = "g.co/gae/error_message"
69
+ GAE_MEMCACHE_COUNT = "g.co/gae/memcache/count"
70
+ GAE_MEMCACHE_SIZE = "g.co/gae/memcache/size"
71
+ GAE_REQUEST_LOG_ID = "g.co/gae/request_log_id"
72
+
73
+ ##
74
+ # Set the stack trace label in the given labels hash. The current call
75
+ # stack is formatted so the Stackdriver UI will display it.
76
+ #
77
+ # @param [Hash] labels The labels hash in which to set the stack trace
78
+ # label value.
79
+ # @param [Array<Thread::Backtrace::Location>] stack_frames The current
80
+ # caller stack as returned from `::Kernel.caller_locations`. If
81
+ # not set, `::Kernel.caller_locations` is called internally.
82
+ # @param [Integer] skip_frames Passed to the internal invocation of
83
+ # `::Kernel.caller_locations` if one is needed.
84
+ # @param [Proc] truncate_stack A procedure that allows skipping of
85
+ # the "topmost" stack frames. Stack frames, represented by
86
+ # instances of `Thread::Backtrace::Location`, are passed to this
87
+ # proc beginning with the topmost frame. As long as the proc
88
+ # returns a falsy value, those frames are dropped. Once the proc
89
+ # returns true for the first time, that frame and all remaining
90
+ # frames (possibly subject to `filter_stack`) are used. If not set,
91
+ # no frames are skipped.
92
+ # @param [Proc] filter_stack A procedure that allows skipping of
93
+ # stack frames in the middle of the stack trace. After possibly
94
+ # skipping frames using `truncate_stack`, all remaining frames are
95
+ # passed to this proc as `Thread::Backtrace::Location` objects.
96
+ # Those for whom the proc returns a falsy value are skipped. If
97
+ # this parameter is not set, no filtering is done and all frames
98
+ # are presented in the stack trace.
99
+ #
100
+ # @example
101
+ # require "google/cloud/trace"
102
+ #
103
+ # trace = Google::Cloud::Trace.new
104
+ # span = trace.create_span "root_span"
105
+ # Google::Cloud::Trace::LabelKey.set_stack_trace span.labels
106
+ #
107
+ def self.set_stack_trace labels,
108
+ stack_frames: nil, skip_frames: 1,
109
+ truncate_stack: nil, filter_stack: nil
110
+ stack_frames ||= ::Kernel.caller_locations skip_frames
111
+ json_frames = []
112
+ collecting_frames = !truncate_stack
113
+ stack_frames.each do |frame|
114
+ collecting_frames ||= truncate_stack.call(frame)
115
+ next unless collecting_frames
116
+ next unless !filter_stack || filter_stack.call(frame)
117
+ json_frames <<
118
+ {
119
+ file_name: frame.absolute_path,
120
+ line_number: frame.lineno,
121
+ method_name: frame.label
122
+ }
123
+ end
124
+ json_object = { stack_frame: json_frames }
125
+ labels[STACKTRACE] = JSON.generate json_object
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,358 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/core/environment"
17
+ require "stackdriver/core/trace_context"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Trace
22
+ ##
23
+ # # Trace Middleware
24
+ #
25
+ # A Rack middleware that manages trace context and captures a trace of
26
+ # the request. Specifically, it:
27
+ #
28
+ # * Reads the trace context from the request headers, if present.
29
+ # Otherwise, generates a new trace context.
30
+ # * Makes a sampling decision if one is not already specified.
31
+ # * Records a span measuring the entire handling of the request,
32
+ # annotated with a set of standard request data.
33
+ # * Makes the trace context available so downstream middlewares and the
34
+ # app can add further spans to the trace.
35
+ # * Sends the completed trace to the Stackdriver service.
36
+ #
37
+ # ## Installing
38
+ #
39
+ # To use this middleware, simply install it in your middleware stack.
40
+ # Here is an example Sinatra application that includes the Trace
41
+ # middleware:
42
+ #
43
+ # ```ruby
44
+ # # Simple sinatra application
45
+ #
46
+ # require "sinatra"
47
+ # require "google/cloud/trace"
48
+ #
49
+ # use Google::Cloud::Trace::Middleware
50
+ #
51
+ # get "/" do
52
+ # "Hello World!"
53
+ # end
54
+ # ```
55
+ #
56
+ # Here is an example `config.ru` file for a web application that uses
57
+ # the standard Rack configuration mechanism.
58
+ #
59
+ # ```ruby
60
+ # # config.ru for simple Rack application
61
+ #
62
+ # require "google/cloud/trace"
63
+ # use Google::Cloud::Trace::Middleware
64
+ #
65
+ # run MyApp
66
+ # ```
67
+ #
68
+ # If your application uses Ruby On Rails, you may also use the provided
69
+ # {Google::Cloud::Trace::Railtie} for close integration with Rails and
70
+ # ActiveRecord.
71
+ #
72
+ # ## Custom measurements
73
+ #
74
+ # By default, this middleware creates traces that measure just the http
75
+ # request handling as a whole. If you want to provide more detailed
76
+ # measurements of smaller processes, use the classes provided in this
77
+ # library. Below is a Sinatra example to get you started.
78
+ #
79
+ # ```ruby
80
+ # # Simple sinatra application
81
+ #
82
+ # require "sinatra"
83
+ # require "google/cloud/trace"
84
+ #
85
+ # use Google::Cloud::Trace::Middleware
86
+ #
87
+ # get "/" do
88
+ # Google::Cloud::Trace.in_span "Sleeping on the job!" do
89
+ # sleep rand
90
+ # end
91
+ # "Hello World!"
92
+ # end
93
+ # ```
94
+ #
95
+ # ## Sampling and blacklisting
96
+ #
97
+ # A sampler makes the decision whether to record a trace for each
98
+ # request (if the decision was not made by the context, e.g. by providing
99
+ # a request header). By default, this sampler is the default
100
+ # {Google::Cloud::Trace::TimeSampler}, which enforces a maximum QPS per
101
+ # process, and blacklists a small number of request paths such as
102
+ # health checks sent by Google App Engine. You may adjust this behavior
103
+ # by providing an alternate sampler. See
104
+ # {Google::Cloud::Trace::TimeSampler}.
105
+ #
106
+ class Middleware
107
+ ##
108
+ # The name of this trace agent as reported to the Stackdriver backend.
109
+ AGENT_NAME = "ruby #{Google::Cloud::Trace::VERSION}".freeze
110
+
111
+ ##
112
+ # Create a new Middleware for traces
113
+ #
114
+ # @param [Rack Application] app Rack application
115
+ # @param [Google::Cloud::Trace::Service] service The service object.
116
+ # Optional if running on GCE.
117
+ # @param [Boolean] capture_stack Whether to capture stack traces for
118
+ # each span. Default is false.
119
+ # @param [Proc] sampler A sampler to use, or `nil` to use the default.
120
+ # See {Google::Cloud::Trace::TimeSampler}. Note that the sampler
121
+ # may be any Proc that implements the sampling contract.
122
+ #
123
+ def initialize app,
124
+ service: nil,
125
+ capture_stack: false,
126
+ sampler: nil,
127
+ span_id_generator: nil
128
+ @app = app
129
+ @capture_stack = capture_stack
130
+ @sampler = sampler
131
+ @span_id_generator = span_id_generator
132
+ if service
133
+ @service = service
134
+ else
135
+ project_id = Google::Cloud::Trace::Project.default_project
136
+ @service = Google::Cloud::Trace.new.service if project_id
137
+ end
138
+ end
139
+
140
+ ##
141
+ # Implementation of the trace middleware. Creates a trace for this
142
+ # request, populates it with a root span for the entire request, and
143
+ # ensures it is reported back to Stackdriver.
144
+ #
145
+ # @param [Hash] env Rack environment hash
146
+ # @return [Rack::Response] The response from downstream Rack app
147
+ #
148
+ def call env
149
+ trace = create_trace env
150
+ begin
151
+ Google::Cloud::Trace.set trace
152
+ Google::Cloud::Trace.in_span "rack-request" do |span|
153
+ configure_span span, env
154
+ result = @app.call env
155
+ configure_result span, result
156
+ result
157
+ end
158
+ ensure
159
+ Google::Cloud::Trace.set nil
160
+ send_trace trace, env
161
+ end
162
+ end
163
+
164
+ ##
165
+ # Gets the current trace context from the given Rack environment.
166
+ # Makes a sampling decision if one has not been made already.
167
+ #
168
+ # @private
169
+ # @param [Hash] env Rack environment hash
170
+ # @return [Stackdriver::Core::TraceContext] The trace context.
171
+ #
172
+ def get_trace_context env
173
+ Stackdriver::Core::TraceContext.parse_rack_env(env) do |tc|
174
+ if tc.sampled?.nil?
175
+ sampler = @sampler || Google::Cloud::Trace::TimeSampler.default
176
+ sampled = sampler.call env
177
+ tc = Stackdriver::Core::TraceContext.new \
178
+ trace_id: tc.trace_id,
179
+ span_id: tc.span_id,
180
+ sampled: sampled,
181
+ capture_stack: sampled && @capture_stack
182
+ end
183
+ tc
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Create a new trace for this request.
189
+ #
190
+ # @private
191
+ # @param [Hash] env The Rack environment.
192
+ #
193
+ def create_trace env
194
+ trace_context = get_trace_context env
195
+ Google::Cloud::Trace::TraceRecord.new \
196
+ @service.project,
197
+ trace_context,
198
+ span_id_generator: @span_id_generator
199
+ end
200
+
201
+ ##
202
+ # Send the given trace to the trace service, if requested.
203
+ #
204
+ # @private
205
+ # @param [Google::Cloud::Trace::TraceRecord] trace The trace to send.
206
+ # @param [Hash] env The Rack environment.
207
+ #
208
+ def send_trace trace, env
209
+ if @service && trace.trace_context.sampled?
210
+ begin
211
+ @service.patch_traces trace
212
+ rescue => ex
213
+ msg = "Transmit to Stackdriver Trace failed: #{ex.inspect}"
214
+ logger = env["rack.logger"]
215
+ if logger
216
+ logger.error msg
217
+ else
218
+ warn msg
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ ##
225
+ # Gets the URI path from the given Rack environment.
226
+ #
227
+ # @private
228
+ # @param [Hash] env Rack environment hash
229
+ # @return [String] The URI path.
230
+ #
231
+ def get_path env
232
+ path = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
233
+ path = "/#{path}" unless path.start_with? "/"
234
+ path
235
+ end
236
+
237
+ ##
238
+ # Gets the URI hostname from the given Rack environment.
239
+ #
240
+ # @private
241
+ # @param [Hash] env Rack environment hash
242
+ # @return [String] The hostname.
243
+ #
244
+ def get_host env
245
+ env["HTTP_HOST"] || env["SERVER_NAME"]
246
+ end
247
+
248
+ ##
249
+ # Gets the full URL from the given Rack environment.
250
+ #
251
+ # @private
252
+ # @param [Hash] env Rack environment hash
253
+ # @return [String] The URL.
254
+ #
255
+ def get_url env
256
+ path = get_path env
257
+ host = get_host env
258
+ scheme = env["rack.url_scheme"]
259
+ query_string = env["QUERY_STRING"].to_s
260
+ url = "#{scheme}://#{host}#{path}"
261
+ url = "#{url}?#{query_string}" unless query_string.empty?
262
+ url
263
+ end
264
+
265
+ ##
266
+ # Configures the root span for this request. This may be called
267
+ # before the request is actually handled because it doesn't depend
268
+ # on the result.
269
+ #
270
+ # @private
271
+ # @param [Google::Cloud::Trace::TraceSpan] span The root span to
272
+ # configure.
273
+ # @param [Hash] env Rack environment hash
274
+ #
275
+ def configure_span span, env
276
+ span.name = get_path env
277
+ set_basic_labels span.labels, env
278
+ set_extended_labels span.labels,
279
+ span.trace.trace_context.capture_stack?
280
+ span
281
+ end
282
+
283
+ ##
284
+ # Configures standard labels.
285
+ # @private
286
+ #
287
+ def set_basic_labels labels, env
288
+ set_label labels, Google::Cloud::Trace::LabelKey::AGENT, AGENT_NAME
289
+ set_label labels, Google::Cloud::Trace::LabelKey::HTTP_HOST,
290
+ get_host(env)
291
+ set_label labels, Google::Cloud::Trace::LabelKey::HTTP_METHOD,
292
+ env["REQUEST_METHOD"]
293
+ set_label labels,
294
+ Google::Cloud::Trace::LabelKey::HTTP_CLIENT_PROTOCOL,
295
+ env["SERVER_PROTOCOL"]
296
+ set_label labels, Google::Cloud::Trace::LabelKey::HTTP_USER_AGENT,
297
+ env["HTTP_USER_AGENT"]
298
+ set_label labels, Google::Cloud::Trace::LabelKey::HTTP_URL,
299
+ get_url(env)
300
+ set_label labels, Google::Cloud::Trace::LabelKey::PID,
301
+ ::Process.pid.to_s
302
+ set_label labels, Google::Cloud::Trace::LabelKey::TID,
303
+ ::Thread.current.object_id.to_s
304
+ end
305
+
306
+ ##
307
+ # Configures stack and gae labels.
308
+ # @private
309
+ #
310
+ def set_extended_labels labels, capture_stack
311
+ if capture_stack
312
+ Google::Cloud::Trace::LabelKey.set_stack_trace labels,
313
+ skip_frames: 3
314
+ end
315
+ if Google::Cloud::Core::Environment.gae?
316
+ set_label labels, Google::Cloud::Trace::LabelKey::GAE_APP_MODULE,
317
+ Google::Cloud::Core::Environment.gae_module_id
318
+ set_label labels,
319
+ Google::Cloud::Trace::LabelKey::GAE_APP_MODULE_VERSION,
320
+ Google::Cloud::Core::Environment.gae_module_version
321
+ end
322
+ end
323
+
324
+ ##
325
+ # Sets the given label if the given value is a proper string.
326
+ #
327
+ # @private
328
+ # @param [Hash] labels The labels hash.
329
+ # @param [String] key The key of the label to set.
330
+ # @param [Object] value The value to set.
331
+ #
332
+ def set_label labels, key, value
333
+ labels[key] = value if value.is_a? ::String
334
+ end
335
+
336
+ ##
337
+ # Performs post-request tasks, including adding result-dependent
338
+ # labels to the root span, and adding trace context headers to the
339
+ # HTTP response.
340
+ #
341
+ # @private
342
+ # @param [Google::Cloud::Trace::TraceSpan] span The root span to
343
+ # configure.
344
+ # @param [Array] result The Rack response.
345
+ #
346
+ def configure_result span, result
347
+ if result.is_a?(::Array) && result.size == 3
348
+ span.labels[Google::Cloud::Trace::LabelKey::HTTP_STATUS_CODE] =
349
+ result[0].to_s
350
+ result[1]["X-Cloud-Trace-Context"] =
351
+ span.trace.trace_context.to_string
352
+ end
353
+ result
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end