honeycomb-beeline 2.1.2 → 2.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,85 @@
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
+
47
+ module_function :parse, :get_fields
48
+ public :parse
49
+ end
50
+
51
+ # Serialize trace headers
52
+ module MarshalTraceContext
53
+ def to_trace_header
54
+ context = [""]
55
+ unless trace.fields.keys.nil?
56
+ trace.fields.keys.each do |key|
57
+ context.push("#{key}=#{trace.fields[key]}")
58
+ end
59
+ end
60
+
61
+ data_to_propagate = [
62
+ "Root=#{trace.id}",
63
+ "Parent=#{id}",
64
+ ]
65
+ "#{data_to_propagate.join(';')}#{context.join(';')}"
66
+ end
67
+
68
+ def self.to_trace_header(propagation_context)
69
+ context = [""]
70
+ fields = propagation_context.trace_fields
71
+ unless fields.keys.nil?
72
+ fields.keys.each do |key|
73
+ context.push("#{key}=#{fields[key]}")
74
+ end
75
+ end
76
+
77
+ data_to_propagate = [
78
+ "Root=#{propagation_context.trace_id}",
79
+ "Parent=#{propagation_context.parent_id}",
80
+ ]
81
+ "#{data_to_propagate.join(';')}#{context.join(';')}"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeycomb
4
+ module Propagation
5
+ Context = Struct.new(:trace_id, :parent_id, :trace_fields, :dataset) do
6
+ def to_array
7
+ [trace_id, parent_id, trace_fields, dataset]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,96 @@
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_rack_env(env)
13
+ parse env["HTTP_X_HONEYCOMB_TRACE"]
14
+ end
15
+
16
+ def parse(serialized_trace)
17
+ unless serialized_trace.nil?
18
+ version, payload = serialized_trace.split(";", 2)
19
+
20
+ if version == "1"
21
+ trace_id, parent_span_id, trace_fields, dataset = parse_v1(payload)
22
+
23
+ if !trace_id.nil? && !parent_span_id.nil?
24
+ return [trace_id, parent_span_id, trace_fields, dataset]
25
+ end
26
+ end
27
+ end
28
+
29
+ [nil, nil, nil, nil]
30
+ end
31
+
32
+ def parse_v1(payload)
33
+ trace_id, parent_span_id, trace_fields, dataset = nil
34
+ payload.split(",").each do |entry|
35
+ key, value = entry.split("=", 2)
36
+ case key.downcase
37
+ when "dataset"
38
+ dataset = URI.decode_www_form_component(value)
39
+ when "trace_id"
40
+ trace_id = value
41
+ when "parent_id"
42
+ parent_span_id = value
43
+ when "context"
44
+ Base64.decode64(value).tap do |json|
45
+ begin
46
+ trace_fields = JSON.parse json
47
+ rescue JSON::ParserError
48
+ trace_fields = {}
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ [trace_id, parent_span_id, trace_fields, dataset]
55
+ end
56
+
57
+ module_function :parse_rack_env, :parse, :parse_v1
58
+ public :parse_rack_env, :parse
59
+ end
60
+
61
+ # Serialize trace headers
62
+ module MarshalTraceContext
63
+ def to_trace_header
64
+ context = Base64.urlsafe_encode64(JSON.generate(trace.fields)).strip
65
+ encoded_dataset = URI.encode_www_form_component(builder.dataset)
66
+ data_to_propogate = [
67
+ "dataset=#{encoded_dataset}",
68
+ "trace_id=#{trace.id}",
69
+ "parent_id=#{id}",
70
+ "context=#{context}",
71
+ ]
72
+ "1;#{data_to_propogate.join(',')}"
73
+ end
74
+
75
+ def self.parse_faraday_env(_env, propagation_context)
76
+ {
77
+ "X-Honeycomb-Trace" => to_trace_header(propagation_context),
78
+ }
79
+ end
80
+
81
+ def self.to_trace_header(propagation_context)
82
+ fields = propagation_context.trace_fields
83
+ context = Base64.urlsafe_encode64(JSON.generate(fields)).strip
84
+ dataset = propagation_context.dataset
85
+ encoded_dataset = URI.encode_www_form_component(dataset)
86
+ data_to_propogate = [
87
+ "dataset=#{encoded_dataset}",
88
+ "trace_id=#{propagation_context.trace_id}",
89
+ "parent_id=#{propagation_context.parent_id}",
90
+ "context=#{context}",
91
+ ]
92
+ "1;#{data_to_propogate.join(',')}"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,79 @@
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_rack_env(env)
12
+ parse env["HTTP_TRACEPARENT"]
13
+ end
14
+
15
+ def parse(serialized_trace)
16
+ unless serialized_trace.nil?
17
+ version, payload = serialized_trace.split("-", 2)
18
+ # version should be 2 hex characters
19
+ if version =~ /^[A-Fa-f0-9]{2}$/
20
+ trace_id, parent_span_id = parse_v1(payload)
21
+
22
+ if !trace_id.nil? && !parent_span_id.nil?
23
+ # return nil for dataset
24
+ return [trace_id, parent_span_id, nil, nil]
25
+ end
26
+ end
27
+ end
28
+ [nil, nil, nil, nil]
29
+ end
30
+
31
+ def parse_v1(payload)
32
+ trace_id, parent_span_id, trace_flags = payload.split("-", 3)
33
+
34
+ if trace_flags.nil?
35
+ # if trace_flags is nil, it means a field is missing
36
+ return [nil, nil]
37
+ end
38
+
39
+ if trace_id == INVALID_TRACE_ID || parent_span_id == INVALID_SPAN_ID
40
+ return [nil, nil]
41
+ end
42
+
43
+ [trace_id, parent_span_id]
44
+ end
45
+
46
+ module_function :parse_rack_env, :parse, :parse_v1
47
+ public :parse
48
+ end
49
+
50
+ # Serialize trace headers
51
+ module MarshalTraceContext
52
+ def to_trace_header
53
+ # do not propagate malformed ids
54
+ if trace.id =~ /^[A-Fa-f0-9]{32}$/ && id =~ /^[A-Fa-f0-9]{16}$/
55
+ return "00-#{trace.id}-#{id}-01"
56
+ end
57
+
58
+ nil
59
+ end
60
+
61
+ def self.parse_faraday_env(_env, propagation_context)
62
+ {
63
+ "traceparent" => to_trace_header(propagation_context),
64
+ }
65
+ end
66
+
67
+ def self.to_trace_header(propagation_context)
68
+ trace_id = propagation_context.trace_id
69
+ parent_id = propagation_context.parent_id
70
+ # do not propagate malformed ids
71
+ if trace_id =~ /^[A-Fa-f0-9]{32}$/ && parent_id =~ /^[A-Fa-f0-9]{16}$/
72
+ return "00-#{trace_id}-#{parent_id}-01"
73
+ end
74
+
75
+ nil
76
+ end
77
+ end
78
+ end
79
+ end
@@ -3,6 +3,7 @@
3
3
  require "forwardable"
4
4
  require "securerandom"
5
5
  require "honeycomb/propagation"
6
+ require "honeycomb/propagation/context"
6
7
  require "honeycomb/deterministic_sampler"
7
8
  require "honeycomb/rollup_fields"
8
9
 
@@ -34,13 +35,14 @@ module Honeycomb
34
35
  @sent = false
35
36
  @started = clock_time
36
37
  parse_options(**options)
38
+ parse_hooks(**options)
37
39
  end
38
40
 
39
41
  def parse_options(parent: nil,
40
42
  parent_id: nil,
41
43
  is_root: parent_id.nil?,
42
- sample_hook: nil,
43
- presend_hook: nil,
44
+ _sample_hook: nil,
45
+ _presend_hook: nil,
44
46
  **_options)
45
47
  @parent = parent
46
48
  # parent_id should be removed in the next major version bump. It has been
@@ -48,8 +50,15 @@ module Honeycomb
48
50
  # compatability
49
51
  @parent_id = parent_id
50
52
  @is_root = is_root
53
+ end
54
+
55
+ def parse_hooks(sample_hook: nil,
56
+ presend_hook: nil,
57
+ propagation_hook: nil,
58
+ **_options)
51
59
  @presend_hook = presend_hook
52
60
  @sample_hook = sample_hook
61
+ @propagation_hook = propagation_hook
53
62
  end
54
63
 
55
64
  def create_child
@@ -59,7 +68,8 @@ module Honeycomb
59
68
  parent: self,
60
69
  parent_id: id,
61
70
  sample_hook: sample_hook,
62
- presend_hook: presend_hook).tap do |c|
71
+ presend_hook: presend_hook,
72
+ propagation_hook: propagation_hook).tap do |c|
63
73
  children << c
64
74
  end
65
75
  end
@@ -70,6 +80,14 @@ module Honeycomb
70
80
  send_internal
71
81
  end
72
82
 
83
+ def trace_headers(env)
84
+ if propagation_hook
85
+ propagation_hook.call(env, propagation_context)
86
+ else
87
+ {}
88
+ end
89
+ end
90
+
73
91
  protected
74
92
 
75
93
  def send_by_parent
@@ -94,7 +112,17 @@ module Honeycomb
94
112
  :builder,
95
113
  :context,
96
114
  :presend_hook,
97
- :sample_hook
115
+ :sample_hook,
116
+ :propagation_hook
117
+
118
+ def propagation_context
119
+ Honeycomb::Propagation::Context.new(
120
+ trace.id,
121
+ id,
122
+ trace.fields,
123
+ builder.dataset,
124
+ )
125
+ end
98
126
 
99
127
  def sent?
100
128
  @sent
@@ -19,7 +19,8 @@ module Honeycomb
19
19
 
20
20
  def initialize(builder:, context:, serialized_trace: nil, **options)
21
21
  trace_id, parent_span_id, trace_fields, dataset =
22
- parse serialized_trace
22
+ internal_parse(serialized_trace: serialized_trace, **options)
23
+
23
24
  dataset && builder.dataset = dataset
24
25
  @id = trace_id || generate_trace_id
25
26
  @fields = trace_fields || {}
@@ -45,5 +46,17 @@ module Honeycomb
45
46
  return id unless id == INVALID_TRACE_ID
46
47
  end
47
48
  end
49
+
50
+ def internal_parse(serialized_trace: nil, parser_hook: nil, **_options)
51
+ # previously we passed in the header directly as a string for us to parse
52
+ # now we get passed the rack env to use as an argument to the provided
53
+ # parser_hook. This preserves the current behaviour and allows us to
54
+ # move forward with the new behaviour without breaking changes
55
+ if serialized_trace.is_a?(Hash) && parser_hook
56
+ parser_hook.call(serialized_trace)
57
+ else
58
+ parse serialized_trace
59
+ end
60
+ end
48
61
  end
49
62
  end