Skip to content

Commit

Permalink
DEBUG-2334 make serialize_value of Serializer public and add test cov… (
Browse files Browse the repository at this point in the history
#3925)

For serializing method return values, we will need to call serialize_value
from outside of Serializer and this call will not have a name provided.

Make serialize_value public and add some tests for it (simple cases that
were under serialize_vars previously + explicit redaction cases).

Co-authored-by: Oleg Pudeyev <code@olegp.name>
  • Loading branch information
p-datadog and p authored Sep 19, 2024
1 parent e97a23c commit d2f09fd
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 18 deletions.
20 changes: 12 additions & 8 deletions lib/datadog/di/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,23 @@ def serialize_args(args, kwargs)
# of executed code.
def serialize_vars(vars)
vars.each_with_object({}) do |(k, v), agg|
agg[k] = serialize_value(k, v)
agg[k] = serialize_value(v, name: k)
end
end

private

# Serializes a single named value.
#
# The name is necessary to perform sensitive data redaction.
# The name is needed to perform sensitive data redaction.
#
# In some cases, the value being serialized does not have a name
# (for example, it is the return value of a method).
# In this case +name+ can be nil.
#
# Returns a data structure comprised of only values of basic types
# (integers, strings, arrays, hashes).
#
# Respects string length, collection size and traversal depth limits.
def serialize_value(name, value, depth: settings.dynamic_instrumentation.max_capture_depth)
def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.max_capture_depth)
if redactor.redact_type?(value)
return {type: class_name(value.class), notCapturedReason: "redactedType"}
end
Expand Down Expand Up @@ -109,7 +111,7 @@ def serialize_value(name, value, depth: settings.dynamic_instrumentation.max_cap
value = value[0...max] || []
end
entries = value.map do |elt|
serialize_value(nil, elt, depth: depth - 1)
serialize_value(elt, depth: depth - 1)
end
serialized.update(elements: entries)
end
Expand All @@ -126,7 +128,7 @@ def serialize_value(name, value, depth: settings.dynamic_instrumentation.max_cap
break
end
cur += 1
entries << [serialize_value(nil, k, depth: depth - 1), serialize_value(k, v, depth: depth - 1)]
entries << [serialize_value(k, depth: depth - 1), serialize_value(v, name: k, depth: depth - 1)]
end
serialized.update(entries: entries)
end
Expand Down Expand Up @@ -166,14 +168,16 @@ def serialize_value(name, value, depth: settings.dynamic_instrumentation.max_cap
break
end
cur += 1
fields[ivar] = serialize_value(ivar, value.instance_variable_get(ivar), depth: depth - 1)
fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1)
end
serialized.update(fields: fields)
end
end
serialized
end

private

# Returns the name for the specified class object.
#
# Ruby can have nameless classes, e.g. Class.new is a class object
Expand Down
5 changes: 3 additions & 2 deletions sig/datadog/di/serializer.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ module Datadog

attr_reader settings: Datadog::Core::Configuration::Settings

attr_reader redactor: untyped
attr_reader redactor: Datadog::DI::Redactor

def serialize_args: (untyped args, untyped kwargs) -> untyped
def serialize_vars: (untyped vars) -> untyped

private
def serialize_value: (untyped name, untyped value, ?depth: untyped) -> ({ type: untyped, notCapturedReason: "redactedType" } | { type: untyped, notCapturedReason: "redactedIdent" } | untyped)
def serialize_value: (untyped value, ?name: String, ?depth: untyped) -> ({ type: untyped, notCapturedReason: "redactedType" } | { type: untyped, notCapturedReason: "redactedIdent" } | untyped)
def class_name: (untyped cls) -> untyped
end
end
Expand Down
51 changes: 43 additions & 8 deletions spec/datadog/di/serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,49 @@ class DISerializerSpecTestClass; end
described_class.new(settings, redactor)
end

describe "#serialize_value" do
let(:serialized) do
serializer.serialize_value(value, **options)
end

def self.define_cases(cases)
cases.each do |c|
value = c.fetch(:input)
expected = c.fetch(:expected)
var_name = c[:var_name]

context c.fetch(:name) do
let(:value) { value }

let(:options) do
{name: var_name}
end

it "serializes as expected" do
expect(serialized).to eq(expected)
end
end
end
end

cases = [
{name: "nil value", input: nil, expected: {type: "NilClass", isNull: true}},
{name: "true value", input: true, expected: {type: "TrueClass", value: "true"}},
{name: "false value", input: false, expected: {type: "FalseClass", value: "false"}},
{name: "int value", input: 42, expected: {type: "Integer", value: "42"}},
{name: "bigint value", input: 420000000000000000000042, expected: {type: "Integer", value: "420000000000000000000042"}},
{name: "float value", input: 42.02, expected: {type: "Float", value: "42.02"}},
{name: "string value", input: "x", expected: {type: "String", value: "x"}},
{name: "symbol value", input: :x, expected: {type: "Symbol", value: "x"}},
{name: "redacted identifier in predefined list", input: "123", var_name: "password",
expected: {type: "String", notCapturedReason: "redactedIdent"}},
{name: "variable name given and is not a redacted identifier", input: "123", var_name: "normal",
expected: {type: "String", value: "123"}},
]

define_cases(cases)
end

describe "#serialize_vars" do
let(:serialized) do
serializer.serialize_vars(vars)
Expand All @@ -83,14 +126,6 @@ def self.define_cases(cases)
end

cases = [
{name: "nil value", input: {a: nil}, expected: {a: {type: "NilClass", isNull: true}}},
{name: "true value", input: {a: true}, expected: {a: {type: "TrueClass", value: "true"}}},
{name: "false value", input: {a: false}, expected: {a: {type: "FalseClass", value: "false"}}},
{name: "int value", input: {a: 42}, expected: {a: {type: "Integer", value: "42"}}},
{name: "bigint value", input: {a: 420000000000000000000042}, expected: {a: {type: "Integer", value: "420000000000000000000042"}}},
{name: "float value", input: {a: 42.02}, expected: {a: {type: "Float", value: "42.02"}}},
{name: "string value", input: {a: "x"}, expected: {a: {type: "String", value: "x"}}},
{name: "symbol value", input: {a: :x}, expected: {a: {type: "Symbol", value: "x"}}},
{name: "redacted value in predefined list", input: {password: "123"},
expected: {password: {type: "String", notCapturedReason: "redactedIdent"}}},
{name: "redacted type", input: {value: DISerializerSpecSensitiveType.new},
Expand Down

0 comments on commit d2f09fd

Please sign in to comment.