Introduction
In this article, we will take a look at what error monitoring is and its benefits to both you and your system's users. We will then take a look at how to use Honeybadger in your Laravel applications for error monitoring, uptime checking, and queue monitoring.
What is Error Monitoring?
Bugs and Errors in Code
Bugs and errors are an inevitable part of any web application. As web developers, we often make mistakes (big and small) that can go unnoticed during review stages and can often lead to bugs being deployed to live systems. For our users, these bugs can cause confusion and frustration. In the worst-case scenario, if users experience too many errors, they might decide to leave your site/application completely and go elsewhere.
Infrastructure Errors
There can be a lot of moving parts in a web application, depending on the features it provides. For example, your application's infrastructure might consist of a load balancer, multiple application servers, databases, cache servers, and queue worker servers. With all of this infrastructure, many things can go wrong. For example, here are a few different infrastructure-related errors that I've experienced while working with different teams on projects:
- One of your application servers might run out of storage space and stop serving web requests.
- Your hosting provider might experience some downtime, resulting in your databases no longer working.
- Some changes might be made to your network's firewall that prevent connections to your Redis cache server.
- An error within one of your queue servers might cause the queues to stop processing jobs.
As you can see, there's a lot that can go wrong in web applications. Sometimes, the issues might be trivial and can be resolved in a couple of minutes. However, there are other times when the issues take a lot of debugging or troubleshooting to figure out. The most important part of fixing any issue, though, is being notified whenever these errors occur and being given as much information as possible to help fix it. This is where error reporting steps in.
Disadvantages of Human Error Reporting
Generally, there are two ways that you can be informed about errors and bugs in your systems: by humans or by an automated system.
When it comes to relying on the humans using your applications to report errors, there are a few key issues:
- It can be frustrating to users because they have to fill out a support ticket to report the bug.
- They might not give a meaningful bug report that can be used to track down the issue. For example, they might not provide information about the steps taken to reproduce the error.
- They might not even report the bug. This can sometimes be caused by a psychological phenomenon known as the “diffusion of responsibility”, where the user might not submit a bug report because they assume that someone else has already sent one.
- The user might not even realize there's a bug in the system. Therefore, they won’t report it, and you likely won’t find it. Thus, it could stay in your application unnoticed for awhile.
As well as relying on your users, you (as a developer) can perform your own form of human error monitoring. For example, you could periodically check through your server and application logs to see if any errors have occurred. In Laravel, this can be made easier by using packages, such as Laravel Telescope. Nonetheless, it's still a manual and potentially tedious task that you'll need to perform routinely. The main issue that comes with this type of process is that errors can go unnoticed until you choose to check the logs.
So, as you can see, human error reporting has some disadvantages that can cause frustration for your users and yourself. However, although it has flaws, it's important to remember that allowing your users to report errors and submit support tickets is something that your system should always provide. These reports can be useful for picking up 'logic errors' rather than 'runtime errors'. For example, let's say that your system displays all of your user's invoices, but this month's total is wrong. This is a 'logic error' and likely caused by a bug in your code that calculated something wrong. But, because the invoice was generated (albeit with an incorrect value), this likely wouldn't have caused an exception that would have been detected by an automated error reporting system.
Advantages of Automated Error Reporting
As you can see, there are many flaws in relying solely on users to report bugs in your system. So, this is why having an automated error reporting system set up for your application or website is a must-have. It can help you understand any exceptions that your code might be throwing and helps you to check that your infrastructure is working as expected.
When it comes to diagnosing the cause of an error or bug, information is key because the more information you have, the easier it is to resolve the issue. This is true regardless of whether the information is in the form of a stack trace showing the exact lines of code that your application ran before failing or the parameters sent in a web request.
In addition to having extra information, having an automated error reporting system can give you a chance to get ahead of your users. For example, let's imagine that an exception was thrown while trying to load a page. You could be notified instantly and would be able to start an investigation before the user has a chance to report the bug. If they do choose to report it, this can lead to some reassuring and impressive customer service because you'd be able to tell them that you're already aware of the problem and putting a fix in place.
Furthermore, let's imagine that your web application sends a report every morning to your customers via email. To implement a feature like this, you'd likely have some queue workers and scheduled jobs to perform this task. I think you would agree that it would be much better to have an automated system in place to let you know as soon as possible when your scheduler or queue workers stop working. It could give you a chance to rectify the issue so that the reports would continue to be sent out, and the customer would never be aware of the fact that it was ever actually broken.
Error Monitoring Using Honeybadger
One of the error monitoring platforms that we can use is Honeybadger. It's a system that integrates really well with Laravel projects and provides three useful features, which we will cover:
- Error monitoring - This taps into your Laravel application and reports any errors or exceptions thrown by it. These errors are then recorded so that we can go to the Honeybadger site and find information about the error. In this article, we'll cover how to set up the error monitoring and then take a look at the information that is available to us for each type of error.
- Uptime monitoring - This part can send requests to your application to make sure it's still running and reachable by your customers. In this article, we'll cover how to set up uptime monitoring for single-server Laravel applications and applications that are spread across multiple app servers.
- Check-ins - This part is really useful for monitoring your scheduler and queues to ensure that they're still running as expected. In this article, we'll explain how to set up check-ins to make sure that your Laravel queue worker is running.
Installing and Configuring Honeybadger in Laravel
Now that we have explored what error monitoring is and its benefits, we can take a look at how to implement it in our own Laravel application using Honeybadger. To get started, you'll need to head over to the Honeybadger site and sign up.
Once you've signed up, you can run the following command in your Laravel project's root:
1composer require honeybadger-io/honeybadger-laravel
Once the package is installed, you'll need to add the following method to your app/Exceptions/Handler.php
file:
1public function report(\Throwable $exception)2{3 if (app()->bound('honeybadger') && $this->shouldReport($exception)) {4 app('honeybadger')->notify($exception, app('request'));5 }6 7 parent::report($exception);8}
After you've added this, you'll then need to run the following installation command:
1php artisan honeybadger:install YOUR-API-KEY-HERE
Make sure to include your Honeybadger API key when you run this command. After running this command, you should have the following:
- A
HONEYBADGER_API_KEY
field in your.env
and.env.example
file. - A new
config/honeybadger.php
config file. - A test exception that you should be able to see on Honeybadger.
If all of this is completed successfully, it means that you've installed Honeybadger, and you're ready to get started using it.
Error reporting and monitoring bugs
Now that we have Honeybadger set up in your Laravel application, let's take a look at the error reporting side of things.
To get started, you can trigger a test exception (like the installation command did) using the following command:
1php artisan honeybadger:test
After running this command, it will send a test notification to Honeybadger to simulate what would happen if an exception was thrown in a live system. So, if you wanted to SSH into your production server after first adding Honeybadger to your live deployment, you could run this command to make sure it is connected correctly.
However, so that we can dive into the error details a bit more, we'll create a basic bug and cause an exception. For this example, we'll make a request as an authenticated user to a controller method and purposely pass incorrect parameters to a method. To do this, let’s take a look at some controller methods that will always cause an error:
1namespace App\Http\Controllers; 2 3use Illuminate\Http\JsonResponse; 4 5class ErrorController extends Controller 6{ 7 public function __invoke() 8 { 9 $answer = $this->addNumbers(1, 'INVALID');10 11 return response()->json([12 'answer' => $answer13 ]);14 }15 16 private function addNumbers(int $numberOne, int $numberTwo): int17 {18 return $numberOne + $numberTwo;19 }20}
As you can see in the basic example above, we have a method that adds two integers together and then returns the answer in a JSON response. However, we are purposely passing a string to the method to cause a failure.
To call this method, I've created a new route:
1Route::get('error', ErrorController::class);
Now, when we go to the /error
route in our browser, the following exception is thrown:
App\Http\Controllers\ErrorController::addNumbers():
Argument #2 ($numberTwo) must be of type int, string given,
called in /var/www/html/app/Http/Controllers/ErrorController.php on line 11
As you would expect, the error is also logged on Honeybadger for us. Let's take a dive into some information that Honeybadger provides about the exception and how it can be useful for us:
- Backtrace - We can see the backtrace that shows the methods and lines of code that were run before the exception was thrown. This can be particularly useful if you have a method that is called in a lot of different places in your application because you can figure out exactly where it was called from.
-
URL - We can see the URL that the request was made to. In our case, the URL was
http://localhost/error
. - Browser - We can see the browser and operating system that the request was made using. We can also see whether the user was accessing the site via a desktop or mobile device. This is particularly helpful if you are trying to determine whether a bug only happens in a particular browser.
- Breadcrumbs - We can see the important events that led up to the exception being thrown, such as the request being made and any database queries that were executed. The database queries part is particularly useful if you need to identify any errors that you might have in your queries.
- Session & cookie data - We can see any session data or cookies used in the request. This can be particularly useful if you are trying to pinpoint a bug that might have been caused by a value in a cookie that you have set in a previous request.
In addition to being able to see the errors, Honeybadger also allows us to perform a few actions to manage our reported errors:
- Update status - You can toggle the status of your errors between 'unresolved' and 'resolved'. This can be useful because you can toggle an error report to be 'resolved' after you've fixed the bug and deployed your fix to your live system.
- Add tags - You can add tags to your bugs so that you can group them together. For example, let's imagine that you keep getting bugs reported that are related to a particular new feature that you've released. You could tag all the errors related to that particular feature so that they're easy to filter from the rest of your bugs.
- Add comments - You can add comments to your errors. This can be particularly useful if you have a hunch as to what might have caused the issue, so if another team member chooses to fix the bug, they’ll have a bit more information. It can also be useful for review purposes if you review past bugs and want to find out what the issue was and how it was fixed.
- Assign to a team member - If you're working as part of a team, you can assign bugs to other team members. This can be useful because it can make it obvious who is working on a bug and can prevent multiple developers from trying to fix the same issue.
- Create a public URL - You can create a public URL to share the bug. This can be useful if you want to share your error with someone outside your team that might not have access to Honeybadger. For example, you might want to pass the information on to someone who maintains a third-party API or package and want to give as much data as possible about a bug that their API/package is causing.
- Search Stack Overflow - From the error report, you can open up a new tab that will search Stack Overflow for your exception. This can be particularly useful when trying to see if anyone else has experienced the same error before and has found a solution.
Uptime Monitoring
Another powerful feature of Honeybadger is uptime monitoring. This feature allows you to set up an HTTP request that is sent periodically to ensure that your site or application is still running as expected.
Basic Checks
When we set up our check, we need to provide some information, such as the URL for the request to be made to and how often it should be made. To give an idea of how we could set up a check for our website, let's take a look at an example.
Let's imagine that we have a website with the following URL: https://example.com
. It's possible that you might want to make a call to a URL on your website or application and assume that the site is reachable if the page returns a HTTP 200
response. To do this, we could set up a check:
- Name: My homepage check
- URL:
https://example.com
- Match type: HTTP Success
- Request method: GET
- Frequency: Every 5 minutes
As you can likely see, this check will run every 5 minutes and will send a GET
request to https://example.com
. If we get a successful response back, the check will pass. Otherwise, the check will fail, and we'll be notified so that we are aware that the site is down.
Advanced Checks
There might be times when you want to run a bit more than just a basic GET
uptime check. For example, you might want to send a POST
request to a route that triggers off some code and only returns a successful response if the code is executed correctly. The code can do anything, such as adding an item to the cache to make sure it's still reachable or creating a file to make sure your file storage is still writeable. So, you could essentially bundle in other system health checks into this check. If this is the case, you'll likely want to protect the route so that the code can't be triggered inadvertently. To protect the route, Honeybadger provides a Honeybadger-Token
header that is sent in the request. So, you could store the expected token in your .env
file, and then, in your Laravel app's controller, you could assert that the tokens match.
Checks for Applications Running on Multiple Servers
If your Laravel app is running on multiple application servers, and the web traffic is sent through a load balancer, you might want to set up multiple checks. Let's imagine that we have our application hosted at https://example.com
. We'll assume that the site has two application servers with the following URLs: https://app1.example.com
and https://app2.example.com
.
If we were to set up a basic GET
request to https://example.com
, we would be able to visibly confirm whether the site is reachable. However, without adding some extra checks, this wouldn't necessarily tell us that both of our app servers are running. For example, if our load balancer was configured to route traffic to any server that is running and ignore any servers that weren't running, how would we know if one of the servers was down?
One way to tackle this issue is to set up three different uptime monitoring checks:
-
GET
tohttps://example.com
-
GET
tohttps://app1.example.com
-
GET
tohttps://app2.example.com
After running these checks, you'd have more confidence that both of the app servers are running and that the load balancer is routing traffic as expected.
Public Status Page
One of the useful features that Honeybadger provides is public status pages. By enabling public status pages for your projects, you can provide a page that your users can go to and check that your site or application is up and running. If your application is experiencing a bit of downtime, this feature can be a simple way to keep users informed of your site's health for full transparency.
Check-ins
Another powerful feature that Honeybadger offers is check-ins. This feature is extremely useful for helping to ensure that your scheduled tasks and queue workers are still running. For example, let's assume that you have an application that generates and emails a report to your users at the end of each day. To implement a feature like this, you'll likely have a scheduled command that runs at the end of each day and then triggers a queued job that generates and sends the email.
If your queues or scheduler stop running, how would you know? Of course, your users would likely submit a bug report if their emails didn't arrive when expected. However, as we mentioned earlier, this doesn't look great for your customer service or provide any confidence that your system is stable. So, this is where check-ins can be useful.
To get started with check-ins, you'll first need to create one on the Honeybadger website. You can create a check-in and specify how often you expect a check-in. After creating it, you are provided with a small unique code that you can use.
Thanks to the honeybadger-io/honeybadger-laravel
package that we installed earlier, we can use the following handy command to make our check-ins much simpler in our Laravel applications:
1php artisan honeybadger:checkin YOUR-CHECK-IN-CODE-HERE
Let's assume that your check-in's code was ABC123
, you could run the following command:
1php artisan honeybadger:checkin ABC123
So, based on this, if we wanted to test that our scheduler was working, we could add the following block of code to our app/Console/Kernel.php
:
1protected function schedule(Schedule $schedule)2{3 $schedule->command('honeybadger:checkin ABC123')->everyFiveMinutes();4}
If the scheduler is working as expected, this would trigger a check-in every five minutes. If the scheduler stopped running for any reason, we would be alerted if the check-in didn't happen as expected.
You can also create separate check-ins for each of your queue workers. For example, let's assume that we have three different queues: default
, email
, and long-running
.
If we wanted to ensure that each of the queues are running, we could add the following blocks of code to our app/Console/Kernel.php
:
1protected function schedule(Schedule $schedule) 2{ 3 $schedule->call( 4 fn() => Artisan::queue('honeybadger:checkin ABC123')->onQueue('default') 5 )->everyFiveMinutes(); 6 7 $schedule->call( 8 fn() => Artisan::queue('honeybadger:checkin DEF456')->onQueue('sms') 9 )->everyFiveMinutes();10 11 $schedule->call(12 fn() => Artisan::queue('honeybadger:checkin GHI789')->onQueue('long-running')13 )->everyFiveMinutes();14}
Let's take a quick look at the code above so that we can understand what is happening. We are specifying in our scheduler that every five minutes, three different callbacks should be run. Inside each of the callbacks, we queue the honeybadger:checkin
Artisan command, with each callback using a different queue. Therefore, if the command runs successfully and checks in, we can be sure that the queue has active workers running on it and processing jobs.
Bonus tips
Setting the User URL
In your project's settings in Honeybadger, you can set a 'User URL'. To give this a bit of context, let's say that your Laravel applications has an /admin/users/{user}
route that you can go to in your browser and get details about a user. You could set your project's user URL to https://example.com/admin/users/[user_id]
. By doing this, if a user is authenticated when the error is thrown, instead of Honeybadger showing just their ID, you'll be able to click it and go to their page. For example, if the user's ID is 3
, you could click their ID and be taken to https://example.com/admin/users/3
. As you can imagine, this is a handy feature that makes it easier to find information about your users to make the debugging and troubleshooting process quicker.
Opening Your Project Code from Honeybadger
One feature that you can use in Honeybadger is opening your IDE or editor from the backtrace. You can set this up by going to your user settings and adding the local path to your projects. You can then choose which editor to open your project in, such as Atom, Visual Studio Code, or Sublime. Once you've done this, whenever a bug is reported, you'll be able to open the project in your editor on the exact line that the error was caused on.
Filtering Out Sensitive Information
When you're building an application, you might need to comply with some data protection regulations, such as the General Data Protection Regulation (GDPR). Alternatively, you may have some legal obligations set out in contracts with your clients that specify the types of personal information you are permitted to send to external services, such as Honeybadger. If this is the case, you can make use of request filtering to comply with these different rules and regulations. To do this, you can simply add the names of the fields that you want to ignore to your config/honeybadger.php
file.
For example, let's imagine that we don't want to send the email
address field from a request to Honeybadger if an error occurs; we could update it as follows:
1return [ 2 3 // ... 4 5 'request' => [ 6 'filter' => [ 7 'email', 8 ], 9 ],10 11 // ...12 13]
Now, whenever an exception is thrown that is caused by a web request containing an email
field, rather than the field's value being shown in the error report, you will see [REDACTED]
instead.
Conclusion
Hopefully, this article has helped you understand what error reporting is and how you can benefit from using it in your projects. It should have also given you an overview of how to get started with error reporting, uptime monitoring, and queue monitoring using Honeybadger in your Laravel applications. Although this article has covered a lot of the cool features that Honeybadger offers, there's still a lot more that is definitely worth checking out to help with error monitoring, such as integrations with Slack, GitHub, and Trello, as well as SMS and browser alerts and public error dashboards.