HEX
Server: Apache/2.4.66 (Debian)
System: Linux 6dfabc3b2241 6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 x86_64
User: (1000)
PHP: 8.3.30
Disabled: NONE
Upload Files
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);
}