sapience 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|