pingora_cache/
trace.rs

1// Copyright 2025 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
17use cf_rustracing_jaeger::span::SpanContextState;
18use std::time::SystemTime;
19
20use crate::{CacheMeta, CachePhase, HitStatus};
21
22pub use cf_rustracing::tag::Tag;
23
24pub type Span = cf_rustracing::span::Span<SpanContextState>;
25pub type SpanHandle = cf_rustracing::span::SpanHandle<SpanContextState>;
26
27#[derive(Debug)]
28pub(crate) struct CacheTraceCTX {
29    // parent span
30    pub cache_span: Span,
31    // only spans across multiple calls need to store here
32    pub miss_span: Span,
33    pub hit_span: Span,
34}
35
36impl CacheTraceCTX {
37    pub fn new() -> Self {
38        CacheTraceCTX {
39            cache_span: Span::inactive(),
40            miss_span: Span::inactive(),
41            hit_span: Span::inactive(),
42        }
43    }
44
45    pub fn enable(&mut self, cache_span: Span) {
46        self.cache_span = cache_span;
47    }
48
49    pub fn get_cache_span(&self) -> SpanHandle {
50        self.cache_span.handle()
51    }
52
53    #[inline]
54    pub fn child(&self, name: &'static str) -> Span {
55        self.cache_span.child(name, |o| o.start())
56    }
57
58    pub fn start_miss_span(&mut self) {
59        self.miss_span = self.child("miss");
60    }
61
62    pub fn get_miss_span(&self) -> SpanHandle {
63        self.miss_span.handle()
64    }
65
66    pub fn finish_miss_span(&mut self) {
67        self.miss_span.set_finish_time(SystemTime::now);
68    }
69
70    pub fn start_hit_span(&mut self, phase: CachePhase, hit_status: HitStatus) {
71        self.hit_span = self.child("hit");
72        self.hit_span.set_tag(|| Tag::new("phase", phase.as_str()));
73        self.hit_span
74            .set_tag(|| Tag::new("status", hit_status.as_str()));
75    }
76
77    pub fn get_hit_span(&self) -> SpanHandle {
78        self.hit_span.handle()
79    }
80
81    pub fn finish_hit_span(&mut self) {
82        self.hit_span.set_finish_time(SystemTime::now);
83    }
84
85    fn log_meta(span: &mut Span, meta: &CacheMeta) {
86        fn ts2epoch(ts: SystemTime) -> f64 {
87            ts.duration_since(SystemTime::UNIX_EPOCH)
88                .unwrap_or_default() // should never overflow but be safe here
89                .as_secs_f64()
90        }
91        let internal = &meta.0.internal;
92        span.set_tags(|| {
93            [
94                Tag::new("created", ts2epoch(internal.created)),
95                Tag::new("fresh_until", ts2epoch(internal.fresh_until)),
96                Tag::new("updated", ts2epoch(internal.updated)),
97                Tag::new("stale_if_error_sec", internal.stale_if_error_sec as i64),
98                Tag::new(
99                    "stale_while_revalidate_sec",
100                    internal.stale_while_revalidate_sec as i64,
101                ),
102                Tag::new("variance", internal.variance.is_some()),
103            ]
104        });
105    }
106
107    pub fn log_meta_in_hit_span(&mut self, meta: &CacheMeta) {
108        CacheTraceCTX::log_meta(&mut self.hit_span, meta);
109    }
110
111    pub fn log_meta_in_miss_span(&mut self, meta: &CacheMeta) {
112        CacheTraceCTX::log_meta(&mut self.miss_span, meta);
113    }
114}