Webhookでエラー通知、リファクタリング
This commit is contained in:
parent
e3eac5581c
commit
e279314af0
93
README.md
93
README.md
@ -11,11 +11,11 @@ PythonのFlaskフレームワークを使用し、Ubuntu上で動作します。
|
|||||||
### 1. リポジトリのクローン
|
### 1. リポジトリのクローン
|
||||||
```bash
|
```bash
|
||||||
git clone https://ntki-personal-web.access.ly/ntki72/voicepeak_api.git
|
git clone https://ntki-personal-web.access.ly/ntki72/voicepeak_api.git
|
||||||
|
cd voicepeak_api
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Pythonの仮想環境を作成
|
### 2. Pythonの仮想環境を作成
|
||||||
仮想環境を作成し、必要なパッケージをインストールします。
|
仮想環境を作成し、必要なパッケージをインストールします。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 仮想環境の作成
|
# 仮想環境の作成
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
@ -27,15 +27,34 @@ source venv/bin/activate
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Systemdサービスの設定
|
### 3. 環境変数の設定
|
||||||
`_voicepeak-api.service`をコピーし`voicepeak-api.service`にリネームします。
|
`.env` ファイルを作成し、必要な環境変数を設定します。
|
||||||
`voicepeak-api.service`を開きServiceセクションのパラメーターを適切なものに書き換えます。
|
|
||||||
`voicepeak-api.service`ファイルをシステムのsystemdサービスディレクトリにシンボリックリンクを作成します。
|
|
||||||
```bash
|
```bash
|
||||||
sudo ln -s /path/to/your/project/voicepeak-api.service /etc/systemd/system/voicepeak-api.service
|
cp _env .env
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. サービスのリロードと起動
|
`.env` ファイルを開き、以下の変数を適切に設定します:
|
||||||
|
|
||||||
|
- `VOICEPEAK_PATH`: VOICEPEAKの実行ファイルへのパス
|
||||||
|
- `PROJECT_PATH`: プロジェクトのルートディレクトリへのパス
|
||||||
|
- `ERROR_WEBHOOK_URL`: エラー通知用のDiscord WebhookのURL
|
||||||
|
|
||||||
|
### 4. Systemdサービスの設定
|
||||||
|
**注意**: 以前にこのサービスをインストールしたことがある場合は、まず「systemdサービスの削除」セクションの手順に従って古い設定を削除することをお勧めします。
|
||||||
|
|
||||||
|
`_voicepeak-api.service` ファイルをコピーし、必要に応じて編集します。
|
||||||
|
```bash
|
||||||
|
cp _voicepeak-api.service voicepeak-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
`voicepeak-api.service` ファイルを開き、`User`、`Group`、`WorkingDirectory`、`Environment` などのパラメータを適切に設定します。
|
||||||
|
|
||||||
|
設定が完了したら、システムのsystemdサービスディレクトリにシンボリックリンクを作成します。
|
||||||
|
```bash
|
||||||
|
sudo ln -s /path/to/your/project/services/voicepeak-api.service /etc/systemd/system/voicepeak-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. サービスのリロードと起動
|
||||||
systemdに新しいサービスを認識させ、サービスを起動します。
|
systemdに新しいサービスを認識させ、サービスを起動します。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -49,7 +68,7 @@ sudo systemctl start voicepeak-api.service
|
|||||||
sudo systemctl enable voicepeak-api.service
|
sudo systemctl enable voicepeak-api.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 起動確認
|
### 6. 起動確認
|
||||||
サービスが正常に起動しているか確認します。
|
サービスが正常に起動しているか確認します。
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl status voicepeak-api.service
|
sudo systemctl status voicepeak-api.service
|
||||||
@ -57,6 +76,62 @@ sudo systemctl status voicepeak-api.service
|
|||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
サービスが正常に起動している場合、以下のようにリクエストを送信して音声を生成できます。
|
サービスが正常に起動している場合、以下のようにリクエストを送信して音声を生成できます。
|
||||||
|
|
||||||
|
### 音声生成
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://127.0.0.1:5000/generate_voice -H "Content-Type: application/json" -d '{"text": "こんにちは"}' --output output.wav
|
curl -X POST http://localhost:15000/generate_voice \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"text": "こんにちは、テストです。",
|
||||||
|
"narrator": "SEKAI",
|
||||||
|
"emotion_happy": "0",
|
||||||
|
"emotion_sad": "0",
|
||||||
|
"emotion_angry": "0",
|
||||||
|
"emotion_fun": "0",
|
||||||
|
"pitch": "1"
|
||||||
|
}' \
|
||||||
|
--output test_output.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
### エラーテスト
|
||||||
|
意図的にエラーを発生させてエラーハンドリングをテストするには:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:15000/test_error
|
||||||
|
```
|
||||||
|
|
||||||
|
## トラブルシューティング
|
||||||
|
エラーが発生した場合、アプリケーションログを確認してください。また、設定したDiscord Webhookにもエラーメッセージが送信されていることを確認してください。
|
||||||
|
|
||||||
|
## 開発モードでの実行
|
||||||
|
```bash
|
||||||
|
./scripts/run_dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## systemdサービスの削除
|
||||||
|
プロジェクトを削除する場合や、サービスの設定をリセットする必要がある場合は、以下の手順でsystemdサービスを削除できます。
|
||||||
|
|
||||||
|
1. サービスを停止し、無効化します:
|
||||||
|
```bash
|
||||||
|
sudo systemctl stop voicepeak-api.service
|
||||||
|
sudo systemctl disable voicepeak-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
2. サービスファイルを systemd のディレクトリから削除します:
|
||||||
|
```bash
|
||||||
|
sudo rm /etc/systemd/system/voicepeak-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
3. シンボリックリンクを削除した場合、プロジェクトディレクトリ内のサービスファイルも削除できます:
|
||||||
|
```bash
|
||||||
|
rm /path/to/your/project/services/voicepeak-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
4. systemd の設定をリロードします:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
5. systemd の設定から完全に削除されたことを確認します:
|
||||||
|
```bash
|
||||||
|
sudo systemctl reset-failed
|
||||||
```
|
```
|
||||||
|
3
_.env
Normal file
3
_.env
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
VOICEPEAK_PATH=/path/to/voicepeak
|
||||||
|
PROJECT_PATH=/path/to/project
|
||||||
|
ERROR_WEBHOOK_URL=https://your-webhook-url.com/endpoint
|
@ -1,12 +1,16 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from config import Config
|
from config import get_config
|
||||||
|
from .errors import register_error_handlers
|
||||||
|
|
||||||
def create_app(config_class=Config):
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(config_class)
|
config = get_config()
|
||||||
config_class.init_app(app)
|
app.config.from_object(config)
|
||||||
|
config.init_app(app)
|
||||||
|
|
||||||
from app import routes
|
from . import routes
|
||||||
app.register_blueprint(routes.bp)
|
app.register_blueprint(routes.bp)
|
||||||
|
|
||||||
|
register_error_handlers(app) # Make sure this line is present
|
||||||
|
|
||||||
return app
|
return app
|
17
app/errors.py
Normal file
17
app/errors.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from flask import jsonify
|
||||||
|
import traceback
|
||||||
|
from .webhook import send_error_webhook
|
||||||
|
|
||||||
|
def handle_exception(app, error):
|
||||||
|
error_message = str(error)
|
||||||
|
error_type = type(error).__name__
|
||||||
|
stack_trace = traceback.format_exc()
|
||||||
|
|
||||||
|
app.logger.error(f'Unhandled Exception: {error_type} - {error_message}\n{stack_trace}')
|
||||||
|
|
||||||
|
send_error_webhook(error_message, error_type, stack_trace)
|
||||||
|
|
||||||
|
return jsonify(error=error_message), 500
|
||||||
|
|
||||||
|
def register_error_handlers(app):
|
||||||
|
app.register_error_handler(Exception, lambda e: handle_exception(app, e))
|
@ -1,9 +1,10 @@
|
|||||||
from flask import Blueprint, request, jsonify, send_file
|
from flask import Blueprint, request, jsonify, send_file, current_app
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from flask import current_app
|
import traceback
|
||||||
|
from .webhook import send_error_webhook
|
||||||
|
|
||||||
bp = Blueprint('main', __name__)
|
bp = Blueprint('main', __name__)
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ def generate_voice():
|
|||||||
try:
|
try:
|
||||||
data = request.json
|
data = request.json
|
||||||
text = data.get('text')
|
text = data.get('text')
|
||||||
narrator = data.get('narrator', '1')
|
narrator = data.get('narrator', 'SEKAI')
|
||||||
emotion_happy = data.get('emotion_happy', '0')
|
emotion_happy = data.get('emotion_happy', '0')
|
||||||
emotion_sad = data.get('emotion_sad', '0')
|
emotion_sad = data.get('emotion_sad', '0')
|
||||||
emotion_angry = data.get('emotion_angry', '0')
|
emotion_angry = data.get('emotion_angry', '0')
|
||||||
@ -74,4 +75,24 @@ def generate_voice():
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
error_message = str(e)
|
||||||
|
error_type = type(e).__name__
|
||||||
|
stack_trace = traceback.format_exc()
|
||||||
|
|
||||||
|
current_app.logger.error(f'Error in generate_voice: {error_message}')
|
||||||
|
current_app.logger.error(f'Traceback: {stack_trace}')
|
||||||
|
|
||||||
|
# Send webhook
|
||||||
|
send_error_webhook(error_message, error_type, stack_trace)
|
||||||
|
|
||||||
|
return jsonify({'error': error_message, 'traceback': stack_trace}), 500
|
||||||
|
|
||||||
|
@bp.route('/test_error')
|
||||||
|
def test_error():
|
||||||
|
current_app.logger.info("Testing error webhook")
|
||||||
|
raise Exception("This is a test error from routes.py")
|
||||||
|
|
||||||
|
@bp.route('/test_500_error')
|
||||||
|
def test_500_error():
|
||||||
|
current_app.logger.info("Testing 500 error webhook")
|
||||||
|
raise InternalServerError("This is a test 500 error from routes.py")
|
30
app/webhook.py
Normal file
30
app/webhook.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import requests
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
def send_error_webhook(error_message, error_type, stack_trace):
|
||||||
|
webhook_url = current_app.config.get('ERROR_WEBHOOK_URL')
|
||||||
|
if not webhook_url:
|
||||||
|
current_app.logger.warning("ERROR_WEBHOOK_URL not set. Skipping webhook.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# エラーメッセージを整形
|
||||||
|
formatted_message = f"**Error Type:** {error_type}\n**Message:** {error_message}\n```\n{stack_trace[:1500]}...\n```"
|
||||||
|
|
||||||
|
# ディスコードのWebhook形式に合わせてペイロードを構築
|
||||||
|
payload = {
|
||||||
|
"embeds": [{
|
||||||
|
"title": "Error Details",
|
||||||
|
"description": formatted_message,
|
||||||
|
"color": 15158332 # 赤色
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_app.logger.debug(f"Sending webhook to URL: {webhook_url}")
|
||||||
|
current_app.logger.debug(f"Webhook payload: {payload}")
|
||||||
|
|
||||||
|
response = requests.post(webhook_url, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
current_app.logger.info(f"Error webhook sent successfully to Discord. Status code: {response.status_code}")
|
||||||
|
except requests.RequestException as e:
|
||||||
|
current_app.logger.error(f"Failed to send error webhook to Discord: {str(e)}")
|
30
config.py
30
config.py
@ -1,30 +0,0 @@
|
|||||||
import os
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
VOICEPEAK_PATH = os.getenv('VOICEPEAK_PATH')
|
|
||||||
PROJECT_PATH = os.getenv('PROJECT_PATH')
|
|
||||||
|
|
||||||
# 開発環境かどうかの判定
|
|
||||||
FLASK_ENV = os.getenv('FLASK_ENV', 'production')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def init_app(cls, app):
|
|
||||||
# Flaskアプリケーションのデバッグモードを設定
|
|
||||||
app.debug = cls.FLASK_ENV == 'development'
|
|
||||||
|
|
||||||
class DevelopmentConfig(Config):
|
|
||||||
# 開発環境時の設定: 今回は特になし
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
|
||||||
# 本番環境時の設定: 今回は特になし
|
|
||||||
pass
|
|
||||||
|
|
||||||
config = {
|
|
||||||
'development': DevelopmentConfig,
|
|
||||||
'production': ProductionConfig,
|
|
||||||
'default': Config
|
|
||||||
}
|
|
3
config/__init__.py
Normal file
3
config/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .settings import get_config, Config, DevelopmentConfig, ProductionConfig
|
||||||
|
|
||||||
|
__all__ = ['get_config', 'Config', 'DevelopmentConfig', 'ProductionConfig']
|
32
config/settings.py
Normal file
32
config/settings.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
VOICEPEAK_PATH = os.getenv('VOICEPEAK_PATH')
|
||||||
|
PROJECT_PATH = os.getenv('PROJECT_PATH')
|
||||||
|
ERROR_WEBHOOK_URL = os.getenv('ERROR_WEBHOOK_URL')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_app(cls, app):
|
||||||
|
app.logger.info(f"VOICEPEAK_PATH: {cls.VOICEPEAK_PATH}")
|
||||||
|
app.logger.info(f"PROJECT_PATH: {cls.PROJECT_PATH}")
|
||||||
|
app.logger.info(f"ERROR_WEBHOOK_URL: {'Set' if cls.ERROR_WEBHOOK_URL else 'Not set'}")
|
||||||
|
|
||||||
|
class DevelopmentConfig(Config):
|
||||||
|
DEBUG = True
|
||||||
|
ENV = 'development'
|
||||||
|
|
||||||
|
class ProductionConfig(Config):
|
||||||
|
DEBUG = False
|
||||||
|
ENV = 'production'
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'development': DevelopmentConfig,
|
||||||
|
'production': ProductionConfig,
|
||||||
|
'default': ProductionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
return config[os.getenv('FLASK_ENV', 'production')]
|
@ -1,19 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SERVICE_NAME="voicepeak-api.service"
|
|
||||||
|
|
||||||
# サービスの状態を確認
|
|
||||||
SERVICE_STATUS=$(systemctl is-active $SERVICE_NAME)
|
|
||||||
|
|
||||||
if [ "$SERVICE_STATUS" = "active" ]; then
|
|
||||||
echo "Restarting $SERVICE_NAME..."
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl restart $SERVICE_NAME
|
|
||||||
else
|
|
||||||
echo "Reloading and starting $SERVICE_NAME..."
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl start $SERVICE_NAME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# サービスの状態を表示
|
|
||||||
sudo systemctl status $SERVICE_NAME
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 仮想環境をアクティブにする
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
# Flaskアプリケーションを起動
|
|
||||||
export FLASK_APP=app
|
|
||||||
export FLASK_ENV=development
|
|
||||||
flask run --host=0.0.0.0 --port=5000
|
|
@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source venv/bin/activate
|
|
||||||
gunicorn --workers 2 --bind 0.0.0.0:5000 wsgi:app
|
|
16
scripts/run_dev.sh
Executable file
16
scripts/run_dev.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# プロジェクトのルートディレクトリに移動
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# 仮想環境が有効でない場合はアクティベート
|
||||||
|
if [[ -z "${VIRTUAL_ENV}" ]]; then
|
||||||
|
source venv/bin/activate
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 環境変数を設定
|
||||||
|
export FLASK_ENV=development
|
||||||
|
export FLASK_APP=wsgi.py
|
||||||
|
|
||||||
|
# Flaskアプリケーションを起動
|
||||||
|
flask run --host=0.0.0.0 --port=5000
|
17
scripts/run_prod.sh
Executable file
17
scripts/run_prod.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# プロジェクトのルートディレクトリに移動
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# 仮想環境が有効でない場合はアクティベート
|
||||||
|
if [[ -z "${VIRTUAL_ENV}" ]]; then
|
||||||
|
source venv/bin/activate
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 環境変数を設定
|
||||||
|
export FLASK_ENV=production
|
||||||
|
export FLASK_APP=wsgi.py
|
||||||
|
|
||||||
|
# Gunicornでアプリケーションを起動
|
||||||
|
# NOTE: workersを1以上にするにはVOICEPEAKが1つ以上のプロセスを起動できないので実装を改善してエラーエラーが発生しないようにする必要がある
|
||||||
|
exec gunicorn --workers 1 --bind 0.0.0.0:5000 wsgi:app
|
@ -7,7 +7,7 @@ User=your_username
|
|||||||
Group=your_group
|
Group=your_group
|
||||||
WorkingDirectory=/path/to/your/project
|
WorkingDirectory=/path/to/your/project
|
||||||
Environment="PATH=/path/to/your/project/venv/bin"
|
Environment="PATH=/path/to/your/project/venv/bin"
|
||||||
ExecStart=/path/to/your/project/run_prod.sh
|
ExecStart=/bin/bash -c 'source /path/to/your/project/venv/bin/activate && /path/to/your/project/scripts/run_prod.sh'
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
[Install]
|
[Install]
|
@ -1,15 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=VoicePeak API Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=ntki
|
|
||||||
Group=ntki
|
|
||||||
WorkingDirectory=/home/ntki/voicepeak_api
|
|
||||||
Environment="PATH=/home/ntki/voicepeak_api/venv/bin"
|
|
||||||
ExecStart=/home/ntki/voicepeak_api/venv/bin/python /home/ntki/voicepeak_api/app.py
|
|
||||||
EnvironmentFile=/home/ntki/voicepeak_api/.env
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
Loading…
Reference in New Issue
Block a user