Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 49 additions & 51 deletions ddtrace/tracer/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ func newConfig(opts ...StartOption) (*config, error) {
}
if c.statsdClient == nil {
// configure statsd client
addr := resolveDogstatsdAddr(c.dogstatsdAddr, af)
addr := resolveDogstatsdAddr(c.dogstatsdAddr, af, defaultSocketDSD)
globalconfig.SetDogstatsdAddr(addr)
c.dogstatsdAddr = addr
}
Expand Down Expand Up @@ -543,41 +543,59 @@ func resolveTraceTransport(cfg *internalconfig.Config) (traceURL string, headers
return traceURL, datadogHeaders()
}

// resolveDogstatsdAddr resolves the Dogstatsd address to use, based on the user-defined
// address and the agent-reported port. If the agent reports a port, it will be used
// instead of the user-defined address' port. UDS paths are honored regardless of the
// agent-reported port.
func resolveDogstatsdAddr(addr string, af agentFeatures) string {
if addr == "" {
// no config defined address; use host and port from env vars
// or default to localhost:8125 if not set
addr = defaultDogstatsdAddr()
}
agentport := af.StatsdPort
if agentport == 0 {
// the agent didn't report a port; use the already resolved address as
// features are loaded from the trace-agent, which might be not running
return addr
}
// the agent reported a port
host, _, err := net.SplitHostPort(addr)
if err != nil {
// parsing the address failed; use the already resolved address as is
return addr
// resolveDogstatsdAddr resolves the Dogstatsd address to use with the following
// priority order:
// 1. Explicitly configured address via WithDogstatsdAddr.
// 2. Environment variables DD_DOGSTATSD_HOST (or DD_AGENT_HOST) and DD_DOGSTATSD_PORT.
// 3. Auto-discovery: UDS socket at /var/run/datadog/dsd.socket, agent-reported port,
// or the default localhost:8125.
func resolveDogstatsdAddr(configAddr string, af agentFeatures, socketDSDPath string) string {
// 1. User explicitly set address via WithDogstatsdAddr; honor it as-is.
if configAddr != "" {
return configAddr
}

// 2. Build address from dogstatsd-specific environment variables.
// DD_AGENT_HOST is only used as a host fallback when DD_DOGSTATSD_HOST or
// DD_DOGSTATSD_PORT is set — on its own it does not trigger this path.
envHost := env.Get("DD_DOGSTATSD_HOST")
envPort := env.Get("DD_DOGSTATSD_PORT")
if envHost != "" || envPort != "" {
host := envHost
if host == "" {
host = env.Get("DD_AGENT_HOST")
}
if host == "" {
host = defaultHostname
}
// For the port, prefer the env var, then the agent-reported port
// (loaded from the trace-agent /info endpoint), then the default.
port := envPort
if port == "" && af.StatsdPort != 0 {
port = strconv.Itoa(af.StatsdPort)
} else if port == "" {
port = defaultStatsdPort
}
return net.JoinHostPort(host, port)
}
if host == "unix" {
// no need to change the address because it's a UDS connection
// and these don't have ports
return addr

// 3. No user configuration at all — auto-discover.
// Check for the UDS socket first; this is the preferred transport when available.
if _, err := os.Stat(socketDSDPath); err == nil {
return "unix://" + socketDSDPath
}
// For TCP, use DD_AGENT_HOST as the hostname if available, otherwise localhost.
host := env.Get("DD_AGENT_HOST")
if host == "" {
// no host was provided; use the default hostname
host = defaultHostname
}
// use agent-reported address if it differs from the user-defined TCP-based protocol URI
// we have a valid host:port address; replace the port because the agent knows better
addr = net.JoinHostPort(host, strconv.Itoa(agentport))
return addr
// Use the agent-reported port if available. Agent features are loaded from
// the trace-agent, which may not be running — in that case StatsdPort is 0.
if af.StatsdPort != 0 {
return net.JoinHostPort(host, strconv.Itoa(af.StatsdPort))
}
// Fall back to default port 8125.
return net.JoinHostPort(host, defaultStatsdPort)
}

func newStatsdClient(c *config) (internal.StatsdClient, error) {
Expand All @@ -587,26 +605,6 @@ func newStatsdClient(c *config) (internal.StatsdClient, error) {
return internal.NewStatsdClient(c.dogstatsdAddr, statsTags(c))
}

// defaultDogstatsdAddr returns the default connection address for Dogstatsd.
func defaultDogstatsdAddr() string {
envHost, envPort := env.Get("DD_DOGSTATSD_HOST"), env.Get("DD_DOGSTATSD_PORT")
if envHost == "" {
envHost = env.Get("DD_AGENT_HOST")
}
if _, err := os.Stat(defaultSocketDSD); err == nil && envHost == "" && envPort == "" {
// socket exists and user didn't specify otherwise via env vars
return "unix://" + defaultSocketDSD
}
host, port := defaultHostname, defaultStatsdPort
if envHost != "" {
host = envHost
}
if envPort != "" {
port = envPort
}
return net.JoinHostPort(host, port)
}

type integrationConfig struct {
Instrumented bool `json:"instrumented"` // indicates if the user has imported and used the integration
Available bool `json:"available"` // indicates if the user is using a library that can be used with DataDog integrations
Expand Down
225 changes: 146 additions & 79 deletions ddtrace/tracer/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,8 +561,8 @@ func TestTracerOptionsDefaults(t *testing.T) {
defer tracer.Stop()
assert.NoError(t, err)
c := tracer.config
assert.Equal(t, "localhost:8125", c.dogstatsdAddr)
assert.Equal(t, "localhost:8125", globalconfig.DogstatsdAddr())
assert.Equal(t, "localhost:123", c.dogstatsdAddr)
assert.Equal(t, "localhost:123", globalconfig.DogstatsdAddr())
})

t.Run("env-port: agent not available", func(t *testing.T) {
Expand All @@ -585,8 +585,8 @@ func TestTracerOptionsDefaults(t *testing.T) {
assert.NoError(t, err)
defer tracer.Stop()
c := tracer.config
assert.Equal(t, "localhost:8125", c.dogstatsdAddr)
assert.Equal(t, "localhost:8125", globalconfig.DogstatsdAddr())
assert.Equal(t, "localhost:123", c.dogstatsdAddr)
assert.Equal(t, "localhost:123", globalconfig.DogstatsdAddr())
})

t.Run("env-all: agent not available", func(t *testing.T) {
Expand All @@ -611,8 +611,8 @@ func TestTracerOptionsDefaults(t *testing.T) {
assert.NoError(t, err)
defer tracer.Stop()
c := tracer.config
assert.Equal(t, "10.1.0.12:8125", c.dogstatsdAddr)
assert.Equal(t, "10.1.0.12:8125", globalconfig.DogstatsdAddr())
assert.Equal(t, "10.1.0.12:4002", c.dogstatsdAddr)
assert.Equal(t, "10.1.0.12:4002", globalconfig.DogstatsdAddr())
})

t.Run("option: agent not available", func(t *testing.T) {
Expand Down Expand Up @@ -1022,80 +1022,147 @@ func TestDefaultHTTPClient(t *testing.T) {
})
}

func TestDefaultDogstatsdAddr(t *testing.T) {
t.Run("no-socket", func(t *testing.T) {
assert.Equal(t, defaultDogstatsdAddr(), "localhost:8125")
})

t.Run("host-env", func(t *testing.T) {
t.Setenv("DD_DOGSTATSD_HOST", "111.111.1.1")
t.Setenv("DD_AGENT_HOST", "222.222.2.2")
assert.Equal(t, "111.111.1.1:8125", defaultDogstatsdAddr())
})

t.Run("port-env", func(t *testing.T) {
t.Setenv("DD_DOGSTATSD_PORT", "8111")
assert.Equal(t, defaultDogstatsdAddr(), "localhost:8111")
t.Setenv("DD_AGENT_HOST", "222.222.2.2")
assert.Equal(t, defaultDogstatsdAddr(), "222.222.2.2:8111")
})

t.Run("host-env+port-env", func(t *testing.T) {
t.Setenv("DD_DOGSTATSD_HOST", "111.111.1.1")
t.Setenv("DD_DOGSTATSD_PORT", "8888")
t.Setenv("DD_AGENT_HOST", "222.222.2.2")
assert.Equal(t, "111.111.1.1:8888", defaultDogstatsdAddr())
})

t.Run("host-env+socket", func(t *testing.T) {
t.Setenv("DD_DOGSTATSD_HOST", "111.111.1.1")
assert.Equal(t, "111.111.1.1:8125", defaultDogstatsdAddr())
f, err := os.CreateTemp("", "dsd.socket")
if err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(f.Name())
defer func(old string) { defaultSocketDSD = old }(defaultSocketDSD)
defaultSocketDSD = f.Name()
assert.Equal(t, "111.111.1.1:8125", defaultDogstatsdAddr())
})

t.Run("port-env+socket", func(t *testing.T) {
t.Setenv("DD_DOGSTATSD_PORT", "8111")
assert.Equal(t, defaultDogstatsdAddr(), "localhost:8111")
f, err := os.CreateTemp("", "dsd.socket")
if err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(f.Name())
defer func(old string) { defaultSocketDSD = old }(defaultSocketDSD)
defaultSocketDSD = f.Name()
assert.Equal(t, defaultDogstatsdAddr(), "localhost:8111")
})
func TestResolveDogstatsdAddr(t *testing.T) {
socketFile, err := os.CreateTemp("", "dsd.socket")
require.NoError(t, err)
require.NoError(t, socketFile.Close())
t.Cleanup(func() { os.RemoveAll(socketFile.Name()) })
socketPath := socketFile.Name()

tests := []struct {
name string
configAddr string
af agentFeatures
env map[string]string
socketPath string
expected string
}{
{
name: "defaults",
expected: "localhost:8125",
},
{
name: "host-env",
env: map[string]string{"DD_DOGSTATSD_HOST": "111.111.1.1", "DD_AGENT_HOST": "222.222.2.2"},
expected: "111.111.1.1:8125",
},
{
name: "port-env",
env: map[string]string{"DD_DOGSTATSD_PORT": "8111"},
expected: "localhost:8111",
},
{
name: "port-env+agent-host-env",
env: map[string]string{"DD_DOGSTATSD_PORT": "8111", "DD_AGENT_HOST": "222.222.2.2"},
expected: "222.222.2.2:8111",
},
{
name: "host-env+port-env",
env: map[string]string{"DD_DOGSTATSD_HOST": "111.111.1.1", "DD_DOGSTATSD_PORT": "8888", "DD_AGENT_HOST": "222.222.2.2"},
expected: "111.111.1.1:8888",
},
{
name: "host-env+socket",
env: map[string]string{"DD_DOGSTATSD_HOST": "111.111.1.1"},
socketPath: socketPath,
expected: "111.111.1.1:8125",
},
{
name: "port-env+socket",
env: map[string]string{"DD_DOGSTATSD_PORT": "8111"},
socketPath: socketPath,
expected: "localhost:8111",
},
{
name: "socket",
socketPath: socketPath,
expected: "unix://" + socketPath,
},
// DD_AGENT_HOST alone should not trigger the env var path;
// it falls through to auto-discovery.
{
name: "agent-host-env-only+socket",
env: map[string]string{"DD_AGENT_HOST": "222.222.2.2"},
socketPath: socketPath,
expected: "unix://" + socketPath,
},
{
name: "agent-host-env-only",
env: map[string]string{"DD_AGENT_HOST": "222.222.2.2"},
expected: "222.222.2.2:8125",
},
{
name: "agent-host-env-only+agent-port",
env: map[string]string{"DD_AGENT_HOST": "222.222.2.2"},
af: agentFeatures{StatsdPort: 9876},
expected: "222.222.2.2:9876",
},
// configAddr (priority 1) wins over everything.
{
name: "config-addr",
configAddr: "custom:9999",
expected: "custom:9999",
},
{
name: "config-addr+env",
configAddr: "custom:9999",
env: map[string]string{"DD_DOGSTATSD_HOST": "111.111.1.1", "DD_DOGSTATSD_PORT": "8111"},
expected: "custom:9999",
},
{
name: "config-addr+socket",
configAddr: "custom:9999",
socketPath: socketPath,
expected: "custom:9999",
},
{
name: "config-addr+agent-port",
configAddr: "custom:9999",
af: agentFeatures{StatsdPort: 9876},
expected: "custom:9999",
},
// Agent-reported port used as fallback when env host is set but no env port.
{
name: "host-env+agent-port",
env: map[string]string{"DD_DOGSTATSD_HOST": "111.111.1.1"},
af: agentFeatures{StatsdPort: 9876},
expected: "111.111.1.1:9876",
},
// Env port wins over agent-reported port.
{
name: "host-env+port-env+agent-port",
env: map[string]string{"DD_DOGSTATSD_HOST": "111.111.1.1", "DD_DOGSTATSD_PORT": "8111"},
af: agentFeatures{StatsdPort: 9876},
expected: "111.111.1.1:8111",
},
// Auto-discovery: agent-reported port when no env and no socket.
{
name: "agent-port",
af: agentFeatures{StatsdPort: 9876},
expected: "localhost:9876",
},
// Auto-discovery: socket wins over agent-reported port.
{
name: "socket+agent-port",
af: agentFeatures{StatsdPort: 9876},
socketPath: socketPath,
expected: "unix://" + socketPath,
},
}

t.Run("socket", func(t *testing.T) {
defer func(old string) { os.Setenv("DD_AGENT_HOST", old) }(os.Getenv("DD_AGENT_HOST"))
defer func(old string) { os.Setenv("DD_DOGSTATSD_PORT", old) }(os.Getenv("DD_DOGSTATSD_PORT"))
os.Unsetenv("DD_AGENT_HOST")
os.Unsetenv("DD_DOGSTATSD_PORT")
f, err := os.CreateTemp("", "dsd.socket")
if err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(f.Name())
defer func(old string) { defaultSocketDSD = old }(defaultSocketDSD)
defaultSocketDSD = f.Name()
assert.Equal(t, defaultDogstatsdAddr(), "unix://"+f.Name())
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear all relevant env vars so that each subtest is isolated
// from the host environment and other subtests.
for _, key := range []string{"DD_DOGSTATSD_HOST", "DD_DOGSTATSD_PORT", "DD_AGENT_HOST"} {
t.Setenv(key, "")
}
for k, v := range tt.env {
t.Setenv(k, v)
}
assert.Equal(t, tt.expected, resolveDogstatsdAddr(tt.configAddr, tt.af, tt.socketPath))
})
}
}

func TestServiceName(t *testing.T) {
Expand Down
Loading