Files
Server_Monitorizare_v2/app/api/logs.py
2026-04-23 15:55:46 +03:00

373 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Enhanced API endpoints for logs with compression and file support
"""
from flask import Blueprint, request, jsonify
from werkzeug.utils import secure_filename
import os
import hashlib
from datetime import datetime
from app.services.log_service import LogCompressionService
from app.services.file_service import FileUploadService
from app.models import Device, LogEntry, FileUpload
from config.database_config import get_db
import logging
# Create blueprint
logs_bp = Blueprint('logs', __name__, url_prefix='/api/logs')
# Initialize services
log_service = LogCompressionService()
file_service = FileUploadService()
@logs_bp.route('/', methods=['POST'])
@logs_bp.route('/submit', methods=['POST'])
def submit_log():
"""
Enhanced log submission with compression support
Expected JSON:
{
"hostname": "device-01",
"device_ip": "192.168.1.100",
"nume_masa": "Masa-01",
"log_message": "Card detected: ABC123",
"severity": "info", # optional: debug, info, warning, error, critical
"source_file": "/path/to/logfile.log", # optional
"metadata": {} # optional additional metadata
}
"""
try:
# Validate content type
if not request.is_json:
return jsonify({
'error': 'Content-Type must be application/json',
'success': False
}), 400
data = request.get_json()
# Validate required fields
required_fields = ['hostname', 'device_ip', 'nume_masa', 'log_message']
missing_fields = [field for field in required_fields if not data.get(field)]
if missing_fields:
return jsonify({
'error': f'Missing required fields: {", ".join(missing_fields)}',
'success': False
}), 400
# Prepare device info
device_info = {
'hostname': data['hostname'],
'device_ip': data['device_ip'],
'nume_masa': data['nume_masa'],
# Optional clients can send these to keep device records up to date
'device_type': data.get('device_type') or (data.get('metadata') or {}).get('device_type'),
'os_version': data.get('os_version') or (data.get('metadata') or {}).get('os_version'),
'location': data.get('location') or (data.get('metadata') or {}).get('location'),
'mac_address': data.get('mac_address') or (data.get('metadata') or {}).get('mac_address'),
}
# Process log with compression
result = log_service.process_log_message(
device_info=device_info,
message=data['log_message'],
severity=data.get('severity', 'info')
)
if result['success']:
# Prepare response with compression info
response_data = {
'success': True,
'message': 'Log processed successfully',
'log_id': result['log_id'],
'device_id': result['device_id'],
'compression_info': result['compression']
}
# Add alias info if template was used
if result['compression'].get('used_template'):
response_data['template_alias'] = result['compression']['template_alias']
# For clients: suggest using alias in future requests
if result['compression'].get('new_template'):
response_data['suggestion'] = f"For similar messages, you can use template alias: {result['compression']['template_alias']}"
return jsonify(response_data), 201
else:
return jsonify(result), 500
except Exception as e:
logging.error(f"Error in submit_log: {e}")
return jsonify({
'error': 'Internal server error',
'success': False
}), 500
@logs_bp.route('/template/<alias>', methods=['POST'])
def submit_templated_log():
"""
Submit log using template alias (smaller payload)
Expected JSON:
{
"alias": "CD001",
"variables": {"card_id": "ABC123"},
"device_info": {
"hostname": "device-01",
"device_ip": "192.168.1.100",
"nume_masa": "Masa-01"
}
}
"""
try:
data = request.get_json()
alias = request.view_args['alias']
# Validate required fields
if not data.get('device_info'):
return jsonify({
'error': 'device_info is required',
'success': False
}), 400
# Get template message
variables = data.get('variables', {})
full_message = log_service.get_message_by_alias(alias, variables)
if not full_message:
return jsonify({
'error': f'Template alias {alias} not found',
'success': False
}), 404
# Process as regular log
result = log_service.process_log_message(
device_info=data['device_info'],
message=full_message,
severity=data.get('severity', 'info')
)
return jsonify(result), 201 if result['success'] else 500
except Exception as e:
logging.error(f"Error in submit_templated_log: {e}")
return jsonify({
'error': 'Internal server error',
'success': False
}), 500
@logs_bp.route('/file', methods=['POST'])
def upload_log_file():
"""
Upload log file for processing
Expects multipart/form-data with:
- file: log file
- device_info: JSON string with device information
"""
try:
# Check if file was uploaded
if 'file' not in request.files:
return jsonify({
'error': 'No file uploaded',
'success': False
}), 400
file = request.files['file']
if file.filename == '':
return jsonify({
'error': 'No file selected',
'success': False
}), 400
# Get device info
device_info_str = request.form.get('device_info')
if not device_info_str:
return jsonify({
'error': 'device_info is required',
'success': False
}), 400
import json
device_info = json.loads(device_info_str)
# Process file upload
result = file_service.process_uploaded_file(file, device_info)
return jsonify(result), 201 if result['success'] else 500
except Exception as e:
logging.error(f"Error in upload_log_file: {e}")
return jsonify({
'error': 'Internal server error',
'success': False
}), 500
@logs_bp.route('/query', methods=['GET'])
def query_logs():
"""
Query logs with filters and pagination
Query parameters:
- device_id: Filter by device ID
- hostname: Filter by hostname
- severity: Filter by severity level
- start_time: Start time (ISO format)
- end_time: End time (ISO format)
- limit: Number of results (default 100)
- offset: Offset for pagination (default 0)
- include_template: Include resolved template messages (default true)
"""
try:
with get_db().get_session() as session:
# Build query
query = session.query(LogEntry).join(Device)
# Apply filters
if request.args.get('device_id'):
query = query.filter(LogEntry.device_id == int(request.args.get('device_id')))
if request.args.get('hostname'):
query = query.filter(Device.hostname == request.args.get('hostname'))
if request.args.get('severity'):
query = query.filter(LogEntry.severity == request.args.get('severity'))
if request.args.get('start_time'):
start_time = datetime.fromisoformat(request.args.get('start_time'))
query = query.filter(LogEntry.timestamp >= start_time)
if request.args.get('end_time'):
end_time = datetime.fromisoformat(request.args.get('end_time'))
query = query.filter(LogEntry.timestamp <= end_time)
# Pagination
limit = min(int(request.args.get('limit', 100)), 1000) # Max 1000
offset = int(request.args.get('offset', 0))
# Order by timestamp descending
query = query.order_by(LogEntry.timestamp.desc())
# Get total count
total_count = query.count()
# Apply pagination
logs = query.limit(limit).offset(offset).all()
# Format response
include_template = request.args.get('include_template', 'true').lower() == 'true'
log_data = []
for log in logs:
log_item = {
'id': log.id,
'device': {
'id': log.device.id,
'hostname': log.device.hostname,
'device_ip': log.device.device_ip,
'nume_masa': log.device.nume_masa
},
'timestamp': log.timestamp.isoformat(),
'severity': log.severity
}
if include_template and log.template:
log_item['message'] = log.resolved_message
log_item['template_alias'] = log.template.alias
log_item['template_category'] = log.template.category
else:
log_item['message'] = log.full_message or log.resolved_message
log_data.append(log_item)
return jsonify({
'success': True,
'logs': log_data,
'pagination': {
'total_count': total_count,
'limit': limit,
'offset': offset,
'has_more': offset + limit < total_count
}
})
except Exception as e:
logging.error(f"Error in query_logs: {e}")
return jsonify({
'error': 'Internal server error',
'success': False
}), 500
@logs_bp.route('/stats', methods=['GET'])
def get_log_stats():
"""Get logging and compression statistics"""
try:
# Get compression stats
compression_stats = log_service.get_compression_stats()
# Get additional stats
with get_db().get_session() as session:
# Device stats
active_devices = session.query(Device).filter_by(status='active').count()
total_devices = session.query(Device).count()
# Recent activity
from datetime import datetime, timedelta
last_hour = datetime.utcnow() - timedelta(hours=1)
recent_logs = session.query(LogEntry).filter(
LogEntry.timestamp >= last_hour
).count()
stats = {
'success': True,
'compression': compression_stats,
'devices': {
'active': active_devices,
'total': total_devices
},
'activity': {
'logs_last_hour': recent_logs
}
}
return jsonify(stats)
except Exception as e:
logging.error(f"Error in get_log_stats: {e}")
return jsonify({
'error': 'Internal server error',
'success': False
}), 500
@logs_bp.route('/templates', methods=['GET'])
def get_templates():
"""Get available message templates and aliases"""
try:
with get_db().get_session() as session:
from app.models import MessageTemplate
templates = session.query(MessageTemplate).order_by(
MessageTemplate.usage_count.desc()
).all()
template_data = [{
'alias': template.alias,
'category': template.category,
'template_text': template.template_text,
'usage_count': template.usage_count,
'created_at': template.created_at.isoformat()
} for template in templates]
return jsonify({
'success': True,
'templates': template_data,
'total_count': len(template_data)
})
except Exception as e:
logging.error(f"Error in get_templates: {e}")
return jsonify({
'error': 'Internal server error',
'success': False
}), 500