Problem/Motivation
catch (MigrateException $e) {
$this->migration->getIdMap()->saveIdMapping($row, [], $e->getStatus());
$this->saveMessage($e->getMessage(), $e->getLevel());
$save = FALSE;
}
catch (MigrateSkipRowException $e) {
if ($e->getSaveToMap()) {
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
}
if ($message = trim($e->getMessage())) {
$this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
}
$save = FALSE;
}
This code in MigrateExecutable catches exceptions, typically from process plugins, and puts the message into the migration's message log. But typically, process plugins throw exceptions with messages that don't tell you enough about what caused the problem. When you find the message in the map table, you know the row, and with some grepping of the message you can find the place in the code that caused the exception, but you don't know:
- the actual migration (since a migration lookup process could have caused the problem)
- the destination property (the same process plugin could be in use in several destination properties).
We could say that all process plugins should put that in their exception messages, but that's not very good DX as it needlessly repeats code.
Instead, MigrateExecutable should prepend these messages with extra detail.
Steps to reproduce
Proposed resolution
Add migration id and destination property to the exception message so it is in this format:
migration_id: destination_property: message
Example message:
d7_field_instance:type: Can't migrate source field field_text_long_plain_filtered configured with both plain text and filtered text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-d...
Change any process plugin that is passing the destination property in MigrateException or MigrateSkipRowException
Remaining tasks
Patch
Review
Commit
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #76 | 2976098-76.patch | 32.91 KB | quietone |
| #76 | interdiff-74-76.txt | 2.16 KB | quietone |
| #74 | 2976098-74.patch | 31.33 KB | quietone |
| #74 | interdiff-71-74.txt | 709 bytes | quietone |
| #71 | 2976098-71.patch | 31.33 KB | quietone |
Comments
Comment #5
wim leersThis is absolutely still an issue. I've been looking into the migration system for about a week, and I already lost count how many times I set a breakpoint in that place and then had to wait patiently for the migration to finally reach that point. Better logging could be a massive productivity boost.
Comment #6
wim leersComment #7
wim leersComment #8
wim leersI think we should bump #2959444: [Meta] Improve exception messages in process plugins to major instead of this. I only found that meta now.
Comment #10
benjifisher+1 for adding information when the exception is caught, not when it is thrown.
I am making some minor edits to the issue summary.
Comment #11
quietone commentedA possible solution. Here is an example of the message, the result of testing with the extract process plugin
Migration 'default_language', destination 'default_langcode' Array index missing, extraction failed.Comment #13
quietone commentedThis will be better.
Comment #15
quietone commentedAdjust the tests that make assertions on the migrate message.
Comment #17
sivaji_ganesh_jojodae commentedPatch attached tries to fix the last test error.
Comment #18
quietone commented@DevJoJodae, thanks for making a patch. Sadly, we have duplicated work, that is I have made the same patch when I returned from dinner. Please have a look at the recent comments to figure out if someone is actively working on it so we can avoid this in the future. Thx.
Comment #19
benjifisherDoes this patch play well with #2969551: Migrate messages from caught exceptions need file and line details?
Comment #20
benjifisherSurprisingly, the patch in #17 does not conflict with the current patch on #2969551: Migrate messages from caught exceptions need file and line details. (That issue has the same patch attached in #12 and #18.)
I will review this issue today, as part of DrupalCon Global.
Comment #21
benjifisherThis new property needs an
@varcomment.Can we be more consistent here?
If it were a few more lines, or if we did the same thing a third time, then I would want to add a helper function to be more DRY. Either of those might happen in the future. If we can be a little more consistent now, it will help when we decide to add that helper function.
We should also get rid of the test for an empty message. Since we have useful information (the destination property and the current migration) we should save a message even if
$e->getMessage()is empty.I wonder if we should use
sprintf()instead of variables in double quotes. We are playing with messages from exception messages, andsprintf()is recommended there. But maybe this counts as a "foolish consistency". (As in the often misquoted "A foolish consistency is the hobgoblin of little minds.")The updates to the tests are all straightforward. I think this means that our test coverage was already pretty good, and we just need to make the necessary adjustments when changing the messages.
We do not need redundant information. Most process plugins do not include
$destination_propertywhen they throw a MigrateException, but FormatDate, ImageStyleMappings, and StaticMap do. We can handle this in a follow-up issue if you prefer, but I think it is not too hard to do it as part of this issue. Curiously, BlockVisibility passesdestination_propertytosprintf(), but there is no matching%s. We should clean that up at the same time.Comment #22
benjifisherI also did some manual testing. I applied the patch to a recent project (Drupal 8.9.2) and tested
drush mim d7_file --upgrade. As expected,drush mmsg d7_fileshows a lot of messages like this:Comment #23
quietone commented#21
1. Fixed
2. Improved and using sprintf
3. Nice
4. Fixed.
Good to know that manual testing shows this works.
Comment #25
quietone commentedI was wondering if removing the test for empty messages was going to cause an error (#21.2) and it does. Restoring that test.
Comment #26
benjifisherI do not have time to look at it now, but this might be a case where we should fix the test instead fixing the "bug".
Comment #27
quietone commentedI've only got a moment...
If we change this then every MigrateSkipRowException will generate messages causing a lot of noise in the message tables. I think that is out of scope and we should discuss in another issue.
Comment #28
benjifisher@quietone:
You convinced me! That part of #21.2 was at best out of scope. Probably it was simply a bad idea.
The rest of the changes look great. The additional changes to the tests after implementing #21.4 look good. Once again, it looks as though our test coverage is in pretty good shape.
Comment #29
catchIs it really OK/necessary to keep overwriting $this->destination here in the foreach loop? I think it needs a comment if it is.
Comment #30
alexpottFollowing up @catch's question...
Get we use
$destination->getPluginId()here instead of $this->destination and not add the property?Comment #31
quietone commented29. Changed the foreach to set $this->destination.
30. No, can't do that, sorry. Here $destination is the current destination property name on the process pipeline not a destination plugin.
Comment #32
quietone commentedIgnore previous patch, bad patch and numbered wrong too!
29. Changed the foreach to set $this->destination.
30. No, can't do that, sorry. Here $destination is the current destination property name on the process pipeline not a destination plugin.
Comment #33
quietone commentedSeems my cold is affecting me in more ways than I thought.
Restarting from reroll of patch #25.
Comment #34
quietone commentedCan this get any worse?
This is just a reroll
Comment #35
quietone commentedNow add the changes I tried to do way back in #31.
#29. Changed the foreach to set $this->destination.
#30. No, can't do that, sorry. Here $destination is the current destination property name on the process pipeline not a destination plugin.
Comment #37
quietone commentedRight, that won't work with the sub_process process plugin. How about adding a catch, saving the destination and throwing the exception.
Comment #38
alexpottoops x-post with #37 - I still think this approach is preferable.
I don't think assignment like this works.
Plus there are way too many things called $destination here it's confusing. And adding it as a class property makes it stateful and look more useful than it is.
Here's an alternate solution that allows us to do this without the class property with a small amount of refactoring that also makes the code a bit simpler to read. Also it makes it obvious that at the earliest point an exception might thrown in
$this->migration->getProcessPlugins($process)we might not get have a destination property name.Interdiff is back to #34 since that is the last working version.
Comment #39
alexpottLol forgot to remove the destination class property which was one of the aims - oops.
Comment #40
quietone commented@alexpott, Yes, that is better. Thanks.
This is some changes to comments.
This needs to be modified as well but maybe tomorrow. $process isn't used here. It is from the doc block for proccesRow in MigrateExecutableInterface and references $process which isn't used here.
Comment #41
quietone commentedSimplify the documentation for the $value parameter, that is, don't repeat what is elsewhere, and add an @see.
Comment #42
wim leersWhy prepend structured information to an unstructured string/blob?
This is structured data. If this were saved as separate fields (
migration_plugin_idanddestination_property_name) in themigrate_message_*DB tables, then this would be much easier to search.It'd also open the door for a single
migrate_messagestable, to allow searching all migration messages with a single query, rather than dozens (or even hundreds) ofmigration_message_*tables.So IMHO this is doing the right thing, but in the wrong way.
AFAICT this is even literally repeating a subset of the table name (the asterisk in
migration_message_*) for every row in that table? 🤭Comment #43
mikelutzI don't know that we should include the potential exceptions thrown by ->getProcessPlugins() here with other migrate exceptions thrown when processing a row. That method doesn't depend on $row at all, so if it throws an exception (MigrateException or PluginException) it's going to continue to throw that exception for every row. I think if we catch an exception in that method, we should handle it separately and bow out of the migration.
Comment #44
mikelutzIn lieu of a response suggesting otherwise, back to NW for #43
Comment #46
quietone commentedAddressing #43. Looks to me like the current behavior is that if $this->migration->getProcessPlugins() throws an exception one will be thrown for every row. So, this changes that, for the better.
Comment #48
quietone commentedI meant to remove those lines. This should be better.
Comment #50
quietone commentedThat leaves failing tests of MigrateExecutable. The unit test needed lots of method calls removed and the Kernel test found errors in the $return value. Should be sorted now.
Comment #51
quietone commentedRemove use of deprecated method,
Comment #53
quietone commentedI stray 'x' got into the file after I tested and while I was making the patch.
Comment #54
quietone commentedJust a reroll
Comment #55
joachim commentedAdmittedly I tried applying the patch here which is for 9.2 to 8.9 (!!!) and ignored what looked like a minor patch hunk that failed, but I get this error when I try to run a migration:
Comment #56
quietone commentedRerolling the latest patch. And add some details to the IS>
Comment #57
quietone commentedI wasn't on HEAD. Fix the coding standard error.
Comment #58
quietone commented#55. When I rerolled the patch the change required was to add a use statement for Drupal\migrate\Plugin\MigrateSourceInterface in MigrateExecutable.
Comment #59
dinarcon commentedI am getting the same error described in #55 in a Drupal 9.1 installation. Migrate Tools 8.x-5.0 is installed which overrides the
getSourcemethod in itsMigrateExecutableclass. The method returnsSourceFilterindeed.Core's
MigrateExecutablereturnsMigrateSourceInterfacein itsgetSourceimplementation. This goes in line with the type hint in thedoImportintroduced by this patch.Comment #60
quietone commentedAFAIKT, the return value from \Drupal\migrate_tools\MigrateExecutable::getSource is not in agreement with the parent class \Drupal\migrate\MigrateExecutable::getSource. How do we sort that out?
Comment #61
heddnIt could be the version of PHP
For the purposes of this method, can we typehint on an iterator instead of the source interface? Probably not wise. See comments below.
The real solution is for the filter in migrate tools to get repaired. And maybe even in drush's new tooling? Looking at https://github.com/drush-ops/drush/blob/10.x/src/Drupal/Migrate/MigrateI..., it isn't quite the same thing. But it could suffer similar issues as we add typehinting. But that's another issue.
Suggested fix for Migrate Tools below:
Comment #63
quietone commentedCreated an issue in Migrate Tools, #3212495: Change SourceFilter to implement MigrateSourceInterface and made a patch from #61.
Comment #64
quietone commentedA reroll.
Comment #65
joachim commentedLGTM
Comment #66
quietone commentedSimple reroll to change order of parameters in assertEquals.
Comment #67
joachim commentedComment #68
scotwith1tI haven't nailed down the "why" behind this, just reporting. I wanted some more details from the migrate_message tables, came across and applied this patch (which applies cleanly to 9.2.3, btw). After applying, a migration that had been running smoothly started throwing the following:
TypeError: Drupal\migrate\MigrateExecutable::doImport(): Argument #1 ($source) must be of type Drupal\migrate\Plugin\MigrateSourceInterface, Drupal\migrate_tools\SourceFilter given, called in /var/www/html/web/core/modules/migrate/src/MigrateExecutable.php on line 214 in Drupal\migrate\MigrateExecutable->doImport() (line 230 of /var/www/html/web/core/modules/migrate/src/MigrateExecutable.php).The quick fix for me was to simply remove the type-hinting for the $source from this new function from the patch
protected function doImport(MigrateSourceInterface $source, array $pipeline)The source, thanks to the new and improved debugging :) seems to be a section in the migration's process section like this
I think, based on the feedback in the error, that it just doesn't handle the possibly empty value being passed around by something like
array_fiilter? Or is there something inherently wrong with the way I've assembled the pipeline for this field and it's just rearing its head because this code is an improvement? All in all, though it caused this error (or caused mine to surface?), the improved logging was both super-helpful while simultaneously enabling me to track down why this new issue was arising. :)Comment #69
quietone commentedtypo
s/give n/given
Comment #70
quietone commentedIt is not as nice but this can be rearranged to not cause problems for MigrateTools.
Comment #71
quietone commentedI was sure I had run commit-code-check.
Comment #72
joachim commentedComment #73
alexpottI think this if is not necessary. And if it is necessary how come? Is a migration with no pipeline valid?
The first param of assertSame() should be the expected message. So this swapping around is incorrect.
Comment #74
quietone commented1. A migration with an empty process pipeline can be created, the process just needs to be an array. For example,
2. Fixed.
Comment #75
joachim commentedComment #76
quietone commentedI was doing a self review here when I noticed an unused variable.
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -196,88 +194,113 @@ public function import() {
+ if ($message = trim($e->getMessage())) {
$message is not used. And further, I could not find a test of the catch block this is in. So, I wrote a test, actually just tagged a bit on to an existing kernel test.
Comment #77
joachim commentedComment #78
alexpottCommitted 8616edd and pushed to 9.3.x. Thanks!