Skip to content

Commit a51bde4

Browse files
committed
Refactor stack_trace in multiple classes, and move it in tracing
1 parent d0c3063 commit a51bde4

File tree

15 files changed

+374
-431
lines changed

15 files changed

+374
-431
lines changed

Diff for: lib/datadog/appsec.rb

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
require_relative 'appsec/context'
66
require_relative 'appsec/ext'
77
require_relative 'appsec/utils'
8-
require_relative 'appsec/stack_trace'
98

109
module Datadog
1110
# Namespace for Datadog AppSec instrumentation

Diff for: lib/datadog/appsec/actions_handler.rb

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# frozen_string_literal: true
22

3+
require_relative '../tracing/stack_trace/collector'
4+
require_relative '../tracing/stack_trace/representor'
5+
36
module Datadog
47
module AppSec
58
# this module encapsulates functions for handling actions that libddawf returns
@@ -19,37 +22,34 @@ def interrupt_execution(action_params)
1922
throw(Datadog::AppSec::Ext::INTERRUPT, action_params)
2023
end
2124

22-
# We should change the name of this method as we also add the stack trace to the trace/span
2325
def generate_stack(action_params)
2426
if Datadog.configuration.appsec.stack_trace.enabled
2527
context = AppSec::Context.active
2628
# We use methods defined in Tracing::Metadata::Tagging,
2729
# which means we can use both the trace and the service entry span
2830
service_entry_operation = (context.trace || context.span) if context
2931

30-
unless service_entry_operation
32+
if service_entry_operation.nil?
3133
Datadog.logger.debug { 'Cannot find trace or service entry span to add stack trace' }
3234
return
3335
end
3436

3537
# caller_locations without params always returns an array but steep still thinks it can be nil
3638
# So we add || [] but it will never run the second part anyway (either this or steep:ignore)
3739
stack_frames = caller_locations || []
38-
# Steep that path can still be nil and that include? is not a method of nil
39-
# A way to fix this is to assign loc.path to a variable and use it instead, but it adds an operation
40-
# Which is why we are ignoring this line
40+
# Steep thinks that path can still be nil and that include? is not a method of nil
41+
# We must add a variable assignment to avoid this
4142
stack_frames.reject! do |loc|
42-
loc.path &&
43-
loc.path.include?('lib/datadog') # steep:ignore NoMethod
43+
path = loc.path
44+
next true if path.nil?
45+
46+
path.include?('lib/datadog')
4447
end
4548

46-
collected_stack_frames = AppSec::StackTrace::Collection.new(stack_frames)
49+
collected_stack_frames = Datadog::Tracing::StackTrace::Collector.collect(stack_frames)
4750

48-
# ASCII-8BIT is encoded as byte array and backend cannot decode it properly so we enforce UTF-8 on stack_id.
49-
# We must copy it as it can be frozen.
5051
utf8_stack_id = action_params['stack_id'].encode('UTF-8') if action_params['stack_id']
51-
# We should always have a stack_id, but we should still send the stack trace if not (visible with feature flags)
52-
stack_trace = AppSec::StackTrace::Representation.new(utf8_stack_id, collected_stack_frames)
52+
stack_trace = Datadog::Tracing::StackTrace::Representor.new(id: utf8_stack_id, message: nil, frames: collected_stack_frames)
5353

5454
# Add stack trace to trace or service entry span by modifying meta_struct['_dd.stack']
5555
service_entry_operation.modify_meta_struct_tag(AppSec::Ext::TAG_STACK_TRACE) do |stack_trace_categories|

Diff for: lib/datadog/appsec/stack_trace.rb

-153
This file was deleted.

Diff for: lib/datadog/tracing/stack_trace/collector.rb

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'frame'
4+
5+
module Datadog
6+
module Tracing
7+
module StackTrace
8+
# Represent a stack trace with its id and message in message pack
9+
module Collector
10+
class << self
11+
def collect(locations)
12+
return [] if locations.nil? || locations.empty?
13+
14+
skip_frames = skip_frames(locations.size)
15+
frames = []
16+
17+
locations.each_with_index do |location, index|
18+
next if skip_frames.include?(index)
19+
20+
frames << StackTrace::Frame.new(
21+
id: index,
22+
text: location.to_s.encode('UTF-8'),
23+
file: file_path(location),
24+
line: location.lineno,
25+
function: function_label(location)
26+
)
27+
end
28+
frames
29+
end
30+
31+
private
32+
33+
def skip_frames(locations_size)
34+
max_depth = Datadog.configuration.appsec.stack_trace.max_depth
35+
return [] if max_depth == 0 || locations_size <= max_depth
36+
37+
top_frames_limit = (max_depth * Datadog.configuration.appsec.stack_trace.max_depth_top_percent / 100.0).round
38+
bottom_frames_limit = locations_size - (max_depth - top_frames_limit)
39+
(top_frames_limit...bottom_frames_limit)
40+
end
41+
42+
def file_path(location)
43+
path = location.absolute_path || location.path
44+
return if path.nil?
45+
46+
path.encode('UTF-8')
47+
end
48+
49+
def function_label(location)
50+
label = location.label
51+
return if label.nil?
52+
53+
label.encode('UTF-8')
54+
end
55+
end
56+
end
57+
end
58+
end
59+
end

Diff for: lib/datadog/tracing/stack_trace/frame.rb

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module Tracing
5+
module StackTrace
6+
# Formatted stack frame.
7+
# This class extends a Struct as it's required by Steep to be able to add a method to it.
8+
class Frame < Struct.new(:id, :text, :file, :line, :function, keyword_init: true)
9+
def to_msgpack(packer = nil)
10+
packer ||= MessagePack::Packer.new
11+
12+
packer.write_map_header(5)
13+
packer.write('id')
14+
packer.write(id)
15+
packer.write('text')
16+
packer.write(text)
17+
packer.write('file')
18+
packer.write(file)
19+
packer.write('line')
20+
packer.write(line)
21+
packer.write('function')
22+
packer.write(function)
23+
packer
24+
end
25+
end
26+
end
27+
end
28+
end

Diff for: lib/datadog/tracing/stack_trace/representor.rb

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module Tracing
5+
module StackTrace
6+
# Represent a stack trace with its id and message in message pack
7+
class Representor < Struct.new(:id, :message, :frames, keyword_init: true)
8+
def to_msgpack(packer = nil)
9+
packer ||= MessagePack::Packer.new
10+
11+
packer.write_map_header(4)
12+
packer.write('language')
13+
packer.write('ruby')
14+
packer.write('id')
15+
packer.write(id)
16+
packer.write('message')
17+
packer.write(message)
18+
packer.write('frames')
19+
packer.write(frames)
20+
packer
21+
end
22+
end
23+
end
24+
end
25+
end

Diff for: sig/datadog/appsec/stack_trace.rbs

-51
This file was deleted.

0 commit comments

Comments
 (0)