@@ -21,7 +21,7 @@ use std::sync::Arc;
2121use tokio:: signal:: unix:: { signal, SignalKind } ;
2222use tokio:: sync:: broadcast;
2323use tokio_util:: sync:: CancellationToken ;
24- use tracing:: { error, info, trace, Level } ;
24+ use tracing:: { error, info, trace, warn , Level } ;
2525use tracing_subscriber:: EnvFilter ;
2626
2727#[ derive( Parser , Debug ) ]
@@ -85,6 +85,14 @@ struct Args {
8585 #[ arg( long, env, default_value = "0.0.0.0:9000" ) ]
8686 metrics_addr : SocketAddr ,
8787
88+ /// Tags to add to every metrics emitted, should be in the format --metrics-global-labels label1=value1,label2=value2
89+ #[ arg( long, env, default_value = "" ) ]
90+ metrics_global_labels : String ,
91+
92+ /// Add the hostname as a label to all Prometheus metrics
93+ #[ arg( long, env, default_value = "false" ) ]
94+ metrics_host_label : bool ,
95+
8896 /// Maximum backoff allowed for upstream connections
8997 #[ arg( long, env, default_value = "20" ) ]
9098 subscriber_max_interval : u64 ,
@@ -117,8 +125,21 @@ async fn main() {
117125 address = args. metrics_addr. to_string( )
118126 ) ;
119127
120- PrometheusBuilder :: new ( )
121- . with_http_listener ( args. metrics_addr )
128+ let mut builder = PrometheusBuilder :: new ( ) . with_http_listener ( args. metrics_addr ) ;
129+
130+ if args. metrics_host_label {
131+ let hostname = hostname:: get ( )
132+ . expect ( "could not find hostname" )
133+ . into_string ( )
134+ . expect ( "could not convert hostname to string" ) ;
135+ builder = builder. add_global_label ( "hostname" , hostname) ;
136+ }
137+
138+ for ( key, value) in parse_global_metrics ( args. metrics_global_labels ) {
139+ builder = builder. add_global_label ( key, value) ;
140+ }
141+
142+ builder
122143 . install ( )
123144 . expect ( "failed to setup Prometheus endpoint" )
124145 }
@@ -191,3 +212,74 @@ async fn main() {
191212 }
192213 }
193214}
215+
216+ fn parse_global_metrics ( metrics : String ) -> Vec < ( String , String ) > {
217+ let mut result = Vec :: new ( ) ;
218+
219+ for metric in metrics. split ( ',' ) {
220+ if metric. is_empty ( ) {
221+ continue ;
222+ }
223+
224+ let parts = metric
225+ . splitn ( 2 , '=' )
226+ . map ( |s| s. to_string ( ) )
227+ . collect :: < Vec < String > > ( ) ;
228+
229+ if parts. len ( ) != 2 {
230+ warn ! (
231+ message = "malformed global metric: invalid count" ,
232+ metric = metric
233+ ) ;
234+ continue ;
235+ }
236+
237+ let label = parts[ 0 ] . to_string ( ) ;
238+ let value = parts[ 1 ] . to_string ( ) ;
239+
240+ if label. is_empty ( ) || value. is_empty ( ) {
241+ warn ! (
242+ message = "malformed global metric: empty value" ,
243+ metric = metric
244+ ) ;
245+ continue ;
246+ }
247+
248+ result. push ( ( label, value) ) ;
249+ }
250+
251+ result
252+ }
253+
254+ #[ cfg( test) ]
255+ mod test {
256+ use crate :: parse_global_metrics;
257+
258+ #[ test]
259+ fn test_parse_global_metrics ( ) {
260+ assert_eq ! (
261+ parse_global_metrics( "" . into( ) ) ,
262+ Vec :: <( String , String ) >:: new( ) ,
263+ ) ;
264+
265+ assert_eq ! (
266+ parse_global_metrics( "key=value" . into( ) ) ,
267+ vec![ ( "key" . into( ) , "value" . into( ) ) ]
268+ ) ;
269+
270+ assert_eq ! (
271+ parse_global_metrics( "key=value,key2=value2" . into( ) ) ,
272+ vec![
273+ ( "key" . into( ) , "value" . into( ) ) ,
274+ ( "key2" . into( ) , "value2" . into( ) )
275+ ] ,
276+ ) ;
277+
278+ assert_eq ! ( parse_global_metrics( "gibberish" . into( ) ) , Vec :: new( ) ) ;
279+
280+ assert_eq ! (
281+ parse_global_metrics( "key=value,key2=," . into( ) ) ,
282+ vec![ ( "key" . into( ) , "value" . into( ) ) ] ,
283+ ) ;
284+ }
285+ }
0 commit comments