Documents
ADR-0002 - Select Crates and Logging
ADR-0002 - Select Crates and Logging
Type
External
Status
Published
Created
Apr 18, 2026
Updated
Apr 18, 2026
Updated by
Dosu Bot
Source
View

Status#

Accepted

Currency note (2026-04-18 backfill): Some crate selections below have evolved since this ADR was accepted. The database selection (tokio_rusqlite) was superseded by redb per the ADR-0001 reaffirmation (2026-04-16). Configuration loading moved from config + notify to figment. atty has been superseded upstream by is-terminal. These updates are not rewritten into the Decision section below (doing so would change the substance of an accepted decision); treat current Cargo.toml as the authoritative per-crate source and this ADR as the selection principles and rationale.

Context#

Following ADR-0001 (Rust language selection), DaemonEye needs to select specific crates for each major functional area. The crate ecosystem is mature but offers multiple viable options for most use cases. Selections must balance:

  • Maturity — Production-ready crates with stable APIs.
  • Performance — Minimal overhead and efficient resource usage.
  • Cross-Platform — Consistent behavior across Linux, macOS, Windows.
  • Maintainability — Well-documented crates with active development.
  • EvilBit Labs Standards — Alignment with preferred tooling and patterns.
    This ADR captures the per-functional-area crate choices and the rationale for each so future additions and replacements have a documented baseline.

Decision#

Crate selections#

Async Runtime: Tokio#

  • Selected: tokio v1.x with full features
  • Alternatives weighed: async-std (smaller ecosystem), smol (less mature, limited tooling)
  • Rationale: industry standard for Rust async applications; excellent ecosystem integration with database and HTTP crates; superior cross-platform file system and network I/O support; built-in task scheduling and timer capabilities needed for daemon operation.

CLI Framework: Clap v4#

  • Selected: clap v4.x with derive feature, clap_complete for shell completions
  • Alternatives weighed: structopt (deprecated), argh (less feature-complete)
  • Rationale: modern derive-based API reduces boilerplate; built-in subcommands, validation, help generation; shell completion generation for bash/zsh/fish/PowerShell; active development with strong backward compatibility guarantees.

Configuration Management: config + notify (superseded by figment)#

  • Originally selected: config v0.13.x, notify v5.x for file watching
  • Alternatives weighed: figment (was considered "more complex than needed" at the time), manual parsing (error-prone)
  • Rationale at the time: multiple-format support (YAML/TOML/JSON) with consistent API; environment variable integration with prefix support; hierarchical merging with clear precedence; serde integration; cross-platform live reload via notify.
  • Current state: DaemonEye now uses figment for configuration loading. The config-crate selection did not survive contact with real hierarchical-precedence requirements.

Database: tokio_rusqlite (superseded by redb)#

  • Originally selected: tokio_rusqlite v0.4.x
  • Alternatives weighed: sqlx (required build-time database), sea-orm (excessive complexity)
  • Rationale at the time: async SQLite operations compatible with Tokio runtime; ACID properties in async context; connection pooling and transaction management; WAL-mode performance.
  • Current state: DaemonEye now uses redb (pure-Rust embedded ACID store). See ADR-0001 (Reaffirmed 2026-04-16) for the migration rationale: pure Rust, no C FFI, MVCC concurrent access, zero-copy deserialization, single-file embedded deployment.

Process Enumeration: sysinfo#

  • Selected: sysinfo v0.29.x with platform-specific augmentations
  • Alternatives weighed: direct OS API calls (too much platform-specific boilerplate)
  • Rationale: best cross-platform process enumeration library for Rust; consistent API across Linux/macOS/Windows; efficient memory usage and caching.
    Platform augmentations:
PlatformAugmentation
Linuxprocfs crate for additional process details
WindowsDirect Windows API calls where sysinfo is insufficient
macOSNative system calls for macOS-specific process attributes

Serialization: Serde Ecosystem#

  • Selected: serde v1.x, serde_json, serde_yaml, toml
  • Rationale: industry standard; zero-cost abstractions with excellent performance; derive macros reduce boilerplate while maintaining type safety. (Note: serde_yaml is deprecated upstream as of 2024 — consider migration to serde_yml or similar.)

Logging: Tracing Ecosystem#

  • Selected: tracing v0.1.x, tracing-subscriber, tracing-appender
  • Alternatives weighed: env_logger (less structured), log (no span support)
  • Rationale: structured logging with span-based context tracking; excellent async support with proper correlation across await points; JSON formatting; configurable filtering and multiple output targets.

HTTP Client: reqwest + rustls#

  • Selected: reqwest v0.11.x with rustls-tls feature
  • Alternatives weighed: hyper (too low-level), ureq (synchronous, blocks async runtime)
  • Rationale: modern async HTTP client; rustls provides memory-safe TLS; connection pooling and timeout management.

SMTP Client: lettre + rustls#

  • Selected: lettre v0.10.x with rustls-tls feature
  • Rationale: pure Rust SMTP client with async support; STARTTLS and SMTPS via rustls; attachment support for detailed alert reports.

Syslog (Unix only)#

  • Selected: syslog v6.x (feature-gated for Unix platforms)
  • Rationale: native syslog integration; UDP and Unix domain socket transports; minimal overhead for high-frequency logging.

Metrics: metrics + prometheus#

  • Selected: metrics v0.21.x, metrics-exporter-prometheus v0.12.x
  • Rationale: lightweight collection with zero cost when disabled; Prometheus exposition format for industry-standard monitoring; optional feature gate.

Testing Stack#

CratePurpose
insta v1.xSnapshot testing of detector outputs
criterion v0.5.xStatistical performance benchmarking
proptest v1.xProperty-based testing of configuration and deduplication
assert_cmd v2.xCLI integration testing (black-box)
tempfile v3.xTemporary database creation in tests

Feature gates strategy#

[features]
default = ["config", "syslog-unix", "webhook", "email"]
config = ["config/yaml", "notify"]
syslog-unix = ["syslog"]
webhook = ["reqwest/rustls-tls"]
email = ["lettre/rustls-tls"]
metrics = ["metrics", "metrics-exporter-prometheus"]
health-endpoint = ["warp", "tokio/net"]
win-eventlog = ["winlog"] # Future milestone

Color and terminal policy#

Color output precedence (highest to lowest):

  1. --no-color CLI flag (explicit override)
  2. NO_COLOR environment variable (any value disables color)
  3. TERM=dumb environment variable
  4. TTY detection (via is-terminalatty is deprecated)

Dependency security posture#

All selected crates are:

  • Pinned to specific minor versions for reproducible builds.
  • Audited using cargo audit in CI.
  • Updated following security advisories with testing validation.
  • Evaluated for supply chain security using cargo deny.
    Security-focused selections: rustls over OpenSSL (memory safety + smaller attack surface); serde over manual parsing (prevents injection vulnerabilities); async DB access over threaded (prevents race conditions).

Consequences#

Positive#

  • Ecosystem maturity. All selected crates are production-ready with active maintenance.
  • Performance. Zero-cost abstractions and async-first design enable the performance targets in ADR-0001.
  • Security. Memory-safe implementations (rustls, serde, async DB) reduce vulnerability surface.
  • Cross-platform. Consistent behavior across all target platforms.
  • Developer experience. Comprehensive tooling (clap derive, tracing spans, insta snapshots, criterion benchmarks) reduces boilerplate and accelerates debugging.

Negative#

  • Large dependency tree increases build times. Mitigation: cargo-chef and layer caching in CI.
  • Multiple crates increase binary size. Mitigation: profile with cargo-bloat; eliminate unused features.
  • Team needs familiarity with crate APIs. Mitigation: maintain internal usage guides.
  • Coordinating updates across many dependencies. Mitigation: automated dependency updates (Dependabot/Renovate) with comprehensive testing.
  • Some selections rot over time and require follow-up (e.g., tokio_rusqlite → redb, config → figment, attyis-terminal). Mitigation: treat this ADR as the selection principles, not a frozen bill of materials; Cargo.toml is the current-state authority.

Neutral#

  • This ADR establishes selection principles (maturity, performance, cross-platform, maintainability, EvilBit standards). Individual crate replacements don't require a new ADR unless they change the principles.
  • Feature-gate strategy means some functionality is opt-in at compile time; downstream packagers need to pick the right feature matrix for their build.

Alternatives Considered#

Minimize dependencies: stdlib-only where possible#

Build DaemonEye with the minimum viable set of third-party crates, implementing common functionality (config loading, CLI parsing, logging, HTTP, SMTP, syslog, metrics, process enumeration) in-house. Pros: smallest attack surface, no supply-chain risk, full control over implementation details. Cons: massive reinvention cost, bugs in hand-rolled components that mature crates have fixed over years, worse cross-platform coverage, slower shipping velocity. Rejected — the crate ecosystem is DaemonEye's force multiplier; reinventing is net-negative.

Defer selection: per-feature decisions as implementation progresses#

Don't commit to specific crates upfront; pick whatever fits best when each feature lands. Pros: no premature commitments; can incorporate newer options that emerge mid-project. Cons: inconsistent patterns across the codebase, divergent configuration styles, repeated debate of solved questions, crate churn leaking into user-visible behavior. Rejected — up-front selection with documented rationale reduces bikeshedding and enforces consistency across the workspace.

Pick one ecosystem and stick to it (e.g., all-Hyper, all-async-std)#

Select a single async runtime / HTTP / ecosystem family and use it everywhere for maximum interop. Pros: uniform concurrency semantics; easier debugging. Cons: Tokio is the pragmatic choice on its merits anyway, and forcing other decisions to fit a single ecosystem excludes best-of-breed options like lettre for SMTP or sysinfo for process enumeration. Rejected — best-of-breed per functional area wins when the integration cost is low.

ADR-0002 - Select Crates and Logging | Dosu