diff --git a/Rakefile b/Rakefile
index 68811527ecd..9b854bd1514 100644
--- a/Rakefile
+++ b/Rakefile
@@ -109,7 +109,8 @@ namespace :spec do
'patcher',
'registerable',
'registry',
- 'registry/*'
+ 'registry/*',
+ 'propagation/**/*'
].join(',')
t.pattern = "spec/**/contrib/{#{contrib_paths}}_spec.rb"
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index 28c3e447ac7..0e573691962 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -1332,6 +1332,7 @@ client.query("SELECT * FROM users WHERE group='x'")
| Key | Description | Default |
| --- | ----------- | ------- |
| `service_name` | Service name used for `mysql2` instrumentation | `'mysql2'` |
+| `comment_propagation` | SQL comment propagation mode for database monitoring.
(example: `disabled` \| `service`).
**Important**: *Note that enabling sql comment propagation results in potentially confidential data (service names) being stored in the databases which can then be accessed by other 3rd parties that have been granted access to the database.* | `'disabled'` |
### Net/HTTP
diff --git a/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb b/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb
index b37228d1f0f..4bd8cac76e5 100644
--- a/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb
+++ b/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb
@@ -3,6 +3,8 @@
require_relative '../../configuration/settings'
require_relative '../ext'
+require_relative '../../propagation/sql_comment'
+
module Datadog
module Tracing
module Contrib
@@ -27,6 +29,16 @@ class Settings < Contrib::Configuration::Settings
end
option :service_name, default: Ext::DEFAULT_PEER_SERVICE_NAME
+
+ option :comment_propagation do |o|
+ o.default do
+ ENV.fetch(
+ Contrib::Propagation::SqlComment::Ext::ENV_DBM_PROPAGATION_MODE,
+ Contrib::Propagation::SqlComment::Ext::DISABLED
+ )
+ end
+ o.lazy
+ end
end
end
end
diff --git a/lib/datadog/tracing/contrib/mysql2/instrumentation.rb b/lib/datadog/tracing/contrib/mysql2/instrumentation.rb
index 5843e6337d7..246be8afe31 100644
--- a/lib/datadog/tracing/contrib/mysql2/instrumentation.rb
+++ b/lib/datadog/tracing/contrib/mysql2/instrumentation.rb
@@ -4,6 +4,8 @@
require_relative '../analytics'
require_relative 'ext'
require_relative '../ext'
+require_relative '../propagation/sql_comment'
+require_relative '../propagation/sql_comment/mode'
module Datadog
module Tracing
@@ -39,6 +41,12 @@ def query(sql, options = {})
span.set_tag(Ext::TAG_DB_NAME, query_options[:database])
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, query_options[:host])
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, query_options[:port])
+
+ propagation_mode = Contrib::Propagation::SqlComment::Mode.new(comment_propagation)
+
+ Contrib::Propagation::SqlComment.annotate!(span, propagation_mode)
+ sql = Contrib::Propagation::SqlComment.prepend_comment(sql, span, propagation_mode)
+
super(sql, options)
end
end
@@ -56,6 +64,10 @@ def analytics_enabled?
def analytics_sample_rate
datadog_configuration[:analytics_sample_rate]
end
+
+ def comment_propagation
+ datadog_configuration[:comment_propagation]
+ end
end
end
end
diff --git a/lib/datadog/tracing/contrib/propagation/sql_comment.rb b/lib/datadog/tracing/contrib/propagation/sql_comment.rb
new file mode 100644
index 00000000000..847b91decbc
--- /dev/null
+++ b/lib/datadog/tracing/contrib/propagation/sql_comment.rb
@@ -0,0 +1,49 @@
+# typed: false
+
+require_relative 'sql_comment/comment'
+require_relative 'sql_comment/ext'
+
+module Datadog
+ module Tracing
+ module Contrib
+ module Propagation
+ # Implements sql comment propagation related contracts.
+ module SqlComment
+ def self.annotate!(span_op, mode)
+ return unless mode.enabled?
+
+ # PENDING: Until `traceparent`` implementation in `full` mode
+ # span_op.set_tag(Ext::TAG_DBM_TRACE_INJECTED, true) if mode.full?
+ end
+
+ def self.prepend_comment(sql, span_op, mode)
+ return sql unless mode.enabled?
+
+ tags = {
+ Ext::KEY_DATABASE_SERVICE => span_op.service,
+ Ext::KEY_ENVIRONMENT => datadog_configuration.env,
+ Ext::KEY_PARENT_SERVICE => datadog_configuration.service,
+ Ext::KEY_VERSION => datadog_configuration.version
+ }
+
+ # PENDING: Until `traceparent`` implementation in `full` mode
+ # tags.merge!(trace_context(span_op)) if mode.full?
+
+ "#{Comment.new(tags)} #{sql}"
+ end
+
+ def self.datadog_configuration
+ Datadog.configuration
+ end
+
+ # TODO: Derive from trace
+ def self.trace_context(_)
+ {
+ # traceparent: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
+ }.freeze
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb b/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb
new file mode 100644
index 00000000000..bd788ee677f
--- /dev/null
+++ b/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# typed: false
+
+require 'erb'
+
+module Datadog
+ module Tracing
+ module Contrib
+ module Propagation
+ module SqlComment
+ # To be prepended to a sql statement.
+ class Comment
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def to_s
+ @string ||= begin
+ ret = String.new
+
+ @hash.each do |key, value|
+ next if value.nil?
+
+ # Url encode
+ value = ERB::Util.url_encode(value)
+
+ # Escape SQL
+ ret << "#{key}='#{value}',"
+ end
+
+ # Remove the last `,`
+ ret.chop!
+
+ "/*#{ret}*/"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb b/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb
new file mode 100644
index 00000000000..0a70804bf1c
--- /dev/null
+++ b/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb
@@ -0,0 +1,32 @@
+# typed: false
+
+module Datadog
+ module Tracing
+ module Contrib
+ module Propagation
+ module SqlComment
+ module Ext
+ ENV_DBM_PROPAGATION_MODE = 'DD_DBM_PROPAGATION_MODE'.freeze
+
+ # The default mode for sql comment propagation
+ DISABLED = 'disabled'.freeze
+
+ # The `service` mode propagates service configuration
+ SERVICE = 'service'.freeze
+
+ # The `full` mode propagates service configuration + trace context
+ FULL = 'full'.freeze
+
+ # The value should be `true` when `full` mode
+ TAG_DBM_TRACE_INJECTED = '_dd.dbm_trace_injected'.freeze
+
+ KEY_DATABASE_SERVICE = 'dddbs'.freeze
+ KEY_ENVIRONMENT = 'dde'.freeze
+ KEY_PARENT_SERVICE = 'ddps'.freeze
+ KEY_VERSION = 'ddpv'.freeze
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb b/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb
new file mode 100644
index 00000000000..3a91a30aaad
--- /dev/null
+++ b/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb
@@ -0,0 +1,28 @@
+# typed: false
+
+require_relative 'ext'
+
+module Datadog
+ module Tracing
+ module Contrib
+ module Propagation
+ # Implements sql comment propagation related contracts.
+ module SqlComment
+ Mode = Struct.new(:mode) do
+ def enabled?
+ service? || full?
+ end
+
+ def service?
+ mode == Ext::SERVICE
+ end
+
+ def full?
+ mode == Ext::FULL
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb b/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb
index 868145b5851..2f39c93af79 100644
--- a/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb
+++ b/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb
@@ -25,7 +25,7 @@ def initialize(app, opt = {})
def call(env)
# Set the trace context (e.g. distributed tracing)
if configuration[:distributed_tracing] && Tracing.active_trace.nil?
- original_trace = Propagation::HTTP.extract(env)
+ original_trace = Tracing::Propagation::HTTP.extract(env)
Tracing.continue_trace!(original_trace)
end
diff --git a/spec/datadog/tracing/contrib/mysql2/patcher_spec.rb b/spec/datadog/tracing/contrib/mysql2/patcher_spec.rb
index b8fcf5c5472..1b30413716e 100644
--- a/spec/datadog/tracing/contrib/mysql2/patcher_spec.rb
+++ b/spec/datadog/tracing/contrib/mysql2/patcher_spec.rb
@@ -3,6 +3,8 @@
require 'datadog/tracing/contrib/integration_examples'
require 'datadog/tracing/contrib/support/spec_helper'
require 'datadog/tracing/contrib/analytics_examples'
+require 'datadog/tracing/contrib/propagation/sql_comment'
+require 'datadog/tracing/contrib/sql_comment_propagation_examples'
require 'ddtrace'
require 'mysql2'
@@ -42,35 +44,43 @@
describe 'tracing' do
describe '#query' do
+ subject(:query) { client.query(sql_statement) }
+
+ let(:sql_statement) { 'SELECT 1' }
+
context 'when the tracer is disabled' do
before { tracer.enabled = false }
it 'does not write spans' do
- client.query('SELECT 1')
+ query
+
expect(spans).to be_empty
end
end
context 'when the client is configured directly' do
- let(:service_override) { 'mysql-override' }
+ let(:service_name) { 'mysql-override' }
before do
- Datadog.configure_onto(client, service_name: service_override)
- client.query('SELECT 1')
+ Datadog.configure_onto(client, service_name: service_name)
end
it 'produces a trace with service override' do
+ query
+
expect(spans.count).to eq(1)
- expect(span.service).to eq(service_override)
+ expect(span.service).to eq(service_name)
expect(span.get_tag('db.system')).to eq('mysql')
- expect(span.get_tag(Datadog::Tracing::Metadata::Ext::TAG_PEER_SERVICE)).to eq(service_override)
+ expect(span.get_tag(Datadog::Tracing::Metadata::Ext::TAG_PEER_SERVICE)).to eq(service_name)
end
+
+ it_behaves_like 'with sql comment propagation', span_op_name: 'mysql2.query'
end
context 'when a successful query is made' do
- before { client.query('SELECT 1') }
-
it 'produces a trace' do
+ query
+
expect(spans.count).to eq(1)
expect(span.get_tag('mysql2.db.name')).to eq(database)
expect(span.get_tag('out.host')).to eq(host)
@@ -81,27 +91,37 @@
end
it_behaves_like 'analytics for integration' do
+ before { query }
let(:analytics_enabled_var) { Datadog::Tracing::Contrib::Mysql2::Ext::ENV_ANALYTICS_ENABLED }
let(:analytics_sample_rate_var) { Datadog::Tracing::Contrib::Mysql2::Ext::ENV_ANALYTICS_SAMPLE_RATE }
end
it_behaves_like 'a peer service span' do
+ before { query }
let(:peer_hostname) { host }
end
- it_behaves_like 'measured span for integration', false
+ it_behaves_like 'measured span for integration', false do
+ before { query }
+ end
+
+ it_behaves_like 'with sql comment propagation', span_op_name: 'mysql2.query'
end
context 'when a failed query is made' do
- before { expect { client.query('SELECT INVALID') }.to raise_error(Mysql2::Error) }
+ let(:sql_statement) { 'SELECT INVALID' }
it 'traces failed queries' do
+ expect { query }.to raise_error(Mysql2::Error)
+
expect(spans.count).to eq(1)
expect(span.status).to eq(1)
expect(span.get_tag('db.system')).to eq('mysql')
expect(span.get_tag('error.msg'))
.to eq("Unknown column 'INVALID' in 'field list'")
end
+
+ it_behaves_like 'with sql comment propagation', span_op_name: 'mysql2.query', error: Mysql2::Error
end
end
end
diff --git a/spec/datadog/tracing/contrib/propagation/sql_comment/comment_spec.rb b/spec/datadog/tracing/contrib/propagation/sql_comment/comment_spec.rb
new file mode 100644
index 00000000000..26a7976ad20
--- /dev/null
+++ b/spec/datadog/tracing/contrib/propagation/sql_comment/comment_spec.rb
@@ -0,0 +1,44 @@
+# typed: ignore
+
+require 'datadog/tracing/contrib/propagation/sql_comment/comment'
+
+RSpec.describe Datadog::Tracing::Contrib::Propagation::SqlComment::Comment do
+ describe '#to_s' do
+ [
+ [
+ { first_name: 'datadog', last_name: nil },
+ "/*first_name='datadog'*/"
+ ],
+ [
+ { first_name: 'data', last_name: 'dog' },
+ "/*first_name='data',last_name='dog'*/"
+ ],
+ [
+ { url_encode: 'DROP TABLE FOO' },
+ "/*url_encode='DROP%20TABLE%20FOO'*/"
+ ],
+ [
+ { route: '/polls 1000' },
+ "/*route='%2Fpolls%201000'*/"
+ ],
+ [
+ { escape_single_quote: "Dunkin' Donuts" },
+ "/*escape_single_quote='Dunkin%27%20Donuts'*/"
+ ],
+ [
+ { star: '*' },
+ "/*star='%2A'*/"
+ ],
+ [
+ { back_slash: '\\' },
+ "/*back_slash='%5C'*/"
+ ],
+ [
+ { equal_sign: '=' },
+ "/*equal_sign='%3D'*/"
+ ]
+ ].each do |tags, comment|
+ it { expect(described_class.new(tags).to_s).to eq(comment) }
+ end
+ end
+end
diff --git a/spec/datadog/tracing/contrib/propagation/sql_comment/mode_spec.rb b/spec/datadog/tracing/contrib/propagation/sql_comment/mode_spec.rb
new file mode 100644
index 00000000000..b89c87cbf29
--- /dev/null
+++ b/spec/datadog/tracing/contrib/propagation/sql_comment/mode_spec.rb
@@ -0,0 +1,47 @@
+# typed: ignore
+
+require 'datadog/tracing/contrib/propagation/sql_comment/mode'
+
+RSpec.describe Datadog::Tracing::Contrib::Propagation::SqlComment::Mode do
+ describe '#enabled?' do
+ [
+ ['disabled', false],
+ ['service', true],
+ ['full', true],
+ ['undefined', false]
+ ].each do |string, result|
+ context "when given `#{string}`" do
+ subject { described_class.new(string).enabled? }
+ it { is_expected.to be result }
+ end
+ end
+ end
+
+ describe '#service?' do
+ [
+ ['disabled', false],
+ ['service', true],
+ ['full', false],
+ ['undefined', false]
+ ].each do |string, result|
+ context "when given `#{string}`" do
+ subject { described_class.new(string).service? }
+ it { is_expected.to be result }
+ end
+ end
+ end
+
+ describe '#full?' do
+ [
+ ['disabled', false],
+ ['service', false],
+ ['full', true],
+ ['undefined', false]
+ ].each do |string, result|
+ context "when given `#{string}`" do
+ subject { described_class.new(string).full? }
+ it { is_expected.to be result }
+ end
+ end
+ end
+end
diff --git a/spec/datadog/tracing/contrib/propagation/sql_comment_spec.rb b/spec/datadog/tracing/contrib/propagation/sql_comment_spec.rb
new file mode 100644
index 00000000000..beda666033e
--- /dev/null
+++ b/spec/datadog/tracing/contrib/propagation/sql_comment_spec.rb
@@ -0,0 +1,83 @@
+# typed: ignore
+
+require 'datadog/tracing/contrib/propagation/sql_comment'
+require 'datadog/tracing/contrib/propagation/sql_comment/mode'
+
+RSpec.describe Datadog::Tracing::Contrib::Propagation::SqlComment do
+ let(:propagation_mode) { Datadog::Tracing::Contrib::Propagation::SqlComment::Mode.new(mode) }
+ let(:span_op) { Datadog::Tracing::SpanOperation.new('sql_comment_propagation_span', service: 'database_service') }
+
+ describe '.annotate!' do
+ context 'when `disabled` mode' do
+ let(:mode) { 'disabled' }
+
+ it do
+ described_class.annotate!(span_op, propagation_mode)
+
+ expect(span_op.get_tag('_dd.dbm_trace_injected')).to be_nil
+ end
+ end
+
+ context 'when `service` mode' do
+ let(:mode) { 'service' }
+
+ it do
+ described_class.annotate!(span_op, propagation_mode)
+
+ expect(span_op.get_tag('_dd.dbm_trace_injected')).to be_nil
+ end
+ end
+
+ context 'when `full` mode', pending: 'until `traceparent` implement with `full` mode' do
+ let(:mode) { 'full' }
+
+ it do
+ described_class.annotate!(span_op, propagation_mode)
+
+ expect(span_op.get_tag('_dd.dbm_trace_injected')).to eq('true')
+ end
+ end
+ end
+
+ describe '.prepend_comment' do
+ around do |example|
+ without_warnings { Datadog.configuration.reset! }
+ example.run
+ without_warnings { Datadog.configuration.reset! }
+ end
+
+ before do
+ Datadog.configure do |c|
+ c.env = 'production'
+ c.service = "Traders' Joe"
+ c.version = '1.0.0'
+ end
+ end
+
+ let(:sql_statement) { 'SELECT 1' }
+
+ subject { described_class.prepend_comment(sql_statement, span_op, propagation_mode) }
+
+ context 'when `disabled` mode' do
+ let(:mode) { 'disabled' }
+
+ it { is_expected.to eq(sql_statement) }
+ end
+
+ context 'when `service` mode' do
+ let(:mode) { 'service' }
+
+ it do
+ is_expected.to eq(
+ "/*dddbs='database_service',dde='production',ddps='Traders%27%20Joe',ddpv='1.0.0'*/ #{sql_statement}"
+ )
+ end
+ end
+
+ context 'when `full` mode', pending: 'until `traceparent` implement with `full` mode' do
+ let(:mode) { 'full' }
+
+ it { is_expected.to eq(sql_statement) }
+ end
+ end
+end
diff --git a/spec/datadog/tracing/contrib/sql_comment_propagation_examples.rb b/spec/datadog/tracing/contrib/sql_comment_propagation_examples.rb
new file mode 100644
index 00000000000..77a3639a6d6
--- /dev/null
+++ b/spec/datadog/tracing/contrib/sql_comment_propagation_examples.rb
@@ -0,0 +1,73 @@
+# typed: ignore
+
+RSpec.shared_examples_for 'with sql comment propagation' do |span_op_name:, error: nil|
+ context 'when default `disabled`' do
+ it_behaves_like 'propagates with sql comment', mode: 'disabled', span_op_name: span_op_name, error: error do
+ let(:propagation_mode) { Datadog::Tracing::Contrib::Propagation::SqlComment::Mode.new('disabled') }
+ end
+ end
+
+ context 'when ENV variable `DD_DBM_PROPAGATION_MODE` is provided' do
+ around do |example|
+ ClimateControl.modify(
+ 'DD_DBM_PROPAGATION_MODE' => 'service',
+ &example
+ )
+ end
+
+ it_behaves_like 'propagates with sql comment', mode: 'service', span_op_name: span_op_name, error: error do
+ let(:propagation_mode) { Datadog::Tracing::Contrib::Propagation::SqlComment::Mode.new('service') }
+ end
+ end
+
+ %w[disabled service full].each do |mode|
+ context "when `comment_propagation` is configured to #{mode}" do
+ let(:configuration_options) do
+ { comment_propagation: mode, service_name: service_name }
+ end
+
+ it_behaves_like 'propagates with sql comment', mode: mode, span_op_name: span_op_name, error: error do
+ let(:propagation_mode) { Datadog::Tracing::Contrib::Propagation::SqlComment::Mode.new(mode) }
+ end
+ end
+ end
+end
+
+RSpec.shared_examples_for 'propagates with sql comment' do |mode:, span_op_name:, error: nil|
+ it "propagates with mode: #{mode}" do
+ expect(Datadog::Tracing::Contrib::Propagation::SqlComment::Mode)
+ .to receive(:new).with(mode).and_return(propagation_mode)
+
+ if error
+ expect { subject }.to raise_error(error)
+ else
+ subject
+ end
+ end
+
+ it 'decorates the span operation' do
+ expect(Datadog::Tracing::Contrib::Propagation::SqlComment).to receive(:annotate!).with(
+ a_span_operation_with(name: span_op_name),
+ propagation_mode
+ )
+ if error
+ expect { subject }.to raise_error(error)
+ else
+ subject
+ end
+ end
+
+ it 'prepends sql comment to the sql statement' do
+ expect(Datadog::Tracing::Contrib::Propagation::SqlComment).to receive(:prepend_comment).with(
+ sql_statement,
+ a_span_operation_with(name: span_op_name, service: service_name),
+ propagation_mode
+ ).and_call_original
+
+ if error
+ expect { subject }.to raise_error(error)
+ else
+ subject
+ end
+ end
+end
diff --git a/spec/support/span_helpers.rb b/spec/support/span_helpers.rb
index 2f50b91247a..641520cb367 100644
--- a/spec/support/span_helpers.rb
+++ b/spec/support/span_helpers.rb
@@ -121,4 +121,13 @@ def description_of(actual)
end
end
end
+
+ RSpec::Matchers.define :a_span_operation_with do |expected|
+ match do |actual|
+ actual.instance_of?(Datadog::Tracing::SpanOperation) &&
+ expected.all? do |key, value|
+ actual.__send__(key) == value
+ end
+ end
+ end
end