@@ -14,6 +14,7 @@ import (
1414 "github.com/Chahine-tech/sql-parser-go/pkg/analyzer"
1515 "github.com/Chahine-tech/sql-parser-go/pkg/dialect"
1616 "github.com/Chahine-tech/sql-parser-go/pkg/logger"
17+ "github.com/Chahine-tech/sql-parser-go/pkg/monitor"
1718 "github.com/Chahine-tech/sql-parser-go/pkg/parser"
1819)
1920
@@ -31,14 +32,17 @@ const banner = `
3132
3233func main () {
3334 var (
34- queryFile = flag .String ("query" , "" , "File containing the SQL query" )
35- queryText = flag .String ("sql" , "" , "SQL query string" )
36- logFile = flag .String ("log" , "" , "SQL Server log file" )
37- outputFormat = flag .String ("output" , "json" , "Output format (json, table)" )
38- verbose = flag .Bool ("verbose" , false , "Verbose mode" )
39- configFile = flag .String ("config" , "" , "Configuration file path" )
40- dialectFlag = flag .String ("dialect" , "" , "SQL dialect (mysql, postgresql, sqlserver, sqlite, oracle)" )
41- showHelp = flag .Bool ("help" , false , "Show help" )
35+ queryFile = flag .String ("query" , "" , "File containing the SQL query" )
36+ queryText = flag .String ("sql" , "" , "SQL query string" )
37+ logFile = flag .String ("log" , "" , "SQL Server log file" )
38+ outputFormat = flag .String ("output" , "json" , "Output format (json, table)" )
39+ verbose = flag .Bool ("verbose" , false , "Verbose mode" )
40+ configFile = flag .String ("config" , "" , "Configuration file path" )
41+ dialectFlag = flag .String ("dialect" , "" , "SQL dialect (mysql, postgresql, sqlserver, sqlite, oracle)" )
42+ showHelp = flag .Bool ("help" , false , "Show help" )
43+ watchMode = flag .Bool ("watch" , false , "Watch log file for real-time monitoring" )
44+ tailLines = flag .Int ("tail" , 10 , "Number of lines to tail when starting watch mode" )
45+ slowThreshold = flag .Float64 ("slow" , 1.0 , "Slow query threshold in seconds" )
4246 )
4347 flag .Parse ()
4448
@@ -74,9 +78,16 @@ func main() {
7478 os .Exit (1 )
7579 }
7680 } else if * logFile != "" {
77- if err := parseLogFile (* logFile , cfg , * verbose ); err != nil {
78- fmt .Printf ("Error parsing log file: %v\n " , err )
79- os .Exit (1 )
81+ if * watchMode {
82+ if err := watchLogFile (* logFile , cfg , * verbose , * tailLines , * slowThreshold ); err != nil {
83+ fmt .Printf ("Error watching log file: %v\n " , err )
84+ os .Exit (1 )
85+ }
86+ } else {
87+ if err := parseLogFile (* logFile , cfg , * verbose ); err != nil {
88+ fmt .Printf ("Error parsing log file: %v\n " , err )
89+ os .Exit (1 )
90+ }
8091 }
8192 } else {
8293 showUsage ()
@@ -91,18 +102,23 @@ func showUsage() {
91102 fmt .Println (" sqlparser -query file.sql Analyze SQL query from file" )
92103 fmt .Println (" sqlparser -sql \" SELECT * FROM...\" Analyze SQL query from string" )
93104 fmt .Println (" sqlparser -log logfile.log Parse SQL Server log file" )
105+ fmt .Println (" sqlparser -log logfile.log -watch Watch log file in real-time" )
94106 fmt .Println ()
95107 fmt .Println ("Options:" )
96108 fmt .Println (" -output FORMAT Output format: json, table (default: json)" )
97109 fmt .Println (" -dialect DIALECT SQL dialect: mysql, postgresql, sqlserver, sqlite, oracle (default: sqlserver)" )
98110 fmt .Println (" -verbose Enable verbose output" )
99111 fmt .Println (" -config FILE Configuration file path" )
112+ fmt .Println (" -watch Enable real-time log monitoring (use with -log)" )
113+ fmt .Println (" -tail N Number of lines to tail when starting watch (default: 10)" )
114+ fmt .Println (" -slow SECONDS Slow query threshold in seconds (default: 1.0)" )
100115 fmt .Println (" -help Show this help" )
101116 fmt .Println ()
102117 fmt .Println ("Examples:" )
103118 fmt .Println (" sqlparser -query complex_query.sql -output json -dialect mysql" )
104119 fmt .Println (" sqlparser -sql \" SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id\" -dialect postgresql" )
105120 fmt .Println (" sqlparser -log sqlserver.log -output table -verbose" )
121+ fmt .Println (" sqlparser -log sqlserver.log -watch -tail 20 -slow 2.0 -dialect mysql" )
106122}
107123
108124func analyzeQueryFile (filename string , cfg * config.Config , verbose bool ) error {
@@ -182,6 +198,136 @@ func analyzeQueryString(sql string, cfg *config.Config, verbose bool) error {
182198 return outputAnalysis (analysis , suggestions , cfg )
183199}
184200
201+ func watchLogFile (filename string , cfg * config.Config , verbose bool , tailLines int , slowThreshold float64 ) error {
202+ if verbose {
203+ fmt .Printf ("🔍 Starting real-time log monitoring: %s\n " , filename )
204+ fmt .Printf ("Dialect: %s\n " , cfg .Parser .Dialect )
205+ fmt .Printf ("Slow query threshold: %.2fs\n " , slowThreshold )
206+ fmt .Printf ("Tailing last %d lines...\n \n " , tailLines )
207+ }
208+
209+ // Create context for graceful shutdown
210+ ctx , cancel := context .WithCancel (context .Background ())
211+ defer cancel ()
212+
213+ // Create watcher
214+ watcher := monitor .NewLogWatcher (filename )
215+ lines := make (chan string , 100 ) // Buffered channel for log lines
216+
217+ // Start watching (with tail)
218+ var err error
219+ if tailLines > 0 {
220+ err = watcher .StartWithTail (ctx , lines , tailLines )
221+ } else {
222+ err = watcher .Start (ctx , lines )
223+ }
224+ if err != nil {
225+ return fmt .Errorf ("failed to start watcher: %w" , err )
226+ }
227+
228+ // Create alert manager
229+ alertMgr := monitor .NewAlertManager ()
230+
231+ // Add alert rules
232+ alertMgr .AddRule (& monitor.SlowQueryRule {Threshold : slowThreshold })
233+ alertMgr .AddRule (& monitor.ParseErrorRule {})
234+ alertMgr .AddRule (& monitor.OptimizationRule {MinSeverity : "medium" })
235+ alertMgr .AddRule (& monitor.FullTableScanRule {})
236+
237+ // Add console alert handler
238+ alertMgr .AddHandler (monitor .ConsoleAlertHandler )
239+
240+ // Create processor
241+ processor := monitor .NewLogProcessor (cfg .Parser .Dialect )
242+ processor .SetQueryHandler (func (pq * monitor.ProcessedQuery ) {
243+ // Check alerts first
244+ alertMgr .Check (pq )
245+
246+ // Print query information
247+ fmt .Printf ("[%s] Duration: %.3fs | Database: %s | User: %s\n " ,
248+ pq .Timestamp .Format ("15:04:05" ),
249+ pq .Duration ,
250+ pq .Database ,
251+ pq .User )
252+
253+ if len (pq .Query ) > 100 {
254+ fmt .Printf (" Query: %s...\n " , pq .Query [:97 ])
255+ } else {
256+ fmt .Printf (" Query: %s\n " , pq .Query )
257+ }
258+
259+ // Show analysis if available
260+ if pq .Analysis != nil {
261+ if len (pq .Analysis .Tables ) > 0 {
262+ tables := make ([]string , len (pq .Analysis .Tables ))
263+ for i , t := range pq .Analysis .Tables {
264+ tables [i ] = t .Name
265+ }
266+ fmt .Printf (" Tables: %s\n " , strings .Join (tables , ", " ))
267+ }
268+
269+ // Show optimizations if any
270+ if len (pq .Analysis .EnhancedSuggestions ) > 0 {
271+ fmt .Printf (" ⚠️ %d optimization suggestions\n " , len (pq .Analysis .EnhancedSuggestions ))
272+ for _ , opt := range pq .Analysis .EnhancedSuggestions {
273+ fmt .Printf (" - [%s] %s\n " , opt .Severity , opt .Description )
274+ }
275+ }
276+ }
277+
278+ fmt .Println ()
279+ })
280+
281+ // Start processor
282+ go processor .Start (ctx , lines )
283+
284+ // Print statistics periodically
285+ ticker := time .NewTicker (30 * time .Second )
286+ defer ticker .Stop ()
287+
288+ // Handle Ctrl+C
289+ sigChan := make (chan os.Signal , 1 )
290+
291+ fmt .Println ("📊 Real-time monitoring started. Press Ctrl+C to stop." )
292+ fmt .Println (strings .Repeat ("=" , 80 ))
293+ fmt .Println ()
294+
295+ // Main loop
296+ for {
297+ select {
298+ case <- ticker .C :
299+ // Print statistics
300+ stats := processor .GetStatistics ().GetSnapshot ()
301+ fmt .Println ()
302+ fmt .Println (strings .Repeat ("=" , 80 ))
303+ fmt .Println (stats .String ())
304+
305+ // Print alert counts
306+ alertCounts := alertMgr .GetAlertCounts ()
307+ if len (alertCounts ) > 0 {
308+ fmt .Println ("\n Alerts:" )
309+ for level , count := range alertCounts {
310+ fmt .Printf (" %s: %d\n " , level .String (), count )
311+ }
312+ }
313+ fmt .Println (strings .Repeat ("=" , 80 ))
314+ fmt .Println ()
315+
316+ case <- sigChan :
317+ fmt .Println ("\n \n Stopping monitoring..." )
318+ cancel ()
319+ watcher .Stop ()
320+
321+ // Print final statistics
322+ stats := processor .GetStatistics ().GetSnapshot ()
323+ fmt .Println ()
324+ fmt .Println ("Final Statistics:" )
325+ fmt .Println (stats .String ())
326+ return nil
327+ }
328+ }
329+ }
330+
185331func parseLogFile (filename string , cfg * config.Config , verbose bool ) error {
186332 if verbose {
187333 fmt .Printf ("Parsing log file: %s\n " , filename )
0 commit comments