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