plain_apm 0.2.6
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/CHANGELOG.md +0 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +18 -0
- data/lib/plain_apm/agent.rb +94 -0
- data/lib/plain_apm/backoff.rb +40 -0
- data/lib/plain_apm/config.rb +25 -0
- data/lib/plain_apm/extensions/exceptions/active_job.rb +34 -0
- data/lib/plain_apm/extensions/exceptions/rack.rb +41 -0
- data/lib/plain_apm/extensions/exceptions/railtie.rb +13 -0
- data/lib/plain_apm/extensions/trace_id/LICENSE.txt +22 -0
- data/lib/plain_apm/extensions/trace_id/active_job.rb +26 -0
- data/lib/plain_apm/extensions/trace_id/middleware.rb +47 -0
- data/lib/plain_apm/extensions/trace_id/railtie.rb +23 -0
- data/lib/plain_apm/extensions/trace_id.rb +15 -0
- data/lib/plain_apm/hooks/action_mailer.rb +44 -0
- data/lib/plain_apm/hooks/action_pack.rb +48 -0
- data/lib/plain_apm/hooks/action_view.rb +64 -0
- data/lib/plain_apm/hooks/active_job.rb +55 -0
- data/lib/plain_apm/hooks/active_record.rb +40 -0
- data/lib/plain_apm/hooks/active_support_subscriber.rb +57 -0
- data/lib/plain_apm/hooks/deploy.rb +47 -0
- data/lib/plain_apm/hooks/error_reporter.rb +53 -0
- data/lib/plain_apm/transport.rb +124 -0
- data/lib/plain_apm/version.rb +5 -0
- data/lib/plain_apm.rb +37 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ef383306da20d00a0bc5c8b2072b1dfbb3d49eb7c5d1f0710cee6d6749b726d2
|
4
|
+
data.tar.gz: b5c174eb5869f5909695af612182f2e9e6fe960230ce4ecd2d94b4701c5856fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 86dc64bd65c57fc77f4713f10bf08beeff9f32c7f9e80e3fc454b4462ff14a4cfc0613bd24879d2ec08e2692e9e35eb3ccad49b015cbfc3e331e9bcbdf4e45b9
|
7
|
+
data.tar.gz: 6f79c406f73217c44c06a46359722fb1ecb8d8c72e1c6de8ed5846992cab12ba56dc4c36fac18919896ab1f002a98129301d28cefde8502ccd32d7f6319f82c6
|
data/CHANGELOG.md
ADDED
File without changes
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 PlainAPM
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# PlainApm for Ruby
|
2
|
+
|
3
|
+
[PlainAPM][plainapm] monitors your Rails application and helps you
|
4
|
+
understand changes in its performance.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'plain_apm'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle install
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install plain_apm
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
To be able to use the gem, collect, and view your app's performance
|
25
|
+
metrics, request an APP key on [plainapm web site][plainapm] and follow
|
26
|
+
the installation instructions.
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
Bug reports and pull requests are welcome on [GitHub][github-issues].
|
31
|
+
|
32
|
+
Alternatively, feel free to send questions, suggestions, and feedback
|
33
|
+
to [PlainAPM support][support-email].
|
34
|
+
|
35
|
+
[plainapm]: https://plainapm.com
|
36
|
+
[support-email]: mailto:support@plainapm.com
|
37
|
+
[github-issues]: https://github.com/plainapm/plainapm-ruby/issues
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "appraisal"
|
5
|
+
require "standard/rake"
|
6
|
+
require "rake/testtask"
|
7
|
+
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.libs << "lib"
|
11
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :test do
|
15
|
+
task all: "appraisal:all"
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :test
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module PlainApm
|
7
|
+
class Agent
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def self.collect(event)
|
11
|
+
instance.collect(event)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.start
|
15
|
+
instance.start
|
16
|
+
end
|
17
|
+
|
18
|
+
def collect(event)
|
19
|
+
return unless @config.enabled
|
20
|
+
|
21
|
+
@events << event.merge(
|
22
|
+
"collected_at" => Time.now.utc.to_f,
|
23
|
+
"pid" => Process.pid,
|
24
|
+
"thread_id" => Thread.current.object_id.to_s
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
return unless @publisher.nil?
|
30
|
+
return unless @config.enabled
|
31
|
+
|
32
|
+
# TODO: sized queue w/ a timeout.
|
33
|
+
@events = Thread::Queue.new
|
34
|
+
|
35
|
+
# TODO: Multiple threads
|
36
|
+
@publisher = Thread.new { publisher_loop }
|
37
|
+
|
38
|
+
install_hooks
|
39
|
+
|
40
|
+
# TODO: add a cleaner shutdown.
|
41
|
+
at_exit { shutdown }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
# TODO: validate config
|
48
|
+
@config = Config.new
|
49
|
+
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def install_hooks
|
54
|
+
[
|
55
|
+
Hooks::Deploy,
|
56
|
+
Hooks::ActionMailer,
|
57
|
+
Hooks::ActionPack,
|
58
|
+
Hooks::ActionView,
|
59
|
+
Hooks::ActiveJob,
|
60
|
+
Hooks::ActiveRecord,
|
61
|
+
Hooks::ErrorReporter
|
62
|
+
].map(&:new).each(&:install)
|
63
|
+
end
|
64
|
+
|
65
|
+
def shutdown
|
66
|
+
return if @publisher.nil?
|
67
|
+
|
68
|
+
@events << nil
|
69
|
+
@publisher.join
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Run a background thread that pops events from the queue and posts them to
|
74
|
+
# the target server.
|
75
|
+
def publisher_loop
|
76
|
+
# Have the thread keep it's own connection.
|
77
|
+
transport = Transport.new(
|
78
|
+
endpoint: @config.endpoint,
|
79
|
+
app_key: @config.app_key
|
80
|
+
)
|
81
|
+
|
82
|
+
loop do
|
83
|
+
event = @events.pop
|
84
|
+
|
85
|
+
break if event.nil?
|
86
|
+
|
87
|
+
# TODO: event serialization
|
88
|
+
_response, _error, _retriable = transport.deliver(JSON.generate(event))
|
89
|
+
|
90
|
+
# TODO: retries / drops
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
class Backoff
|
5
|
+
##
|
6
|
+
# Exponential backoff with jitter.
|
7
|
+
|
8
|
+
DEFAULT_BASE_SECONDS = 1.5
|
9
|
+
DEFAULT_MAX_RETRIES = 10 # sum_0^10 1.5 ** k ~ 170s
|
10
|
+
DEFAULT_JITTER_MULTIPLIER = 0.2 # % of the current retry interval
|
11
|
+
|
12
|
+
##
|
13
|
+
# @param base_seconds [Integer] base of the exponential retry.
|
14
|
+
# @param max_retries [Integer] maximum retries to perform.
|
15
|
+
# @param jitter_multiplier [Float] % of the current retry interval to use for jitter.
|
16
|
+
def initialize(base_seconds: nil, max_retries: nil, jitter_multiplier: nil)
|
17
|
+
@base_seconds = base_seconds || DEFAULT_BASE_SECONDS
|
18
|
+
@max_retries = max_retries || DEFAULT_MAX_RETRIES
|
19
|
+
@jitter_multiplier = jitter_multiplier || DEFAULT_JITTER_MULTIPLIER
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# @param retries [Integer] Number of current retry attempts.
|
24
|
+
#
|
25
|
+
# @return [Integer|nil] Amount of time slept, or nil if out of retries.
|
26
|
+
def delay_time(retries:)
|
27
|
+
return if retries >= max_retries
|
28
|
+
|
29
|
+
base_interval = (base_seconds**retries)
|
30
|
+
jitter_interval = base_interval * jitter_multiplier
|
31
|
+
|
32
|
+
# The random factor is never -1, but that shouldn't be an issue.
|
33
|
+
base_interval + jitter_interval * (1.0 - 2.0 * rand)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :base_seconds, :max_retries, :jitter_multiplier
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
class Config
|
5
|
+
DEFAULT_EVENT_ENDPOINT = "https://ingest.plainapm.com/"
|
6
|
+
|
7
|
+
attr_accessor :endpoint, :app_key, :enabled
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@enabled = enabled?
|
11
|
+
@endpoint = ENV["PLAIN_APM_ENDPOINT"] || DEFAULT_EVENT_ENDPOINT
|
12
|
+
@app_key = ENV["PLAIN_APM_APP_KEY"] || warn("Missing PLAIN_APM_APP_KEY environment variable, plain_apm agent won't start.")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def enabled?
|
18
|
+
force = ENV["PLAIN_APM_ENABLED"] == "1"
|
19
|
+
key_present = ENV["PLAIN_APM_APP_KEY"] != ""
|
20
|
+
production = (ENV["RAILS_ENV"] || ENV["RACK_ENV"]) == "production"
|
21
|
+
|
22
|
+
key_present && (production || force)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Extensions
|
5
|
+
module TraceId
|
6
|
+
module ActiveJob
|
7
|
+
attr_accessor :trace_id
|
8
|
+
|
9
|
+
def initialize(*arguments)
|
10
|
+
super(*arguments)
|
11
|
+
|
12
|
+
# Either from request headers / a previous job, or a new trace.
|
13
|
+
@trace_id = PlainApm::Extensions::TraceId.current || SecureRandom.uuid
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize
|
17
|
+
super.update("trace_id" => trace_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deserialize(job)
|
21
|
+
PlainApm::Extensions::TraceId.current = job["trace_id"]
|
22
|
+
|
23
|
+
super(job)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Allow tracing request ID through jobs
|
29
|
+
ActiveSupport.on_load(:active_job) do |klass|
|
30
|
+
klass.prepend(ActiveJob)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Extensions
|
5
|
+
module Exceptions
|
6
|
+
class Rack
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
response = @app.call(env)
|
13
|
+
|
14
|
+
e = env["action_dispatch.exception"]
|
15
|
+
report_exception(e, env) unless e.nil?
|
16
|
+
|
17
|
+
response
|
18
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
19
|
+
report_exception(e, env)
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def report_exception(e, env)
|
26
|
+
event = {
|
27
|
+
"source" => "rack_exceptions",
|
28
|
+
"name" => "exception",
|
29
|
+
"class" => e.class.name,
|
30
|
+
"message" => e.message,
|
31
|
+
"backtrace" => e.backtrace,
|
32
|
+
"params" => env["action_dispatch.request.parameters"],
|
33
|
+
"trace_id" => PlainApm::Extensions::TraceId.current
|
34
|
+
}
|
35
|
+
|
36
|
+
PlainApm::Agent.collect(event)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Extensions
|
5
|
+
module Exceptions
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer "plain_apm.insert_exceptions_middleware" do |app|
|
8
|
+
app.config.middleware.insert(0, PlainApm::Extensions::Exceptions::Rack)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Steve Klabnik
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Extensions
|
5
|
+
module TraceId
|
6
|
+
module ActiveJob
|
7
|
+
def serialize
|
8
|
+
trace_id = PlainApm::Extensions::TraceId.current || SecureRandom.uuid
|
9
|
+
super.update("trace_id" => trace_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def deserialize(job)
|
13
|
+
PlainApm::Extensions::TraceId.current = job["trace_id"]
|
14
|
+
|
15
|
+
super(job)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Allow tracing request ID through jobs
|
21
|
+
ActiveSupport.on_load(:active_job) do |klass|
|
22
|
+
klass.prepend(ActiveJob)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
##
|
6
|
+
# This code is inspired by request_store gem by Steve Klabnik:
|
7
|
+
#
|
8
|
+
# https://github.com/steveklabnik/request_store/
|
9
|
+
#
|
10
|
+
# See LICENSE.txt in the current directory for the license.
|
11
|
+
module PlainApm
|
12
|
+
module Extensions
|
13
|
+
module TraceId
|
14
|
+
class Middleware
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
TraceId.current = trace_id(env)
|
21
|
+
|
22
|
+
status, headers, body = @app.call(env)
|
23
|
+
|
24
|
+
body = Rack::BodyProxy.new(body) do
|
25
|
+
TraceId.current = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
processed = true
|
29
|
+
|
30
|
+
[status, headers, body]
|
31
|
+
ensure
|
32
|
+
TraceId.current = nil if !processed
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def trace_id(env)
|
38
|
+
request_id(env) || SecureRandom.uuid
|
39
|
+
end
|
40
|
+
|
41
|
+
def request_id(env)
|
42
|
+
env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# This code is inspired by request_store gem by Steve Klabnik:
|
5
|
+
#
|
6
|
+
# https://github.com/steveklabnik/request_store/
|
7
|
+
#
|
8
|
+
# See LICENSE.txt in the current directory for the license.
|
9
|
+
module PlainApm
|
10
|
+
module Extensions
|
11
|
+
module TraceId
|
12
|
+
class Railtie < Rails::Railtie
|
13
|
+
initializer "plain_apm.insert_trace_id_middleware" do |app|
|
14
|
+
if defined?(ActionDispatch::RequestId)
|
15
|
+
app.config.middleware.insert_after ActionDispatch::RequestId, PlainApm::Extensions::TraceId::Middleware
|
16
|
+
else
|
17
|
+
app.config.middleware.insert_after Rack::MethodOverride, PlainApm::Extensions::TraceId::Middleware
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Hooks
|
5
|
+
class ActionMailer < ActiveSupportSubscriber
|
6
|
+
NOTIFICATION_PATTERN = /\A[^!]\w+\.action_mailer\Z/.freeze
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def notification_pattern
|
11
|
+
NOTIFICATION_PATTERN
|
12
|
+
end
|
13
|
+
|
14
|
+
def payload(event)
|
15
|
+
name, source = *event.name.split(".")
|
16
|
+
payload = event.payload
|
17
|
+
|
18
|
+
base = {
|
19
|
+
"source" => source,
|
20
|
+
"name" => name,
|
21
|
+
"backtrace" => filtered_backtrace,
|
22
|
+
"allocations" => event.allocations,
|
23
|
+
"started_at" => event.time,
|
24
|
+
"finished_at" => event.end,
|
25
|
+
"trace_id" => trace_id
|
26
|
+
}
|
27
|
+
|
28
|
+
case name
|
29
|
+
when "deliver"
|
30
|
+
base.merge({
|
31
|
+
"message_id" => payload[:message_id],
|
32
|
+
"mailer" => payload[:mailer],
|
33
|
+
"perform_deliveries" => payload[:perform_deliveries]
|
34
|
+
})
|
35
|
+
when "process"
|
36
|
+
base.merge({
|
37
|
+
"mailer" => payload[:mailer],
|
38
|
+
"action" => payload[:action]
|
39
|
+
})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Hooks
|
5
|
+
class ActionPack < ActiveSupportSubscriber
|
6
|
+
NOTIFICATION_PATTERN = /\A[^!]\w+\.action_controller\Z/.freeze
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def notification_pattern
|
11
|
+
NOTIFICATION_PATTERN
|
12
|
+
end
|
13
|
+
|
14
|
+
def payload(event)
|
15
|
+
name, source = *event.name.split(".")
|
16
|
+
payload = event.payload
|
17
|
+
|
18
|
+
base = {
|
19
|
+
"source" => source,
|
20
|
+
"name" => name,
|
21
|
+
"backtrace" => filtered_backtrace,
|
22
|
+
"allocations" => event.allocations,
|
23
|
+
"started_at" => event.time,
|
24
|
+
"finished_at" => event.end,
|
25
|
+
"trace_id" => trace_id
|
26
|
+
}
|
27
|
+
|
28
|
+
case name
|
29
|
+
when "process_action"
|
30
|
+
base.merge({
|
31
|
+
"controller" => payload[:controller],
|
32
|
+
"action" => payload[:action],
|
33
|
+
"params" => payload[:params],
|
34
|
+
"format" => payload[:format],
|
35
|
+
"method" => payload[:method],
|
36
|
+
"path" => payload[:path],
|
37
|
+
"status" => payload[:status]
|
38
|
+
})
|
39
|
+
when "redirect_to", "start_processing", "halted_callback", "send_file", "send_data"
|
40
|
+
nil
|
41
|
+
when "read_fragment", "write_fragment", "exist_fragment?", "expire_fragment"
|
42
|
+
# controller, action, key
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Hooks
|
5
|
+
class ActionView < ActiveSupportSubscriber
|
6
|
+
NOTIFICATION_PATTERN = /\A[^!]\w+\.action_view\Z/.freeze
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def notification_pattern
|
11
|
+
NOTIFICATION_PATTERN
|
12
|
+
end
|
13
|
+
|
14
|
+
def payload(event)
|
15
|
+
name, source = *event.name.split(".")
|
16
|
+
payload = event.payload
|
17
|
+
|
18
|
+
base = {
|
19
|
+
"source" => source,
|
20
|
+
"name" => name,
|
21
|
+
"backtrace" => filtered_backtrace,
|
22
|
+
"started_at" => event.time,
|
23
|
+
"finished_at" => event.end,
|
24
|
+
"allocations" => event.allocations,
|
25
|
+
"trace_id" => trace_id
|
26
|
+
}
|
27
|
+
|
28
|
+
case name
|
29
|
+
when "render_collection"
|
30
|
+
base.merge({
|
31
|
+
"identifier" => identifier(payload[:identifier]),
|
32
|
+
"layout" => payload[:layout],
|
33
|
+
"count" => payload[:count],
|
34
|
+
"cache_hits" => payload[:cache_hits]
|
35
|
+
})
|
36
|
+
when "render_layout"
|
37
|
+
base.merge({
|
38
|
+
"identifier" => identifier(payload[:identifier])
|
39
|
+
})
|
40
|
+
when "render_template"
|
41
|
+
base.merge({
|
42
|
+
"identifier" => identifier(payload[:identifier]),
|
43
|
+
"layout" => payload[:layout]
|
44
|
+
})
|
45
|
+
when "render_partial"
|
46
|
+
base.merge({
|
47
|
+
"identifier" => identifier(payload[:identifier]),
|
48
|
+
"layout" => payload[:layout],
|
49
|
+
"cache_hit" => payload[:cache_hit]
|
50
|
+
})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# strip rails root
|
55
|
+
def identifier(id)
|
56
|
+
return id unless defined?(Rails) && Rails.root
|
57
|
+
|
58
|
+
root = Rails.root.to_s
|
59
|
+
|
60
|
+
id.start_with?(root) ? id[root.size + 1..-1] : id # standard:disable Style/SlicingWithRange
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Hooks
|
5
|
+
class ActiveJob < ActiveSupportSubscriber
|
6
|
+
NOTIFICATION_PATTERN = /\A[^!]\w+\.active_job\Z/.freeze
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def notification_pattern
|
11
|
+
NOTIFICATION_PATTERN
|
12
|
+
end
|
13
|
+
|
14
|
+
def payload(event)
|
15
|
+
name, source = *event.name.split(".")
|
16
|
+
payload = event.payload
|
17
|
+
job = payload[:job]
|
18
|
+
|
19
|
+
base = {
|
20
|
+
"source" => source,
|
21
|
+
"name" => name,
|
22
|
+
"backtrace" => filtered_backtrace,
|
23
|
+
"started_at" => event.time,
|
24
|
+
"finished_at" => event.end,
|
25
|
+
"trace_id" => trace_id,
|
26
|
+
"allocations" => event.allocations,
|
27
|
+
"queue_name" => job.queue_name,
|
28
|
+
"job_id" => job.job_id,
|
29
|
+
"job_class" => job.class.name,
|
30
|
+
"job_arguments" => job.arguments,
|
31
|
+
"executions" => job.executions,
|
32
|
+
"scheduled_at" => job.scheduled_at,
|
33
|
+
"adapter" => payload[:adapter].class.name,
|
34
|
+
"aborted" => payload[:aborted]
|
35
|
+
}
|
36
|
+
|
37
|
+
case name
|
38
|
+
when "enqueue", "enqueue_at", "perform"
|
39
|
+
base
|
40
|
+
when "enqueue_retry"
|
41
|
+
base.merge({
|
42
|
+
"error" => payload[:error],
|
43
|
+
"wait" => payload[:wait]
|
44
|
+
})
|
45
|
+
when "retry_stopped", "discard"
|
46
|
+
base.merge({
|
47
|
+
"error" => payload[:error]
|
48
|
+
})
|
49
|
+
when "perform_start"
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "active_support_subscriber"
|
4
|
+
|
5
|
+
module PlainApm
|
6
|
+
module Hooks
|
7
|
+
class ActiveRecord < ActiveSupportSubscriber
|
8
|
+
NOTIFICATION_PATTERN = /\A[^!]\w+\.active_record\Z/.freeze
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def notification_pattern
|
13
|
+
NOTIFICATION_PATTERN
|
14
|
+
end
|
15
|
+
|
16
|
+
def payload(event)
|
17
|
+
name, source = *event.name.split(".")
|
18
|
+
payload = event.payload
|
19
|
+
|
20
|
+
base = {
|
21
|
+
"source" => source,
|
22
|
+
"name" => name,
|
23
|
+
"backtrace" => filtered_backtrace,
|
24
|
+
"started_at" => event.time,
|
25
|
+
"finished_at" => event.end,
|
26
|
+
"allocations" => event.allocations,
|
27
|
+
"trace_id" => trace_id
|
28
|
+
}
|
29
|
+
|
30
|
+
case name
|
31
|
+
when "sql"
|
32
|
+
base.merge({
|
33
|
+
"sql_name" => payload[:name],
|
34
|
+
"sql" => payload[:sql]
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainApm
|
4
|
+
module Hooks
|
5
|
+
class ActiveSupportSubscriber
|
6
|
+
def install
|
7
|
+
begin
|
8
|
+
require "active_support/notifications"
|
9
|
+
rescue LoadError
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
asn = ActiveSupport::Notifications
|
14
|
+
|
15
|
+
# Rails >= 6.1
|
16
|
+
if asn.respond_to?(:monotonic_subscribe)
|
17
|
+
asn.monotonic_subscribe(notification_pattern, method(:collect))
|
18
|
+
else
|
19
|
+
asn.subscribe(notification_pattern, method(:collect))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def collect(event)
|
24
|
+
# id / transaction_id is by instrumenter and thread
|
25
|
+
payload = payload(event)
|
26
|
+
|
27
|
+
return if payload.nil?
|
28
|
+
|
29
|
+
Agent.instance.collect(payload)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def pattern
|
35
|
+
raise "Not implemented"
|
36
|
+
end
|
37
|
+
|
38
|
+
def payload(event)
|
39
|
+
raise "Not implemented"
|
40
|
+
end
|
41
|
+
|
42
|
+
def filtered_backtrace
|
43
|
+
if defined?(Rails) && defined?(Rails::BacktraceCleaner)
|
44
|
+
@cleaner ||= Rails::BacktraceCleaner.new
|
45
|
+
@cleaner.clean(caller)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# The trace ID (comes from either HTTP_X_REQUEST_ID header, or is
|
51
|
+
# generated by the trace_id middleware)
|
52
|
+
def trace_id
|
53
|
+
PlainApm::Extensions::TraceId.current
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module PlainApm
|
2
|
+
##
|
3
|
+
# Tracks current revision of the app.
|
4
|
+
#
|
5
|
+
# This enables per-deploy metrics segmentation and checking
|
6
|
+
# for performance regressions.
|
7
|
+
module Hooks
|
8
|
+
class Deploy
|
9
|
+
##
|
10
|
+
# Collect once, immediately on install.
|
11
|
+
def install
|
12
|
+
collect
|
13
|
+
end
|
14
|
+
|
15
|
+
def collect
|
16
|
+
result = git_revision || hg_revision || return
|
17
|
+
|
18
|
+
tool, revision = *result
|
19
|
+
|
20
|
+
Agent.instance.collect(
|
21
|
+
{"source" => tool, "revision" => revision, "name" => "deploy"}
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# TODO: other deploy mechanisms
|
28
|
+
#
|
29
|
+
# Also, we might not be in the app root.
|
30
|
+
def git_revision
|
31
|
+
return nil unless File.exist?(".git")
|
32
|
+
|
33
|
+
["git", `git rev-parse --short HEAD`.strip]
|
34
|
+
rescue Error::ENOENT # No git installed
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def hg_revision
|
39
|
+
return nil unless File.exist?(".hg")
|
40
|
+
|
41
|
+
["hg", `hg log -l 1 -r . -T '{node}'`]
|
42
|
+
rescue Error::ENOENT # No mercurial installed
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module PlainApm
|
2
|
+
module Hooks
|
3
|
+
# Rails 7 error notification mechanism
|
4
|
+
class ErrorReporter
|
5
|
+
IGNORED_EXCEPTIONS = [
|
6
|
+
"Sidekiq::JobRetry::Skip" # Sidekiq uses exceptions for control flow.
|
7
|
+
].freeze
|
8
|
+
|
9
|
+
def install
|
10
|
+
begin
|
11
|
+
require "active_support/error_reporter"
|
12
|
+
require "active_support/lazy_load_hooks"
|
13
|
+
rescue LoadError
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
# Install the hook when the app is up. This might miss errors that
|
18
|
+
# happen before that, but that's OK.
|
19
|
+
ActiveSupport.on_load(:after_initialize, yield: self, run_once: true) do
|
20
|
+
::Rails.error.subscribe(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def collect(e, handled:, severity:, context: {})
|
25
|
+
return if IGNORED_EXCEPTIONS.include?(e.class.name)
|
26
|
+
|
27
|
+
event = {
|
28
|
+
"source" => "error_subscriber",
|
29
|
+
"name" => "exception",
|
30
|
+
"class" => e.class.name,
|
31
|
+
"message" => e.message,
|
32
|
+
"backtrace" => e.backtrace,
|
33
|
+
"handled" => handled,
|
34
|
+
"severity" => severity,
|
35
|
+
"context" => context,
|
36
|
+
"trace_id" => PlainApm::Extensions::TraceId.current
|
37
|
+
}
|
38
|
+
|
39
|
+
if e.cause
|
40
|
+
event.merge!({
|
41
|
+
"cause_class" => e.cause.class.name,
|
42
|
+
"cause_message" => e.cause.message,
|
43
|
+
"cause_backtrace" => e.cause.backtrace
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
PlainApm::Agent.collect(event)
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :report, :collect
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module PlainApm
|
7
|
+
class Transport
|
8
|
+
##
|
9
|
+
# HTTP transport class, mostly a wrapper for errors and timeout-handling.
|
10
|
+
|
11
|
+
# TODO: tune these.
|
12
|
+
HTTP_READ_TIMEOUT_SECONDS = 8 # default is 60
|
13
|
+
HTTP_WRITE_TIMEOUT_SECONDS = 8 # default is 60
|
14
|
+
HTTP_OPEN_TIMEOUT_SECONDS = 8 # default is 60
|
15
|
+
|
16
|
+
HTTP_TIMEOUTS = [
|
17
|
+
Net::OpenTimeout,
|
18
|
+
Net::ReadTimeout,
|
19
|
+
RUBY_VERSION >= "2.6.0" ? Net::WriteTimeout : nil
|
20
|
+
].compact.freeze
|
21
|
+
|
22
|
+
ERRNO_ERRORS = [
|
23
|
+
Errno::ECONNABORTED,
|
24
|
+
Errno::ECONNREFUSED,
|
25
|
+
Errno::ECONNRESET,
|
26
|
+
Errno::EHOSTDOWN,
|
27
|
+
Errno::EHOSTUNREACH,
|
28
|
+
Errno::EINVAL,
|
29
|
+
Errno::ENETUNREACH,
|
30
|
+
Errno::EPIPE
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
##
|
34
|
+
# @param endpoint [String] http URL to send the event to
|
35
|
+
# @param app_key [String] api key / token identifying this app
|
36
|
+
# @param http_client [Net::HTTP] for dependency injection in tests
|
37
|
+
def initialize(endpoint:, app_key:, http_client: Net::HTTP)
|
38
|
+
@uri = URI.parse(endpoint)
|
39
|
+
@app_key = app_key
|
40
|
+
@http = http_client.new(uri.host, uri.port)
|
41
|
+
|
42
|
+
# Forgotten /, e.g. https://example.com:3000
|
43
|
+
uri.path = "/" if uri.path.nil? || uri.path.empty?
|
44
|
+
|
45
|
+
# TODO: our own CA bundle?
|
46
|
+
http.use_ssl = uri.scheme == "https"
|
47
|
+
|
48
|
+
http.open_timeout = HTTP_OPEN_TIMEOUT_SECONDS
|
49
|
+
http.read_timeout = HTTP_READ_TIMEOUT_SECONDS
|
50
|
+
|
51
|
+
if RUBY_VERSION >= "2.6.0"
|
52
|
+
http.write_timeout = HTTP_WRITE_TIMEOUT_SECONDS
|
53
|
+
end
|
54
|
+
|
55
|
+
at_exit { shutdown }
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Performs the actual HTTP request.
|
60
|
+
#
|
61
|
+
# @param data [String] serialized payload to POST
|
62
|
+
# @return [Array] [response, error, retriable]
|
63
|
+
def deliver(data)
|
64
|
+
http_response do
|
65
|
+
http_request(http, uri.path, data)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
attr_reader :http, :uri, :app_key
|
72
|
+
|
73
|
+
##
|
74
|
+
# Opens the underlying TCP connection.
|
75
|
+
def start
|
76
|
+
http.start unless http.started?
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Close the connection at exit.
|
81
|
+
def shutdown
|
82
|
+
http.finish if http.started?
|
83
|
+
end
|
84
|
+
|
85
|
+
def http_request(http, path, body)
|
86
|
+
request = Net::HTTP::Post.new(path, http_headers)
|
87
|
+
http.request(request, body)
|
88
|
+
end
|
89
|
+
|
90
|
+
def http_headers
|
91
|
+
{
|
92
|
+
"Content-Type" => "application/json, charset=UTF-8",
|
93
|
+
"X-PlainApm-Key" => app_key
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def http_response
|
98
|
+
# Opening the connection here allows us to rescue socket and SSL errors.
|
99
|
+
# It'll be a NO-OP if already connected.
|
100
|
+
start
|
101
|
+
|
102
|
+
response = yield
|
103
|
+
|
104
|
+
case response
|
105
|
+
when Net::HTTPSuccess
|
106
|
+
[response, nil, false]
|
107
|
+
when Net::HTTPServerError, Net::HTTPTooManyRequests
|
108
|
+
[response, nil, true]
|
109
|
+
when Net::HTTPBadRequest, Net::HTTPUnauthorized, Net::HTTPRequestEntityTooLarge
|
110
|
+
[response, nil, false]
|
111
|
+
else
|
112
|
+
# Caveat: this includes redirects.
|
113
|
+
[response, nil, false]
|
114
|
+
end
|
115
|
+
rescue *HTTP_TIMEOUTS, *ERRNO_ERRORS, IOError => e
|
116
|
+
# Lowlevel libc6, connection errors.
|
117
|
+
[nil, e, true]
|
118
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
119
|
+
# SSL errors, socket errors, http protocol errors from Net. To avoid the
|
120
|
+
# caller thread loop dying.
|
121
|
+
[nil, e, false]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/plain_apm.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "plain_apm/version"
|
4
|
+
require_relative "plain_apm/transport"
|
5
|
+
require_relative "plain_apm/config"
|
6
|
+
require_relative "plain_apm/agent"
|
7
|
+
|
8
|
+
require_relative "plain_apm/hooks/deploy"
|
9
|
+
|
10
|
+
# Rails extensions
|
11
|
+
begin
|
12
|
+
require "rack/body_proxy"
|
13
|
+
|
14
|
+
require_relative "plain_apm/extensions/trace_id"
|
15
|
+
require_relative "plain_apm/extensions/trace_id/middleware"
|
16
|
+
require_relative "plain_apm/extensions/trace_id/active_job" if defined?(ActiveSupport)
|
17
|
+
require_relative "plain_apm/extensions/trace_id/railtie" if defined?(Rails::Railtie)
|
18
|
+
rescue LoadError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# Rack exceptions
|
23
|
+
require_relative "plain_apm/extensions/exceptions/rack"
|
24
|
+
require_relative "plain_apm/extensions/exceptions/railtie" if defined?(Rails::Railtie)
|
25
|
+
|
26
|
+
# Rails instrumentation
|
27
|
+
require_relative "plain_apm/hooks/active_support_subscriber"
|
28
|
+
require_relative "plain_apm/hooks/action_mailer"
|
29
|
+
require_relative "plain_apm/hooks/action_pack"
|
30
|
+
require_relative "plain_apm/hooks/action_view"
|
31
|
+
require_relative "plain_apm/hooks/active_job"
|
32
|
+
require_relative "plain_apm/hooks/active_record"
|
33
|
+
require_relative "plain_apm/hooks/error_reporter"
|
34
|
+
|
35
|
+
module PlainApm
|
36
|
+
Agent.start
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: plain_apm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- PlainAPM Team
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: standard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: appraisal
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
description: Ruby gem to collect events/metrics and send them to PlainAPM.
|
70
|
+
email:
|
71
|
+
- support@plainapm.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- CHANGELOG.md
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/plain_apm.rb
|
82
|
+
- lib/plain_apm/agent.rb
|
83
|
+
- lib/plain_apm/backoff.rb
|
84
|
+
- lib/plain_apm/config.rb
|
85
|
+
- lib/plain_apm/extensions/exceptions/active_job.rb
|
86
|
+
- lib/plain_apm/extensions/exceptions/rack.rb
|
87
|
+
- lib/plain_apm/extensions/exceptions/railtie.rb
|
88
|
+
- lib/plain_apm/extensions/trace_id.rb
|
89
|
+
- lib/plain_apm/extensions/trace_id/LICENSE.txt
|
90
|
+
- lib/plain_apm/extensions/trace_id/active_job.rb
|
91
|
+
- lib/plain_apm/extensions/trace_id/middleware.rb
|
92
|
+
- lib/plain_apm/extensions/trace_id/railtie.rb
|
93
|
+
- lib/plain_apm/hooks/action_mailer.rb
|
94
|
+
- lib/plain_apm/hooks/action_pack.rb
|
95
|
+
- lib/plain_apm/hooks/action_view.rb
|
96
|
+
- lib/plain_apm/hooks/active_job.rb
|
97
|
+
- lib/plain_apm/hooks/active_record.rb
|
98
|
+
- lib/plain_apm/hooks/active_support_subscriber.rb
|
99
|
+
- lib/plain_apm/hooks/deploy.rb
|
100
|
+
- lib/plain_apm/hooks/error_reporter.rb
|
101
|
+
- lib/plain_apm/transport.rb
|
102
|
+
- lib/plain_apm/version.rb
|
103
|
+
homepage: https://plainapm.com
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata:
|
107
|
+
homepage_uri: https://plainapm.com
|
108
|
+
source_code_uri: https://github.com/plainapm/plainapm-ruby
|
109
|
+
changelog_uri: https://github.com/plainapm/plainapm-ruby/blob/main/CHANGELOG.md
|
110
|
+
github_repo: git@github.com:plainapm/plainapm-ruby.git
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '2.0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubygems_version: 3.3.7
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: PlainAPM agent
|
130
|
+
test_files: []
|