PluginProbe
Debug Log Manager – Conveniently Monitor and Inspect Errors / 2.5.1
Debug Log Manager – Conveniently Monitor and Inspect Errors v2.5.1
2.5.2 2.5.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.3.0 1.3.1 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.8.0 1.8.2 1.8.3 1.8.4 All 49 releases
debug-log-manager / classes / class-log-trimmer.php

class-log-trimmer.php in Debug Log Manager – Conveniently Monitor and Inspect Errors 2.5.1, at classes/class-log-trimmer.php

190 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace DLM\Classes;
4
5 /**
6 * Trims the debug log file to stay within a size cap based on PHP memory limit.
7 *
8 * @since 2.5.0
9 */
10 class Log_Trimmer {
11
12 /**
13 * Percentage of memory limit used as the log size cap.
14 *
15 * @var int
16 */
17 const CAP_PERCENT = 75;
18
19 /**
20 * Fallback base size in bytes when memory_limit is unlimited (-1).
21 *
22 * @var int
23 */
24 const FALLBACK_BASE_BYTES = 67108864; // 64 MB.
25
26 /**
27 * Returns the maximum allowed debug log file size in bytes.
28 *
29 * @since 2.5.0
30 *
31 * @return int
32 */
33 public function get_max_log_size_bytes() {
34
35 $memory_limit = ini_get( 'memory_limit' );
36
37 if ( '-1' === $memory_limit ) {
38 $limit_bytes = self::FALLBACK_BASE_BYTES;
39 } else {
40 $limit_bytes = wp_convert_hr_to_bytes( $memory_limit );
41 }
42
43 if ( $limit_bytes <= 0 ) {
44 $limit_bytes = self::FALLBACK_BASE_BYTES;
45 }
46
47 return (int) floor( $limit_bytes * ( self::CAP_PERCENT / 100 ) );
48 }
49
50 /**
51 * Trims the log file if it exceeds the size cap.
52 *
53 * @since 2.5.0
54 *
55 * @param string|null $path Optional. Full path to the log file.
56 * @return bool True if trimmed, false if no trim was needed or trim failed.
57 */
58 public function maybe_trim_log( $path = null ) {
59
60 if ( null === $path ) {
61 $path = get_option( 'debug_log_manager_file_path' );
62 }
63
64 if ( empty( $path ) || ! is_file( $path ) ) {
65 return false;
66 }
67
68 $max_bytes = $this->get_max_log_size_bytes();
69 $file_size = (int) filesize( $path );
70
71 if ( $file_size <= $max_bytes ) {
72 return false;
73 }
74
75 return $this->trim_log_file( $path, $max_bytes );
76 }
77
78 /**
79 * Trims the log file by keeping only the newest entries up to max_bytes.
80 *
81 * @since 2.5.0
82 *
83 * @param string $path Full path to the log file.
84 * @param int $max_bytes Maximum file size in bytes.
85 * @return bool True on success, false on failure.
86 */
87 public function trim_log_file( $path, $max_bytes ) {
88
89 $file_size = (int) filesize( $path );
90
91 if ( $file_size <= $max_bytes ) {
92 return false;
93 }
94
95 $handle = fopen( $path, 'rb' );
96
97 if ( false === $handle ) {
98 return false;
99 }
100
101 $read_size = min( $max_bytes, $file_size );
102 $offset = $file_size - $read_size;
103
104 if ( fseek( $handle, $offset, SEEK_SET ) !== 0 ) {
105 fclose( $handle );
106 return false;
107 }
108
109 $chunk = fread( $handle, $read_size );
110 fclose( $handle );
111
112 if ( false === $chunk || '' === $chunk ) {
113 return false;
114 }
115
116 $boundary_offset = $this->find_entry_boundary_offset( $chunk );
117
118 if ( $boundary_offset > 0 ) {
119 $chunk = substr( $chunk, $boundary_offset );
120 }
121
122 if ( '' === $chunk ) {
123 return false;
124 }
125
126 $notice = $this->get_trim_notice_line();
127 $content = $notice . $chunk;
128
129 $dir = dirname( $path );
130 $temp_path = $dir . '/' . wp_unique_filename( $dir, 'dlm-trim-temp.log' );
131
132 if ( false === file_put_contents( $temp_path, $content ) ) {
133 return false;
134 }
135
136 if ( ! rename( $temp_path, $path ) ) {
137 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- cleanup temp file on failure.
138 @unlink( $temp_path );
139 return false;
140 }
141
142 return true;
143 }
144
145 /**
146 * Finds the byte offset of the first complete log entry in a tail chunk.
147 *
148 * @since 2.5.0
149 *
150 * @param string $chunk Tail portion of the log file.
151 * @return int Byte offset to start keeping content, or 0 if none found.
152 */
153 private function find_entry_boundary_offset( $chunk ) {
154
155 if ( preg_match( '/\[\d{2}-[A-Za-z]{3}-\d{4}/', $chunk, $matches, PREG_OFFSET_CAPTURE ) ) {
156 $match_pos = $matches[0][1];
157
158 // Walk back to the opening bracket of this entry.
159 $bracket_pos = strrpos( substr( $chunk, 0, $match_pos + 1 ), '[' );
160
161 if ( false !== $bracket_pos ) {
162 return $bracket_pos;
163 }
164 }
165
166 return 0;
167 }
168
169 /**
170 * Returns a single-line notice to prepend when the log is trimmed.
171 *
172 * @since 2.5.0
173 *
174 * @return string
175 */
176 private function get_trim_notice_line() {
177
178 if ( function_exists( 'wp_date' ) ) {
179 $timestamp = wp_date( 'd-M-Y H:i:s' ) . ' UTC';
180 } else {
181 $timestamp = gmdate( 'd-M-Y H:i:s' ) . ' UTC';
182 }
183
184 $message = __( 'Debug Log Manager: Older log entries were removed to stay within the size limit.', 'debug-log-manager' );
185
186 return '[' . $timestamp . '] ' . $message . PHP_EOL;
187 }
188
189 }
190