ray/
lib.rs

1#![forbid(unsafe_code)]
2//! Rust client for the Ray debugging app.
3//!
4//! Install (package name is `ray-dbg`, crate is `ray`):
5//!
6//! ```toml
7//! [dependencies]
8//! ray = { version = "0.1", package = "ray-dbg" }
9//! ```
10//!
11//! Fluent chaining is the intended style:
12//!
13//! ```no_run
14//! use ray::ray;
15//!
16//! ray!("Hello from Rust").green().label("init");
17//! ```
18//!
19//! Ray app workflow (screens, labels, visuals):
20//!
21//! ```no_run
22//! use ray::ray;
23//! use serde::Serialize;
24//!
25//! #[derive(Serialize)]
26//! struct User {
27//!     id: u64,
28//!     name: &'static str,
29//! }
30//!
31//! let user = User { id: 1, name: "Ada" };
32//! ray!().new_screen("Checkout flow");
33//! ray!(&user).label("user").green();
34//! ray!().table(&user).label("user table");
35//! ray!().image("https://example.com/avatar.png").label("avatar");
36//! ray!().separator();
37//! ray!().link("https://myray.app").label("Ray docs");
38//! ray!().notify("Checkout complete");
39//! ```
40//!
41//! Send multiple values in one call:
42//!
43//! ```no_run
44//! use ray::ray;
45//!
46//! ray!("first", "second", "third");
47//! ```
48//!
49//! See the caller or full trace:
50//!
51//! ```no_run
52//! use ray::ray;
53//!
54//! ray!().caller();
55//! ray!().trace();
56//! ```
57//!
58//! Render custom payloads (JSON, HTML, XML, files, URLs):
59//!
60//! ```no_run
61//! use ray::ray;
62//! use serde::Serialize;
63//!
64//! #[derive(Serialize)]
65//! struct Event {
66//!     name: String,
67//! }
68//!
69//! let event = Event { name: "started".into() };
70//! ray!().to_json(&event).label("json");
71//! ray!().html("<strong>bold html</strong>");
72//! ray!().xml("<root />");
73//! ray!().file("Cargo.toml");
74//! ray!().url("https://example.com");
75//! ```
76//!
77//! `ray_dbg!` mirrors `dbg!` by labeling the expression and returning it:
78//! It uses `Debug` formatting, so any `Debug` value works.
79//!
80//! ```no_run
81//! use ray::ray_dbg;
82//!
83//! let value = ray_dbg!(2 + 2);
84//! assert_eq!(value, 4);
85//! ```
86//!
87//! Send panic details to Ray:
88//!
89//! ```no_run
90//! ray::panic_hook();
91//! ```
92//!
93//! Pause execution (no-op stub):
94//!
95//! ```no_run
96//! use ray::ray;
97//!
98//! ray!().pause();
99//! ```
100//!
101//! Halt execution with `die` or `rd!`:
102//!
103//! ```no_run
104//! use ray::{ray, rd};
105//!
106//! ray!("fatal").die();
107//! rd!("fatal");
108//! ```
109//!
110//! Count executions and read counters:
111//!
112//! ```no_run
113//! use ray::ray;
114//!
115//! for _ in 0..3 {
116//!     ray!().count();
117//! }
118//! for _ in 0..2 {
119//!     ray!().count_named("first");
120//! }
121//! if ray!().counter_value("first") == 2 {
122//!     ray!("counter value is two!");
123//! }
124//! ```
125//!
126//! Measure time and memory usage between calls:
127//!
128//! ```no_run
129//! use ray::ray;
130//! use std::thread;
131//! use std::time::Duration;
132//!
133//! ray!().measure();
134//! thread::sleep(Duration::from_millis(200));
135//! ray!().measure();
136//! ```
137//!
138//! Display the class name of an object:
139//!
140//! ```no_run
141//! use ray::ray;
142//!
143//! let value = vec![1, 2, 3];
144//! ray!().class_name(&value);
145//! ```
146//!
147//! Update a single entry by reusing the same `Ray` handle:
148//!
149//! ```no_run
150//! use ray::ray;
151//!
152//! let mut ray = ray!("counting down");
153//! for n in (1..=3).rev() {
154//!     ray = ray.send(&n);
155//! }
156//! ray.green().small().label("count");
157//! ```
158//!
159//! Conditionally show items with `when`:
160//!
161//! ```no_run
162//! use ray::ray;
163//!
164//! ray!().when(true, |ray| ray.log(&"will be shown"));
165//! ray!().when(false, |ray| ray.log(&"will be hidden"));
166//! ```
167//!
168//! Return a value while still sending it to Ray:
169//!
170//! ```no_run
171//! use ray::ray;
172//!
173//! let value = ray!().pass("return value");
174//! assert_eq!(value, "return value");
175//! ```
176//!
177//! Show Rust runtime/build info (alias: `phpinfo`):
178//!
179//! ```no_run
180//! use ray::ray;
181//!
182//! ray!().rust_info();
183//! ray!().phpinfo_with_env(["RUST_LOG", "PATH"]);
184//! ```
185//!
186//! Display exceptions and handle fallible callables:
187//!
188//! ```no_run
189//! use ray::ray;
190//!
191//! let err = std::io::Error::new(std::io::ErrorKind::Other, "boom");
192//! ray!().exception(&err);
193//! ray!().catch(|| -> Result<(), std::io::Error> {
194//!     Err(std::io::Error::new(std::io::ErrorKind::Other, "boom"))
195//! });
196//! ```
197//!
198//! Show raw Debug output:
199//!
200//! ```no_run
201//! use ray::ray;
202//!
203//! let value = vec![1, 2, 3];
204//! ray!().raw(&value);
205//! ```
206//!
207//! Remove items and manage screens:
208//!
209//! ```no_run
210//! use ray::ray;
211//!
212//! let handle = ray!("will be removed");
213//! handle.remove();
214//! ray!().new_screen("Example screen");
215//! ray!().clear_all();
216//! ```
217//!
218//! Control app visibility and UI helpers:
219//!
220//! ```no_run
221//! use ray::ray;
222//!
223//! ray!().show_app();
224//! ray!().hide_app();
225//! ray!().notify("Hello from Ray");
226//! ray!().confetti();
227//! ```
228//!
229//! Additional helpers (limit/once, rate limiting, carbon):
230//!
231//! ```no_run
232//! use ray::{ray, ray_once};
233//! use std::time::SystemTime;
234//!
235//! ray!().limit(2).text("only twice");
236//! ray!().once().text("only once");
237//! ray!().once_send("only once (send)");
238//! ray_once!("only once (macro)");
239//! ray!().rate_limiter().max(10).per_second(5);
240//! ray!().carbon(SystemTime::now());
241//! ```
242//!
243//! Batch multiple entries into a single request:
244//!
245//! ```no_run
246//! use ray::ray;
247//!
248//! ray!()
249//!     .batch()
250//!     .log(&"first")
251//!     .label("batch")
252//!     .commit();
253//! ```
254//!
255//! Async support (feature: `transport-reqwest`):
256//!
257//! ```no_run
258//! # #[cfg(feature = "transport-reqwest")]
259//! # async fn run() {
260//! # use ray::ray_async;
261//! ray_async!().label("async").await;
262//! # }
263//! ```
264//!
265//! Tracing integration (feature: `tracing`):
266//!
267//! ```no_run
268//! # #[cfg(feature = "tracing")]
269//! # fn run() {
270//! ray::tracing::init().unwrap();
271//! tracing::info!("hello from tracing");
272//! # }
273//! ```
274//!
275//! `ray!` logs `Serialize` values as JSON. `ray_json!` is an explicit alias:
276//!
277//! ```no_run
278//! use ray::ray_json;
279//! use serde::Serialize;
280//!
281//! #[derive(Serialize)]
282//! struct Event {
283//!     name: String,
284//! }
285//!
286//! ray_json!(Event {
287//!     name: "started".to_string(),
288//! });
289//! ```
290
291mod client;
292mod config;
293mod datetime;
294mod error;
295mod info;
296mod measure;
297mod origin;
298mod panic;
299mod rate_limiter;
300mod ray;
301mod render;
302mod request;
303mod trace;
304mod url;
305
306pub mod payload;
307#[cfg(feature = "tracing")]
308pub mod tracing;
309
310pub use crate::client::Client;
311pub use crate::config::{ConfigError, RayConfig};
312pub use crate::error::RayError;
313pub use crate::origin::Origin;
314pub use crate::panic::panic_hook;
315pub use crate::rate_limiter::RateLimiterHandle;
316#[doc(hidden)]
317pub use crate::ray::mode::{Buffered, Immediate};
318pub use crate::ray::{Ray, RayBatch};
319#[cfg(feature = "transport-reqwest")]
320pub use crate::ray::{RayAsync, RayBatchAsync};
321pub use crate::request::{PayloadEnvelope, Request};
322pub use crate::url::{RayUrl, RayUrlInput};
323
324/// Create a `Ray` handle for fluent chaining or log `Serialize` values immediately.
325/// `ray!()` returns a handle, and `ray!(value, ...)` logs and returns the handle
326/// so you can keep chaining. Serialization errors are converted to strings.
327/// For Debug-only types, use `ray!().raw(&value)`.
328///
329/// `ray!(json: value)` is an alias for explicit JSON logging.
330///
331/// ```no_run
332/// use ray::ray;
333///
334/// ray!("hello").green().label("init");
335/// ray!("first", "second");
336/// ray!(json: "explicit json").label("json");
337/// ray!().log(&"structured").label("log");
338/// ```
339#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
340#[macro_export]
341macro_rules! ray {
342    () => {{
343        $crate::Ray::new_default_callsite_with_base(
344            file!(),
345            line!(),
346            module_path!(),
347            env!("CARGO_MANIFEST_DIR"),
348        )
349    }};
350    (json: $($value:expr),+ $(,)?) => {{
351        $crate::ray!($($value),+)
352    }};
353    ($($value:expr),+ $(,)?) => {{
354        let ray = $crate::Ray::new_default_callsite_with_base(
355            file!(),
356            line!(),
357            module_path!(),
358            env!("CARGO_MANIFEST_DIR"),
359        );
360        ray.log_debug(vec![
361            $(
362                match ::serde_json::to_value(&$value) {
363                    Ok(value) => value,
364                    Err(err) => ::serde_json::Value::String(format!(
365                        "<serialization error: {}>",
366                        err
367                    )),
368                }
369            ),+
370        ])
371    }};
372}
373
374#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
375#[macro_export]
376macro_rules! ray {
377    () => {{
378        $crate::Ray::new_default_callsite_with_base(
379            file!(),
380            line!(),
381            module_path!(),
382            env!("CARGO_MANIFEST_DIR"),
383        )
384        .disable()
385    }};
386    (json: $($value:expr),+ $(,)?) => {{
387        $crate::ray!($($value),+)
388    }};
389    ($($value:expr),+ $(,)?) => {{
390        $crate::Ray::new_default_callsite_with_base(
391            file!(),
392            line!(),
393            module_path!(),
394            env!("CARGO_MANIFEST_DIR"),
395        )
396        .disable()
397    }};
398}
399
400/// Log values as JSON using `Serialize`.
401/// `ray_json!()` returns a handle, and `ray_json!(value, ...)` logs and returns
402/// the handle for chaining.
403/// `ray!(json: value, ...)` is an alias if you prefer to stay on `ray!`.
404///
405/// ```no_run
406/// use ray::ray_json;
407/// use serde::Serialize;
408///
409/// #[derive(Serialize)]
410/// struct Event {
411///     name: String,
412/// }
413///
414/// ray_json!(Event {
415///     name: "started".to_string(),
416/// });
417/// ```
418#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
419#[macro_export]
420macro_rules! ray_json {
421    () => {{
422        $crate::ray!()
423    }};
424    ($($value:expr),+ $(,)?) => {{
425        $crate::ray!($($value),+)
426    }};
427}
428
429/// Debug helper that sends a value to Ray and returns it.
430/// Labels the entry with the expression string, similar to `dbg!`.
431///
432/// ```no_run
433/// use ray::ray_dbg;
434///
435/// let value = ray_dbg!(2 + 2);
436/// assert_eq!(value, 4);
437/// ```
438#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
439#[macro_export]
440macro_rules! ray_dbg {
441    () => {{
442        let location = format!("{}:{}", file!(), line!());
443        let ray = $crate::Ray::new_default_callsite_with_base(
444            file!(),
445            line!(),
446            module_path!(),
447            env!("CARGO_MANIFEST_DIR"),
448        );
449        let _ = ray.text(location).label("dbg");
450    }};
451    ($value:expr $(,)?) => {{
452        let value = $value;
453        let ray = $crate::Ray::new_default_callsite_with_base(
454            file!(),
455            line!(),
456            module_path!(),
457            env!("CARGO_MANIFEST_DIR"),
458        );
459        ray.raw(&value).label(stringify!($value));
460        value
461    }};
462    ($($value:expr),+ $(,)?) => {{
463        ( $( $crate::ray_dbg!($value) ),+, )
464    }};
465}
466
467#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
468#[macro_export]
469macro_rules! ray_dbg {
470    () => { () };
471    ($value:expr $(,)?) => {{ $value }};
472    ($($value:expr),+ $(,)?) => {{
473        ( $( $crate::ray_dbg!($value) ),+, )
474    }};
475}
476
477#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
478#[macro_export]
479macro_rules! ray_json {
480    () => {{
481        $crate::ray!()
482    }};
483    ($($value:expr),+ $(,)?) => {{
484        $crate::ray!($($value),+)
485    }};
486}
487
488/// Send values to Ray only once from the callsite.
489///
490/// ```no_run
491/// use ray::ray_once;
492///
493/// ray_once!("only once");
494/// ```
495#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
496#[macro_export]
497macro_rules! ray_once {
498    () => {{
499        $crate::ray!().once()
500    }};
501    ($($value:expr),+ $(,)?) => {{
502        let ray = $crate::ray!().once();
503        ray.log_debug(vec![
504            $(
505                match ::serde_json::to_value(&$value) {
506                    Ok(value) => value,
507                    Err(err) => ::serde_json::Value::String(format!(
508                        "<serialization error: {}>",
509                        err
510                    )),
511                }
512            ),+
513        ])
514    }};
515}
516
517#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
518#[macro_export]
519macro_rules! ray_once {
520    () => {{
521        $crate::ray!().once()
522    }};
523    ($($value:expr),+ $(,)?) => {{
524        let ray = $crate::ray!().once();
525        ray.log_debug(vec![
526            $(
527                match ::serde_json::to_value(&$value) {
528                    Ok(value) => value,
529                    Err(err) => ::serde_json::Value::String(format!(
530                        "<serialization error: {}>",
531                        err
532                    )),
533                }
534            ),+
535        ])
536    }};
537}
538
539/// Send values to Ray and terminate the process (alias of `ray!(...).die()`).
540///
541/// ```no_run
542/// use ray::rd;
543///
544/// rd!("fatal");
545/// ```
546#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
547#[macro_export]
548macro_rules! rd {
549    () => {{
550        $crate::ray!().die()
551    }};
552    ($($value:expr),+ $(,)?) => {{
553        $crate::ray!($($value),+).die()
554    }};
555}
556
557#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
558#[macro_export]
559macro_rules! rd {
560    () => {{
561        $crate::ray!().die()
562    }};
563    ($($value:expr),+ $(,)?) => {{
564        $crate::ray!($($value),+).die()
565    }};
566}
567
568#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
569#[macro_export]
570/// Create a `Ray` handle with strict error handling.
571///
572/// ```no_run
573/// use ray::ray_strict;
574///
575/// ray_strict!().try_label("strict").unwrap();
576/// ```
577macro_rules! ray_strict {
578    () => {{
579        $crate::Ray::new_default_callsite_with_base(
580            file!(),
581            line!(),
582            module_path!(),
583            env!("CARGO_MANIFEST_DIR"),
584        )
585        .strict(true)
586    }};
587    ($($value:expr),+ $(,)?) => {{
588        let ray = $crate::Ray::new_default_callsite_with_base(
589            file!(),
590            line!(),
591            module_path!(),
592            env!("CARGO_MANIFEST_DIR"),
593        )
594        .strict(true);
595        ray.try_log_debug(vec![
596            $(
597                match ::serde_json::to_value(&$value) {
598                    Ok(value) => value,
599                    Err(err) => ::serde_json::Value::String(format!(
600                        "<serialization error: {}>",
601                        err
602                    )),
603                }
604            ),+
605        ])
606        .expect("ray_strict! failed to send payload");
607        ray
608    }};
609}
610
611#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
612#[macro_export]
613/// Create a `Ray` handle with strict error handling.
614///
615/// ```no_run
616/// use ray::ray_strict;
617///
618/// ray_strict!().try_label("strict").unwrap();
619/// ```
620macro_rules! ray_strict {
621    () => {{
622        $crate::Ray::new_default_callsite_with_base(
623            file!(),
624            line!(),
625            module_path!(),
626            env!("CARGO_MANIFEST_DIR"),
627        )
628        .disable()
629    }};
630    ($($value:expr),+ $(,)?) => {{
631        $crate::Ray::new_default_callsite_with_base(
632            file!(),
633            line!(),
634            module_path!(),
635            env!("CARGO_MANIFEST_DIR"),
636        )
637        .disable()
638    }};
639}
640
641#[cfg(feature = "transport-reqwest")]
642#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
643#[macro_export]
644/// Create a `RayAsync` handle for fluent async chaining.
645///
646/// ```no_run
647/// use ray::ray_async;
648///
649/// #[tokio::main]
650/// async fn main() {
651///     ray_async!().label("async label").await;
652/// }
653/// ```
654macro_rules! ray_async {
655    () => {{
656        $crate::RayAsync::new_default_callsite_with_base(
657            file!(),
658            line!(),
659            module_path!(),
660            env!("CARGO_MANIFEST_DIR"),
661        )
662    }};
663}
664
665#[cfg(feature = "transport-reqwest")]
666#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
667#[macro_export]
668/// Create a `RayAsync` handle for fluent async chaining.
669///
670/// ```no_run
671/// use ray::ray_async;
672///
673/// #[tokio::main]
674/// async fn main() {
675///     ray_async!().label("async label").await;
676/// }
677/// ```
678macro_rules! ray_async {
679    () => {{
680        $crate::RayAsync::new_default_callsite_with_base(
681            file!(),
682            line!(),
683            module_path!(),
684            env!("CARGO_MANIFEST_DIR"),
685        )
686        .disable()
687    }};
688}
689
690#[cfg(feature = "transport-reqwest")]
691#[cfg(any(not(feature = "debug-macros"), debug_assertions))]
692#[macro_export]
693/// Create a `RayAsync` handle with strict error handling.
694///
695/// ```no_run
696/// use ray::ray_async_strict;
697///
698/// #[tokio::main]
699/// async fn main() {
700///     ray_async_strict!().try_label("async label").await.unwrap();
701/// }
702/// ```
703macro_rules! ray_async_strict {
704    () => {{
705        $crate::RayAsync::new_default_callsite_with_base(
706            file!(),
707            line!(),
708            module_path!(),
709            env!("CARGO_MANIFEST_DIR"),
710        )
711        .strict(true)
712    }};
713}
714
715#[cfg(feature = "transport-reqwest")]
716#[cfg(all(feature = "debug-macros", not(debug_assertions)))]
717#[macro_export]
718/// Create a `RayAsync` handle with strict error handling.
719///
720/// ```no_run
721/// use ray::ray_async_strict;
722///
723/// #[tokio::main]
724/// async fn main() {
725///     ray_async_strict!().try_label("async label").await.unwrap();
726/// }
727/// ```
728macro_rules! ray_async_strict {
729    () => {{
730        $crate::RayAsync::new_default_callsite_with_base(
731            file!(),
732            line!(),
733            module_path!(),
734            env!("CARGO_MANIFEST_DIR"),
735        )
736        .disable()
737    }};
738}