Creating a custom XML logger for SugarCRM 7

I recently ran across a question on StackOverflow in which the original poster asked about creating a custom XML logger for SugarCRM. He was wondering where to put his custom class and some of the particulars surrounding this so I thought I’d pitch in and help out.

I created my own custom XML logger class – SugarXMLLogger – and saved it at custom/include/SugarLogger/SugarXMLLogger.php. I then applied it to the Ping API for testing both as a replacement for fatal level logging and as a replacement for the default logger. Since the functionality of the logger is intended to be simple, I didn’t feel the need to extend SugarLogger. However, I did feel it at least appropriate to implement LoggerTemplate so that the integrity of the logger among the stack of loggers was kept in tact. If you want the fullness of SugarLogger in your custom loggers then you should extend SugarLogger. However, for a simple XML logger that wasn’t absolutely necessary.

As far as the log is concerned, I kept all the same information as is normally in sugarcrm.log with the only change being how it is stored. Since XML is structured it made sense to structure the logged information accordingly. So, without further ado, here is the code for the SugarXMLLogger…

The SugarXMLLogger Class

<?php
/** 
 * Save to custom/include/SugarLogger/SugarXMLLogger.php
 * 
 * Usage: 
 * To make one particular log level write to the XML log do this:
 * ```php
 * <?php
 * LoggerManager::setLogger('fatal', 'SugarXMLLogger');
 * $GLOBALS['log']->fatal('Testing out the XML logger');
 * ```
 * 
 * To make all levels log to the XML log, do this
 * ```php
 * <?php
 * LoggerManager::setLogger('default', 'SugarXMLLogger');
 * $GLOBALS['log']->warn('Testing out the XML logger');
 * ```
 */
 
/**
 * Get the interface that this logger should implement
 */
require_once 'include/SugarLogger/LoggerTemplate.php';
 
/**
 * SugarXMLLogger - A very simple logger that will save log entries into an XML
 * log file
 */
class SugarXMLLogger implements LoggerTemplate
{
    /**
     * The name of the log file
     * 
     * @var string
     */
    protected $logfile = 'sugarcrm.log.xml';
 
    /**
     * The format for the timestamp entry of the log
     * 
     * @var string
     */
    protected $dateFormat = '%c';
 
    /**
     * The current SimpleXMLElement logger resource
     * 
     * @var SimpleXMLElement
     */
    protected $currentData;
 
    /**
     * Logs an entry to the XML log
     * 
     * @param string $level The log level being logged
     * @param array $message The message to log
     * @return boolean True if the log was saved
     */
    public function log($level, $message)
    {
        // Get the current log XML
        $this->setCurrentLog();
 
        // Append to it
        $this->appendToLog($level, $message);
 
        // Save it
        return $this->saveLog();
    }
 
    /**
     * Saves the log file
     * 
     * @return boolean True if the save was successful
     */
    protected function saveLog()
    {
        $write = $this->currentData->asXML();
        return sugar_file_put_contents_atomic($this->logfile, $write);
    }
 
    /**
     * Sets the SimpleXMLElement log object
     * 
     * If there is an existing log, it will consume it. Otherwise it will create
     * a SimpleXMLElement object from a default construct.
     */
    protected function setCurrentLog()
    {
        if (file_exists($this->logfile)) {
            $this->currentData = simplexml_load_file($this->logfile);
        } else {
            sugar_touch($this->logfile);
            $this->currentData = simplexml_load_string("<?xml version='1.0' standalone='yes'?><entries></entries>");
        }
    }
 
    /**
     * Adds an entry of level $level to the log, with message $message
     * 
     * @param string $level The log level being logged
     * @param array $message The message to log
     */
    protected function appendToLog($level, $message)
    {
        // Set some basics needed for every entry, starting with the current
        // user id
        $userID = $this->getUserID();
 
        // Get the process id
        $pid = getmypid();
 
        // Get the message to log
        $message = $this->getMessage($message);
 
        // Set the timestamp
        $timestamp = strftime($this->dateFormat);
 
        // Add it to the data now
        $newEntry = $this->currentData->addChild('entry');
        $newEntry->addChild('timestamp', $timestamp);
        $newEntry->addChild('pid', $pid);
        $newEntry->addChild('userid', $userID);
        $newEntry->addChild('level', $level);
        $newEntry->addChild('message', $message);
    }
 
    /**
     * Gets the user id for the current user, or '-none-' if the current user ID
     * is not attainable
     * 
     * @return string The ID of the current user
     */
    protected function getUserID()
    {
        if (!empty($GLOBALS['current_user']->id)) {
            return $GLOBALS['current_user']->id;
        } 
 
        return '-none-';
    }
 
    /**
     * Gets the message in a loggable format
     * 
     * @param mixed $message The message to log, as a string or an array
     * @return string The message to log, as a string
     */
    protected function getMessage($message)
    {
        if (is_array($message) && count($message) == 1) {
            $message = array_shift($message);
        }
 
        // change to a human-readable array output if it's any other array
        if (is_array($message)) {
            $message = print_r($message,true);
        }
 
        return $message;
    }
}

The structure of the log

<?xml version="1.0" standalone="yes"?>
<entries>
    <entry>
        <timestamp>Wed Oct  8 11:56:04 2014</timestamp>
        <pid>89160</pid>
        <userid>1</userid>
        <level>fatal</level>
        <message>Testing out the XML logger</message>
    </entry>
</entries>

How to log only fatal logs to the XML log

<?php
LoggerManager::setLogger('fatal', 'SugarXMLLogger');
$GLOBALS['log']->fatal('Testing out the XML logger');
?>

How to log all logs to the XML log

<?php
LoggerManager::setLogger('default', 'SugarXMLLogger');
$GLOBALS['log']->warn('Testing out the XML logger');