Class: Datadog::DI::ProbeNotificationBuilder Private

Inherits:
Object
  • Object
show all
Defined in:
lib/datadog/di/probe_notification_builder.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Builds probe status notification and snapshot payloads.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(settings, serializer) ⇒ ProbeNotificationBuilder

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of ProbeNotificationBuilder.



9
10
11
12
# File 'lib/datadog/di/probe_notification_builder.rb', line 9

def initialize(settings, serializer)
  @settings = settings
  @serializer = serializer
end

Instance Attribute Details

#serializerObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



15
16
17
# File 'lib/datadog/di/probe_notification_builder.rb', line 15

def serializer
  @serializer
end

#settingsObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



14
15
16
# File 'lib/datadog/di/probe_notification_builder.rb', line 14

def settings
  @settings
end

Instance Method Details

#build_emitting(probe) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
32
33
# File 'lib/datadog/di/probe_notification_builder.rb', line 29

def build_emitting(probe)
  build_status(probe,
    message: "Probe #{probe.id} is emitting",
    status: 'EMITTING',)
end

#build_executed(probe, trace_point: nil, rv: nil, duration: nil, caller_locations: nil, args: nil, kwargs: nil, serialized_entry_args: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Duration is in seconds.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/datadog/di/probe_notification_builder.rb', line 36

def build_executed(probe,
  trace_point: nil, rv: nil, duration: nil, caller_locations: nil,
  args: nil, kwargs: nil, serialized_entry_args: nil)
  snapshot = if probe.line? && probe.capture_snapshot?
    if trace_point.nil?
      raise "Cannot create snapshot because there is no trace point"
    end
    get_local_variables(trace_point)
  end
  # TODO check how many stack frames we should be keeping/sending,
  # this should be all frames for enriched probes and no frames for
  # non-enriched probes?
  build_snapshot(probe, rv: rv, snapshot: snapshot,
    # Actual path of the instrumented file.
    path: trace_point&.path,
    duration: duration, caller_locations: caller_locations, args: args, kwargs: kwargs,
    serialized_entry_args: serialized_entry_args)
end

#build_installed(probe) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



23
24
25
26
27
# File 'lib/datadog/di/probe_notification_builder.rb', line 23

def build_installed(probe)
  build_status(probe,
    message: "Probe #{probe.id} has been instrumented correctly",
    status: 'INSTALLED',)
end

#build_received(probe) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



17
18
19
20
21
# File 'lib/datadog/di/probe_notification_builder.rb', line 17

def build_received(probe)
  build_status(probe,
    message: "Probe #{probe.id} has been received correctly",
    status: 'RECEIVED',)
end

#build_snapshot(probe, rv: nil, snapshot: nil, path: nil, duration: nil, caller_locations: nil, args: nil, kwargs: nil, serialized_entry_args: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/datadog/di/probe_notification_builder.rb', line 55

def build_snapshot(probe, rv: nil, snapshot: nil, path: nil,
  duration: nil, caller_locations: nil, args: nil, kwargs: nil,
  serialized_entry_args: nil)
  # TODO also verify that non-capturing probe does not pass
  # snapshot or vars/args into this method
  captures = if probe.capture_snapshot?
    if probe.method?
      {
        entry: {
          # standard:disable all
          arguments: if serialized_entry_args
            serialized_entry_args
          else
            (args || kwargs) && serializer.serialize_args(args, kwargs,
              depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
              attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
          end,
          throwable: nil,
          # standard:enable all
        },
        return: {
          arguments: {
            "@return": serializer.serialize_value(rv,
              depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
              attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
          },
          throwable: nil,
        },
      }
    elsif probe.line?
      {
        lines: snapshot && {
          probe.line_no => {locals: serializer.serialize_vars(snapshot,
            depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
            attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)},
        },
      }
    end
  end

  location = if probe.line?
    {
      file: path,
      lines: [probe.line_no],
    }
  elsif probe.method?
    {
      method: probe.method_name,
      type: probe.type_name,
    }
  end

  stack = if caller_locations
    format_caller_locations(caller_locations)
  end

  timestamp = timestamp_now
  {
    service: settings.service,
    "debugger.snapshot": {
      id: SecureRandom.uuid,
      timestamp: timestamp,
      evaluationErrors: [],
      probe: {
        id: probe.id,
        version: 0,
        location: location,
      },
      language: 'ruby',
      # TODO add test coverage for callers being nil
      stack: stack,
      captures: captures,
    },
    # In python tracer duration is under debugger.snapshot,
    # but UI appears to expect it here at top level.
    duration: duration ? (duration * 10**9).to_i : 0,
    host: nil,
    logger: {
      name: probe.file,
      method: probe.method_name || 'no_method',
      thread_name: Thread.current.name,
      # Dynamic instrumentation currently does not need thread_id for
      # anything. It can be sent if a customer requests it at which point
      # we can also determine which thread identifier to send
      # (Thread#native_thread_id or something else).
      thread_id: nil,
      version: 2,
    },
    # TODO add tests that the trace/span id is correctly propagated
    "dd.trace_id": Datadog::Tracing.active_trace&.id&.to_s,
    "dd.span_id": Datadog::Tracing.active_span&.id&.to_s,
    ddsource: 'dd_debugger',
    message: probe.template && evaluate_template(probe.template,
      duration: duration ? duration * 1000 : 0),
    timestamp: timestamp,
  }
end

#build_status(probe, message:, status:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/datadog/di/probe_notification_builder.rb', line 153

def build_status(probe, message:, status:)
  {
    service: settings.service,
    timestamp: timestamp_now,
    message: message,
    ddsource: 'dd_debugger',
    debugger: {
      diagnostics: {
        probeId: probe.id,
        probeVersion: 0,
        runtimeId: Core::Environment::Identity.id,
        parentId: nil,
        status: status,
      },
    },
  }
end

#evaluate_template(template, **vars) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



177
178
179
180
181
182
183
# File 'lib/datadog/di/probe_notification_builder.rb', line 177

def evaluate_template(template, **vars)
  message = template.dup
  vars.each do |key, value|
    message.gsub!("{@#{key}}", value.to_s)
  end
  message
end

#format_caller_locations(caller_locations) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



171
172
173
174
175
# File 'lib/datadog/di/probe_notification_builder.rb', line 171

def format_caller_locations(caller_locations)
  caller_locations.map do |loc|
    {fileName: loc.path, function: loc.label, lineNumber: loc.lineno}
  end
end

#get_local_variables(trace_point) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/datadog/di/probe_notification_builder.rb', line 189

def get_local_variables(trace_point)
  # binding appears to be constructed on access, therefore
  # 1) we should attempt to cache it and
  # 2) we should not call +binding+ until we actually need variable values.
  binding = trace_point.binding

  # steep hack - should never happen
  return {} unless binding

  binding.local_variables.each_with_object({}) do |name, map|
    value = binding.local_variable_get(name)
    map[name] = value
  end
end

#timestamp_nowObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



185
186
187
# File 'lib/datadog/di/probe_notification_builder.rb', line 185

def timestamp_now
  (Time.now.to_f * 1000).to_i
end