File size: 7,945 Bytes
b54637e
 
f27c4a0
b54637e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f27c4a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import logging
import socket
from datetime import datetime, timedelta
from flask import request, current_app
from models import SystemLog
from app import db

# Create logger
logger = logging.getLogger(__name__)

def get_logger(name):
    """Get a logger instance"""
    return logging.getLogger(name)

def log_event(severity, message, event_type=None, user_id=None, source_ip=None, 
              facility=None, app_name=None, proc_id=None, msg_id=None, structured_data=None):
    """
    Log an event in RFC 5424 format to the database
    
    Severity levels (RFC 5424):
    0 - Emergency, 1 - Alert, 2 - Critical, 3 - Error
    4 - Warning, 5 - Notice, 6 - Informational, 7 - Debug
    """
    try:
        config = current_app.config.get('STORAGE_CONFIG')
        
        # Default values
        if facility is None:
            facility = config.SYSLOG_FACILITY if config else 16
        if app_name is None:
            app_name = config.SYSLOG_APP_NAME if config else 'attestation-store'
        if source_ip is None and request:
            source_ip = request.remote_addr
        
        hostname = config.SYSLOG_HOSTNAME if config else socket.gethostname()
        
        # Create log entry
        log_entry = SystemLog(
            facility=facility,
            severity=severity,
            version=1,
            timestamp=datetime.utcnow(),
            hostname=hostname,
            app_name=app_name,
            proc_id=proc_id,
            msg_id=msg_id,
            structured_data=structured_data,
            message=message,
            user_id=user_id,
            source_ip=source_ip,
            event_type=event_type
        )
        
        db.session.add(log_entry)
        db.session.commit()
        
        # Also log to application logger
        log_level = {
            0: logging.CRITICAL,  # Emergency
            1: logging.CRITICAL,  # Alert
            2: logging.CRITICAL,  # Critical
            3: logging.ERROR,     # Error
            4: logging.WARNING,   # Warning
            5: logging.INFO,      # Notice
            6: logging.INFO,      # Informational
            7: logging.DEBUG      # Debug
        }.get(severity, logging.INFO)
        
        logger.log(log_level, f"[{event_type}] {message}")
        
    except Exception as e:
        # Fallback to standard logging if database logging fails
        logger.error(f"Failed to log event to database: {e}")
        logger.log(logging.INFO, f"[{event_type}] {message}")

def parse_rfc5424_log(log_message):
    """
    Parse an RFC 5424 syslog message
    Returns a dictionary with parsed fields
    """
    try:
        # Basic RFC 5424 parsing
        # Format: <priority>version timestamp hostname app-name procid msgid structured-data msg
        
        if not log_message.startswith('<'):
            raise ValueError("Invalid RFC 5424 format - missing priority")
        
        # Extract priority
        priority_end = log_message.find('>')
        if priority_end == -1:
            raise ValueError("Invalid RFC 5424 format - missing priority end")
        
        priority = int(log_message[1:priority_end])
        facility = priority // 8
        severity = priority % 8
        
        # Split remaining parts
        remaining = log_message[priority_end + 1:].strip()
        parts = remaining.split(' ', 6)
        
        if len(parts) < 6:
            raise ValueError("Invalid RFC 5424 format - insufficient parts")
        
        version = int(parts[0]) if parts[0] != '-' else 1
        timestamp_str = parts[1] if parts[1] != '-' else None
        hostname = parts[2] if parts[2] != '-' else None
        app_name = parts[3] if parts[3] != '-' else None
        proc_id = parts[4] if parts[4] != '-' else None
        msg_id = parts[5] if parts[5] != '-' else None
        
        # Parse structured data and message
        structured_data = None
        message = ""
        
        if len(parts) > 6:
            remainder = parts[6]
            if remainder.startswith('['):
                # Find end of structured data
                bracket_count = 0
                sd_end = 0
                for i, char in enumerate(remainder):
                    if char == '[':
                        bracket_count += 1
                    elif char == ']':
                        bracket_count -= 1
                        if bracket_count == 0:
                            sd_end = i + 1
                            break
                
                if sd_end > 0:
                    structured_data = remainder[:sd_end]
                    message = remainder[sd_end:].strip()
                else:
                    message = remainder
            else:
                if remainder != '-':
                    message = remainder
        
        # Parse timestamp
        timestamp = None
        if timestamp_str:
            try:
                # Handle various timestamp formats
                for fmt in ['%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S']:
                    try:
                        timestamp = datetime.strptime(timestamp_str, fmt)
                        break
                    except ValueError:
                        continue
            except:
                timestamp = datetime.utcnow()
        
        return {
            'facility': facility,
            'severity': severity,
            'version': version,
            'timestamp': timestamp or datetime.utcnow(),
            'hostname': hostname,
            'app_name': app_name,
            'proc_id': proc_id,
            'msg_id': msg_id,
            'structured_data': structured_data,
            'message': message
        }
        
    except Exception as e:
        logger.error(f"Failed to parse RFC 5424 log: {e}")
        # Return basic log entry for unparseable messages
        return {
            'facility': 16,  # Local use
            'severity': 6,   # Info
            'version': 1,
            'timestamp': datetime.utcnow(),
            'hostname': None,
            'app_name': 'unknown',
            'proc_id': None,
            'msg_id': None,
            'structured_data': None,
            'message': log_message
        }

def cleanup_old_logs(retention_days=None):
    """
    Delete logs older than the specified retention period.
    
    Args:
        retention_days (int, optional): Number of days to retain logs.
            If not provided, uses LOG_RETENTION_DAYS from config.
            Set to 0 to skip cleanup.
    
    Returns:
        int: Number of log entries deleted
    
    Example:
        # Delete logs older than 10 days (using default config)
        deleted_count = cleanup_old_logs()
        
        # Delete logs older than 30 days
        deleted_count = cleanup_old_logs(retention_days=30)
    """
    try:
        # Get retention period from config if not specified
        if retention_days is None:
            config = current_app.config.get('STORAGE_CONFIG')
            retention_days = config.LOG_RETENTION_DAYS if config else 10
        
        # Skip cleanup if retention is 0 or negative
        if retention_days <= 0:
            logger.info("Log cleanup skipped (retention disabled)")
            return 0
        
        # Calculate cutoff date
        cutoff_date = datetime.utcnow() - timedelta(days=retention_days)
        
        # Delete old logs
        deleted = SystemLog.query.filter(SystemLog.timestamp < cutoff_date).delete()
        db.session.commit()
        
        if deleted > 0:
            logger.info(f"Deleted {deleted} log entries older than {retention_days} days (before {cutoff_date.isoformat()})")
        else:
            logger.debug(f"No logs found older than {retention_days} days")
        
        return deleted
        
    except Exception as e:
        logger.error(f"Failed to cleanup old logs: {e}")
        db.session.rollback()
        return 0