Action_Logger.php
1 week ago
Admin.php
1 week ago
Canonical_Formatter.php
1 week ago
File_Logger.php
1 week ago
Logger.php
1 week ago
Monolog_Logger.php
1 week ago
Null_Logger.php
1 week ago
Service_Provider.php
1 week ago
File_Logger.php
315 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Simple file based logging implementation. |
| 4 | * |
| 5 | * By default, this logger uses the system temporary directory for logging |
| 6 | * purposes and performs daily log rotation. |
| 7 | */ |
| 8 | class Tribe__Log__File_Logger implements Tribe__Log__Logger { |
| 9 | protected $module_id = 'tribe_tmp_file_logger'; |
| 10 | protected $log_dir = ''; |
| 11 | protected $log_file = ''; |
| 12 | protected $context = 'a'; |
| 13 | protected $handle; |
| 14 | |
| 15 | public function __construct() { |
| 16 | $this->set_log_dir(); |
| 17 | $this->set_log_file(); |
| 18 | } |
| 19 | |
| 20 | public function __destruct() { |
| 21 | $this->close_handle(); |
| 22 | } |
| 23 | |
| 24 | protected function set_log_dir() { |
| 25 | /** |
| 26 | * Controls the directory used for logging. |
| 27 | * |
| 28 | * @var string $log_dir |
| 29 | */ |
| 30 | $this->log_dir = apply_filters( 'tribe_file_logger_directory', get_temp_dir() ); |
| 31 | } |
| 32 | |
| 33 | /** |
| 34 | * Sets the path for the log file we're currently interested in using. |
| 35 | * |
| 36 | * @param string $date = null |
| 37 | */ |
| 38 | protected function set_log_file( $date = null ) { |
| 39 | $this->log_file = $this->get_log_file_name( $date ); |
| 40 | $this->obtain_handle(); |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * Used to switch between contexts for reading ('r') and writing |
| 45 | * ('a' := append) modes. |
| 46 | * |
| 47 | * @see fopen() documentation |
| 48 | * |
| 49 | * @param string $context |
| 50 | */ |
| 51 | protected function set_context( $context ) { |
| 52 | $this->context = $context; |
| 53 | $this->close_handle(); |
| 54 | $this->obtain_handle(); |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Attempts to obtain a file handle for the current log file. |
| 59 | */ |
| 60 | protected function obtain_handle() { |
| 61 | $this->close_handle(); |
| 62 | |
| 63 | if ( ! file_exists( $this->log_file ) && $this->is_available() ) { |
| 64 | touch( $this->log_file ); |
| 65 | } |
| 66 | |
| 67 | // Bail if we're attempting to write but don't have permission. |
| 68 | if ( 'r' !== $this->context && ! is_writable( $this->log_file ) ) { |
| 69 | return; |
| 70 | } |
| 71 | |
| 72 | if ( is_readable( $this->log_file ) ) { |
| 73 | $this->handle = fopen( $this->log_file, $this->context ); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Closes the current file handle, if one is open. |
| 79 | */ |
| 80 | protected function close_handle() { |
| 81 | // is_resource() only returns true for open resources |
| 82 | if ( is_resource( $this->handle ) ) { |
| 83 | fclose( $this->handle ); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Returns the log name to be used for reading/writing events for a specified date |
| 89 | * (defaulting to today, if no date is specified). |
| 90 | * |
| 91 | * @param string $date = null |
| 92 | * |
| 93 | * @return string |
| 94 | */ |
| 95 | protected function get_log_file_name( $date = null ) { |
| 96 | if ( null === $date ) { |
| 97 | $date = date_i18n( 'Y-m-d' ); |
| 98 | } |
| 99 | |
| 100 | $filename = $this->log_dir . DIRECTORY_SEPARATOR . $this->get_log_file_basename() . $date . '.log'; |
| 101 | |
| 102 | /** |
| 103 | * Dictates the filename of the log used to record events for the specified date. |
| 104 | * |
| 105 | * @var string $filename |
| 106 | * @var string $date |
| 107 | */ |
| 108 | return apply_filters( 'tribe_file_logger_filename', $filename, $date ); |
| 109 | } |
| 110 | |
| 111 | protected function get_log_file_basename() { |
| 112 | /** |
| 113 | * Log files share a common prefix, which aids identifying archived/rotated logs. |
| 114 | * This filter allows a degree of control to be exercised over the prefix to avoid |
| 115 | * conflicts, etc. |
| 116 | * |
| 117 | * @var string $log_file_base_name |
| 118 | */ |
| 119 | return apply_filters( 'tribe_file_logger_file_prefix', $this->module_id . '_' ); |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Returns a 'human friendly' name for the logging implementation. |
| 124 | * |
| 125 | * @return string |
| 126 | */ |
| 127 | public function get_name() { |
| 128 | return __( 'Default (uses temporary files)', 'tribe-common' ); |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Indicates if the logger will work in the current environment. |
| 133 | * |
| 134 | * @return bool |
| 135 | */ |
| 136 | public function is_available() { |
| 137 | return is_writable( $this->log_dir ) && is_readable( $this->log_dir ); |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * Responsible for commiting the entry to the log. |
| 142 | * |
| 143 | * @param string $entry |
| 144 | * @param string $type |
| 145 | * @param string $src |
| 146 | */ |
| 147 | public function log( $entry, $type = Tribe__Log::DEBUG, $src = '' ) { |
| 148 | // Ensure we're in 'append' mode before we try to write |
| 149 | if ( 'a' !== $this->context ) { |
| 150 | $this->set_context( 'a' ); |
| 151 | } |
| 152 | |
| 153 | // Couldn't obtain the file handle? We'll bail out without causing further disruption |
| 154 | if ( ! $this->handle ) { |
| 155 | return; |
| 156 | } |
| 157 | |
| 158 | fputcsv( $this->handle, [ date_i18n( 'Y-m-d H:i:s' ), $entry, $type, $src ] ); |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Retrieve up to $limit most recent log entries in reverse chronological |
| 163 | * order. If $limit is a negative or zero value, there is no limit. |
| 164 | * |
| 165 | * Supports passing a 'log' argument to recover |
| 166 | * |
| 167 | * @see Tribe__Log__Logger::list_available_logs() |
| 168 | * |
| 169 | * @param int $limit |
| 170 | * @param array $args |
| 171 | * |
| 172 | * @return array |
| 173 | */ |
| 174 | public function retrieve( $limit = 0, array $args = [] ) { |
| 175 | // Ensure we're in 'read' mode before we try to retrieve |
| 176 | if ( 'r' !== $this->context ) { |
| 177 | $this->set_context( 'r' ); |
| 178 | } |
| 179 | |
| 180 | // Couldn't obtain the file handle? We'll bail out without causing further disruption |
| 181 | if ( ! $this->handle ) { |
| 182 | return []; |
| 183 | } |
| 184 | |
| 185 | $rows = []; |
| 186 | |
| 187 | while ( $current_row = fgetcsv( $this->handle ) ) { |
| 188 | if ( $limit && $limit === count( $rows ) ) { |
| 189 | array_shift( $rows ); |
| 190 | } |
| 191 | |
| 192 | $rows[] = $current_row; |
| 193 | } |
| 194 | |
| 195 | return array_reverse( $rows ); |
| 196 | } |
| 197 | |
| 198 | /** |
| 199 | * Returns a list of currently accessible logs (current first, oldest last). |
| 200 | * Each is refered to by date. |
| 201 | * |
| 202 | * Example: |
| 203 | * |
| 204 | * [ '2016-12-31', |
| 205 | * '2016-12-30', |
| 206 | * '2016-12-30', |
| 207 | * '2016-12-30', |
| 208 | * '2016-12-30', ... ] |
| 209 | * |
| 210 | * @since 4.6.2 added extra safety checks before attempting to access log directory |
| 211 | * |
| 212 | * @return array |
| 213 | */ |
| 214 | public function list_available_logs() { |
| 215 | $logs = []; |
| 216 | |
| 217 | // This could be called when the log dir is not accessible. |
| 218 | if ( ! $this->is_available() ) { |
| 219 | return $logs; |
| 220 | } |
| 221 | |
| 222 | $basename = $this->get_log_file_basename(); |
| 223 | |
| 224 | /** |
| 225 | * Though the is_available() method tests to see if the log directory is |
| 226 | * readable and writeable there are situations where that isn't a |
| 227 | * sufficient check by itself, hence the try/catch block. |
| 228 | * |
| 229 | * @see https://central.tri.be/issues/90436 |
| 230 | */ |
| 231 | try { |
| 232 | $log_files_dir = new DirectoryIterator( $this->log_dir ); |
| 233 | |
| 234 | // Look through the log storage directory |
| 235 | foreach ( $log_files_dir as $node ) { |
| 236 | if ( ! $node->isReadable() ) { |
| 237 | continue; |
| 238 | } |
| 239 | |
| 240 | $name = $node->getFilename(); |
| 241 | $ext = $node->getExtension(); |
| 242 | |
| 243 | // Skip unless it is a .log file with the expected prefix |
| 244 | if ( 'log' !== $ext || 0 !== strpos( $name, $basename ) ) { |
| 245 | continue; |
| 246 | } |
| 247 | |
| 248 | if ( preg_match( '/([0-9]{4}\-[0-9]{2}\-[0-9]{2})/', $name, $matches ) ) { |
| 249 | $logs[] = $matches[1]; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | rsort( $logs ); |
| 254 | } catch ( Exception $e ) { |
| 255 | return $logs; |
| 256 | } |
| 257 | |
| 258 | return $logs; |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Switches to the specified log. The $log_identifier should take the |
| 263 | * form of a "yyyy-mm-dd" format date string. |
| 264 | * |
| 265 | * If optional param $create is true then it will try to create a log |
| 266 | * using the provided identifier. If the log does not exist, cannot be |
| 267 | * created or an invalid identifier has been passed in then boolean false |
| 268 | * will be returned, otherwise it will attempt to switch to the new log. |
| 269 | * |
| 270 | * @param mixed $log_identifier |
| 271 | * @param bool $create |
| 272 | * |
| 273 | * @return bool |
| 274 | */ |
| 275 | public function use_log( $log_identifier, $create = false ) { |
| 276 | $log_file = $this->get_log_file_name( $log_identifier ); |
| 277 | $exists = file_exists( $log_file ); |
| 278 | |
| 279 | if ( ! $exists && ! $create ) { |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | if ( ! $exists && $create && preg_match( '/^([0-9]{4}\-[0-9]{2}\-[0-9]{2})$/', $log_file ) ) { |
| 284 | if ( false === file_put_contents( $log_file, '' ) ) { |
| 285 | return false; |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | $this->set_log_file( $log_identifier ); |
| 290 | return true; |
| 291 | } |
| 292 | |
| 293 | /** |
| 294 | * Performs routine maintenance and cleanup work (such as log rotation) |
| 295 | * whenever it is called. |
| 296 | */ |
| 297 | public function cleanup() { |
| 298 | // Default to retaining 7 days worth of logs |
| 299 | $cutoff = date_i18n( 'Y-m-d', current_time( 'timestamp' ) - WEEK_IN_SECONDS ); |
| 300 | |
| 301 | /** |
| 302 | * Logs falling on or earlier than this date will be removed. |
| 303 | * |
| 304 | * @param string $cutoff 'Y-m-d' format date string |
| 305 | */ |
| 306 | $cutoff = apply_filters( 'tribe_file_logger_cutoff', $cutoff ); |
| 307 | |
| 308 | foreach ( $this->list_available_logs() as $available_log ) { |
| 309 | if ( $available_log <= $cutoff ) { |
| 310 | unlink( $this->get_log_file_name( $available_log ) ); |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 |