Skip to content

Commit 8b9a655

Browse files
authored
Merge pull request #129 from alleyinteractive/feature/condense-trace-do-action
Reduce backtrace display of hooks
2 parents 20a8c7b + d8ca787 commit 8b9a655

File tree

6 files changed

+122
-2
lines changed

6 files changed

+122
-2
lines changed

.phpunit-watcher.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
watch:
2+
directories:
3+
- tests
4+
- inc
5+
fileMask: '*.php'
6+
phpunit:
7+
timeout: 300

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a
44
CHANGELOG](https://keepachangelog.com/en/1.0.0/).
55

6+
## 2.6.0 - 2024-12-19
7+
8+
- Collapse internal calls to `do_action` and `apply_filters` in the log backtrace.
9+
610
## 2.5.0 - 2024-09-03
711

812
- Change the garbage collector to schedule a single recurring event to clean up logs instead of `wp_schedule_single_event`.

inc/backtrace/class-frame.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@
88
namespace AI_Logger\Backtrace;
99

1010
use Spatie\Backtrace\Frame as SpatieFrame;
11+
use WP_Hook;
1112

1213
/**
1314
* Frame extension class.
1415
*
1516
* Stores the frame's code snippet in the frame itself for serialization and storage.
1617
*/
1718
class Frame extends SpatieFrame {
19+
/**
20+
* Hook methods to ignore.
21+
*
22+
* @var array<string>
23+
*/
24+
const HOOK_METHODS = [ 'do_action', 'do_action_ref_array', 'apply_filters', 'apply_filters_ref_array' ];
25+
1826
/**
1927
* Code snippet.
2028
*
@@ -56,6 +64,16 @@ public static function from_base( SpatieFrame $frame ): self {
5664
* @param int $line_count Number of lines to load.
5765
*/
5866
public function load_snippet( int $line_count ): void {
67+
// Prevent snippet from being loaded for specific internal frames which
68+
// don't make sense to store (such as do_action).
69+
if ( WP_Hook::class === $this->class ) {
70+
return;
71+
}
72+
73+
if ( ! $this->class && in_array( $this->method, self::HOOK_METHODS, true ) ) {
74+
return;
75+
}
76+
5977
$this->snippet = $this->getSnippet( $line_count );
6078
}
6179
}

inc/handler/class-post-handler.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ protected function write( array $record ): void {
113113
],
114114
true
115115
)
116-
)
117-
->frames();
116+
)->frames();
118117

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

template-parts/log-display.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @package AI_Logger
66
*/
77

8+
use AI_Logger\Backtrace\Frame;
89
use AI_Logger\Data_Structures;
910

1011
use function Mantle\Support\Helpers\str;
@@ -47,12 +48,60 @@ function ai_logger_render_legacy_backtrace( array $backtrace ): void {
4748
<?php
4849
}
4950

51+
/**
52+
* Collapse do_action/do_action_ref_array/apply_filters/apply_filters_ref_array
53+
* calls in the backtrace. This is to prevent the backtrace from being
54+
* cluttered with calls to these internal WordPress functions.
55+
*
56+
* @param array<\AI_Logger\Backtrace\Frame> $backtrace Backtrace to collapse.
57+
* @return array<\AI_Logger\Backtrace\Frame> Collapsed backtrace.
58+
*/
59+
function ai_logger_collapse_hook_calls( array $backtrace ): array {
60+
// Prevent compression if the query parameter is set.
61+
if ( ! empty( $_GET['ai_logger_dont_compress'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
62+
return $backtrace;
63+
}
64+
65+
for ( $i = 0; $i < count( $backtrace ) - 1; $i++ ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopShouldBeWhileLoop.ForLoop, Squiz.PHP.DisallowSizeFunctionsInLoops.Found, Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
66+
$current = $backtrace[ $i ];
67+
68+
if ( \WP_Hook::class !== $current->class ) {
69+
continue;
70+
}
71+
72+
if ( ! in_array( $current->method, Frame::HOOK_METHODS, true ) ) {
73+
continue;
74+
}
75+
76+
// Determine where the backtrace exits from WP_Hook. Find all the frames and
77+
// remove them. This could be in 2 frames or 5.
78+
for ( $si = $i + 1; $si < count( $backtrace ); $si++ ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopShouldBeWhileLoop.ForLoop, Squiz.PHP.DisallowSizeFunctionsInLoops.Found, Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
79+
$next = $backtrace[ $si ];
80+
81+
if ( \WP_Hook::class === $next->class ) {
82+
continue;
83+
}
84+
85+
if ( in_array( $next->method, Frame::HOOK_METHODS, true ) ) {
86+
continue;
87+
}
88+
89+
array_splice( $backtrace, $i, $si - $i, [] );
90+
91+
break;
92+
}
93+
}
94+
95+
return $backtrace;
96+
}
97+
5098
/**
5199
* Render the backtrace powered by spatie/backtrace.
52100
*
53101
* @param array<\AI_Logger\Backtrace\Frame> $backtrace Backtrace to render.
54102
*/
55103
function ai_logger_render_backtrace( array $backtrace ): void {
104+
$backtrace = ai_logger_collapse_hook_calls( $backtrace );
56105
?>
57106
<div class="ai-log-backtrace">
58107
<?php

tests/Handler/PostHandlerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
namespace AI_Logger\Tests\Handler;
3+
4+
use AI_Logger\Backtrace\Frame;
5+
use AI_Logger\Handler\Post_Handler;
6+
use Mantle\Testkit\Test_Case;
7+
use Monolog\Logger;
8+
use WP_Post;
9+
10+
class PostHandlerTest extends Test_Case {
11+
protected Logger $logger;
12+
13+
protected function setUp(): void {
14+
parent::setUp();
15+
16+
$this->logger = new Logger( 'test', [
17+
new Post_Handler(),
18+
] );
19+
20+
// Prevent logging on shutdown by default.
21+
remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
22+
add_filter( 'ai_logger_should_write_on_shutdown', '__return_false' );
23+
}
24+
25+
public function test_generates_a_backtrace(): void {
26+
$this->logger->info( 'a message to log' );
27+
28+
$log = $this->get_last_log();
29+
30+
$this->assertNotNull( $log );
31+
32+
$record = get_post_meta( $log->ID, '_logger_record', true );
33+
34+
$this->assertNotEmpty( $record['extra']['backtrace'] );
35+
$this->assertContainsOnlyInstancesOf( Frame::class, $record['extra']['backtrace'] );
36+
}
37+
38+
protected function get_last_log(): ?WP_Post {
39+
$logs = get_posts( [ 'post_type' => 'ai_log', 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'DESC' ] );
40+
41+
return array_shift( $logs );
42+
}
43+
}

0 commit comments

Comments
 (0)