honeycomb-beeline 1.3.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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