Logging in Laravel
One of the more challenging aspects of being a professional programmer is spending the majority of the day immersed in code, trying to maintain a mental construct of what I’m building.
As the projects I work on have grown, I realized that it’s impossible for me to grasp all of what is going on in the application at once. I don’t have enough RAM!
As a result, I’ve been forced to leave myself breadcrumbs to keep me oriented. One of those breadcrumbs that has proven immensely helpful is logging. As a younger programmer I failed to realize the benefit logging offers but these days I’m relying on it more consistently.
Why implement logging?
To help you learn more about what's happening within your application, Laravel provides robust logging services that allow you to log messages to files, the system error log, and even to Slack to notify your entire team.
In PHP, when a request arrives, the application boots up, responds accordingly, and then shuts down. Even though, as the architect, I’m aware of how data moves through the application, the entire process is ephemeral. Sure, there may be side effects, like interacting with a database, or consuming system resources, but it is possible that the request is handled without much evidence it was ever there in the first place.
Logging provides a way to peek inside the application and drop some pins to help maintain our bearings. Here are a few markers that might be helpful:
- Track who took what action when
- Raise an alert when something bad happens
- Keep a history of how an item changes over time
- Debug messages that can be toggled on/off
Can you see how these waypoints provide insight into what is going on inside the application?
Now that we have identified a few logging points, let’s talk about the different types of logging.
Types of logging
System logs
Whether you know it or not, logging is taking place whenever you interact with an application, albeit at the lower levels of infrastructure. For example, nginx is capable of maintaining both error and access logs which can be helpful if we need to troubleshoot at that level. PHP generally writes log files and this is true of many services used to power an application (database, redis, cache).
It’s helpful to know these system-level logs exist, and how to view them in case you need to, but let’s move up one level and talk about logging at the application level.
Application logs
Laravel provides application-level logging out of the box, which handles things like writing exceptions to log files (or other output, depending on configuration), and for most situations this can operate on its own. During development, the application logs are helpful to see why something is throwing a 500 exception or inspecting an errant SQL query.
Tools are available that can automatically write logs for events on the application level. Eloquent models announce their changes to the framework and these tools pick up those changes and store them for future use.
Activity logs
Moving up one more level, we have a type of logging referred to as “activity logging”. In this kind of logging, developers raise flags at strategic points in the application, such as:
Authentication
- User registration
- User logs in
- User changes important information on their profile
Significant events
- User modifies a resource (especially deletes)
- User adds an item to a shopping cart
- User invites another user to a team
Exceptional behaviors
- User deletes their account
- User attempts to join a team they are not invited to
- User requests multiple password resets
When possible it is good to include additional metadata with these logs such as IP address, user id, and user agent.* These values will help when you are asked “Who deleted the important thing?!”
*A note on security
Regions have different laws on what can be tracked or logged so be sure to comply with anything that would apply to your application. Beyond the applicable laws, it’s important to not let any sensitive information make it to the logs.
Log output
As with most techniques in programming, there are myriad ways to output a log stream. Choose the one that makes the most sense with your system. Important logs should be treated as such and reside in a secure location, such as a database or third-party vendor.
If you choose to log to files with Laravel, make sure it is configured to use an incremental file system, otherwise, you run the risk of generating one massive file that contains all the logs.
Because there are so many outputs available we can choose to deliver more important messages on channels that have a higher visibility. We could deliver all messages of a particular type to a Slack channel, via email, or even SMS.
Severity levels
Delineating between the different levels of importance can be done using the eight levels of severity defined in the syslog specification:
DEBUG (100): Detailed debug information.
INFO (200): Interesting events. Examples: User logs in, SQL logs.
NOTICE (250): Normal but significant events.
WARNING (300): Exceptional occurrences that are not errors. Examples: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
ERROR (400): Runtime errors that do not require immediate action but should typically be logged and monitored.
CRITICAL (500): Critical conditions. Example: Application component unavailable, unexpected exception.
ALERT (550): Action must be taken immediately. Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
EMERGENCY (600): Emergency: system is unusable.
These descriptions come from the Monolog documentation, which is what powers Laravel’s logging tools.
Preferred Implementation
I have landed on a combination of the built-in tools provided by Laravel, a third-party package, and some custom code.
Inconsequential logs
Anything that is not very important, but still log-worthy is sent to a daily log file using the Log
facade.
Log::debug('CSV import started at ' . now());
Log::warning('Could not match a record from the import. Skipping', $record->toArray());
Log::info('CSV import has finished successfully');
Long-term logs
Activity that is more important uses a pairing of Spatie’s Activity Logger and custom LogEvent
classes to persist to a database.
// ApplicationTypeLog Enum
enum ApplicationTypeLog implements LogType
{
case created;
case updated;
case deleted;
private function prefix(string $suffix): string
{
return 'admin.application_type.'.$suffix;
}
public function type(): string
{
return match ($this) {
self::created => $this->prefix('created'),
self::updated => $this->prefix('updated'),
self::deleted => $this->prefix('deleted'),
};
}
public function message(): string
{
return match ($this) {
self::created => 'Admin created application type',
self::updated => 'Admin updated application type',
self::deleted => 'Admin deleted application type',
};
}
}
// ApplicationTypeUpdated Log Event
class ApplicationTypeUpdated implements LogEvent
{
public function __construct(
public ApplicationType $applicationType,
public array $before,
) {
}
public function log(): void
{
activity()
->on($this->applicationType)
->withProperties([
'before' => $this->before,
'after' => $this->applicationType->toArray(),
])
->event(ApplicationTypeLog::updated->type())
->log(ApplicationTypeLog::updated->message());
}
}
// ApplicationTypeController
$before = $applicationType->toArray();
$applicationType->update(request()->all());
Logger::log(new ApplicationTypeUpdated(
$applicationType,
$before
));
This class-based method allows me to colocate all the LogEvent
implementations in a single directory. As a result all the activity logs are in one place instead of having them buried in the codebase.
A second benefit is that by implementing the EventLog
contract we can enforce consistency to the additional properties saved to the log database.
Lastly, because we have consistent types on the messages, and they are stored in a database, we can do things like display a list of security events to the admins of the site.
In conclusion
Descriptive logging, on all three levels, will provide a more colorful view into an application as well as a history of important events. Use the log tools available as more than just exception handling or simple debugging and your application will benefit from the new insights.
Want to read more tips and insights on working with a Laravel development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.
By Jesse Schutt
Director of Engineering
Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.