file = $file; $this->threshold = $threshold; $this->max_bytes = $max_bytes; } /** * Write a log record. * * @param string $level Level name. * @param string $message Human readable message. * @param array $context Structured context. * @return void */ public function log( string $level, string $message, array $context = array() ): void { if ( LogLevel::weight( $level ) < LogLevel::weight( $this->threshold ) ) { return; } $line = sprintf( '[%s] [%s] %s', gmdate( 'Y-m-d H:i:s' ), $level, $message ); if ( array() !== $context ) { $encoded = wp_json_encode( $context ); if ( is_string( $encoded ) ) { $line .= ' ' . $encoded; } } $this->write( $line . "\n" ); } /** * Debug level shortcut. * * @param string $message Message. * @param array $context Context. * @return void */ public function debug( string $message, array $context = array() ): void { $this->log( LogLevel::DEBUG, $message, $context ); } /** * Info level shortcut. * * @param string $message Message. * @param array $context Context. * @return void */ public function info( string $message, array $context = array() ): void { $this->log( LogLevel::INFO, $message, $context ); } /** * Warning level shortcut. * * @param string $message Message. * @param array $context Context. * @return void */ public function warning( string $message, array $context = array() ): void { $this->log( LogLevel::WARNING, $message, $context ); } /** * Error level shortcut. * * @param string $message Message. * @param array $context Context. * @return void */ public function error( string $message, array $context = array() ): void { $this->log( LogLevel::ERROR, $message, $context ); } /** * Absolute path of the log file. * * @return string */ public function file(): string { return $this->file; } /** * Read the last N lines of the log. * * @param int $lines Line count. * @return string */ public function tail( int $lines = 200 ): string { if ( ! is_readable( $this->file ) ) { return ''; } $content = (string) file_get_contents( $this->file ); $rows = explode( "\n", rtrim( $content, "\n" ) ); if ( count( $rows ) > $lines ) { $rows = array_slice( $rows, -1 * $lines ); } return implode( "\n", $rows ); } /** * Append a line to the log file, rotating it when it grows too large. * * @param string $line Fully formatted line including the newline. * @return void */ private function write( string $line ): void { $dir = dirname( $this->file ); if ( ! is_dir( $dir ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @mkdir( $dir, 0755, true ); } // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $size = is_file( $this->file ) ? (int) @filesize( $this->file ) : 0; if ( $size > $this->max_bytes ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @rename( $this->file, $this->file . '.1' ); } // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @file_put_contents( $this->file, $line, FILE_APPEND | LOCK_EX ); } }