feat(websocket): 添加日志封装函数并优化服务器启动输出

添加ws_log系列函数封装日志记录,统一WebSocket服务器日志格式
使用ws_echo替换原有echo输出,同时记录日志和控制台显示
优化服务器启动流程,增加配置信息输出和状态记录
This commit is contained in:
2026-01-24 10:05:19 +08:00
parent 3341d41422
commit 5cbd11be38

View File

@@ -10,6 +10,89 @@ error_reporting(E_ALL);
require __DIR__ . '/vendor/autoload.php';
// 引入应用公共文件包含log_write函数
require __DIR__ . '/app/common.php';
/**
* WebSocket服务器日志封装函数
* 简化log_write函数的调用自动添加WebSocket服务器前缀
*
* @param string $message 日志内容
* @param string $level 日志级别info, warning, error
* @param int $callerDep 调用栈深度默认1
* @return void
*/
function ws_log(string $message, string $level = 'info', int $callerDep = 1): void
{
log_write($message, $level, 'websocket-server.log', $callerDep + 1);
}
/**
* WebSocket服务器错误日志封装函数
* 简化错误日志的记录自动添加WebSocket服务器前缀
*
* @param string $message 错误信息
* @param int $callerDep 调用栈深度默认1
* @return void
*/
function ws_error(string $message, int $callerDep = 1): void
{
log_write($message, 'error', 'websocket-server.log', $callerDep + 1);
}
/**
* WebSocket服务器警告日志封装函数
* 简化警告日志的记录自动添加WebSocket服务器前缀
*
* @param string $message 警告信息
* @param int $callerDep 调用栈深度默认1
* @return void
*/
function ws_warning(string $message, int $callerDep = 1): void
{
log_write($message, 'warning', 'websocket-server.log', $callerDep + 1);
}
/**
* WebSocket服务器信息日志封装函数
* 简化信息日志的记录自动添加WebSocket服务器前缀
*
* @param string $message 信息内容
* @param int $callerDep 调用栈深度默认1
* @return void
*/
function ws_info(string $message, int $callerDep = 1): void
{
log_write($message, 'info', 'websocket-server.log', $callerDep + 1);
}
/**
* WebSocket服务器输出封装函数
* 同时处理日志记录和控制台输出,使代码更简洁
*
* @param string $message 输出内容
* @param string $level 日志级别info, warning, error
* @param int $callerDep 调用栈深度默认1
* @return void
*/
function ws_echo(string $message, string $level = 'info', int $callerDep = 1): void
{
// 调用相应的日志函数
switch ($level) {
case 'error':
ws_error($message, $callerDep + 1);
break;
case 'warning':
ws_warning($message, $callerDep + 1);
break;
default:
ws_info($message, $callerDep + 1);
break;
}
// 输出到控制台
echo $message . "\n";
}
// 初始化ThinkPHP应用环境加载配置和环境变量主要用于数据库连接和其他配置
use think\App;
@@ -77,8 +160,13 @@ if ($httpHost === '') {
$port = getenv('WS_PORT') ?: 8080; // WebSocket服务器端口
$address = '0.0.0.0'; // 监听所有网络接口
// 记录服务器启动信息
ws_echo("[WebSocket服务器] 正在启动...");
ws_echo("[WebSocket服务器] 配置信息: HTTP_HOST={$httpHost}, PORT={$port}, ADDRESS={$address}");
// 创建WebSocket服务器应用 - 参数顺序httpHost, port, address
$ratchetApp = new RatchetApp($httpHost, $port, $address);
ws_echo("[WebSocket服务器] 应用创建成功");
// 获取所有addon与InitAddon.php逻辑保持一致
// 1. 从数据库获取插件列表
@@ -97,10 +185,10 @@ try {
$cachedAddons = $cache->get($cacheKey);
if ($cachedAddons !== null && !empty($cachedAddons)) {
echo "[WebSocket服务器] 从缓存获取插件列表\n";
ws_echo("[WebSocket服务器] 从缓存获取插件列表");
$current_addons = $cachedAddons;
} else {
echo "[WebSocket服务器] 从数据库获取插件列表\n";
ws_echo("[WebSocket服务器] 从数据库获取插件列表");
// 尝试获取数据库连接并确保连接有效
try {
@@ -111,10 +199,10 @@ try {
// 将结果存入缓存
$cache->set($cacheKey, $current_addons, $cacheExpire);
echo "[WebSocket服务器] 插件列表已缓存(有效期: {$cacheExpire}秒)\n";
ws_echo("[WebSocket服务器] 插件列表已缓存(有效期: {$cacheExpire}秒)");
} catch (\Exception $dbEx) {
echo "[WebSocket服务器] 数据库操作失败: {$dbEx->getMessage()}\n";
echo "[WebSocket服务器] 尝试重新初始化数据库连接...\n";
ws_echo("[WebSocket服务器] 数据库操作失败: {$dbEx->getMessage()}", 'error');
ws_echo("[WebSocket服务器] 尝试重新初始化数据库连接...");
// 尝试重新初始化应用和数据库连接
try {
@@ -129,10 +217,10 @@ try {
// 将结果存入缓存
$cache->set($cacheKey, $current_addons, $cacheExpire);
echo "[WebSocket服务器] 重新连接数据库成功,插件列表已缓存\n";
ws_echo("[WebSocket服务器] 重新连接数据库成功,插件列表已缓存");
} catch (\Exception $retryEx) {
echo "[WebSocket服务器] 重新连接数据库失败: {$retryEx->getMessage()}\n";
echo "[WebSocket服务器] 回退到直接扫描目录获取插件列表\n";
ws_echo("[WebSocket服务器] 重新连接数据库失败: {$retryEx->getMessage()}", 'error');
ws_echo("[WebSocket服务器] 回退到直接扫描目录获取插件列表");
// 回退到直接扫描目录
$addonNames = [];
@@ -183,25 +271,25 @@ try {
}
}
echo "[WebSocket服务器] 从数据库获取的插件列表: " . implode(', ', $current_addon_names) . "\n";
echo "[WebSocket服务器] 已启用的插件: " . implode(', ', $enabled_addons) . "\n";
echo "[WebSocket服务器] 目录中存在的插件: " . implode(', ', $dir_addon_names) . "\n";
ws_echo("[WebSocket服务器] 从数据库获取的插件列表: " . implode(', ', $current_addon_names));
ws_echo("[WebSocket服务器] 已启用的插件: " . implode(', ', $enabled_addons));
ws_echo("[WebSocket服务器] 目录中存在的插件: " . implode(', ', $dir_addon_names));
// 5. 比较数据库和目录插件列表的差异
$db_only_addons = array_diff($db_addon_names, $dir_addon_names);
$dir_only_addons = array_diff($dir_addon_names, $db_addon_names);
if (!empty($db_only_addons)) {
echo "[WebSocket服务器] 数据库中存在但目录中不存在的插件: " . implode(', ', $db_only_addons) . "\n";
ws_echo("[WebSocket服务器] 数据库中存在但目录中不存在的插件: " . implode(', ', $db_only_addons));
}
if (!empty($dir_only_addons)) {
echo "[WebSocket服务器] 目录中存在但数据库中不存在的插件: " . implode(', ', $dir_only_addons) . "\n";
ws_echo("[WebSocket服务器] 目录中存在但数据库中不存在的插件: " . implode(', ', $dir_only_addons));
}
} catch (\Exception $e) {
echo "[WebSocket服务器] 获取插件列表失败: " . $e->getMessage() . "\n";
echo "[WebSocket服务器] 回退到直接扫描目录获取插件列表\n";
ws_echo("[WebSocket服务器] 获取插件列表失败: " . $e->getMessage(), 'error');
ws_echo("[WebSocket服务器] 回退到直接扫描目录获取插件列表");
// 回退到直接扫描目录
$addonNames = [];
@@ -229,14 +317,14 @@ $missingDirAddons = [];
foreach ($current_addon_names as $addonName) {
// 检查插件是否已启用
if (!in_array($addonName, $enabled_addons)) {
echo "[{$addonName}] 插件未启用跳过WebSocket控制器注册\n";
ws_echo("[{$addonName}] 插件未启用跳过WebSocket控制器注册");
$disabledAddons[] = $addonName;
continue;
}
// 检查插件目录是否存在
if (!is_dir($addonDir . '/' . $addonName)) {
echo "[{$addonName}] 插件目录不存在跳过WebSocket控制器注册\n";
ws_echo("[{$addonName}] 插件目录不存在跳过WebSocket控制器注册");
$missingDirAddons[] = $addonName;
continue;
}
@@ -250,14 +338,14 @@ foreach ($current_addon_names as $addonName) {
$path = '/ws/' . $addonName;
// 允许任意 Origin并且不限制 Host支持通过任意 IP/域名访问)
$ratchetApp->route($path, new $webSocketClass(), array('*'), '');
echo "已注册WebSocket控制器{$webSocketClass} 到路径 {$path}\n";
ws_echo("已注册WebSocket控制器{$webSocketClass} 到路径 {$path}");
$registeredAddons[] = $addonName;
} else {
echo "[{$addonName}] WebSocket控制器不存在 ({$webSocketClass})\n";
ws_echo("[{$addonName}] WebSocket控制器不存在 ({$webSocketClass})");
$unregisteredAddons[] = $addonName;
}
} catch (\Exception $e) {
echo "[{$addonName}] 检查WebSocket控制器时出错{$e->getMessage()}\n";
ws_echo("[{$addonName}] 检查WebSocket控制器时出错{$e->getMessage()}", 'error');
$unregisteredAddons[] = $addonName;
}
}
@@ -275,7 +363,7 @@ class DefaultWebSocketController implements MessageComponentInterface
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
echo "[默认路径] New connection! ({$conn->resourceId})\n";
ws_echo("[默认路径] New connection! ({$conn->resourceId})");
$conn->send(json_encode([
'type' => 'welcome',
'message' => '欢迎连接到默认WebSocket测试路径',
@@ -285,7 +373,7 @@ class DefaultWebSocketController implements MessageComponentInterface
public function onMessage(ConnectionInterface $conn, $msg)
{
echo "[默认路径] Received message from {$conn->resourceId}: $msg\n";
ws_echo("[默认路径] Received message from {$conn->resourceId}: $msg");
try {
$data = json_decode($msg, true);
if (isset($data['action']) && $data['action'] === 'ping') {
@@ -298,6 +386,7 @@ class DefaultWebSocketController implements MessageComponentInterface
]));
}
} catch (\Exception $e) {
ws_echo("[默认路径] 解析消息失败: {$e->getMessage()}", 'error');
$conn->send(json_encode(['type' => 'error', 'message' => '解析消息失败: ' . $e->getMessage()]));
}
}
@@ -305,12 +394,12 @@ class DefaultWebSocketController implements MessageComponentInterface
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
echo "[默认路径] Connection {$conn->resourceId} has disconnected\n";
ws_echo("[默认路径] Connection {$conn->resourceId} has disconnected");
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "[默认路径] An error has occurred: {$e->getMessage()}\n";
ws_echo("[默认路径] An error has occurred: {$e->getMessage()}", 'error');
$conn->close();
}
}
@@ -318,7 +407,7 @@ class DefaultWebSocketController implements MessageComponentInterface
// 注册默认的/ws路径测试控制器
// 默认测试路径同样不限制 Host
$ratchetApp->route('/ws', new DefaultWebSocketController(), array('*'), '');
echo "已注册默认WebSocket测试控制器到路径 /ws\n";
ws_echo("已注册默认WebSocket测试控制器到路径 /ws");
// 缓存WebSocket服务器信息可选用于其他服务查询
$serverInfoKey = 'websocket_server_info';
@@ -330,123 +419,124 @@ $cache->set($serverInfoKey, [
'registered_addons' => $registeredAddons
], 0); // 0表示永久缓存直到手动删除
echo "WebSocket服务器已启动监听地址: ws://{$httpHost}:{$port}\n";
ws_echo("WebSocket服务器已启动监听地址: ws://{$httpHost}:{$port}");
// 显示已注册WebSocket控制器的addon路径
echo "\n已注册WebSocket控制器的addon路径\n";
ws_echo("\n已注册WebSocket控制器的addon路径");
if (!empty($registeredAddons)) {
foreach ($registeredAddons as $addonName) {
echo " - ws://{$httpHost}:{$port}/ws/{$addonName} (已注册)\n";
ws_echo(" - ws://{$httpHost}:{$port}/ws/{$addonName} (已注册)");
}
} else {
echo " - 暂无已注册的WebSocket控制器\n";
ws_echo(" - 暂无已注册的WebSocket控制器");
}
// 显示未注册WebSocket控制器的addon路径
echo "\n未注册WebSocket控制器的addon路径\n";
ws_echo("\n未注册WebSocket控制器的addon路径");
if (!empty($unregisteredAddons)) {
foreach ($unregisteredAddons as $addonName) {
echo " - ws://{$httpHost}:{$port}/ws/{$addonName} (未注册)\n";
ws_echo(" - ws://{$httpHost}:{$port}/ws/{$addonName} (未注册)");
}
} else {
echo " - 所有已启用且目录存在的addon都已注册WebSocket控制器\n";
ws_echo(" - 所有已启用且目录存在的addon都已注册WebSocket控制器");
}
// 显示未启用的addon
echo "\n未启用的addon\n";
ws_echo("\n未启用的addon");
if (!empty($disabledAddons)) {
foreach ($disabledAddons as $addonName) {
echo " - {$addonName} (未启用)\n";
ws_echo(" - {$addonName} (未启用)");
}
} else {
echo " - 所有addon都已启用\n";
ws_echo(" - 所有addon都已启用");
}
// 显示目录不存在的addon
echo "\n目录不存在的addon\n";
ws_echo("\n目录不存在的addon");
if (!empty($missingDirAddons)) {
foreach ($missingDirAddons as $addonName) {
echo " - {$addonName} (目录不存在)\n";
ws_echo(" - {$addonName} (目录不存在)");
}
} else {
echo " - 所有已启用的addon目录都存在\n";
ws_echo(" - 所有已启用的addon目录都存在");
}
// 添加定期检查数据库连接的机制
// 创建一个单独的进程来定期检查和维护数据库连接
// 添加信号处理,确保当父进程停止时,子进程也会被终止
if (extension_loaded('pcntl')) {
// 记录子进程PID
$dbMaintenancePid = null;
// 创建数据库连接维护子进程
$pid = pcntl_fork();
if ($pid == -1) {
echo "[WebSocket服务器] 无法创建子进程来维护数据库连接\n";
} elseif ($pid == 0) {
// 子进程:定期检查数据库连接
echo "[WebSocket服务器] 启动数据库连接维护进程\n";
// 记录子进程PID
$dbMaintenancePid = null;
// 每30秒检查一次数据库连接
$checkInterval = 30; // 秒
// 设置子进程的信号处理
pcntl_signal(SIGTERM, function() {
echo "[数据库维护子进程] 收到终止信号,正在退出...\n";
exit(0);
});
while (true) {
// 检查是否有信号需要处理
pcntl_signal_dispatch();
// 创建数据库连接维护子进程
$pid = pcntl_fork();
if ($pid == -1) {
ws_echo("[WebSocket服务器] 无法创建子进程来维护数据库连接", 'error');
} elseif ($pid == 0) {
// 子进程:定期检查数据库连接
ws_echo("[WebSocket服务器] 启动数据库连接维护进程");
try {
// 尝试执行一个简单的数据库查询来测试连接
$addon_model = new Addon();
$addon_model->getAddonList([], 'name', 1, 1); // 只查询一条记录
echo "[数据库维护子进程] 数据库连接正常\n";
} catch (\Exception $e) {
echo "[数据库维护子进程] 数据库连接异常: {$e->getMessage()}\n";
echo "[数据库维护子进程] 尝试重新初始化数据库连接...\n";
// 每30秒检查一次数据库连接
$checkInterval = 30; // 秒
// 设置子进程的信号处理
pcntl_signal(SIGTERM, function() {
ws_echo("[数据库维护子进程] 收到终止信号,正在退出...");
exit(0);
});
while (true) {
// 检查是否有信号需要处理
pcntl_signal_dispatch();
try {
// 重新初始化应用和数据库连接
$app->initialize();
$cache = $app->cache;
echo "[数据库维护子进程] 重新初始化应用成功\n";
} catch (\Exception $retryEx) {
echo "[数据库维护子进程] 重新初始化应用失败: {$retryEx->getMessage()}\n";
// 尝试执行一个简单的数据库查询来测试连接
$addon_model = new Addon();
$addon_model->getAddonList([], 'name', 1, 1); // 只查询一条记录
ws_echo("[数据库维护子进程] 数据库连接正常");
} catch (\Exception $e) {
ws_echo("[数据库维护子进程] 数据库连接异常: {$e->getMessage()}", 'error');
ws_echo("[数据库维护子进程] 尝试重新初始化数据库连接...");
try {
// 重新初始化应用和数据库连接
$app->initialize();
$cache = $app->cache;
ws_echo("[数据库维护子进程] 重新初始化应用成功");
} catch (\Exception $retryEx) {
ws_echo("[数据库维护子进程] 重新初始化应用失败: {$retryEx->getMessage()}", 'error');
}
}
// 等待指定的时间间隔
sleep($checkInterval);
}
} else {
// 父进程记录子进程PID并设置信号处理
$dbMaintenancePid = $pid;
// 等待指定的时间间隔
sleep($checkInterval);
// 设置父进程的信号处理
pcntl_signal(SIGINT, function() use ($dbMaintenancePid) {
ws_echo("[WebSocket服务器] 收到终止信号,正在停止...");
// 如果子进程存在,发送终止信号
if ($dbMaintenancePid) {
ws_echo("[WebSocket服务器] 停止数据库连接维护进程");
posix_kill($dbMaintenancePid, SIGTERM);
// 等待子进程退出
pcntl_wait($status);
}
ws_echo("[WebSocket服务器] 已停止");
exit(0);
});
}
} else {
// 父进程记录子进程PID并设置信号处理
$dbMaintenancePid = $pid;
// 设置父进程的信号处理
pcntl_signal(SIGINT, function() use ($dbMaintenancePid) {
echo "[WebSocket服务器] 收到终止信号,正在停止...\n";
// 如果子进程存在,发送终止信号
if ($dbMaintenancePid) {
echo "[WebSocket服务器] 停止数据库连接维护进程\n";
posix_kill($dbMaintenancePid, SIGTERM);
// 等待子进程退出
pcntl_wait($status);
}
echo "[WebSocket服务器] 已停止\n";
exit(0);
});
}
}
// 运行服务器
echo "[WebSocket服务器] 启动主服务器进程\n";
echo "\n默认测试路径:\n";
echo " - ws://{$httpHost}:{$port}/ws (默认路径,用于连接测试)\n";
echo "按 Ctrl+C 停止服务器\n";
ws_echo("[WebSocket服务器] 启动主服务器进程");
ws_echo("\n默认测试路径:");
ws_echo(" - ws://{$httpHost}:{$port}/ws (默认路径,用于连接测试)");
ws_echo("按 Ctrl+C 停止服务器");
ws_info("WebSocket服务器已启动监听地址: ws://{$httpHost}:{$port}");
$ratchetApp->run();