diff --git a/lib/datadog/statsd/connection_cfg.rb b/lib/datadog/statsd/connection_cfg.rb index c52b5a2..0259041 100644 --- a/lib/datadog/statsd/connection_cfg.rb +++ b/lib/datadog/statsd/connection_cfg.rb @@ -23,12 +23,25 @@ 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.", ) @@ -36,13 +49,11 @@ def initialize_with_constructor_args(host: nil, port: nil, socket_path: nil) 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 @@ -50,9 +61,13 @@ 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 @@ -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 diff --git a/spec/statsd/connection_cfg_spec.rb b/spec/statsd/connection_cfg_spec.rb index 9b7726a..bc110a5 100644 --- a/spec/statsd/connection_cfg_spec.rb +++ b/spec/statsd/connection_cfg_spec.rb @@ -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 @@ -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 } @@ -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 @@ -144,6 +147,75 @@ 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' } @@ -151,9 +223,85 @@ 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