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

improve error handling and logging #1311

Merged
merged 7 commits into from
Aug 23, 2024
Merged

Conversation

asolntsev
Copy link
Collaborator

@asolntsev asolntsev commented Jul 21, 2024

This PR doesn't fix #1306, but makes much easier debugging such problems.

@asolntsev asolntsev marked this pull request as draft July 21, 2024 11:44
@asolntsev asolntsev linked an issue Jul 21, 2024 that may be closed by this pull request
@asolntsev asolntsev self-assigned this Jul 21, 2024
Copy link

what-the-diff bot commented Jul 21, 2024

PR Summary

  • Type Modification of REGEXP2SUPPLIER_MAP field in FakeValuesService.java
    The type of this field was upgraded from Map<RegExpContext, Supplier<?>> to Map<RegExpContext, ValueResolver>. This change helps in supporting advanced map operations without the need of external supplier.

  • Addition of New Inner Class SafeFetchResolver
    An inner class SafeFetchResolver was introduced to help with code readability and organization. This should make maintaining the code easier in the future.

  • Method resolveExpression Refactoring
    This method was optimized to utilize the newly created SafeFetchResolver and MethodResolver classes consequently enhancing performance and overall code sophistication.

  • Method resolveFromMethodOn Refactoring
    The return type of resolveFromMethodOn method was adjusted from Supplier<Object> to ValueResolver, followed by refactoring of the method to use the MethodAndCoercedArgsResolver class. These modifications facilitate improved design architecture and cleaner code.

  • Addition of Nested Static Classes
    Three new nested static classes - ConstantResolver, MethodResolver, and MethodAndCoercedArgsResolver were introduced, adding to the structure and robustness of the overall code. This brings added flexibility and allows the application to be more modular in nature.

  • Conversion of RegExpContext Class to Record
    The transition of RegExpContext class to record simplifies code by automatically creating important methods like equals(), hashCode(), and toString(). This ensures more efficiency and consistency of operations.

  • Addition of ValueResolver Interface
    This interface was implemented to facilitate easier value resolution in the code, providing a simplified way to manage value resolution logic.

  • Conversion of Classes to Records
    Numerous classes, including SafeFetchResolver, ConstantResolver, MethodResolver, and MethodAndCoercedArgsResolver were converted to records for simplified handling and to streamline standard java operations, enhancing efficiency, readability, and maintainability.

This collection of changes overall leans towards refactoring the code for improved readability, maintainability, and performance.

Copy link

codecov bot commented Jul 21, 2024

Codecov Report

Attention: Patch coverage is 77.38095% with 19 lines in your changes missing coverage. Please review.

Project coverage is 91.97%. Comparing base (74e02a8) to head (5a81bbf).
Report is 8 commits behind head on main.

Files Patch % Lines
.../java/net/datafaker/service/FakeValuesService.java 81.66% 9 Missing and 2 partials ⚠️
...in/java/net/datafaker/providers/base/Internet.java 71.42% 4 Missing ⚠️
...rc/main/java/net/datafaker/service/FakeValues.java 0.00% 2 Missing ⚠️
.../main/java/net/datafaker/providers/base/Image.java 0.00% 1 Missing ⚠️
...tafaker/transformations/JavaObjectTransformer.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1311      +/-   ##
============================================
+ Coverage     91.82%   91.97%   +0.14%     
+ Complexity     3159     3152       -7     
============================================
  Files           316      316              
  Lines          6229     6232       +3     
  Branches        633      634       +1     
============================================
+ Hits           5720     5732      +12     
+ Misses          353      338      -15     
- Partials        156      162       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@asolntsev asolntsev force-pushed the chore/1306-improve-logging branch 2 times, most recently from 604b3b3 to 58aa282 Compare July 21, 2024 15:26
@asolntsev asolntsev marked this pull request as ready for review July 21, 2024 15:32
@asolntsev asolntsev changed the title improve logging (useful for debugging flaky tests etc.) improve error handling and logging Jul 21, 2024
@@ -400,4 +400,9 @@ public final <B extends ProviderRegistration> B getFaker() {
public static Method getMethod(AbstractProvider<?> ap, String methodName) {
return ap == null ? null : ObjectMethods.getMethodByName(ap, methodName);
}

@Override
public String toString() {
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this the default toString?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Almost. Except that it's shorter due to getSimpleName instead of getName.
I would like to make it even more shorter, but it doesn't have any human-readable fields...

Copy link
Contributor

Choose a reason for hiding this comment

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

but why do you need it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In debug logs.
To make output of such logs a bit better readable (than the default):

    LOG.fine(() -> "resExp(%s [%s]) current: %s, root: %s, context: %s, regExpContext: %s -> res: %s".formatted(directive, Arrays.toString(args), current, root, context, regExpContext, res));

@@ -158,8 +158,7 @@ private String generateBase64RasterImage(ImageType imageType, int width, int hei
byte[] imageBytes = baos.toByteArray();
return "data:" + imageType.mimeType + ";base64," + Base64.getEncoder().encodeToString(imageBytes);
} catch (IOException e) {
e.printStackTrace();
return null;
throw new RuntimeException("Failed to generate image %s of size %sx%s".formatted(imageType, width, height), e);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see why you'd want to throw an exception here.

Copy link
Collaborator Author

@asolntsev asolntsev Jul 22, 2024

Choose a reason for hiding this comment

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

Because it's much better to clearly show the problem instead of hiding it.

  • When you return null, it almost always causes NullPointerException in the next step. And user will not understand what happened. For example, in DF own test:
        assertThat(faker.image().base64TIFF()).startsWith("data:image/tiff;base64,");
  • If user wants to protect, he will need to write IF:
String image = faker.image().base64TIFF();
if (image == null) {
  // WHAT SHOULD I DO HERE?..
  throw new RuntimeException("WTF, the image is null, and I have no idea why.");
}

Here is more about "Throw Early, Catch Late" Principle

Copy link
Contributor

@bodiam bodiam Jul 22, 2024

Choose a reason for hiding this comment

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

How is writing that if(null) check different from:

try {
   String image = faker.image().base64TIFF();
} catch(Exception e) {
  throw new RuntimeException("WTF, the image generation failed");
}

I don't mind throwing exceptions early if the code is in my control, but this is a library, and how would a user know this code can throw an exception?

The implicit contract in DF is that none of the methods will ever throw an exception,this changes that approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It differs a lot.

  • User doesn't need to check if image is null.
  • User doesn't need to pollute his code with unneeded IFs and try/catches.
  • User will see a clear error message explaining WHY the image could not be generated.

The implicit contract in DF is that none of the methods will ever throw an exception

It sounds very strange to me.
Nobody can ever guarantee that his code never throws any exceptions.

Anyway, in this case, such an exception actually cannot happen. We need to catch IOException only because java interfaces like InputStream declare throws IOException. But in this case we operate with ByteArrayOutputStream which never throws IOException.

} catch (UnknownHostException e) {
return "127.0.0.1";
}
return getIpV4Address().getHostAddress();
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is ideal, I believe the code can throw exceptions now sometimes, and I don't think we should.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I also had a doubt, but no, this code doesn't fail.

This is a test that tries through all possible values:

    @Test
    void allPossibleAddresses() {
        for (int first = 2; first < 256; first++) {
            for (int second = 2; second < 256; second++) {
                for (int third = 2; third < 256; third++) {
                    for (int fourth = 2; fourth < 256; fourth++) {
                        InetAddress inetAddress = inet4Address((byte) first, (byte) second, (byte) third, (byte) fourth);
                        assertThat(inetAddress).isNotNull();
                    }
                }
            }
        }
    }

} catch (UnknownHostException e) {
return "127.0.0.1";
}
return getPrivateIpV4Address().getHostAddress();
Copy link
Contributor

Choose a reason for hiding this comment

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

again, I don't think this is correct.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same as above: it's correct :)

} catch (UnknownHostException e) {
return "0:0:0:0:0:0:0:1";
}
return getIpV6Address().getHostAddress();
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same as above. :)

@asolntsev asolntsev requested a review from bodiam July 22, 2024 09:33
@asolntsev
Copy link
Collaborator Author

@bodiam Let me summarize: I am pretty sure it's a good PR and should be merged. ;)

@bodiam
Copy link
Contributor

bodiam commented Aug 22, 2024

Then feel free to merge it! :)

…olver)

it allows to log what objects were returned when debugging flaky test failures.
... to make it easier to read these values in logs
... especially with FINE level (== DEBUG).

This pattern causes flaky failures that are very hard to debug.
If something went wrong, it's always safer to throw it with a clear description of what happened.

See "Throw early, catch late" principle.
…fore()

It was WTF moment when I realized that method BaseFakerTest.before() resets all logger levels to INFO.
It took few hours to find out why my logging configuration doesn't work. :(
* by default, FINE logs are NOT written to console, BUT
* if some test failed, all its FINE logs are written to console.

Thus we can investigate flaky tests.
there is no need to initialize static Faker's vars in some specific way. They should stably work in any order.
@asolntsev asolntsev force-pushed the chore/1306-improve-logging branch from b01d682 to 5a81bbf Compare August 23, 2024 06:35
@asolntsev asolntsev merged commit 1f346c7 into main Aug 23, 2024
11 of 12 checks passed
@asolntsev asolntsev deleted the chore/1306-improve-logging branch August 23, 2024 08:00
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.

Unable to resolve expression during build
2 participants