honeycomb-beeline 1.0.0.pre.alpha → 1.0.0.pre.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +13 -0
- data/.overcommit.yml +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +56 -0
- data/.ruby-version +1 -0
- data/.travis.yml +48 -0
- data/Appraisals +61 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +126 -0
- data/LICENSE +201 -0
- data/README.md +37 -0
- data/Rakefile +16 -0
- data/UPGRADING.md +82 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bundler_version.sh +12 -0
- data/honeycomb-beeline.gemspec +54 -0
- data/lib/generators/honeycomb/honeycomb_generator.rb +37 -0
- data/lib/honeycomb-beeline.rb +74 -0
- data/lib/honeycomb/beeline/version.rb +9 -0
- data/lib/honeycomb/client.rb +79 -0
- data/lib/honeycomb/context.rb +40 -0
- data/lib/honeycomb/integrations/active_support.rb +68 -0
- data/lib/honeycomb/integrations/faraday.rb +57 -0
- data/lib/honeycomb/integrations/rack.rb +94 -0
- data/lib/honeycomb/integrations/rails.rb +47 -0
- data/lib/honeycomb/integrations/sequel.rb +31 -0
- data/lib/honeycomb/integrations/sinatra.rb +18 -0
- data/lib/honeycomb/propagation.rb +58 -0
- data/lib/honeycomb/span.rb +125 -0
- data/lib/honeycomb/trace.rb +40 -0
- data/lib/sequel/extensions/honeycomb.rb +3 -0
- metadata +37 -3
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/notifications"
|
4
|
+
|
5
|
+
module Honeycomb
|
6
|
+
module ActiveSupport
|
7
|
+
##
|
8
|
+
# Included in the configuration object to specify events that should be
|
9
|
+
# subscribed to
|
10
|
+
module Configuration
|
11
|
+
attr_accessor :notification_events
|
12
|
+
|
13
|
+
def after_initialize(client)
|
14
|
+
super(client) if defined?(super)
|
15
|
+
|
16
|
+
events = notification_events || []
|
17
|
+
ActiveSupport::Subscriber.new(client: client).tap do |sub|
|
18
|
+
events.each do |event|
|
19
|
+
sub.subscribe(event) do |span, payload|
|
20
|
+
payload.each do |key, value|
|
21
|
+
span.add_field("#{event}.#{key}", value.to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Handles ActiveSupport::Notification subscriptions, relaying them to a
|
30
|
+
# Honeycomb client
|
31
|
+
class Subscriber
|
32
|
+
def initialize(client:)
|
33
|
+
@client = client
|
34
|
+
@handlers = {}
|
35
|
+
@key = ["honeycomb", self.class.name, object_id].join("-")
|
36
|
+
end
|
37
|
+
|
38
|
+
def subscribe(event, &block)
|
39
|
+
return unless block_given?
|
40
|
+
|
41
|
+
handlers[event] = block
|
42
|
+
::ActiveSupport::Notifications.subscribe(event, self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def start(name, id, _payload)
|
46
|
+
spans[id] << client.start_span(name: name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def finish(name, id, payload)
|
50
|
+
return unless (span = spans[id].pop)
|
51
|
+
|
52
|
+
handlers[name].call(span, payload)
|
53
|
+
|
54
|
+
span.send
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :key, :client, :handlers
|
60
|
+
|
61
|
+
def spans
|
62
|
+
Thread.current[key] ||= Hash.new { |h, id| h[id] = [] }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Honeycomb::Configuration.include Honeycomb::ActiveSupport::Configuration
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
|
5
|
+
module Honeycomb
|
6
|
+
# Faraday middleware to create spans around outgoing http requests
|
7
|
+
class Faraday < ::Faraday::Middleware
|
8
|
+
def initialize(app, client:)
|
9
|
+
super(app)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
@client.start_span(name: "http_client") do |span|
|
15
|
+
span.add_field "request.method", env.method.upcase
|
16
|
+
span.add_field "request.protocol", env.url.scheme
|
17
|
+
span.add_field "request.host", env.url.host
|
18
|
+
span.add_field "request.path", env.url.path
|
19
|
+
span.add_field "meta.type", "http_client"
|
20
|
+
span.add_field "meta.package", "faraday"
|
21
|
+
span.add_field "meta.package_version", ::Faraday::VERSION
|
22
|
+
|
23
|
+
env.request_headers["X-Honeycomb-Trace"] = span.to_trace_header
|
24
|
+
|
25
|
+
@app.call(env).tap do |response|
|
26
|
+
span.add_field "response.status_code", response.status
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
::Faraday::Connection.module_eval do
|
34
|
+
alias_method :standard_initialize, :initialize
|
35
|
+
|
36
|
+
def initialize(url = nil, options = nil, &block)
|
37
|
+
standard_initialize(url, options, &block)
|
38
|
+
|
39
|
+
return if @builder.handlers.include? Honeycomb::Faraday
|
40
|
+
|
41
|
+
adapter_index = @builder.handlers.find_index do |handler|
|
42
|
+
handler.klass.ancestors.include? Faraday::Adapter
|
43
|
+
end
|
44
|
+
|
45
|
+
if adapter_index
|
46
|
+
@builder.insert_before(
|
47
|
+
adapter_index,
|
48
|
+
Honeycomb::Faraday,
|
49
|
+
client: Honeycomb.client,
|
50
|
+
)
|
51
|
+
else
|
52
|
+
@builder.use(Honeycomb::Faraday, client: Honeycomb.client)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
Faraday::Middleware.register_middleware honeycomb: -> { Honeycomb::Faraday }
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
|
5
|
+
module Honeycomb
|
6
|
+
# Automatically capture rack requests and create a trace
|
7
|
+
class Rack
|
8
|
+
RACK_FIELDS = [
|
9
|
+
["REQUEST_METHOD", "request.method"],
|
10
|
+
["PATH_INFO", "request.path"],
|
11
|
+
["QUERY_STRING", "request.query_string"],
|
12
|
+
["HTTP_VERSION", "request.http_version"],
|
13
|
+
["HTTP_HOST", "request.host"],
|
14
|
+
["REMOTE_ADDR", "request.remote_addr"],
|
15
|
+
["HTTP_USER_AGENT", "request.header.user_agent"],
|
16
|
+
["rack.url_scheme", "request.protocol"],
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
COMMON_USER_FIELDS = %i[
|
20
|
+
email
|
21
|
+
name
|
22
|
+
first_name
|
23
|
+
last_name
|
24
|
+
created_at
|
25
|
+
id
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
SCOPE_PATTERN = /^warden\.user\.([^.]+)\.key$/.freeze
|
29
|
+
|
30
|
+
attr_reader :app, :client
|
31
|
+
|
32
|
+
def initialize(app, client:)
|
33
|
+
@app = app
|
34
|
+
@client = client
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
hny = env["HTTP_X_HONEYCOMB_TRACE"]
|
39
|
+
client.start_span(name: "http_request", serialized_trace: hny) do |span|
|
40
|
+
add_field = lambda do |key, value|
|
41
|
+
next unless value && !value.empty?
|
42
|
+
|
43
|
+
span.add_field(key, value)
|
44
|
+
end
|
45
|
+
|
46
|
+
extract_fields(env, RACK_FIELDS, &add_field)
|
47
|
+
|
48
|
+
status, headers, body = app.call(env)
|
49
|
+
|
50
|
+
add_package_information(env, &add_field)
|
51
|
+
|
52
|
+
extract_user_information(env, &add_field)
|
53
|
+
|
54
|
+
span.add_field("response.status_code", status)
|
55
|
+
|
56
|
+
[status, headers, body]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_package_information(_env)
|
61
|
+
yield "meta.package", "rack"
|
62
|
+
yield "meta.package_version", ::Rack::VERSION.join(".")
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_fields(env, fields)
|
66
|
+
fields.each do |key, value|
|
67
|
+
yield value, env[key]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def extract_user_information(env)
|
72
|
+
warden = env["warden"]
|
73
|
+
|
74
|
+
return unless warden
|
75
|
+
|
76
|
+
session = env["rack.session"] || {}
|
77
|
+
keys = session.keys.select do |key|
|
78
|
+
key.match(SCOPE_PATTERN)
|
79
|
+
end
|
80
|
+
warden_scopes = keys.map do |key|
|
81
|
+
key.gsub(SCOPE_PATTERN, "\1")
|
82
|
+
end
|
83
|
+
best_scope = warden_scopes.include?("user") ? "user" : warden_scopes.first
|
84
|
+
|
85
|
+
return unless best_scope
|
86
|
+
|
87
|
+
env["warden"].user(scope: best_scope, run_callbacks: false).tap do |user|
|
88
|
+
COMMON_USER_FIELDS.each do |field|
|
89
|
+
user.respond_to?(field) && yield("user.#{field}", user.send(field))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "honeycomb/integrations/active_support"
|
5
|
+
require "honeycomb/integrations/rack"
|
6
|
+
|
7
|
+
module Honeycomb
|
8
|
+
# Add Rails specific information to the Honeycomb::Rack middleware
|
9
|
+
module Rails
|
10
|
+
def add_package_information(env)
|
11
|
+
yield "meta.package", "rails"
|
12
|
+
yield "meta.package_version", ::Rails::VERSION::STRING
|
13
|
+
|
14
|
+
::ActionDispatch::Request.new(env).tap do |request|
|
15
|
+
yield "request.controller", request.params[:controller]
|
16
|
+
yield "request.action", request.params[:action]
|
17
|
+
|
18
|
+
break unless request.respond_to? :routes
|
19
|
+
break unless request.routes.respond_to? :router
|
20
|
+
|
21
|
+
found_route = false
|
22
|
+
request.routes.router.recognize(request) do |route, _|
|
23
|
+
break if found_route
|
24
|
+
|
25
|
+
found_route = true
|
26
|
+
yield "request.route", "#{env['REQUEST_METHOD']} #{route.path.spec}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Automatically capture rack requests and create a trace
|
33
|
+
class Railtie < ::Rails::Railtie
|
34
|
+
initializer "honeycomb.install_middleware", after: :load_config_initializers do |app|
|
35
|
+
if Honeycomb.client
|
36
|
+
# what location should we insert the middleware at?
|
37
|
+
app.config.middleware.insert_before(
|
38
|
+
::Rails::Rack::Logger,
|
39
|
+
Honeycomb::Rack,
|
40
|
+
client: Honeycomb.client,
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Honeycomb::Rack.prepend Honeycomb::Rails
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sequel"
|
4
|
+
|
5
|
+
module Honeycomb
|
6
|
+
# Wrap sequel commands in a span
|
7
|
+
module Sequel
|
8
|
+
def honeycomb_client
|
9
|
+
@honeycomb_client || Honeycomb.client
|
10
|
+
end
|
11
|
+
|
12
|
+
def honeycomb_client=(client)
|
13
|
+
@honeycomb_client = client
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_connection_yield(sql, conn, args = nil)
|
17
|
+
return super if honeycomb_client.nil?
|
18
|
+
|
19
|
+
honeycomb_client.start_span(name: sql.sub(/\s+.*/, "").upcase) do |span|
|
20
|
+
span.add_field "meta.package", "sequel"
|
21
|
+
span.add_field "meta.package_version", ::Sequel::VERSION
|
22
|
+
span.add_field "type", "db"
|
23
|
+
span.add_field "db.sql", sql
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Sequel::Database.register_extension(:honeycomb, Honeycomb::Sequel)
|
31
|
+
Sequel::Database.extension :honeycomb
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sinatra"
|
4
|
+
require "honeycomb/integrations/rack"
|
5
|
+
|
6
|
+
module Honeycomb
|
7
|
+
# Add Sinatra specific information to the Honeycomb::Rack middleware
|
8
|
+
module Sinatra
|
9
|
+
def add_package_information(env)
|
10
|
+
yield "meta.package", "sinatra"
|
11
|
+
yield "meta.package_version", ::Sinatra::VERSION
|
12
|
+
|
13
|
+
yield "request.route", env["sinatra.route"]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Honeycomb::Rack.prepend Honeycomb::Sinatra
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Honeycomb
|
7
|
+
# Parse trace headers
|
8
|
+
module PropagationParser
|
9
|
+
def parse(serialized_trace)
|
10
|
+
unless serialized_trace.nil?
|
11
|
+
version, payload = serialized_trace.split(";", 2)
|
12
|
+
|
13
|
+
if version == "1"
|
14
|
+
trace_id, parent_span_id, trace_fields, dataset = parse_v1(payload)
|
15
|
+
|
16
|
+
if !trace_id.nil? && !parent_span_id.nil?
|
17
|
+
return [trace_id, parent_span_id, trace_fields, dataset]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
[nil, nil, nil, nil]
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_v1(payload)
|
26
|
+
trace_id, parent_span_id, trace_fields, dataset = nil
|
27
|
+
payload.split(",").each do |entry|
|
28
|
+
key, value = entry.split("=", 2)
|
29
|
+
case key
|
30
|
+
when "dataset"
|
31
|
+
dataset = value
|
32
|
+
when "trace_id"
|
33
|
+
trace_id = value
|
34
|
+
when "parent_id"
|
35
|
+
parent_span_id = value
|
36
|
+
when "context"
|
37
|
+
Base64.decode64(value).tap do |json|
|
38
|
+
begin
|
39
|
+
trace_fields = JSON.parse json
|
40
|
+
rescue JSON::ParserError
|
41
|
+
trace_fields = {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
[trace_id, parent_span_id, trace_fields, dataset]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Serialize trace headers
|
52
|
+
module PropagationSerializer
|
53
|
+
def to_trace_header
|
54
|
+
context = Base64.urlsafe_encode64(JSON.generate(trace.fields)).strip
|
55
|
+
"1;trace_id=#{trace.id},parent_id=#{id},context=#{context}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
require "honeycomb/propagation"
|
5
|
+
|
6
|
+
module Honeycomb
|
7
|
+
# Represents a Honeycomb span, which wraps a Honeycomb event and adds specific
|
8
|
+
# tracing functionality
|
9
|
+
class Span
|
10
|
+
include PropagationSerializer
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators :@event, :add_field, :add
|
14
|
+
|
15
|
+
attr_reader :id, :trace
|
16
|
+
|
17
|
+
def initialize(trace:,
|
18
|
+
builder:,
|
19
|
+
context:,
|
20
|
+
parent_id: nil,
|
21
|
+
is_root: parent_id.nil?)
|
22
|
+
@id = SecureRandom.uuid
|
23
|
+
@context = context
|
24
|
+
@context.current_span = self
|
25
|
+
@builder = builder
|
26
|
+
@event = builder.event
|
27
|
+
@trace = trace
|
28
|
+
@parent_id = parent_id
|
29
|
+
@is_root = is_root
|
30
|
+
@rollup_fields = Hash.new(0)
|
31
|
+
@children = []
|
32
|
+
@sent = false
|
33
|
+
@started = clock_time
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_rollup_field(key, value)
|
37
|
+
trace.add_rollup_field(key, value)
|
38
|
+
rollup_fields[key] += value
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_trace_field(key, value)
|
42
|
+
trace.add_field(key, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_child
|
46
|
+
self.class.new(trace: trace,
|
47
|
+
builder: builder,
|
48
|
+
context: context,
|
49
|
+
parent_id: id).tap do |c|
|
50
|
+
children << c
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def send
|
55
|
+
return if sent?
|
56
|
+
|
57
|
+
send_internal
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def send_by_parent
|
63
|
+
return if sent?
|
64
|
+
|
65
|
+
add_field "meta.sent_by_parent", true
|
66
|
+
send_internal
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
attr_reader :rollup_fields,
|
72
|
+
:event,
|
73
|
+
:parent_id,
|
74
|
+
:children,
|
75
|
+
:builder,
|
76
|
+
:context
|
77
|
+
|
78
|
+
def sent?
|
79
|
+
@sent
|
80
|
+
end
|
81
|
+
|
82
|
+
def root?
|
83
|
+
@is_root
|
84
|
+
end
|
85
|
+
|
86
|
+
def send_internal
|
87
|
+
add_field "duration_ms", duration_ms
|
88
|
+
add_field "trace.trace_id", trace.id
|
89
|
+
add_field "trace.span_id", id
|
90
|
+
add_field "meta.span_type", span_type
|
91
|
+
parent_id && add_field("trace.parent_id", parent_id)
|
92
|
+
add rollup_fields
|
93
|
+
add trace.fields
|
94
|
+
span_type == "root" && add(trace.rollup_fields)
|
95
|
+
send_children
|
96
|
+
event.send
|
97
|
+
@sent = true
|
98
|
+
context.span_sent(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
def send_children
|
102
|
+
children.each do |child|
|
103
|
+
child.send_by_parent
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def duration_ms
|
108
|
+
(clock_time - @started) * 1000
|
109
|
+
end
|
110
|
+
|
111
|
+
def clock_time
|
112
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
113
|
+
end
|
114
|
+
|
115
|
+
def span_type
|
116
|
+
if root?
|
117
|
+
parent_id.nil? ? "root" : "subroot"
|
118
|
+
elsif children.empty?
|
119
|
+
"leaf"
|
120
|
+
else
|
121
|
+
"mid"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|