Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
log_manager.inc
1 <?php
16 require_once SQ_INCLUDE_PATH.'/asset.inc';
17 
30 class Log_Manager extends Asset
31 {
32 
33  protected $log_level_map;
34 
35  protected $min_tail_lines = 10;
36 
37 
44  function __construct($assetid=0)
45  {
46  $this->_ser_attrs = TRUE;
47  parent::__construct($assetid);
48 
49  }//end constructor
50 
51 
63  public function create(Array &$link)
64  {
65  require_once SQ_CORE_PACKAGE_PATH.'/system/system_asset_fns.inc';
66  if (!system_asset_fns_create_pre_check($this)) {
67  return FALSE;
68  }
69 
70  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
71  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
72 
73  if ($linkid = parent::create($link)) {
74  if (!system_asset_fns_create_cleanup($this)) {
75  $linkid = FALSE;
76  }
77  }
78 
79  if ($linkid) {
80  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
81  } else {
82  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
83  }
84 
85  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
86  return $linkid;
87 
88  }//end create()
89 
90 
98  public function _getAllowedLinks()
99  {
100  return Array(
101  SQ_LINK_TYPE_1 => Array(),
102  SQ_LINK_TYPE_2 => Array(),
103  SQ_LINK_TYPE_3 => Array(),
104  SQ_LINK_NOTICE => Array(),
105  );
106 
107  }//end _getAllowedLinks()
108 
109 
116  public function canClone()
117  {
118  return FALSE;
119 
120  }//end canClone()
121 
122 
132  protected function _getName($short_name=FALSE)
133  {
134  return $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name');
135 
136  }//end _getName()
137 
138 
139 //-- LOG REGISTRY & SETTINGS --//
140 
141 
164  public function getLogs()
165  {
166  $files = glob(SQ_LOG_PATH.'/*.log');
167  $logs = $this->attr('logs');
168 
169  foreach ($files as $file) {
170  $logname = $this->_getLogPrefixFromFileName($file);
171  if (!empty($logname)) {
172  if (!isset($logs[$logname])) {
173  $logs[$logname] = $this->_setLogDefaults();
174  }
175  }
176  }
177 
178  return $logs;
179 
180  }//end getLogs()
181 
182 
193  protected function _getLogPrefixFromFileName($file)
194  {
195  preg_match('/^'.preg_quote(SQ_LOG_PATH.'/', '/').'(\S*).log$/', $file, $matches);
196  if (isset($matches[1]) && !empty($matches[1])) {
197  return $matches[1];
198  } else {
199  return FALSE;
200  }
201 
202  }//end _getLogPrefixFromFileName()
203 
204 
213  public function getLogSettings($logname)
214  {
215  $logs = $this->getLogs();
216  if (isset($logs[$logname]) && !empty($logs[$logname])) {
217  return $logs[$logname];
218  } else {
219  return FALSE;
220  }
221 
222  }//end getLogSettings()
223 
224 
225 //-- LOG READING & WRITING --//
226 
227 
238  public function &getLogLine($logname, $rotation_index=NULL, $offset=0)
239  {
240  $rotation_suffix = (!is_null($rotation_index) && is_numeric($rotation_index)) ? '.'.$rotation_index : '';
241 
242  $read_data = $this->_readLineFromFile(SQ_LOG_PATH.'/'.$logname.SQ_CONF_LOG_EXTENSION.$rotation_suffix, $offset);
243 
244  if (!empty($read_data)) {
245  $read_data['line'] = $this->_decodeLine(array_get_index($read_data, 'line', FALSE));
246  return $read_data;
247  } else {
248  return FALSE;
249  }
250 
251  }//end getLogLine()
252 
253 
263  public function getLogSize($logname, $rotation_index=NULL)
264  {
265  $rotation_suffix = (!is_null($rotation_index) && is_numeric($rotation_index)) ? '.'.$rotation_index : '';
266  return $this->_getFileSize(SQ_LOG_PATH.'/'.$logname.SQ_CONF_LOG_EXTENSION.$rotation_suffix);
267 
268  }//end getLogSize()
269 
270 
280  public function getLogIterator($logname, $rotation_index=NULL)
281  {
282  include_once 'log_iterator.inc';
283  // start up the generate report HIPO
284  $iterator = new Log_Iterator($logname, $rotation_index);
285  return $iterator;
286 
287  }//end getLogIterator()
288 
289 
299  protected function _setLogDefaults($log=Array())
300  {
301  $rotation = array_get_index($log, 'rotation', Array());
302 
303  $log['rotation']['interval'] = array_get_index($rotation, 'interval', 86400);
304  $log['rotation']['min_index'] = array_get_index($rotation, 'min_index', 1);
305  $log['rotation']['max_index'] = array_get_index($rotation, 'max_index', 10);
306  $log['rotation']['last_timestamp'] = array_get_index($rotation, 'last_timestamp', 0);
307 
308  return $log;
309 
310  }//end _setLogDefaults()
311 
312 
333  protected function _decodeLine($line)
334  {
335  if (empty($line)) return FALSE;
336 
337  // matches the first three brackets containing metadata, then splits off the rest of the line containing the log data
338  // the string contain data in the format:
339  // [DATE][USERNAME:USERID][LEVEL(LEVELID)][FLAG] DATA
340  // NOTE: only flags ' ' and 'S' are read - raw ('R') log entries are ignored
341  preg_match('/\[([^[\]]*)\]\[([^[\]]*):([^[\]]*)\]\[(\d+):([^[\]]*)\]\[(.)\] (.*)/', $line, $matches);
342 
343  if (empty($matches)) return FALSE;
344 
345  // unmangle the user name
346  $user = $matches[3];
347  $user = str_replace('&#91;', '[', $user);
348  $user = str_replace('&#93;', ']', $user);
349  $user = str_replace('&#58;', ':', $user);
350  $user = str_replace('&#10;', "\n", $user);
351  $data = str_replace('&#13;', "\r", $user);
352 
353  $data = $matches[7];
354  $data = str_replace('&#10;', "\n", $data);
355  $data = str_replace('&#13;', "\r", $data);
356 
357  $decoded_line = Array(
358  'userid' => $matches[2],
359  'user' => $user,
360  'date' => strtotime($matches[1]),
361  'level' => (int) $matches[4],
362  'data' => $data,
363  );
364 
365  // unserialise the data if it is marked as 'S'(erialised)
366  if ($matches[6] == 'S') {
367  $decoded_line['data'] = unserialize($decoded_line['data']);
368  }
369 
370  return $decoded_line;
371 
372  }//end _decodeLine()
373 
374 
392  protected function _readLineFromFile($file, $offset=0)
393  {
394  if (empty($file)) return FALSE;
395 
396  $file_contents = Array();
397 
398  if (file_exists($file) && $handle = fopen($file, 'r')) {
399  if (fseek($handle, $offset) >= 0 && !feof($handle)) {
400  $line = fgets($handle);
401  // new offset
402  $offset = ftell($handle);
403  $result = Array(
404  'line' => trim($line),
405  'offset' => $offset,
406  );
407  } else {
408  $result = FALSE;
409  }
410  fclose($handle);
411  } else {
412  trigger_localised_error('CORE0018', E_USER_ERROR, $file);
413  $result = FALSE;
414  }
415 
416  return $result;
417 
418  }//end _readLineFromFile()
419 
420 
429  protected function _getFileSize($file)
430  {
431  if (empty($file)) return FALSE;
432 
433  $filesize = FALSE;
434  if (file_exists($file)) {
435  $filesize = sprintf('%u', filesize($file));
436  }
437 
438  return $filesize;
439 
440  }//end _getFileSize()
441 
442 
443 //-- LOG ROTATION --//
444 
445 
453  public function rotateLogs()
454  {
455  $logs = $this->getLogs();
456  $status = FALSE;
457  $rotated_logs = Array();
458  $rotate_hr = $this->attr('log_rotate_time');
459  $rotate_hr_fwd = ($rotate_hr > 22) ? '0' : ($rotate_hr + 1);
460  $time_to_rotate = mktime($rotate_hr, '0', '0');
461  $time_to_rotate_fwd = mktime($rotate_hr_fwd, '0', '0');
462 
463  // Rotate the logs within the hour it should take place
464  if (time() >= $time_to_rotate && time() <= $time_to_rotate_fwd) {
465  foreach ($logs as $logname => $log_data) {
466  // determine whether we need to rotate the log
467  // this formula should calculate:
468  // [Time to Rotate] - [Last Rotation] >= [Within 24 Hours]
469  // interval is needed to make sure we do not rotate many times within the day
470  if ($time_to_rotate - $log_data['rotation']['last_timestamp'] >= ($log_data['rotation']['interval']-360)) {
471  if ($this->rotateLog($logname, $log_data)) {
472  $logs[$logname] = $log_data;
473  $rotated_logs[] = $logname;
474  }
475  }
476 
477  }
478  }//end if
479 
480  // turn on forced run level, as not everything has write privileges to log_manager's attributes
481  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
482 
483  $this->setAttrValue('logs', $logs);
484  $this->saveAttributes();
485 
486  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
487 
488  return $rotated_logs;
489 
490  }//end rotateLogs()
491 
492 
502  public function rotateLog($logname, &$log_data)
503  {
504  $rotation = array_get_index($log_data, 'rotation', Array());
505  $interval = array_get_index($rotation, 'interval');
506  $min_index = array_get_index($rotation, 'min_index');
507  $max_index = array_get_index($rotation, 'max_index');
508  $last_timestamp = array_get_index($rotation, 'last_timestamp');
509 
510  // check to see if we have the settings we need, otherwise, fail
511  if (empty($rotation) || empty($interval) || !is_numeric($min_index) || empty($max_index) || $min_index > $max_index) {
512  $status = FALSE;
513  } else {
514 
523  $existing_logs = glob(SQ_LOG_PATH . '/' . $logname . SQ_CONF_LOG_EXTENSION . '*');
524 
529  $log_id = $max_index;
530  while ($log_id > 0) {
531  $full_log_path = SQ_LOG_PATH . '/' . $logname . SQ_CONF_LOG_EXTENSION;
537  if ($log_id == $max_index) {
538  if (in_array($full_log_path . '.' . $max_index, $existing_logs)) {
539  $log_deleted = @unlink($full_log_path . '.' . $max_index);
540  if (!$log_deleted) {
541  return FALSE;
542  }
543  }
544 
545  $log_id--;
546  continue;
547  }
548 
549  if (in_array($full_log_path . '.' . $log_id, $existing_logs)) {
550  $status = $this->_rotateLogFiles($full_log_path, $log_id, ($log_id+1));
551  // if we can't move a log file, break out of the loop.
552  if (!$status) {
553  break;
554  }
555  }
556  $log_id--;
557  }
558 
562  $status = $this->_rotateLogFiles(SQ_LOG_PATH.'/'.$logname.SQ_CONF_LOG_EXTENSION, '', '1');
563 
564  if ($status) {
565  $rotate_hr = $this->attr('log_rotate_time');
566  $time_to_rotate = mktime($rotate_hr, '0', '0');
567  $log_data['rotation']['interval'] = $interval;
568  $log_data['rotation']['min_index'] = $min_index;
569  $log_data['rotation']['max_index'] = $max_index;
570  $log_data['rotation']['last_timestamp'] = $time_to_rotate;
571  } else {
572  $status = FALSE;
573  }
574 
575  }
576  return $status;
577 
578  }//end rotateLog()
579 
580 
593  protected function _rotateLogFiles($file, $current_rotation, $new_rotation)
594  {
595  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
596  if (file_exists($file)) {
597  $orig_filename = $file;
598  if (!empty($current_rotation)) {
599  $orig_filename .= '.' . $current_rotation;
600  }
601  // Bug #4499 - if the file is locked a move file will not capture log data, so copy and truncate
602  if (copy_file($orig_filename, $file.'.'.$new_rotation)) {
603  return truncate_file($orig_filename);
604  } else {
605  return FALSE;
606  }//end if
607  } else {
608  return FALSE;
609  }
610 
611  }//end _rotateLogFiles()
612 
613 
623  public function getCommunicatorUrl()
624  {
625  return $this->getBackendHref().'&'.$this->getPrefix().'_direct_connection=true&ignore_frames=1';
626 
627  }//end getCommunicatorUrl()
628 
629 
638  public function paintBackend(Backend_Outputter $o)
639  {
640  $prefix = $this->getPrefix();
641 
642  $direct = array_get_index($_REQUEST, $prefix.'_direct_connection', FALSE);
643 
644  // if a direct connection is requested, pass the control
645  // to the direct Communicator, otherwise take normal course of action
646  if ($direct) {
647  $this->_directCommunicator();
648  exit; // need this since Matrix tries to print extras after this section is done
649  } else {
650  parent::paintBackend($o);
651  }
652 
653  }//end paintBackend()
654 
655 
664  protected function _directCommunicator()
665  {
666  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
667 
668  $prefix = $this->getPrefix();
669 
670  $action = array_get_index($_REQUEST, $prefix.'_action');
671  $value = array_get_index($_REQUEST, $prefix.'_value');
672  $logname = array_get_index($_REQUEST, $prefix.'_log');
673  $offset = array_get_index($_REQUEST, $prefix.'_offset');
674  $numlines= array_get_index($_REQUEST, $prefix.'_num_lines');
675 
676  $content = '';
677 
678  if (empty($action)) {
679  echo translate('no_action_supplied');
680  return TRUE;
681 
682  } else if ($action == 'monitor' && !empty($logname)) {
683  $num_lines = empty($value) ? (int)$numlines : (int)$value;
684  $filename = SQ_LOG_PATH.'/'.$logname.SQ_CONF_LOG_EXTENSION;
685 
686  if (!is_readable($filename)) {
687  echo translate('file_not_readable');
688  return TRUE;
689  }
690 
691  if (empty($offset)) {
692  $lines = get_last_lines_from_file($filename, $num_lines);
693  $new_offset = filesize($filename);
694  } else {
695  $handle = fopen($filename, 'r');
696  if (!$handle) {
697  echo translate('cannot_open_file');
698  return;
699  }
700 
701  $error_status = fseek($handle, $offset);
702  if ($error_status) {
703  fclose($handle);
704  $lines = get_last_lines_from_file($filename, $num_lines);
705  $new_offset = filesize($filename);
706  } else {
707  while (!feof($handle)) {
708  $lines[] = rtrim(fgets($handle));
709  }
710 
711  $new_offset = ftell($handle);
712  fclose($handle);
713  if (count($lines) < $num_lines) {
714  $old = $lines;
715  $lines = get_last_lines_from_file($filename, $num_lines);
716  $new_offset = filesize($filename);
717  }
718  }
719  }
720 
721  $date_string = date('G:i:s - d M');
722  $content = '
723  <table class="sq-backend-table">
724  <tr>
725  <th style="width: 150px">'.translate('metadata').'</th>
726  <th>'.translate('message').'</th>
727  </tr>';
728 
729  if (empty($lines)) {
730  $content .= translate('empty');
731  } else {
732  foreach ($lines as $line) {
733  if (empty($line)) continue;
734  $d_line = $this->_decodeLine($line);
735  if ($d_line !== FALSE) {
736  $message = $d_line['data'];
737  if (is_array($message)) {
738  $message = '<pre>'.print_r($message, TRUE).'</pre>';
739  }
740  $message = htmlspecialchars($message);
741  $message = nl2br($message);
742  $error_type_name = get_error_name($d_line['level']);
743  $error_type_colour = get_error_colour($d_line['level']);
744  $log_date = date('G:i:s - d M',$d_line['date']);
745 
746  $content .= '
747  <tr>
748  <td style="background-color: '.$error_type_colour.'; color: white">
749  '.$log_date.'<br />
750  '.translate('user').':&nbsp;<strong>'.$d_line['user'].'</strong>&nbsp;('.$d_line['userid'].')<br />
751  '.translate('level').':&nbsp;<strong>'.$error_type_name.'</strong><br />
752  </td>
753  <td><span style="font-family: \'Courier New\', Courier, monospace;">'.$message.'</span></td>
754  </tr>';
755  } else {
756  $line = htmlspecialchars($line);
757  $line = nl2br($line);
758  $content .= '
759  <tr class="alt">
760  <td style="color: red; background-color: #eef; text-align: right;">
761  <strong>'.translate('raw_entry').':</strong>
762  </td>
763  <td><span style="font-family: \'Courier New\', Courier, monospace;">'.$line.'<span></td>
764  </tr>';
765  }
766 
767  }//end foreach
768  }//end else
769 
770  $content .= '</table>';
771 
772  } else {
773  trigger_localised_error('CORE0125', E_USER_WARNING);
774  return TRUE;
775  }
776 
777  ?>
778  <html>
779  <head>
780  <title><?php echo translate('log_monitor'); ?></title>
781  <link rel="stylesheet" type="text/css" href="<?php echo sq_web_path('lib');?>/web/css/edit.css" />
782  <script type="text/javascript">
783  //<![CDATA[
790  function getEltPosition(elt)
791  {
792  var posX = 0;
793  var posY = 0;
794  while (elt != null) {
795  posX += elt.offsetLeft;
796  posY += elt.offsetTop;
797  elt = elt.offsetParent;
798  }
799  return {x:posX,y:posY};
800 
801  }//end getEltPosition()
802 
808  function scrolToBottom()
809  {
810  elt = document.getElementById("sq-log-end");
811  coords = getEltPosition(elt);
812 
813  window.scrollTo(0,coords.y);
814  return true;
815 
816  }//end getEltPosition()
817  //]]>
818  </script>
819  </head>
820  <body onload="scrolToBottom(); parent.setLastRefreshInfo('<?php echo $date_string; ?>','<?php echo count($lines); ?>','<?php echo $new_offset; ?>')">
821  <?php echo $content; ?>
822  <a name="bottom" id="sq-log-end" />
823  </body>
824  </html>
825 
826  <?php
827  return TRUE;
828 
829  }//end _directCommunicator()
830 
831 
832 }//end class
833 
834 
835 ?>