491 lines
18 KiB
PHP
491 lines
18 KiB
PHP
<?php
|
||
/**
|
||
* 日志管理界面类
|
||
*
|
||
* 处理后台日志查看和管理功能
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) {
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* 日志管理界面类
|
||
*/
|
||
class Yoone_Admin_Logs {
|
||
|
||
/**
|
||
* 构造函数
|
||
*/
|
||
public function __construct() {
|
||
add_action('admin_menu', array($this, 'add_menu_page'));
|
||
add_action('wp_ajax_yoone_clear_logs', array($this, 'ajax_clear_logs'));
|
||
add_action('wp_ajax_yoone_download_logs', array($this, 'ajax_download_logs'));
|
||
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
|
||
}
|
||
|
||
/**
|
||
* 添加菜单页面
|
||
*/
|
||
public function add_menu_page() {
|
||
add_submenu_page(
|
||
'yoone-subscriptions',
|
||
__('日志管理', 'yoone-subscriptions'),
|
||
__('日志', 'yoone-subscriptions'),
|
||
'manage_options',
|
||
'yoone-logs',
|
||
array($this, 'display_logs_page')
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 显示日志页面
|
||
*/
|
||
public function display_logs_page() {
|
||
$current_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'recent';
|
||
$log_level = isset($_GET['level']) ? sanitize_text_field($_GET['level']) : 'all';
|
||
$search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
|
||
|
||
?>
|
||
<div class="wrap">
|
||
<h1><?php _e('日志管理', 'yoone-subscriptions'); ?></h1>
|
||
|
||
<nav class="nav-tab-wrapper">
|
||
<a href="<?php echo esc_url(add_query_arg('tab', 'recent')); ?>"
|
||
class="nav-tab <?php echo $current_tab === 'recent' ? 'nav-tab-active' : ''; ?>">
|
||
<?php _e('最近日志', 'yoone-subscriptions'); ?>
|
||
</a>
|
||
<a href="<?php echo esc_url(add_query_arg('tab', 'subscription')); ?>"
|
||
class="nav-tab <?php echo $current_tab === 'subscription' ? 'nav-tab-active' : ''; ?>">
|
||
<?php _e('订阅日志', 'yoone-subscriptions'); ?>
|
||
</a>
|
||
<a href="<?php echo esc_url(add_query_arg('tab', 'payment')); ?>"
|
||
class="nav-tab <?php echo $current_tab === 'payment' ? 'nav-tab-active' : ''; ?>">
|
||
<?php _e('支付日志', 'yoone-subscriptions'); ?>
|
||
</a>
|
||
<a href="<?php echo esc_url(add_query_arg('tab', 'error')); ?>"
|
||
class="nav-tab <?php echo $current_tab === 'error' ? 'nav-tab-active' : ''; ?>">
|
||
<?php _e('错误日志', 'yoone-subscriptions'); ?>
|
||
</a>
|
||
<a href="<?php echo esc_url(add_query_arg('tab', 'settings')); ?>"
|
||
class="nav-tab <?php echo $current_tab === 'settings' ? 'nav-tab-active' : ''; ?>">
|
||
<?php _e('设置', 'yoone-subscriptions'); ?>
|
||
</a>
|
||
</nav>
|
||
|
||
<div class="log-filters">
|
||
<form method="get">
|
||
<input type="hidden" name="page" value="yoone-logs" />
|
||
<input type="hidden" name="tab" value="<?php echo esc_attr($current_tab); ?>" />
|
||
|
||
<select name="level">
|
||
<option value="all" <?php selected($log_level, 'all'); ?>><?php _e('所有级别', 'yoone-subscriptions'); ?></option>
|
||
<option value="error" <?php selected($log_level, 'error'); ?>><?php _e('错误', 'yoone-subscriptions'); ?></option>
|
||
<option value="warning" <?php selected($log_level, 'warning'); ?>><?php _e('警告', 'yoone-subscriptions'); ?></option>
|
||
<option value="info" <?php selected($log_level, 'info'); ?>><?php _e('信息', 'yoone-subscriptions'); ?></option>
|
||
<option value="debug" <?php selected($log_level, 'debug'); ?>><?php _e('调试', 'yoone-subscriptions'); ?></option>
|
||
</select>
|
||
|
||
<input type="text" name="search" value="<?php echo esc_attr($search); ?>"
|
||
placeholder="<?php _e('搜索日志...', 'yoone-subscriptions'); ?>" />
|
||
|
||
<input type="submit" class="button" value="<?php _e('筛选', 'yoone-subscriptions'); ?>" />
|
||
</form>
|
||
|
||
<div class="log-actions">
|
||
<button type="button" class="button" id="refresh-logs">
|
||
<?php _e('刷新', 'yoone-subscriptions'); ?>
|
||
</button>
|
||
<button type="button" class="button" id="download-logs">
|
||
<?php _e('下载日志', 'yoone-subscriptions'); ?>
|
||
</button>
|
||
<button type="button" class="button button-secondary" id="clear-logs">
|
||
<?php _e('清空日志', 'yoone-subscriptions'); ?>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="log-content">
|
||
<?php
|
||
switch ($current_tab) {
|
||
case 'recent':
|
||
$this->display_recent_logs($log_level, $search);
|
||
break;
|
||
case 'subscription':
|
||
$this->display_subscription_logs($log_level, $search);
|
||
break;
|
||
case 'payment':
|
||
$this->display_payment_logs($log_level, $search);
|
||
break;
|
||
case 'error':
|
||
$this->display_error_logs($search);
|
||
break;
|
||
case 'settings':
|
||
$this->display_log_settings();
|
||
break;
|
||
}
|
||
?>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.log-filters {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin: 20px 0;
|
||
padding: 15px;
|
||
background: #f9f9f9;
|
||
border: 1px solid #ddd;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.log-filters form {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.log-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.log-content {
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.log-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.log-table th,
|
||
.log-table td {
|
||
padding: 12px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.log-table th {
|
||
background: #f5f5f5;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.log-level {
|
||
padding: 4px 8px;
|
||
border-radius: 3px;
|
||
font-size: 0.8em;
|
||
font-weight: bold;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.log-level.error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
}
|
||
|
||
.log-level.warning {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
}
|
||
|
||
.log-level.info {
|
||
background: #d1ecf1;
|
||
color: #0c5460;
|
||
}
|
||
|
||
.log-level.debug {
|
||
background: #e2e3e5;
|
||
color: #383d41;
|
||
}
|
||
|
||
.log-message {
|
||
max-width: 400px;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.log-context {
|
||
font-family: monospace;
|
||
font-size: 0.9em;
|
||
color: #666;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.no-logs {
|
||
padding: 40px;
|
||
text-align: center;
|
||
color: #666;
|
||
}
|
||
</style>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* 显示最近日志
|
||
*/
|
||
private function display_recent_logs($level = 'all', $search = '') {
|
||
$logs = $this->get_filtered_logs($level, $search, 100);
|
||
$this->render_log_table($logs);
|
||
}
|
||
|
||
/**
|
||
* 显示订阅日志
|
||
*/
|
||
private function display_subscription_logs($level = 'all', $search = '') {
|
||
$logs = $this->get_filtered_logs($level, $search, 100, 'subscription');
|
||
$this->render_log_table($logs);
|
||
}
|
||
|
||
/**
|
||
* 显示支付日志
|
||
*/
|
||
private function display_payment_logs($level = 'all', $search = '') {
|
||
$logs = $this->get_filtered_logs($level, $search, 100, 'payment');
|
||
$this->render_log_table($logs);
|
||
}
|
||
|
||
/**
|
||
* 显示错误日志
|
||
*/
|
||
private function display_error_logs($search = '') {
|
||
$logs = $this->get_filtered_logs('error', $search, 100);
|
||
$this->render_log_table($logs);
|
||
}
|
||
|
||
/**
|
||
* 显示日志设置
|
||
*/
|
||
private function display_log_settings() {
|
||
$log_level = get_option('yoone_log_level', 'info');
|
||
$log_retention = get_option('yoone_log_retention_days', 30);
|
||
$enable_debug = get_option('yoone_enable_debug_logging', false);
|
||
|
||
if (isset($_POST['save_log_settings'])) {
|
||
check_admin_referer('yoone_log_settings');
|
||
|
||
update_option('yoone_log_level', sanitize_text_field($_POST['log_level']));
|
||
update_option('yoone_log_retention_days', absint($_POST['log_retention']));
|
||
update_option('yoone_enable_debug_logging', isset($_POST['enable_debug']));
|
||
|
||
echo '<div class="notice notice-success"><p>' . __('设置已保存', 'yoone-subscriptions') . '</p></div>';
|
||
}
|
||
|
||
?>
|
||
<form method="post" class="log-settings-form">
|
||
<?php wp_nonce_field('yoone_log_settings'); ?>
|
||
|
||
<table class="form-table">
|
||
<tr>
|
||
<th scope="row"><?php _e('日志级别', 'yoone-subscriptions'); ?></th>
|
||
<td>
|
||
<select name="log_level">
|
||
<option value="error" <?php selected($log_level, 'error'); ?>><?php _e('仅错误', 'yoone-subscriptions'); ?></option>
|
||
<option value="warning" <?php selected($log_level, 'warning'); ?>><?php _e('警告及以上', 'yoone-subscriptions'); ?></option>
|
||
<option value="info" <?php selected($log_level, 'info'); ?>><?php _e('信息及以上', 'yoone-subscriptions'); ?></option>
|
||
<option value="debug" <?php selected($log_level, 'debug'); ?>><?php _e('所有日志', 'yoone-subscriptions'); ?></option>
|
||
</select>
|
||
<p class="description"><?php _e('选择要记录的最低日志级别', 'yoone-subscriptions'); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row"><?php _e('日志保留天数', 'yoone-subscriptions'); ?></th>
|
||
<td>
|
||
<input type="number" name="log_retention" value="<?php echo esc_attr($log_retention); ?>" min="1" max="365" />
|
||
<p class="description"><?php _e('超过此天数的日志将被自动删除', 'yoone-subscriptions'); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row"><?php _e('调试模式', 'yoone-subscriptions'); ?></th>
|
||
<td>
|
||
<label>
|
||
<input type="checkbox" name="enable_debug" <?php checked($enable_debug); ?> />
|
||
<?php _e('启用调试日志记录', 'yoone-subscriptions'); ?>
|
||
</label>
|
||
<p class="description"><?php _e('启用后将记录详细的调试信息,可能会产生大量日志', 'yoone-subscriptions'); ?></p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<p class="submit">
|
||
<input type="submit" name="save_log_settings" class="button-primary" value="<?php _e('保存设置', 'yoone-subscriptions'); ?>" />
|
||
</p>
|
||
</form>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* 渲染日志表格
|
||
*/
|
||
private function render_log_table($logs) {
|
||
if (empty($logs)) {
|
||
echo '<div class="no-logs">' . __('没有找到日志记录', 'yoone-subscriptions') . '</div>';
|
||
return;
|
||
}
|
||
|
||
?>
|
||
<table class="log-table">
|
||
<thead>
|
||
<tr>
|
||
<th><?php _e('时间', 'yoone-subscriptions'); ?></th>
|
||
<th><?php _e('级别', 'yoone-subscriptions'); ?></th>
|
||
<th><?php _e('消息', 'yoone-subscriptions'); ?></th>
|
||
<th><?php _e('上下文', 'yoone-subscriptions'); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($logs as $log): ?>
|
||
<tr>
|
||
<td><?php echo esc_html($log['timestamp']); ?></td>
|
||
<td><span class="log-level <?php echo esc_attr($log['level']); ?>"><?php echo esc_html($log['level']); ?></span></td>
|
||
<td class="log-message"><?php echo esc_html($log['message']); ?></td>
|
||
<td class="log-context"><?php echo esc_html($log['context']); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* 获取过滤后的日志
|
||
*/
|
||
private function get_filtered_logs($level = 'all', $search = '', $limit = 100, $type = '') {
|
||
$logs = Yoone_Logger::get_recent_logs($limit * 2); // 获取更多以便过滤
|
||
$filtered_logs = array();
|
||
|
||
foreach ($logs as $log_line) {
|
||
$parsed_log = $this->parse_log_line($log_line);
|
||
|
||
if (!$parsed_log) {
|
||
continue;
|
||
}
|
||
|
||
// 级别过滤
|
||
if ($level !== 'all' && $parsed_log['level'] !== $level) {
|
||
continue;
|
||
}
|
||
|
||
// 类型过滤
|
||
if ($type && strpos($parsed_log['message'], $type) === false) {
|
||
continue;
|
||
}
|
||
|
||
// 搜索过滤
|
||
if ($search && stripos($parsed_log['message'], $search) === false && stripos($parsed_log['context'], $search) === false) {
|
||
continue;
|
||
}
|
||
|
||
$filtered_logs[] = $parsed_log;
|
||
|
||
if (count($filtered_logs) >= $limit) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
return $filtered_logs;
|
||
}
|
||
|
||
/**
|
||
* 解析日志行
|
||
*/
|
||
private function parse_log_line($log_line) {
|
||
// 简单的日志解析,实际应该根据WooCommerce日志格式调整
|
||
if (preg_match('/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2})\s+(\w+)\s+(.+)$/', $log_line, $matches)) {
|
||
$context = '';
|
||
$message = $matches[3];
|
||
|
||
// 提取JSON上下文
|
||
if (preg_match('/^(.+?)\s+(\{.+\})$/', $message, $msg_matches)) {
|
||
$message = $msg_matches[1];
|
||
$context = $msg_matches[2];
|
||
}
|
||
|
||
return array(
|
||
'timestamp' => $matches[1],
|
||
'level' => strtolower($matches[2]),
|
||
'message' => $message,
|
||
'context' => $context
|
||
);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* AJAX清空日志
|
||
*/
|
||
public function ajax_clear_logs() {
|
||
check_ajax_referer('yoone_admin_nonce', 'nonce');
|
||
|
||
if (!current_user_can('manage_options')) {
|
||
wp_die(__('权限不足', 'yoone-subscriptions'));
|
||
}
|
||
|
||
$log_file = Yoone_Logger::get_log_file_path();
|
||
|
||
if (file_exists($log_file)) {
|
||
file_put_contents($log_file, '');
|
||
}
|
||
|
||
wp_send_json_success(array('message' => __('日志已清空', 'yoone-subscriptions')));
|
||
}
|
||
|
||
/**
|
||
* AJAX下载日志
|
||
*/
|
||
public function ajax_download_logs() {
|
||
check_ajax_referer('yoone_admin_nonce', 'nonce');
|
||
|
||
if (!current_user_can('manage_options')) {
|
||
wp_die(__('权限不足', 'yoone-subscriptions'));
|
||
}
|
||
|
||
$log_file = Yoone_Logger::get_log_file_path();
|
||
|
||
if (!file_exists($log_file)) {
|
||
wp_die(__('日志文件不存在', 'yoone-subscriptions'));
|
||
}
|
||
|
||
header('Content-Type: text/plain');
|
||
header('Content-Disposition: attachment; filename="yoone-subscriptions-' . date('Y-m-d') . '.log"');
|
||
header('Content-Length: ' . filesize($log_file));
|
||
|
||
readfile($log_file);
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* 加载脚本
|
||
*/
|
||
public function enqueue_scripts($hook) {
|
||
if ($hook !== 'yoone-subscriptions_page_yoone-logs') {
|
||
return;
|
||
}
|
||
|
||
wp_enqueue_script(
|
||
'yoone-admin-logs',
|
||
YOONE_SUBSCRIPTIONS_PLUGIN_URL . 'assets/js/admin-logs.js',
|
||
array('jquery'),
|
||
YOONE_SUBSCRIPTIONS_VERSION,
|
||
true
|
||
);
|
||
|
||
wp_localize_script('yoone-admin-logs', 'yoone_logs_params', array(
|
||
'ajax_url' => admin_url('admin-ajax.php'),
|
||
'nonce' => wp_create_nonce('yoone_admin_nonce'),
|
||
'i18n' => array(
|
||
'confirm_clear' => __('确定要清空所有日志吗?此操作不可恢复。', 'yoone-subscriptions'),
|
||
'clearing' => __('正在清空...', 'yoone-subscriptions'),
|
||
'downloading' => __('正在下载...', 'yoone-subscriptions')
|
||
)
|
||
));
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
new Yoone_Admin_Logs();
|