Module: Datadog::Trace

Defined in:
lib/datadog/lambda/trace/xray.rb,
lib/datadog/lambda/trace/context.rb,
lib/datadog/lambda/trace/ddtrace.rb,
lib/datadog/lambda/trace/listener.rb,
lib/datadog/lambda/trace/constants.rb,
lib/datadog/lambda/trace/patch_http.rb

Overview

Trace contains methods to help with patching Net/HTTP

Defined Under Namespace

Modules: NetExtensions Classes: Listener

Constant Summary collapse

SAMPLE_MODE_USER_REJECT =
-1
SAMPLE_MODE_AUTO_REJECT =
0
SAMPLE_MODE_AUTO_KEEP =
1
SAMPLE_MODE_USER_KEEP =
2
DD_TRACE_ID_HEADER =
'x-datadog-trace-id'
DD_PARENT_ID_HEADER =
'x-datadog-parent-id'
DD_SAMPLING_PRIORITY_HEADER =
'x-datadog-sampling-priority'
DD_ORIGIN =
'x-datadog-origin'
DD_XRAY_SUBSEGMENT_NAME =
'datadog-metadata'
DD_XRAY_SUBSEGMENT_KEY =
'trace'
DD_XRAY_SUBSEGMENT_NAMESPACE =
'datadog'
DD_TRACE_MANAGED_SERVICES =
'DD_TRACE_MANAGED_SERVICES'
SOURCE_XRAY =
'XRAY'
SOURCE_EVENT =
'EVENT'
XRAY_ENV_VAR =
'_X_AMZN_TRACE_ID'
XRAY_UDP_PORT =
2000
LOCAL_HOST =
'127.0.0.1'
AWS_XRAY_DAEMON_ADDRESS_ENV_VAR =
'AWS_XRAY_DAEMON_ADDRESS'

Class Method Summary collapse

Class Method Details

.add_trace_context_to_xray(context) ⇒ Object



40
41
42
43
44
# File 'lib/datadog/lambda/trace/xray.rb', line 40

def add_trace_context_to_xray(context)
  data = (context)
  send_xray_daemon_data(data)
  Datadog::Utils.logger.debug("sent metadata to xray #{data}")
end

.apply_datadog_trace_context(context) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/datadog/lambda/trace/ddtrace.rb', line 14

def apply_datadog_trace_context(context)
  unless context.nil?
    trace_id = context[:trace_id].to_i
    span_id = context[:parent_id].to_i
    sampling_priority = context[:sample_mode]
    trace_digest = Datadog::Tracing::TraceDigest.new(
      span_id:,
      trace_id:,
      trace_sampling_priority: sampling_priority
    )
    Datadog::Tracing.continue_trace!(trace_digest)
  end
rescue StandardError
  Datadog::Utils.logger.debug 'dd-trace unavailable'
end

.convert_to_apm_parent_id(xray_parent_id) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/datadog/lambda/trace/xray.rb', line 128

def convert_to_apm_parent_id(xray_parent_id)
  return nil if xray_parent_id.length != 16
  return nil if xray_parent_id.upcase[/\H/]

  hex = xray_parent_id.to_i(16)
  hex.to_s(10)
end

.convert_to_apm_trace_id(xray_trace_id) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/datadog/lambda/trace/xray.rb', line 114

def convert_to_apm_trace_id(xray_trace_id)
  parts = xray_trace_id.split('-')
  return nil if parts.length < 3

  last_part = parts[2]
  return nil if last_part.length != 24
  # Make sure every character is hex
  return nil if last_part.upcase[/\H/]

  hex = last_part.to_i(16)
  last_63_bits = hex & 0x7fffffffffffffff
  last_63_bits.to_s(10)
end

.convert_to_sample_mode(xray_sampled) ⇒ Object



136
137
138
# File 'lib/datadog/lambda/trace/xray.rb', line 136

def convert_to_sample_mode(xray_sampled)
  xray_sampled == '1' ? SAMPLE_MODE_USER_KEEP : SAMPLE_MODE_USER_REJECT
end

.current_trace_context(trace_context) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/datadog/lambda/trace/xray.rb', line 70

def current_trace_context(trace_context)
  trace_context = Hash[trace_context]
  begin
    # This will only succeed if the user has imported xray themselves
    entity = XRay.recorder.current_entity
    trace_context[:parent_id] = convert_to_apm_parent_id(entity.id)
  rescue StandardError
    Datadog::Utils.logger.debug("couldn't fetch xray entity")
  end
  trace_context
end

.extract_trace_context(event, merge_xray_traces) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/datadog/lambda/trace/context.rb', line 20

def extract_trace_context(event, merge_xray_traces)
  context = read_trace_context_from_event(event)
  unless context.nil?
    begin
      add_trace_context_to_xray(context)
    rescue StandardError => e
      Datadog::Utils.logger.debug("couldn't add metadata to xray #{e}")
    end
    return context
  end
  return nil unless merge_xray_traces

  read_trace_context_from_xray
end

.generate_xray_metadata_subsegment(context) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/datadog/lambda/trace/xray.rb', line 46

def (context)
  header = ENV[XRAY_ENV_VAR]
  segment = parse_xray_trace_context_header(header)

  {
    "id": SecureRandom.hex(8),
    "trace_id": segment[:xray_trace_id],
    "parent_id": segment[:xray_parent_id],
    "name": DD_XRAY_SUBSEGMENT_NAME,
    "start_time": Time.now.to_f,
    "end_time": Time.now.to_f,
    "type": 'subsegment',
    "metadata": {
      "datadog": {
        "trace": {
          "parent-id": context[:parent_id],
          "sampling-priority": context[:sample_mode].to_s,
          "trace-id": context[:trace_id]
        }
      }
    }
  }.to_json
end

.headers?(event) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
53
# File 'lib/datadog/lambda/trace/context.rb', line 50

def headers?(event)
  event.is_a?(Hash) && event.key?('headers') &&
    event['headers'].is_a?(Hash)
end

.parse_assignment(value) ⇒ Object



107
108
109
110
111
112
# File 'lib/datadog/lambda/trace/xray.rb', line 107

def parse_assignment(value)
  return nil if value.nil?

  _, raw_value, * = value.split('=')
  raw_value
end

.parse_xray_trace_context_header(header) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/datadog/lambda/trace/xray.rb', line 93

def parse_xray_trace_context_header(header)
  Datadog::Utils.logger.debug("Reading trace context from env #{header}")
  trace_id, parent_id, sampled = header.split(';')
                                       .map { |v| parse_assignment(v) }

  return nil if trace_id.nil? || parent_id.nil? || sampled.nil?

  {
    xray_trace_id: trace_id,
    xray_parent_id: parent_id,
    xray_sample_mode: sampled
  }
end

.patch_httpObject



25
26
27
28
# File 'lib/datadog/lambda/trace/patch_http.rb', line 25

def self.patch_http
  Net::HTTP.prepend NetExtensions unless @patched
  @patched = true
end

.read_trace_context_from_event(event) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/datadog/lambda/trace/context.rb', line 35

def read_trace_context_from_event(event)
  return nil unless headers?(event)

  headers = event['headers'].transform_keys(&:downcase)

  return nil unless trace_headers_present?(headers)

  {
    trace_id: headers[DD_TRACE_ID_HEADER],
    parent_id: headers[DD_PARENT_ID_HEADER],
    sample_mode: headers[DD_SAMPLING_PRIORITY_HEADER].to_i,
    source: SOURCE_EVENT
  }
end

.read_trace_context_from_xrayObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/datadog/lambda/trace/xray.rb', line 21

def read_trace_context_from_xray
  header = ENV[XRAY_ENV_VAR]
  segment = parse_xray_trace_context_header(header)
  trace_id = convert_to_apm_trace_id(segment[:xray_trace_id])
  parent_id = convert_to_apm_parent_id(segment[:xray_parent_id])
  sample_mode = convert_to_sample_mode(segment[:xray_sample_mode])

  if trace_id.nil? || parent_id.nil? || sample_mode.nil?
    Datadog::Utils.logger.error("couldn't read xray header #{header}")
    return nil
  end
  {
    trace_id:,
    parent_id:,
    sample_mode:,
    source: SOURCE_XRAY
  }
end

.send_xray_daemon_data(data) ⇒ Object



82
83
84
85
86
87
88
89
90
91
# File 'lib/datadog/lambda/trace/xray.rb', line 82

def send_xray_daemon_data(data)
  xray_daemon_env = ENV[AWS_XRAY_DAEMON_ADDRESS_ENV_VAR]
  socket = XRAY_UDP_PORT
  address = LOCAL_HOST
  address, socket = xray_daemon_env.split(':') unless xray_daemon_env.nil?

  sock = UDPSocket.open
  message = "{\"format\": \"json\", \"version\": 1}\n#{data}"
  sock.send(message, 0, address, socket)
end

.trace_contextObject



16
17
18
# File 'lib/datadog/lambda/trace/patch_http.rb', line 16

def self.trace_context
  @trace_context
end

.trace_context=(val) ⇒ Object



20
21
22
# File 'lib/datadog/lambda/trace/patch_http.rb', line 20

def self.trace_context=(val)
  @trace_context = val
end

.trace_headers_present?(headers) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
58
59
60
61
62
63
64
65
# File 'lib/datadog/lambda/trace/context.rb', line 55

def trace_headers_present?(headers)
  expected = [
    DD_TRACE_ID_HEADER,
    DD_PARENT_ID_HEADER,
    DD_SAMPLING_PRIORITY_HEADER
  ]
  expected.each do |key|
    return false unless headers.key?(key) && headers[key].is_a?(String)
  end
  true
end

.wrap_datadog(options, &block) ⇒ Object



30
31
32
33
34
35
36
37
38
39
# File 'lib/datadog/lambda/trace/ddtrace.rb', line 30

def wrap_datadog(options, &block)
  unless Datadog::Tracing.enabled?
    Datadog::Utils.logger.debug 'dd-trace unavailable'
    return block.call
  end

  Datadog::Tracing.trace('aws.lambda', **options) do |_span|
    block.call
  end
end