convex-rust 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +137 -2
  3. package/dist/cli/bin.d.ts +2 -0
  4. package/dist/cli/bin.js +200 -0
  5. package/dist/cli/build.d.ts +146 -0
  6. package/dist/cli/build.js +902 -0
  7. package/dist/cli/demangle.d.ts +7 -0
  8. package/dist/cli/demangle.js +328 -0
  9. package/dist/cli/diagnostics.d.ts +34 -0
  10. package/dist/cli/diagnostics.js +296 -0
  11. package/dist/cli/generate.d.ts +29 -0
  12. package/dist/cli/generate.js +82 -0
  13. package/dist/cli/manifest.d.ts +24 -0
  14. package/dist/cli/manifest.js +75 -0
  15. package/dist/cli/rustApi.d.ts +78 -0
  16. package/dist/cli/rustApi.js +609 -0
  17. package/dist/cli/scaffold.d.ts +22 -0
  18. package/dist/cli/scaffold.js +114 -0
  19. package/dist/cli/shim.d.ts +30 -0
  20. package/dist/cli/shim.js +140 -0
  21. package/dist/cli/size.d.ts +17 -0
  22. package/dist/cli/size.js +73 -0
  23. package/dist/cli/split.d.ts +20 -0
  24. package/dist/cli/split.js +140 -0
  25. package/dist/cli/symbols.d.ts +30 -0
  26. package/dist/cli/symbols.js +133 -0
  27. package/dist/cli/toolchain.d.ts +53 -0
  28. package/dist/cli/toolchain.js +167 -0
  29. package/dist/cli/validators.d.ts +6 -0
  30. package/dist/cli/validators.js +66 -0
  31. package/dist/cli/wasisdk.d.ts +82 -0
  32. package/dist/cli/wasisdk.js +220 -0
  33. package/dist/cli/wasmHints.d.ts +29 -0
  34. package/dist/cli/wasmHints.js +172 -0
  35. package/dist/runtime/base64.d.ts +4 -0
  36. package/dist/runtime/base64.js +77 -0
  37. package/dist/runtime/crypto.d.ts +7 -0
  38. package/dist/runtime/crypto.js +421 -0
  39. package/dist/runtime/fastjson.d.ts +3 -0
  40. package/dist/runtime/fastjson.js +170 -0
  41. package/dist/runtime/host.d.ts +73 -0
  42. package/dist/runtime/host.js +1230 -0
  43. package/dist/runtime/http.d.ts +40 -0
  44. package/dist/runtime/http.js +157 -0
  45. package/dist/runtime/index.d.ts +2 -0
  46. package/dist/runtime/index.js +1 -0
  47. package/dist/runtime/intl.d.ts +5 -0
  48. package/dist/runtime/intl.js +69 -0
  49. package/dist/runtime/tmpfs.d.ts +129 -0
  50. package/dist/runtime/tmpfs.js +624 -0
  51. package/dist/runtime/wasi.d.ts +33 -0
  52. package/dist/runtime/wasi.js +216 -0
  53. package/package.json +57 -4
  54. package/rust/convex_fn/Cargo.toml +32 -0
  55. package/rust/convex_fn/LICENSE +201 -0
  56. package/rust/convex_fn/build.rs +3 -0
  57. package/rust/convex_fn/src/args.rs +416 -0
  58. package/rust/convex_fn/src/crypto.rs +237 -0
  59. package/rust/convex_fn/src/ctx.rs +1044 -0
  60. package/rust/convex_fn/src/dns.rs +356 -0
  61. package/rust/convex_fn/src/error.rs +179 -0
  62. package/rust/convex_fn/src/executor.rs +577 -0
  63. package/rust/convex_fn/src/fanout.rs +106 -0
  64. package/rust/convex_fn/src/fs.rs +19 -0
  65. package/rust/convex_fn/src/function_ref.rs +219 -0
  66. package/rust/convex_fn/src/http.rs +441 -0
  67. package/rust/convex_fn/src/intl.rs +75 -0
  68. package/rust/convex_fn/src/lib.rs +242 -0
  69. package/rust/convex_fn/src/logging.rs +222 -0
  70. package/rust/convex_fn/src/native.rs +360 -0
  71. package/rust/convex_fn/src/net.rs +122 -0
  72. package/rust/convex_fn/src/sys.rs +178 -0
  73. package/rust/convex_fn/src/values.rs +121 -0
  74. package/rust/convex_fn/src/wire.rs +579 -0
  75. package/rust/convex_fn_macros/Cargo.toml +13 -0
  76. package/rust/convex_fn_macros/LICENSE +201 -0
  77. package/rust/convex_fn_macros/src/derive.rs +632 -0
  78. package/rust/convex_fn_macros/src/lib.rs +338 -0
  79. package/rust/convex_reqwest/Cargo.toml +69 -0
  80. package/rust/convex_reqwest/LICENSE +201 -0
  81. package/rust/convex_reqwest/src/blocking.rs +287 -0
  82. package/rust/convex_reqwest/src/body.rs +152 -0
  83. package/rust/convex_reqwest/src/client.rs +359 -0
  84. package/rust/convex_reqwest/src/config_types.rs +150 -0
  85. package/rust/convex_reqwest/src/error.rs +241 -0
  86. package/rust/convex_reqwest/src/into_url.rs +57 -0
  87. package/rust/convex_reqwest/src/lib.rs +42 -0
  88. package/rust/convex_reqwest/src/multipart.rs +140 -0
  89. package/rust/convex_reqwest/src/request.rs +358 -0
  90. package/rust/convex_reqwest/src/response.rs +310 -0
@@ -0,0 +1,75 @@
1
+ //! Time zones and locale formatting through the host's `Intl` (`intl/*` sync ops, see
2
+ //! contracts/ABI.md "Optional sync ops: intl/*").
3
+ //!
4
+ //! A wasm32 guest has no time zone database: `chrono::Local` and `std` know only UTC. Asking for a
5
+ //! zone here never falls back to UTC; an unknown zone, or a host without these ops, is an error.
6
+ //! (To stay self-contained instead, `jiff` with its bundled tzdb or `chrono-tz` work on wasm32 too, at
7
+ //! roughly 0.4-0.5 MB of wasm per call, which the cloud pays at every cold call.)
8
+
9
+ use serde_json::{json, Value};
10
+
11
+ use crate::{Error, Result};
12
+
13
+ fn call(op: &str, args: Value) -> Result<Value> {
14
+ let out = crate::sys::sync(op, args.to_string().as_bytes());
15
+ let v: Value = serde_json::from_slice(&out)?;
16
+ match v.get("error") {
17
+ Some(err) => {
18
+ let e = Error::from_json(err);
19
+ let msg = e.message().to_string();
20
+ if msg.contains("unknown sync op") {
21
+ Err(Error::msg(format!(
22
+ "host lacks {op}: update convex-rust to >= {MIN_CONVEX_RUST} (V8 host); the wasmtime host has no intl ops, use jiff or chrono-tz with a bundled tzdb"
23
+ )))
24
+ } else {
25
+ Err(e)
26
+ }
27
+ }
28
+ None => Ok(v.get("ok").cloned().unwrap_or(Value::Null)),
29
+ }
30
+ }
31
+
32
+ /// First `convex-rust` release that serves the `intl/*` ops on V8 hosts.
33
+ pub const MIN_CONVEX_RUST: &str = "0.2.0";
34
+
35
+ /// Offset of the IANA zone `tz` from UTC at `epoch_ms`, in seconds (positive east of UTC), DST included.
36
+ /// Errors on an unknown zone; never returns 0 as a stand-in.
37
+ pub fn tz_offset_secs(tz: &str, epoch_ms: f64) -> Result<i32> {
38
+ let v = call("intl/tzOffset", json!({ "tz": tz, "epochMs": epoch_ms }))?;
39
+ v.as_i64()
40
+ .map(|n| n as i32)
41
+ .ok_or_else(|| Error::msg(format!("intl/tzOffset: unexpected reply {v}")))
42
+ }
43
+
44
+ /// Converts a wall-clock time in `tz` (milliseconds since the epoch as if the wall clock were UTC) to the real
45
+ /// epoch milliseconds. In a DST gap or overlap (a wall-clock time that does not exist, or exists twice) the
46
+ /// result is one of the two candidate instants.
47
+ pub fn local_to_epoch_ms(tz: &str, local_ms: f64) -> Result<f64> {
48
+ let first = tz_offset_secs(tz, local_ms)? as f64 * 1000.0;
49
+ let guess = local_ms - first;
50
+ let second = tz_offset_secs(tz, guess)? as f64 * 1000.0;
51
+ Ok(if second == first { guess } else { local_ms - second })
52
+ }
53
+
54
+ /// `new Intl.DateTimeFormat(locale, options).format(epoch_ms)`. `options` is the Intl options object, for example
55
+ /// `{"timeZone": "Europe/Lisbon", "dateStyle": "full", "timeStyle": "short"}`. An unknown `timeZone` or locale
56
+ /// is an error.
57
+ pub fn format_date(epoch_ms: f64, locale: &str, options: Value) -> Result<String> {
58
+ format("date", json!(epoch_ms), locale, options)
59
+ }
60
+
61
+ /// `new Intl.NumberFormat(locale, options).format(value)`, for example options
62
+ /// `{"style": "currency", "currency": "EUR"}`.
63
+ pub fn format_number(value: f64, locale: &str, options: Value) -> Result<String> {
64
+ format("number", json!(value), locale, options)
65
+ }
66
+
67
+ fn format(kind: &str, value: Value, locale: &str, options: Value) -> Result<String> {
68
+ let v = call(
69
+ "intl/format",
70
+ json!({ "kind": kind, "value": value, "locale": locale, "options": options }),
71
+ )?;
72
+ v.as_str()
73
+ .map(str::to_string)
74
+ .ok_or_else(|| Error::msg(format!("intl/format: unexpected reply {v}")))
75
+ }
@@ -0,0 +1,242 @@
1
+ //! Write Convex actions in Rust (`"use rust"`).
2
+ //!
3
+ //! ```ignore
4
+ //! use convex_fn::{action, Ctx, Result};
5
+ //!
6
+ //! #[action]
7
+ //! async fn hello(ctx: &Ctx, args: serde_json::Value) -> Result<String> {
8
+ //! let msgs = ctx.run_query("messages:list", serde_json::json!({})).await?;
9
+ //! Ok(format!("{} messages", msgs.as_array().map_or(0, Vec::len)))
10
+ //! }
11
+ //! ```
12
+ //!
13
+ //! # Logging, clock, random, env
14
+ //!
15
+ //! - `println!` goes to WASI stdout and `eprintln!` to stderr; the host turns
16
+ //! each line into a function log line (stdout at log/info level, stderr at
17
+ //! error level). No setup needed.
18
+ //! - Feature `log`: a `log::Log` implementation is installed by the `#[action]`
19
+ //! entry unless a logger is already set (`error`->3, `warn`->2, `info`->1,
20
+ //! `debug`/`trace`->0). Messages are written as-is (no target prefix).
21
+ //! - Feature `tracing`: a minimal subscriber is installed the same way; events
22
+ //! are written as `span{fields}: message key=value`, at the mapped level.
23
+ //! - `std::env::var`, `SystemTime::now`, `Instant` and `getrandom` (0.2 and 0.3,
24
+ //! hence `rand`) work through WASI.
25
+ //! - With `tokio-rt`, blocking calls (`block_on`, `reqwest::blocking`) made
26
+ //! inside an action drive the host task directly; tokio tasks spawned on the
27
+ //! `LocalSet` do not progress until the blocking call returns.
28
+ //!
29
+ //! Host interface: `contracts/ABI.md`. Function metadata: `contracts/MANIFEST.md`.
30
+
31
+ mod args;
32
+ pub mod crypto;
33
+ mod ctx;
34
+ pub mod dns;
35
+ mod error;
36
+ mod executor;
37
+ mod fanout;
38
+ pub mod fs;
39
+ pub mod intl;
40
+ mod function_ref;
41
+ mod http;
42
+ mod logging;
43
+ #[cfg(convex_native)]
44
+ pub mod native;
45
+ mod net;
46
+ mod sys;
47
+ pub mod values;
48
+ #[doc(hidden)]
49
+ pub mod wire;
50
+
51
+ pub use args::{ConvexType, Field, Validator};
52
+ pub use convex_fn_macros::{action, http_action, ConvexArgs, ConvexValue};
53
+ pub use ctx::{
54
+ ChunkSource, Chunks, Ctx, FetchRequest, FetchResponse, FetchStream, Redirect, Scheduler,
55
+ Storage, StorageFile, StorageMetadata, VectorFilter, VectorHit, VectorSearchQuery,
56
+ };
57
+ pub use error::{ConvexError, Error, Result};
58
+ pub use executor::{block_on, join_all, race, select, Either};
59
+ pub use fanout::par_map;
60
+ pub use function_ref::{
61
+ ComponentFunctionRef, FunctionAddress, FunctionHandle, FunctionRef, FunctionTarget,
62
+ };
63
+ pub use http::{BodyWriter, HttpRequest, HttpResponse};
64
+ pub use serde;
65
+ pub use serde_json::{json, Value};
66
+ pub use values::{Bytes, Float64, Int64};
67
+
68
+ /// Wire format shared with `convex_fn_test`; not public API.
69
+ #[doc(hidden)]
70
+ pub use wire as __wire;
71
+
72
+ /// Write one line to the function log at the given level.
73
+ pub mod log {
74
+ use crate::wire::LogLevel;
75
+
76
+ pub fn debug(msg: &str) {
77
+ crate::sys::log_line(LogLevel::Debug, msg)
78
+ }
79
+ pub fn info(msg: &str) {
80
+ crate::sys::log_line(LogLevel::Info, msg)
81
+ }
82
+ pub fn warn(msg: &str) {
83
+ crate::sys::log_line(LogLevel::Warn, msg)
84
+ }
85
+ pub fn error(msg: &str) {
86
+ crate::sys::log_line(LogLevel::Error, msg)
87
+ }
88
+ }
89
+
90
+ /// Hooks for running actions natively (used by `convex_fn_test`).
91
+ #[cfg(all(not(target_arch = "wasm32"), not(convex_native)))]
92
+ pub mod testing {
93
+ /// A host implementing contracts/ABI.md in-process. Byte payloads are the
94
+ /// same JSON the wasm host exchanges.
95
+ pub trait Host {
96
+ fn task_submit(&mut self, request: &[u8]) -> i64;
97
+ /// `None` means "nothing ready" (`block = false`) or "no outstanding
98
+ /// tasks" (`block = true`).
99
+ fn task_poll(&mut self, block: bool) -> Option<Vec<u8>>;
100
+ fn sync_syscall(&mut self, op: &str, args: &[u8]) -> Vec<u8>;
101
+ fn log(&mut self, level: i32, msg: &str);
102
+ }
103
+
104
+ /// Install `host` for the current thread, returning the previous one.
105
+ pub fn install_host(host: Box<dyn Host>) -> Option<Box<dyn Host>> {
106
+ crate::executor::reset();
107
+ crate::sys::install(host)
108
+ }
109
+
110
+ /// Remove and return the current thread's host.
111
+ pub fn uninstall_host() -> Option<Box<dyn Host>> {
112
+ crate::sys::uninstall()
113
+ }
114
+ }
115
+
116
+ /// Optional wasm export: frees the result buffer of a `convex_run:*` call once the host has read it
117
+ /// (contracts/V8HOST.md). `ptr`/`len` are the two halves of the packed value the call returned; that
118
+ /// buffer is an exactly sized boxed slice (see `run_export`). Hosts that do not know it just leak the buffer.
119
+ #[cfg(target_arch = "wasm32")]
120
+ #[no_mangle]
121
+ pub extern "C" fn convex_free(ptr: *mut u8, len: usize) {
122
+ if len == 0 || ptr.is_null() {
123
+ return;
124
+ }
125
+ // SAFETY: `run_export` returned `Box<[u8]>::into_raw` parts (ptr, len); the host frees them once.
126
+ unsafe { drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len))) };
127
+ }
128
+
129
+ #[doc(hidden)]
130
+ pub mod __rt {
131
+ use std::future::Future;
132
+
133
+ use serde::{de::DeserializeOwned, Serialize};
134
+
135
+ use crate::{error::Error, Ctx};
136
+
137
+ /// A fresh `Ctx` for native test hosts (`convex_fn_test`).
138
+ #[cfg(all(not(target_arch = "wasm32"), not(convex_native)))]
139
+ pub fn test_ctx() -> Ctx {
140
+ Ctx::new()
141
+ }
142
+
143
+ pub use crate::http::adapter as http_adapter;
144
+
145
+ pub use crate::args::{manifest_bytes, manifest_len, manifest_string, NoValidator, Probe};
146
+
147
+ /// Stand-in return type with no validator.
148
+ pub struct NoReturnType;
149
+
150
+ pub const fn const_bytes<const N: usize>(s: &str) -> [u8; N] {
151
+ let b = s.as_bytes();
152
+ let mut out = [0u8; N];
153
+ let mut i = 0;
154
+ while i < N {
155
+ out[i] = b[i];
156
+ i += 1;
157
+ }
158
+ out
159
+ }
160
+
161
+ /// Installs the `log` / `tracing` bridges (features `log`, `tracing`) unless
162
+ /// the user already installed their own. No-op without those features.
163
+ pub fn install_logging() {
164
+ crate::logging::install();
165
+ }
166
+
167
+ /// Body of every `convex_run:*` export.
168
+ #[cfg(target_arch = "wasm32")]
169
+ pub fn run_export<A, R, F, Fut>(args_ptr: *mut u8, args_len: usize, f: F) -> i64
170
+ where
171
+ A: DeserializeOwned,
172
+ R: Serialize,
173
+ F: FnOnce(Ctx, A) -> Fut,
174
+ Fut: Future<Output = crate::Result<R>>,
175
+ {
176
+ crate::error::install_panic_hook();
177
+ // A reused instance must not see the previous call's tasks/streams (the host restarts ids at 0).
178
+ crate::executor::reset_state();
179
+ // SAFETY: the host wrote `args_len` bytes into a buffer from `convex_alloc`.
180
+ let args = unsafe { Vec::from_raw_parts(args_ptr, args_len, args_len.max(1)) };
181
+ let envelope = run_to_envelope(&args, f);
182
+ let out = std::mem::ManuallyDrop::new(envelope.into_bytes().into_boxed_slice());
183
+ ((out.as_ptr() as u64) << 32 | out.len() as u64) as i64
184
+ }
185
+
186
+ /// Body of every `convex_run:*` export in native (`--cfg convex_native`) builds.
187
+ #[cfg(convex_native)]
188
+ pub fn run_export<A, R, F, Fut>(args_ptr: *mut u8, args_len: usize, f: F) -> i64
189
+ where
190
+ A: DeserializeOwned,
191
+ R: Serialize,
192
+ F: FnOnce(Ctx, A) -> Fut,
193
+ Fut: Future<Output = crate::Result<R>>,
194
+ {
195
+ crate::native::run_export_native(args_ptr, args_len, f)
196
+ }
197
+
198
+ /// Plain native builds (`cargo test`) never call the exports.
199
+ #[cfg(all(not(target_arch = "wasm32"), not(convex_native)))]
200
+ pub fn run_export<A, R, F, Fut>(_args_ptr: *mut u8, _args_len: usize, _f: F) -> i64
201
+ where
202
+ A: DeserializeOwned,
203
+ R: Serialize,
204
+ F: FnOnce(Ctx, A) -> Fut,
205
+ Fut: Future<Output = crate::Result<R>>,
206
+ {
207
+ unreachable!("convex_run exports are only callable on wasm32 or with --cfg convex_native")
208
+ }
209
+
210
+ /// Body of every `#[http_action]` `convex_run:*` export: the call's args are the request head.
211
+ pub fn run_http_export<F, Fut>(args_ptr: *mut u8, args_len: usize, f: F) -> i64
212
+ where
213
+ F: FnOnce(Ctx, crate::HttpRequest) -> Fut + 'static,
214
+ Fut: Future<Output = crate::Result<crate::HttpResponse>> + 'static,
215
+ {
216
+ run_export(args_ptr, args_len, crate::http::adapter(f))
217
+ }
218
+
219
+ /// Runs an action on raw Convex-JSON args and returns the result envelope
220
+ /// (contracts/ABI.md "Result envelope").
221
+ pub fn run_to_envelope<A, R, F, Fut>(args: &[u8], f: F) -> String
222
+ where
223
+ A: DeserializeOwned,
224
+ R: Serialize,
225
+ F: FnOnce(Ctx, A) -> Fut,
226
+ Fut: Future<Output = crate::Result<R>>,
227
+ {
228
+ install_logging();
229
+ let result = serde_json::from_slice::<A>(args)
230
+ .map_err(|e| Error::msg(format!("invalid arguments: {e}")))
231
+ .and_then(|a| crate::executor::block_on(f(Ctx::new(), a)))
232
+ .and_then(|r| {
233
+ serde_json::to_value(r)
234
+ .map_err(|e| Error::msg(format!("invalid return value: {e}")))
235
+ });
236
+ let envelope = match result {
237
+ Ok(v) => serde_json::json!({ "ok": v }),
238
+ Err(e) => serde_json::json!({ "error": e.to_json() }),
239
+ };
240
+ envelope.to_string()
241
+ }
242
+ }
@@ -0,0 +1,222 @@
1
+ //! Optional bridges from the `log` and `tracing` ecosystems to the host `log`
2
+ //! import (contracts/ABI.md levels: 0 debug, 1 info, 2 warn, 3 error).
3
+ //!
4
+ //! Both are installed automatically by the `#[action]` entry (and by
5
+ //! `convex_fn_test`) when the corresponding cargo feature is on and the user has
6
+ //! not installed a logger / default subscriber of their own.
7
+ //!
8
+ //! Plain `println!` / `eprintln!` need no feature: the WASI stdout / stderr
9
+ //! file descriptors are turned into log lines by the host (stdout at info,
10
+ //! stderr at error).
11
+
12
+ #[cfg(feature = "log")]
13
+ mod log_bridge {
14
+ use log_crate::{Level, LevelFilter, Log, Metadata, Record};
15
+
16
+ use crate::{sys, wire::LogLevel};
17
+
18
+ pub struct HostLogger;
19
+
20
+ pub fn level(l: Level) -> LogLevel {
21
+ match l {
22
+ Level::Trace | Level::Debug => LogLevel::Debug,
23
+ Level::Info => LogLevel::Info,
24
+ Level::Warn => LogLevel::Warn,
25
+ Level::Error => LogLevel::Error,
26
+ }
27
+ }
28
+
29
+ impl Log for HostLogger {
30
+ fn enabled(&self, _: &Metadata<'_>) -> bool {
31
+ true
32
+ }
33
+ fn log(&self, record: &Record<'_>) {
34
+ sys::log_line(level(record.level()), &record.args().to_string());
35
+ }
36
+ fn flush(&self) {}
37
+ }
38
+
39
+ static LOGGER: HostLogger = HostLogger;
40
+
41
+ pub fn install() {
42
+ if log_crate::set_logger(&LOGGER).is_ok() {
43
+ log_crate::set_max_level(LevelFilter::Trace);
44
+ }
45
+ }
46
+ }
47
+
48
+ #[cfg(feature = "tracing")]
49
+ mod tracing_bridge {
50
+ use std::{
51
+ collections::HashMap,
52
+ fmt::Write as _,
53
+ sync::{
54
+ atomic::{AtomicU64, Ordering},
55
+ Mutex,
56
+ },
57
+ };
58
+
59
+ use tracing_core::{
60
+ field::{Field, Visit},
61
+ span, Event, Level, Metadata, Subscriber,
62
+ };
63
+
64
+ use crate::{sys, wire::LogLevel};
65
+
66
+ #[derive(Default)]
67
+ struct Fields {
68
+ message: String,
69
+ rest: String,
70
+ }
71
+
72
+ impl Fields {
73
+ fn kv(&mut self, name: &str, value: std::fmt::Arguments<'_>) {
74
+ if !self.rest.is_empty() {
75
+ self.rest.push(' ');
76
+ }
77
+ let _ = write!(self.rest, "{name}={value}");
78
+ }
79
+ }
80
+
81
+ impl Visit for Fields {
82
+ fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
83
+ if field.name() == "message" {
84
+ let _ = write!(self.message, "{value:?}");
85
+ } else {
86
+ self.kv(field.name(), format_args!("{value:?}"));
87
+ }
88
+ }
89
+ fn record_str(&mut self, field: &Field, value: &str) {
90
+ if field.name() == "message" {
91
+ self.message.push_str(value);
92
+ } else {
93
+ self.kv(field.name(), format_args!("{value:?}"));
94
+ }
95
+ }
96
+ }
97
+
98
+ struct SpanData {
99
+ name: &'static str,
100
+ fields: String,
101
+ refs: usize,
102
+ }
103
+
104
+ /// Formats events as `span{fields}: message key=value ...` and writes them
105
+ /// to the host log.
106
+ #[derive(Default)]
107
+ pub struct HostSubscriber {
108
+ next_id: AtomicU64,
109
+ spans: Mutex<HashMap<u64, SpanData>>,
110
+ stack: Mutex<Vec<u64>>,
111
+ }
112
+
113
+ pub fn level(l: &Level) -> LogLevel {
114
+ match *l {
115
+ Level::TRACE | Level::DEBUG => LogLevel::Debug,
116
+ Level::INFO => LogLevel::Info,
117
+ Level::WARN => LogLevel::Warn,
118
+ Level::ERROR => LogLevel::Error,
119
+ }
120
+ }
121
+
122
+ impl Subscriber for HostSubscriber {
123
+ fn enabled(&self, _: &Metadata<'_>) -> bool {
124
+ true
125
+ }
126
+ fn new_span(&self, attrs: &span::Attributes<'_>) -> span::Id {
127
+ let mut f = Fields::default();
128
+ attrs.record(&mut f);
129
+ let id = self.next_id.fetch_add(1, Ordering::Relaxed) + 1;
130
+ self.spans.lock().unwrap().insert(
131
+ id,
132
+ SpanData {
133
+ name: attrs.metadata().name(),
134
+ fields: f.rest,
135
+ refs: 1,
136
+ },
137
+ );
138
+ span::Id::from_u64(id)
139
+ }
140
+ fn record(&self, id: &span::Id, values: &span::Record<'_>) {
141
+ let mut f = Fields::default();
142
+ values.record(&mut f);
143
+ if let Some(s) = self.spans.lock().unwrap().get_mut(&id.into_u64()) {
144
+ if !s.fields.is_empty() && !f.rest.is_empty() {
145
+ s.fields.push(' ');
146
+ }
147
+ s.fields.push_str(&f.rest);
148
+ }
149
+ }
150
+ fn record_follows_from(&self, _: &span::Id, _: &span::Id) {}
151
+ fn clone_span(&self, id: &span::Id) -> span::Id {
152
+ if let Some(s) = self.spans.lock().unwrap().get_mut(&id.into_u64()) {
153
+ s.refs += 1;
154
+ }
155
+ id.clone()
156
+ }
157
+ fn try_close(&self, id: span::Id) -> bool {
158
+ let mut spans = self.spans.lock().unwrap();
159
+ let k = id.into_u64();
160
+ if let Some(s) = spans.get_mut(&k) {
161
+ s.refs -= 1;
162
+ if s.refs == 0 {
163
+ spans.remove(&k);
164
+ return true;
165
+ }
166
+ }
167
+ false
168
+ }
169
+ fn enter(&self, id: &span::Id) {
170
+ self.stack.lock().unwrap().push(id.into_u64());
171
+ }
172
+ fn exit(&self, id: &span::Id) {
173
+ let mut st = self.stack.lock().unwrap();
174
+ if let Some(i) = st.iter().rposition(|x| *x == id.into_u64()) {
175
+ st.remove(i);
176
+ }
177
+ }
178
+ fn event(&self, event: &Event<'_>) {
179
+ let mut f = Fields::default();
180
+ event.record(&mut f);
181
+ let mut line = String::new();
182
+ {
183
+ let spans = self.spans.lock().unwrap();
184
+ for id in self.stack.lock().unwrap().iter() {
185
+ if let Some(s) = spans.get(id) {
186
+ line.push_str(s.name);
187
+ if !s.fields.is_empty() {
188
+ let _ = write!(line, "{{{}}}", s.fields);
189
+ }
190
+ line.push(':');
191
+ }
192
+ }
193
+ }
194
+ if !line.is_empty() {
195
+ line.push(' ');
196
+ }
197
+ line.push_str(&f.message);
198
+ if !f.rest.is_empty() {
199
+ if !f.message.is_empty() {
200
+ line.push(' ');
201
+ }
202
+ line.push_str(&f.rest);
203
+ }
204
+ sys::log_line(level(event.metadata().level()), &line);
205
+ }
206
+ }
207
+
208
+ pub fn install() {
209
+ // Fails (harmlessly) when a global default subscriber already exists.
210
+ let _ = tracing_core::dispatcher::set_global_default(tracing_core::Dispatch::new(
211
+ HostSubscriber::default(),
212
+ ));
213
+ }
214
+ }
215
+
216
+ /// Installs the enabled bridges unless the user already installed their own.
217
+ pub(crate) fn install() {
218
+ #[cfg(feature = "log")]
219
+ log_bridge::install();
220
+ #[cfg(feature = "tracing")]
221
+ tracing_bridge::install();
222
+ }