@@ -533,20 +533,36 @@ async fn handle_unban(
533533 let action = FlowSpecAction :: from ( ( mitigation. action_type , & mitigation. action_params ) ) ;
534534 let rule = FlowSpecRule :: new ( nlri, action) ;
535535
536+ let start = std:: time:: Instant :: now ( ) ;
536537 if let Err ( e) = state. announcer . withdraw ( & rule) . await {
537538 tracing:: error!( error = %e, "BGP withdrawal failed" ) ;
538539 // Continue anyway - mark as withdrawn in DB
540+ } else {
541+ crate :: observability:: metrics:: ANNOUNCEMENTS_TOTAL
542+ . with_label_values ( & [ "withdrawn" ] )
543+ . inc ( ) ;
544+ crate :: observability:: metrics:: ANNOUNCEMENTS_LATENCY
545+ . observe ( start. elapsed ( ) . as_secs_f64 ( ) ) ;
539546 }
540547 }
541548
542549 // Update mitigation status
550+ let action_type_str = mitigation. action_type . to_string ( ) ;
543551 mitigation. withdraw ( Some ( format ! ( "Detector unban: {}" , source) ) ) ;
544552 state
545553 . repo
546554 . update_mitigation ( & mitigation)
547555 . await
548556 . map_err ( AppError ) ?;
549557
558+ crate :: observability:: metrics:: MITIGATIONS_WITHDRAWN
559+ . with_label_values ( & [
560+ action_type_str. as_str ( ) ,
561+ mitigation. pop . as_str ( ) ,
562+ "detector_unban" ,
563+ ] )
564+ . inc ( ) ;
565+
550566 // Broadcast withdrawal via WebSocket
551567 let _ = state
552568 . ws_broadcast
@@ -589,6 +605,9 @@ async fn handle_ban(
589605 . find_ban_event_by_external_id ( & input. source , ext_id)
590606 . await
591607 {
608+ crate :: observability:: metrics:: EVENTS_REJECTED
609+ . with_label_values ( & [ input. source . as_str ( ) , "duplicate" ] )
610+ . inc ( ) ;
592611 return Err ( AppError ( PrefixdError :: DuplicateEvent {
593612 detector_source : input. source . clone ( ) ,
594613 external_id : ext_id. clone ( ) ,
@@ -602,6 +621,10 @@ async fn handle_ban(
602621 // Store event
603622 state. repo . insert_event ( & event) . await . map_err ( AppError ) ?;
604623
624+ crate :: observability:: metrics:: EVENTS_INGESTED
625+ . with_label_values ( & [ & event. source , & event. attack_vector ( ) . to_string ( ) ] )
626+ . inc ( ) ;
627+
605628 // Check if shutting down
606629 if state. is_shutting_down ( ) {
607630 return Err ( AppError ( PrefixdError :: ShuttingDown ) ) ;
@@ -873,6 +896,20 @@ async fn handle_ban(
873896 . validate ( & intent, state. repo . as_ref ( ) , is_safelisted)
874897 . await
875898 {
899+ crate :: observability:: metrics:: EVENTS_REJECTED
900+ . with_label_values ( & [ event. source . as_str ( ) , "guardrail" ] )
901+ . inc ( ) ;
902+ let reason = match & e {
903+ PrefixdError :: GuardrailViolation ( g) => format ! ( "{:?}" , g)
904+ . split_whitespace ( )
905+ . next ( )
906+ . unwrap_or ( "unknown" )
907+ . to_string ( ) ,
908+ _ => "unknown" . to_string ( ) ,
909+ } ;
910+ crate :: observability:: metrics:: GUARDRAIL_REJECTIONS
911+ . with_label_values ( & [ & reason] )
912+ . inc ( ) ;
876913 tracing:: warn!( error = %e, "guardrail rejected mitigation" ) ;
877914 return Err ( AppError ( e) ) ;
878915 }
@@ -888,6 +925,7 @@ async fn handle_ban(
888925 let action = FlowSpecAction :: from ( ( mitigation. action_type , & mitigation. action_params ) ) ;
889926 let rule = FlowSpecRule :: new ( nlri, action) ;
890927
928+ let start = std:: time:: Instant :: now ( ) ;
891929 if let Err ( e) = state. announcer . announce ( & rule) . await {
892930 tracing:: error!( error = %e, "BGP announcement failed" ) ;
893931 mitigation. reject ( e. to_string ( ) ) ;
@@ -898,6 +936,10 @@ async fn handle_ban(
898936 . map_err ( AppError ) ?;
899937 return Err ( AppError ( e) ) ;
900938 }
939+ crate :: observability:: metrics:: ANNOUNCEMENTS_TOTAL
940+ . with_label_values ( & [ "announced" ] )
941+ . inc ( ) ;
942+ crate :: observability:: metrics:: ANNOUNCEMENTS_LATENCY . observe ( start. elapsed ( ) . as_secs_f64 ( ) ) ;
901943 }
902944
903945 mitigation. activate ( ) ;
@@ -907,6 +949,10 @@ async fn handle_ban(
907949 . await
908950 . map_err ( AppError ) ?;
909951
952+ crate :: observability:: metrics:: MITIGATIONS_CREATED
953+ . with_label_values ( & [ & mitigation. action_type . to_string ( ) , & state. settings . pop ] )
954+ . inc ( ) ;
955+
910956 // Resolve signal group to 'resolved' now that mitigation is confirmed
911957 if let Some ( group_id) = signal_group_id {
912958 if let Ok ( Some ( mut group) ) = state. repo . get_signal_group ( group_id) . await {
@@ -1370,6 +1416,17 @@ pub async fn create_mitigation(
13701416 . validate ( & intent, state. repo . as_ref ( ) , is_safelisted)
13711417 . await
13721418 {
1419+ let reason = match & e {
1420+ PrefixdError :: GuardrailViolation ( g) => format ! ( "{:?}" , g)
1421+ . split_whitespace ( )
1422+ . next ( )
1423+ . unwrap_or ( "unknown" )
1424+ . to_string ( ) ,
1425+ _ => "unknown" . to_string ( ) ,
1426+ } ;
1427+ crate :: observability:: metrics:: GUARDRAIL_REJECTIONS
1428+ . with_label_values ( & [ & reason] )
1429+ . inc ( ) ;
13731430 return Ok ( AppError ( e) . into_response ( ) ) ;
13741431 }
13751432
@@ -1381,16 +1438,25 @@ pub async fn create_mitigation(
13811438 let nlri = FlowSpecNlri :: from ( & mitigation. match_criteria ) ;
13821439 let action = FlowSpecAction :: from ( ( mitigation. action_type , & mitigation. action_params ) ) ;
13831440 let rule = FlowSpecRule :: new ( nlri, action) ;
1441+ let start = std:: time:: Instant :: now ( ) ;
13841442 if let Err ( e) = state. announcer . announce ( & rule) . await {
13851443 return Ok ( AppError ( e) . into_response ( ) ) ;
13861444 }
1445+ crate :: observability:: metrics:: ANNOUNCEMENTS_TOTAL
1446+ . with_label_values ( & [ "announced" ] )
1447+ . inc ( ) ;
1448+ crate :: observability:: metrics:: ANNOUNCEMENTS_LATENCY . observe ( start. elapsed ( ) . as_secs_f64 ( ) ) ;
13871449 }
13881450
13891451 mitigation. activate ( ) ;
13901452 if let Err ( e) = state. repo . insert_mitigation ( & mitigation) . await {
13911453 return Ok ( AppError ( e) . into_response ( ) ) ;
13921454 }
13931455
1456+ crate :: observability:: metrics:: MITIGATIONS_CREATED
1457+ . with_label_values ( & [ & mitigation. action_type . to_string ( ) , & state. settings . pop ] )
1458+ . inc ( ) ;
1459+
13941460 Ok ( (
13951461 StatusCode :: CREATED ,
13961462 Json ( MitigationResponse :: from ( & mitigation) ) ,
@@ -1434,20 +1500,34 @@ pub async fn withdraw_mitigation(
14341500 let nlri = FlowSpecNlri :: from ( & mitigation. match_criteria ) ;
14351501 let action = FlowSpecAction :: from ( ( mitigation. action_type , & mitigation. action_params ) ) ;
14361502 let rule = FlowSpecRule :: new ( nlri, action) ;
1503+ let start = std:: time:: Instant :: now ( ) ;
14371504 state
14381505 . announcer
14391506 . withdraw ( & rule)
14401507 . await
14411508 . map_err ( |_| StatusCode :: INTERNAL_SERVER_ERROR ) ?;
1509+ crate :: observability:: metrics:: ANNOUNCEMENTS_TOTAL
1510+ . with_label_values ( & [ "withdrawn" ] )
1511+ . inc ( ) ;
1512+ crate :: observability:: metrics:: ANNOUNCEMENTS_LATENCY . observe ( start. elapsed ( ) . as_secs_f64 ( ) ) ;
14421513 }
14431514
1515+ let action_type_str = mitigation. action_type . to_string ( ) ;
14441516 mitigation. withdraw ( Some ( format ! ( "{}: {}" , req. operator_id, req. reason) ) ) ;
14451517 state
14461518 . repo
14471519 . update_mitigation ( & mitigation)
14481520 . await
14491521 . map_err ( |_| StatusCode :: INTERNAL_SERVER_ERROR ) ?;
14501522
1523+ crate :: observability:: metrics:: MITIGATIONS_WITHDRAWN
1524+ . with_label_values ( & [
1525+ action_type_str. as_str ( ) ,
1526+ mitigation. pop . as_str ( ) ,
1527+ "operator" ,
1528+ ] )
1529+ . inc ( ) ;
1530+
14511531 // Broadcast withdrawal via WebSocket
14521532 let _ = state
14531533 . ws_broadcast
@@ -1565,8 +1645,15 @@ pub async fn bulk_withdraw_mitigations(
15651645 let nlri = FlowSpecNlri :: from ( & mitigation. match_criteria ) ;
15661646 let action = FlowSpecAction :: from ( ( mitigation. action_type , & mitigation. action_params ) ) ;
15671647 let rule = FlowSpecRule :: new ( nlri, action) ;
1648+ let start = std:: time:: Instant :: now ( ) ;
15681649 if let Err ( e) = state. announcer . withdraw ( & rule) . await {
15691650 tracing:: error!( error = %e, mitigation_id = %id, "BGP withdrawal failed in bulk withdraw" ) ;
1651+ } else {
1652+ crate :: observability:: metrics:: ANNOUNCEMENTS_TOTAL
1653+ . with_label_values ( & [ "withdrawn" ] )
1654+ . inc ( ) ;
1655+ crate :: observability:: metrics:: ANNOUNCEMENTS_LATENCY
1656+ . observe ( start. elapsed ( ) . as_secs_f64 ( ) ) ;
15701657 }
15711658 }
15721659
0 commit comments