@@ -11,13 +11,17 @@ use rustpbx::config::{SipFlowConfig, SipFlowEngine, SipFlowSubdirs};
1111use rustpbx:: sipflow:: {
1212 SipFlowBackend , SipFlowItem , SipFlowMsgType ,
1313 create_backend,
14+ perf:: { PerfCounters , PerfDumper } ,
1415 protocol:: { MsgType , Packet , parse_packet} ,
1516 storage:: extract_callid,
1617} ;
1718use std:: collections:: HashMap ;
1819use std:: net:: SocketAddr ;
1920use std:: sync:: Arc ;
21+ use std:: sync:: atomic:: Ordering ;
2022use tokio:: net:: UdpSocket ;
23+ use tracing_appender:: non_blocking;
24+ use tracing_subscriber:: { fmt, EnvFilter } ;
2125
2226#[ derive( Parser , Debug ) ]
2327#[ command( author, version, about = "SipFlow - SIP and RTP flow recording server" , long_about = None ) ]
@@ -38,8 +42,8 @@ struct Args {
3842 #[ arg( short, long, default_value = "./config/sipflow" ) ]
3943 root : String ,
4044
41- /// Storage engine: "flowdb" (default) or "sqlite"
42- #[ arg( long, default_value = "flowdb " ) ]
45+ /// Storage engine: "flowdb" or "sqlite" (default)
46+ #[ arg( long, default_value = "sqlite " ) ]
4347 engine : String ,
4448
4549 /// Channel buffer size
@@ -60,6 +64,16 @@ struct Args {
6064 #[ arg( long, default_value_t = 8192 ) ]
6165 id_cache_size : usize ,
6266
67+ // ── Logging options ──
68+
69+ /// Log file path
70+ #[ arg( long, default_value = "/var/log/sipflow.log" ) ]
71+ log_file : String ,
72+
73+ /// Log level (trace, debug, info, warn, error)
74+ #[ arg( long, default_value = "info" ) ]
75+ log_level : String ,
76+
6377 // ── FlowDB options ──
6478
6579 /// TTL in seconds for FlowDB records (optional, 0 = no ttl)
@@ -111,15 +125,41 @@ fn convert_packet_to_item(packet: Packet) -> (String, SipFlowItem) {
111125
112126#[ tokio:: main]
113127async fn main ( ) -> Result < ( ) > {
114- tracing_subscriber:: fmt:: init ( ) ;
115128 let args = Args :: parse ( ) ;
116129
130+ // Initialize tracing: try log file, fall back to stdout on permission error
131+ if let Some ( parent) = std:: path:: Path :: new ( & args. log_file ) . parent ( ) {
132+ let _ = std:: fs:: create_dir_all ( parent) ;
133+ }
134+ let ( _guard, writer) = match std:: fs:: OpenOptions :: new ( )
135+ . create ( true )
136+ . append ( true )
137+ . open ( & args. log_file )
138+ {
139+ Ok ( f) => {
140+ let ( w, g) = non_blocking ( f) ;
141+ ( g, w)
142+ }
143+ Err ( e) => {
144+ eprintln ! (
145+ "sipflow: cannot open '{}' ({}), falling back to stdout" ,
146+ args. log_file, e
147+ ) ;
148+ let ( w, g) = non_blocking ( std:: io:: stdout ( ) ) ;
149+ ( g, w)
150+ }
151+ } ;
152+ fmt ( )
153+ . with_env_filter ( EnvFilter :: new ( & args. log_level ) )
154+ . with_writer ( writer)
155+ . init ( ) ;
156+
117157 // Ensure data directory exists
118158 std:: fs:: create_dir_all ( & args. root ) ?;
119159
120160 let engine = match args. engine . as_str ( ) {
121- "sqlite " => SipFlowEngine :: Sqlite ,
122- "flowdb " | _ => SipFlowEngine :: FlowDb ,
161+ "flowdb " => SipFlowEngine :: FlowDb ,
162+ "sqlite " | _ => SipFlowEngine :: Sqlite ,
123163 } ;
124164 let ttl_secs = args. ttl_secs . filter ( |& s| s > 0 ) ;
125165
@@ -137,6 +177,8 @@ async fn main() -> Result<()> {
137177 } ;
138178
139179 let backend: Arc < dyn SipFlowBackend > = Arc :: from ( create_backend ( & config) ?) ;
180+ let perf_counters = PerfCounters :: new_arc ( ) ;
181+
140182 let app_state = AppState {
141183 backend : backend. clone ( ) ,
142184 } ;
@@ -148,12 +190,14 @@ async fn main() -> Result<()> {
148190 let ( tx, mut rx) = tokio:: sync:: mpsc:: channel :: < Packet > ( args. buffer_size ) ;
149191
150192 // UDP Receiver Task
193+ let perf_rx = perf_counters. clone ( ) ;
151194 rustpbx:: utils:: spawn ( async move {
152195 let mut buf = vec ! [ 0u8 ; 65535 ] ;
153196 loop {
154197 match socket. recv_from ( & mut buf) . await {
155198 Ok ( ( size, _) ) => {
156199 if let Ok ( packet) = parse_packet ( & buf[ ..size] ) {
200+ perf_rx. packets_received . fetch_add ( 1 , Ordering :: Relaxed ) ;
157201 let _ = tx. try_send ( packet) ;
158202 }
159203 }
@@ -166,6 +210,7 @@ async fn main() -> Result<()> {
166210
167211 // Storage Worker Task
168212 let storage_backend = backend. clone ( ) ;
213+ let perf_worker = perf_counters. clone ( ) ;
169214 rustpbx:: utils:: spawn ( async move {
170215 let mut interval = tokio:: time:: interval ( std:: time:: Duration :: from_secs ( 1 ) ) ;
171216 loop {
@@ -174,15 +219,32 @@ async fn main() -> Result<()> {
174219 let ( call_id, item) = convert_packet_to_item( packet) ;
175220 if !call_id. is_empty( ) {
176221 let _ = storage_backend. record( & call_id, item) ;
222+ perf_worker. items_recorded. fetch_add( 1 , Ordering :: Relaxed ) ;
223+ } else {
224+ perf_worker. items_dropped. fetch_add( 1 , Ordering :: Relaxed ) ;
177225 }
226+ perf_worker. set_pending( rx. len( ) as i64 ) ;
178227 }
179228 _ = interval. tick( ) => {
180229 let _ = storage_backend. flush( ) . await ;
230+ perf_worker. flushes. fetch_add( 1 , Ordering :: Relaxed ) ;
181231 }
182232 }
183233 }
184234 } ) ;
185235
236+ // Periodic perf dump (every 10 s, skipped when idle)
237+ let perf_dump = perf_counters. clone ( ) ;
238+ rustpbx:: utils:: spawn ( async move {
239+ let mut dumper = PerfDumper :: new ( perf_dump) ;
240+ loop {
241+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 10 ) ) . await ;
242+ if let Some ( msg) = dumper. try_dump ( ) {
243+ tracing:: info!( "{msg}" ) ;
244+ }
245+ }
246+ } ) ;
247+
186248 // HTTP Server
187249 let app = Router :: new ( )
188250 . route ( "/health" , get ( health_handler) )
0 commit comments