hopper_runtime/log.rs
1//! Backend-neutral logging helpers.
2//!
3//! Two tiers are exposed:
4//!
5//! - [`log`] for arbitrary UTF-8 text through the active backend's
6//! `sol_log_` syscall.
7//! - [`log_64`] for integer-heavy logs through the five-u64 `sol_log_64_`
8//! syscall, which is the cheapest structured-log path on Solana. This
9//! backs the `hopper_log!` macro's "label + values" form and lets
10//! hot handlers emit telemetry without the `core::fmt::Write` setup
11//! cost that `msg!` pays.
12
13/// Log a UTF-8 message through the active backend.
14#[inline(always)]
15pub fn log(message: &str) {
16 #[cfg(all(target_os = "solana", feature = "hopper-native-backend"))]
17 // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
18 unsafe {
19 hopper_native::syscalls::sol_log_(message.as_ptr(), message.len() as u64);
20 }
21
22 #[cfg(all(target_os = "solana", feature = "legacy-pinocchio-compat"))]
23 // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
24 unsafe {
25 pinocchio::syscalls::sol_log_(message.as_ptr(), message.len() as u64);
26 }
27
28 #[cfg(all(target_os = "solana", feature = "solana-program-backend"))]
29 {
30 ::solana_program::log::sol_log(message);
31 }
32
33 #[cfg(not(target_os = "solana"))]
34 {
35 let _ = message;
36 }
37}
38
39/// Log up to five `u64` values through the `sol_log_64_` syscall.
40///
41/// One syscall, no allocation, no format parsing. Pad unused slots
42/// with zero. The Solana runtime renders the five values as a single
43/// line "Program log: 0x... 0x... ...". Use this as the tight-loop
44/// escape hatch when the output is going to be grep'd, not read.
45///
46/// ```ignore
47/// // Emit "balance, delta, new_balance":
48/// hopper_runtime::log::log_64(balance, delta, new_balance, 0, 0);
49/// ```
50#[inline(always)]
51pub fn log_64(a: u64, b: u64, c: u64, d: u64, e: u64) {
52 #[cfg(all(target_os = "solana", feature = "hopper-native-backend"))]
53 // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
54 unsafe {
55 hopper_native::syscalls::sol_log_64_(a, b, c, d, e);
56 }
57
58 #[cfg(all(target_os = "solana", feature = "legacy-pinocchio-compat"))]
59 // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
60 unsafe {
61 pinocchio::syscalls::sol_log_64_(a, b, c, d, e);
62 }
63
64 #[cfg(all(target_os = "solana", feature = "solana-program-backend"))]
65 {
66 ::solana_program::log::sol_log_64(a, b, c, d, e);
67 }
68
69 #[cfg(not(target_os = "solana"))]
70 {
71 let _ = (a, b, c, d, e);
72 }
73}
74
75/// Stack-allocated write buffer for formatted log messages.
76pub struct StackWriter<'a> {
77 buf: &'a mut [u8],
78 pos: usize,
79}
80
81impl<'a> StackWriter<'a> {
82 #[inline(always)]
83 pub fn new(buf: &'a mut [u8]) -> Self {
84 Self { buf, pos: 0 }
85 }
86
87 #[inline(always)]
88 pub fn pos(&self) -> usize {
89 self.pos
90 }
91}
92
93impl core::fmt::Write for StackWriter<'_> {
94 fn write_str(&mut self, s: &str) -> core::fmt::Result {
95 let bytes = s.as_bytes();
96 let remaining = self.buf.len().saturating_sub(self.pos);
97 let to_write = bytes.len().min(remaining);
98 self.buf[self.pos..self.pos + to_write].copy_from_slice(&bytes[..to_write]);
99 self.pos += to_write;
100 Ok(())
101 }
102}