diff --git a/AGENTS.md b/AGENTS.md index 465ebfc4d3..4408a02822 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -410,6 +410,39 @@ This is useful for iterating on a specific test without running the full suite. - **Dependencies**: Inspect module dependency graph in `deps.rs` - **File Watching**: Monitor file change events in `watcher.rs` +#### OpenTelemetry Tracing + +Rewatch supports OpenTelemetry (OTEL) tracing for build and watch commands. To visualize traces locally, run a Jaeger all-in-one container: + +```bash +docker run -d --name jaeger \ + -p 4317:4317 -p 4318:4318 -p 16686:16686 \ + jaegertracing/all-in-one +``` + +Then run rewatch with the OTLP endpoint set: + +```bash +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 cargo run --manifest-path rewatch/Cargo.toml -- build +``` + +Open http://localhost:16686 to view traces in the Jaeger UI. + +Note: Use `tracing::debug!` (not `log::debug!`) for events you want to appear in OTEL traces — they use separate logging systems. + +##### Honored environment variables + +Rewatch follows the OTEL spec for configuration — no rewatch-specific knobs exist. + +| Variable | Purpose | +|---|---| +| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base endpoint of the collector (e.g. `http://localhost:4318`). `/v1/traces` is appended for the trace exporter. Setting this (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is what enables telemetry — if neither is set, tracing is a no-op. | +| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Full trace endpoint used verbatim. Overrides the general endpoint for traces. | +| `OTEL_EXPORTER_OTLP_HEADERS` | Extra headers on exporter requests (e.g. `authorization=Bearer xyz`). | +| `OTEL_SERVICE_NAME` | Service name reported on spans. Defaults to `rewatch`. | +| `OTEL_RESOURCE_ATTRIBUTES` | Comma-separated `key=value` pairs added as resource attributes (e.g. `deployment.environment=ci,host.name=$HOSTNAME`). | +| `RUST_LOG` | Controls which span/event levels are captured (e.g. `RUST_LOG=info`, `RUST_LOG=rewatch=debug`). Defaults to `debug` when telemetry is enabled. | + #### Running Rewatch Directly When running the rewatch binary directly (via `cargo run` or the compiled binary) during development, you need to set environment variables to point to the local compiler and runtime. Otherwise, rewatch will try to use the installed versions: diff --git a/CHANGELOG.md b/CHANGELOG.md index 314e08424e..863bd868ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ #### :nail_care: Polish - Allow builds while watchers are running. https://github.com/rescript-lang/rescript/pull/8349 +- Build system: Add OpenTelemetry tracing support for cli commands. #### :house: Internal diff --git a/rewatch/Cargo.lock b/rewatch/Cargo.lock index 5ebb674c3f..2a16bdc86e 100644 --- a/rewatch/Cargo.lock +++ b/rewatch/Cargo.lock @@ -92,6 +92,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -117,12 +140,27 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "cc" version = "1.2.28" @@ -270,12 +308,36 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "ctrlc" -version = "3.4.7" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ + "dispatch2", "nix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -310,7 +372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -331,6 +393,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -459,12 +530,197 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "humantime" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indicatif" version = "0.17.11" @@ -498,6 +754,22 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -515,6 +787,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -551,11 +832,17 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.174" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libredox" @@ -574,12 +861,27 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.7.5" @@ -598,11 +900,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + [[package]] name = "nix" -version = "0.30.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -623,7 +936,7 @@ dependencies = [ "inotify", "kqueue", "libc", - "mio", + "mio 0.8.11", "serde", "walkdir", "windows-sys 0.45.0", @@ -638,6 +951,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "num_cpus" version = "1.17.0" @@ -654,6 +976,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + [[package]] name = "once_cell" version = "1.21.3" @@ -667,91 +1004,258 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "opentelemetry" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", + "tracing", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "opentelemetry-http" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] [[package]] -name = "portable-atomic" -version = "1.11.1" +name = "opentelemetry-otlp" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror", +] [[package]] -name = "proc-macro2" -version = "1.0.95" +name = "opentelemetry-proto" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ - "unicode-ident", + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", ] [[package]] -name = "quote" -version = "1.0.40" +name = "opentelemetry_sdk" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" dependencies = [ - "proc-macro2", + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand", + "thiserror", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "rayon" -version = "1.10.0" +name = "pin-project" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ - "either", - "rayon-core", + "pin-project-internal", ] [[package]] -name = "rayon-core" -version = "1.12.1" +name = "pin-project-internal" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "redox_syscall" -version = "0.5.13" +name = "pin-project-lite" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] -name = "regex" +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "zerovec", ] [[package]] -name = "regex-automata" -version = "0.4.9" +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ @@ -766,6 +1270,40 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rescript" version = "13.0.0-alpha.4" @@ -785,6 +1323,9 @@ dependencies = [ "log", "notify", "num_cpus", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", "rayon", "regex", "serde", @@ -793,6 +1334,9 @@ dependencies = [ "serde_path_to_error", "sysinfo", "tempfile", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", ] [[package]] @@ -805,9 +1349,15 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -886,6 +1436,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -898,6 +1469,28 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "strsim" version = "0.11.1" @@ -915,6 +1508,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sysinfo" version = "0.29.11" @@ -952,6 +1565,229 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "libc", + "mio 1.1.0", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -970,12 +1806,36 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -992,6 +1852,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1015,6 +1884,7 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] @@ -1032,6 +1902,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -1064,6 +1947,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -1096,7 +1989,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -1105,6 +1998,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.45.0" @@ -1132,6 +2031,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -1171,13 +2088,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -1196,6 +2130,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1214,6 +2154,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1232,12 +2178,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1256,6 +2214,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1274,6 +2238,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1292,6 +2262,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1310,6 +2286,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -1319,6 +2301,35 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -1338,3 +2349,57 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rewatch/Cargo.toml b/rewatch/Cargo.toml index e80f10b40f..15a555a85f 100644 --- a/rewatch/Cargo.toml +++ b/rewatch/Cargo.toml @@ -12,7 +12,12 @@ clap = { version = "4.3", features = ["derive"] } clap-verbosity-flag = "2.2" console = "0.15.5" convert_case = "0.6.0" -ctrlc = "3.4.4" +# The "termination" feature extends ctrlc's handler to also catch SIGTERM and +# SIGHUP (not just SIGINT). This is required so the telemetry guard's Drop +# runs on those signals and buffered OTLP spans get flushed before exit. +# Without it, `kill ` or a container/systemd stop would silently drop +# the last batch of traces. +ctrlc = { version = "3.4.4", features = ["termination"] } env_logger = "0.10" futures = "0.3.25" futures-timer = "3.0.2" @@ -29,6 +34,20 @@ serde_path_to_error = "0.1.16" sysinfo = "0.29.10" tempfile = "3.10.1" +# OpenTelemetry for tracing +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-opentelemetry = "0.32" +opentelemetry = "0.31" +opentelemetry_sdk = "0.31" +# default-features disabled to skip metrics/logs support and the internal-logs +# feature we don't need. We only use HTTP/protobuf trace export via blocking +# reqwest — no Tokio runtime required. +opentelemetry-otlp = { version = "0.31", default-features = false, features = [ + "http-proto", + "reqwest-blocking-client", + "trace", +] } [profile.release] codegen-units = 1 diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs index 88394eddda..66ea61743f 100644 --- a/rewatch/src/build.rs +++ b/rewatch/src/build.rs @@ -28,6 +28,7 @@ use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; +use tracing::{info_span, instrument}; fn is_dirty(module: &Module) -> bool { match module.source_type { @@ -56,6 +57,7 @@ pub struct CompilerArgs { pub parser_args: Vec, } +#[instrument(name = "rewatch.compiler_args", skip_all, fields(file_path = %rescript_file_path.display()))] pub fn get_compiler_args(rescript_file_path: &Path) -> Result { let filename = &helpers::get_abs_path(rescript_file_path); let current_package = helpers::get_abs_path( @@ -127,6 +129,7 @@ pub fn get_compiler_info(project_context: &ProjectContext) -> Result, filter: &Option, @@ -237,6 +240,7 @@ impl fmt::Display for IncrementalBuildError { } } +#[instrument(name = "build.incremental", skip_all, fields(module_count = build_state.modules.len()))] pub fn incremental_build( build_state: &mut BuildCommandState, default_timing: Option, @@ -279,6 +283,7 @@ pub fn incremental_build( warnings } Err(err) => { + let _error_span = info_span!("build.parse_error").entered(); logs::finalize(&build_state.packages); if !plain_output && show_progress { @@ -374,6 +379,7 @@ pub fn incremental_build( } pb.finish(); if !compile_errors.is_empty() { + let _error_span = info_span!("build.compile_error").entered(); if show_progress { if plain_output { eprintln!("Compiled {num_compiled_modules} modules") @@ -389,6 +395,7 @@ pub fn incremental_build( } } if helpers::contains_ascii_characters(&compile_warnings) { + let _warning_span = info_span!("build.compile_warning").entered(); eprintln!("{}", &compile_warnings); } if initial_build { @@ -482,6 +489,7 @@ pub fn write_build_ninja(build_state: &BuildCommandState) { } #[allow(clippy::too_many_arguments)] +#[instrument(name = "rewatch.build", skip_all, fields(working_dir = %path.display()))] pub fn build( filter: &Option, path: &Path, diff --git a/rewatch/src/build/clean.rs b/rewatch/src/build/clean.rs index 73e77987dd..2ecc4de32f 100644 --- a/rewatch/src/build/clean.rs +++ b/rewatch/src/build/clean.rs @@ -13,6 +13,7 @@ use rayon::prelude::*; use std::io::Write; use std::path::{Path, PathBuf}; use std::time::Instant; +use tracing::instrument; fn remove_ast(package: &packages::Package, source_file: &Path) { let _ = std::fs::remove_file(helpers::get_compiler_asset( @@ -102,6 +103,7 @@ fn clean_source_files(build_state: &BuildState, root_config: &Config) { // TODO: change to scan_previous_build => CompileAssetsState // and then do cleanup on that state (for instance remove all .mjs files that are not in the state) +#[instrument(name = "clean.cleanup_previous_build", skip_all)] pub fn cleanup_previous_build( build_state: &mut BuildCommandState, compile_assets_state: CompileAssetsState, @@ -332,6 +334,7 @@ pub fn cleanup_after_build(build_state: &BuildCommandState) { }); } +#[instrument(name = "rewatch.clean", skip_all, fields(working_dir = %path.display()))] pub fn clean(path: &Path, show_progress: bool, plain_output: bool, prod: bool) -> Result<()> { let project_context = ProjectContext::new(path)?; let compiler_info = build::get_compiler_info(&project_context)?; diff --git a/rewatch/src/build/compile.rs b/rewatch/src/build/compile.rs index 5247342a2a..2a7ceb849c 100644 --- a/rewatch/src/build/compile.rs +++ b/rewatch/src/build/compile.rs @@ -20,6 +20,7 @@ use std::path::PathBuf; use std::process::Command; use std::sync::OnceLock; use std::time::SystemTime; +use tracing::{info_span, instrument}; /// Execute js-post-build command for a compiled JavaScript file. /// The command runs in the directory containing the rescript.json that defines it. @@ -27,6 +28,13 @@ use std::time::SystemTime; fn execute_post_build_command(cmd: &str, js_file_path: &Path, working_dir: &Path) -> Result<()> { let full_command = format!("{} {}", cmd, js_file_path.display()); + let _span = info_span!( + "build.js_post_build", + command = %cmd, + js_file = %js_file_path.display(), + ) + .entered(); + debug!( "Executing js-post-build: {} (in {})", full_command, @@ -72,6 +80,7 @@ fn execute_post_build_command(cmd: &str, js_file_path: &Path, working_dir: &Path } } +#[instrument(name = "build.compile", skip_all)] pub fn compile( build_state: &mut BuildCommandState, show_progress: bool, @@ -162,6 +171,13 @@ pub fn compile( loop_count, ); + let wave_span = info_span!( + "build.compile_wave", + wave = loop_count, + file_count = in_progress_modules.len(), + ); + let _wave_entered = wave_span.enter(); + let current_in_progres_modules = in_progress_modules.clone(); let results = current_in_progres_modules @@ -196,6 +212,31 @@ pub fn compile( )) } SourceType::SourceFile(source_file) => { + // Construct span + attributes only when a subscriber is listening. + // Otherwise root_config.get_package_specs() (which clones) and the + // attribute formatting run per-module on the build hot path. + let _file_span = if tracing::enabled!(tracing::Level::INFO) { + let root_config = build_state.get_root_config(); + let specs = root_config.get_package_specs(); + // A package can be built under multiple specs (e.g. ESM + CJS). + // Report all of them joined by "," instead of silently picking + // the first — the compile_file span covers work for every spec. + let suffix = specs + .iter() + .map(|s| root_config.get_suffix(s)) + .collect::>() + .join(","); + let module_system = specs + .iter() + .map(|s| s.module.as_str()) + .collect::>() + .join(","); + let namespace = package.namespace.to_suffix().unwrap_or_default(); + info_span!(parent: &wave_span, "build.compile_file", module = %module_name, package = %package.name, suffix, module_system, namespace).entered() + } else { + tracing::Span::none().entered() + }; + let cmi_path = helpers::get_compiler_asset( package, &package.namespace, diff --git a/rewatch/src/build/packages.rs b/rewatch/src/build/packages.rs index a936b415f6..1754fa9d0f 100644 --- a/rewatch/src/build/packages.rs +++ b/rewatch/src/build/packages.rs @@ -18,6 +18,7 @@ use std::fs::{self}; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; use std::time::SystemTime; +use tracing::{info_span, instrument}; #[derive(Debug, Clone)] pub struct SourceFileMeta { @@ -607,6 +608,7 @@ fn extend_with_children( prod: bool, ) -> AHashMap { for (_key, package) in build.iter_mut() { + let _span = info_span!("build.load_package_sources", package = %package.name).entered(); let mut map: AHashMap = AHashMap::new(); package .source_folders @@ -656,6 +658,7 @@ fn extend_with_children( /// interface files. /// /// The two step process is there to reduce IO overhead. +#[instrument(name = "packages.make", skip_all)] pub fn make( filter: &Option, project_context: &ProjectContext, @@ -671,6 +674,7 @@ pub fn make( Ok(result) } +#[instrument(name = "packages.parse_packages", skip_all)] pub fn parse_packages(build_state: &mut BuildState) -> Result<()> { let packages = build_state.packages.clone(); for (package_name, package) in packages.iter() { diff --git a/rewatch/src/build/parse.rs b/rewatch/src/build/parse.rs index 223d49d90e..0945f30d35 100644 --- a/rewatch/src/build/parse.rs +++ b/rewatch/src/build/parse.rs @@ -12,6 +12,7 @@ use log::debug; use rayon::prelude::*; use std::path::{Path, PathBuf}; use std::process::Command; +use tracing::info_span; pub fn generate_asts( build_state: &mut BuildCommandState, @@ -20,6 +21,26 @@ pub fn generate_asts( let mut has_failure = false; let mut stderr = "".to_string(); + // Count dirty modules for the span attribute only when a subscriber is + // listening. Otherwise iterating every module every parse phase is pure + // waste on the hot path. + let parse_span = if tracing::enabled!(tracing::Level::INFO) { + let dirty_modules = build_state + .modules + .values() + .filter(|m| match &m.source_type { + SourceType::SourceFile(sf) => { + sf.implementation.parse_dirty || sf.interface.as_ref().is_some_and(|i| i.parse_dirty) + } + SourceType::MlMap(mlmap) => mlmap.parse_dirty, + }) + .count(); + info_span!("build.parse", dirty_modules = dirty_modules) + } else { + tracing::Span::none() + }; + let _span = parse_span.enter(); + build_state .modules .par_iter() @@ -53,6 +74,7 @@ pub fn generate_asts( &source_file.implementation.path.to_owned(), build_state, build_state.get_warn_error_override(), + &parse_span, ) .map_err(|e| e.to_string()); @@ -63,6 +85,7 @@ pub fn generate_asts( &interface_file_path.to_owned(), build_state, build_state.get_warn_error_override(), + &parse_span, ) { Ok(v) => Ok(Some(v)), Err(e) => Err(e.to_string()), @@ -322,11 +345,52 @@ pub fn parser_args( )) } +fn make_parse_file_span( + filename: &Path, + package: &Package, + parser_args: &[String], + build_state: &BuildState, + parent: &tracing::Span, +) -> tracing::Span { + let module_name = helpers::file_path_to_module_name(filename, &package.namespace); + let mut ppx_names: Vec = Vec::new(); + let mut experimental: Vec = Vec::new(); + for pair in parser_args.windows(2) { + if pair[0] == "-ppx" { + // Use the PPX executable's file name so paths like + // `node_modules/foo/ppx.exe` reduce to `ppx.exe` cross-platform. + let name = Path::new(&pair[1]) + .file_name() + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or_else(|| pair[1].clone()); + ppx_names.push(name); + } else if pair[0] == "-enable-experimental" { + experimental.push(pair[1].clone()); + } + } + let ppx = ppx_names.join(","); + let experimental = experimental.join(","); + let root_config = build_state.get_root_config(); + let jsx = root_config.get_jsx_args().get(1).cloned().unwrap_or_default(); + let bsc_flags = config::flatten_flags(&package.config.compiler_flags).join(" "); + info_span!( + parent: parent, + "build.parse_file", + module = %module_name, + package = %package.name, + ppx = %ppx, + experimental = %experimental, + jsx = %jsx, + bsc_flags = %bsc_flags, + ) +} + fn generate_ast( package: Package, filename: &Path, build_state: &BuildState, warn_error_override: Option, + parent_span: &tracing::Span, ) -> anyhow::Result<(PathBuf, Option)> { let file_path = PathBuf::from(&package.path).join(filename); let contents = helpers::read_file(&file_path).expect("Error reading file"); @@ -341,6 +405,12 @@ fn generate_ast( warn_error_override, )?; + let _parse_span = if tracing::enabled!(tracing::Level::INFO) { + make_parse_file_span(filename, &package, &parser_args, build_state, parent_span).entered() + } else { + tracing::Span::none().entered() + }; + // generate the dir of the ast_path (it mirrors the source file dir) let ast_parent_path = package.get_build_path().join(ast_path.parent().unwrap()); helpers::create_path(&ast_parent_path); diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs index 07c3cdfb9b..60b4ebffe7 100644 --- a/rewatch/src/cli.rs +++ b/rewatch/src/cli.rs @@ -11,7 +11,7 @@ // // However, we may want to revisit the decision to use clap after the v12 release. -use std::{env, ffi::OsString, ops::Deref}; +use std::{env, ffi::OsString, ops::Deref, path::Path}; use clap::{Args, CommandFactory, Parser, Subcommand, error::ErrorKind}; use clap_verbosity_flag::InfoLevel; @@ -496,6 +496,12 @@ impl Deref for FolderArg { } } +impl AsRef for FolderArg { + fn as_ref(&self) -> &Path { + Path::new(&self.folder) + } +} + impl Deref for FilterArg { type Target = Option; diff --git a/rewatch/src/format.rs b/rewatch/src/format.rs index c4ea876e45..1c70d95987 100644 --- a/rewatch/src/format.rs +++ b/rewatch/src/format.rs @@ -7,11 +7,13 @@ use std::io::{self, Write}; use std::path::Path; use std::process::Command; use std::sync::atomic::{AtomicUsize, Ordering}; +use tracing::{info_span, instrument}; use crate::build::packages; use crate::cli::FileExtension; use clap::ValueEnum; +#[instrument(name = "rewatch.format", skip_all, fields(check, is_stdin = stdin_extension.is_some()))] pub fn format(stdin_extension: Option, check: bool, files: Vec) -> Result<()> { let bsc_path = helpers::get_bsc(); @@ -102,6 +104,7 @@ fn format_files(bsc_exe: &Path, files: Vec, check: bool) -> Result<()> { eprintln!("[format check] {file}"); incorrectly_formatted_files.fetch_add(1, Ordering::SeqCst); } else { + let _file_span = info_span!("format.write_file", file = %file).entered(); // Only write if content actually changed fs::write(file, &*formatted_content)?; } diff --git a/rewatch/src/lib.rs b/rewatch/src/lib.rs index a389e8172e..2216c16fa8 100644 --- a/rewatch/src/lib.rs +++ b/rewatch/src/lib.rs @@ -8,4 +8,5 @@ pub mod lock; pub mod project_context; pub mod queue; pub mod sourcedirs; +pub mod telemetry; pub mod watcher; diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 9d3a235435..9bafc60533 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -6,32 +6,50 @@ use std::{io::Write, path::Path}; use rescript::{ build, cli, cmd, format, lock::{LockKind, drop_lock, get_lock_or_exit}, - watcher, + telemetry, watcher, }; -fn main() -> Result<()> { +fn main() { + // Initialize telemetry (only active if OTEL_EXPORTER_OTLP_ENDPOINT is set) + let telemetry_guard = telemetry::init_telemetry(); + let otel_enabled = telemetry_guard.otel_enabled(); + + let exit_code = run_main(otel_enabled); + + // std::process::exit terminates the process without running Drops for + // values still on the stack, so the TelemetryGuard's Drop (which flushes + // buffered OTLP spans) would be skipped. Drop explicitly first. + drop(telemetry_guard); + + std::process::exit(exit_code); +} + +fn run_main(otel_enabled: bool) -> i32 { let cli = cli::parse_with_default().unwrap_or_else(|err| err.exit()); let log_level_filter = cli.verbose.log_level_filter(); - let stdout_logger = env_logger::Builder::new() - .format(|buf, record| writeln!(buf, "{}:\n{}", record.level(), record.args())) - .filter_level(log_level_filter) - .target(env_logger::fmt::Target::Stdout) - .build(); - - let stderr_logger = env_logger::Builder::new() - .format(|buf, record| writeln!(buf, "{}:\n{}", record.level(), record.args())) - .filter_level(log_level_filter) - .target(env_logger::fmt::Target::Stderr) - .build(); - - log::set_max_level(log_level_filter); - log::set_boxed_logger(Box::new(SplitLogger { - stdout: stdout_logger, - stderr: stderr_logger, - })) - .expect("Failed to initialize logger"); + // Only set up custom logger if OTEL is not enabled (OTEL sets up its own subscriber) + if !otel_enabled { + let stdout_logger = env_logger::Builder::new() + .format(|buf, record| writeln!(buf, "{}:\n{}", record.level(), record.args())) + .filter_level(log_level_filter) + .target(env_logger::fmt::Target::Stdout) + .build(); + + let stderr_logger = env_logger::Builder::new() + .format(|buf, record| writeln!(buf, "{}:\n{}", record.level(), record.args())) + .filter_level(log_level_filter) + .target(env_logger::fmt::Target::Stderr) + .build(); + + log::set_max_level(log_level_filter); + log::set_boxed_logger(Box::new(SplitLogger { + stdout: stdout_logger, + stderr: stderr_logger, + })) + .expect("Failed to initialize logger"); + } let is_tty: bool = Term::stdout().is_term() && Term::stderr().is_term(); let plain_output = !is_tty; @@ -42,36 +60,29 @@ fn main() -> Result<()> { match cli.command { cli::Command::CompilerArgs { path } => { - println!("{}", build::get_compiler_args(Path::new(&path))?); - std::process::exit(0); + exit_code(build::get_compiler_args(Path::new(&path)).map(|args| println!("{}", args))) } cli::Command::Build(build_args) => { - match build::build( + let result = build::build( &build_args.filter, - Path::new(&build_args.folder as &str), + build_args.folder.as_ref(), show_progress, build_args.no_timing, true, // create_sourcedirs is now always enabled plain_output, (*build_args.warn_error).clone(), build_args.prod, - ) { - Err(e) => { - eprintln!("{:#}", e); - std::process::exit(1) - } - Ok(_) => { - if let Some(args_after_build) = (*build_args.after_build).clone() { - cmd::run(args_after_build) - } - std::process::exit(0) - } - }; + ); + if result.is_ok() + && let Some(args_after_build) = (*build_args.after_build).clone() + { + cmd::run(args_after_build); + } + exit_code(result.map(|_| ())) } cli::Command::Watch(watch_args) => { let _lock = get_lock_or_exit(LockKind::Watch, &watch_args.folder); - - match watcher::start( + exit_code(watcher::start( &watch_args.filter, show_progress, &watch_args.folder, @@ -80,22 +91,31 @@ fn main() -> Result<()> { plain_output, (*watch_args.warn_error).clone(), watch_args.prod, - ) { - Err(e) => { - eprintln!("{:#}", e); - std::process::exit(1) - } - Ok(_) => Ok(()), - } + )) } cli::Command::Clean { folder, prod } => { let _lock = get_lock_or_exit(LockKind::Build, &folder); - let result = build::clean::clean(Path::new(&folder as &str), show_progress, plain_output, prod); - let _lock = drop_lock(LockKind::Build, &folder); + let code = exit_code(build::clean::clean( + folder.as_ref(), + show_progress, + plain_output, + prod, + )); + let _ = drop_lock(LockKind::Build, &folder); + code + } + cli::Command::Format { stdin, check, files } => exit_code(format::format(stdin, check, files)), + } +} - result +/// Map a command's Result<()> to a process exit code, printing the error on failure. +fn exit_code(result: Result<()>) -> i32 { + match result { + Ok(()) => 0, + Err(e) => { + eprintln!("{:#}", e); + 1 } - cli::Command::Format { stdin, check, files } => format::format(stdin, check, files), } } diff --git a/rewatch/src/telemetry.rs b/rewatch/src/telemetry.rs new file mode 100644 index 0000000000..0af6c20b5e --- /dev/null +++ b/rewatch/src/telemetry.rs @@ -0,0 +1,164 @@ +//! OpenTelemetry setup for rewatch. +//! +//! Provides optional tracing export via OTLP HTTP when OTEL_EXPORTER_OTLP_ENDPOINT is set. +//! When the environment variable is not set, tracing is disabled (no-op). + +use opentelemetry::trace::TracerProvider as _; +use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig}; +use opentelemetry_sdk::Resource; +use opentelemetry_sdk::trace::SdkTracerProvider; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::fmt; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +/// Guard that ensures telemetry is properly flushed and shut down. +/// Drop this at the end of main to flush all spans. +pub struct TelemetryGuard { + provider: Option, + /// Whether OTEL tracing was initialized (affects logger setup in main). + otel_enabled: bool, +} + +impl TelemetryGuard { + /// Returns whether OTLP span export was successfully initialized. + pub fn otel_enabled(&self) -> bool { + self.otel_enabled + } +} + +impl Drop for TelemetryGuard { + fn drop(&mut self) { + if let Some(provider) = self.provider.take() { + // Force flush - this exports any buffered spans + if let Err(e) = provider.force_flush() { + log::warn!("Error flushing tracer provider: {}", e); + } + // Shutdown + if let Err(e) = provider.shutdown() { + log::warn!("Error shutting down tracer provider: {}", e); + } + } + } +} + +/// Initialize OpenTelemetry tracing if OTEL_EXPORTER_OTLP_ENDPOINT is set. +/// +/// Returns a guard that must be kept alive for the duration of the program. +/// When the guard is dropped, it flushes all pending spans. +/// +/// If OTEL_EXPORTER_OTLP_ENDPOINT is not set, returns a no-op guard and +/// tracing calls become no-ops. +pub fn init_telemetry() -> TelemetryGuard { + // Presence of either the general or trace-specific endpoint env var enables OTLP export. + let enabled = std::env::var_os("OTEL_EXPORTER_OTLP_ENDPOINT").is_some() + || std::env::var_os("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT").is_some(); + + if enabled { init_with_otlp() } else { init_noop() } +} + +/// Initialize with OTLP exporter. +fn init_with_otlp() -> TelemetryGuard { + // Build a blocking reqwest-backed HTTP exporter. Blocking is fine here + // because the batch span processor runs in its own background thread. + // + // The exporter reads OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (used verbatim) + // or falls back to OTEL_EXPORTER_OTLP_ENDPOINT (with /v1/traces appended), + // matching the OTEL spec — so we pass no explicit endpoint here. + let exporter = match SpanExporter::builder() + .with_http() + .with_protocol(Protocol::HttpBinary) + .build() + { + Ok(exp) => exp, + Err(e) => { + log::warn!("Failed to create OTLP exporter: {}. Falling back to no-op.", e); + return init_noop(); + } + }; + + // Resource::builder() applies the SdkProvidedResourceDetector and + // EnvResourceDetector, which already pick up OTEL_SERVICE_NAME and + // OTEL_RESOURCE_ATTRIBUTES from the environment. Only default + // service.name = "rewatch" when the user hasn't supplied their own. + let mut resource_builder = Resource::builder(); + if std::env::var_os("OTEL_SERVICE_NAME").is_none() { + resource_builder = resource_builder.with_service_name("rewatch"); + } + let resource = resource_builder.build(); + + let provider = SdkTracerProvider::builder() + .with_batch_exporter(exporter) + .with_resource(resource) + .build(); + + // Bridge the `tracing` crate into this provider. + let tracer = provider.tracer("rewatch"); + let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer); + + // Default to debug level so that diagnostic tracing::debug! events + // (e.g. in the watcher) are captured as OTEL span events. + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")); + + // Also write logs to stderr. Without this layer, `log::*` / `tracing::*` + // output is silently swallowed when telemetry is enabled, since the + // OTEL layer only exports spans — it does not print them. + let fmt_layer = fmt::layer().with_writer(std::io::stderr); + + tracing_subscriber::registry() + .with(filter) + .with(telemetry_layer) + .with(fmt_layer) + .init(); + + TelemetryGuard { + provider: Some(provider), + otel_enabled: true, + } +} + +/// Initialize with no-op tracing (no export). +fn init_noop() -> TelemetryGuard { + // Don't set up any subscriber - let main.rs handle logging normally + TelemetryGuard { + provider: None, + otel_enabled: false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn noop_guard_reports_otel_disabled() { + let guard = init_noop(); + assert!(!guard.otel_enabled()); + // Explicit drop — must not panic with provider == None. + drop(guard); + } + + #[test] + fn noop_guard_drops_cleanly_without_explicit_shutdown() { + // Implicit drop at end of scope: exercises Drop with no provider. + let _guard = init_noop(); + } + + #[test] + fn init_telemetry_without_env_is_noop() { + // SAFETY: These env vars are process-global; tests run serially in a + // single thread by default for this crate (no #[test(flavor = ...)]). + // If parallelised later, wrap in a Mutex. + // Ensure the env is clean for this check. + unsafe { + std::env::remove_var("OTEL_EXPORTER_OTLP_ENDPOINT"); + std::env::remove_var("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"); + } + + let guard = init_telemetry(); + assert!( + !guard.otel_enabled(), + "expected no-op guard when OTEL endpoint env vars are unset" + ); + } +} diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs index d6aba426f9..130a62fec1 100644 --- a/rewatch/src/watcher.rs +++ b/rewatch/src/watcher.rs @@ -267,6 +267,12 @@ async fn async_watch( ) { log::debug!("rescript.json changed -> full compile"); + tracing::debug!( + reason = "rescript.json changed", + event_kind = ?event.kind, + paths = ?event.paths, + "watcher.full_compile_triggered" + ); needs_compile_type = CompileType::Full; continue; } @@ -473,6 +479,7 @@ async fn async_watch( } #[allow(clippy::too_many_arguments)] +#[tracing::instrument(name = "rewatch.watch", skip_all, fields(working_dir = %folder))] pub fn start( filter: &Option, show_progress: bool,