honeycomb-beeline 1.0.0.pre.alpha → 1.0.0.pre.alpha1
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 +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
|