Skip to main content

zlayer_observability/
container_spans.rs

1//! Container operation span helpers
2//!
3//! Provides instrumentation helpers for container lifecycle operations
4//! following OpenTelemetry semantic conventions.
5
6use tracing::{info_span, Span};
7
8/// Semantic attribute keys for container operations
9pub mod attributes {
10    pub const CONTAINER_ID: &str = "container.id";
11    pub const CONTAINER_NAME: &str = "container.name";
12    pub const CONTAINER_IMAGE: &str = "container.image.name";
13    pub const CONTAINER_RUNTIME: &str = "container.runtime";
14    pub const SERVICE_NAME: &str = "service.name";
15    pub const SERVICE_REPLICA: &str = "zlayer.service.replica";
16    pub const OPERATION: &str = "zlayer.operation";
17}
18
19/// Container operation types for span naming
20#[derive(Debug, Clone, Copy)]
21pub enum ContainerOperation {
22    Create,
23    Start,
24    Stop,
25    Remove,
26    Pull,
27    Exec,
28    Logs,
29    Stats,
30    PersistLayer,
31    RestoreLayer,
32}
33
34impl ContainerOperation {
35    #[must_use]
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            Self::Create => "container.create",
39            Self::Start => "container.start",
40            Self::Stop => "container.stop",
41            Self::Remove => "container.remove",
42            Self::Pull => "image.pull",
43            Self::Exec => "container.exec",
44            Self::Logs => "container.logs",
45            Self::Stats => "container.stats",
46            Self::PersistLayer => "layer.persist",
47            Self::RestoreLayer => "layer.restore",
48        }
49    }
50}
51
52impl std::fmt::Display for ContainerOperation {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(f, "{}", self.as_str())
55    }
56}
57
58/// Create a span for container operations
59pub fn container_span(
60    operation: ContainerOperation,
61    service: &str,
62    replica: u32,
63    image: Option<&str>,
64) -> Span {
65    let container_id = format!("{service}-{replica}");
66
67    let span = info_span!(
68        target: "zlayer::container",
69        "container_operation",
70        otel.name = %operation.as_str(),
71        %container_id,
72        service.name = %service,
73        service.replica = %replica,
74        operation = %operation.as_str(),
75        container.runtime = "youki",
76    );
77
78    if let Some(img) = image {
79        span.record("container.image.name", img);
80    }
81
82    span
83}
84
85/// Create a span for image pull operations
86pub fn image_pull_span(image: &str) -> Span {
87    info_span!(
88        target: "zlayer::registry",
89        "image.pull",
90        otel.name = "image.pull",
91        container.image.name = %image,
92        operation = "pull",
93    )
94}
95
96/// Create a span for layer storage operations
97pub fn layer_storage_span(
98    operation: ContainerOperation,
99    container_id: &str,
100    digest: Option<&str>,
101) -> Span {
102    let span = info_span!(
103        target: "zlayer::layer_storage",
104        "layer_operation",
105        otel.name = %operation.as_str(),
106        %container_id,
107        operation = %operation.as_str(),
108    );
109
110    if let Some(d) = digest {
111        span.record("layer.digest", d);
112    }
113
114    span
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_operation_as_str() {
123        assert_eq!(ContainerOperation::Create.as_str(), "container.create");
124        assert_eq!(ContainerOperation::PersistLayer.as_str(), "layer.persist");
125    }
126
127    #[test]
128    fn test_container_span_creation() {
129        let span = container_span(
130            ContainerOperation::Create,
131            "my-service",
132            1,
133            Some("nginx:latest"),
134        );
135        assert!(span.is_disabled() || !span.is_disabled()); // Just verify it doesn't panic
136    }
137}