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

Handle async routes in the Mojolicious plugin #28

Open
rabbiveesh opened this issue Nov 18, 2024 · 1 comment
Open

Handle async routes in the Mojolicious plugin #28

rabbiveesh opened this issue Nov 18, 2024 · 1 comment

Comments

@rabbiveesh
Copy link
Contributor

Because the sentry integrations are global, they don't play nicely out of the box with async; in my project at work i've written my own sentry plugin around this one that uses Syntax::Keyword::Dynamically and Future::AsyncAwait to solve this, relevant bits below -

  $app->hook(
    around_action => sub ($next, $c, $action, $last) {
      my $req = $c->req;

      my $scope       = $c->stash('_sentry_scope');
      my $transaction = $c->stash('_sentry_transaction');
      unless ($scope) {

        # eliminate any non-request scopes, b/c memory leaks for the lose
        Sentry::Hub->get_current_hub->reset;
        Sentry::SDK->configure_scope(sub ($new_scope) {
          my $transaction = Sentry::SDK->start_transaction({
            name    => $c->match->endpoint->to_string // '/',
            op      => 'http.server',
            request => {
              url     => $req->url->to_abs->to_string,
              method  => $req->method,
              query   => $req->url->query->to_hash,
              headers => { pairgrep { $a ne 'Cookie' } $req->headers->to_hash->%* }
            },
            $self->continue_from_headers($req->headers)->%*
          });
          $new_scope->set_span($transaction);

          $c->stash(_sentry_scope       => $scope = $new_scope);
          $c->stash(_sentry_transaction => $transaction);
        });
      }

      # save our scope for this dynamic context
      dynamically Sentry::Hub->get_current_hub->{scopes} = [$scope];

      # turn on breadcrumbs for this dynamic context
      dynamically Sentry::Integration->get_integration('DBI')->{breadcrumbs}           = 1;
      dynamically Sentry::Integration->get_integration('MojoUserAgent')->{breadcrumbs} = 1;

      return $next->() if $c->tx->is_websocket;

      # always returns a promise
      $self->handle_action($c, $next, $last);

      # break the dispatch chain, but the action handler may actually continue it
      return $last;
    }
  );

and the call to handle_action is

async sub handle_action ($self, $c, $next_hook, $last) {
  # we must hold onto the $tx here, b/c the req may get cancelled while await-ing
  my $tx;
  try {
    $tx = $c->render_later->tx;
    my $fin = $next_hook->();
    $fin = await $fin if blessed $fin and $fin->can('AWAIT_IS_READY');

    # we need to delay to avoid an infinite loop
    Mojo::IOLoop->next_tick(sub {
      $c->continue if $fin && !$last;
    });
  } catch ($err) {
    $c->reply->exception($err);
    Sentry::SDK->capture_exception($err) if $c->res->is_server_error;
  } finally {
    if ($last) {
      my $transaction = $c->stash('_sentry_transaction');
      if (defined $c->stash('sentry_tracing')) {
        $transaction->sampled($c->stash('sentry_tracing'));
      }
      $transaction->set_http_status($c->res->code) if $c->res->code;
      $transaction->finish();
    }
  }
}

Would be happy to open a PR, just wanna know if the dependencies are acceptable for the scope of this project.

@pmb0
Copy link
Member

pmb0 commented Nov 18, 2024

Hi, I've never used async in Perl (and never will in the future) -- if your changes don't cause problems with the blocking variant, I'd merge them.

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

No branches or pull requests

2 participants