Skip to content

Commit eb4dc12

Browse files
committed
fix(nginx_log): initialize global log parser singleton in modern_services
1 parent 6567d92 commit eb4dc12

File tree

3 files changed

+56
-25
lines changed

3 files changed

+56
-25
lines changed

app/components.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ declare module 'vue' {
2020
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
2121
ADivider: typeof import('ant-design-vue/es')['Divider']
2222
ADrawer: typeof import('ant-design-vue/es')['Drawer']
23+
AForm: typeof import('ant-design-vue/es')['Form']
24+
AFormItem: typeof import('ant-design-vue/es')['FormItem']
2325
AInput: typeof import('ant-design-vue/es')['Input']
2426
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
2527
ALayout: typeof import('ant-design-vue/es')['Layout']
@@ -41,8 +43,10 @@ declare module 'vue' {
4143
ASelect: typeof import('ant-design-vue/es')['Select']
4244
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
4345
ASpace: typeof import('ant-design-vue/es')['Space']
46+
ASpin: typeof import('ant-design-vue/es')['Spin']
4447
AStatistic: typeof import('ant-design-vue/es')['Statistic']
4548
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
49+
ASwitch: typeof import('ant-design-vue/es')['Switch']
4650
ATabPane: typeof import('ant-design-vue/es')['TabPane']
4751
ATabs: typeof import('ant-design-vue/es')['Tabs']
4852
ATag: typeof import('ant-design-vue/es')['Tag']

internal/nginx_log/indexer/parser.go

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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
1719
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
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)
6580
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+
}
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
96114
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+
}
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
123144
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+
}
124148
// Auto-detect and handle gzip files
125149
actualReader, cleanup, err := createReaderForFile(reader, filePath)
126150
if err != nil {

internal/nginx_log/modern_services.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ func InitializeModernServices(ctx context.Context) {
8787
func initializeWithDefaults(ctx context.Context) error {
8888
logger.Info("Initializing services with default configuration")
8989

90+
// Initialize global log parser singleton before starting indexer/searcher
91+
indexer.InitLogParser()
92+
9093
// Create empty searcher (will be populated when indexes are available)
9194
searcherConfig := searcher.DefaultSearcherConfig()
9295
globalSearcher = searcher.NewDistributedSearcher(searcherConfig, []bleve.Index{})

0 commit comments

Comments
 (0)