subscription/includes/admin/class-yoone-admin-logs.php

491 lines
18 KiB
PHP
Raw Permalink 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.

<?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();