File: /var/www/html/wp-content/themes/custom-theme/inc/logger.php
<?php
// Structured dev logging. Outputs to php://stderr so Docker captures it,
// and also stores entries in a custom DB table for the admin dashboard.
// DEBUG/INFO suppressed when WP_DEBUG is off.
// --- Database table management ---
function headless_log_table_name() {
global $wpdb;
return $wpdb->prefix . 'headless_logs';
}
function headless_log_ensure_table() {
static $checked = false;
if ($checked) {
return;
}
$checked = true;
$db_version = '1.0';
if (get_option('headless_log_db_version') === $db_version) {
return;
}
global $wpdb;
$table = headless_log_table_name();
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL,
level VARCHAR(10) NOT NULL,
component VARCHAR(50) NOT NULL,
message TEXT NOT NULL,
context TEXT,
PRIMARY KEY (id),
KEY created_at (created_at),
KEY level (level),
KEY component (component)
) {$charset};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
update_option('headless_log_db_version', $db_version);
}
// --- Insert ---
function headless_log_insert($level, $component, $message, $context = []) {
headless_log_ensure_table();
global $wpdb;
$wpdb->insert(
headless_log_table_name(),
[
'created_at' => gmdate('Y-m-d H:i:s'),
'level' => $level,
'component' => $component,
'message' => $message,
'context' => !empty($context) ? json_encode($context, JSON_UNESCAPED_SLASHES) : null,
],
['%s', '%s', '%s', '%s', '%s']
);
}
// --- Query ---
function headless_log_query($args = []) {
global $wpdb;
headless_log_ensure_table();
$table = headless_log_table_name();
$defaults = [
'level' => '',
'component' => '',
'search' => '',
'page' => 1,
'per_page' => 50,
];
$args = wp_parse_args($args, $defaults);
$where = [];
$values = [];
if ($args['level'] !== '') {
$where[] = 'level = %s';
$values[] = $args['level'];
}
if ($args['component'] !== '') {
$where[] = 'component = %s';
$values[] = $args['component'];
}
if ($args['search'] !== '') {
$where[] = 'message LIKE %s';
$values[] = '%' . $wpdb->esc_like($args['search']) . '%';
}
$where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
// Count total matching rows.
$count_sql = "SELECT COUNT(*) FROM {$table} {$where_sql}";
if (!empty($values)) {
$count_sql = $wpdb->prepare($count_sql, ...$values);
}
$total = (int) $wpdb->get_var($count_sql);
// Fetch the page of rows.
$offset = ($args['page'] - 1) * $args['per_page'];
$query_sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC, id DESC LIMIT %d OFFSET %d";
$all_values = array_merge($values, [$args['per_page'], $offset]);
$rows = $wpdb->get_results($wpdb->prepare($query_sql, ...$all_values));
return [
'rows' => $rows ?: [],
'total' => $total,
];
}
// --- Prune & Clear ---
function headless_log_prune($days = 30) {
global $wpdb;
$table = headless_log_table_name();
$cutoff = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS));
$wpdb->query($wpdb->prepare("DELETE FROM {$table} WHERE created_at < %s", $cutoff));
}
function headless_log_clear() {
global $wpdb;
$table = headless_log_table_name();
$wpdb->query("TRUNCATE TABLE {$table}");
}
// --- Components list ---
function headless_log_components() {
global $wpdb;
headless_log_ensure_table();
$table = headless_log_table_name();
return $wpdb->get_col("SELECT DISTINCT component FROM {$table} ORDER BY component ASC");
}
// --- Formatting & main log function ---
function headless_log_format_line($level, $component, $message, $context = []) {
$timestamp = gmdate('Y-m-d H:i:s');
$line = "[{$timestamp}] [{$level}] [{$component}] {$message}";
if (!empty($context)) {
$line .= ' ' . json_encode($context, JSON_UNESCAPED_SLASHES);
}
return $line;
}
function headless_log($level, $component, $message, $context = []) {
if (!defined('WP_DEBUG') || !WP_DEBUG) {
if ($level === 'DEBUG' || $level === 'INFO') {
return;
}
}
$line = headless_log_format_line($level, $component, $message, $context);
error_log($line . "\n", 3, 'php://stderr');
headless_log_insert($level, $component, $message, $context);
}
function headless_log_error($component, $message, $context = []) {
headless_log('ERROR', $component, $message, $context);
}
function headless_log_warning($component, $message, $context = []) {
headless_log('WARNING', $component, $message, $context);
}
function headless_log_info($component, $message, $context = []) {
headless_log('INFO', $component, $message, $context);
}
function headless_log_debug($component, $message, $context = []) {
headless_log('DEBUG', $component, $message, $context);
}