sapience 1.0.3 → 1.0.4
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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +12 -4
- data/Rakefile +1 -1
- data/dev-entrypoint.sh +5 -0
- data/docker-compose.yml +10 -1
- data/lib/sapience/appender/datadog.rb +20 -29
- data/lib/sapience/configuration.rb +2 -2
- data/lib/sapience/extensions/action_controller/notifications.rb +54 -0
- data/lib/sapience/extensions/active_job/notifications.rb +35 -0
- data/lib/sapience/extensions/active_record/notifications.rb +45 -0
- data/lib/sapience/extensions/grape/middleware/logging.rb +1 -1
- data/lib/sapience/extensions/grape/notifications.rb +46 -0
- data/lib/sapience/extensions/notifications.rb +32 -0
- data/lib/sapience/grape.rb +2 -0
- data/lib/sapience/rails/engine.rb +13 -2
- data/lib/sapience/sapience.rb +2 -12
- data/lib/sapience/version.rb +1 -1
- data/lib/sapience.rb +1 -0
- data/test_apps/grape/Gemfile +2 -1
- data/test_apps/grape/gemfiles/grape_0.16.2.gemfile.lock +1 -1
- data/test_apps/grape/gemfiles/grape_0.17.0.gemfile +1 -0
- data/test_apps/grape/gemfiles/grape_0.17.0.gemfile.lock +2 -1
- data/test_apps/grape/lib/ping/api.rb +1 -0
- data/test_apps/grape/spec/lib/ping/api_spec.rb +16 -2
- data/test_apps/grape/spec/spec_helper.rb +1 -0
- data/test_apps/rails/Gemfile +1 -1
- data/test_apps/rails/app/jobs/test_job.rb +12 -0
- data/test_apps/rails/app/models/post.rb +1 -1
- data/test_apps/rails/app/models/user.rb +1 -0
- data/test_apps/rails/app/workers/test_worker.rb +1 -1
- data/test_apps/rails/config/database.yml +6 -7
- data/test_apps/rails/config/initializers/sneakers.rb +9 -10
- data/test_apps/rails/db/migrate/20160902141445_create_posts.rb +7 -1
- data/test_apps/rails/db/schema.rb +7 -3
- data/test_apps/rails/lib/external_sneaker.rb +4 -4
- data/test_apps/rails/lib/tasks/sneakers.rake +21 -0
- data/test_apps/rails/spec/controllers/posts_controller_spec.rb +1 -1
- data/test_apps/rails/spec/fixtures/sapience.yml +1 -0
- data/test_apps/rails/spec/jobs/test_job_spec.rb +36 -0
- data/test_apps/rails/spec/models/post_spec.rb +10 -1
- data/test_apps/rails/spec/rails_helper.rb +4 -1
- data/test_apps/rails/spec/requests/posts_spec.rb +37 -4
- data/test_apps/rails/spec/workers/test_worker_spec.rb +10 -5
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67aad6878ced498fd8bb3f53f38316eb1d0c311a
|
4
|
+
data.tar.gz: 8530772e63086bd9e30d1bcba0a743e3f2df83ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ea440554539370aa76691e74c66d8e1edc4ebdab2b6539250206302892ca8d699508f0255d35c2e2b455dfa03d4af189c7f10da718be3da32bb27ed94653b93
|
7
|
+
data.tar.gz: a091be856a79a94b3a62a5b4c0e90456f89f68f9afab0e41c386a5663849cd98ca16e526a62d904c7394d69cd5c65c19d1d7bc21319f76c4a674fe9b22cf4d8a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,16 +1,24 @@
|
|
1
1
|
sudo: required
|
2
2
|
dist: trusty
|
3
3
|
language: ruby
|
4
|
+
cache:
|
5
|
+
bundler: true
|
6
|
+
directories:
|
7
|
+
- $HOME/docker
|
8
|
+
|
4
9
|
services:
|
5
10
|
- docker
|
6
11
|
|
7
12
|
before_script:
|
8
13
|
- mkdir -p coverage/rails coverage/grape coverage/sapience
|
14
|
+
- bundle exec rake reevoocop
|
15
|
+
|
16
|
+
env:
|
17
|
+
- TEST_SUITE=rspec INSTALL=false
|
18
|
+
- TEST_SUITE=rails INSTALL=false
|
19
|
+
- TEST_SUITE=grape INSTALL=false
|
9
20
|
|
10
|
-
script:
|
11
|
-
- docker-compose run rspec
|
12
|
-
- docker-compose run rails
|
13
|
-
- docker-compose run grape
|
21
|
+
script: "docker-compose run $TEST_SUITE"
|
14
22
|
|
15
23
|
after_success:
|
16
24
|
- chown -R $(whoami) coverage
|
data/Rakefile
CHANGED
data/dev-entrypoint.sh
CHANGED
data/docker-compose.yml
CHANGED
@@ -10,7 +10,11 @@ services:
|
|
10
10
|
RABBITMQ_DEFAULT_USER: sapience
|
11
11
|
RABBITMQ_DEFAULT_PASS: tests
|
12
12
|
|
13
|
-
|
13
|
+
postgres:
|
14
|
+
image: postgres:latest
|
15
|
+
environment:
|
16
|
+
POSTGRES_USER: sapience
|
17
|
+
POSTGRES_PASSWORD: tests
|
14
18
|
|
15
19
|
base:
|
16
20
|
image: ruby:2.3
|
@@ -26,6 +30,7 @@ services:
|
|
26
30
|
# Enable sending signals (CTRL+C, CTRL+P + CTRL+Q) into the container:
|
27
31
|
tty: true
|
28
32
|
volumes:
|
33
|
+
# - vendor/bundle:/usr/local/bundle
|
29
34
|
# Mount our app code directory (".") into our app containers at the
|
30
35
|
# "/usr/src/app" folder:
|
31
36
|
- .:/usr/src/app
|
@@ -55,8 +60,12 @@ services:
|
|
55
60
|
command: bundle exec rspec
|
56
61
|
depends_on:
|
57
62
|
- rabbitmq
|
63
|
+
- postgres
|
58
64
|
environment:
|
59
65
|
APP_NAME: rails_app
|
66
|
+
POSTGRES_HOST: postgres
|
67
|
+
POSTGRES_USER: sapience
|
68
|
+
POSTGRES_PASSWORD: tests
|
60
69
|
AMQP: amqp://sapience:tests@rabbitmq:5672
|
61
70
|
PATH: /usr/src/app/bin:/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
62
71
|
|
@@ -33,7 +33,6 @@ module Sapience
|
|
33
33
|
@tags = options.delete(:tags)
|
34
34
|
@uri = URI.parse(url)
|
35
35
|
fail('Statsd only supports udp. Example: "udp://localhost:8125"') if @uri.scheme != "udp"
|
36
|
-
|
37
36
|
super(options, &block)
|
38
37
|
end
|
39
38
|
|
@@ -47,58 +46,50 @@ module Sapience
|
|
47
46
|
return false unless metric
|
48
47
|
|
49
48
|
if log.duration
|
50
|
-
timing(metric, log.duration)
|
49
|
+
timing(metric, log.duration, tags: log.tags)
|
51
50
|
else
|
52
51
|
amount = (log.metric_amount || 1).round
|
53
|
-
|
54
|
-
decrement(metric, amount.abs)
|
55
|
-
else
|
56
|
-
increment(metric, amount)
|
57
|
-
end
|
52
|
+
count(metric, amount, tags: log.tags)
|
58
53
|
end
|
59
54
|
true
|
60
55
|
end
|
61
56
|
|
62
|
-
def timing(metric, duration = 0)
|
57
|
+
def timing(metric, duration = 0, options = {})
|
63
58
|
if block_given?
|
64
59
|
start = Time.now
|
65
60
|
yield
|
66
|
-
provider.timing(metric, ((Time.now - start) * 1000).floor)
|
61
|
+
provider.timing(metric, ((Time.now - start) * 1000).floor, options)
|
67
62
|
else
|
68
|
-
provider.timing(metric, duration)
|
63
|
+
provider.timing(metric, duration, options)
|
69
64
|
end
|
70
65
|
end
|
71
66
|
|
72
|
-
def increment(metric,
|
73
|
-
provider.
|
74
|
-
amount.times { provider.increment(metric) }
|
75
|
-
end
|
67
|
+
def increment(metric, options = {})
|
68
|
+
provider.increment(metric, options)
|
76
69
|
end
|
77
70
|
|
78
|
-
def decrement(metric,
|
79
|
-
provider.
|
80
|
-
amount.times { provider.decrement(metric) }
|
81
|
-
end
|
71
|
+
def decrement(metric, options = {})
|
72
|
+
provider.decrement(metric, options)
|
82
73
|
end
|
83
74
|
|
84
|
-
def histogram(metric, amount)
|
85
|
-
provider.histogram(metric, amount)
|
75
|
+
def histogram(metric, amount, options = {})
|
76
|
+
provider.histogram(metric, amount, options)
|
86
77
|
end
|
87
78
|
|
88
|
-
def gauge(metric, amount,
|
89
|
-
provider.gauge(metric, amount,
|
79
|
+
def gauge(metric, amount, options = {})
|
80
|
+
provider.gauge(metric, amount, options)
|
90
81
|
end
|
91
82
|
|
92
|
-
def count(metric, amount,
|
93
|
-
provider.count(metric, amount,
|
83
|
+
def count(metric, amount, options = {})
|
84
|
+
provider.count(metric, amount, options)
|
94
85
|
end
|
95
86
|
|
96
|
-
def time(metric, &block)
|
97
|
-
provider.time(metric, &block)
|
87
|
+
def time(metric, options = {}, &block)
|
88
|
+
provider.time(metric, options, &block)
|
98
89
|
end
|
99
90
|
|
100
|
-
def batch
|
101
|
-
|
91
|
+
def batch(&block)
|
92
|
+
provider.batch(&block)
|
102
93
|
end
|
103
94
|
|
104
95
|
def event(title, text, options = {})
|
@@ -106,7 +97,7 @@ module Sapience
|
|
106
97
|
end
|
107
98
|
|
108
99
|
def namespace
|
109
|
-
ns = Sapience.namify(
|
100
|
+
ns = Sapience.namify(app_name)
|
110
101
|
ns << ".#{Sapience.namify(Sapience.environment)}" if Sapience.environment
|
111
102
|
ns
|
112
103
|
end
|
@@ -21,8 +21,8 @@ module Sapience
|
|
21
21
|
|
22
22
|
# Initial default Level for all new instances of Sapience::Logger
|
23
23
|
def initialize(options = {}) # rubocop:disable AbcSize
|
24
|
-
fail ArgumentError, "options need to be a hash" unless options.is_a?(Hash)
|
25
|
-
@options = DEFAULT.merge(options.deep_symbolize_keyz!)
|
24
|
+
fail ArgumentError, "options need to be a hash #{options.inspect}" unless options.is_a?(Hash)
|
25
|
+
@options = DEFAULT.merge(options.dup.deep_symbolize_keyz!)
|
26
26
|
@options[:log_executor] &&= @options[:log_executor].to_sym
|
27
27
|
validate_log_executor!(@options[:log_executor])
|
28
28
|
self.default_level = @options[:log_level].to_sym
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Sapience
|
2
|
+
module Extensions
|
3
|
+
module ActionController
|
4
|
+
class Notifications < ::Sapience::Extensions::Notifications
|
5
|
+
# Options:
|
6
|
+
#
|
7
|
+
# *<tt>:metric_name</tt> - the metric name, defaults to "rails.request"
|
8
|
+
# *<tt>:tags</tt> - additional tags
|
9
|
+
def initialize(options = {})
|
10
|
+
@metric_name = options[:metric_name] || "rails.request"
|
11
|
+
super
|
12
|
+
Sapience::Extensions::Notifications.subscribe("process_action.action_controller") do |event|
|
13
|
+
record event
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def record(event) # rubocop:disable AbcSize
|
20
|
+
return unless record?
|
21
|
+
|
22
|
+
payload = event.payload
|
23
|
+
method = payload[:method].downcase
|
24
|
+
status = payload[:status]
|
25
|
+
action = payload[:action]
|
26
|
+
ctrl = payload[:controller].sub(/Controller$/, "").underscore
|
27
|
+
format = payload[:format]
|
28
|
+
|
29
|
+
tags = self.tags + %W(
|
30
|
+
method:#{method}
|
31
|
+
status:#{status}
|
32
|
+
action:#{action}
|
33
|
+
controller:#{ctrl}
|
34
|
+
format:#{format}
|
35
|
+
)
|
36
|
+
|
37
|
+
metrics.batch do
|
38
|
+
metrics.increment metric_name, tags: tags
|
39
|
+
metrics.timing("#{metric_name}.time", event.duration, tags: tags)
|
40
|
+
if payload[:db_runtime]
|
41
|
+
metrics.timing("#{metric_name}.time.db", payload[:db_runtime].round(10), tags: tags)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
if payload[:view_runtime]
|
47
|
+
metrics.timing("#{metric_name}.time.view", payload[:view_runtime].round(2), tags: tags)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sapience
|
2
|
+
module Extensions
|
3
|
+
module ActiveJob
|
4
|
+
class Notifications < ::Sapience::Extensions::Notifications
|
5
|
+
|
6
|
+
# Options:
|
7
|
+
#
|
8
|
+
# *<tt>:metric_name</tt> - the metric name, defaults to "activejob.perform"
|
9
|
+
# *<tt>:tags</tt> - additional tags
|
10
|
+
def initialize(opts = {})
|
11
|
+
super
|
12
|
+
@metric_name = opts[:metric_name] || "activejob.perform"
|
13
|
+
|
14
|
+
Sapience::Extensions::Notifications.subscribe "perform.active_job" do |event|
|
15
|
+
record event
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def record(event) # rubocop:disable AbcSize
|
22
|
+
return unless record?
|
23
|
+
|
24
|
+
job = event.payload[:job]
|
25
|
+
name = job.class.name.sub(/Job$/, "").underscore
|
26
|
+
tags = self.tags + %W(name:#{name} queue:#{job.queue_name})
|
27
|
+
metrics.batch do
|
28
|
+
metrics.increment metric_name, tags: tags
|
29
|
+
metrics.timing "#{metric_name}.time", event.duration, tags: tags
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sapience
|
2
|
+
module Extensions
|
3
|
+
module ActiveRecord
|
4
|
+
class Notifications < ::Sapience::Extensions::Notifications
|
5
|
+
# Options:
|
6
|
+
#
|
7
|
+
# *<tt>:metric_name</tt> - the metric name, defaults to "activerecord.query"
|
8
|
+
# *<tt>:include_schema</tt> - record schema queries, off by default
|
9
|
+
# *<tt>:include_generic</tt> - record general (nameless) queries, off by default
|
10
|
+
# *<tt>:tags</tt> - additional tags
|
11
|
+
def initialize(opts = {})
|
12
|
+
super
|
13
|
+
@metric_name = opts[:metric_name] || "activerecord.sql"
|
14
|
+
@include_schema = opts[:include_schema] == true
|
15
|
+
@include_generic = opts[:include_generic] == true
|
16
|
+
@include_raw = opts[:include_raw] == true
|
17
|
+
|
18
|
+
Sapience::Extensions::Notifications.subscribe "sql.active_record" do |event|
|
19
|
+
record event
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def record(event) # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
|
26
|
+
return unless record?
|
27
|
+
|
28
|
+
payload = event.payload
|
29
|
+
name = payload[:name]
|
30
|
+
return if (name.nil? || name == "SQL") && !@include_generic
|
31
|
+
return if name == "SCHEMA" && !@include_schema
|
32
|
+
|
33
|
+
name = name.downcase.split(/\W/).join(".") if name
|
34
|
+
tags = self.tags.dup
|
35
|
+
tags.push "query:#{name}" if name
|
36
|
+
|
37
|
+
metrics.batch do
|
38
|
+
metrics.increment metric_name, tags: tags
|
39
|
+
metrics.timing "#{metric_name}.time", event.duration, tags: tags
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -39,7 +39,7 @@ module Sapience
|
|
39
39
|
{
|
40
40
|
method: request.request_method,
|
41
41
|
request_path: request.path,
|
42
|
-
format: "
|
42
|
+
format: env["api.endpoint"].instance_variable_get(:@app).instance_variable_get(:@app).options[:format],
|
43
43
|
status: response.try(:status) || 404,
|
44
44
|
class_name: env["api.endpoint"].options[:for].to_s,
|
45
45
|
action: "index",
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Sapience
|
2
|
+
module Extensions
|
3
|
+
module Grape
|
4
|
+
class Notifications < ::Sapience::Extensions::Notifications
|
5
|
+
# Options:
|
6
|
+
#
|
7
|
+
# *<tt>:metric_name</tt> - the metric name, defaults to "grape.request"
|
8
|
+
# *<tt>:tags</tt> - additional tags
|
9
|
+
def initialize(opts = {})
|
10
|
+
super
|
11
|
+
@metric_name = opts[:metric_name] || "grape.request"
|
12
|
+
|
13
|
+
Sapience::Extensions::Notifications.subscribe "endpoint_run.grape" do |event|
|
14
|
+
record event
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def record(event) # rubocop:disable AbcSize
|
21
|
+
return unless record?
|
22
|
+
|
23
|
+
payload = event.payload
|
24
|
+
endpoint = payload[:endpoint]
|
25
|
+
route = endpoint.route
|
26
|
+
version = route.version
|
27
|
+
method = route.request_method.downcase
|
28
|
+
format = payload[:endpoint].instance_variable_get(:@app).instance_variable_get(:@app).options[:format]
|
29
|
+
path = route.pattern.path.dup
|
30
|
+
|
31
|
+
path.sub!(/\(\.#{format}\)$/, "")
|
32
|
+
path.sub!(":version/", "") if version
|
33
|
+
path.gsub!(/:(\w+)/) { |m| m[1..-1].upcase }
|
34
|
+
path.gsub!(/[^\w\/\-]+/, "_")
|
35
|
+
|
36
|
+
tags = self.tags + %W(method:#{method} format:#{format} path:#{path} status:#{endpoint.status})
|
37
|
+
tags.push "version:#{version}" if version
|
38
|
+
metrics.batch do
|
39
|
+
metrics.increment metric_name, tags: tags
|
40
|
+
metrics.timing "#{metric_name}.time", event.duration, tags: tags
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/notifications"
|
3
|
+
|
4
|
+
module Sapience
|
5
|
+
module Extensions
|
6
|
+
class Notifications
|
7
|
+
attr_reader :tags, :metric_name
|
8
|
+
|
9
|
+
def self.use(options = {})
|
10
|
+
new(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.subscribe(pattern, &block)
|
14
|
+
::ActiveSupport::Notifications.subscribe(pattern) do |*args|
|
15
|
+
block.call ::ActiveSupport::Notifications::Event.new(*args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@tags = options[:tags] || []
|
21
|
+
end
|
22
|
+
|
23
|
+
def record?
|
24
|
+
!metrics.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def metrics
|
28
|
+
Sapience.metrics
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/sapience/grape.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "sapience"
|
2
2
|
require "sapience/extensions/grape/timings"
|
3
3
|
require "sapience/extensions/grape/middleware/logging"
|
4
|
+
require "sapience/extensions/grape/notifications"
|
4
5
|
|
5
6
|
module Grape
|
6
7
|
class API
|
@@ -12,5 +13,6 @@ module Sapience
|
|
12
13
|
class Grape
|
13
14
|
Sapience.configure
|
14
15
|
::Grape::API.send(:include, Sapience::Loggable)
|
16
|
+
Sapience::Extensions::Grape::Notifications.use
|
15
17
|
end
|
16
18
|
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
require "sapience"
|
2
2
|
require "sapience/extensions/action_controller/live" if defined?(ActionController::Live)
|
3
3
|
require "sapience/extensions/action_controller/log_subscriber"
|
4
|
+
require "sapience/extensions/action_controller/notifications"
|
4
5
|
require "sapience/extensions/action_dispatch/debug_exceptions"
|
5
6
|
require "sapience/extensions/action_view/streaming_template_renderer"
|
6
|
-
|
7
|
+
if defined?(ActiveRecord)
|
8
|
+
require "sapience/extensions/active_record/log_subscriber"
|
9
|
+
require "sapience/extensions/active_record/notifications"
|
10
|
+
end
|
11
|
+
require "sapience/extensions/active_job/notifications" if defined?(ActiveJob)
|
7
12
|
require "sapience/extensions/rails/rack/logger"
|
8
13
|
require "sapience/extensions/rails/rack/logger_info_as_debug"
|
9
14
|
require "sapience/extensions/action_view/log_subscriber"
|
@@ -67,9 +72,15 @@ module Sapience
|
|
67
72
|
Bugsnag.configure { |config| config.logger = Sapience[Bugsnag] } if defined?(Bugsnag)
|
68
73
|
Sapience::Extensions::ActionController::LogSubscriber.attach_to :action_controller
|
69
74
|
# Sapience::Extensions::ActiveSupport::MailerLogSubscriber.attach_to :action_mailer
|
70
|
-
|
75
|
+
if defined?(ActiveRecord)
|
76
|
+
Sapience::Extensions::ActiveRecord::LogSubscriber.attach_to :active_record
|
77
|
+
Sapience::Extensions::ActiveRecord::Notifications.use
|
78
|
+
end
|
79
|
+
|
71
80
|
Sapience::Extensions::ActionView::LogSubscriber.attach_to :action_view
|
72
81
|
# Sapience::Extensions::ActiveJob::LogSubscriber.attach_to :active_job
|
82
|
+
Sapience::Extensions::ActionController::Notifications.use
|
83
|
+
Sapience::Extensions::ActiveJob::Notifications.use if defined?(ActiveJob)
|
73
84
|
end
|
74
85
|
end
|
75
86
|
end
|
data/lib/sapience/sapience.rb
CHANGED
@@ -33,12 +33,9 @@ module Sapience
|
|
33
33
|
RAILS_ENV = "RAILS_ENV".freeze
|
34
34
|
SAPIENCE_ENV = "SAPIENCE_ENV".freeze
|
35
35
|
|
36
|
-
# TODO: Maybe when configuring with a block we should create a new config?
|
37
|
-
# See the TODO note on .config for more information
|
38
36
|
def self.configure(force: false)
|
39
37
|
yield config if block_given?
|
40
38
|
return config if configured? && force == false
|
41
|
-
reload_config!
|
42
39
|
reset_appenders!
|
43
40
|
add_appenders(*config.appenders)
|
44
41
|
@@configured = true
|
@@ -50,13 +47,6 @@ module Sapience
|
|
50
47
|
@@config_hash ||= ConfigLoader.load_from_file
|
51
48
|
end
|
52
49
|
|
53
|
-
def self.reload_config!
|
54
|
-
@@config_hash = ConfigLoader.load_from_file
|
55
|
-
end
|
56
|
-
|
57
|
-
# TODO: Should we really always read from file?
|
58
|
-
# What if someone wants to configure sapience with a block
|
59
|
-
# without reading the default.yml?
|
60
50
|
def self.config
|
61
51
|
@@config ||= begin
|
62
52
|
options = config_hash[environment]
|
@@ -220,7 +210,7 @@ module Sapience
|
|
220
210
|
# logger.debug("Login time", user: 'Joe', duration: 100, ip_address: '127.0.0.1')
|
221
211
|
def self.add_appender(appender, options = {}, _deprecated_level = nil, &_block)
|
222
212
|
fail ArgumentError, "options should be a hash" unless options.is_a?(Hash)
|
223
|
-
options.deep_symbolize_keyz!
|
213
|
+
options = options.dup.deep_symbolize_keyz!
|
224
214
|
appender_class = constantize_symbol(appender)
|
225
215
|
validate_appender!(appender_class)
|
226
216
|
|
@@ -286,7 +276,7 @@ module Sapience
|
|
286
276
|
end
|
287
277
|
|
288
278
|
def self.metrics
|
289
|
-
@@metrics ||=
|
279
|
+
@@metrics ||= nil
|
290
280
|
end
|
291
281
|
|
292
282
|
def self.logger=(logger)
|
data/lib/sapience/version.rb
CHANGED
data/lib/sapience.rb
CHANGED
data/test_apps/grape/Gemfile
CHANGED
@@ -11,11 +11,12 @@ gem "puma", "~> 3.0"
|
|
11
11
|
gem "sapience", path: "../.."
|
12
12
|
gem "sentry-raven"
|
13
13
|
gem "dogstatsd-ruby"
|
14
|
+
gem "activesupport"
|
14
15
|
|
15
16
|
group :development, :test do
|
16
17
|
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
17
18
|
gem "byebug", platform: :mri
|
18
|
-
# gem "pry-nav"
|
19
|
+
# gem "pry-nav", platform: :mri
|
19
20
|
gem "pry-byebug", platform: :mri
|
20
21
|
gem "rspec"
|
21
22
|
gem "racksh"
|
@@ -16,11 +16,25 @@ describe Ping::API do
|
|
16
16
|
expect(last_response.body).to match(/PONG/)
|
17
17
|
end
|
18
18
|
|
19
|
+
describe "ActiveSupport::Notifications" do
|
20
|
+
let(:metrics) { Sapience.metrics }
|
21
|
+
let(:tags) { %w(method:get format:json path:/api/ping status:200) }
|
22
|
+
before do
|
23
|
+
Sapience.configure { |c| c.app_name = "grape" }
|
24
|
+
Sapience.add_appender(:datadog)
|
25
|
+
end
|
26
|
+
specify do
|
27
|
+
expect(metrics).to receive(:increment).with("grape.request", tags: tags)
|
28
|
+
expect(metrics).to receive(:timing).with("grape.request.time", kind_of(Float), tags: tags)
|
29
|
+
get "/api/ping"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
19
33
|
it "logs something" do
|
20
34
|
expect(logger).to receive(:info).with(
|
21
35
|
method: "GET",
|
22
36
|
request_path: "/api/ping",
|
23
|
-
format:
|
37
|
+
format: :json,
|
24
38
|
status: 200,
|
25
39
|
class_name: "Ping::API",
|
26
40
|
action: "index",
|
@@ -44,7 +58,7 @@ describe Ping::API do
|
|
44
58
|
expect(logger).to receive(:info).with(
|
45
59
|
method: "GET",
|
46
60
|
request_path: "/api/404",
|
47
|
-
format:
|
61
|
+
format: :json,
|
48
62
|
status: 404,
|
49
63
|
class_name: "Ping::API",
|
50
64
|
action: "index",
|
data/test_apps/rails/Gemfile
CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
|
|
4
4
|
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
5
5
|
gem "rails", "~> 5.0.0"
|
6
6
|
# Use sqlite3 as the database for Active Record
|
7
|
-
gem "
|
7
|
+
gem "pg"
|
8
8
|
# Use Puma as the app server
|
9
9
|
gem "puma", "~> 3.0"
|
10
10
|
# Use Redis adapter to run Action Cable in production
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "application_job"
|
2
|
+
require_relative "../../../../spec/support/file_helper"
|
3
|
+
|
4
|
+
class TestJob < ApplicationJob
|
5
|
+
VERIFICATION_FILE = Rails.root.join("tmp/test_job.verified")
|
6
|
+
queue_as :test_queue
|
7
|
+
include FileHelper
|
8
|
+
|
9
|
+
def perform
|
10
|
+
create_file(VERIFICATION_FILE)
|
11
|
+
end
|
12
|
+
end
|
@@ -3,7 +3,7 @@ require_relative "../../../../spec/support/file_helper"
|
|
3
3
|
class TestWorker
|
4
4
|
QUEUE_NAME = :sneakers_queue
|
5
5
|
ROUTING_KEY = :sneakers_routing_key
|
6
|
-
VERIFICATION_FILE = "tmp/
|
6
|
+
VERIFICATION_FILE = Rails.root.join("tmp/test_worker.verified")
|
7
7
|
|
8
8
|
include Sneakers::Worker
|
9
9
|
include FileHelper
|
@@ -5,21 +5,20 @@
|
|
5
5
|
# gem 'sqlite3'
|
6
6
|
#
|
7
7
|
default: &default
|
8
|
-
adapter:
|
8
|
+
adapter: postgresql
|
9
|
+
host: <%= ENV.fetch('POSTGRES_HOST') { "localhost" } %>
|
10
|
+
username: <%= ENV.fetch('POSTGRES_USER') { `whoami` } %>
|
11
|
+
password: <%= ENV.fetch('POSTGRES_PASSWORD') { nil } %>
|
9
12
|
pool: 5
|
10
13
|
timeout: 5000
|
11
14
|
|
12
15
|
development:
|
13
16
|
<<: *default
|
14
|
-
database:
|
17
|
+
database: rails_app_dev
|
15
18
|
|
16
19
|
# Warning: The database defined as "test" will be erased and
|
17
20
|
# re-generated from your development database when you run "rake".
|
18
21
|
# Do not set this db to the same as development or production.
|
19
22
|
test:
|
20
23
|
<<: *default
|
21
|
-
database:
|
22
|
-
|
23
|
-
production:
|
24
|
-
<<: *default
|
25
|
-
database: db/production.sqlite3
|
24
|
+
database: rails_app_test
|
@@ -1,15 +1,14 @@
|
|
1
1
|
require "serverengine"
|
2
2
|
require "sneakers"
|
3
|
-
|
4
|
-
p ENV.fetch("AMQP") { "amqp://guest:guest@localhost:5672" }
|
3
|
+
require "sapience"
|
5
4
|
|
6
5
|
Sneakers.configure(
|
7
|
-
amqp:
|
8
|
-
exchange_type:
|
9
|
-
log:
|
10
|
-
exchange:
|
11
|
-
durable:
|
12
|
-
ack:
|
13
|
-
metrics:
|
6
|
+
amqp: ENV.fetch("AMQP") { "amqp://guest:guest@localhost:5672" },
|
7
|
+
exchange_type: :direct,
|
8
|
+
log: Sapience[Sneakers], # Log file
|
9
|
+
exchange: "sapience", # AMQP exchange
|
10
|
+
durable: false, # Is queue durable?
|
11
|
+
ack: true, # Must we acknowledge?
|
12
|
+
metrics: Sapience.metrics,
|
13
|
+
heartbeat: nil,
|
14
14
|
)
|
15
|
-
Sapience.logger.level = Logger::DEBUG
|
@@ -3,7 +3,13 @@ class CreatePosts < ActiveRecord::Migration[5.0]
|
|
3
3
|
create_table :posts do |t|
|
4
4
|
t.string :title
|
5
5
|
t.string :body
|
6
|
-
t.belongs_to :author, foreign_key:
|
6
|
+
t.belongs_to :author, foreign_key: {
|
7
|
+
to_table: :users,
|
8
|
+
column: :author_id,
|
9
|
+
name: :posts_author_fk,
|
10
|
+
on_delete: :cascade,
|
11
|
+
on_update: :restrict,
|
12
|
+
}
|
7
13
|
|
8
14
|
t.timestamps
|
9
15
|
end
|
@@ -12,13 +12,16 @@
|
|
12
12
|
|
13
13
|
ActiveRecord::Schema.define(version: 20160902141445) do
|
14
14
|
|
15
|
+
# These are extensions that must be enabled in order to support this database
|
16
|
+
enable_extension "plpgsql"
|
17
|
+
|
15
18
|
create_table "posts", force: :cascade do |t|
|
16
19
|
t.string "title"
|
17
20
|
t.string "body"
|
18
21
|
t.integer "author_id"
|
19
22
|
t.datetime "created_at", null: false
|
20
23
|
t.datetime "updated_at", null: false
|
21
|
-
t.index ["author_id"], name: "index_posts_on_author_id"
|
24
|
+
t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
|
22
25
|
end
|
23
26
|
|
24
27
|
create_table "users", force: :cascade do |t|
|
@@ -26,8 +29,9 @@ ActiveRecord::Schema.define(version: 20160902141445) do
|
|
26
29
|
t.string "email"
|
27
30
|
t.datetime "created_at", null: false
|
28
31
|
t.datetime "updated_at", null: false
|
29
|
-
t.index ["email"], name: "users_email_key", unique: true
|
30
|
-
t.index ["username"], name: "users_username_key", unique: true
|
32
|
+
t.index ["email"], name: "users_email_key", unique: true, using: :btree
|
33
|
+
t.index ["username"], name: "users_username_key", unique: true, using: :btree
|
31
34
|
end
|
32
35
|
|
36
|
+
add_foreign_key "posts", "users", column: "author_id", name: "posts_author_fk", on_update: :restrict, on_delete: :cascade
|
33
37
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
require "timeout"
|
2
2
|
|
3
3
|
class ExternalSneaker
|
4
|
-
attr_accessor :worker_pid, :start_command
|
4
|
+
attr_accessor :worker_pid, :start_command, :workers
|
5
5
|
|
6
|
-
def initialize(start_command)
|
6
|
+
def initialize(start_command, *workers)
|
7
7
|
fail ArgumentError, "start_command was expected" if start_command.nil?
|
8
|
-
|
8
|
+
self.workers = workers.map(&:to_s).join(",")
|
9
9
|
self.start_command = start_command
|
10
10
|
end
|
11
11
|
|
@@ -23,7 +23,7 @@ class ExternalSneaker
|
|
23
23
|
private
|
24
24
|
|
25
25
|
def start_child
|
26
|
-
exec({ "RAILS_ENV" => Rails.env, "WORKERS" =>
|
26
|
+
exec({ "RAILS_ENV" => Rails.env, "WORKERS" => workers }, start_command)
|
27
27
|
end
|
28
28
|
|
29
29
|
def stop_child # rubocop:disable AbcSize
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "sneakers/runner"
|
2
|
+
|
3
|
+
task :environment
|
4
|
+
|
5
|
+
namespace :sneakers do
|
6
|
+
desc "Start processing jobs with all workers"
|
7
|
+
task work: :environment do
|
8
|
+
silence_warnings do
|
9
|
+
Rails.application.eager_load! unless Rails.application.config.eager_load
|
10
|
+
end
|
11
|
+
|
12
|
+
workers = ApplicationJob.subclasses.map do |klass|
|
13
|
+
klass.const_set("Wrapper", Class.new(ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper) do
|
14
|
+
from_queue klass.queue_name
|
15
|
+
end)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
Sneakers::Runner.new(workers).run
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rails_helper"
|
2
|
+
require "serverengine"
|
3
|
+
require "sneakers"
|
4
|
+
require "sneakers/runner"
|
5
|
+
require "external_sneaker"
|
6
|
+
|
7
|
+
describe TestJob do
|
8
|
+
include FileHelper
|
9
|
+
include ActiveJob::TestHelper
|
10
|
+
let(:metrics) { Sapience.add_appender(:datadog) }
|
11
|
+
let(:tags) do
|
12
|
+
%w(name:test queue:test_queue)
|
13
|
+
end
|
14
|
+
let(:message) do
|
15
|
+
{
|
16
|
+
title: "Cool",
|
17
|
+
body: "Hot",
|
18
|
+
}
|
19
|
+
end
|
20
|
+
let(:logger) { Sapience[described_class] }
|
21
|
+
|
22
|
+
after do
|
23
|
+
delete_file("config/sapience.yml")
|
24
|
+
delete_file(described_class::VERIFICATION_FILE)
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO: Possible make this less flaky or run it with retry (rspec-retry)
|
28
|
+
it "runs properly" do
|
29
|
+
expect(metrics).to receive(:increment).with("activejob.perform", tags: tags)
|
30
|
+
expect(metrics).to receive(:timing).with("activejob.perform.time", kind_of(Float), tags: tags)
|
31
|
+
|
32
|
+
perform_enqueued_jobs do
|
33
|
+
TestJob.perform_later
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,5 +1,14 @@
|
|
1
1
|
require "rails_helper"
|
2
2
|
|
3
3
|
RSpec.describe Post, type: :model do
|
4
|
-
|
4
|
+
let!(:metrics) { Sapience.add_appender(:datadog, app_name: "active_record") }
|
5
|
+
let(:tags) { %w(query:post.load) }
|
6
|
+
|
7
|
+
before(:each) { create :post }
|
8
|
+
|
9
|
+
it "records som sql metrics" do
|
10
|
+
expect(metrics).to receive(:increment).with("activerecord.sql", tags: tags)
|
11
|
+
expect(metrics).to receive(:timing).with("activerecord.sql.time", kind_of(Float), tags: tags)
|
12
|
+
Post.first
|
13
|
+
end
|
5
14
|
end
|
@@ -27,11 +27,14 @@ RSpec.configure do |config|
|
|
27
27
|
config.infer_spec_type_from_file_location!
|
28
28
|
config.filter_rails_from_backtrace!
|
29
29
|
config.before(:each) do
|
30
|
-
Sapience.reset!
|
31
30
|
FileUtils.cp(
|
32
31
|
Rails.root.join("spec/fixtures/sapience.yml"),
|
33
32
|
Rails.root.join("config/sapience.yml"),
|
34
33
|
)
|
34
|
+
Sapience.reset!
|
35
|
+
Sapience.configure do |c|
|
36
|
+
c.app_name = "rails_app"
|
37
|
+
end
|
35
38
|
end
|
36
39
|
|
37
40
|
config.after(:each) do
|
@@ -2,10 +2,43 @@ require "rails_helper"
|
|
2
2
|
|
3
3
|
describe "Posts", type: :request do
|
4
4
|
describe "GET /posts" do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
describe "ActiveSupport::Notification" do
|
6
|
+
context "when metrics is configured" do
|
7
|
+
before do
|
8
|
+
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
9
|
+
Rails.logger.level = :debug
|
10
|
+
Sapience.add_appender(:datadog)
|
11
|
+
FactoryGirl.create_list(:post, 10)
|
12
|
+
allow_any_instance_of(Sapience::Extensions::ActiveRecord::Notifications)
|
13
|
+
.to receive(:record).and_return(true)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "records a batch of metrics" do
|
17
|
+
expect(Sapience.metrics).to receive(:increment) do |metric_name, options|
|
18
|
+
expect(metric_name).to eq("rails.request")
|
19
|
+
expect(options[:tags]).to match_array(%w(method:get status:200 action:index controller:posts format:html))
|
20
|
+
end
|
21
|
+
expect(Sapience.metrics).to receive(:timing) do |metric_name, duration, options|
|
22
|
+
expect(metric_name).to eq("rails.request.time")
|
23
|
+
expect(duration).to be_a(Float).and be > 0
|
24
|
+
expect(options[:tags]).to match_array(%w(method:get status:200 action:index controller:posts format:html))
|
25
|
+
end
|
26
|
+
|
27
|
+
expect(Sapience.metrics).to receive(:timing) do |metric_name, duration, options|
|
28
|
+
expect(metric_name).to eq("rails.request.time.db")
|
29
|
+
expect(duration).to be_a(Float).and be > 0
|
30
|
+
expect(options[:tags]).to match_array(%w(method:get status:200 action:index controller:posts format:html))
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(Sapience.metrics).to receive(:timing) do |metric_name, duration, options|
|
34
|
+
expect(metric_name).to eq("rails.request.time.view")
|
35
|
+
expect(duration).to be_a(Float).and be > 0
|
36
|
+
expect(options[:tags]).to match_array(%w(method:get status:200 action:index controller:posts format:html))
|
37
|
+
end
|
38
|
+
|
39
|
+
get posts_path
|
40
|
+
end
|
41
|
+
end
|
9
42
|
end
|
10
43
|
end
|
11
44
|
end
|
@@ -4,7 +4,7 @@ require "sneakers"
|
|
4
4
|
require "sneakers/runner"
|
5
5
|
require "external_sneaker"
|
6
6
|
|
7
|
-
describe TestWorker
|
7
|
+
describe TestWorker do
|
8
8
|
include FileHelper
|
9
9
|
let(:message) do
|
10
10
|
{
|
@@ -12,12 +12,10 @@ describe TestWorker, "This is manual labor as we can't verify that sneakers is r
|
|
12
12
|
body: "Hot",
|
13
13
|
}
|
14
14
|
end
|
15
|
-
let(:logger) { Sapience[described_class] }
|
16
15
|
|
17
16
|
before do
|
18
|
-
@sneakers_worker = ExternalSneaker.new("rake sneakers:run")
|
17
|
+
@sneakers_worker = ExternalSneaker.new("rake sneakers:run", described_class)
|
19
18
|
@sneakers_worker.start
|
20
|
-
|
21
19
|
Sneakers.publish(
|
22
20
|
message.to_json,
|
23
21
|
to_queue: described_class::QUEUE_NAME,
|
@@ -30,7 +28,14 @@ describe TestWorker, "This is manual labor as we can't verify that sneakers is r
|
|
30
28
|
delete_file(described_class::VERIFICATION_FILE)
|
31
29
|
end
|
32
30
|
|
31
|
+
# TODO: Possible make this less flaky or run it with retry (rspec-retry)
|
33
32
|
it "runs properly" do
|
34
|
-
|
33
|
+
count = 0
|
34
|
+
until File.exist?(described_class::VERIFICATION_FILE)
|
35
|
+
sleep 0.1
|
36
|
+
count += 1
|
37
|
+
expect(true).to be(false) if count > 240
|
38
|
+
end
|
39
|
+
expect(true).to be(true)
|
35
40
|
end
|
36
41
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sapience
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikael Henriksson
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-09-
|
12
|
+
date: 2016-09-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: concurrent-ruby
|
@@ -289,14 +289,19 @@ files:
|
|
289
289
|
- lib/sapience/extensions/action_cable/tagged_logger_proxy.rb
|
290
290
|
- lib/sapience/extensions/action_controller/live.rb
|
291
291
|
- lib/sapience/extensions/action_controller/log_subscriber.rb
|
292
|
+
- lib/sapience/extensions/action_controller/notifications.rb
|
292
293
|
- lib/sapience/extensions/action_dispatch/debug_exceptions.rb
|
293
294
|
- lib/sapience/extensions/action_view/log_subscriber.rb
|
294
295
|
- lib/sapience/extensions/action_view/streaming_template_renderer.rb
|
295
296
|
- lib/sapience/extensions/active_job/logging.rb
|
297
|
+
- lib/sapience/extensions/active_job/notifications.rb
|
296
298
|
- lib/sapience/extensions/active_model_serializers/logging.rb
|
297
299
|
- lib/sapience/extensions/active_record/log_subscriber.rb
|
300
|
+
- lib/sapience/extensions/active_record/notifications.rb
|
298
301
|
- lib/sapience/extensions/grape/middleware/logging.rb
|
302
|
+
- lib/sapience/extensions/grape/notifications.rb
|
299
303
|
- lib/sapience/extensions/grape/timings.rb
|
304
|
+
- lib/sapience/extensions/notifications.rb
|
300
305
|
- lib/sapience/extensions/rails/rack/logger.rb
|
301
306
|
- lib/sapience/extensions/rails/rack/logger_info_as_debug.rb
|
302
307
|
- lib/sapience/formatters/base.rb
|
@@ -353,6 +358,7 @@ files:
|
|
353
358
|
- test_apps/rails/app/helpers/application_helper.rb
|
354
359
|
- test_apps/rails/app/helpers/posts_helper.rb
|
355
360
|
- test_apps/rails/app/jobs/application_job.rb
|
361
|
+
- test_apps/rails/app/jobs/test_job.rb
|
356
362
|
- test_apps/rails/app/mailers/application_mailer.rb
|
357
363
|
- test_apps/rails/app/models/application_record.rb
|
358
364
|
- test_apps/rails/app/models/concerns/.keep
|
@@ -404,6 +410,7 @@ files:
|
|
404
410
|
- test_apps/rails/lib/assets/.keep
|
405
411
|
- test_apps/rails/lib/external_sneaker.rb
|
406
412
|
- test_apps/rails/lib/tasks/.keep
|
413
|
+
- test_apps/rails/lib/tasks/sneakers.rake
|
407
414
|
- test_apps/rails/public/404.html
|
408
415
|
- test_apps/rails/public/422.html
|
409
416
|
- test_apps/rails/public/500.html
|
@@ -417,6 +424,7 @@ files:
|
|
417
424
|
- test_apps/rails/spec/fixtures/sapience.yml
|
418
425
|
- test_apps/rails/spec/helpers/posts_helper_spec.rb
|
419
426
|
- test_apps/rails/spec/integration/sapience_spec.rb
|
427
|
+
- test_apps/rails/spec/jobs/test_job_spec.rb
|
420
428
|
- test_apps/rails/spec/models/post_spec.rb
|
421
429
|
- test_apps/rails/spec/models/user_spec.rb
|
422
430
|
- test_apps/rails/spec/rails_helper.rb
|