Skip to content

Commit

Permalink
Merge pull request #278 from DataDog/remeh/dd_dogstatsd_url
Browse files Browse the repository at this point in the history
connection_cfg: add support for `DD_DOGSTATSD_URL` environment variable.
  • Loading branch information
remeh committed Jun 30, 2023
2 parents 9b3ae2d + 8ffee4a commit 0677902
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 10 deletions.
63 changes: 56 additions & 7 deletions lib/datadog/statsd/connection_cfg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,51 @@ def make_connection(**params)

private

ERROR_MESSAGE = "Valid environment variables combination for connection configuration:\n" +
" - DD_DOGSTATSD_URL for UDP or UDS connection.\n" +
" Example for UDP: DD_DOGSTATSD_URL='udp://localhost:8125'\n" +
" Example for UDS: DD_DOGSTATSD_URL='unix:///path/to/unix.sock'\n" +
" or\n" +
" - DD_AGENT_HOST and DD_DOGSTATSD_PORT for an UDP connection. E.g. DD_AGENT_HOST='localhost' DD_DOGSTATSD_PORT=8125\n" +
" or\n" +
" - DD_DOGSTATSD_SOCKET for an UDS connection: E.g. DD_DOGSTATSD_SOCKET='/path/to/unix.sock'\n" +
" Note that DD_DOGSTATSD_URL has priority on other environment variables."

DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 8125

UDP_PREFIX = 'udp://'
UDS_PREFIX = 'unix://'

def initialize_with_constructor_args(host: nil, port: nil, socket_path: nil)
try_initialize_with(host: host, port: port, socket_path: socket_path,
not_both_error_message:
error_message:
"Both UDP: (host/port #{host}:#{port}) and UDS (socket_path #{socket_path}) " +
"constructor arguments were given. Use only one or the other.",
)
end

def initialize_with_env_vars()
try_initialize_with(
dogstatsd_url: ENV['DD_DOGSTATSD_URL'],
host: ENV['DD_AGENT_HOST'],
port: ENV['DD_DOGSTATSD_PORT'] && ENV['DD_DOGSTATSD_PORT'].to_i,
socket_path: ENV['DD_DOGSTATSD_SOCKET'],
not_both_error_message:
"Both UDP (DD_AGENT_HOST/DD_DOGSTATSD_PORT #{ENV['DD_AGENT_HOST']}:#{ENV['DD_DOGSTATSD_PORT']}) " +
"and UDS (DD_DOGSTATSD_SOCKET #{ENV['DD_DOGSTATSD_SOCKET']}) environment variables are set. " +
"Set only one or the other.",
error_message: ERROR_MESSAGE,
)
end

def initialize_with_defaults()
try_initialize_with(host: DEFAULT_HOST, port: DEFAULT_PORT)
end

def try_initialize_with(host: nil, port: nil, socket_path: nil, not_both_error_message: "")
def try_initialize_with(dogstatsd_url: nil, host: nil, port: nil, socket_path: nil, error_message: ERROR_MESSAGE)
if (host || port) && socket_path
raise ArgumentError, not_both_error_message
raise ArgumentError, error_message
end

if dogstatsd_url
host, port, socket_path = parse_dogstatsd_url(str: dogstatsd_url.to_s)
end

if host || port
Expand All @@ -71,6 +86,40 @@ def try_initialize_with(host: nil, port: nil, socket_path: nil, not_both_error_m

return false
end

def parse_dogstatsd_url(str:)
# udp socket connection

if str.start_with?(UDP_PREFIX)
dogstatsd_url = str[UDP_PREFIX.size..str.size]
host = nil
port = nil

if dogstatsd_url.include?(":")
parts = dogstatsd_url.split(":")
if parts.size > 2
raise ArgumentError, "Error: DD_DOGSTATSD_URL wrong format for an UDP connection. E.g. 'udp://localhost:8125'"
end

host = parts[0]
port = parts[1].to_i
else
host = dogstatsd_url
end

return host, port, nil
end

# unix socket connection

if str.start_with?(UDS_PREFIX)
return nil, nil, str[UDS_PREFIX.size..str.size]
end

# malformed value

raise ArgumentError, "Error: DD_DOGSTATSD_URL has been provided but is not starting with 'udp://' nor 'unix://'"
end
end
end
end
154 changes: 151 additions & 3 deletions spec/statsd/connection_cfg_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
around do |example|
ClimateControl.modify(
'DD_AGENT_HOST' => dd_agent_host,
'DD_DOGSTATSD_URL' => dd_dogstatsd_url,
'DD_DOGSTATSD_PORT' => dd_dogstatsd_port,
'DD_DOGSTATSD_SOCKET' => dd_dogstatsd_socket,
) do
Expand All @@ -19,6 +20,7 @@
let(:port) { nil }
let(:socket_path) { nil }
let(:dd_agent_host) { nil }
let(:dd_dogstatsd_url) { nil }
let(:dd_dogstatsd_port) { nil }
let(:dd_dogstatsd_socket) { nil }

Expand All @@ -29,6 +31,7 @@
let(:dd_agent_host) { 'unused' }
let(:dd_dogstatsd_port) { '999' }
let(:dd_dogstatsd_socket) { '/un/used' }
let(:dd_dogstatsd_url) { 'unix://unused.sock' }

it 'creates a UDP connection' do
expect(subject.transport_type).to eq :udp
Expand Down Expand Up @@ -144,16 +147,161 @@
end
end

context 'with no args and a malformed DD_DOGSTATSD_URL' do
let(:dd_dogstatsd_url) { 'tcppp://somehost' }
it 'raises an exception' do
expect do
subject.new(dogstatsd_url: dogstatsd_url, host: host, port: port, socket_path: socket_path)
end.to raise_error(ArgumentError)
end
end

context 'with no args and DD_DOGSTATSD_URL set for UDP connection without port' do
let(:dd_dogstatsd_url) { 'udp://somehost' }

it 'creates an UDP connection' do
expect(subject.transport_type).to eq :udp
end

it 'sets host' do
expect(subject.host).to eq 'somehost'
end

it 'sets port to default port' do
expect(subject.port).to eq 8125
end

it 'sets socket_path to nil' do
expect(subject.socket_path).to eq nil
end
end

context 'with no args and DD_DOGSTATSD_URL set for UDP connection with a port' do
let(:dd_dogstatsd_url) { 'udp://somehost:1111' }

it 'creates an UDP connection' do
expect(subject.transport_type).to eq :udp
end

it 'sets host' do
expect(subject.host).to eq 'somehost'
end

it 'sets port' do
expect(subject.port).to eq 1111
end

it 'sets socket_path to nil' do
expect(subject.socket_path).to eq nil
end
end

context 'with no args and DD_DOGSTATSD_URL set for UDS connection' do
let(:dd_dogstatsd_url) { 'unix:///path/to/some-unix.sock' }

it 'creates an UDS connection' do
expect(subject.transport_type).to eq :uds
end

it 'sets host to nil' do
expect(subject.host).to eq nil
end

it 'sets port to nil' do
expect(subject.port).to eq nil
end

it 'sets socket_path' do
expect(subject.socket_path).to eq '/path/to/some-unix.sock'
end
end

context 'with both DD_AGENT_HOST and DD_DOGSTATSD_SOCKET set' do
let(:dd_agent_host) { 'some-host' }
let(:dd_dogstatsd_socket) { '/some/socket' }

it 'raises an exception' do
expect do
subject.new(host: host, port: port, socket_path: socket_path)
end.to raise_error(
ArgumentError,
'Both UDP (DD_AGENT_HOST/DD_DOGSTATSD_PORT some-host:) and UDS (DD_DOGSTATSD_SOCKET /some/socket) environment variables are set. Set only one or the other.')
end.to raise_error(ArgumentError)
end
end

context 'with both DD_DOGSTATSD_URL and DD_AGENT_HOST, udp variant' do
let(:dd_agent_host) { 'some-host' }
let(:dd_dogstatsd_port) { '1111' }
let(:dd_dogstatsd_url) { 'udp://localhost' }

# DD_DOGSTATSD_URL has priority

it 'sets host' do
expect(subject.host).to eq 'localhost'
end

it 'sets port' do
expect(subject.port).to eq 8125
end

it 'sets socket_path' do
expect(subject.socket_path).to eq nil
end
end

context 'with both DD_DOGSTATSD_URL and DD_DOGSTATSD_SOCKET, udp variant' do
let(:dd_dogstatsd_socket) { '/not/used.sock' }
let(:dd_dogstatsd_url) { 'udp://localhost:2222' }

# DD_DOGSTATSD_URL has priority

it 'sets host' do
expect(subject.host).to eq 'localhost'
end

it 'sets port' do
expect(subject.port).to eq 2222
end

it 'sets socket_path' do
expect(subject.socket_path).to eq nil
end
end

context 'with both DD_DOGSTATSD_URL and DD_AGENT_HOST, uds variant' do
let(:dd_agent_host) { 'some-host' }
let(:dd_dogstatsd_port) { '1111' }
let(:dd_dogstatsd_url) { 'unix:///path/to/unix.sock' }

# DD_DOGSTATSD_URL has priority

it 'sets host to nil' do
expect(subject.host).to eq nil
end

it 'sets port to nil' do
expect(subject.port).to eq nil
end

it 'sets socket_path' do
expect(subject.socket_path).to eq '/path/to/unix.sock'
end
end

context 'with both DD_DOGSTATSD_URL and DD_DOGSTATSD_SOCKET, uds variant' do
let(:dd_dogstatsd_socket) { '/not/used.sock' }
let(:dd_dogstatsd_url) { 'unix:///path/to/unix.sock' }

# DD_DOGSTATSD_URL has priority

it 'sets host to nil' do
expect(subject.host).to eq nil
end

it 'sets port to nil' do
expect(subject.port).to eq nil
end

it 'sets socket_path' do
expect(subject.socket_path).to eq '/path/to/unix.sock'
end
end

Expand Down

0 comments on commit 0677902

Please sign in to comment.