rewrite icon indicating copy to clipboard operation
rewrite copied to clipboard

Add source snippet to exception stack trace for easier debugging

Open Bananeweizen opened this issue 11 months ago • 6 comments

What problem are you trying to solve?

When a recipe throws an exception, only the file name of the source file is shown. Quite often that makes guessing the affected source snippet basically impossible, at least that's my experience from previous bug reports. Therefore adding either a short source snippet or the position in the file (line and row, or offset) would really help debugging recipe exceptions.

Describe the solution you'd like

In a prototype, I just added cursor.toString() to the log output that prints the exception stack trace. While that produced far too much output, it helped me identify the relevant source code in many cases, as this produces a kind of "nested snippets" with the uppermost being the relevant piece.

You surely have better ideas how to do that.

Are you interested in contributing this feature to OpenRewrite?

You probably can do that better than me.

Bananeweizen avatar Jan 10 '25 08:01 Bananeweizen

This issue is stale because it has not had any activity for 60 days. Remove question label or comment or this will be closed in two weeks. Issues may be reopened when there is renewed interest.

github-actions[bot] avatar Jul 28 '25 11:07 github-actions[bot]

Thanks for raising this issue @Bananeweizen !

@timtebeek Just to clarify - is this about recipe execution errors (not parsing errors)?

If so, I think we could add a snippet field to SourcesFileErrors.Row and implement the extraction logic in RecipeRunCycle.handleError(), following the same pattern as ParseFailures.

Would this address what you're looking for?

yybmion avatar Nov 20 '25 16:11 yybmion

hi! Yes this appears to be about unexpected exceptions during recipe execution; linking the code sample you've referenced: https://github.com/openrewrite/rewrite/blob/8ce5308a9557dd6ca91253a5c8fb7e6c34a0ef5a/rewrite-core/src/main/java/org/openrewrite/scheduling/RecipeRunCycle.java#L289-L307

How did you plan to access the relevant snippet only for the cursor position?

timtebeek avatar Nov 25 '25 18:11 timtebeek

Thanks for the response @timtebeek! Here's my approach

Extract line:column from Range marker

I'll use the existing Range marker to extract position information from the cursor where the error occurred:

private @Nullable String extractLocation(RecipeRunException rre) {
    Cursor cursor = rre.getCursor();
    if (cursor == null) return null;
    
    Object value = cursor.getValue();
    if (!(value instanceof Tree)) return null;
    
    // Extract from Range marker
    Optional<Range> range = ((Tree) value).getMarkers().findFirst(Range.class);
    if (range.isPresent()) {
        Range.Position start = range.get().getStart();
        return String.format(":%d:%d", start.getLine(), start.getColumn());
    }
    
    return null;
}

Then append this to sourcePath in SourcesFileErrors.Row:

UserService.java:245:12  (instead of just UserService.java)

Benefits:

  • Minimal output (avoids "too much output")
  • IDE-clickable format
  • Standard Java stack trace style

Questions: Is Range marker always present on Tree nodes during recipe execution?

Thanks!

yybmion avatar Nov 26 '25 15:11 yybmion

Thanks! That's my first time seeing Range. From what I can find it's only available for Java files, which would leave quite a bit of code not covered (Yaml, Xml, Properties, ....). Wondering if there's a way to include more details there too, but with my schedule it could be a week+ before I find time to dive in myself.

timtebeek avatar Nov 26 '25 15:11 timtebeek

Wondering if there's a way to include more details there too, but with my schedule it could be a week+ before I find time to dive in myself.

Totally understand @timtebeek! I took a quick look at a few things to help move this forward.

I checked the codebase and found that all file types (.Java, XML, YAML, Properties) use the Markers interface through the Tree interface.

Proposed solution:

private @Nullable String extractLocation(RecipeRunException rre) {
    Cursor cursor = rre.getCursor();
    if (cursor == null) return null;
    
    Object value = cursor.getValue();
    if (!(value instanceof Tree)) return null;
    
    Tree tree = (Tree) value;
    
    // Try Range marker first (works if present)
    Optional<Range> range = tree.getMarkers().findFirst(Range.class);
    if (range.isPresent()) {
        Range.Position start = range.get().getStart();
        return String.format(":%d:%d", start.getLine(), start.getColumn());
    }
    
    // Fallback: short snippet if no Range
    String snippet = tree.printTrimmed(cursor.getParentTreeCursor());
    if (snippet != null && snippet.length() > 50) {
        return " [" + snippet.substring(0, 50) + "...]";
    }
    
    return null;
}

Result:

  • Java files: UserService.java:245:12 (if Range exists)
  • XML/YAML/Properties: Either :line:col (if Range exists) OR [<dependency>...] (snippet fallback)

I think this covers all file types with a single unified method. The fallback ensures we always provide some location context, even if Range markers aren't universally added by all parsers.

Does this approach (Range first, snippet fallback) sound reasonable?

yybmion avatar Nov 26 '25 16:11 yybmion