honeycomb-beeline 1.3.0 → 2.2.0

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.
@@ -106,8 +106,12 @@ module Honeycomb
106
106
  "aws.region" => context.config.region,
107
107
  "aws.service" => context.client.class.identifier,
108
108
  "aws.operation" => context.operation_name,
109
- "aws.params" => context.params,
110
109
  }
110
+
111
+ context.params && context.params.each do |key, value|
112
+ context[:honeycomb_aws_sdk_data]["aws.params.#{key}"] = value
113
+ end
114
+
111
115
  span.add context[:honeycomb_aws_sdk_data]
112
116
  end
113
117
 
@@ -15,7 +15,7 @@ module Honeycomb
15
15
 
16
16
  @client.start_span(name: "http_client") do |span|
17
17
  span.add_field "request.method", env.method.upcase
18
- span.add_field "request.protocol", env.url.scheme
18
+ span.add_field "request.scheme", env.url.scheme
19
19
  span.add_field "request.host", env.url.host
20
20
  span.add_field "request.path", env.url.path
21
21
  span.add_field "meta.type", "http_client"
@@ -13,8 +13,14 @@ module Honeycomb
13
13
  ["HTTP_VERSION", "request.http_version"],
14
14
  ["HTTP_HOST", "request.host"],
15
15
  ["REMOTE_ADDR", "request.remote_addr"],
16
+ ["HTTP_X_FORWARDED_FOR", "request.header.x_forwarded_for"],
17
+ ["HTTP_X_FORWARDED_PROTO", "request.header.x_forwarded_proto"],
18
+ ["HTTP_X_FORWARDED_PORT", "request.header.x_forwarded_port"],
19
+ ["HTTP_ACCEPT", "request.header.accept"],
20
+ ["HTTP_ACCEPT_LANGUAGE", "request.header.accept_language"],
21
+ ["CONTENT_TYPE", "request.header.content_type"],
16
22
  ["HTTP_USER_AGENT", "request.header.user_agent"],
17
- ["rack.url_scheme", "request.protocol"],
23
+ ["rack.url_scheme", "request.scheme"],
18
24
  ].freeze
19
25
 
20
26
  attr_reader :app, :client
@@ -25,16 +31,20 @@ module Honeycomb
25
31
  end
26
32
 
27
33
  def call(env)
34
+ req = ::Rack::Request.new(env)
28
35
  hny = env["HTTP_X_HONEYCOMB_TRACE"]
29
36
  client.start_span(name: "http_request", serialized_trace: hny) do |span|
30
37
  add_field = lambda do |key, value|
31
- next unless value && !value.empty?
32
-
33
- span.add_field(key, value)
38
+ unless value.nil? || (value.respond_to?(:empty?) && value.empty?)
39
+ span.add_field(key, value)
40
+ end
34
41
  end
35
42
 
36
43
  extract_fields(env, RACK_FIELDS, &add_field)
37
44
 
45
+ span.add_field("request.secure", req.ssl?)
46
+ span.add_field("request.xhr", req.xhr?)
47
+
38
48
  status, headers, body = app.call(env)
39
49
 
40
50
  add_package_information(env, &add_field)
@@ -42,6 +52,7 @@ module Honeycomb
42
52
  extract_user_information(env, &add_field)
43
53
 
44
54
  span.add_field("response.status_code", status)
55
+ span.add_field("response.content_type", headers["Content-Type"])
45
56
 
46
57
  [status, headers, body]
47
58
  end
@@ -11,29 +11,79 @@ module Honeycomb
11
11
  yield "meta.package", "rails"
12
12
  yield "meta.package_version", ::Rails::VERSION::STRING
13
13
 
14
- ::ActionDispatch::Request.new(env).tap do |request|
15
- # calling request.params will blow up if raw_post is nil
16
- # the only known cause of this is when using the
17
- # [twirp](https://github.com/twitchtv/twirp-ruby) rack app mounted in
18
- # the rails app
19
- if request.raw_post
20
- yield "request.controller", request.params[:controller]
21
- yield "request.action", request.params[:action]
22
- end
23
-
24
- break unless request.respond_to? :routes
25
- break unless request.routes.respond_to? :router
26
-
27
- found_route = false
28
- request.routes.router.recognize(request) do |route, _|
29
- break if found_route
30
-
31
- found_route = true
32
- yield "request.route", "#{env['REQUEST_METHOD']} #{route.path.spec}"
33
- end
14
+ request = ::ActionDispatch::Request.new(env)
15
+
16
+ yield "request.controller", request.path_parameters[:controller]
17
+ yield "request.action", request.path_parameters[:action]
18
+ yield "request.route", route_for(request)
19
+ end
20
+
21
+ private
22
+
23
+ def route_for(request)
24
+ router = router_for(request)
25
+ routing = routing_for(request)
26
+
27
+ return unless router && routing
28
+
29
+ router.recognize(routing) do |route, _|
30
+ return "#{request.method} #{route.path.spec}"
34
31
  end
35
32
  end
36
33
 
34
+ # Broadly compatible way of getting the ActionDispatch::Routing::RouteSet.
35
+ #
36
+ # While we'd like to just use ActionDispatch::Request#routes, that method
37
+ # was only added circa Rails 5. To support Rails 4, we have to use direct
38
+ # Rack env access.
39
+ #
40
+ # @see https://github.com/rails/rails/commit/87a75910640b83a677099198ccb3317d9850c204
41
+ def router_for(request)
42
+ routes = request.env["action_dispatch.routes"]
43
+ routes.router if routes.respond_to?(:router)
44
+ end
45
+
46
+ # Constructs a simplified ActionDispatch::Request with the original route.
47
+ #
48
+ # This is based on ActionDispatch::Routing::RouteSet#recognize_path, which
49
+ # reconstructs an ActionDispatch::Request using a given HTTP method + path
50
+ # by making a mock Rack environment. Here, instead of taking the method +
51
+ # path from input parameters, we use the original values from the actual
52
+ # incoming request (prior to any mangling that may have been done by
53
+ # middleware).
54
+ #
55
+ # The resulting ActionDispatch::Request instance is suitable for passing to
56
+ # ActionDispatch::Journey::Router#recognize to get the original Rails
57
+ # routing information corresponding to the incoming request.
58
+ #
59
+ # @param request [ActionDispatch::Request]
60
+ # the actual incoming Rails request
61
+ #
62
+ # @return [ActionDispatch::Request]
63
+ # a simplified version of the incoming request that retains the original
64
+ # routing information, but nothing else (e.g., no HTTP parameters)
65
+ #
66
+ # @return [nil]
67
+ # if the original request's path is invalid
68
+ #
69
+ # @see https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-method
70
+ # @see https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-original_fullpath
71
+ # @see https://github.com/rails/rails/blob/2a44ff12c858d296797963f7aa97abfa0c840a15/actionpack/lib/action_dispatch/journey/router/utils.rb#L7-L27
72
+ # @see https://github.com/rails/rails/blob/2a44ff12c858d296797963f7aa97abfa0c840a15/actionpack/lib/action_dispatch/routing/route_set.rb#L846-L859
73
+ def routing_for(request)
74
+ verb = request.method
75
+ path = request.original_fullpath
76
+ path = normalize(path) unless path =~ %r{://}
77
+ env = ::Rack::MockRequest.env_for(path, method: verb)
78
+ ::ActionDispatch::Request.new(env)
79
+ rescue URI::InvalidURIError
80
+ nil
81
+ end
82
+
83
+ def normalize(path)
84
+ ::ActionDispatch::Journey::Router::Utils.normalize_path(path)
85
+ end
86
+
37
87
  # Rails middleware
38
88
  class Middleware
39
89
  include Rack
@@ -9,9 +9,8 @@ module Honeycomb
9
9
  initializer("honeycomb.install_middleware",
10
10
  after: :load_config_initializers) do |app|
11
11
  if Honeycomb.client
12
- # what location should we insert the middleware at?
13
- app.config.middleware.insert_before(
14
- ::Rails::Rack::Logger,
12
+ app.config.middleware.insert_after(
13
+ ActionDispatch::ShowExceptions,
15
14
  Honeycomb::Rails::Middleware,
16
15
  client: Honeycomb.client,
17
16
  )
@@ -23,7 +23,9 @@ module Honeycomb
23
23
  attr_writer :honeycomb_client
24
24
 
25
25
  def honeycomb_client
26
- @honeycomb_client || Honeycomb.client
26
+ return @honeycomb_client if defined?(@honeycomb_client)
27
+
28
+ Honeycomb.client
27
29
  end
28
30
  end
29
31
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Honeycomb
4
- # Methods for extracing common warden/devise fields from a rack env hash
4
+ # Methods for extracting common warden/devise fields from a rack env hash
5
5
  module Warden
6
6
  COMMON_USER_FIELDS = %i[
7
7
  email
@@ -24,7 +24,7 @@ module Honeycomb
24
24
  key.match(SCOPE_PATTERN)
25
25
  end
26
26
  warden_scopes = keys.map do |key|
27
- key.gsub(SCOPE_PATTERN, "\1")
27
+ key.gsub(SCOPE_PATTERN, "\\1")
28
28
  end
29
29
  best_scope = warden_scopes.include?("user") ? "user" : warden_scopes.first
30
30
 
@@ -4,63 +4,16 @@ require "base64"
4
4
  require "json"
5
5
  require "uri"
6
6
 
7
+ require "honeycomb/propagation/honeycomb"
8
+
7
9
  module Honeycomb
8
10
  # Parse trace headers
9
11
  module PropagationParser
10
- def parse(serialized_trace)
11
- unless serialized_trace.nil?
12
- version, payload = serialized_trace.split(";", 2)
13
-
14
- if version == "1"
15
- trace_id, parent_span_id, trace_fields, dataset = parse_v1(payload)
16
-
17
- if !trace_id.nil? && !parent_span_id.nil?
18
- return [trace_id, parent_span_id, trace_fields, dataset]
19
- end
20
- end
21
- end
22
-
23
- [nil, nil, nil, nil]
24
- end
25
-
26
- def parse_v1(payload)
27
- trace_id, parent_span_id, trace_fields, dataset = nil
28
- payload.split(",").each do |entry|
29
- key, value = entry.split("=", 2)
30
- case key
31
- when "dataset"
32
- dataset = URI.decode_www_form_component(value)
33
- when "trace_id"
34
- trace_id = value
35
- when "parent_id"
36
- parent_span_id = value
37
- when "context"
38
- Base64.decode64(value).tap do |json|
39
- begin
40
- trace_fields = JSON.parse json
41
- rescue JSON::ParserError
42
- trace_fields = {}
43
- end
44
- end
45
- end
46
- end
47
-
48
- [trace_id, parent_span_id, trace_fields, dataset]
49
- end
12
+ include HoneycombPropagation::UnmarshalTraceContext
50
13
  end
51
14
 
52
15
  # Serialize trace headers
53
16
  module PropagationSerializer
54
- def to_trace_header
55
- context = Base64.urlsafe_encode64(JSON.generate(trace.fields)).strip
56
- encoded_dataset = URI.encode_www_form_component(builder.dataset)
57
- data_to_propogate = [
58
- "dataset=#{encoded_dataset}",
59
- "trace_id=#{trace.id}",
60
- "parent_id=#{id}",
61
- "context=#{context}",
62
- ]
63
- "1;#{data_to_propogate.join(',')}"
64
- end
17
+ include HoneycombPropagation::MarshalTraceContext
65
18
  end
66
19
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeycomb
4
+ # Parsing and propagation for AWS trace headers
5
+ module AWSPropagation
6
+ # Parse trace headers
7
+ module UnmarshalTraceContext
8
+ def parse(serialized_trace)
9
+ unless serialized_trace.nil?
10
+ split = serialized_trace.split(";")
11
+
12
+ trace_id, parent_span_id, trace_fields = get_fields(split)
13
+
14
+ parent_span_id = trace_id if parent_span_id.nil?
15
+
16
+ trace_fields = nil if trace_fields.empty?
17
+
18
+ if !trace_id.nil? && !parent_span_id.nil?
19
+ # return nil for dataset
20
+ return [trace_id, parent_span_id, trace_fields, nil]
21
+ end
22
+ end
23
+
24
+ [nil, nil, nil, nil]
25
+ end
26
+
27
+ def get_fields(fields)
28
+ trace_id, parent_span_id = nil
29
+ trace_fields = {}
30
+ fields.each do |entry|
31
+ key, value = entry.split("=", 2)
32
+ case key.downcase
33
+ when "root"
34
+ trace_id = value
35
+ when "self"
36
+ parent_span_id = value
37
+ when "parent"
38
+ parent_span_id = value if parent_span_id.nil?
39
+ else
40
+ trace_fields[key] = value unless key.empty?
41
+ end
42
+ end
43
+
44
+ [trace_id, parent_span_id, trace_fields]
45
+ end
46
+ end
47
+
48
+ # Serialize trace headers
49
+ module MarshalTraceContext
50
+ def to_trace_header
51
+ context = [""]
52
+ unless trace.fields.keys.nil?
53
+ trace.fields.keys.each do |key|
54
+ context.push("#{key}=#{trace.fields[key]}")
55
+ end
56
+ end
57
+
58
+ data_to_propagate = [
59
+ "Root=#{trace.id}",
60
+ "Parent=#{id}",
61
+ ]
62
+ "#{data_to_propagate.join(';')}#{context.join(';')}"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module Honeycomb
8
+ # Parsing and propagation for honeycomb trace headers
9
+ module HoneycombPropagation
10
+ # Parse trace headers
11
+ module UnmarshalTraceContext
12
+ def parse(serialized_trace)
13
+ unless serialized_trace.nil?
14
+ version, payload = serialized_trace.split(";", 2)
15
+
16
+ if version == "1"
17
+ trace_id, parent_span_id, trace_fields, dataset = parse_v1(payload)
18
+
19
+ if !trace_id.nil? && !parent_span_id.nil?
20
+ return [trace_id, parent_span_id, trace_fields, dataset]
21
+ end
22
+ end
23
+ end
24
+
25
+ [nil, nil, nil, nil]
26
+ end
27
+
28
+ def parse_v1(payload)
29
+ trace_id, parent_span_id, trace_fields, dataset = nil
30
+ payload.split(",").each do |entry|
31
+ key, value = entry.split("=", 2)
32
+ case key.downcase
33
+ when "dataset"
34
+ dataset = URI.decode_www_form_component(value)
35
+ when "trace_id"
36
+ trace_id = value
37
+ when "parent_id"
38
+ parent_span_id = value
39
+ when "context"
40
+ Base64.decode64(value).tap do |json|
41
+ begin
42
+ trace_fields = JSON.parse json
43
+ rescue JSON::ParserError
44
+ trace_fields = {}
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ [trace_id, parent_span_id, trace_fields, dataset]
51
+ end
52
+ end
53
+
54
+ # Serialize trace headers
55
+ module MarshalTraceContext
56
+ def to_trace_header
57
+ context = Base64.urlsafe_encode64(JSON.generate(trace.fields)).strip
58
+ encoded_dataset = URI.encode_www_form_component(builder.dataset)
59
+ data_to_propogate = [
60
+ "dataset=#{encoded_dataset}",
61
+ "trace_id=#{trace.id}",
62
+ "parent_id=#{id}",
63
+ "context=#{context}",
64
+ ]
65
+ "1;#{data_to_propogate.join(',')}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeycomb
4
+ # Parsing and propagation for W3C trace headers
5
+ module W3CPropagation
6
+ # Parse trace headers
7
+ module UnmarshalTraceContext
8
+ INVALID_TRACE_ID = "00000000000000000000000000000000".freeze
9
+ INVALID_SPAN_ID = "0000000000000000".freeze
10
+
11
+ def parse(serialized_trace)
12
+ unless serialized_trace.nil?
13
+ version, payload = serialized_trace.split("-", 2)
14
+ # version should be 2 hex characters
15
+ if version =~ /^[A-Fa-f0-9]{2}$/
16
+ trace_id, parent_span_id = parse_v1(payload)
17
+
18
+ if !trace_id.nil? && !parent_span_id.nil?
19
+ # return nil for dataset
20
+ return [trace_id, parent_span_id, nil, nil]
21
+ end
22
+ end
23
+ end
24
+ [nil, nil, nil, nil]
25
+ end
26
+
27
+ def parse_v1(payload)
28
+ trace_id, parent_span_id, trace_flags = payload.split("-", 3)
29
+
30
+ if trace_flags.nil?
31
+ # if trace_flags is nil, it means a field is missing
32
+ return [nil, nil]
33
+ end
34
+
35
+ if trace_id == INVALID_TRACE_ID || parent_span_id == INVALID_SPAN_ID
36
+ return [nil, nil]
37
+ end
38
+
39
+ [trace_id, parent_span_id]
40
+ end
41
+ end
42
+
43
+ # Serialize trace headers
44
+ module MarshalTraceContext
45
+ def to_trace_header
46
+ # do not propagate malformed ids
47
+ if trace.id =~ /^[A-Fa-f0-9]{32}$/ && id =~ /^[A-Fa-f0-9]{16}$/
48
+ return "00-#{trace.id}-#{id}-01"
49
+ end
50
+
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ end