From d2f09fd3ddab3d8d1ffd782688182b9c46432fe5 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <156273877+p-datadog@users.noreply.github.com> Date: Thu, 19 Sep 2024 07:57:12 -0400 Subject: [PATCH] =?UTF-8?q?DEBUG-2334=20make=20serialize=5Fvalue=20of=20Se?= =?UTF-8?q?rializer=20public=20and=20add=20test=20cov=E2=80=A6=20(#3925)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- lib/datadog/di/serializer.rb | 20 +++++++----- sig/datadog/di/serializer.rbs | 5 +-- spec/datadog/di/serializer_spec.rb | 51 +++++++++++++++++++++++++----- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/lib/datadog/di/serializer.rb b/lib/datadog/di/serializer.rb index 8a6a0165c22..7958302dc86 100644 --- a/lib/datadog/di/serializer.rb +++ b/lib/datadog/di/serializer.rb @@ -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 @@ -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 @@ -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 @@ -166,7 +168,7 @@ 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 @@ -174,6 +176,8 @@ def serialize_value(name, value, depth: settings.dynamic_instrumentation.max_cap serialized end + private + # Returns the name for the specified class object. # # Ruby can have nameless classes, e.g. Class.new is a class object diff --git a/sig/datadog/di/serializer.rbs b/sig/datadog/di/serializer.rbs index 54f075aa43a..aaaf674148e 100644 --- a/sig/datadog/di/serializer.rbs +++ b/sig/datadog/di/serializer.rbs @@ -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 diff --git a/spec/datadog/di/serializer_spec.rb b/spec/datadog/di/serializer_spec.rb index c32974c478c..a19d02b79e7 100644 --- a/spec/datadog/di/serializer_spec.rb +++ b/spec/datadog/di/serializer_spec.rb @@ -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) @@ -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},