123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- package nginx_log
- import (
- "fmt"
- "path/filepath"
- "regexp"
- "strings"
- "time"
- "github.com/0xJacky/Nginx-UI/model"
- "github.com/0xJacky/Nginx-UI/query"
- "github.com/uozi-tech/cosy/logger"
- "gorm.io/gen/field"
- "gorm.io/gorm"
- )
- // PersistenceManager handles database operations for log index positions
- type PersistenceManager struct{}
- // NewPersistenceManager creates a new persistence manager
- func NewPersistenceManager() *PersistenceManager {
- return &PersistenceManager{}
- }
- // GetLogIndex retrieves the index record for a log file path
- func (pm *PersistenceManager) GetLogIndex(path string) (*model.NginxLogIndex, error) {
- q := query.NginxLogIndex
- logIndex, err := q.Where(q.Path.Eq(path)).First()
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- // Return a new record for first-time indexing
- // Determine main log path for grouping
- mainLogPath := getMainLogPathFromFile(path)
- return &model.NginxLogIndex{
- Path: path,
- MainLogPath: mainLogPath,
- Enabled: true,
- }, nil
- }
- return nil, fmt.Errorf("failed to get log index: %w", err)
- }
- return logIndex, nil
- }
- // SaveLogIndex saves or updates the index record using gen Assign.FirstOrCreate
- func (pm *PersistenceManager) SaveLogIndex(logIndex *model.NginxLogIndex) error {
- logIndex.Enabled = true
- // Ensure MainLogPath is set
- if logIndex.MainLogPath == "" {
- logIndex.MainLogPath = getMainLogPathFromFile(logIndex.Path)
- }
- q := query.NginxLogIndex
- savedRecord, err := q.Where(q.Path.Eq(logIndex.Path)).
- Assign(field.Attrs(logIndex)).
- FirstOrCreate()
- if err != nil {
- return fmt.Errorf("failed to save log index: %w", err)
- }
- // Update the passed object with the saved record data
- *logIndex = *savedRecord
- return nil
- }
- // GetAllLogIndexes retrieves all log index records
- func (pm *PersistenceManager) GetAllLogIndexes() ([]*model.NginxLogIndex, error) {
- q := query.NginxLogIndex
- indexes, err := q.Where(q.Enabled.Is(true)).Find()
- if err != nil {
- return nil, fmt.Errorf("failed to get log indexes: %w", err)
- }
- return indexes, nil
- }
- // DeleteLogIndex deletes a log index record (hard delete)
- func (pm *PersistenceManager) DeleteLogIndex(path string) error {
- q := query.NginxLogIndex
- _, err := q.Unscoped().Where(q.Path.Eq(path)).Delete()
- if err != nil {
- return fmt.Errorf("failed to delete log index: %w", err)
- }
- logger.Infof("Hard deleted log index for path: %s", path)
- return nil
- }
- // DisableLogIndex disables indexing for a log file
- func (pm *PersistenceManager) DisableLogIndex(path string) error {
- q := query.NginxLogIndex
- _, err := q.Where(q.Path.Eq(path)).Update(q.Enabled, false)
- if err != nil {
- return fmt.Errorf("failed to disable log index: %w", err)
- }
- logger.Infof("Disabled log index for path: %s", path)
- return nil
- }
- // EnableLogIndex enables indexing for a log file
- func (pm *PersistenceManager) EnableLogIndex(path string) error {
- q := query.NginxLogIndex
- _, err := q.Where(q.Path.Eq(path)).Update(q.Enabled, true)
- if err != nil {
- return fmt.Errorf("failed to enable log index: %w", err)
- }
- logger.Infof("Enabled log index for path: %s", path)
- return nil
- }
- // CleanupOldIndexes removes index records for files that haven't been indexed in a long time
- func (pm *PersistenceManager) CleanupOldIndexes(maxAge time.Duration) error {
- cutoff := time.Now().Add(-maxAge)
- q := query.NginxLogIndex
- result, err := q.Unscoped().Where(q.LastIndexed.Lt(cutoff)).Delete()
- if err != nil {
- return fmt.Errorf("failed to cleanup old indexes: %w", err)
- }
- if result.RowsAffected > 0 {
- logger.Infof("Cleaned up %d old log index records", result.RowsAffected)
- }
- return nil
- }
- // GetIndexStats returns statistics about stored index records
- func (pm *PersistenceManager) GetIndexStats() (map[string]interface{}, error) {
- q := query.NginxLogIndex
- // Count total records
- totalCount, err := q.Count()
- if err != nil {
- return nil, fmt.Errorf("failed to count total indexes: %w", err)
- }
- // Count enabled records
- enabledCount, err := q.Where(q.Enabled.Is(true)).Count()
- if err != nil {
- return nil, fmt.Errorf("failed to count enabled indexes: %w", err)
- }
- // Sum document counts
- var result struct {
- Total uint64
- }
- if err := q.Select(q.DocumentCount.Sum().As("total")).Scan(&result); err != nil {
- return nil, fmt.Errorf("failed to sum document counts: %w", err)
- }
- return map[string]interface{}{
- "total_files": totalCount,
- "enabled_files": enabledCount,
- "total_documents": result.Total,
- }, nil
- }
- // GetLogFileInfo retrieves the log file info for a given path.
- func (pm *PersistenceManager) GetLogFileInfo(path string) (*LogFileInfo, error) {
- logIndex, err := pm.GetLogIndex(path)
- if err != nil {
- return nil, err
- }
- return &LogFileInfo{
- Path: logIndex.Path,
- LastModified: logIndex.LastModified,
- LastSize: logIndex.LastSize,
- LastIndexed: logIndex.LastIndexed,
- }, nil
- }
- // SaveLogFileInfo saves the log file info for a given path.
- func (pm *PersistenceManager) SaveLogFileInfo(path string, info *LogFileInfo) error {
- logIndex, err := pm.GetLogIndex(path)
- if err != nil {
- return err
- }
- logIndex.LastModified = info.LastModified
- logIndex.LastSize = info.LastSize
- logIndex.LastIndexed = info.LastIndexed
- return pm.SaveLogIndex(logIndex)
- }
- // Close is a no-op for PersistenceManager (database connections are managed globally)
- func (pm *PersistenceManager) Close() error {
- return nil
- }
- // DeleteAllLogIndexes deletes all log index records
- func (pm *PersistenceManager) DeleteAllLogIndexes() error {
- q := query.NginxLogIndex
- result, err := q.Unscoped().Delete()
- if err != nil {
- return fmt.Errorf("failed to delete all log indexes: %w", err)
- }
- logger.Infof("Deleted all %d log index records", result.RowsAffected)
- return nil
- }
- // getMainLogPathFromFile extracts the main log path from a file (including rotated files)
- // This is a standalone version of getMainLogPath for use in persistence layer
- func getMainLogPathFromFile(filePath string) string {
- dir := filepath.Dir(filePath)
- filename := filepath.Base(filePath)
- // Remove .gz compression suffix if present
- filename = strings.TrimSuffix(filename, ".gz")
- // Handle numbered rotation (access.log.1, access.log.2, etc.)
- // Use a more specific pattern to avoid matching date patterns like "20231201"
- if match := regexp.MustCompile(`^(.+)\.(\d{1,3})$`).FindStringSubmatch(filename); len(match) > 1 {
- // Only match if the number is reasonable for rotation (1-999)
- baseFilename := match[1]
- return filepath.Join(dir, baseFilename)
- }
- // Handle date-based rotation (access.20231201, access.2023-12-01, etc.)
- if isDatePattern(filename) {
- // This is a date-based rotation, return the parent directory
- // as we can't determine the exact base name
- return filepath.Join(dir, "access.log") // Default assumption
- }
- // Check if it's a dot-separated rotation (access.log.YYYYMMDD)
- parts := strings.Split(filename, ".")
- if len(parts) >= 3 {
- lastPart := parts[len(parts)-1]
- if isDatePattern(lastPart) {
- // Remove the date part
- basenameParts := parts[:len(parts)-1]
- baseFilename := strings.Join(basenameParts, ".")
- return filepath.Join(dir, baseFilename)
- }
- }
- // If no rotation pattern is found, return the original path
- return filePath
- }
|