Skip to content

Show the file, line, and stack trace for errors thrown while preparing documents - #4392

Open
anjulahettige wants to merge 6 commits into
developfrom
fix/prepare-document-error-location
Open

anjulahettige wants to merge 6 commits into
developfrom
fix/prepare-document-error-location

Conversation

@anjulahettige

@anjulahettige anjulahettige commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Description of the Change

Since 5.3.0 (#4210), Indexable::index(), bulk_index(), and bulk_index_dynamically() catch any Throwable thrown by prepare_document(), so a sync can continue past a broken object. The catch blocks keep only $th->getMessage(), which drops the file, line, and stack trace:

  • wp elasticpress sync --show-errors prints only <ID> (Post): [prepare_document_error] <message>.
  • The Sync page shows the same message.
  • Nothing is written to the PHP error log, because the error never reaches PHP's error handler.

Before 5.3.0, the same error stopped the sync, but PHP printed and logged the full fatal error, so --show-errors and the error log were how you found the plugin or theme responsible.

This PR keeps the 5.3.0 behavior (the object is skipped and the sync continues) and restores the missing details:

  • Error data: a new Indexable::handle_prepare_document_error() is called by all three catch blocks. When WP_DEBUG is on, it writes the full error, with its stack trace, to the PHP error log with error_log(). It returns the sync error with:

    • reason: the message plus in <file>:<line>
    • stack_trace: the stack trace

    Paths in reason and stack_trace are relative to the WordPress root. The error log keeps absolute paths, like PHP's own fatal errors.

  • Passing traces along: IndexHelper sends each error's stack trace along with its message through a new optional $stack_traces parameter on queue_message(), output(), and build_message_errors_data(). build_message_errors_data() adds the trace to the errors data that the CLI and the Sync page receive.

  • WP-CLI: --show-errors now prints Stack trace for: <error> followed by the trace. There is no new flag. With --stop-on-error, the traces print before the sync stops.

  • Sync page: errors that have a trace get a "Stack trace" toggle (<details>), collapsed by default, in the Errors tab. Nothing else on the page changes, and "Copy log to clipboard" is unchanged.

Example output from wp elasticpress sync --include=4 --show-errors:

Warning: 4 (Post): [prepare_document_error] Call to a member function set() on null in wp-content/mu-plugins/broken-plugin.php:11
Stack trace for: 4 (Post): [prepare_document_error] Call to a member function set() on null in wp-content/mu-plugins/broken-plugin.php:11
#0 wp-includes/class-wp-hook.php(353): {closure}('<p>Hello</p>\n')
#1 wp-includes/plugin.php(206): WP_Hook->apply_filters('<p>Hello</p>\n', Array)
#2 wp-content/plugins/elasticpress/includes/classes/Indexable/Post/Post.php(508): apply_filters('the_content', 'Hello')
#3 wp-content/plugins/elasticpress/includes/classes/Indexable.php(481): ElasticPress\Indexable\Post\Post->prepare_document(4)
...

Notes and drawbacks:

  • Error log size: when WP_DEBUG is on, one error log entry, with its stack trace, is written for every object that fails. If a plugin breaks every product on a large store, the log grows by about 2 to 3 KB per failed object per sync. With WP_DEBUG off, nothing is logged, and the file, line, and trace are only available in WP-CLI and on the Sync page.
  • Response size: the errors data sent to the Sync page and the CLI includes a trace for each failed object, about 2 KB each. Stack traces are never written to the ep_index_meta option, because each message is removed from the queue before the option is saved. With 170 objects failing in one 350-object batch, the largest write to the option was 8 KB.
  • File paths: the error text now includes file paths relative to the WordPress root. Only users who can run a sync see it.
  • Method signatures: the three IndexHelper methods above are protected and gained an optional parameter, so a subclass that overrides one of them with the old signature would cause a fatal error. IndexHelper::factory() always instantiates new self(), and I found no subclasses in ElasticPress Labs, Debug Bar ElasticPress, or GitHub code search.
  • Errors that share a row: the Errors tab combines errors with the same text into one row with a count, for example when the error interpreter recognizes a message. Those rows keep the first stack trace available. countErrors() now builds a new object for the combined row instead of modifying the previous React state.

Related: #4210, #4333

How to test the Change

  1. Add a must-use plugin that throws while ElasticPress prepares one post, for example wp-content/mu-plugins/broken.php, replacing 123 with an existing post ID:
    <?php
    add_filter( 'ep_post_sync_args', function ( $args, $post_id ) {
    	if ( 123 === (int) $post_id ) {
    		$session = null;
    		$session->set( 'viewed', 1 );
    	}
    	return $args;
    }, 10, 2 );
  2. Run wp elasticpress sync --include=123 --show-errors. The warning ends with in wp-content/mu-plugins/broken.php:<line>, followed by Stack trace for: ... and the trace. Without --show-errors, the output is the same as before.
  3. With WP_DEBUG and WP_DEBUG_LOG enabled, check wp-content/debug.log. It has an ElasticPress: could not prepare post 123 for indexing. entry with the full stack trace. With WP_DEBUG off, no entry is written.
  4. Go to ElasticPress > Sync, start a sync, and open Log > Errors. The error shows the file and line, and the "Stack trace" toggle is collapsed until you open it.
  5. Run wp elasticpress sync --stop-on-error. The trace prints before the sync stops.
  6. Run the unit tests: composer run test-single-site -- --filter 'TestIndexHelper|TestCommands|TestPost' and composer run test -- --filter 'TestIndexHelper|TestCommands|TestPost'.

Changelog Entry

Fixed - Errors thrown while preparing content for a sync now include the file, line, and stack trace in WP-CLI (--show-errors), the Sync page, and, when WP_DEBUG is on, the PHP error log.

Credits

Props @anjulahettige

Checklist:

@anjulahettige anjulahettige added this to the 5.4.0 milestone Oct 22, 2026
Comment thread includes/classes/Indexable.php Outdated
*/
protected function handle_prepare_document_error( \Throwable $th, $object_id ): array {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( sprintf( 'ElasticPress: could not prepare %s %d for indexing. %s', $this->slug, $object_id, $th ) );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anjulahettige any chance you can wrap this call with a check of WP_DEBUG being on, please?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4855362.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants