@@ -4,8 +4,10 @@ import (
44 "bufio"
55 "compress/gzip"
66 "context"
7+ "errors"
78 "io"
89 "strings"
10+ "sync"
911
1012 "github.com/0xJacky/Nginx-UI/internal/geolite"
1113 "github.com/0xJacky/Nginx-UI/internal/nginx_log/parser"
@@ -15,35 +17,44 @@ import (
1517
1618// Global parser instances
1719var (
18- logParser * parser.OptimizedParser // Use the concrete type for both regular and single-line parsing
20+ logParser * parser.OptimizedParser // Use the concrete type for both regular and single-line parsing
21+ parserInitOnce sync.Once
1922)
2023
21- func init () {
22- // Initialize the parser with production-ready configuration
23- config := parser .DefaultParserConfig ()
24- config .MaxLineLength = 16 * 1024 // 16KB for large log lines
25- config .BatchSize = 15000 // Maximum batch size for highest frontend throughput
26- config .WorkerCount = 24 // Match CPU core count for high-throughput
27- // Note: Caching is handled by the CachedUserAgentParser
28-
29- // Initialize user agent parser with caching (10,000 cache size for production)
30- uaParser := parser .NewCachedUserAgentParser (
31- parser .NewSimpleUserAgentParser (),
32- 10000 , // Large cache for production workloads
33- )
34-
35- var geoIPService parser.GeoIPService
36- geoService , err := geolite .GetService ()
37- if err != nil {
38- logger .Warnf ("Failed to initialize GeoIP service, geo-enrichment will be disabled: %v" , err )
39- } else {
40- geoIPService = parser .NewGeoLiteAdapter (geoService )
41- }
24+ // InitLogParser initializes the global optimized parser once (singleton).
25+ func InitLogParser () {
26+ parserInitOnce .Do (func () {
27+ // Initialize the parser with production-ready configuration
28+ config := parser .DefaultParserConfig ()
29+ config .MaxLineLength = 16 * 1024 // 16KB for large log lines
30+ config .BatchSize = 15000 // Maximum batch size for highest frontend throughput
31+ config .WorkerCount = 24 // Match CPU core count for high-throughput
32+ // Note: Caching is handled by the CachedUserAgentParser
33+
34+ // Initialize user agent parser with caching (10,000 cache size for production)
35+ uaParser := parser .NewCachedUserAgentParser (
36+ parser .NewSimpleUserAgentParser (),
37+ 10000 , // Large cache for production workloads
38+ )
39+
40+ var geoIPService parser.GeoIPService
41+ geoService , err := geolite .GetService ()
42+ if err != nil {
43+ logger .Warnf ("Failed to initialize GeoIP service, geo-enrichment will be disabled: %v" , err )
44+ } else {
45+ geoIPService = parser .NewGeoLiteAdapter (geoService )
46+ }
4247
43- // Create the optimized parser with production configuration
44- logParser = parser .NewOptimizedParser (config , uaParser , geoIPService )
48+ // Create the optimized parser with production configuration
49+ logParser = parser .NewOptimizedParser (config , uaParser , geoIPService )
4550
46- logger .Info ("Nginx log processing optimization system initialized with production configuration" )
51+ logger .Info ("Nginx log processing optimization system initialized with production configuration" )
52+ })
53+ }
54+
55+ // IsLogParserInitialized returns true if the global parser singleton has been created.
56+ func IsLogParserInitialized () bool {
57+ return logParser != nil
4758}
4859
4960// ParseLogLine parses a raw log line into a structured LogDocument using optimized parsing
@@ -52,6 +63,10 @@ func ParseLogLine(line string) (*LogDocument, error) {
5263 return nil , nil
5364 }
5465
66+ if logParser == nil {
67+ return nil , errors .New ("log parser is not initialized; call indexer.InitLogParser() before use" )
68+ }
69+
5570 // Use optimized parser for single line processing
5671 entry , err := logParser .ParseLine (line )
5772 if err != nil {
@@ -63,6 +78,9 @@ func ParseLogLine(line string) (*LogDocument, error) {
6378
6479// ParseLogStream parses a stream of log data using OptimizedParseStream (7-8x faster)
6580func ParseLogStream (ctx context.Context , reader io.Reader , filePath string ) ([]* LogDocument , error ) {
81+ if logParser == nil {
82+ return nil , errors .New ("log parser is not initialized; call indexer.InitLogParser() before use" )
83+ }
6684 // Auto-detect and handle gzip files
6785 actualReader , cleanup , err := createReaderForFile (reader , filePath )
6886 if err != nil {
@@ -94,6 +112,9 @@ func ParseLogStream(ctx context.Context, reader io.Reader, filePath string) ([]*
94112
95113// ParseLogStreamChunked processes large files using chunked processing for memory efficiency
96114func ParseLogStreamChunked (ctx context.Context , reader io.Reader , filePath string , chunkSize int ) ([]* LogDocument , error ) {
115+ if logParser == nil {
116+ return nil , errors .New ("log parser is not initialized; call indexer.InitLogParser() before use" )
117+ }
97118 // Auto-detect and handle gzip files
98119 actualReader , cleanup , err := createReaderForFile (reader , filePath )
99120 if err != nil {
@@ -121,6 +142,9 @@ func ParseLogStreamChunked(ctx context.Context, reader io.Reader, filePath strin
121142
122143// ParseLogStreamMemoryEfficient uses memory-efficient parsing for low memory environments
123144func ParseLogStreamMemoryEfficient (ctx context.Context , reader io.Reader , filePath string ) ([]* LogDocument , error ) {
145+ if logParser == nil {
146+ return nil , errors .New ("log parser is not initialized; call indexer.InitLogParser() before use" )
147+ }
124148 // Auto-detect and handle gzip files
125149 actualReader , cleanup , err := createReaderForFile (reader , filePath )
126150 if err != nil {
0 commit comments