Thank you for your quick reply and your suggestions. We initially implemented logging by creating a custom directory inside wp-content, and this approach worked well on most environments. Here's the code we used:
function site_file() {
$log_dir = WP_CONTENT_DIR . '/site-logs';
// Create directory if it doesn't exist
if (!file_exists($log_dir)) {
mkdir($log_dir, 0755, true);
file_put_contents($log_dir . '/index.php', "<?php // Silence is golden");
file_put_contents($log_dir . '/.htaccess', "Deny from all\n");
}
return $log_dir . '/site.log';
}
However, due to WordPress compliance guidelines and common restrictions on shared hosting, we cannot create writable files outside the uploads folder.So we updated our implementation to fallback to wp_upload_dir() when writing to the custom directory failed. Here's a simplified version of the updated logic:
$root_dir = dirname(ABSPATH);
$log_dir = trailingslashit($root_dir) . 'site-logs';
// Fall back to uploads folder if not writable
if (!wp_mkdir_p($log_dir) || !is_writable($log_dir)) {
$upload_dir = wp_upload_dir();
$log_dir = trailingslashit($upload_dir['basedir']) . 'site-logs';
if (!file_exists($log_dir)) {
wp_mkdir_p($log_dir);
}
// Add basic protections
if (!file_exists($log_dir . '/index.php')) {
@file_put_contents($log_dir . '/index.php', "<?php\n// Silence is golden.\nexit;");
}
if (!file_exists($log_dir . '/.htaccess')) {
@file_put_contents($log_dir . '/.htaccess', "Deny from all\n");
}
// Generate obfuscated log filename
$unique = substr(md5(wp_salt() . get_current_blog_id()), 0, 12);
self::$log_file = trailingslashit($log_dir) . "site-{$unique}.log";
}
This fallback ensures logging works even in restrictive hosting environments, which is important for plugin compatibility. We do not log sensitive data, and we add basic protections like .htaccess and obfuscation.
On Nginx servers, we realize .htaccess is ignored, and the file remains publicly accessible if its path is known — which is the core issue we're trying to mitigate without server-level config access.