Dashboard for tracking Errors in Anvil App

I always struggled with various runtime errors that users faced on my anvil apps. And there was no easy way for me to track them. So I decided to create a dashboard that allows better tracking and collaboration on errors.

And the results were great. So far, I have already discovered and fixed many errors that had been around since god knows when. Hence, I am making this available for community use.

Clone Links -
Dependency -Anvil | Login

Dashboard - Anvil | Login

You can also clone the preview app to see how it works (Some features are missing here)
Anvil | Login

Features

  • Group same errors, merge similar errors and maintain a timeline of any updates and occurrences for it.
  • Mark any error as fixed or ignore the ones that don’t need to be fixed.
  • See details for any error including the detailed traceback, users facing it, device details for users, link to session logs, etc.
  • Keep a track of errors that were previously fixed but had reappeared.
  • Filter, search and sort errors. You can also search by email to see errors faced by a user
  • Allows multiple users to collaborate

How to use

  1. Clone the Dependency and the Dashboard

  2. Add the Dependency to the App you want to use it for.

  3. Add the following existing tables from the Dashboard to your App:’

    • app_data
    • errors
    • timeline

Since the dashboard is an anvil app itself, you can publish it at any URL you want and share it with other users too.

How to collect errors

In your main app, you can call the following function to collect error

from Error_Tracker.error_tracker import collect_error

try: 
   #Something
except Exception as e:
   collect_error(e)

Or you can attach it to your Error Handler

def error_handler(err):
   collect_error(err)

set_default_error_handling(error_handler)

You can also collect the device and browser details or any custom data you require.

collect_err(err, collect_device_info = True)

#Or collect any custom data

collect_err(err, url_hash = get_url_hash(), form = get_open_form())

Note: By default, it will not collect any errors in debug environment

Managing Users

By default, any logged out user will not be able to access the dashboard. Any new accounts that are created also need to be enabled by the app owner You can enable accounts by checking the enabled column of the user.

Let me know if you face any issues or need any help

8 Likes

This is the sort of thing that every eventually has to build into their Anvil app, and having a base level of it ready to use is super helpful.

2 Likes

@divyeshlakhotia amazing work! Everything is polished to the standard of an extension that I would easily pay for. Many thanks for sharing this with the community.

I have a question that I suspect I already have the answer to, but for the sake of being sure I’ll ask it anyway :sweat_smile:.

Does the error tracking work for errors on the server side as well as the client side? Or is the idea that an exception raised in the server code will be sent to the client anyway, so everything can be logged from there?

And a follow-up to my question: is there anything in the dashboard that indicates whether the error happened on the client code or server code? Although I suppose looking at the traceback would normally answer that question.

Actually, I hadn’t thought much about the server side while making this dependency. However, since the errors are returned back to the client anyway, the error tracker should track them too. But not very well. It appears that all errors sent from the server start with "AnvilWrappedError: " and they don’t come with tracebacks (But you can see the traceback when seeing the App logs for that session).

But I suppose I can probably deal with server side errors better. Will try coming up with solutions for that.

1 Like

@divyeshlakhotia thanks for quick reply :slight_smile:

No pressure, I was just asking. I might also tinker around with it and if I make any meaningful updates I’ll share them with you :slight_smile:

1 Like

Sure. FYI, you might want to check out sys.excepthook on server. It should globally catch errors. Not tested on Anvil server yet though

2 Likes

Hey @divyeshlakhotia

I added some things to the error tracker. You can check if it would be useful for you :slight_smile: (and also for others of course). Here are the clone links:

Server-side error handling

I added logging for errors on the server. For this I use a modified decorator that you can use in place of @anvil.server.callable for your callable server functions:

def secure_callable(func):
    @anvil.server.callable
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            exception_traceback = traceback.format_exc()
            # log the error and traceback to the console
            errorLogger.error(f"{type(e).__name__}: {str(e)}")
            errorLogger.error(f"{exception_traceback}")
            # record the error
            collect_error_server(e, origin='server', traceback=reformat_traceback(exception_traceback))
            # raise an exception with no server code information
            sys.tracebacklimit = 1 # to remove the traceback before sending back to the client (keep at 1, the main app crashes at 0 for some reason)
            if occurrence_id:
                server_error = Exception("An unexpected server error occurred\n" + f"(id: {occurrence_id})")
            else:
                server_error = Exception("An unexpected server error occurred.")
            raise server_error
    return wrapper

The decorator has built-in exception handling such that errors are logged on the server and recorded in the Error table, while a generic exception is sent back to the client without any traceback information (preventing leaking your server-side code). I also added Client/Server labels and filtering in the dashboard based on the new column origin in the Error table.

Email alerts

I also added the option of sending an email notification. You can specify the environments for which you want email alerts to be sent via the monitored_enviroments variable in utils.py.

How to use

For everyone who would like to use this dashboard, here are the steps:

  1. Clone the dashboard and the error_tracking_dependency

  2. Add the error_tracking_dependency as a dependency to your app

  3. Add the following existing tables from the Dashboard to your App

    • app_data
    • errors
    • timeline
  4. In utils.py of the error_tracking_dependency, specify the monitored_environments, app_name and email_address for receiving email alerts

  5. Replace your @anvil.server.callable decorators with the @secure_callable decorator which you can import from error_tracker_dependency.Error_Tracker_Server

  6. Set up a default error handler in your startup module which can be as minimal as:

from anvil import set_default_error_handling
from error_tracker_dependency.error_tracker import collect_error

def error_handler(err):
   collect_error(err)

set_default_error_handling(error_handler)

Remarks

Note 1: The error_tracker_dependency supposes that you have anvil_extras as a dependency in your main app. I haven’t added anvil_extras as dependency to error_tracker_dependency itself to avoid dependency conflicts. But if you do not want to have anvil_extras as a dependency you can replace the errorLogger with normal print statements and replace call_async with anvil.server.call in error_tracker.py.

Note 2: It’s not going to be completely backwards compatible with your old errors as I moved the traceback formatting from the dashboard to the dependency (before they’re saved to the Error table) to account for the different processing required for client and server errors. The traceback column is now a string containing the already formatted errors ready for html rendering.

If you notice any things that I’ve overseen or that could be done cleaner please let me know! I avoided touching the original code as much as possible but I admit that I introduced some inconsistencies - e.g., log_error is now called both through the http endpoint and also call_async depending on situation (because I’m more comfortable using call_async but I didn’t touch your http endpoint as I figured you might be using it for a reason).

1 Like

Thanks a lot for the awesome contributions. Will love to have a look at that. Also, I have been thinking about make this app a GitHub repo. That way, users can just add the app to the editor by pasting the GitHub url. Live updates can also be sent to them without requiring to clone the app again. And contributions like yours can also be welcomed.

That said, I would take a look at how practical this workflow and if there could be issues arising from it.

2 Likes