@@ -19,6 +19,8 @@ use tracing_subscriber::{EnvFilter, FmtSubscriber, Registry, fmt, layer::Subscri
19
19
20
20
/// JSON field name for project identification in logs.
21
21
const PROJECT_KEY_IN_LOG : & str = "project" ;
22
+ /// JSON field name for pipeline identification in logs.
23
+ const PIPELINE_KEY_IN_LOG : & str = "pipeline_id" ;
22
24
23
25
/// Errors that can occur during tracing initialization.
24
26
#[ derive( Debug , Error ) ]
@@ -70,6 +72,8 @@ pub fn init_test_tracing() {
70
72
71
73
/// Global project reference storage
72
74
static PROJECT_REF : OnceLock < String > = OnceLock :: new ( ) ;
75
+ /// Global pipeline id storage.
76
+ static PIPELINE_ID : OnceLock < u64 > = OnceLock :: new ( ) ;
73
77
74
78
/// Sets the global project reference for all tracing events.
75
79
///
@@ -86,6 +90,21 @@ pub fn get_global_project_ref() -> Option<&'static str> {
86
90
PROJECT_REF . get ( ) . map ( |s| s. as_str ( ) )
87
91
}
88
92
93
+ /// Sets the global pipeline id for all tracing events.
94
+ ///
95
+ /// The pipeline id will be injected into all structured log entries
96
+ /// as a top-level field named "pipeline_id" for identification and filtering.
97
+ pub fn set_global_pipeline_id ( pipeline_id : u64 ) {
98
+ let _ = PIPELINE_ID . set ( pipeline_id) ;
99
+ }
100
+
101
+ /// Returns the current global pipeline id.
102
+ ///
103
+ /// Returns `None` if no pipeline id has been set.
104
+ pub fn get_global_pipeline_id ( ) -> Option < u64 > {
105
+ PIPELINE_ID . get ( ) . copied ( )
106
+ }
107
+
89
108
/// Writer wrapper that injects project field into JSON log entries.
90
109
///
91
110
/// Parses JSON log entries and adds a project field if one doesn't already exist,
@@ -105,31 +124,47 @@ impl<W> Write for ProjectInjectingWriter<W>
105
124
where
106
125
W : Write ,
107
126
{
108
- /// Writes log data, injecting project field into JSON entries.
127
+ /// Writes log data, injecting project and pipeline fields into JSON entries.
109
128
///
110
129
/// Attempts to parse the buffer as JSON and inject a project field if:
111
130
/// - A global project reference is set
112
131
/// - The content is valid JSON
113
132
/// - No project field already exists
114
133
fn write ( & mut self , buf : & [ u8 ] ) -> std:: io:: Result < usize > {
115
- // Only try to inject project field if we have one and the content looks like JSON
116
- if let Some ( project_ref) = get_global_project_ref ( )
117
- && let Ok ( json_str) = std:: str:: from_utf8 ( buf)
118
- {
134
+ // Only try to inject fields if the content looks like JSON
135
+ if let Ok ( json_str) = std:: str:: from_utf8 ( buf) {
119
136
// Try to parse as JSON
120
137
if let Ok ( serde_json:: Value :: Object ( mut map) ) =
121
138
serde_json:: from_str :: < serde_json:: Value > ( json_str)
122
139
{
123
- // Only inject if "project" field doesn't already exist
124
- if !map. contains_key ( PROJECT_KEY_IN_LOG ) {
140
+ let mut modified = false ;
141
+
142
+ // Inject project if available and not present
143
+ if let Some ( project_ref) = get_global_project_ref ( )
144
+ && !map. contains_key ( PROJECT_KEY_IN_LOG )
145
+ {
125
146
map. insert (
126
147
PROJECT_KEY_IN_LOG . to_string ( ) ,
127
148
serde_json:: Value :: String ( project_ref. to_string ( ) ) ,
128
149
) ;
150
+ modified = true ;
151
+ }
129
152
153
+ // Inject pipeline_id if available and not present
154
+ if let Some ( pipeline_id) = get_global_pipeline_id ( )
155
+ && !map. contains_key ( PIPELINE_KEY_IN_LOG )
156
+ {
157
+ map. insert (
158
+ PIPELINE_KEY_IN_LOG . to_string ( ) ,
159
+ serde_json:: Value :: Number ( serde_json:: Number :: from ( pipeline_id) ) ,
160
+ ) ;
161
+ modified = true ;
162
+ }
163
+
164
+ if modified {
130
165
// Try to serialize back to JSON
131
166
if let Ok ( modified) = serde_json:: to_string ( & map) {
132
- // Add new line if it was there
167
+ // Preserve trailing newline if present
133
168
let output = if json_str. ends_with ( '\n' ) {
134
169
format ! ( "{modified}\n " )
135
170
} else {
@@ -161,22 +196,28 @@ where
161
196
/// Sets up structured logging with environment-appropriate configuration.
162
197
/// Production environments log to rotating files, development to console.
163
198
pub fn init_tracing ( app_name : & str ) -> Result < LogFlusher , TracingError > {
164
- init_tracing_with_project ( app_name, None )
199
+ init_tracing_with_top_level_fields ( app_name, None , None )
165
200
}
166
201
167
- /// Initializes tracing with optional project identification .
202
+ /// Initializes tracing with optional top-level fields .
168
203
///
169
- /// Like [`init_tracing`] but allows specifying a project reference that will
170
- /// be injected into all structured log entries for identification .
171
- pub fn init_tracing_with_project (
204
+ /// Like [`init_tracing`] but allows specifying multiple top-level fields that will be added to each
205
+ /// log entry .
206
+ pub fn init_tracing_with_top_level_fields (
172
207
app_name : & str ,
173
208
project_ref : Option < String > ,
209
+ pipeline_id : Option < u64 > ,
174
210
) -> Result < LogFlusher , TracingError > {
175
- // Set global project reference if provided
211
+ // Set global project reference if provided.
176
212
if let Some ( ref project) = project_ref {
177
213
set_global_project_ref ( project. clone ( ) ) ;
178
214
}
179
215
216
+ // Set global pipeline id if provided.
217
+ if let Some ( pipeline_id) = pipeline_id {
218
+ set_global_pipeline_id ( pipeline_id) ;
219
+ }
220
+
180
221
// Initialize the log tracer to capture logs from the `log` crate
181
222
// and send them to the `tracing` subscriber. This captures logs
182
223
// from libraries that use the `log` crate.
0 commit comments