Skip to content
Snippets Groups Projects
Commit cf9f51d0 authored by Marius Burkard's avatar Marius Burkard
Browse files

Merge branch '6302-slightly-improved-debug-logging' into 'develop'

feat: Add filename/line data to DEBUG messages

Closes #6302

See merge request ispconfig/ispconfig3!1580
parents 60fa055c 0ce6a642
No related branches found
No related tags found
1 merge request!1580feat: Add filename/line data to DEBUG messages
Pipeline #10813 passed
.DS_Store
.idea .idea
/nbproject/private/ /nbproject/private/
.phplint-cache .phplint-cache
*.swp *.swp
# macOS-specific things to exclude
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
Icon?
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Configuration for the Nova editor
.nova
\ No newline at end of file
<?php <?php
/**
/* Copyright (c) 2007-2022, Till Brehm, projektfarm Gmbh
Copyright (c) 2007, Till Brehm, projektfarm Gmbh
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
...@@ -28,35 +27,52 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, ...@@ -28,35 +27,52 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
//* Set timezone // Set timezone
if(isset($conf['timezone']) && $conf['timezone'] != '') date_default_timezone_set($conf['timezone']); if(isset($conf['timezone']) && $conf['timezone'] != '') { // note: !empty($conf['timezone']) should give the same result and is more idiomatic for current versions of PHP (gwyneth 20220315)
date_default_timezone_set($conf['timezone']);
}
/**
* Class for defining (mostly static) methods that are commonly used across the whole application.
*
* @category unknown
* @package server
* @author Till Brehm
* @license bsd-3-clause
* @link empty
**/
class app { class app {
/** @var array List of modules that have been loaded. */
var $loaded_modules = array(); var $loaded_modules = [];
var $loaded_plugins = array(); /** @var array List of plugins that have been loaded. */
var $loaded_plugins = [];
/** @var callable Script calling this. */
var $_calling_script = ''; var $_calling_script = '';
/** /** @var resource? Database used for ISPConfig3. */
* @var db
*/
public $db; public $db;
/**
* Class constructor, which depends on the global configuration stored in $conf.
*
* @param void
* @return void
*/
function __construct() { function __construct() {
/** @var object Global object storing this application's configuration. */
global $conf; global $conf;
if($conf['start_db'] == true) { if($conf['start_db'] == true) {
$this->load('db_'.$conf['db_type']); $this->load('db_' . $conf['db_type']);
try { try {
$this->db = new db; $this->db = new db;
} catch (Exception $e) { } catch(Exception $e) {
$this->db = false; $this->db = false;
} }
/* /*
Initialize the connection to the master DB, Initialize the connection to the master DB,
if we are in a multiserver setup if we are in a multiserver setup
*/ */
if($conf['dbmaster_host'] != '' && ($conf['dbmaster_host'] != $conf['db_host'] || ($conf['dbmaster_host'] == $conf['db_host'] && $conf['dbmaster_database'] != $conf['db_database']))) { if($conf['dbmaster_host'] != '' && ($conf['dbmaster_host'] != $conf['db_host'] || ($conf['dbmaster_host'] == $conf['db_host'] && $conf['dbmaster_database'] != $conf['db_database']))) {
try { try {
...@@ -67,14 +83,19 @@ class app { ...@@ -67,14 +83,19 @@ class app {
} else { } else {
$this->dbmaster = $this->db; $this->dbmaster = $this->db;
} }
} }
} // end constructor
} /**
* Getter method for some of the (valid) proprieties.
*
* @param string $name A valid property name to get. Will be checked for validity first!
*
* @return mixed
*/
public function __get($name) { public function __get($name) {
$valid_names = array('functions', 'getconf', 'letsencrypt', 'modules', 'plugins', 'services', 'system'); /** @var array List of all possible proprieties that are valid to get. */
$valid_names = ['functions', 'getconf', 'letsencrypt', 'modules', 'plugins', 'services', 'system'];
if(!in_array($name, $valid_names)) { if(!in_array($name, $valid_names)) {
trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING); trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING);
} }
...@@ -89,14 +110,37 @@ class app { ...@@ -89,14 +110,37 @@ class app {
} }
} }
/**
* Sets the calling script.
*
* @param callable $caller Calling script function.
*
* @return void
*/
function setCaller($caller) { function setCaller($caller) {
$this->_calling_script = $caller; $this->_calling_script = $caller;
} }
/**
* Gets the calling script.
*
* Note that there is no error checking!
*
* @param void
*
* @return callable|null
*/
function getCaller() { function getCaller() {
return $this->_calling_script; return $this->_calling_script;
} }
/**
* Emergency exit funcion.
*
* @param string $errmsg Error message to be displayedby the die() command on exit.
*
* @return void
*/
function forceErrorExit($errmsg = 'undefined') { function forceErrorExit($errmsg = 'undefined') {
global $conf; global $conf;
...@@ -106,16 +150,27 @@ class app { ...@@ -106,16 +150,27 @@ class app {
die('Exiting because of error: ' . $errmsg); die('Exiting because of error: ' . $errmsg);
} }
/**
* Dynamic plugin loader and instantiator.
*
* This will include PHP scripts on demand, each representing a class to be loaded,
* and if the process succeeds, it will retrieve an instance for the class.
*
* @param string $classes A list of plugin classes to be loaded (e.g. their files will be included)
* and subsequently instantiated; it's a comma-separated string.
*
* @return void
*/
function uses($classes) { function uses($classes) {
global $conf; global $conf;
/** @var array|null List of classes to be used, as an array, after successful 'explosion' */
$cl = explode(',', $classes); $cl = explode(',', $classes);
if(is_array($cl)) { if(is_array($cl)) {
foreach($cl as $classname) { foreach($cl as $classname) {
if(!@is_object($this->$classname)) { if(!@is_object($this->$classname)) {
if(is_file($conf['classpath'].'/'.$classname.'.inc.php') && (DEVSYSTEM || !is_link($conf['classpath'].'/'.$classname.'.inc.php'))) { if(is_file($conf['classpath'] . '/' . $classname . '.inc.php') && (DEVSYSTEM || !is_link($conf['classpath'] . '/' . $classname . '.inc.php'))) {
include_once $conf['classpath'].'/'.$classname.'.inc.php'; include_once $conf['classpath'] . '/' . $classname . '.inc.php';
$this->$classname = new $classname; $this->$classname = new $classname;
} }
} }
...@@ -123,64 +178,119 @@ class app { ...@@ -123,64 +178,119 @@ class app {
} }
} }
/**
* Dynamic plugin loader (no instantation).
*
* Similar to uses() but does _not_ instantate a new class; files are merely included.
* die() is called on a failure to include the file for a class.
*
* @param string $classes A list of plugin classes to be loaded (e.g. their files will be included);
* it's a comma-separated string.
*
* @return void
*/
function load($classes) { function load($classes) {
global $conf; global $conf;
/** @var array|null List of classes to be loaded, as an array, after successful 'explosion' */
$cl = explode(',', $classes); $cl = explode(',', $classes);
if(is_array($cl)) { if(is_array($cl)) {
foreach($cl as $classname) { foreach($cl as $classname) {
if(is_file($conf['classpath'].'/'.$classname.'.inc.php') && (DEVSYSTEM || !is_link($conf['classpath'].'/'.$classname.'.inc.php'))) { if(is_file($conf['classpath'] . '/' . $classname . '.inc.php') && (DEVSYSTEM || !is_link($conf['classpath'] . '/' . $classname . '.inc.php'))) {
include_once $conf['classpath'].'/'.$classname.'.inc.php'; include_once $conf['classpath'] . '/' . $classname . '.inc.php';
} else { } else {
die('Unable to load: '.$conf['classpath'].'/'.$classname.'.inc.php'); die('Unable to load: ' . $conf['classpath'] . '/' . $classname . '.inc.php');
} }
} }
} }
} }
/* /**
0 = DEBUG * Logs a message with a certain priority to the different log backends.
1 = WARNING *
2 = ERROR * This method will check if the priority is equal or larger than what the user has
*/ * defined as the minimum logging level, and will output to several logging facilities:
* - At the very least, the message will _usually_ go to stdout;
* - It may optionally also go to the file log (usually `/var/log/ispconfig/ispconfig.log`)
* which will be created if it doesn't exist;
* - When the $dblog parameter is set to true (the default), the message will also be logged
* to the database;
* - If the system is configured to send email messages to the administrator,
* this method will also handle those (assuming, again, that the priority matches).
*
* Debugging messages will also have the name of the calling module/script as well as a line number
* to assist error tracking (gwyneth 20220315). This incurs in a slight performance hit.
*
* @param string $msg The message to be logged.
* @param int $priority Should be set to 0 = DEBUG, 1 = WARNING or 2 = ERROR; anything else
* will skip setting the priority textual variable.
* @param bool $dblog Should the message also be logged to the database? (Default is _true_)
*
* @return void
*
* @note The error() method below seems to write to an invalid priority (3), which will cause
* no message priority text to be emitted, and will _force_ a database write and/or sending
* an email to the administrator.
*/
function log($msg, $priority = 0, $dblog = true) { function log($msg, $priority = 0, $dblog = true) {
global $conf; global $conf;
/**
* @var string $file_line_caller
*
* For debugging, deal with retrieving caller information from the stack. (gwyneth 20220315)
* See https://stackoverflow.com/q/1252529/1035977 (including the precious comments!) for an explanation
* of how this works.
**/
$file_line_caller = "";
/** @var string Defined here because recent versions of PHP are stricter with scoping issues. (gwyneth 20220315) */
$priority_txt = '';
switch ($priority) { switch ($priority) {
case 0: case 0:
$priority_txt = 'DEBUG'; $priority_txt = 'DEBUG';
break; $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); // we don't need _all_ data, so we save some processing time here (gwyneth 20220315)
$caller = array_shift($bt);
if(!empty($caller['file']) && !empty($caller['line'])) {
$file_line_caller = '[' . strtr(basename($caller['file'], '.php'), '_', ' ') . ':' . $caller['line'] . '] ';
}
break;
case 1: case 1:
$priority_txt = 'WARNING'; $priority_txt = 'WARNING';
break; break;
case 2: case 2:
$priority_txt = 'ERROR'; $priority_txt = 'ERROR';
break; break;
// Note: $this->error() seems to use case 3 to deliberately skip setting a priority text.
// It will also *force* a write to the logs and/or send emails. (gwyneth 20220315)
} }
$log_msg = @date('d.m.Y-H:i').' - '.$priority_txt.' - '. $msg;
/** @var string Formatted message to be sent to the logging subsystems. */
$log_msg = @date('d.m.Y-H:i') . ' - ' . $priority_txt .' ' . $file_line_caller . '- '. $msg;
// Check if the user-set priority defines that this message should be output at all.
if($priority >= $conf['log_priority']) { if($priority >= $conf['log_priority']) {
//if (is_writable($conf["log_file"])) { // Prepare to add a line on the logfile, or to create the logfile in
if (!$fp = fopen($conf['log_file'], 'a')) { // append mode if it doesn't exist yet. Failure means that die() is called.
//if(is_writable($conf["log_file"])) {
if(!$fp = fopen($conf['log_file'], 'a')) {
die('Unable to open logfile.'); die('Unable to open logfile.');
} }
if (!fwrite($fp, $log_msg."\r\n")) { if(!fwrite($fp, $log_msg . '\r\n')) {
die('Unable to write to logfile.'); die('Unable to write to logfile.');
} }
echo $log_msg."\n"; echo $log_msg . "\n";
fclose($fp); fclose($fp);
// Log to database // Log to database.
if($dblog === true && isset($this->dbmaster)) { if($dblog === true && isset($this->dbmaster)) {
$server_id = $conf['server_id']; $server_id = $conf['server_id'];
$loglevel = $priority; $loglevel = $priority;
$message = $msg; $message = $msg;
$datalog_id = (isset($this->modules->current_datalog_id) && $this->modules->current_datalog_id > 0)?$this->modules->current_datalog_id:0; $datalog_id = (isset($this->modules->current_datalog_id) && $this->modules->current_datalog_id > 0)? $this->modules->current_datalog_id : 0;
if($datalog_id > 0) { if($datalog_id > 0) {
$tmp_rec = $this->dbmaster->queryOneRecord("SELECT count(syslog_id) as number FROM sys_log WHERE datalog_id = ? AND loglevel = ?", $datalog_id, LOGLEVEL_ERROR); $tmp_rec = $this->dbmaster->queryOneRecord("SELECT count(syslog_id) as number FROM sys_log WHERE datalog_id = ? AND loglevel = ?", $datalog_id, LOGLEVEL_ERROR);
//* Do not insert duplicate errors into the web log. //* Do not insert duplicate errors into the web log.
...@@ -198,18 +308,18 @@ class app { ...@@ -198,18 +308,18 @@ class app {
// die("Unable to write to logfile."); // die("Unable to write to logfile.");
//} //}
} // if } // if
// Send an email to the administrator if the current priority demands it.
if(isset($conf['admin_notify_priority']) && $priority >= $conf['admin_notify_priority'] && $conf['admin_mail'] != '') { if(isset($conf['admin_notify_priority']) && $priority >= $conf['admin_notify_priority'] && $conf['admin_mail'] != '') {
if($conf['hostname'] != 'localhost' && $conf['hostname'] != '') { if($conf['hostname'] != 'localhost' && $conf['hostname'] != '') {
$hostname = $conf['hostname']; $hostname = $conf['hostname'];
} else { } else {
$hostname = exec('hostname -f'); $hostname = exec('hostname -f');
} }
// send notification to admin // Send notification to admin.
$mailBody = $hostname . " - " . $log_msg; $mailBody = $hostname . " - " . $log_msg;
$mailSubject = substr("[" . $hostname . "]" . " " . $log_msg, 0, 70).'...'; $mailSubject = substr("[" . $hostname . "]" . " " . $log_msg, 0, 70) . '...';
$mailHeaders = "MIME-Version: 1.0" . "\n"; $mailHeaders = "MIME-Version: 1.0" . "\n";
$mailHeaders .= "Content-type: text/plain; charset=utf-8" . "\n"; $mailHeaders .= "Content-type: text/plain; charset=utf-8" . "\n";
$mailHeaders .= "Content-Transfer-Encoding: 8bit" . "\n"; $mailHeaders .= "Content-Transfer-Encoding: 8bit" . "\n";
...@@ -218,26 +328,30 @@ class app { ...@@ -218,26 +328,30 @@ class app {
mail($conf['admin_mail'], $mailSubject, $mailBody, $mailHeaders); mail($conf['admin_mail'], $mailSubject, $mailBody, $mailHeaders);
} }
} // func } // func log
/*
0 = DEBUG
1 = WARNING
2 = ERROR
*/
/**
* Logs a message with an undefined priority (3) and dies.
*
* This method writes to an invalid/undefined priority level (3), which will cause
* no message priority text to be emitted, but will _force_ a database write and/or sending
* an email to the administrator.
*
* @param string $msg The message to be logged.
*
* @return void
*/
function error($msg) { function error($msg) {
$this->log($msg, 3); $this->log($msg, 3); // isn't this supposed to be error code 2? (gwyneth 20220315)
die($msg); die($msg);
} }
} }
/* /**
Initialize application (app) object * @var \app $app
*/ *
* Initialize application object.
*/
$app = new app; $app = new app;
?> ?>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment