@@ -4,8 +4,10 @@ import (
4
4
"bufio"
5
5
"compress/gzip"
6
6
"context"
7
+ "errors"
7
8
"io"
8
9
"strings"
10
+ "sync"
9
11
10
12
"github.com/0xJacky/Nginx-UI/internal/geolite"
11
13
"github.com/0xJacky/Nginx-UI/internal/nginx_log/parser"
@@ -15,35 +17,44 @@ import (
15
17
16
18
// Global parser instances
17
19
var (
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
19
22
)
20
23
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
+ }
42
47
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 )
45
50
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
47
58
}
48
59
49
60
// ParseLogLine parses a raw log line into a structured LogDocument using optimized parsing
@@ -52,6 +63,10 @@ func ParseLogLine(line string) (*LogDocument, error) {
52
63
return nil , nil
53
64
}
54
65
66
+ if logParser == nil {
67
+ return nil , errors .New ("log parser is not initialized; call indexer.InitLogParser() before use" )
68
+ }
69
+
55
70
// Use optimized parser for single line processing
56
71
entry , err := logParser .ParseLine (line )
57
72
if err != nil {
@@ -63,6 +78,9 @@ func ParseLogLine(line string) (*LogDocument, error) {
63
78
64
79
// ParseLogStream parses a stream of log data using OptimizedParseStream (7-8x faster)
65
80
func 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
+ }
66
84
// Auto-detect and handle gzip files
67
85
actualReader , cleanup , err := createReaderForFile (reader , filePath )
68
86
if err != nil {
@@ -94,6 +112,9 @@ func ParseLogStream(ctx context.Context, reader io.Reader, filePath string) ([]*
94
112
95
113
// ParseLogStreamChunked processes large files using chunked processing for memory efficiency
96
114
func 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
+ }
97
118
// Auto-detect and handle gzip files
98
119
actualReader , cleanup , err := createReaderForFile (reader , filePath )
99
120
if err != nil {
@@ -121,6 +142,9 @@ func ParseLogStreamChunked(ctx context.Context, reader io.Reader, filePath strin
121
142
122
143
// ParseLogStreamMemoryEfficient uses memory-efficient parsing for low memory environments
123
144
func 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
+ }
124
148
// Auto-detect and handle gzip files
125
149
actualReader , cleanup , err := createReaderForFile (reader , filePath )
126
150
if err != nil {
0 commit comments