PHP Application Logging
Last updated May 04, 2023
Table of Contents
Heroku treats logs as streams and thus will automatically aggregate all app, system and API log messages that are sent to stdout
or stderr
to provide a single channel of logging information for your application.
Most PHP frameworks can be configured to write appropriate log output, and this article provides examples for some common libraries and frameworks.
Logging using PHP or libraries
Logging from plain PHP
The normal PHP error log is automatically sent to stderr
, and any message logged using the error_log
function will thus be available in heroku logs
:
error_log("hello, this is a test!");
The alternative approach, is writing to the php://stderr
stream directly:
file_put_contents("php://stderr", "hello, this is a test!\n");
Make sure you always terminate a message with a trailing newline character like in the example above when manually writing to stderr
!
Logging using Monolog
If you’re using the excellent Monolog library for PHP (and you should!), you can simply use a StreamHandler
to write to php://stderr
:
use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('name');
$log->pushHandler(new StreamHandler('php://stderr', Level::Warning));
$log->warning('Foo');
In this example, the logger will only log messages with level “warning” and higher. Refer to the Monolog documentation for further usage instructions.
Logging when using web frameworks
CodeIgniter 3.x
Unfortunately, CodeIgniter does not allow sufficient configuration of its logging functionality, and has no way of cleanly extending core classes to replace functionality using custom code.
As a workaround, you can drop the following contents into a file named application/core/MY_Log.php
(filename is case sensitive), provided that your “sub-class prefix” is set to the default “MY_”:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
// this class is adapted from system/libraries/Log.php
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc.
* @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/
// ------------------------------------------------------------------------
/**
* Logging Class
*
* @package CodeIgniter
* @subpackage Libraries
* @category Logging
* @author EllisLab Dev Team
* @link http://codeigniter.com/user_guide/general/errors.html
*/
class MY_Log {
protected $_threshold = 1;
protected $_date_fmt = 'Y-m-d H:i:s';
protected $_levels = array('ERROR' => '1', 'DEBUG' => '2', 'INFO' => '3', 'ALL' => '4');
/**
* Constructor
*/
public function __construct()
{
$config =& get_config();
if (is_numeric($config['log_threshold']))
{
$this->_threshold = $config['log_threshold'];
}
if ($config['log_date_format'] != '')
{
$this->_date_fmt = $config['log_date_format'];
}
}
// --------------------------------------------------------------------
/**
* Write Log to php://stderr
*
* Generally this function will be called using the global log_message() function
*
* @param string the error level
* @param string the error message
* @param bool whether the error is a native PHP error
* @return bool
*/
public function write_log($level = 'error', $msg, $php_error = FALSE)
{
$level = strtoupper($level);
if ( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold))
{
return FALSE;
}
file_put_contents('php://stderr', $level.' '.(($level == 'INFO') ? ' -' : '-').' '.date($this->_date_fmt). ' --> '.$msg."\n");
return TRUE;
}
}
// END Log Class
/* End of file MY_Log.php */
/* Location: ./application/core/MY_Log.php */
Any logging calls will then be logged appropriately.
CakePHP
In your application configuration, instruct CakePHP to use the ConsoleLog
engine for your logger setups:
CakeLog::config('default', array(
'engine' => 'ConsoleLog',
));
You can then use the regular logging methods.
CakeLog::warning("Hello, this is a test message!");
Refer to the Logging section of the CakePHP manual for more information.
Laravel
In app/config/logging.php
, you can configure various logging channels. By default, the configuration file contains a definition for an “errorlog
” channel, which will result in PHP’s own error handling to be used to log messages. These are then automatically sent to heroku logs
.
At the top of the file, the default channel is typically set up to be read from a LOG_CHANNEL
environment variable:
'default' => env('LOG_CHANNEL', 'stack'),
This means you can simply set the LOG_CHANNEL
environment variable to “errorlog
” for correct logging on Heroku:
$ heroku config:set LOG_CHANNEL=errorlog
Newer releases of Laravel 5.6 and later also have an “stderr
” channel defined in app/config/logging.php
, which will log straight to heroku logs
without going through PHP’s error logging mechanism first:
$ heroku config:set LOG_CHANNEL=stderr
Lumen
The Lumen framework uses the same mechanisms as Laravel, documented above.
The recommended configuration is to set LOG_CHANNEL
to stderr
, as follows:
$ heroku config:set LOG_CHANNEL=stderr
Symfony
Please refer to the “Logging” section in the Deploying Symfony Apps guide for instructions.
Adding logging for legacy applications
Some applications cannot be easily modified to write logs to stderr
or stdout
. In these cases, if the application allows configuration of a static log file name, it’s possible to instruct Heroku’s PHP support to tail
such a log file into the Logplex stream using the -l
option of the boot scripts for PHP applications.
For example, if your legacy application writes to a file named log/error.log
your Procfile
entry could look like this:
web: heroku-php-apache2 -l log/error.log
You can specify the -l
option multiple times to tail multiple files, and of course combine it with other options and the document root argument:
web: heroku-php-nginx -C rewrites.conf -l log/error.log -l log/slow.log wwwroot/
For obvious reasons, if your legacy application creates dynamic log file names, e.g. including the current date, you will need to patch or configure it to use a fixed, static log file name instead for this feature to work.