feat(metrics): Add runtime-switchable DogStatsD UDS support, migrate Rust to metrics-exporter-dogstatsd, and propagate runtime global tags#7796
Conversation
Add UDS as an alternative transport for reporting metrics to DogStatsd, alongside the existing UDP path. In Python, this is controlled by the `use_dogstatsd_uds` runtime config flag. In Rust, the socket path is passed through the consumer config pipeline and preferred over UDP when present. The default socket path is `/var/run/datadog/dsd.socket`, configurable via the `SNUBA_DOGSTATSD_SOCKET_PATH` environment variable. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/df5Kw0WzWjehG9JtCoOzCyP26xppU-Mei2HymuQJatM
Extract the common global tag setup into a single block, choosing the backend (UDS vs UDP) first and then applying shared configuration. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/PXyfvNv6PB8v5UP2payHqvUh6lSTy_8lE7QH6H8tU9o
…ixUpstream Replace the custom UnixUpstream middleware with cadence's built-in BufferedUnixMetricSink, which already provides the same 512-byte buffered UDS transport. This removes a file and leverages a well-tested library instead. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/mpfDfVQ7ALBfclRYP4cw_R3ypl2IhChN8cxM7pcaUyY
Pass storage and consumer_group tags to the UDS backend via StatsdRecorder::with_tag instead of the statsdproxy AddGlobalTags middleware. set_global_tag is still called for Sentry scope tags. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/V26YRfVXmvlDYDieSQ0UFx5soTUFz1D5coqoNKUGApk
…ogstatsd Use metrics-exporter-dogstatsd for both UDP and UDS transports, removing the cadence and statsdproxy dependencies. The new DogStatsDBackend provides native DogStatsD protocol support with client-side aggregation built in. This simplifies the metrics pipeline from statsdproxy (Upstream/AggregateMetrics/AddGlobalTags) + cadence to a single exporter with built-in aggregation and global labels. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/wuD5d2s6g_PVGjhylOUjPEALVpYSHWc_Uqq4OQ8RD6Q
…support # Conflicts: # rust_snuba/Cargo.lock Agent transcript: https://claudescope.sentry.dev/share/hGwX0afKzwa4EE72ryquHfk8W77IOSqA5TGW56k5K58
The DogStatsDBuilder's set_global_prefix() already prepends the prefix to all metric keys. The manual prefix in record_metric() was applying it a second time, resulting in names like snuba.consumer.snuba.consumer.key. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/yNYhYQs5p2MewtRCxK7gIDSQmeP3GQUcNzmewFdG1n0
Without this, the socket path always has a value, causing the Rust consumer to unconditionally prefer UDS over UDP and crash in environments where the socket doesn't exist. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/gKz1Hb8a0jqABMB6pXUrvW8vduz1-NWXBsA1ck7OB20
The metrics-exporter-dogstatsd crate defaults to sending histograms as distributions (d). Disable this to keep using DogStatsD histograms (h), matching the existing behavior. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/8WIrttAgOHZtq804L6DAZsBUhsP7cvdCPxGosPgN3mg
Re-resolve dependencies so sentry_arroyo 2.38.3 uses sentry-core 0.41.0 instead of pulling in a second sentry-core 0.46.2. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/rjZ2lzFmeZvcAjnOk0zEK8nc-zhGDPIFUu4itpW1eAQ
Update rust-toolchain.toml from pinned 1.85.0 to stable channel so newer dependency versions (e.g. time 0.3.47) can be used without manually tracking MSRV. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/wbVtV_UrXNIcixIo7NhOCJn5XUpV-Zx38aXqCPjQvpE
rdkafka-sys 4.10.0 (pulled in by sentry_arroyo 2.38.3) requires libcurl headers for building librdkafka from source. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/gKXtckTzheTqwwfUjVL-pvJdVs2zUGu5PMvDmGHK0Kk
sentry_protos 0.7.1 added TraceItemType::ProcessingError. Map it to "processing_errors" for COGS tracking. Co-Authored-By: Claude <noreply@anthropic.com>
- Allow clippy::result_large_err on accumulator closures in factory_v2 - Allow clippy::large_enum_variant on errors::Message enum - Use .is_multiple_of() instead of manual % check in generic_metrics Co-Authored-By: Claude <noreply@anthropic.com>
Upgrade rdkafka-sys to 4.10.0 (matching relay) and install libcurl4-openssl-dev in CI/Docker where needed for librdkafka 2.12.1. Fix uuid type ambiguity in replays tests exposed by Rust 1.94, update bench to use renamed DogStatsDBackend, suppress result_large_err lint in python_processor_infinite, and use static metadata string in DogStatsD adapter instead of misleading module_path!(). Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/syhPGGyt06o4xJ3ijv1AtTtCMCh5v_jYJ-BqcSsYyUA
Update insta snapshots to account for new fields (fingerprint, etc.) from updated sentry-kafka-schemas and sentry_protos versions. Co-Authored-By: Claude <noreply@anthropic.com> Agent transcript: https://claudescope.sentry.dev/share/Ky2hBIs_19wk0Z4QAJIvvGZlLkSg8i82ONi2XjxTzc4
create_metrics() returned DummyMetricsBackend whenever DOGSTATSD_HOST and DOGSTATSD_PORT were both unset, and that guard ran before the UDS check. A UDS-only deployment (use_dogstatsd_uds + DOGSTATSD_SOCKET_PATH, no UDP host/port) would therefore silently fall back to the no-op backend and drop all metrics -- exactly the failure mode when switching fully over to UDS. Evaluate the UDS transport before the host/port guards so UDS works as an independent transport, matching the Rust create_dogstatsd_backend() behavior. Adds a test for the UDS-only (no host/port) case. Reported by Seer code review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
The previous UDS-only fix nested the host/port guards inside 'if not use_uds:', which broke mypy's narrowing on the UDP path (host: str | None / port: int | None no longer matched DogStatsd's str/int params), failing the pre-commit mypy hook. Restructure so the host/port ValueError guard runs at function scope after the UDS early-return: UDS still works without host/port, and mypy narrows host/port to non-None for the UDP branch. Behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
…metrics create_metrics() runs at snuba.environment import time (module-level 'metrics = create_metrics(...)'). snuba.state imports snuba.environment and reads environment.metrics at its own import time, so importing snuba.state from inside create_metrics() before environment finishes initializing is a circular import. The previous UDS change read the use_dogstatsd_uds flag (importing snuba.state) unconditionally, which broke the no-metrics path -- the Docker image's 'snuba --help' smoke test (no DOGSTATSD_* env set) crashed with 'partially initialized module snuba.environment has no attribute metrics'. Only import snuba.state / read the flag when DOGSTATSD_SOCKET_PATH is configured. The no-metrics path now returns DummyMetricsBackend without touching snuba.state (as before), UDS-only still works, and host/port type narrowing is preserved. Adds a regression test asserting the dummy path never calls state.get_config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
create_metrics() runs while snuba.environment is being imported (module-level metrics = create_metrics(...)). snuba.state binds MetricsWrapper(environment.metrics) at its own import time, so importing snuba.state from create_metrics() before environment finishes is a circular import. The prior socket-path gate still hit this whenever DOGSTATSD_SOCKET_PATH was set -- including the documented prepare step (socket set, flag off) -- which would crash process startup with AttributeError (flagged by Cursor Bugbot). Note master's create_metrics never imported snuba.state; the dependency was introduced with the UDS flag. Move the use_dogstatsd_uds lookup into the DogStatsd client factory, which DatadogMetricsBackend invokes lazily on first metric emission (per thread), long after imports complete. The no-metrics path returns DummyMetricsBackend and the transport choice (UDP vs UDS) is decided at runtime. Static config still picks Dummy vs real backend at create time, so no snuba.state import happens during environment import for any configuration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 033eaf7. Configure here.
The global_tags tests share the process-wide GLOBAL_TAGS static and run in parallel, so test_set_and_get_global_tags (set env=production) could race test_overwrite_global_tag (set env=staging) on the same key and flake. Give each test its own key prefix so they can't collide. Reported by Seer code review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
…ne transport Three reviewers flagged that Python and Rust disagreed for a socket-only deploy (DOGSTATSD_SOCKET_PATH set, no host/port) with the flag off: Python sent over the socket while Rust disabled metrics. Make both consistent and the flag authoritative: host/port (UDP) is the baseline transport, and UDS is used only when use_dogstatsd_uds is on AND a socket path is configured. With the flag off it never falls back to the socket, so host/port stay configured as the UDP/rollback target -- matching the documented 'set both' rollout. A deployment with only a socket and no host/port now has no transport and returns the dummy/disabled backend in both languages (previously Python used the socket). Python: host/port unset -> DummyMetricsBackend (static decision, no snuba.state import), so the circular-import fix is preserved; the flag is read lazily in the client factory only when host/port are configured. Rust select_transport: no host/port -> Disabled regardless of the flag. Tests updated for both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
metrics-exporter-dogstatsd enables client telemetry by default, which emits a new family of datadog.dogstatsd.client.* metrics (metrics, packets_sent, bytes_sent, aggregated_context, ...) that the previous statsdproxy pipeline never sent. Disable it so migrating to the new backend does not change the set of metrics emitted -- the consumer sends only its own metrics, over UDP or UDS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
There was a problem hiding this comment.
This seems fine to me, I'm wondering if you were able to test locally at all; not with unit tests, but by starting a snuba consumer <storage> and seeing some data flow across the wire with the new metrics recorder, maybe with a UDP capture utility or something.
I've just seen that sort of test catch problems before merge before.
|
Good call. Short of a full UDP: UDS ( Confirms:
Still to do: the end-to-end capture from a running consumer in staging. Generated by Claude Code |
Resolve conflicts with master: - CI workflows (ci.yml, ddl-changes.yml): keep the libcurl system-deps steps and take master's setup-uv bump to v8.3.0. - Cargo.toml: keep the higher dependency versions and the statsdproxy removal from this branch, and adopt master's serde_json raw_value feature (now required by the generic_metrics parser that dropped serde(flatten)). - metrics/statsd.rs: master migrated the Rust runtime-config reader to sentry-options and removed runtime_config::get_str_config. Read the use_dogstatsd_uds rollout flag directly from the legacy Redis runtime config via snuba.state.get_str_config so it stays live-flippable from snuba-admin (no deploy) and reads the same store as the Python create_metrics(); failures default to the UDP path. cargo check/clippy/fmt clean; cargo test --workspace has only the pre-existing ClickHouse/Python infra-dependent failures (same on master). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
…support # Conflicts: # .github/workflows/ci.yml # .github/workflows/ddl-changes.yml
Move the use_dogstatsd_uds rollout flag off the legacy Redis runtime config
onto the sentry-options "snuba" namespace, so both the Python and Rust
DogStatsD backends read it from the same store the rest of snuba's runtime
config now uses (migrated in the sentry-options move on master).
- Add a boolean `use_dogstatsd_uds` key to sentry-options/schemas/snuba/schema.json.
- Rust: use_dogstatsd_uds_enabled() now reads sentry_options::options("snuba")
instead of calling snuba.state.get_str_config over pyo3. init_with_schemas
runs at consumer startup before the metrics backend is built, and any read
failure defaults to false (the stable UDP path).
- Python: create_metrics() reads snuba.state.sentry_options.get_option
("use_dogstatsd_uds", False) lazily in the client factory. The lazy read still
avoids the snuba.state circular import at snuba.environment import time; by
first-emit time init_options() has run, and get_option defaults to False if not.
- Update the create_metrics tests to patch get_option (boolean) instead of
state.get_config.
The flag stays authoritative and restart-scoped: flipping the option and
restarting switches transports with no redeploy, and host/port remain the UDP
rollback target.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
…pshots The test_rust job failed on `processors::llm_proxy_cost::tests::schema`. Root cause was accidental dependency drift in rust_snuba/Cargo.toml, not the metrics change: this branch pinned `schemars = "0.8.22"` (master pins "0.8.16") and `json-schema-diff = "0.1.8"` (master "0.1.7"), plus a wholesale bump of ~25 other crates that had over-resolved the lockfile (e.g. a stray schemars 0.9/1.2 pulled in via serde_with 3.21). The schema tests diff each processor's schemars-generated schema against the canonical sentry-kafka-schemas schema and snapshot the breaking changes. schemars 0.8.22 emits numeric `format` annotations (uint64/uint32/double) that 0.8.16 does not, which added FormatAdd diffs. A prior session regenerated most snapshots against 0.8.22, but the llm-proxy-cost processor was added to master later with a 0.8.16-style snapshot, so it failed under this branch's 0.8.22. Fix by realigning with master instead of touching snapshots: - Reset Cargo.toml/Cargo.lock to master and re-apply only the metrics deps (add metrics + metrics-exporter-dogstatsd, drop cadence + statsdproxy). The Cargo.toml diff vs master is now metrics-only; the lock diff is just the metrics subtree swap. - Revert the 0.8.22-regenerated schema snapshots to master's (events, profiles-call-tree, snuba-profile-chunks) and delete the two snapshots that only exist under 0.8.22 (processed-profiles, snuba-queries) -- under 0.8.16 those processors have an empty schema diff and write no snapshot. All Rust schema tests pass; the remaining local test failures are the Python/ClickHouse-infra tests that also fail on master in a bare environment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
Seer flagged that create_metrics()'s client factory read the use_dogstatsd_uds option on each thread's first metric emission. DatadogMetricsBackend builds a DogStatsd client per thread, so flipping the option at runtime without a restart could leave already-warmed threads on the old transport while new threads picked up the new one -- UDP and UDS metrics from the same process. Resolve the UDS-vs-UDP decision once per process and cache it (double-checked locking), so every thread's client uses the same transport. The value is fixed until restart, matching the documented "flip the option, then restart" contract; a mid-process flip is now a clean no-op instead of splitting threads across transports. Reading at create_metrics() time (the literal "read at startup" suggestion) isn't possible -- it runs during snuba.environment import and reading sentry-options there is a circular import -- so the read stays lazy but is now memoized process-wide rather than per-thread. Add a test that emits on the main thread with the flag off, flips it on, then emits from a fresh thread and asserts that thread still builds a UDP client. The Rust backend already reads the option once at consumer startup before installing the global recorder, so it is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
Previously a deployment with only DOGSTATSD_SOCKET_PATH set (no host/port) emitted no metrics -- host/port was treated as the required baseline transport, so a socket alone resolved to disabled/Dummy even with use_dogstatsd_uds on. Change the gating so use_dogstatsd_uds selects the *preferred* transport and a single configured transport is used regardless of the flag: - socket + host/port : flag picks UDS (on) or UDP (off) - host/port only : UDP - socket only : UDS <-- was: no metrics - neither : no metrics The flag stays authoritative when both transports are configured (a socket never overrides an available UDP target while off), so host/port remain the UDP rollback target; it just no longer gates the socket-only case, where UDS is the only transport available. - Rust select_transport(): UDS when a socket is set and (flag || no UDP target), else UDP when host/port set, else disabled. - Python create_metrics(): return Dummy only when neither host/port nor socket is set; validate host/port are both-or-neither (partial UDP config still raises); the lazily-resolved decision uses UDS when a socket is set and (flag || no host/port). - Update the sentry-options schema description and both docstrings; add Rust uds_when_socket_only + rename disabled_when_nothing_configured; add Python socket-only-uses-UDS and partial-host/port-raises tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
…heme) Stop hardcoding the `unixgram://` scheme in the Rust UDS path so the full socket address can be supplied via SNUBA_DOGSTATSD_SOCKET_PATH. DogStatsDBackend::new_uds now passes socket_path straight to the exporter's with_remote_address, which parses the transport from the scheme (`unixgram://` datagram, `unix://` stream). The same env var already reaches the Python datadog client verbatim, and that client strips a `unixgram://`/`unixstream://`/`unix://` scheme itself and selects the socket kind, so no scheme handling is needed on the Python side either -- one full address (e.g. `unixgram:///run/dogstatsd.sock`) now works for both runtimes. Using `unixgram://` preserves the previous datagram behavior. - Rust: new_uds forwards socket_path unchanged; doc updated; select_transport tests use a scheme'd address. - Python: document the verbatim/scheme contract in create_metrics; tests use a scheme'd address and assert it is passed through unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3
This PR touches rust_snuba/src/processors/replays.rs. Its CODEOWNERS entry listed only @getsentry/replay-backend, and since CODEOWNERS is last-match-wins (owners do not merge), that overrode the /rust_snuba @getsentry/owners-snuba rule and left owners-snuba off the file. Add @getsentry/owners-snuba alongside replay-backend, matching every other team-scoped entry in the file (e.g. eap_spans.rs and the dataset configs), so both teams own the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FrDg6sMR1aG1xJf6VBod3

Summary
metrics-exporter-dogstatsd(native DogStatsD protocol, client-side aggregation, UDP + Unix domain socket transports)use_dogstatsd_udssentry-optionset_global_tag()(e.g.assigned_partitions,min_partitionduring Kafka rebalancing) are included in every DogStatsD metric payloadstorage/consumer_grouptag setting in the Rust consumers, since those are already passed as static labels to the DogStatsD builderTransport selection
The
use_dogstatsd_udssentry-option (in thesnubanamespace, read by both Python viasnuba.state.sentry_options.get_optionand Rust viasentry_options::options("snuba"), same key) selects the preferred transport. A deployment configured with only one transport uses that one regardless of the flag; the flag is authoritative only when both are configured (a configured socket never overrides an available UDP target while the flag is off), so host/port stay configured as the UDP rollback target.false(default)trueDOGSTATSD_HOST/DOGSTATSD_PORTcome fromSNUBA_STATSD_ADDR(orDOGSTATSD_HOST+DOGSTATSD_PORT). The socket address comes fromSNUBA_DOGSTATSD_SOCKET_PATHand is a full address including the transport scheme (e.g.unixgram:///run/dogstatsd.sockfor datagram,unix:///run/dogstatsd.sockfor stream). It is passed verbatim to both runtimes — the Rust exporter parses the scheme, and the Python datadog client strips it and selects the socket kind — so no scheme is hardcoded on either side.Switching between UDP and UDS (rollout / rollback)
SNUBA_DOGSTATSD_SOCKET_PATH, and leave theuse_dogstatsd_udssentry-option atfalse. Everything runs on UDP.use_dogstatsd_udssentry-option totrue, then restart the consumers (rolling restart — no redeploy).falseand restart. Instant rollback, no redeploy.Runtime config source
The
use_dogstatsd_udsflag lives in the sentry-optionssnubanamespace (schema:sentry-options/schemas/snuba/schema.json), the same store the rest of snuba's runtime config now uses. Both runtimes read the same key: Python throughsnuba.state.sentry_options.get_option("use_dogstatsd_uds", False)and Rust throughsentry_options::options("snuba"). Reads are defensive — an uninitialized options client, a missing key, or a wrong type all default tofalse(prefer UDP).Implementation
Python (
create_metrics()): returnsDummyMetricsBackendonly when neither a UDP (host/port) nor a UDS (socket) transport is configured — a static decision, becausecreate_metrics()runs atsnuba.environmentimport time and importingsnuba.state.sentry_optionsthere would be a circular import. When a transport is configured it builds theDogStatsdclient lazily; the factory resolves the transport once per process on first metric emission (by which pointinit_options()has run) — UDS when a socket is set and the flag selects it (or there's no UDP target), otherwise UDP. Resolving once per process keeps every thread on the same transport (DatadogMetricsBackendbuilds a client per thread). The socket address is passed to the datadog client verbatim (the client strips the scheme).Rust: the new
DogStatsDBackendadapts arroyo'sRecordertrait to themetricscrate facade installed by the exporter. Histograms are emitted as DogStatsD histograms (h), not distributions, so the agent-side.avg/.count/.median/.95percentile/.maxsub-metrics are unchanged — timers move from themswire type toh, which Datadog treats identically. A sharedcreate_dogstatsd_backend()with a pureselect_transport()applies the same gating as Python; bothconsumer.rsandaccepted_outcomes_consumer.rsgo through it. The socket address is passed to the exporter'swith_remote_addressverbatim (it parses the scheme). Theuse_dogstatsd_uds_enabled()reader usessentry_options::options("snuba")(native, no pyo3), andinit_with_schemasruns at consumer startup before the metrics backend is built.Runtime global tags: a thread-safe global tag map (a sorted
BTreeMapbehind aLazyLock/RwLock) stores tags set at runtime.set_global_tag()writes to both this map and the Sentry scope;record_metric()appends every entry to each metric, so tags likeassigned_partitionsandmin_partitionappear in DogStatsD payloads.Follows the same pattern as getsentry/relay#5675.
Test plan
cargo test --workspace— passes (the only failures require ClickHouse/Redis/Python infra and fail identically onmasterin a bare environment)cargo clippy --workspace --all-targets -- -D warnings— no warningscargo fmt --check— cleanselect_transportgating matrix (incl. socket-only → UDS)create_metricsUDP/UDS/socket-only selection, process-wide transport resolution, verbatim socket address, and the no-transport dummy pathuse_dogstatsd_udssentry-option totrue+ restart)🤖 Generated with Claude Code