Class: Datadog::Core::Crashtracking::Component

Inherits:
Object
  • Object
show all
Defined in:
lib/datadog/core/crashtracking/component.rb,
ext/libdatadog_api/crashtracker.c

Overview

Used to report Ruby VM crashes.

NOTE: The crashtracker native state is a singleton; so even if you create multiple instances of Crashtracking::Component and start them, it only works as “last writer wins”. Same for stop – there’s only one state, so calling stop on it will stop the crash tracker, regardless of which instance started it.

Methods prefixed with native are implemented in crashtracker.c

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tags:, agent_base_url:, ld_library_path:, path_to_crashtracking_receiver_binary:, logger:) ⇒ Component

Returns a new instance of Component.



70
71
72
73
74
75
76
# File 'lib/datadog/core/crashtracking/component.rb', line 70

def initialize(tags:, agent_base_url:, ld_library_path:, path_to_crashtracking_receiver_binary:, logger:)
  @tags = tags
  @agent_base_url = agent_base_url
  @ld_library_path = ld_library_path
  @path_to_crashtracking_receiver_binary = path_to_crashtracking_receiver_binary
  @logger = logger
end

Class Method Details

._native_report_ruby_exceptionObject



10
11
12
# File 'ext/libdatadog_api/crashtracker_report_exception.c', line 10

static VALUE _native_report_ruby_exception(VALUE _self, VALUE agent_base_url,
VALUE message, VALUE frames_data,
VALUE tags_as_array, VALUE library_version);

._native_start_or_update_on_forkObject



7
# File 'ext/libdatadog_api/crashtracker.c', line 7

static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);

._native_stopObject



8
# File 'ext/libdatadog_api/crashtracker.c', line 8

static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);

.build(settings, agent_settings, logger:) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/datadog/core/crashtracking/component.rb', line 19

def self.build(settings, agent_settings, logger:)
  tags = latest_tags(settings)
  agent_base_url = agent_settings.url

  ld_library_path = ::Libdatadog.ld_library_path
  logger.warn('Missing ld_library_path; cannot enable crash tracking') unless ld_library_path

  path_to_crashtracking_receiver_binary = ::Libdatadog.path_to_crashtracking_receiver_binary
  unless path_to_crashtracking_receiver_binary
    logger.warn('Missing path_to_crashtracking_receiver_binary; cannot enable crash tracking')
  end

  return unless agent_base_url
  return unless ld_library_path
  return unless path_to_crashtracking_receiver_binary

  new(
    tags: tags,
    agent_base_url: agent_base_url,
    ld_library_path: ld_library_path,
    path_to_crashtracking_receiver_binary: path_to_crashtracking_receiver_binary,
    logger: logger
  ).tap(&:start)
end

.latest_tags(settings) ⇒ Object

Gets the latest tags from the current configuration.

We always fetch fresh tags because: After forking, we need the latest tags, not the parent’s tags, such as the pid or runtime-id



66
67
68
# File 'lib/datadog/core/crashtracking/component.rb', line 66

def self.latest_tags(settings)
  TagBuilder.call(settings)
end

.report_unhandled_exception(exception) ⇒ Object

Reports unhandled exceptions to the crash tracker if available and appropriate. This is called from the at_exit hook to report unhandled exceptions.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/datadog/core/crashtracking/component.rb', line 46

def self.report_unhandled_exception(exception)
  return unless exception && !exception.is_a?(SystemExit) && !exception.is_a?(NoMemoryError)

  begin
    crashtracker = Datadog.send(:components, allow_initialization: false)&.crashtracker
    return unless crashtracker

    crashtracker.report_unhandled_exception(exception)
  rescue => e
    # Unhandled exception report triggering means that the application is already in a bad state
    # We don't want to swallow non-StandardError exceptions here; we would rather just let the
    # application crash
    Datadog.logger.debug("Crashtracker failed to report unhandled exception: #{e.message}")
  end
end

Instance Method Details

#report_unhandled_exception(exception, settings: Datadog.configuration) ⇒ Object



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
# File 'lib/datadog/core/crashtracking/component.rb', line 86

def report_unhandled_exception(exception, settings: Datadog.configuration)
  # Maximum number of stack frames to include in exception crash reports
  # This is the same number used for signal-based crashtracking's runtime stack
  max_exception_stack_frames = 512

  current_tags = self.class.latest_tags(settings)
  # extract all frame data upfront; c expects exactly 3 elements, proper types, no nils
  # limit to max_exception_stack_frames frames
  all_backtrace_locations = exception.backtrace_locations || []
  was_truncated = all_backtrace_locations.length > max_exception_stack_frames

  backtrace_slice = all_backtrace_locations[0...max_exception_stack_frames] || []
  # @type var frames_data: Array[[String, String, Integer]]
  frames_data = backtrace_slice.map do |loc|
    file = loc.path
    file = '<unknown>' if file.nil? || file.empty? || !file.is_a?(String)

    function = loc.label
    function = '<unknown>' if function.nil? || function.empty? || !function.is_a?(String)

    line = loc.lineno
    line = 0 if line.nil? || line < 0 || !line.is_a?(Integer)

    [file, function, line]
  end

  # Add truncation indicator frame if we had to cut off frames
  if was_truncated
    truncated_count = all_backtrace_locations.length - max_exception_stack_frames
    frames_data << ['<truncated>', "<truncated #{truncated_count} more frames>", 0]
  end

  exception_message = exception.message
  message =
    if exception_message && !exception_message.empty?
      "Process was terminated due to an unhandled exception of type '#{exception.class}'. Message: \"#{exception_message}\""
    else
      "Process was terminated due to an unhandled exception of type '#{exception.class}'."
    end

  success = self.class._native_report_ruby_exception(
    agent_base_url,
    message,
    frames_data,
    current_tags.to_a,
    Datadog::VERSION::STRING
  )

  logger.debug('Crashtracker failed to report unhandled exception to crash tracker') unless success
end

#startObject



78
79
80
# File 'lib/datadog/core/crashtracking/component.rb', line 78

def start
  start_or_update_on_fork(action: :start, tags: tags)
end

#stopObject



137
138
139
140
141
142
# File 'lib/datadog/core/crashtracking/component.rb', line 137

def stop
  self.class._native_stop
  logger.debug('Crash tracking stopped successfully')
rescue => e
  logger.error("Failed to stop crash tracking: #{e.message}")
end

#update_on_fork(settings: Datadog.configuration) ⇒ Object



82
83
84
# File 'lib/datadog/core/crashtracking/component.rb', line 82

def update_on_fork(settings: Datadog.configuration)
  start_or_update_on_fork(action: :update_on_fork, tags: self.class.latest_tags(settings))
end