Skip to main content

pingora_cache/
trace.rs

1// Copyright 2026 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Distributed tracing helpers
16//!
17//! When the `trace` feature is enabled, this module re-exports the real
18//! [`cf_rustracing`]/[`cf_rustracing_jaeger`] span types.
19//!
20//! When the `trace` feature is **disabled**, lightweight no-op shim types are
21//! provided instead so that the rest of the crate compiles without pulling in
22//! the tracing dependencies.
23
24use std::time::SystemTime;
25
26use crate::{CacheMeta, CachePhase, HitStatus};
27
28// ---------------------------------------------------------------------------
29// Real tracing implementation (feature = "trace")
30// ---------------------------------------------------------------------------
31#[cfg(feature = "trace")]
32mod real {
33    pub use cf_rustracing::tag::Tag;
34
35    use cf_rustracing_jaeger::span::SpanContextState;
36
37    pub type Span = cf_rustracing::span::Span<SpanContextState>;
38    pub type SpanHandle = cf_rustracing::span::SpanHandle<SpanContextState>;
39}
40
41#[cfg(feature = "trace")]
42pub use real::*;
43
44// ---------------------------------------------------------------------------
45// No-op shim types (feature = "trace" disabled)
46// ---------------------------------------------------------------------------
47#[cfg(not(feature = "trace"))]
48mod noop {
49    /// A no-op replacement for [`cf_rustracing::tag::Tag`].
50    #[derive(Debug)]
51    pub struct Tag {
52        _priv: (),
53    }
54
55    impl Tag {
56        /// Create a no-op tag.  All arguments are ignored.
57        #[inline]
58        pub fn new<N, V>(_name: N, _value: V) -> Self {
59            Tag { _priv: () }
60        }
61    }
62
63    /// A no-op replacement for a rustracing `Span`.
64    #[derive(Debug)]
65    pub struct Span {
66        _priv: (),
67    }
68
69    impl Span {
70        /// Return an inactive (no-op) span.
71        #[inline]
72        pub fn inactive() -> Self {
73            Span { _priv: () }
74        }
75
76        /// Return a no-op handle.
77        #[inline]
78        pub fn handle(&self) -> SpanHandle {
79            SpanHandle { _priv: () }
80        }
81
82        /// No-op: create a child span.
83        #[inline]
84        pub fn child<F>(&self, _name: &'static str, _f: F) -> Span
85        where
86            F: FnOnce(SpanOptionsPlaceholder) -> SpanOptionsPlaceholder,
87        {
88            Span::inactive()
89        }
90
91        /// No-op: set a single tag via a closure.
92        #[inline]
93        pub fn set_tag<F: FnOnce() -> Tag>(&self, _f: F) {}
94
95        /// No-op: set multiple tags via a closure.
96        #[inline]
97        pub fn set_tags<F, I>(&self, _f: F)
98        where
99            F: FnOnce() -> I,
100            I: IntoIterator<Item = Tag>,
101        {
102        }
103
104        /// No-op: set a finish time.
105        #[inline]
106        pub fn set_finish_time<F: Fn() -> std::time::SystemTime>(&self, _f: F) {}
107    }
108
109    /// Placeholder type used in [`Span::child`] closure signatures so that
110    /// existing call-sites like `span.child("name", |o| o.start())` compile.
111    #[doc(hidden)]
112    pub struct SpanOptionsPlaceholder {
113        _priv: (),
114    }
115
116    impl SpanOptionsPlaceholder {
117        /// No-op: mirrors `SpanOptions::start()`.
118        #[inline]
119        pub fn start(self) -> Self {
120            self
121        }
122    }
123
124    /// A no-op replacement for a rustracing `SpanHandle`.
125    #[derive(Debug)]
126    pub struct SpanHandle {
127        _priv: (),
128    }
129}
130
131#[cfg(not(feature = "trace"))]
132pub use noop::*;
133
134// ---------------------------------------------------------------------------
135// Shared helpers (work with both real and no-op types)
136// ---------------------------------------------------------------------------
137
138/// Tag a span with metadata from a [`CacheMeta`].
139pub fn tag_span_with_meta(span: &mut Span, meta: &CacheMeta) {
140    fn ts2epoch(ts: SystemTime) -> f64 {
141        ts.duration_since(SystemTime::UNIX_EPOCH)
142            .unwrap_or_default() // should never overflow but be safe here
143            .as_secs_f64()
144    }
145    let internal = &meta.0.internal;
146    span.set_tags(|| {
147        [
148            Tag::new("created", ts2epoch(internal.created)),
149            Tag::new("fresh_until", ts2epoch(internal.fresh_until)),
150            Tag::new("updated", ts2epoch(internal.updated)),
151            Tag::new("stale_if_error_sec", internal.stale_if_error_sec as i64),
152            Tag::new(
153                "stale_while_revalidate_sec",
154                internal.stale_while_revalidate_sec as i64,
155            ),
156            Tag::new("variance", internal.variance.is_some()),
157        ]
158    });
159}
160
161#[derive(Debug)]
162pub(crate) struct CacheTraceCTX {
163    // parent span
164    pub cache_span: Span,
165    // only spans across multiple calls need to store here
166    pub miss_span: Span,
167    pub hit_span: Span,
168}
169
170impl CacheTraceCTX {
171    pub fn new() -> Self {
172        CacheTraceCTX {
173            cache_span: Span::inactive(),
174            miss_span: Span::inactive(),
175            hit_span: Span::inactive(),
176        }
177    }
178
179    pub fn enable(&mut self, cache_span: Span) {
180        self.cache_span = cache_span;
181    }
182
183    pub fn get_cache_span(&self) -> SpanHandle {
184        self.cache_span.handle()
185    }
186
187    #[inline]
188    pub fn child(&self, name: &'static str) -> Span {
189        self.cache_span.child(name, |o| o.start())
190    }
191
192    pub fn start_miss_span(&mut self) {
193        self.miss_span = self.child("miss");
194    }
195
196    pub fn get_miss_span(&self) -> SpanHandle {
197        self.miss_span.handle()
198    }
199
200    pub fn finish_miss_span(&mut self) {
201        self.miss_span.set_finish_time(SystemTime::now);
202    }
203
204    pub fn start_hit_span(&mut self, phase: CachePhase, hit_status: HitStatus) {
205        self.hit_span = self.child("hit");
206        self.hit_span.set_tag(|| Tag::new("phase", phase.as_str()));
207        self.hit_span
208            .set_tag(|| Tag::new("status", hit_status.as_str()));
209    }
210
211    pub fn get_hit_span(&self) -> SpanHandle {
212        self.hit_span.handle()
213    }
214
215    pub fn finish_hit_span(&mut self) {
216        self.hit_span.set_finish_time(SystemTime::now);
217    }
218
219    pub fn log_meta_in_hit_span(&mut self, meta: &CacheMeta) {
220        tag_span_with_meta(&mut self.hit_span, meta);
221    }
222
223    pub fn log_meta_in_miss_span(&mut self, meta: &CacheMeta) {
224        tag_span_with_meta(&mut self.miss_span, meta);
225    }
226}