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.
@@ -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