A simple HTTP request, response and error logger for API projects in PHP. It can be used with different types of middleware (depending on the framework or library), as well as pure PHP projects.
This guides assumes that you are currently using (or planning to use) the following technologies in your project:
- PHP 7.1
- Composer
- First, make sure that you have installed and configured a running web server (such as Apache) with PHP 7.1 or higher.
- Use
composer
to install the library:composer require tribeos/http-log
Before using the library, you first have to require the generated autoload.php
file:
require_once "vendor/autoload.php";
After that, you can use the HttpLog\*
namespace and functions you need in your project.
In order to start logging HTTP requests and responses, you need to first create an HttpLogger
object. The object constructor has three required parameters, and an optional fourth one:
type
: Type of logger (for now, only the file logger is supported; SQL and MongoDB loggers are planned for addition at a latter date; default:file
)filter
: Log filter. It represents which request/response properties will be logged (default:full h
= all supported properties request and response headers) (see more)- features several default filter configurations, as well as the option to define custom filters
path
: Path to the log file (default:logs/debug.log
)- Make sure that your target folder exists and that you have the correct write permissions.
- Note: Any custom path can be specifed, but it has to be specified as an absolute path (e.g. by using
__DIR__
) to the project root. The reason for this is that certain PHP functions which can be triggered by the library change the execution path, so we must rely on absolute paths.
default_log
(optional): Controls whether the errors will also be logged to the default Apache/PHP log file (default:true
)- The library logs all errors to its specified
path
file by design. With thedefault_log
parameter, you can also let all errors be logged to the default error log file, in addition to the library's log file. - Note: Fatal PHP errors (which are irrecoverable) are always logged to the default error log file, regardless of this parameter.
- The library logs all errors to its specified
Example logger initialization looks like this:
require_once __DIR__."/vendor/autoload.php";
/* Use the logger namespace */
use HttpLog\HttpLogger;
$logger = HttpLogger::create("file", "full h", "logs/debug.log", false)::get();
HttpLogger::create()
constructs a static instance of the logger (which can later be reused anywhere in the code), while HttpLogger::get()
returns the newly created logger instance.
These methods can be chained, as seen in the example above, or called individually, like this:
HttpLogger::create("file", "full h", "logs/debug.log", false);
/* The HttpLogger::get() can then be called from anywhere inside the project. */
$logger = HttpLogger::get();
After logger initialization, the logger is ready to be used. Depending on your project needs and technologies used, call the log()
method either at the end of your response outputs, or in specific middlewares that are set to fire after an API response has been sent.
- Note: This is the recommended behavior. The logger could theoretically be called from elsewhere before the response, but in that case, it will not catch and log the response output. Moreover, additional unexpected behavior is possible.
/* Pure PHP example */
require_once __DIR__."/vendor/autoload.php";
use HttpLog\HttpLogger;
/* Create and fetch the logger instance */
$logger = HttpLogger::create("file", "full h", "logs/debug.log", false)::get();
/* Output a response */
header("Content-Type: application/json");
echo json_encode( ["param" => "test"] );
/* Log the incoming request, outgoing response and possible errors */
$logger->log();
The log is formatted as a TSV textual file, with all logged parameters separated by tabs (\t
), which allows for easy parsing and formatting. Each request/response "pair" is represented by a single line in the file, starting with the request time, and followed by request data, response data and any encountered errors.
Example (sent with Postman):
2019-03-12 21:05:31 /http-logger /?param=test http://localhost/http-logger?param=test GET ::1 36360 HTTP/1.1 PostmanRuntime/7.6.0 0 */* {"param":"test"} [] [] [] 0 0 {"test_header":"test_value","cache-control":"no-cache","Postman-Token":"a84d2d58-3f77-4c39-a60d-a06487a6cdc8","User-Agent":"PostmanRuntime\/7.6.0","Accept":"*\/*","accept-encoding":"gzip, deflate","referer":"http:\/\/localhost\/http-logger?param=test","Host":"localhost","Connection":"keep-alive"} 200 {"param":"test"} {"Keep-Alive":"timeout=5, max=99","Connection":"Keep-Alive","Transfer-Encoding":"chunked","Content-Type":"application\/json"}
The following parameters are available for logging (and used in the default configuration):
date
: The exact time when the request occuredbase
: The parent subdirectory of the URLurl
: The URL being requestedreferrer
: The referrer URLmethod
: The request method (GET, POST, PUT, DELETE, etc.)ip
: IP address of the clientport
: Client's accessing portscheme
: The server protocol (HTTP/HTTPS)user_agent
: Browser informationtype
: The content typelength
: The content lengthaccept
: HTTP accept parametersquery
: Query string parametersdata
: POST parameterscookies
: Cookie parametersfiles
: Uploaded filesis_https
: Whether the connection is secure (HTTPS)is_ajax
: Whether the request is an AJAX requestrequest_headers
: HTTP request headerscode
: HTTP response codebody
: HTTP response bodyresponse_headers
: HTTP response headers
The library allows for different logging filters to be applied to the logger. The filters (specified as the second parameter in the logger creation) specify which of the aforementioned parameters will be logged (and in which order).
Each built-in filter features two variants: the base variant and the headers variant. The base variant includes all parameters related to that filter, except full header information (request_headers
and response_headers
are absent). The headers variant contains both the parameters and all header information.
The available (base variant) filters are:
standard
: includesdate
,url
,method
,ip
,query
,data
,code
,body
full
: all loggable properties (as seen above)request_only
: only request properties (all properties exceptcode
andbody
)response_only
: only response properties (code
andbody
)error
: only logs the encountered errors (notices, warning and errors) (see more)
Each of these filters (except error
) also features a headers variant (standard h
, full h
, request_only h
, response_only h
), which includes the additional header information (request headers after request properties and response headers after response properties).
The full h
filter is set as the default in the library, meaning that the logger will store all request and response data, along with request and response headers.
The library allows for the creation of custom filters, allowing the users to set the specific properties they want to log, as well as their order.
The custom filters are defined as follows: property1|property2|property3|...
(properties separated by a pipe symbol - |
).
When parsed by the logger, only the properties specified in the custom filter will be logged, disregarding the rest.
Example:
/* Defining a logger with a custom filter */
$logger = HttpLogger::create("file", "date|url|method|ip|", "logs/debug.log", false)::get();
The logger in this example will only log the request date, requested URL, request method and the client's IP address, in that exact order.
In addition to request and response logging, the library also features error intercepting and handling. The libary is able to log and gracefully handle PHP notices, warnings, errors and fatal errors. The errors will be logged on the same line after request and response data, as encoded JSON strings.
- Note: for the sake of simplicity, in the remainder of this document we will use the term "error" to refer to all notices, warnings and errors, if not explicitly stated otherwise.
Example:
2019-03-12 22:07:11 /http-logger /?param=test http://localhost/http-logger?param=test GET ::1 37714 HTTP/1.1 PostmanRuntime/7.6.0 0 */* {"param":"test"} [] [] [] 0 0 {"test_header":"test_value","cache-control":"no-cache","Postman-Token":"3a41e974-4f0c-49e6-8af3-3942ecd25f80","User-Agent":"PostmanRuntime\/7.6.0","Accept":"*\/*","accept-encoding":"gzip, deflate","referer":"http:\/\/localhost\/http-logger?param=test","Host":"localhost","Connection":"keep-alive"} 500 {"Connection":"close","Content-Type":"application\/json"} [{"error_type":"NOTICE","log_level":5,"error_code":8,"description":"Undefined variable: undefined","file":"\/var\/www\/html\/http-logger\/index.php","line":13},{"error_type":"INFO","log_level":6,"error_code":1024,"description":"This is an informational message.","file":"\/var\/www\/html\/http-logger\/index.php","line":15},{"error_type":"FATAL","log_level":3,"error_code":1,"description":"Uncaught Error: Call to undefined function no_such_function() in \/var\/www\/html\/http-logger\/index.php:17\nStack trace:\n#0 {main}\n thrown","file":"\/var\/www\/html\/http-logger\/index.php","line":17}]
During the request/response lifecycle, the PHP script is left to execute. The logger is setting one of its methods as the error handler (via set_error_handler()
), and intercepting any irregular flows.
All encountered errors are stored in the logger's error stack (array), and after the log()
method is called, all errors are logged to the same line as the request and response data, allowing for an easy review of irregular flows that may have occured during execution.
Notices and warnings will be stored in the error stack, and the regular script execution will continue as normal, until the logging is performed and the script finishes execution naturally. However, serious errors (user errors and fatal PHP errors) will, after being logged, stop the script's execution at the time of occurence and return a formatted JSON response to the user, with the details of the fatal error.
The error object contains the following parameters (all of which are logged):
error_type
: The type of the caught error (notice, warning, error, etc.)log_level
: The error's logging levelerror_code
: The error codedescription
: Message description of the caught errorfile
: The file in which the error occuredline
: The line in which the error occured
Some users might only care about the encountered errors, and not necessarily about request and response data. For that reason, it is possible to apply the error
filter during logger initialization.
With the error
filter applied, only the errors will be logged, in TSV format (as opposed to formatted JSON in all other filters). Moreover, each error will be logged on a separate line, and include a date
property.
/* Create and fetch an error-only logger instance */
$logger = HttpLogger::create("file", "error", "logs/debug.log", false)::get();
print_r($undefined);
md5();
no_such_function();
Resulting log:
2019-03-12 21:55:25 NOTICE 5 8 Undefined variable: undefined /var/www/html/http-logger/index.php 13
2019-03-12 21:55:25 WARNING 4 2 md5() expects at least 1 parameter, 0 given /var/www/html/http-logger/index.php 14
2019-03-12 21:55:25 FATAL 3 1 Uncaught Error: Call to undefined function no_such_function() in /var/www/html/http-logger/index.php:17 Stack trace: #0 {main} thrown /var/www/html/http-logger/index.php 17
Apart from automatically managing encountered errors, the library also provides users with the ability to log a custom error message anywhere in the project (similar to how Log4j works).
The library contains the following logging methods, which can be called from the logger object:
debug()
info()
warning()
error()
fatal()
Example:
/* Create and fetch the logger instance */
$logger = HttpLogger::create("file", "full h", "logs/debug.log", false)::get();
$logger->warning("Testing a sample warning.");
$logger->info("This is an informational message.");
$logger->fatal("This fatal error will stop program execution.");
These user-defined errors "behave" in the same way as regular errors: debug messages, warnings and info messages are simply logged, whereas errors and fatal errors also stop program execution. Moreover, the logger will have no difficulties logging both the regular errors and the user-defined errors in a single session.
- Note: Since the primary purpose of the logger library is request/response logging, the user still needs to call
$logger->log()
orHttpLogger::get()->log()
somewhere in the code. The user-defined functions will only store the logs within the logger object. Thelog()
method is the one that actually commits the logs to a file.
If the default_log
parameter in the logger initialization is set to true
, user-defined errors will also be logged in the default Apache/PHP log file.
/* Create and fetch an error-only logger instance */
$logger = HttpLogger::create("file", "error", "logs/debug.log", false)::get();
/* A mix of regular and user-defined errors */
$logger->info("This is an informational message.");
print_r($undefined_variable);
$logger->fatal("This is a fatal error.");
Resulting log:
2019-03-12 22:15:55 INFO 6 1024 This is an informational message. /var/www/html/http-logger/index.php 15
2019-03-12 22:15:55 NOTICE 5 8 Undefined variable: undefined_variable /var/www/html/http-logger/index.php 13
2019-03-12 22:15:55 FATAL 3 256 This is a fatal error. /var/www/html/http-logger/index.php 16
- It is highly recommended not to use
ini_set('display_errors', 1)
, when using this library, because this setting will cause all errors to go to the response body buffer, polluting your response logs. If you do not care about response logging, however, feel free to use this setting. - Our recommendation is to disable the default PHP logging by setting the
default_log
parameter tofalse
during logger initialization, as all information (even more detailed than the default logger) will be kept in the library's log file, and we wish to avoid data redundancy. However, you can use both loggers together without issues, should your needs require.
Extensive documentation can be found at: https://aldin-sxr.github.io/http-logger/
The documentation was generated with PHPDocumentor.
- Aldin Kovačević, initial work on the library and documentation - Aldin-SXR
- Adnan Miljković, for his professional advice and suggestions on how to implement certain features - adnanmiljkovic
- Dino Kečo, for encouraging me to create this library in the first place - dinokeco
The skeleton is licensed under the MIT license. See the LICENSE file for details.
Work in progress by tribeOS - The Fairest, Most Profitable Advertising Marketplace Ever.