Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhauling the backtrace display in the admin #121

Merged
merged 14 commits into from
Apr 30, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGELOG](https://keepachangelog.com/en/1.0.0/).

- New filter `ai_logger_unrestricted_logging` to allow unrestricted logging
without checking if the log was recently seen.
- Overhaul of the log backtrace display in the admin.

## 2.3.2

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ See the [wiki](https://github.com/alleyinteractive/logger/wiki) for complete inf

## Installation

Logger requires PHP 8.0 and Composer to run properly.
Logger requires PHP 8.1 and Composer to run properly.

```bash
composer require alleyinteractive/logger
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
"monolog/monolog": "^2.8",
"psr/log": "^1.0|^2.0|^3.0"
"psr/log": "^1.0|^2.0|^3.0",
"spatie/backtrace": "^1.6",
"mantle-framework/support": "^1.0"
},
"require-dev": {
"alleyinteractive/alley-coding-standards": "^2.0",
Expand Down
7,325 changes: 3,694 additions & 3,631 deletions composer.lock

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions inc/backtrace/class-frame.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Frame class file
*
* @package AI_Logger
*/

namespace AI_Logger\Backtrace;

use Spatie\Backtrace\Frame as SpatieFrame;

/**
* Frame extension class.
*
* Stores the frame's code snippet in the frame itself for serialization and storage.
*/
class Frame extends SpatieFrame {
/**
* Code snippet.
*
* @var array
*/
public array $snippet;

/**
* Create a new instance from a SpatieFrame.
*
* @param SpatieFrame $frame SpatieFrame to create from.
* @return Frame
*/
public static function from_base( SpatieFrame $frame ): self {
$instance = new self(
$frame->file,
$frame->lineNumber,
$frame->arguments,
$frame->method,
$frame->class,
$frame->applicationFrame,
$frame->textSnippet
);

// Escape the class name to prevent issues when storing backslashes.
if ( ! empty( $instance->class ) ) {
// Convert backslashes to forward slashes for storage. For an unknown
// reason, the backslashes are being stripped out when storing the class
// name. This is a workaround to prevent that.
$instance->class = str_replace( '\\', '/', $instance->class );
}

return $instance;
}

/**
* Load the code snippet for this frame.
*
* @param int $line_count Number of lines to load.
*/
public function load_snippet( int $line_count ): void {
$this->snippet = $this->getSnippet( $line_count );
}
}
9 changes: 8 additions & 1 deletion inc/class-ai-logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,14 @@ protected function get_processors(): array {
*
* @param \Monolog\Processor\ProcessorInterface[] $processors Monolog processors.
*/
return (array) apply_filters( 'ai_logger_processors', [ new \Monolog\Processor\WebProcessor() ] );
return (array) apply_filters(
'ai_logger_processors',
[
new \Monolog\Processor\WebProcessor(),
srtfisher marked this conversation as resolved.
Show resolved Hide resolved
new Processor\Server_Context_Processor(),
new Processor\WordPress_User_Processor(),
]
);
}

/**
Expand Down
9 changes: 8 additions & 1 deletion inc/class-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,14 @@ public function generate( $args, $assoc_args ) {
[
'context' => $assoc_args['log-context'],
'example_context' => $i,
]
'example_data' => [
'key' => 'value',
'key_2' => 'value_2',
'key_3' => [
'key' => 'value',
],
],
],
);
}

Expand Down
16 changes: 9 additions & 7 deletions inc/class-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public function render_textarea( $args, $value ) {
esc_attr( $args['field'] ),
esc_attr( $args['rows'] ),
esc_attr( $args['cols'] ),
esc_textarea( $value )
esc_textarea( (string) $value )
);
}

Expand Down Expand Up @@ -283,12 +283,14 @@ public function on_admin_enqueue_scripts() {
return;
}

wp_enqueue_style(
'ai-logger-admin',
AI_LOGGER_URL . 'static/css/admin.css',
[],
'0.1',
);
wp_enqueue_style( 'ai-logger-admin', AI_LOGGER_URL . 'static/css/admin.css', [], '0.1' );
wp_enqueue_style( 'prism', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css', [], '1.29.0' );
srtfisher marked this conversation as resolved.
Show resolved Hide resolved
wp_enqueue_style( 'prism-line-numbers', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.css', [], '1.29.0' );
wp_enqueue_style( 'prism-line-highlight', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.css', [], '1.29.0' );
wp_enqueue_script( 'prism', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js', [], '1.29.0', true );
wp_enqueue_script( 'prism-autoloader', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.js', [ 'prism' ], '1.29.0', true );
wp_enqueue_script( 'prism-line-numbers', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js', [ 'prism' ], '1.29.0', true );
wp_enqueue_script( 'prism-line-highlight', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.js', [ 'prism' ], '1.29.0', true );
}

/**
Expand Down
62 changes: 51 additions & 11 deletions inc/handler/class-post-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@

namespace AI_Logger\Handler;

use AI_Logger\AI_Logger;
use AI_Logger\Backtrace\Frame;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Spatie\Backtrace\Backtrace;
use Spatie\Backtrace\Frame as SpatieFrame;

/**
* Post Log Handler
Expand Down Expand Up @@ -94,19 +98,50 @@ public function clear() {
* @param array $record Log Record.
*/
protected function write( array $record ): void {
$user = wp_get_current_user();

// Capture the stack trace.
if ( empty( $log['context'] ) || 'front-end' !== $log['context'] ) {
$record['extra']['backtrace'] = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
}
// Store the backtrace for only this handler. Not created as a processor
// to avoid bloat of backtrace on all log types.
$record['extra']['backtrace'] = Backtrace::create()->startingFromFrame(
fn ( SpatieFrame $frame ) => ! in_array(
$frame->class,
[
static::class,
AI_Logger::class,
\Monolog\Handler\AbstractProcessingHandler::class,
\Monolog\Logger::class,
],
true
)
)
->frames();

$record['extra']['backtrace'] = array_map(
fn ( SpatieFrame $frame ) => Frame::from_base( $frame ),
$record['extra']['backtrace'],
);

if ( $user ) {
$record['extra']['user'] = [
'ID' => $user->ID,
'user_login' => $user->user_login,
'user_email' => $user->user_email,
];
/**
* Filter the number of code frames to store in the log.
*
* @param int $frames Number of code frames to store.
* @param array $record Log record.
*/
$frames = min( (int) apply_filters( 'ai_logger_backtrace_code_frames', 8, $record ), count( $record['extra']['backtrace'] ) );

if ( $frames > 0 ) {
/**
* Filter the number of lines to store for each code frame.
*
* @param int $frame_lines Number of lines to store for each code frame.
* @param array $record Log record.
*/
$frame_lines = (int) apply_filters( 'ai_logger_backtrace_code_lines', 5, $record );

for ( $i = 0; $i < $frames; $i++ ) {
// Enforce a maximum of 20 lines per frame with a default of 5.
$record['extra']['backtrace'][ $i ]->load_snippet( max( $frame_lines, 20 ) );
}
}
}

/**
Expand Down Expand Up @@ -229,6 +264,11 @@ protected function insert_permitted( string $transient_key, array $log ): bool {
return false;
}

// Allow unrestricted logging if filtered.
if ( \apply_filters( 'ai_logger_unrestricted_logging', false ) ) {
return true;
}

/**
* Allow unrestricted logging if filtered.
*
Expand Down
4 changes: 2 additions & 2 deletions inc/meta-box/class-meta-box.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* Logger Meta Box
*
* Expose stored logs to the front-end.
* Expose stored logs to the front-end for logs stored against a post or term.
*/
abstract class Meta_Box {
/**
Expand Down Expand Up @@ -80,6 +80,6 @@ public function render_meta_box( $object ) {
);
}

include AI_LOGGER_PATH . '/template-parts/meta-box.php'; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingCustomConstant
include AI_LOGGER_PATH . '/template-parts/object-log-display.php'; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingCustomConstant
}
}
35 changes: 35 additions & 0 deletions inc/processor/class-server-context-processor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/**
* Server Context Processor class file
*
* @package AI_Logger
*/

namespace AI_Logger\Processor;

use Monolog\Processor\ProcessorInterface;

/**
* Include information about the server in the log record.
*/
class Server_Context_Processor implements ProcessorInterface {
/**
* Adds server context to the log record.
*
* @param array $record The record to process.
* @return array The processed record
*/
public function __invoke( array $record ): array {
$record['extra']['php_version'] = phpversion();
$record['extra']['php_sapi'] = php_sapi_name();
$record['extra']['php_os'] = PHP_OS;
$record['extra']['php_uname'] = php_uname();
$record['extra']['is_wp_cli'] = defined( 'WP_CLI' ) && WP_CLI;
$record['extra']['is_cron'] = wp_doing_cron();
$record['extra']['is_rest'] = defined( 'REST_REQUEST' ) && REST_REQUEST;
$record['extra']['is_ajax'] = defined( 'DOING_AJAX' ) && DOING_AJAX;
$record['extra']['is_xmlrpc'] = defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST;

return $record;
}
}
35 changes: 35 additions & 0 deletions inc/processor/class-wordpress-user-processor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/**
* WordPress_User_Processor class file
*
* @package AI_Logger
*/

namespace AI_Logger\Processor;

use Monolog\Processor\ProcessorInterface;

/**
* Include information about the current WordPress user in the log record.
*/
class WordPress_User_Processor implements ProcessorInterface {
/**
* Adds user context to the log record.
*
* @param array $record The record to process.
* @return array The processed record
*/
public function __invoke( array $record ): array {
$user = wp_get_current_user();

if ( $user ) {
$record['extra']['user'] = [
'ID' => $user->ID,
'user_login' => $user->user_login,
'user_email' => $user->user_email,
];
}

return $record;
}
}
1 change: 1 addition & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<exclude name="Generic.Arrays.DisallowLongArraySyntax.Found" />
<exclude name="VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable" />
<exclude name="WordPress.NamingConventions.PrefixAllGlobals.ShortPrefixPassed" />
<exclude name="WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase" />
</rule>

<arg value="ps" />
Expand Down
39 changes: 37 additions & 2 deletions static/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,43 @@ Logger Admin Styles

/* Style the <pre> tags */
.post-type-ai_log .ai-log-display pre {
margin: 0;
}

.post-type-ai_log .ai-log-display pre:not([class*="language-"]) {
background: rgba(0, 0, 0, 0.07);
max-width: 100%;
overflow-x: scroll;
padding: 3px 5px 2px 5px;
padding: 5px;
}

/* Style the backtrace */
.ai-log-backtrace details {
border: 1px solid #ddd;
}

.ai-log-backtrace details:not(:last-child) {
border-bottom: none;
}

.ai-log-backtrace details summary {
border-bottom: 1px solid transparent;
cursor: pointer;
padding: 8px;
}

.ai-log-backtrace details[open] summary {
border-bottom-color: #ddd;
}

.ai-log-backtrace details summary:hover {
background: #f9f9f9;
}

.ai-log-backtrace details pre {
margin: 0;
}

.ai-log-backtrace details p {
padding: 10px;
margin: 0;
}
Loading