224 lines
7.2 KiB
Python
224 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
系統健康檢查 API
|
||
|
||
Author: PANJIT IT Team
|
||
Created: 2024-01-28
|
||
Modified: 2024-01-28
|
||
"""
|
||
|
||
from datetime import datetime
|
||
from flask import Blueprint, jsonify
|
||
from app.utils.helpers import create_response
|
||
from app.utils.logger import get_logger
|
||
from app.models.job import TranslationJob
|
||
from app.utils.timezone import format_taiwan_time, now_taiwan
|
||
|
||
health_bp = Blueprint('health', __name__, url_prefix='/health')
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
@health_bp.route('', methods=['GET'])
|
||
def health_check():
|
||
"""系統健康檢查"""
|
||
try:
|
||
status = {
|
||
'timestamp': format_taiwan_time(datetime.utcnow(), "%Y-%m-%d %H:%M:%S"),
|
||
'status': 'healthy',
|
||
'services': {}
|
||
}
|
||
|
||
# 資料庫檢查
|
||
try:
|
||
from app import db
|
||
from sqlalchemy import text
|
||
db.session.execute(text('SELECT 1'))
|
||
status['services']['database'] = {'status': 'healthy'}
|
||
except Exception as e:
|
||
status['services']['database'] = {
|
||
'status': 'unhealthy',
|
||
'error': str(e)
|
||
}
|
||
status['status'] = 'unhealthy'
|
||
|
||
# Redis 檢查
|
||
try:
|
||
import redis
|
||
from flask import current_app
|
||
redis_client = redis.from_url(current_app.config['REDIS_URL'])
|
||
redis_client.ping()
|
||
status['services']['redis'] = {'status': 'healthy'}
|
||
except Exception as e:
|
||
status['services']['redis'] = {
|
||
'status': 'unhealthy',
|
||
'error': str(e)
|
||
}
|
||
# Redis 暫時異常不影響整體狀態(如果沒有使用 Celery)
|
||
|
||
# LDAP 檢查
|
||
try:
|
||
from app.utils.ldap_auth import LDAPAuthService
|
||
ldap_service = LDAPAuthService()
|
||
if ldap_service.test_connection():
|
||
status['services']['ldap'] = {'status': 'healthy'}
|
||
else:
|
||
status['services']['ldap'] = {'status': 'unhealthy', 'error': 'Connection failed'}
|
||
except Exception as e:
|
||
status['services']['ldap'] = {
|
||
'status': 'unhealthy',
|
||
'error': str(e)
|
||
}
|
||
# LDAP 異常會影響整體狀態
|
||
status['status'] = 'unhealthy'
|
||
|
||
# 檔案系統檢查
|
||
try:
|
||
from pathlib import Path
|
||
from flask import current_app
|
||
upload_folder = Path(current_app.config['UPLOAD_FOLDER'])
|
||
|
||
# 檢查上傳目錄是否可寫
|
||
test_file = upload_folder / 'health_check.tmp'
|
||
test_file.write_text('health_check')
|
||
test_file.unlink()
|
||
|
||
status['services']['filesystem'] = {'status': 'healthy'}
|
||
except Exception as e:
|
||
status['services']['filesystem'] = {
|
||
'status': 'unhealthy',
|
||
'error': str(e)
|
||
}
|
||
status['status'] = 'unhealthy'
|
||
|
||
# 檢查 Dify API(如果配置了)
|
||
try:
|
||
from flask import current_app
|
||
if current_app.config.get('DIFY_API_KEY') and current_app.config.get('DIFY_API_BASE_URL'):
|
||
# 這裡會在實作 Dify 服務時加入連線測試
|
||
status['services']['dify_api'] = {'status': 'not_tested'}
|
||
else:
|
||
status['services']['dify_api'] = {'status': 'not_configured'}
|
||
except Exception as e:
|
||
status['services']['dify_api'] = {
|
||
'status': 'error',
|
||
'error': str(e)
|
||
}
|
||
|
||
return jsonify(status), 200 if status['status'] == 'healthy' else 503
|
||
|
||
except Exception as e:
|
||
logger.error(f"Health check error: {str(e)}")
|
||
return jsonify({
|
||
'timestamp': format_taiwan_time(datetime.utcnow(), "%Y-%m-%d %H:%M:%S"),
|
||
'status': 'error',
|
||
'error': str(e)
|
||
}), 500
|
||
|
||
|
||
@health_bp.route('/metrics', methods=['GET'])
|
||
def get_metrics():
|
||
"""系統指標"""
|
||
try:
|
||
# 統計任務狀態
|
||
from app import db
|
||
from sqlalchemy import func
|
||
|
||
job_stats = db.session.query(
|
||
TranslationJob.status,
|
||
func.count(TranslationJob.id)
|
||
).group_by(TranslationJob.status).all()
|
||
|
||
job_counts = {status: count for status, count in job_stats}
|
||
|
||
# 系統指標
|
||
metrics_data = {
|
||
'timestamp': format_taiwan_time(datetime.utcnow(), "%Y-%m-%d %H:%M:%S"),
|
||
'jobs': {
|
||
'pending': job_counts.get('PENDING', 0),
|
||
'processing': job_counts.get('PROCESSING', 0),
|
||
'completed': job_counts.get('COMPLETED', 0),
|
||
'failed': job_counts.get('FAILED', 0),
|
||
'retry': job_counts.get('RETRY', 0),
|
||
'total': sum(job_counts.values())
|
||
}
|
||
}
|
||
|
||
# 添加最近24小時的統計
|
||
from datetime import timedelta
|
||
yesterday = datetime.utcnow() - timedelta(days=1)
|
||
|
||
recent_jobs = db.session.query(
|
||
TranslationJob.status,
|
||
func.count(TranslationJob.id)
|
||
).filter(
|
||
TranslationJob.created_at >= yesterday
|
||
).group_by(TranslationJob.status).all()
|
||
|
||
recent_counts = {status: count for status, count in recent_jobs}
|
||
|
||
metrics_data['recent_24h'] = {
|
||
'pending': recent_counts.get('PENDING', 0),
|
||
'processing': recent_counts.get('PROCESSING', 0),
|
||
'completed': recent_counts.get('COMPLETED', 0),
|
||
'failed': recent_counts.get('FAILED', 0),
|
||
'retry': recent_counts.get('RETRY', 0),
|
||
'total': sum(recent_counts.values())
|
||
}
|
||
|
||
return jsonify(create_response(
|
||
success=True,
|
||
data=metrics_data
|
||
))
|
||
|
||
except Exception as e:
|
||
logger.error(f"Get metrics error: {str(e)}")
|
||
return jsonify(create_response(
|
||
success=False,
|
||
error='SYSTEM_ERROR',
|
||
message='取得系統指標失敗'
|
||
)), 500
|
||
|
||
|
||
@health_bp.route('/version', methods=['GET'])
|
||
def get_version():
|
||
"""取得版本資訊"""
|
||
try:
|
||
version_info = {
|
||
'application': 'PANJIT Document Translator',
|
||
'version': '1.0.0',
|
||
'build_date': '2024-01-28',
|
||
'python_version': None,
|
||
'flask_version': None
|
||
}
|
||
|
||
# 取得 Python 版本
|
||
import sys
|
||
version_info['python_version'] = sys.version
|
||
|
||
# 取得 Flask 版本
|
||
import flask
|
||
version_info['flask_version'] = flask.__version__
|
||
|
||
return jsonify(create_response(
|
||
success=True,
|
||
data=version_info
|
||
))
|
||
|
||
except Exception as e:
|
||
logger.error(f"Get version error: {str(e)}")
|
||
return jsonify(create_response(
|
||
success=False,
|
||
error='SYSTEM_ERROR',
|
||
message='取得版本資訊失敗'
|
||
)), 500
|
||
|
||
|
||
@health_bp.route('/ping', methods=['GET'])
|
||
def ping():
|
||
"""簡單的 ping 檢查"""
|
||
return jsonify({
|
||
'status': 'ok',
|
||
'timestamp': format_taiwan_time(datetime.utcnow(), "%Y-%m-%d %H:%M:%S"),
|
||
'message': 'pong'
|
||
}) |