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}