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

Add helper to report lazy loading violations #678

Merged
merged 3 commits into from
May 2, 2023

Conversation

stayallive
Copy link
Collaborator

@stayallive stayallive commented Apr 8, 2023

In Laravel you can instruct Eloquent to report whenever you forget to eager load a relation (causing N+1 problems) and you can register a callback to report those exceptions in production instead of throwing an exception breaking your application. Since a lazy loading violation is a performance problem and not a app-breaking problem it is common to forward these to the logs and Sentry by doing the following:

Model::preventLazyLoading();

// In production we just report the lazy loading violation instead of crashing the application since it's a performance issue not a security issue
if (app()->isProduction()) {
    Model::handleLazyLoadingViolationUsing(
        static fn (Model $model, string $relation) => report(new LazyLoadingViolationException($model, $relation)),
    );
}

See Laravel docs: https://laravel.com/docs/10.x/eloquent-relationships#preventing-lazy-loading.

This uses the report helper to report the exception to the framework logging it to the log files and to Sentry (if installed).

I propose a new helper that allows you to do the following:

Model::preventLazyLoading();

// In production we just report the lazy loading violation instead of crashing the application since it's a performance issue not a security issue
if (app()->isProduction()) {
    Model::handleLazyLoadingViolationUsing(
        \Sentry\Laravel\Integration::lazyLoadingViolationReporter(),
    );
}

This is a little syntax sugar over the manual method and also adds a "context" with information about the violation (even though all that information is already available to the user via the exception message and the shown backtrace) but also set's the exception as handled and set's the level to warning.

There is a notable upside to this, and that is that since the exception is created in our vendored code it is marked as not in-app and the stacktrace therefore shows the correct line where the violation occured from instead of the users service provider where the violation would otherwise most likely be reported from.

We could simplify this even more but I am concerned that that would cause undefined behaviour since I don't find it clear that when you'd call something like Integration::reportLazyLoadingViolations() that we'd only do that in production (what about other env's like staging?) and such so I opted to go for the more explicit route.

@stayallive stayallive requested a review from cleptric April 8, 2023 10:28
@stayallive stayallive force-pushed the lazy-loading-violations branch 2 times, most recently from b492337 to e529eeb Compare April 8, 2023 10:45
@stayallive stayallive marked this pull request as ready for review May 1, 2023 05:52
@cleptric cleptric merged commit 04bad6b into master May 2, 2023
@cleptric cleptric deleted the lazy-loading-violations branch May 2, 2023 09:01
@devfrey
Copy link

devfrey commented May 15, 2023

Laravel performs two additional checks before throwing the LazyLoadingViolationException:
https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L559-L561

Wouldn't it make sense to add these same checks to Sentry's lazyLoadingViolationReporter()?

@stayallive
Copy link
Collaborator Author

Well... yes 😅 I think I misread some things where I though the callback would only be called after those checks, interesting... anyway good call!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants