google_cloud_run 0.2.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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +200 -0
- data/app/controllers/google_cloud_run/jobs_controller.rb +32 -0
- data/config/routes.rb +3 -0
- data/lib/google_cloud_run/engine.rb +5 -0
- data/lib/google_cloud_run/entry.rb +153 -0
- data/lib/google_cloud_run/exceptions.rb +32 -0
- data/lib/google_cloud_run/job_adapter.rb +135 -0
- data/lib/google_cloud_run/logger.rb +316 -0
- data/lib/google_cloud_run/railtie.rb +60 -0
- data/lib/google_cloud_run/request_id.rb +16 -0
- data/lib/google_cloud_run/severity.rb +124 -0
- data/lib/google_cloud_run/util.rb +103 -0
- data/lib/google_cloud_run/version.rb +3 -0
- data/lib/google_cloud_run.rb +9 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1978e88dc8a5bfb84eab657f11332750562931fc15d4ed9594e22ab0278b76f1
|
4
|
+
data.tar.gz: 45d40f141a603729fac0d75c54cfa4ebdde0c986500c357897db89a010ff18d1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6eebceb1d26a294f9f990464e2ca611dabffa7a1a02fd8605d0756d5c4e5f884d988805f5e7cc58064af9b12aad52f0de05d63c1866f9b5bddeac6eb3db46524
|
7
|
+
data.tar.gz: e39fa2604dbc229083c43285ba066712a70df61733ccd21aec5ce62758a2520d486f5ebef8b99e36bca78e2c9f56c81351f939d99a85c7f2b132b07ffc8d499b
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2021 Matthias Kadenbach
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# Rails on Google Cloud Run
|
2
|
+
|
3
|
+
* Logging
|
4
|
+
* Error Reporting
|
5
|
+
* Active Job via [Cloud Tasks](https://cloud.google.com/tasks), including delayed jobs.
|
6
|
+
* Minor patches for better compatibility
|
7
|
+
* Works with Ruby 3 and Rails 6
|
8
|
+
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
logger.info "Hello World"
|
14
|
+
|
15
|
+
logger.info do
|
16
|
+
"Expensive logging operation, only run when logged"
|
17
|
+
end
|
18
|
+
|
19
|
+
logger.info "Labels work, too!", my_label: "works", another_one: "great"
|
20
|
+
```
|
21
|
+
|
22
|
+
All Google Cloud Logging Severities are supported:
|
23
|
+
|
24
|
+
```
|
25
|
+
logger.default (or logger.unknown) - The log entry has no assigned severity level.
|
26
|
+
logger.debug - Debug or trace information.
|
27
|
+
logger.info - Routine information, such as ongoing status or performance.
|
28
|
+
logger.notice - Normal but significant events, such as start up, shut down, or a configuration change.
|
29
|
+
logger.warning (or logger.warn) - Warning events might cause problems.
|
30
|
+
logger.error - Error events are likely to cause problems.
|
31
|
+
logger.critical (or logger.fatal) - Critical events cause more severe problems or outages.
|
32
|
+
logger.alert - A person must take an action immediately.
|
33
|
+
logger.emergency - One or more systems are unusable.
|
34
|
+
```
|
35
|
+
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
Add the gem to your Gemfile and run `bundle install`.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# Gemfile
|
43
|
+
group :production do
|
44
|
+
gem 'google_cloud_run'
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
|
49
|
+
In your production config:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# config/environments/production.rb
|
53
|
+
|
54
|
+
config.log_level = :g_notice
|
55
|
+
config.logger = GoogleCloudRun::Logger.new
|
56
|
+
|
57
|
+
config.active_job.queue_adapter = :google_cloudrun_tasks
|
58
|
+
config.google_cloudrun.job_queue_default_region = "us-central1"
|
59
|
+
config.google_cloudrun.job_callback_url = "https://your-domain.com/rails/google_cloudrun/job_callback"
|
60
|
+
```
|
61
|
+
|
62
|
+
Set the default queue:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# app/jobs/application_job.rb
|
66
|
+
queue_as "my-queue"
|
67
|
+
|
68
|
+
# or if `config.google_cloudrun.job_queue_default_region` isn't set:
|
69
|
+
queue_as "us-central1/my-queue"
|
70
|
+
```
|
71
|
+
|
72
|
+
---
|
73
|
+
|
74
|
+
In the default production config, the logger is wrapped around
|
75
|
+
a `ENV["RAILS_LOG_TO_STDOUT"].present?` block. I usually just
|
76
|
+
remove this block so I don't have to actually set this ENV var.
|
77
|
+
|
78
|
+
You can also remove `config.log_formatter` as we don't need it anymore.
|
79
|
+
|
80
|
+
I recommend logging `:g_notice` and higher. Rails logs a lot of noise when logging
|
81
|
+
`:info` and higher.
|
82
|
+
|
83
|
+
|
84
|
+
## Configuration
|
85
|
+
|
86
|
+
You can change more settings in `config/environments/production.rb`. See below
|
87
|
+
for the default configuration.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# Enable Google Cloud Logging
|
91
|
+
config.google_cloudrun.logger = true
|
92
|
+
|
93
|
+
# Set output (STDERR or STDOUT)
|
94
|
+
config.google_cloudrun.out = STDERR
|
95
|
+
|
96
|
+
# Add source location (file, line number, method) to each log
|
97
|
+
config.google_cloudrun.logger_source_location = true
|
98
|
+
|
99
|
+
# Run Proc to assign current user as label to each log
|
100
|
+
config.google_cloudrun.logger_user = nil
|
101
|
+
|
102
|
+
|
103
|
+
# Enable Error Reporting
|
104
|
+
config.google_cloudrun.error_reporting = true
|
105
|
+
|
106
|
+
# Assign a default severity level to exceptions
|
107
|
+
config.google_cloudrun.error_reporting_exception_severity = :critical
|
108
|
+
|
109
|
+
# Run Proc to assign current user to Error Report
|
110
|
+
config.google_cloudrun.error_reporting_user = nil
|
111
|
+
|
112
|
+
# Turn logs into error reports for this severity and higher.
|
113
|
+
# Set to nil to disable.
|
114
|
+
config.google_cloudrun.error_reporting_level = :error
|
115
|
+
|
116
|
+
# When log is turned into error report, discard the original
|
117
|
+
# log and only report the error.
|
118
|
+
# Set to false to log and report the error at the same time.
|
119
|
+
config.google_cloudrun.error_reporting_discard_log = true
|
120
|
+
|
121
|
+
|
122
|
+
# Don't log or error report the following exceptions,
|
123
|
+
# because Cloud Run will create access logs for us already.
|
124
|
+
config.google_cloudrun.silence_exceptions = [
|
125
|
+
ActionController::RoutingError,
|
126
|
+
ActionController::MethodNotAllowed,
|
127
|
+
ActionController::UnknownHttpMethod,
|
128
|
+
ActionController::NotImplemented,
|
129
|
+
ActionController::UnknownFormat,
|
130
|
+
ActionController::BadRequest,
|
131
|
+
ActionController::ParameterMissing,
|
132
|
+
]
|
133
|
+
|
134
|
+
|
135
|
+
# Set Rails' request id to the trace id from X-Cloud-Trace-Context header
|
136
|
+
# as set by Cloud Run.
|
137
|
+
config.google_cloudrun.patch_request_id = true
|
138
|
+
|
139
|
+
|
140
|
+
# Enable Jobs via Cloud Tasks
|
141
|
+
config.google_cloudrun.jobs = true
|
142
|
+
|
143
|
+
# Set the default Google Cloud Task region, i.e. us-central1
|
144
|
+
config.google_cloudrun.job_queue_default_region = nil
|
145
|
+
|
146
|
+
# Google Cloud Tasks will call this url to execute the job
|
147
|
+
config.google_cloudrun.job_callback_url = nil # required, see above
|
148
|
+
|
149
|
+
# The default route for the callback url.
|
150
|
+
config.google_cloudrun.job_callback_path = "/rails/google_cloudrun/job_callback"
|
151
|
+
|
152
|
+
# Time for a job to run in seconds, default is 30min.
|
153
|
+
# Use `timeout_after 5.minutes` to configure a job individually.
|
154
|
+
config.google_cloudrun.job_timeout_sec = 1800 # (min 15s, max 30m)
|
155
|
+
```
|
156
|
+
|
157
|
+
---
|
158
|
+
|
159
|
+
Both `error_reporting_user` and `logger_user` expect a Proc like this:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
config.google_cloudrun.logger_user = Proc.new do |request|
|
163
|
+
# extract and return user id from request, example:
|
164
|
+
request.try { cookie_jar.encrypted[:user_id] }
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
---
|
169
|
+
|
170
|
+
An example job:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class MyJob < ApplicationJob
|
174
|
+
queue_as "us-central1/urgent"
|
175
|
+
timeout_after 1.minute # min 15s, max 30m, overrides config.google_cloudrun.job_timeout_sec
|
176
|
+
|
177
|
+
def perform(*args)
|
178
|
+
# Do something
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
## Cloud Task considerations
|
184
|
+
|
185
|
+
* Cloud Tasks are a better fit than Google Pub/Sub.
|
186
|
+
[Read more](https://cloud.google.com/pubsub/docs/choosing-pubsub-or-cloud-tasks#detailed-feature-comparison)
|
187
|
+
* I'd recommend to create two different Cloud Run services.
|
188
|
+
One for HTTP requests (aka Heroku Dynos) and another service
|
189
|
+
for jobs (aka Heroku Workers). Set the `Request Timeout` for
|
190
|
+
the request-bound service to something like `15s`, and for workers
|
191
|
+
to `1800s` or match `config.google_cloudrun.job_timeout_sec`.
|
192
|
+
* Cloud Task execution calls are authenticated with a Google-issued
|
193
|
+
OIDC token. So even though `/rails/google_cloudrun/job_callback` is publicly
|
194
|
+
available, without a valid token, no job will be executed.
|
195
|
+
* Cloud Task job processing is async. It supports multiple queues. Delayed jobs
|
196
|
+
are natively supported through Cloud Task. Priority jobs are not supported, use
|
197
|
+
different queues for that, i.e. "urgent", or "low-priority". Timeouts can be set
|
198
|
+
globally or per job-basis (min 15s, max 30m).
|
199
|
+
Retries are natively supported by Cloud Tasks.
|
200
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GoogleCloudRun
|
2
|
+
class JobsController < ActionController::Base
|
3
|
+
skip_before_action :verify_authenticity_token
|
4
|
+
|
5
|
+
def callback
|
6
|
+
# verify User-Agent and Content-Type
|
7
|
+
return head :bad_request unless request.user_agent == "Google-Cloud-Tasks"
|
8
|
+
return head :bad_request unless request.headers["Content-type"].include?("application/json")
|
9
|
+
return head :bad_request unless request.headers["Authorization"].start_with?("Bearer")
|
10
|
+
|
11
|
+
# verify Bearer token
|
12
|
+
begin
|
13
|
+
r = Google::Auth::IDTokens.verify_oidc request.headers["Authorization"]&.delete_prefix("Bearer")&.strip
|
14
|
+
rescue => e
|
15
|
+
Rails.logger.warning "Google Cloud Run Job callback failed: #{e.message}"
|
16
|
+
return head :bad_request
|
17
|
+
end
|
18
|
+
|
19
|
+
# parse JSON body
|
20
|
+
begin
|
21
|
+
body = JSON.parse(request.body.read)
|
22
|
+
rescue => e
|
23
|
+
raise "Google Cloud Run Job callback failed: Unable to parse JSON body: #{e.message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# execute the job
|
27
|
+
ActiveJob::Base.execute body
|
28
|
+
|
29
|
+
head :ok
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
module GoogleCloudRun
|
2
|
+
class LogEntry
|
3
|
+
include ::Logger::Severity
|
4
|
+
|
5
|
+
attr_accessor :severity,
|
6
|
+
:message,
|
7
|
+
:labels,
|
8
|
+
:timestamp,
|
9
|
+
:request,
|
10
|
+
:user,
|
11
|
+
:location_path, :location_line, :location_method,
|
12
|
+
:project_id
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@severity = G_DEFAULT
|
16
|
+
@timestamp = Time.now.utc
|
17
|
+
@insert_id = SecureRandom.uuid
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_json
|
21
|
+
raise "labels must be hash" if !@labels.blank? && !@labels.is_a?(Hash)
|
22
|
+
|
23
|
+
labels["user"] = @user unless @user.blank?
|
24
|
+
|
25
|
+
j = {}
|
26
|
+
|
27
|
+
j["logging.googleapis.com/insertId"] = @insert_id
|
28
|
+
j["severity"] = Severity.to_s(Severity.mapping(@severity))
|
29
|
+
j["message"] = @message.is_a?(String) ? @message.strip : @message.inspect
|
30
|
+
j["timestampSeconds"] = @timestamp.to_i
|
31
|
+
j["timestampNanos"] = @timestamp.nsec
|
32
|
+
j["logging.googleapis.com/labels"] = @labels unless @labels.blank?
|
33
|
+
|
34
|
+
if @request
|
35
|
+
# https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#httprequest
|
36
|
+
j["httpRequest"] = {}
|
37
|
+
j["httpRequest"]["requestMethod"] = @request&.method.to_s
|
38
|
+
j["httpRequest"]["requestUrl"] = @request&.url.to_s
|
39
|
+
j["httpRequest"]["userAgent"] = @request&.headers["user-agent"].to_s unless @request&.headers["user-agent"].blank?
|
40
|
+
j["httpRequest"]["remoteIp"] = @request&.remote_ip.to_s
|
41
|
+
j["httpRequest"]["referer"] = @request&.headers["referer"].to_s unless @request&.headers["referer"].blank?
|
42
|
+
|
43
|
+
trace, span, sample = GoogleCloudRun.parse_trace_context(@request&.headers["X-Cloud-Trace-Context"])
|
44
|
+
j["logging.googleapis.com/trace"] = "projects/#{@project_id}/traces/#{trace}" unless trace.blank?
|
45
|
+
j["logging.googleapis.com/spanId"] = span unless span.blank?
|
46
|
+
j["logging.googleapis.com/trace_sampled"] = sample unless sample.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
if @location_path || @location_line || @location_method
|
50
|
+
j["logging.googleapis.com/sourceLocation"] = {}
|
51
|
+
j["logging.googleapis.com/sourceLocation"]["function"] = @location_method.to_s
|
52
|
+
j["logging.googleapis.com/sourceLocation"]["file"] = @location_path.to_s
|
53
|
+
j["logging.googleapis.com/sourceLocation"]["line"] = @location_line.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
j.to_json
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report#ReportedErrorEvent
|
61
|
+
# https://cloud.google.com/error-reporting/docs/formatting-error-messages
|
62
|
+
class ErrorReportingEntry
|
63
|
+
include ::Logger::Severity
|
64
|
+
|
65
|
+
attr_accessor :severity,
|
66
|
+
:exception,
|
67
|
+
:project_id,
|
68
|
+
:message,
|
69
|
+
:labels,
|
70
|
+
:timestamp,
|
71
|
+
:request,
|
72
|
+
:user,
|
73
|
+
:location_path, :location_line, :location_method,
|
74
|
+
:context_service, :context_version
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@severity = G_CRITICAL
|
78
|
+
@timestamp = Time.now.utc
|
79
|
+
@insert_id = SecureRandom.uuid
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_json
|
83
|
+
raise "labels must be hash" if !@labels.blank? && !@labels.is_a?(Hash)
|
84
|
+
|
85
|
+
j = {}
|
86
|
+
|
87
|
+
j["@type"] = "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent"
|
88
|
+
j["logging.googleapis.com/insertId"] = @insert_id
|
89
|
+
j["severity"] = Severity.to_s(Severity.mapping(@severity))
|
90
|
+
j["eventTime"] = @timestamp.strftime("%FT%T.%9NZ")
|
91
|
+
j["logging.googleapis.com/labels"] = @labels unless @labels.blank?
|
92
|
+
|
93
|
+
if @context_service || @context_version
|
94
|
+
j["serviceContext"] = {}
|
95
|
+
j["serviceContext"]["service"] = @context_service.to_s
|
96
|
+
j["serviceContext"]["version"] = @context_version.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
if @request
|
100
|
+
# https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#httprequest
|
101
|
+
j["httpRequest"] = {}
|
102
|
+
j["httpRequest"]["requestMethod"] = @request&.method.to_s
|
103
|
+
j["httpRequest"]["requestUrl"] = @request&.url.to_s
|
104
|
+
j["httpRequest"]["userAgent"] = @request&.headers["user-agent"].to_s unless @request&.headers["user-agent"].blank?
|
105
|
+
j["httpRequest"]["remoteIp"] = @request&.remote_ip.to_s
|
106
|
+
j["httpRequest"]["referer"] = @request&.headers["referer"].to_s unless @request&.headers["referer"].blank?
|
107
|
+
|
108
|
+
trace, span, sample = GoogleCloudRun.parse_trace_context(@request&.headers["X-Cloud-Trace-Context"])
|
109
|
+
j["logging.googleapis.com/trace"] = "projects/#{@project_id}/traces/#{trace}" unless trace.blank?
|
110
|
+
j["logging.googleapis.com/spanId"] = span unless span.blank?
|
111
|
+
j["logging.googleapis.com/trace_sampled"] = sample unless sample.nil?
|
112
|
+
end
|
113
|
+
|
114
|
+
if @exception
|
115
|
+
j["message"] = @exception.class.to_s
|
116
|
+
|
117
|
+
e_message = @exception&.message.to_s.strip
|
118
|
+
unless e_message.blank?
|
119
|
+
j["message"] << ": " + e_message + "\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
j["message"] << @exception&.backtrace.join("\n")
|
123
|
+
else
|
124
|
+
j["message"] = @message.is_a?(String) ? @message.strip : @message.inspect
|
125
|
+
end
|
126
|
+
|
127
|
+
j["context"] = {}
|
128
|
+
|
129
|
+
if @request
|
130
|
+
# https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#httprequest
|
131
|
+
j["context"]["httpRequest"] = {}
|
132
|
+
j["context"]["httpRequest"]["method"] = @request&.method
|
133
|
+
j["context"]["httpRequest"]["url"] = @request&.url
|
134
|
+
j["context"]["httpRequest"]["userAgent"] = @request&.headers["user-agent"] unless @request&.headers["user-agent"].blank?
|
135
|
+
j["context"]["httpRequest"]["remoteIp"] = @request&.remote_ip
|
136
|
+
j["context"]["httpRequest"]["referrer"] = @request&.headers["referer"] unless @request&.headers["referer"].blank?
|
137
|
+
end
|
138
|
+
|
139
|
+
if @user
|
140
|
+
j["context"]["user"] = @user
|
141
|
+
end
|
142
|
+
|
143
|
+
if @location_path || @location_line || @location_method
|
144
|
+
j["context"]["reportLocation"] = {}
|
145
|
+
j["context"]["reportLocation"]["filePath"] = @location_path.to_s
|
146
|
+
j["context"]["reportLocation"]["lineNumber"] = @location_line.to_i
|
147
|
+
j["context"]["reportLocation"]["functionName"] = @location_method.to_s
|
148
|
+
end
|
149
|
+
|
150
|
+
j.to_json
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GoogleCloudRun
|
2
|
+
def self.exception_interceptor(request, exception)
|
3
|
+
|
4
|
+
# ref: https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report#reportederrorevent
|
5
|
+
|
6
|
+
return false if Rails.application.config.google_cloudrun.silence_exceptions.any? { |e| exception.is_a?(e) }
|
7
|
+
|
8
|
+
l = ErrorReportingEntry.new
|
9
|
+
l.project_id = GoogleCloudRun.project_id
|
10
|
+
l.severity = Rails.application.config.google_cloudrun.error_reporting_exception_severity
|
11
|
+
l.exception = exception
|
12
|
+
l.request = request
|
13
|
+
|
14
|
+
l.context_service = GoogleCloudRun.k_service
|
15
|
+
l.context_version = GoogleCloudRun.k_revision
|
16
|
+
|
17
|
+
# attach user to entry
|
18
|
+
p = Rails.application.config.google_cloudrun.error_reporting_user
|
19
|
+
if p && p.is_a?(Proc)
|
20
|
+
begin
|
21
|
+
l.user = p.call(request)
|
22
|
+
rescue
|
23
|
+
# TODO ignore or log?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Rails.application.config.google_cloudrun.out.puts l.to_json
|
28
|
+
Rails.application.config.google_cloudrun.out.flush
|
29
|
+
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module QueueAdapters
|
3
|
+
class GoogleCloudrunTasksAdapter
|
4
|
+
def initialize
|
5
|
+
@client = Google::Cloud::Tasks.cloud_tasks
|
6
|
+
@project_id = GoogleCloudRun.project_id
|
7
|
+
@service_account_email = GoogleCloudRun.default_service_account_email
|
8
|
+
@default_job_timeout_sec = Rails.application.config.google_cloudrun.job_timeout_sec
|
9
|
+
@job_callback_url = Rails.application.config.google_cloudrun.job_callback_url
|
10
|
+
@queue_default_region = Rails.application.config.google_cloudrun.job_queue_default_region
|
11
|
+
|
12
|
+
if @job_callback_url.blank? || !@job_callback_url.end_with?(Rails.application.config.google_cloudrun.job_callback_path)
|
13
|
+
raise "Set config.google_cloudrun.job_callback_url to 'https://your-domain.com#{Rails.application.config.google_cloudrun.job_callback_path}'"
|
14
|
+
end
|
15
|
+
|
16
|
+
if !@job_callback_url.start_with?("https://")
|
17
|
+
raise "config.google_cloudrun.job_callback_url must start with https://"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def enqueue(job)
|
22
|
+
create_cloudtask(job.class,
|
23
|
+
job.job_id,
|
24
|
+
job.queue_name,
|
25
|
+
local_timeout(job) || @default_job_timeout_sec,
|
26
|
+
nil,
|
27
|
+
job.serialize)
|
28
|
+
end
|
29
|
+
|
30
|
+
def enqueue_at(job, timestamp)
|
31
|
+
create_cloudtask(job.class,
|
32
|
+
job.job_id,
|
33
|
+
job.queue_name,
|
34
|
+
local_timeout(job) || @default_job_timeout_sec,
|
35
|
+
timestamp,
|
36
|
+
job.serialize)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def create_cloudtask(job_name, job_id, full_queue_name, job_timeout, scheduled_at, job)
|
42
|
+
return if !Rails.application.config.google_cloudrun.jobs
|
43
|
+
|
44
|
+
region, queue_name = parse_full_queue_name(full_queue_name)
|
45
|
+
queue = @client.queue_path project: @project_id, location: region, queue: queue_name
|
46
|
+
|
47
|
+
task = build_task_request(
|
48
|
+
"projects/#{@project_id}/locations/#{region}/queues/#{queue_name}/tasks/#{job_id}",
|
49
|
+
@job_callback_url,
|
50
|
+
@service_account_email,
|
51
|
+
job.to_json,
|
52
|
+
job_timeout,
|
53
|
+
scheduled_at,
|
54
|
+
)
|
55
|
+
|
56
|
+
response = nil
|
57
|
+
begin
|
58
|
+
response = @client.create_task parent: queue, task: task
|
59
|
+
rescue => e
|
60
|
+
raise "Failed sending job #{job_name}(#{job_id}) to queue '#{region}/#{queue_name}'. #{e.message}"
|
61
|
+
end
|
62
|
+
if response.nil?
|
63
|
+
raise "Failed sending job #{job_name}(#{job_id}) to queue '#{region}/#{queue_name}'. Google didn't return a response."
|
64
|
+
end
|
65
|
+
|
66
|
+
Rails.logger&.notice "Job #{job_name}(#{job_id}) sent to queue '#{region}/#{queue_name}'"
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_task_request(name, url, service_account_email, body, job_timeout, scheduled_at)
|
70
|
+
# ref: https://cloud.google.com/tasks/docs/reference/rest/v2/projects.locations.queues.tasks#Task
|
71
|
+
req = {
|
72
|
+
name: name,
|
73
|
+
http_request: {
|
74
|
+
oidc_token: { service_account_email: service_account_email },
|
75
|
+
headers: { "Content-Type": "application/json" },
|
76
|
+
http_method: "POST",
|
77
|
+
url: url,
|
78
|
+
body: body,
|
79
|
+
},
|
80
|
+
}
|
81
|
+
|
82
|
+
d = Google::Protobuf::Duration.new
|
83
|
+
d.seconds = job_timeout.to_i
|
84
|
+
req[:dispatch_deadline] = d
|
85
|
+
|
86
|
+
if scheduled_at
|
87
|
+
t = Google::Protobuf::Timestamp.new
|
88
|
+
t.seconds = Time.at(scheduled_at).utc.to_i
|
89
|
+
req[:schedule_time] = t
|
90
|
+
end
|
91
|
+
|
92
|
+
return req
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_full_queue_name(queue_name)
|
96
|
+
# config.active_job.queue_name_prefix will add an underscore,
|
97
|
+
# queue names can't have underscores. Let's turn it into a hyphen.
|
98
|
+
queue_name = queue_name.gsub("_", "-")
|
99
|
+
|
100
|
+
# see if we have something like this: region/queue
|
101
|
+
parts = queue_name.split("/")
|
102
|
+
if parts.size == 2
|
103
|
+
return parts[0], parts[1]
|
104
|
+
end
|
105
|
+
|
106
|
+
if @queue_default_region.blank?
|
107
|
+
raise "queue_as \"#{queue_name}\" needs region: \"region/#{queue_name}\" or set config.google_cloudrun.job_queue_default_region"
|
108
|
+
end
|
109
|
+
|
110
|
+
# use our default region
|
111
|
+
return @queue_default_region, queue_name
|
112
|
+
end
|
113
|
+
|
114
|
+
def local_timeout(job)
|
115
|
+
begin
|
116
|
+
job.class.class_variable_get(:@@google_cloudrun_job_timeout)
|
117
|
+
rescue
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module GoogleCloudRun
|
126
|
+
module TimeoutAfterExtension
|
127
|
+
extend ActiveSupport::Concern
|
128
|
+
|
129
|
+
class_methods do
|
130
|
+
def timeout_after(t)
|
131
|
+
self.class_variable_set(:@@google_cloudrun_job_timeout, t)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
module GoogleCloudRun
|
2
|
+
class Logger
|
3
|
+
include ::Logger::Severity
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@level = G_INFO
|
7
|
+
@formatter = DummyFormatter.new
|
8
|
+
@out = Rails.application.config.google_cloudrun.out
|
9
|
+
@project_id = GoogleCloudRun.project_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def level=(level)
|
13
|
+
@level = Severity.mapping(level)
|
14
|
+
end
|
15
|
+
|
16
|
+
def level?(level)
|
17
|
+
Severity.mapping(level) >= @level
|
18
|
+
end
|
19
|
+
|
20
|
+
def log(severity, msg = nil, progname = nil, **labels, &block)
|
21
|
+
labels["progname"] = progname unless progname.blank?
|
22
|
+
write(severity, msg, labels, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def default(msg = nil, **labels, &block)
|
26
|
+
write(G_DEFAULT, msg, labels, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def default?
|
30
|
+
self.level?(G_DEFAULT)
|
31
|
+
end
|
32
|
+
|
33
|
+
def default!
|
34
|
+
self.level = G_DEFAULT
|
35
|
+
end
|
36
|
+
|
37
|
+
def debug(msg = nil, **labels, &block)
|
38
|
+
write(G_DEBUG, msg, labels, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def debug?
|
42
|
+
self.level?(G_DEBUG)
|
43
|
+
end
|
44
|
+
|
45
|
+
def debug!
|
46
|
+
self.level = G_DEBUG
|
47
|
+
end
|
48
|
+
|
49
|
+
def info(msg = nil, **labels, &block)
|
50
|
+
write(G_INFO, msg, labels, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def info?
|
54
|
+
self.level?(G_INFO)
|
55
|
+
end
|
56
|
+
|
57
|
+
def info!
|
58
|
+
self.level = G_INFO
|
59
|
+
end
|
60
|
+
|
61
|
+
def notice(msg = nil, **labels, &block)
|
62
|
+
write(G_NOTICE, msg, labels, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def notice?
|
66
|
+
self.level?(G_NOTICE)
|
67
|
+
end
|
68
|
+
|
69
|
+
def notice!
|
70
|
+
self.level = G_NOTICE
|
71
|
+
end
|
72
|
+
|
73
|
+
def warning(msg = nil, **labels, &block)
|
74
|
+
write(G_WARNING, msg, labels, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def warning?
|
78
|
+
self.level?(G_WARNING)
|
79
|
+
end
|
80
|
+
|
81
|
+
def warning!
|
82
|
+
self.level = G_WARNING
|
83
|
+
end
|
84
|
+
|
85
|
+
def error(msg = nil, **labels, &block)
|
86
|
+
write(G_ERROR, msg, labels, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def error?
|
90
|
+
self.level?(G_ERROR)
|
91
|
+
end
|
92
|
+
|
93
|
+
def error!
|
94
|
+
self.level = G_ERROR
|
95
|
+
end
|
96
|
+
|
97
|
+
def critical(msg = nil, **labels, &block)
|
98
|
+
write(G_CRITICAL, msg, labels, &block)
|
99
|
+
end
|
100
|
+
|
101
|
+
def critical?
|
102
|
+
self.level?(G_CRITICAL)
|
103
|
+
end
|
104
|
+
|
105
|
+
def critical!
|
106
|
+
self.level = G_CRITICAL
|
107
|
+
end
|
108
|
+
|
109
|
+
def alert(msg = nil, **labels, &block)
|
110
|
+
write(G_ALERT, msg, labels, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def alert?
|
114
|
+
self.level?(G_ALERT)
|
115
|
+
end
|
116
|
+
|
117
|
+
def alert!
|
118
|
+
self.level = G_ALERT
|
119
|
+
end
|
120
|
+
|
121
|
+
def emergency(msg = nil, **labels, &block)
|
122
|
+
write(G_EMERGENCY, msg, labels, &block)
|
123
|
+
end
|
124
|
+
|
125
|
+
def emergency?
|
126
|
+
self.level?(G_EMERGENCY)
|
127
|
+
end
|
128
|
+
|
129
|
+
def emergency!
|
130
|
+
self.level = G_EMERGENCY
|
131
|
+
end
|
132
|
+
|
133
|
+
def <<(msg)
|
134
|
+
log(G_DEBUG, msg)
|
135
|
+
end
|
136
|
+
|
137
|
+
# called by LoggerMiddleware
|
138
|
+
def inject_request(request)
|
139
|
+
Thread.current[thread_key] = request
|
140
|
+
end
|
141
|
+
|
142
|
+
# called by ActiveSupport::LogSubscriber.flush_all!
|
143
|
+
def flush
|
144
|
+
Thread.current[thread_key] = nil
|
145
|
+
@out.flush
|
146
|
+
end
|
147
|
+
|
148
|
+
def datetime_format
|
149
|
+
"%FT%T.%9NZ" # RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits
|
150
|
+
end
|
151
|
+
|
152
|
+
def formatter
|
153
|
+
@formatter
|
154
|
+
end
|
155
|
+
|
156
|
+
# implement ::Logger interface, but do nothing
|
157
|
+
def close; end
|
158
|
+
|
159
|
+
def reopen(logdev = nil); end
|
160
|
+
|
161
|
+
def datetime_format=(format); end
|
162
|
+
|
163
|
+
def formatter=(formatter); end
|
164
|
+
|
165
|
+
alias_method :warn, :warning
|
166
|
+
alias_method :warn!, :warning!
|
167
|
+
alias_method :warn?, :warning?
|
168
|
+
alias_method :unknown, :default
|
169
|
+
alias_method :fatal, :critical
|
170
|
+
alias_method :fatal!, :critical!
|
171
|
+
alias_method :fatal?, :critical?
|
172
|
+
alias_method :add, :log
|
173
|
+
alias_method :sev_threshold, :level=
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def should_log?(severity)
|
178
|
+
Rails.application.config.google_cloudrun.logger && self.level?(severity)
|
179
|
+
end
|
180
|
+
|
181
|
+
def should_error_report?(severity)
|
182
|
+
Rails.application.config.google_cloudrun.error_reporting &&
|
183
|
+
!Rails.application.config.google_cloudrun.error_reporting_level.nil? &&
|
184
|
+
Severity.mapping(severity) >= Severity.mapping(Rails.application.config.google_cloudrun.error_reporting_level)
|
185
|
+
end
|
186
|
+
|
187
|
+
def write(severity, msg, labels = {}, &block)
|
188
|
+
should_log = should_log?(severity)
|
189
|
+
should_error_report = should_error_report?(severity)
|
190
|
+
return false if !should_log && !should_error_report
|
191
|
+
|
192
|
+
# execute given block
|
193
|
+
msg = block.call if block
|
194
|
+
|
195
|
+
# write error report
|
196
|
+
if should_error_report
|
197
|
+
write_error_report(severity, msg, labels)
|
198
|
+
|
199
|
+
# return early if we don't want to log as well
|
200
|
+
return true if Rails.application.config.google_cloudrun.error_reporting_discard_log
|
201
|
+
end
|
202
|
+
|
203
|
+
# write log
|
204
|
+
if should_log
|
205
|
+
write_log(severity, msg, labels)
|
206
|
+
end
|
207
|
+
|
208
|
+
return true
|
209
|
+
end
|
210
|
+
|
211
|
+
def write_log(severity, msg, labels)
|
212
|
+
l = GoogleCloudRun::LogEntry.new
|
213
|
+
l.severity = severity
|
214
|
+
l.message = msg
|
215
|
+
l.labels = labels
|
216
|
+
l.request = current_request
|
217
|
+
l.project_id = @project_id
|
218
|
+
|
219
|
+
# set caller location
|
220
|
+
if Rails.application.config.google_cloudrun.logger_source_location
|
221
|
+
loc = caller_locations(3, 1)&.first
|
222
|
+
if loc
|
223
|
+
l.location_path = loc.path
|
224
|
+
l.location_line = loc.lineno
|
225
|
+
l.location_method = loc.label
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# attach user to entry
|
230
|
+
p = Rails.application.config.google_cloudrun.logger_user
|
231
|
+
if p && p.is_a?(Proc)
|
232
|
+
begin
|
233
|
+
l.user = p.call(current_request)
|
234
|
+
rescue
|
235
|
+
raise
|
236
|
+
# TODO ignore or log?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
@out.puts l.to_json
|
241
|
+
end
|
242
|
+
|
243
|
+
def write_error_report(severity, msg, labels)
|
244
|
+
l = ErrorReportingEntry.new
|
245
|
+
l.severity = severity
|
246
|
+
l.request = current_request
|
247
|
+
l.labels = labels
|
248
|
+
l.message = msg
|
249
|
+
l.project_id = @project_id
|
250
|
+
|
251
|
+
# set caller location
|
252
|
+
loc = caller_locations(3, 1)&.first
|
253
|
+
if loc
|
254
|
+
l.location_path = loc.path
|
255
|
+
l.location_line = loc.lineno
|
256
|
+
l.location_method = loc.label
|
257
|
+
end
|
258
|
+
|
259
|
+
# set context
|
260
|
+
l.context_service = GoogleCloudRun.k_service
|
261
|
+
l.context_version = GoogleCloudRun.k_revision
|
262
|
+
|
263
|
+
# attach user to entry
|
264
|
+
p = Rails.application.config.google_cloudrun.error_reporting_user
|
265
|
+
if p && p.is_a?(Proc)
|
266
|
+
begin
|
267
|
+
l.user = p.call(current_request)
|
268
|
+
rescue
|
269
|
+
# TODO ignore or log?
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
@out.puts l.to_json
|
274
|
+
end
|
275
|
+
|
276
|
+
def current_request
|
277
|
+
Thread.current[thread_key]
|
278
|
+
end
|
279
|
+
|
280
|
+
def thread_key
|
281
|
+
# We use our object ID here to avoid conflicting with other instances
|
282
|
+
thread_key = @thread_key ||= "google_cloudrun_logging_request:#{object_id}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class LoggerMiddleware
|
287
|
+
def initialize(app)
|
288
|
+
@app = app
|
289
|
+
end
|
290
|
+
|
291
|
+
# A middleware which injects the request into the Rails.logger
|
292
|
+
def call(env)
|
293
|
+
request = ActionDispatch::Request.new(env)
|
294
|
+
Rails.logger.inject_request(request)
|
295
|
+
@app.call(env)
|
296
|
+
ensure
|
297
|
+
ActiveSupport::LogSubscriber.flush_all!
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class DummyFormatter < ::Logger::Formatter
|
302
|
+
def call(severity, timestamp, progname, msg)
|
303
|
+
# we bypass all formatters
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
module SilenceExceptions
|
308
|
+
private
|
309
|
+
|
310
|
+
def log_error(_request, wrapper)
|
311
|
+
exception = wrapper.exception
|
312
|
+
return if Rails.application.config.google_cloudrun.silence_exceptions.any? { |e| exception.is_a?(e) }
|
313
|
+
super
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module GoogleCloudRun
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
config.google_cloudrun = ActiveSupport::OrderedOptions.new
|
4
|
+
|
5
|
+
config.google_cloudrun.out = STDERR
|
6
|
+
|
7
|
+
config.google_cloudrun.logger = true
|
8
|
+
config.google_cloudrun.logger_source_location = true
|
9
|
+
config.google_cloudrun.logger_user = nil
|
10
|
+
|
11
|
+
config.google_cloudrun.error_reporting = true
|
12
|
+
config.google_cloudrun.error_reporting_exception_severity = :critical
|
13
|
+
config.google_cloudrun.error_reporting_user = nil
|
14
|
+
config.google_cloudrun.error_reporting_level = :error
|
15
|
+
config.google_cloudrun.error_reporting_discard_log = true
|
16
|
+
|
17
|
+
config.google_cloudrun.silence_exceptions = [
|
18
|
+
ActionController::RoutingError,
|
19
|
+
ActionController::MethodNotAllowed,
|
20
|
+
ActionController::UnknownHttpMethod,
|
21
|
+
ActionController::NotImplemented,
|
22
|
+
ActionController::UnknownFormat,
|
23
|
+
ActionController::BadRequest,
|
24
|
+
ActionController::ParameterMissing,
|
25
|
+
]
|
26
|
+
|
27
|
+
config.google_cloudrun.patch_request_id = true
|
28
|
+
|
29
|
+
config.google_cloudrun.jobs = true
|
30
|
+
config.google_cloudrun.job_queue_default_region = nil
|
31
|
+
config.google_cloudrun.job_callback_url = nil # required
|
32
|
+
config.google_cloudrun.job_callback_path = "/rails/google_cloudrun/job_callback"
|
33
|
+
config.google_cloudrun.job_timeout_sec = 1800 # 30 min (min 15s, max 30m)
|
34
|
+
|
35
|
+
# ref: https://guides.rubyonrails.org/rails_on_rack.html#internal-middleware-stack
|
36
|
+
|
37
|
+
initializer "google_cloud_run" do |app|
|
38
|
+
if app.config.google_cloudrun.error_reporting
|
39
|
+
ActionDispatch::DebugExceptions.register_interceptor GoogleCloudRun.method(:exception_interceptor)
|
40
|
+
end
|
41
|
+
|
42
|
+
if app.config.google_cloudrun.logger
|
43
|
+
app.config.middleware.insert_after Rails::Rack::Logger, GoogleCloudRun::LoggerMiddleware
|
44
|
+
end
|
45
|
+
|
46
|
+
if app.config.google_cloudrun.patch_request_id
|
47
|
+
app.config.middleware.insert_before ActionDispatch::RequestId, GoogleCloudRun::RequestId
|
48
|
+
end
|
49
|
+
|
50
|
+
# https://stackoverflow.com/a/52475865/2142441
|
51
|
+
if config.google_cloudrun.silence_exceptions.size > 0
|
52
|
+
ActiveSupport.on_load(:action_controller) do
|
53
|
+
ActionDispatch::DebugExceptions.prepend GoogleCloudRun::SilenceExceptions
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ActiveJob::Base.send(:include, GoogleCloudRun::TimeoutAfterExtension)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module GoogleCloudRun
|
2
|
+
class RequestId
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
# A middleware to replace X-Request-Id with X-Cloud-Trace-Context's Trace ID
|
8
|
+
# ref: https://github.com/rails/rails/blob/6-1-stable/actionpack/lib/action_dispatch/middleware/request_id.rb
|
9
|
+
# ref: https://github.com/Octo-Labs/heroku-request-id/blob/master/lib/heroku-request-id/railtie.rb
|
10
|
+
def call(env)
|
11
|
+
req = ActionDispatch::Request.new env
|
12
|
+
trace, _, _ = GoogleCloudRun.parse_trace_context(req.headers["X-Cloud-Trace-Context"])
|
13
|
+
@app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = trace }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
class Logger
|
2
|
+
module Severity
|
3
|
+
# Ruby/Rails default severities:
|
4
|
+
# Low-level information, mostly for developers.
|
5
|
+
# DEBUG = 0
|
6
|
+
# Generic (useful) information about system operation.
|
7
|
+
# INFO = 1
|
8
|
+
# A warning.
|
9
|
+
# WARN = 2
|
10
|
+
# A handleable error condition.
|
11
|
+
# ERROR = 3
|
12
|
+
# An unhandleable error that results in a program crash.
|
13
|
+
# FATAL = 4
|
14
|
+
# An unknown message that should always be logged.
|
15
|
+
# UNKNOWN = 5
|
16
|
+
|
17
|
+
# Google Cloud severities:
|
18
|
+
# The log entry has no assigned severity level.
|
19
|
+
G_DEFAULT = 0
|
20
|
+
|
21
|
+
# Debug or trace information.
|
22
|
+
G_DEBUG = 100
|
23
|
+
|
24
|
+
# Routine information, such as ongoing status or performance.
|
25
|
+
G_INFO = 200
|
26
|
+
|
27
|
+
# Normal but significant events, such as start up, shut down, or a configuration change.
|
28
|
+
G_NOTICE = 300
|
29
|
+
|
30
|
+
# Warning events might cause problems.
|
31
|
+
G_WARNING = 400
|
32
|
+
|
33
|
+
# Error events are likely to cause problems.
|
34
|
+
G_ERROR = 500
|
35
|
+
|
36
|
+
# Critical events cause more severe problems or outages.
|
37
|
+
G_CRITICAL = 600
|
38
|
+
|
39
|
+
# A person must take an action immediately.
|
40
|
+
G_ALERT = 700
|
41
|
+
|
42
|
+
# One or more systems are unusable.
|
43
|
+
G_EMERGENCY = 800
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module GoogleCloudRun
|
48
|
+
module Severity
|
49
|
+
include ::Logger::Severity
|
50
|
+
|
51
|
+
def self.to_s(severity)
|
52
|
+
case mapping(severity)
|
53
|
+
when G_DEFAULT; return "DEFAULT"
|
54
|
+
when G_DEBUG; return "DEBUG"
|
55
|
+
when G_INFO; return "INFO"
|
56
|
+
when G_NOTICE; return "NOTICE"
|
57
|
+
when G_WARNING; return "WARNING"
|
58
|
+
when G_ERROR; return "ERROR"
|
59
|
+
when G_CRITICAL; return "CRITICAL"
|
60
|
+
when G_ALERT; return "ALERT"
|
61
|
+
when G_EMERGENCY; return "EMERGENCY"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.mapping(severity)
|
66
|
+
case severity
|
67
|
+
when nil; return G_DEFAULT
|
68
|
+
when G_DEFAULT, G_DEBUG, G_INFO, G_NOTICE, G_WARNING, G_ERROR, G_CRITICAL, G_ALERT, G_EMERGENCY; return severity
|
69
|
+
when DEBUG; return G_DEBUG
|
70
|
+
when INFO; return G_INFO
|
71
|
+
when WARN; return G_WARNING
|
72
|
+
when ERROR; return G_ERROR
|
73
|
+
when FATAL; return G_CRITICAL
|
74
|
+
when UNKNOWN; return G_DEFAULT
|
75
|
+
when 0; return G_DEFAULT
|
76
|
+
when 1; return G_INFO
|
77
|
+
when 2; return G_WARNING
|
78
|
+
when 3; return G_ERROR
|
79
|
+
when 4; return G_CRITICAL
|
80
|
+
when 5; return G_DEFAULT
|
81
|
+
when 100; return G_DEBUG
|
82
|
+
when 200; return G_INFO
|
83
|
+
when 300; return G_NOTICE
|
84
|
+
when 400; return G_WARNING
|
85
|
+
when 500; return G_ERROR
|
86
|
+
when 600; return G_CRITICAL
|
87
|
+
when 700; return G_ALERT
|
88
|
+
when 800; return G_EMERGENCY
|
89
|
+
when "G_DEFAULT"; return G_DEFAULT
|
90
|
+
when "G_DEBUG"; return G_DEBUG
|
91
|
+
when "G_INFO"; return G_INFO
|
92
|
+
when "G_NOTICE"; return G_NOTICE
|
93
|
+
when "G_WARNING"; return G_WARNING
|
94
|
+
when "G_ERROR"; return G_ERROR
|
95
|
+
when "G_CRITICAL"; return G_CRITICAL
|
96
|
+
when "G_ALERT"; return G_ALERT
|
97
|
+
when "G_EMERGENCY"; return G_EMERGENCY
|
98
|
+
when :g_default; return G_DEFAULT
|
99
|
+
when :g_debug; return G_DEBUG
|
100
|
+
when :g_info; return G_INFO
|
101
|
+
when :g_notice; return G_NOTICE
|
102
|
+
when :g_warning; return G_WARNING
|
103
|
+
when :g_error; return G_ERROR
|
104
|
+
when :g_critical; return G_CRITICAL
|
105
|
+
when :g_alert; return G_ALERT
|
106
|
+
when :g_emergency; return G_EMERGENCY
|
107
|
+
when :debug; return G_DEBUG
|
108
|
+
when :info; return G_INFO
|
109
|
+
when :warn; return G_WARNING
|
110
|
+
when :error; return G_ERROR
|
111
|
+
when :fatal; return G_CRITICAL
|
112
|
+
when :unknown; return G_DEFAULT
|
113
|
+
when :default; return G_DEFAULT
|
114
|
+
when :notice; return G_NOTICE
|
115
|
+
when :warning; return G_WARNING
|
116
|
+
when :critical; return G_CRITICAL
|
117
|
+
when :alert; return G_ALERT
|
118
|
+
when :emergency; return G_EMERGENCY
|
119
|
+
else
|
120
|
+
raise "unknown severity '#{severity.inspect}'"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
module GoogleCloudRun
|
5
|
+
def self.k_service
|
6
|
+
@k_service ||= begin
|
7
|
+
ENV.fetch("K_SERVICE", "")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.k_revision
|
12
|
+
@k_revision ||= begin
|
13
|
+
revision = ENV.fetch("K_REVISION", "")
|
14
|
+
service = ENV.fetch("K_SERVICE", "")
|
15
|
+
revision.delete_prefix(service + "-")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# parse_trace_context parses header
|
20
|
+
# X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE
|
21
|
+
def self.parse_trace_context(raw)
|
22
|
+
raw&.strip!
|
23
|
+
return nil, nil, nil if raw.blank?
|
24
|
+
|
25
|
+
trace = nil
|
26
|
+
span = nil
|
27
|
+
sample = nil
|
28
|
+
|
29
|
+
first = raw.split("/")
|
30
|
+
if first.size > 0
|
31
|
+
trace = first[0]
|
32
|
+
end
|
33
|
+
|
34
|
+
if first.size > 1
|
35
|
+
second = first[1].split(";")
|
36
|
+
|
37
|
+
if second.size > 0
|
38
|
+
span = second[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
if second.size > 1
|
42
|
+
case second[1].delete_prefix("o=")
|
43
|
+
when "1"; sample = true
|
44
|
+
when "0"; sample = false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
return trace, span, sample
|
50
|
+
end
|
51
|
+
|
52
|
+
# project_id returns the current Google project id from the
|
53
|
+
# metadata server
|
54
|
+
def self.project_id
|
55
|
+
return "dummy-project" if Rails.env.test?
|
56
|
+
|
57
|
+
@project_id ||= begin
|
58
|
+
uri = URI.parse("http://metadata.google.internal/computeMetadata/v1/project/project-id")
|
59
|
+
request = Net::HTTP::Get.new(uri)
|
60
|
+
request["Metadata-Flavor"] = "Google"
|
61
|
+
|
62
|
+
req_options = {
|
63
|
+
open_timeout: 5,
|
64
|
+
read_timeout: 5,
|
65
|
+
max_retries: 2,
|
66
|
+
}
|
67
|
+
|
68
|
+
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
69
|
+
http.request(request)
|
70
|
+
end
|
71
|
+
|
72
|
+
raise "unknown google cloud project" if response.code.to_i != 200
|
73
|
+
|
74
|
+
response.body.strip
|
75
|
+
end
|
76
|
+
end
|
77
|
+
#
|
78
|
+
# default_service_account_email returns the default service account's email from the
|
79
|
+
# metadata server
|
80
|
+
def self.default_service_account_email
|
81
|
+
return "123456789-compute@developer.gserviceaccount.com" if Rails.env.test?
|
82
|
+
|
83
|
+
@default_service_account_email ||= begin
|
84
|
+
uri = URI.parse("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email")
|
85
|
+
request = Net::HTTP::Get.new(uri)
|
86
|
+
request["Metadata-Flavor"] = "Google"
|
87
|
+
|
88
|
+
req_options = {
|
89
|
+
open_timeout: 5,
|
90
|
+
read_timeout: 5,
|
91
|
+
max_retries: 2,
|
92
|
+
}
|
93
|
+
|
94
|
+
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
95
|
+
http.request(request)
|
96
|
+
end
|
97
|
+
|
98
|
+
raise "unknown google default service account" if response.code.to_i != 200
|
99
|
+
|
100
|
+
response.body.strip
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "google_cloud_run/severity"
|
2
|
+
require "google_cloud_run/util"
|
3
|
+
require "google_cloud_run/entry"
|
4
|
+
require "google_cloud_run/logger"
|
5
|
+
require "google_cloud_run/exceptions"
|
6
|
+
require "google_cloud_run/request_id"
|
7
|
+
require "google_cloud_run/engine"
|
8
|
+
require "google_cloud_run/job_adapter"
|
9
|
+
require "google_cloud_run/railtie"
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: google_cloud_run
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Kadenbach
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: google-cloud-tasks
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: googleauth
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.15'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.15'
|
55
|
+
description: Opinionated Logging, Error Reporting and minor patches for Rails on Google
|
56
|
+
Cloud Run.
|
57
|
+
email:
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- LICENSE
|
63
|
+
- README.md
|
64
|
+
- app/controllers/google_cloud_run/jobs_controller.rb
|
65
|
+
- config/routes.rb
|
66
|
+
- lib/google_cloud_run.rb
|
67
|
+
- lib/google_cloud_run/engine.rb
|
68
|
+
- lib/google_cloud_run/entry.rb
|
69
|
+
- lib/google_cloud_run/exceptions.rb
|
70
|
+
- lib/google_cloud_run/job_adapter.rb
|
71
|
+
- lib/google_cloud_run/logger.rb
|
72
|
+
- lib/google_cloud_run/railtie.rb
|
73
|
+
- lib/google_cloud_run/request_id.rb
|
74
|
+
- lib/google_cloud_run/severity.rb
|
75
|
+
- lib/google_cloud_run/util.rb
|
76
|
+
- lib/google_cloud_run/version.rb
|
77
|
+
homepage: https://github.com/mattes/google_cloud_run
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata:
|
81
|
+
homepage_uri: https://github.com/mattes/google_cloud_run
|
82
|
+
source_code_uri: https://github.com/mattes/google_cloud_run
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.2.3
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Rails on Google Cloud Run
|
102
|
+
test_files: []
|