One of the more complex topics that often receives less attention in software development is the proper use of logs. I would venture to say that I have seen as many variations as there are companies, and within companies, as many as there are teams, and within teams, as many as there are projects. This is because, when it comes to adding logs, there seem to be as many styles as there are developers. In an area where personal style and objective perception significantly influence how standards are applied, it becomes challenging to define strategies that facilitate subsequent monitoring and debugging. After all, talking about logs is talking about maintainability. And we all know that maintaining our code is as important, if not more so, than writing it in the first place.
As a confessed poor user of logs and their levels, I have taken some time to review certain concepts and try to create a brief guide primarily focused on Laravel, but applicable to other contexts.
“The process of logging is often the first step in understanding what’s happening in your code. It provides a record that helps you diagnose problems and improve your system.”
Logs, a brief intro
When we talk about logs, we refer to the records that our applications generate to document events and activities. They are like a diary or ledger for our application, where both successes and setbacks are noted. These records are essential because they help us understand the behavior of the application, identify problems, and ultimately improve the user experience.
You can implement your own logging system, from something as simple as appending messages to the end of a text file to creating records in a database or sending messages to a tool like Slack. Depending on your needs, the strategy can range from a simple catch-all log to an organized table where you can perform complex searches, or even an alert system that sends you instant notifications about the most important events in the system. All of this becomes much simpler and more escalable if you use well-established protocols and specialized tools.
Syslog is a standard protocol that allows systems to send log messages to a centralized server. Why is this useful? Because it helps us keep everything in one place, making it easier to monitor and analyze logs from multiple applications and servers. It’s like having an organized library where you can quickly and efficiently find the information you need. Using a series of severity levels from 0 to 7, it becomes very straightforward to classify messages.
In Laravel, logging is handled by the Monolog library, which provides a powerful and flexible way to record application events and errors. By default, Laravel is configured to write logs to a single file or a daily log file in the storage/logs
directory, but it supports many other logging channels, such as database logs, emails, and third-party services.
Using the Log
facade, you can easily log messages with different severity levels (like emergency
, alert
, critical
, error
, warning
, notice
, info
, and debug
) to keep track of various events in your application. This makes debugging and monitoring smoother, as you can adjust log levels and formats based on your needs, whether you’re in a development or production environment. Laravel also supports logging stacks, which lets you combine multiple log channels for more robust logging setups.
https://laravel.com/docs/11.x/logging
In this article, we’ll focus on reviewing log levels, understanding the purpose of each one, and exploring a practical example with Laravel. Log messages are a form of communication, and the choice of severity level reflects the intent behind each entry, making both search and code maintenance easier. For example, you’ll want to set up notifications to alert you if the system goes down, but you wouldn’t want to be woken up at 3 a.m. if a user enters an unusual character in a registration form. Similarly, you’ll want to navigate errors easily without normal or minor events cluttering the log.
When reviewing code, or revisiting your own work months later, you’ll want to quickly recognize whether a log is a forgotten debug line or an important warning you need to retain in production. Most tools won’t alert you if you systematically use Log::info
for debugging or logging errors, and few teammates will risk being seen as overly picky if they call it out in a pull request. So, it’s a good idea to invest a bit of time in choosing log levels wisely.
Choosing the right log level
0 Emergency: system is unusable
If your application depends on a service and that service fails, such as a database whose cluster is completely down, you are facing an emergency. You should consider as emergencies those failures in critical components that become unusable and require immediate attention. When monitoring your system in production, it is essential to have immediate visibility of this type of message, whether through alerts via an external service or a custom notification configuration, such as Slack, email, among others.
Log::emergency("Database cluster down. Application cannot function without it.");
Example
Connection check: Attempt to obtain the PDO connection to the database. If this fails, it means that the database cluster is down.
Emergency logging: If an exception occurs, an emergency message is logged with additional details, such as the error message and the timestamp.
Additional actions: Further actions can be implemented, such as notifying administrators or sending alerts through other services, although the implementation of those actions is not shown in this example.
Reserve this level for events that require an immediate response to restore the application’s availability. Avoid using it excessively, as it could lead to “alert fatigue.”
1 Alert: action must be taken immediately
A level below an emergency is a failure that requires immediate action but does not render the system completely unusable. This type of error includes, for example, the loss of critical configurations that affect essential integrations, such as a payment provider, blocking only part of the application.
Log::alert("Missing API key for payment provider. Transactions cannot be processed.");
Example
API key validation: The payment provider’s API key is checked to see if it is configured in Laravel’s configuration files. If it is empty, it is considered a critical issue.
Alert logging: If the API key is missing, an alert message is logged along with the payment data that was being processed and the current timestamp.
Error handling: An exception can be thrown to stop processing and notify other parts of the system that the payment process has failed due to the missing API key. This allows for appropriate actions to be taken, such as displaying a message to the user.
Use the alert level when a quick human intervention is needed to prevent system disruption.
2 Critical: critical conditions
Critical errors do not require an immediate response, but they can lead to serious problems if left unresolved. For example, an error in the payment gateway during peak hours.
Log::critical("Payment gateway timeout during checkout for order ID: {$order->id}");
Example
Payment processing: An attempt is made to process the payment for a specific order. If an error occurs, an exception is thrown.
Exception handling: In the catch block, it is checked whether the exception is related to a timeout from the payment gateway.
Critical logging: If a timeout is detected, a critical message is logged with the order ID and the error message. This indicates that it is a serious issue that requires immediate attention.
Logging other errors: If a different exception is thrown, it can be logged as a normal error or an alert, as appropriate. This allows for better visibility of what happens during the payment process.
Use the critical level for serious errors that affect the business but do not require an immediate fix, allowing for a scheduled intervention.
3 Error: error conditions
A failure in a part of the application that does not prevent the system from functioning. For example, when a user registration fails due to validation errors or violation of database constraints.
Log::error("User registration failed due to validation error.", ['user_data' => $userData]);
Example
Data validation: Laravel’s validation rules are used to ensure that the data provided for user registration is valid.
Handling validation failures: If validation fails, an error message is logged with the user data and the specific error messages generated by the validator.
Error logging: The log includes detailed information about the user’s input and the errors, making it easier to identify problems during registration.
Response handling: An appropriate response can be returned to the user, or an exception can be thrown to halt the process and notify other components of the system that an error occurred during user registration.
Use the error level for non-fatal conditions that affect functionality, ideally providing detailed context.
4 Warning: warning conditions
Under certain conditions, unusual behaviors may arise that are not errors but could indicate potential problems. For example, a significant delay in the response of a third-party API, which could affect performance without stopping the system
Log::warning("Slow response from shipping API. Response time: {$responseTime}ms.");
Example
API timing: The time it takes for the shipping API to respond is measured using microtime()
to capture the time before and after the call.
Warning logic: A threshold (in this case, 2000 ms) is set to determine if the response is considered slow. If the response time exceeds this threshold, a warning is logged.
Warning logging: The log includes the order ID and the response time, providing additional context that can aid in investigating performance issues.
Response processing: After logging the warning, the processing of the API response continues.
Use the warning level for events that, if they recur, may require investigation, as they could indicate latent problems. This level allows for monitoring potential issues without classifying them as errors.
5 Notice: normal but significant condition
Significant changes occur in the system’s conditions without implying errors. For example, when there is a loss in the cache that requires data to be loaded from the database.
Log::notice("Cache miss for product details. Loaded from database for product ID: {$product->id}");
Example
Cache access: An attempt is made to retrieve the product details from the cache using the product ID.
Cache miss: If the details are not found in the cache (a “cache miss”), they are loaded from the database.
Warning logging: A warning is logged indicating that a cache miss has occurred, including the product ID. This helps monitor the effectiveness of the cache and identify data access patterns.
Caching: After loading the product details from the database, they are stored in the cache to improve performance on future requests.
Use the notice level for significant events that do not impact functionality but may be useful for monitoring application behavior or identifying trends.
6 Informational: informational messages
Normal events of the application, such as user actions or routine system activities. For example, when a user logs in successfully.
Log::info("User logged in successfully.", ['user_id' => $user->id]);
Example
User authentication: An attempt is made to authenticate the user using the provided credentials.
Information logging: If the authentication is successful, an info log is recorded indicating that the user has logged in successfully. The log includes the user ID, the date and time of the login, and optionally the user’s IP address, providing additional context that can be useful for auditing and security.
Redirection: After logging the information, the user is redirected to the main page of the application.
Error handling: If authentication fails, the error is handled and an appropriate message is returned.
Use the info level to log expected events, but avoid logging every minor action to prevent noise. Limit it to important actions that can assist in auditing the system.
7 Debug: debug-level messages
During development, it is useful to have detailed visibility into technical information or issues that arise when debugging errors. This type of information is generally not useful in production and is, in fact, discouraged. For example, tracing the execution time of database queries in different scenarios.
Log::debug("Executed query for user profile load.", ['query' => $query, 'time' => $executionTime]);
Example
Timing: A timer is started to measure the time taken to execute the query for loading the user profile.
Loading the user profile: The User model is used to find the user by their ID.
Calculating execution time: The time that has passed since the timer was started is calculated.
Debug logging: A debug log is recorded that includes the user ID, the executed query (captured through Laravel’s query log), and the execution time. This log level is useful for developers who need to verify the performance of the queries.
Returning the view: Finally, the view with the user profile is returned, displaying the necessary data in the user interface.
Use the debug level to obtain details during development and disable it in production.
RFC5424 The Syslog Protocol – March 2009
Laravel logging
Create images from code with ray.so
Leave a Reply
You must be logged in to post a comment.