Welcome Guest, Not a member yet? Register   Sign In
Log core error messages in database
#1

[eluser]ilumos[/eluser]
Hi Guys,

What's the best way for me to log the error messages generated internally by CodeIgniter?

I plan to log them in a database, and fall back to a file if the database is unreachable, but without hacking the core I'm not sure how to best capture the messages.

Any help appreciated, thanks in advance!
#2

[eluser]Aken[/eluser]
You can extend the Log library and add your own code to the write_log() method.
#3

[eluser]ilumos[/eluser]
So I've extended the log library and I'm met with this error:

Code:
Fatal error: Class 'CI_Controller' not found in /Users/ilumos/Sites/lanager/system/core/CodeIgniter.php on line 233

I'm using modular extensions.

Here's my /application/libraries/MY_Log.php


Code:
class MY_Log extends CI_Log
{

/*
public function __construct()
{
  parent::__construct();
}
*/


function log($category, $severity, $message, $data = NULL, $file = '', $line = '')
{
  $CI =& get_instance(); // commenting this fixes everything
  //$CI->load->helper('session');
  //$user_id = get_user_id();
  $user_id = 0;
  $ip = $_SERVER['REMOTE_ADDR'];
  
  // only backtrace non-core errors which have no file/line specified
  if($category != 'core' && (empty($file) OR empty($line))) {
   // find out where the log request was called from
   $backtrace = debug_backtrace();
   $line = $backtrace[0]['line'];
   $file = $backtrace[0]['file'];
   $file = strstr($file,'/application');
  }

  if(is_array($data))
  {
   $data = print_r($data,TRUE);
  }
  $insert_data = array(
   'category' => $category,
   'severity' => $severity,
   'message' => $message,
   'data' => $data,
   'user_id' => $user_id,
   'ip' => $ip,
   'file' => $file,
   'line' => $line,
  );
  
  //$log_to_db = $CI->db->insert('usage', $insert_data);
  
  if($log_to_db = TRUE)
  {
   return TRUE;
  }
  else // if logging to database fails, fall back to file logging
  {
   $message = $message.' user_id:'.$user_id.' ip:'.$ip.' file:'.$file.' line:'.$line.' data:'.print_r($data, TRUE);
   $log_to_file = $this->log_to_file($severity,$message);
   return $log_to_file;
  }

}

function log_browser()
{
  //$CI =& get_instance();
}



public function write_log($level = 'error', $msg, $php_error = FALSE)
{
  if ($this->_enabled === FALSE)
  {
   return FALSE;
  }

  $level = strtoupper($level);

  if ( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold))
  {
   return FALSE;
  }
  
  $this->log('core',$level,$msg,NULL); // hijack message about to be logged

  return TRUE;
}


function log_to_file($level = 'error', $msg)
{
  if ($this->_enabled === FALSE)
  {
   return FALSE;
  }

  $level = strtoupper($level);

  if ( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold))
  {
   return FALSE;
  }

  $filepath = $this->_log_path.'log-'.date('Y-m-d').'.php';
  $message  = '';

  if ( ! file_exists($filepath))
  {
   $message .= "<"."?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?".">\n\n";
  }

  if ( ! $fp = @fopen($filepath, FOPEN_WRITE_CREATE))
  {
   return FALSE;
  }

  $message .= $level.' '.(($level == 'INFO') ? ' -' : '-').' '.date($this->_date_fmt). ' --&gt; '.$msg."\n";

  flock($fp, LOCK_EX);
  fwrite($fp, $message);
  flock($fp, LOCK_UN);
  fclose($fp);

  @chmod($filepath, FILE_WRITE_MODE);
  return TRUE;
}


}

(I've commented all references to $CI as this seems to be the culprit.

I seem to be having the same problem as this guy:

http://stackoverflow.com/questions/88788...-not-found

Any help appreciated

Thanks
#4

[eluser]Aken[/eluser]
The problem is happening because the log_message() function - which utilizes the Log library - is being used before the CI_Controller singleton is available.

You'll likely need to add a check to see if the CI_Controller class is available yet, and if it is, then use the get_instance() function. Then, when using $CI, you'll have to make sure it's set appropriately first (or just do it inside the first check's if statement if you aren't utilizing the constructor to assign $CI).

Chances are you're trying to log errors that occur in your controllers, which would mean the DB functions would be available by then. You'll just be without DB logging for those earlier debug messages that occur when some Core files initialize.

Look through the order that system/core/CodeIgniter.php goes through, and you'll get a gist of when CI_Controller first becomes available.
#5

[eluser]ilumos[/eluser]
[quote author="Aken" date="1343020941"]Then, when using $CI, you'll have to make sure it's set appropriately first (or just do it inside the first check's if statement if you aren't utilizing the constructor to assign $CI).[/quote]

I'm using isset($CI), is that correct?

I've hit upon another problem when trying to load a helper:

Code:
Fatal error: Call to a member function helper() on a non-object

What further checks do I need to do to make sure I can load the required helper?



New code:
Code:
function log($category, $severity, $message, $data = NULL, $file = '', $line = '')
{
  if(class_exists('CI_Controller') && function_exists('get_instance'))
  {
   $CI =& get_instance();
   $CI->load->helper('session');
   $user_id = get_user_id();
  }
  else
  {
   $user_id = 0;
  }
...
#6

[eluser]Aken[/eluser]
The load property doesn't exist until a controller (which extends CI_Controller) is actually instantiated - you'll need to make sure it exists also before using it.
#7

[eluser]PhilTem[/eluser]
I'm not sure that your new code is good, because - according to the PHP docu on class_exists(), second parameter: If the class cannot be found, then it will use the autoloader. And I'm not sure the CI-autoloader is registered at that time. So you might eventually screw up the whole business logic of CI.

A workaround would be to look at the CI files starting in index.php and follow the source code. You will see that you can access every core class also via something like

Code:
global $IN

which also refers to the input class but not via $this so it's not assigned to a property of CI_Controller.

That should fix your problem: Having a look at ./system/core/CodeIgniter.php Wink
#8

[eluser]Aken[/eluser]
Phil brings a good point with the __autoload parameter for class_exists(), however:

1) CI doesn't have any __autoload or related functionality.
2) Even if it did happen to include the system/core/Controller.php file, it wouldn't instantiate it, so it shouldn't alter the original logic (at least to the best of my knowledge).

Also, I don't think the global variables inside CodeIgniter.php would be of any use to his goal, since he wishes to use the session and database libraries.

You can always load helper files manually using include/require, but you'd have to make a note to whether or not they use get_instance(). If they did, you'd run into the same issues.
#9

[eluser]PhilTem[/eluser]
Just looked at the CI source code. Could have sworn I saw the 'spl_autoload_register()' there once. But it's no longer there. So you can kinda forget half my post above. But anyway one should be cautious with using 'class_exists()' due to the call of __autoload of the class in question if it's not yet instantiated Wink

ilumos might however use
Code:
load_class()

to load the classes while there's no $this->load available.

Maybe somebody with more insight on the CI core can shed some light? I could only guess what would be best to solving this problem Wink
#10

[eluser]Aken[/eluser]
Okay, I came up with what I think is a pretty good solution for this: use the post_controller_constructor hook to tell the log class when it can use DB resources. The hook fires after the controller class is instantiated, but before the controller method itself is called. So you won't be able to use any DB logging before this point, but anytime after. If you use the MY_Log.php file I include below and log the debug messages, you'll see when it first becomes available. If you want to be able to log to the DB prior to this point, you'd have to do a lot more hacking.

1) Enable hooks in your /application/config/config.php file.

2) In /application/config/hooks.php, add the post_controller_constructor hook:

Code:
$hook['post_controller_constructor'] = array(
'class'  => 'LogHook',
'function' => 'setDbReady',
'filename' => 'LogHook.php',
'filepath' => 'hooks',
'params' => true,
);

3) Create the hook file itself at /application/hooks/LogHook.php

Code:
&lt;?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**
* Hooks relating to logging.
*/
class LogHook {

/**
  * Call the setDbReady() method in the extended MY_Log library.
  *
  * @param bool Whether or not the DB is ready to use.
  * @return void
  */
public function setDbReady($ready = false)
{
  // We can use get_instance() here because the controller
  // is already instantiated fully.
  $CI = get_instance();
  $CI->log->setDbReady($ready);
}
}

4) Create your extended log library at /application/libraries/MY_Log.php - See some notes in the comments regarding this file. Chances are you'll check it to add your own functionality anyway, this is just the base necessity

Code:
&lt;?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**
* Extend the functionality of the default Log library.
*
* @author Eric 'Aken' Roberts
* @website http://www.cryode.com
*/
class MY_Log extends CI_Log {

/**
  * Whether DB logging functionality is available or not.
  *
  * @var  boolean
  */
protected $dbReady = false;

/**
  * Constructor. Line added for log file readability.
  *
  * THIS CONSTRUCTOR IS OPTIONAL. You can remove it if you don't want the line.
  */
public function __construct()
{
  parent::__construct();
  
  $this->write_log('debug', '--------------------------------------------------------');
}

/**
  * write_log function adds the database logging check prior to
  * using the default write_log() method in the parent, and thus
  * writing the log message to the file like usual.
  *
  * @param string Priority level.
  * @param string Message to log.
  * @param bool Is this a PHP error?
  * @return void
  */
public function write_log($level = 'error', $msg, $php_error = false)
{
  if ($this->dbReady === true)
  {
   // This is where you would perform your DB logging,
   // using $CI = get_instance();
  
   // This is here just for reference to see when it would fire:
   parent::write_log($level, '-- INSERT INTO DB: ' . $msg, $php_error);
  }
  
  // Write the message to the log file like normal.
  parent::write_log($level, $msg, $php_error);
}

/**
  * Set the current availability of DB logging.
  *
  * @param bool
  * @return void
  */
public function setDbReady($ready = false)
{
  $this->dbReady = (bool) $ready;
}

}

5) Use log_message() like normal in your controllers, models, libraries, etc.

Give that a shot, modify it to your needs, let me know if anything doesn't work or if you have questions.




Theme © iAndrew 2016 - Forum software by © MyBB