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,115 @@
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
+ module Google
17
+ module Cloud
18
+ module Trace
19
+ ##
20
+ # Utility methods for configuring ActiveSupport notifications to generate
21
+ # spans in the current trace.
22
+ #
23
+ module Notifications
24
+ ##
25
+ # The default max length for label data.
26
+ DEFAULT_MAX_DATA_LENGTH = 1024
27
+
28
+ ##
29
+ # The default prefix for label keys
30
+ DEFAULT_LABEL_NAMESPACE = "/ruby/"
31
+
32
+ ##
33
+ # Stack truncation method that removes the ActiveSupport::Notifications
34
+ # calls from the top.
35
+ REMOVE_NOTIFICATION_FRAMEWORK =
36
+ lambda do |frame|
37
+ frame.absolute_path !~ %r{/lib/active_support/notifications}
38
+ end
39
+
40
+ ##
41
+ # Subscribes to the given event type or any type matching the given
42
+ # pattern. When an event is raised, a span is generated in the current
43
+ # thread's trace. The event payload is exposed as labels on the span.
44
+ # If there is no active trace for the current thread, then no span is
45
+ # generated.
46
+ #
47
+ # @param [String, Regex] type A specific type or pattern to select
48
+ # notifications to listen for.
49
+ # @param [Integer] max_length The maximum length for label values.
50
+ # If a label value exceeds this length, it is truncated.
51
+ # If the length is nil, no truncation takes place.
52
+ # @param [String] label_namespace A string to prepend to all label
53
+ # keys.
54
+ # @param [Boolean] capture_stack Whether traces should include the
55
+ # call stack.
56
+ #
57
+ # @example
58
+ #
59
+ # require "google/cloud/trace"
60
+ # require "activerecord"
61
+ #
62
+ # Google::Cloud::Trace::Notifications.instrument "sql.activerecord"
63
+ #
64
+ # trace = Google::Cloud::Trace::Trace.new
65
+ # Google::Cloud::Trace.set trace
66
+ # ActiveRecord::Base.query "SHOW TABLES"
67
+ #
68
+ def self.instrument type,
69
+ max_length: DEFAULT_MAX_DATA_LENGTH,
70
+ label_namespace: DEFAULT_LABEL_NAMESPACE,
71
+ capture_stack: false
72
+ require "active_support/notifications"
73
+ ActiveSupport::Notifications.subscribe(type) do |*args|
74
+ event = ActiveSupport::Notifications::Event.new(*args)
75
+ handle_notification_event event, max_length, label_namespace,
76
+ capture_stack
77
+ end
78
+ end
79
+
80
+ ##
81
+ # @private
82
+ def self.handle_notification_event event, maxlen, label_namespace,
83
+ capture_stack
84
+ cur_span = Google::Cloud::Trace.get
85
+ if cur_span && event.time && event.end
86
+ labels = payload_to_labels event, maxlen, label_namespace
87
+ if capture_stack
88
+ Google::Cloud::Trace::LabelKey.set_stack_trace \
89
+ labels,
90
+ skip_frames: 2,
91
+ truncate_stack: REMOVE_NOTIFICATION_FRAMEWORK
92
+ end
93
+ cur_span.create_span event.name,
94
+ start_time: event.time,
95
+ end_time: event.end,
96
+ labels: labels
97
+ end
98
+ end
99
+
100
+ ##
101
+ # @private
102
+ def self.payload_to_labels event, maxlen, label_namespace
103
+ labels = {}
104
+ event.payload.each do |k, v|
105
+ if v.is_a? ::String
106
+ v = v[0, maxlen-3] + "..." if maxlen && v.size > maxlen
107
+ labels["#{label_namespace}#{k}"] = v
108
+ end
109
+ end
110
+ labels
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,219 @@
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/core/environment"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Trace
21
+ ##
22
+ # # Project
23
+ #
24
+ # Projects are top-level containers in Google Cloud Platform. They store
25
+ # information about billing and authorized users, and they control access
26
+ # to Stackdriver Trace resources. Each project has a friendly name and a
27
+ # unique ID. Projects can be created only in the [Google Developers
28
+ # Console](https://console.developers.google.com).
29
+ #
30
+ # This class is a client to make API calls for the project's trace data.
31
+ # Create an instance using {Google::Cloud::Trace.new} or
32
+ # {Google::Cloud#trace}. You may then use the `get_trace` method to
33
+ # retrieve a trace by ID, `list_traces` to query for a set of traces,
34
+ # and `patch_traces` to update trace data. You may also use `new_trace`
35
+ # as a convenience constructor to build a
36
+ # {Google::Cloud::Trace::TraceRecord} object.
37
+ #
38
+ # @example
39
+ # require "google/cloud/trace"
40
+ #
41
+ # trace_client = Google::Cloud::Trace.new
42
+ # traces = trace_client.list_traces Time.now - 3600, Time.now
43
+ #
44
+ class Project
45
+ ##
46
+ # @private The Service object.
47
+ attr_accessor :service
48
+
49
+ ##
50
+ # @private Creates a new Project instance.
51
+ def initialize service
52
+ @service = service
53
+ end
54
+
55
+ ##
56
+ # The ID of the current project.
57
+ #
58
+ # @return [String] the Google Cloud project ID
59
+ #
60
+ # @example
61
+ # require "google/cloud/trace"
62
+ #
63
+ # trace_client = Google::Cloud::Trace.new(
64
+ # project: "my-project",
65
+ # keyfile: "/path/to/keyfile.json"
66
+ # )
67
+ #
68
+ # trace_client.project #=> "my-project"
69
+ #
70
+ def project
71
+ service.project
72
+ end
73
+
74
+ ##
75
+ # Create a new empty trace record for this project. Uses the current
76
+ # thread's TraceContext by default; otherwise you may provide a
77
+ # specific TraceContext.
78
+ #
79
+ # @param [Stackdriver::Core::TraceContext, nil] trace_context The
80
+ # context within which to locate this trace (i.e. sets the trace ID
81
+ # and the context parent span, if present.) If the context is set
82
+ # explicitly to `nil`, a new trace with a new trace ID is created.
83
+ # If no context is provided, the current thread's context is used.
84
+ # @return [Google::Cloud::Trace::TraceRecord] The new trace.
85
+ #
86
+ # @example
87
+ # require "google/cloud/trace"
88
+ #
89
+ # trace_client = Google::Cloud::Trace.new(
90
+ # project: "my-project",
91
+ # keyfile: "/path/to/keyfile.json"
92
+ # )
93
+ #
94
+ # trace = trace_client.new_trace
95
+ #
96
+ def new_trace trace_context: :DEFAULT
97
+ if trace_context == :DEFAULT
98
+ trace_context = Stackdriver::Core::TraceContext.get
99
+ end
100
+ Google::Cloud::Trace::TraceRecord.new project, trace_context
101
+ end
102
+
103
+ ##
104
+ # Sends new traces to Stackdriver Trace or updates existing traces.
105
+ # If the ID of a trace that you send matches that of an existing trace,
106
+ # any fields in the existing trace and its spans are overwritten by the
107
+ # provided values, and any new fields provided are merged with the
108
+ # existing trace data. If the ID does not match, a new trace is created.
109
+ #
110
+ # @param [Google::Cloud::Trace::TraceRecord,
111
+ # Array{Google::Cloud::Trace::TraceRecord}] traces Either a single
112
+ # trace object or an array of trace objects.
113
+ # @return [Array{Google::Cloud::Trace::TraceRecord}] The traces written.
114
+ #
115
+ # @example
116
+ # require "google/cloud/trace"
117
+ #
118
+ # trace_client = Google::Cloud::Trace.new
119
+ #
120
+ # trace = trace_client.new_trace
121
+ # trace.in_span "root_span" do
122
+ # # Do stuff...
123
+ # end
124
+ #
125
+ # trace_client.patch_traces trace
126
+ #
127
+ def patch_traces traces
128
+ ensure_service!
129
+ service.patch_traces traces
130
+ end
131
+
132
+ ##
133
+ # Gets a single trace by its ID.
134
+ #
135
+ # @param [String] trace_id The ID of the trace to fetch.
136
+ # @return [Google::Cloud::Trace::TraceRecord, nil] The trace object, or
137
+ # `nil` if there is no accessible trace with the given ID.
138
+ #
139
+ # @example
140
+ # require "google/cloud/trace"
141
+ #
142
+ # trace_client = Google::Cloud::Trace.new
143
+ #
144
+ # trace = trace_client.get_trace "1234567890abcdef1234567890abcdef"
145
+ #
146
+ def get_trace trace_id
147
+ ensure_service!
148
+ service.get_trace trace_id
149
+ end
150
+
151
+ ##
152
+ # Returns of a list of traces that match the specified conditions.
153
+ # You must provide a time interval. You may optionally provide a
154
+ # filter, an ordering, a view type.
155
+ # Results are paginated, and you may specify a page size. The result
156
+ # will come with a token you can pass back to retrieve the next page.
157
+ #
158
+ # @param [Time] start_time The start of the time interval (inclusive).
159
+ # @param [Time] end_time The end of the time interval (inclusive).
160
+ # @param [String] filter An optional filter.
161
+ # @param [String] order_by The optional sort order for returned traces.
162
+ # May be `trace_id`, `name`, `duration`, or `start`. Any sort order
163
+ # may also be reversed by appending `desc`; for example use
164
+ # `start desc` to order traces from newest to oldest.
165
+ # @param [Symbol] view The optional type of view. Valid values are
166
+ # `:MINIMAL`, `:ROOTSPAN`, and `:COMPLETE`. Default is `:MINIMAL`.
167
+ # @param [Integer] page_size The size of each page to return. Optional;
168
+ # if omitted, the service will select a reasonable page size.
169
+ # @param [String] page_token A token indicating the page to return.
170
+ # Each page of results includes proper token for specifying the
171
+ # following page.
172
+ # @return [Google::Cloud::Trace::ResultSet] A page of results.
173
+ #
174
+ # @example
175
+ # require "google/cloud/trace"
176
+ #
177
+ # trace_client = Google::Cloud::Trace.new
178
+ #
179
+ # traces = trace_client.list_traces Time.now - 3600, Time.now
180
+ # traces.each do |trace|
181
+ # puts "Retrieved trace ID: #{trace.trace_id}"
182
+ # end
183
+ #
184
+ def list_traces start_time, end_time,
185
+ filter: nil,
186
+ order_by: nil,
187
+ view: nil,
188
+ page_size: nil,
189
+ page_token: nil
190
+ ensure_service!
191
+ service.list_traces project, start_time, end_time,
192
+ filter: filter,
193
+ order_by: order_by,
194
+ view: view,
195
+ page_size: page_size,
196
+ page_token: page_token
197
+ end
198
+
199
+ ##
200
+ # @private Default project.
201
+ def self.default_project
202
+ ENV["TRACE_PROJECT"] ||
203
+ ENV["GOOGLE_CLOUD_PROJECT"] ||
204
+ ENV["GCLOUD_PROJECT"] ||
205
+ Google::Cloud::Core::Environment.project_id
206
+ end
207
+
208
+ protected
209
+
210
+ ##
211
+ # @private Raise an error unless an active connection to the service is
212
+ # available.
213
+ def ensure_service!
214
+ fail "Must have active connection to service" unless service
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,231 @@
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
+ require "google/cloud/core/environment"
16
+ require "google/cloud/trace"
17
+
18
+
19
+ module Google
20
+ module Cloud
21
+ module Trace
22
+ ##
23
+ # # Rails integration for Stackdriver Trace
24
+ #
25
+ # This Railtie is a drop-in Stackdriver Trace instrumentation plugin
26
+ # for Ruby on Rails applications. If present, it automatically
27
+ # instruments your Rails app to record performance traces and cause them
28
+ # to appear on your Stackdriver console.
29
+ #
30
+ # ## Installation
31
+ #
32
+ # To install this plugin, the gem `google-cloud-trace` must be in your
33
+ # Gemfile. You also must add the following line to your `application.rb`
34
+ # file:
35
+ #
36
+ # ```ruby
37
+ # require "google/cloud/trace/rails"
38
+ # ```
39
+ #
40
+ # If you include the `stackdriver` gem in your Gemfile, the above is done
41
+ # for you automatically, and you do not need to edit your
42
+ # `application.rb`.
43
+ #
44
+ # ## Configuration
45
+ #
46
+ # The following Rails configuration options are recognized.
47
+ #
48
+ # ```ruby
49
+ # config.google_cloud.use_trace = true | false
50
+ # ```
51
+ #
52
+ # Normally, tracing is activated when `RAILS_ENV` is set to `production`
53
+ # and credentials are available. You can override this and enable tracing
54
+ # in other environments by setting `use_trace` explicitly.
55
+ #
56
+ # ```ruby
57
+ # config.google_cloud.keyfile = "path/to/file"
58
+ # ```
59
+ #
60
+ # If your application is running on Google Cloud Platform, it will
61
+ # automatically use credentials available to your project. However, if
62
+ # you are running an application locally or on a different hosting
63
+ # provider, you may provide a path to your credentials file using this
64
+ # configuration.
65
+ #
66
+ # ```ruby
67
+ # config.google_cloud.project_id = "my-project-id"
68
+ # ```
69
+ #
70
+ # If your application is running on Google Cloud Platform, it will
71
+ # automatically select the project under which it is running. However, if
72
+ # you are running an application locally or on a different hosting
73
+ # provider, or if you want to log traces to a different project than you
74
+ # are using to host your application, you may provide the project ID.
75
+ #
76
+ # ```ruby
77
+ # config.google_cloud.trace.notifications = ["event1", "event2"]
78
+ # ```
79
+ #
80
+ # By default, this Railtie subscribes to ActiveSupport notifications
81
+ # emitted by ActiveRecord queries, rendering, and emailing functions.
82
+ # See {DEFAULT_NOTIFICATIONS}. If you want to customize the list of
83
+ # notification types, edit the notifications configuration.
84
+ #
85
+ # ```ruby
86
+ # config.google_cloud.trace.max_data_length = 1024
87
+ # ```
88
+ #
89
+ # The maximum length of span properties recorded with ActiveSupport
90
+ # notification events. Any property value larger than this length is
91
+ # truncated.
92
+ #
93
+ # ```ruby
94
+ # config.google_cloud.trace.capture_stack = true | false
95
+ # ```
96
+ #
97
+ # Whether to capture the call stack with each trace span. Default is
98
+ # false.
99
+ #
100
+ # ## Measuring custom functionality
101
+ #
102
+ # To add a custom measurement to a request trace, use the classes
103
+ # provided in this library. Below is an example to get you started.
104
+ #
105
+ # ```ruby
106
+ # class MyController < ApplicationController
107
+ # def index
108
+ # Google::Cloud::Trace.in_span "Sleeping on the job!" do
109
+ # sleep rand
110
+ # end
111
+ # render plain: "Hello World!"
112
+ # end
113
+ # end
114
+ # ```
115
+ #
116
+ class Railtie < ::Rails::Railtie
117
+ ##
118
+ # The default list of ActiveSupport notification types to include in
119
+ # traces.
120
+ #
121
+ DEFAULT_NOTIFICATIONS = [
122
+ "sql.active_record",
123
+ "render_template.action_view",
124
+ "send_file.action_controller",
125
+ "send_data.action_controller",
126
+ "deliver.action_mailer"
127
+ ].freeze
128
+
129
+ unless config.respond_to? :google_cloud
130
+ config.google_cloud = ActiveSupport::OrderedOptions.new
131
+ end
132
+ config.google_cloud.trace = ActiveSupport::OrderedOptions.new
133
+ config.google_cloud.trace.notifications = DEFAULT_NOTIFICATIONS.dup
134
+ config.google_cloud.trace.max_data_length =
135
+ Google::Cloud::Trace::Notifications::DEFAULT_MAX_DATA_LENGTH
136
+
137
+ initializer "Google.Cloud.Trace" do |app|
138
+ if Google::Cloud::Trace::Railtie.use_trace? app.config
139
+ Google::Cloud::Trace::Railtie.init_trace app
140
+ end
141
+ end
142
+
143
+ ##
144
+ # Determine whether to use Stackdriver Trace or not.
145
+ #
146
+ # Returns true if valid GCP project_id and keyfile are provided and
147
+ # either Rails is in "production" environment or
148
+ # config.google_cloud.use_trace is explicitly true. Otherwise false.
149
+ #
150
+ # @param [Rails::Railtie::Configuration] config The
151
+ # Rails.application.config
152
+ #
153
+ # @return [Boolean] Whether to use Stackdriver Trace
154
+ #
155
+ def self.use_trace? config
156
+ gcp_config = config.google_cloud
157
+
158
+ # Return false if config.google_cloud.use_trace is
159
+ # explicitly false
160
+ return false if gcp_config.key?(:use_trace) &&
161
+ !gcp_config.use_trace
162
+
163
+ # Try authenticate authorize client API. Return false if unable to
164
+ # authorize.
165
+ keyfile = gcp_config.trace.keyfile || gcp_config.keyfile
166
+ begin
167
+ Google::Cloud::Trace::Credentials.credentials_with_scope keyfile
168
+ rescue Exception => e
169
+ warn "Unable to initialize Google::Cloud::Trace due " \
170
+ "to authorization error: #{e.message}"
171
+ return false
172
+ end
173
+
174
+ if project_id(config).to_s.empty?
175
+ warn "Google::Cloud::Trace is not activated due to empty project_id"
176
+ return false
177
+ end
178
+
179
+ # Otherwise return true if Rails is running in production or
180
+ # config.google_cloud.use_trace is explicitly true
181
+ Rails.env.production? ||
182
+ (gcp_config.key?(:use_trace) && gcp_config.use_trace)
183
+ end
184
+
185
+ ##
186
+ # Return the effective project ID for this app, given a Rails config.
187
+ #
188
+ # @param [Rails::Railtie::Configuration] config The
189
+ # Rails.application.config
190
+ # @return [String] The project ID, or nil if not found.
191
+ #
192
+ def self.project_id config
193
+ config.google_cloud.trace.project_id ||
194
+ config.google_cloud.project_id ||
195
+ ENV["TRACE_PROJECT"] ||
196
+ ENV["GOOGLE_CLOUD_PROJECT"] ||
197
+ Google::Cloud::Core::Environment.project_id
198
+ end
199
+
200
+ ##
201
+ # Initialize trace integration for Rails. Sets up the configuration,
202
+ # adds and configures middleware, and installs notifications.
203
+ #
204
+ # @private
205
+ #
206
+ def self.init_trace app
207
+ gcp_config = app.config.google_cloud
208
+ trace_config = gcp_config.trace
209
+ project_id = trace_config.project_id || gcp_config.project_id
210
+ keyfile = trace_config.keyfile || gcp_config.keyfile
211
+
212
+ tracer = Google::Cloud::Trace.new project: project_id,
213
+ keyfile: keyfile
214
+
215
+ app.middleware.insert_before \
216
+ Rack::Runtime,
217
+ Google::Cloud::Trace::Middleware,
218
+ service: tracer.service,
219
+ capture_stack: trace_config.capture_stack
220
+
221
+ trace_config.notifications.each do |type|
222
+ Google::Cloud::Trace::Notifications.instrument \
223
+ type,
224
+ max_length: trace_config.max_data_length,
225
+ capture_stack: trace_config.capture_stack
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end