google-cloud-trace 0.21.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.
@@ -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