Logging
In Jalno, logs are used instead of comments. Using logs increases readability and improves the UX for other programmers. Logs also make it easier to debug projects. In fact, logs are descriptions written among the code, each of which—according to its level—conveys specific information about the program. This information includes the execution flow, precise and low-level details such as the queries sent to the database, the errors received, and so on.
To work with logs, the packages\base\Log class has been created.
To use logs, you must first create an object of the Log class via the getInstance() method.
$log = Log::getInstance();
Logs are stored automatically in the packages/base/storage/protected/logs path, with each day's logs saved in a separate file named after that day's date.
Each log is a separate line in the log file, composed—in order—of the columns for the storage time, the level, and the log message.
Configuration
The settings related to logs are stored in Jalno's configuration file (config.php), which is located at the path packages/base/libraries/config.
Setting the Log Level
The packages.base.logging.level setting determines which levels of logs are stored.
If you want all logs to be stored in the file, set the value of the setting to debug.
'packages.base.logging.level' => 'debug' // info, warn, error, fatal, off
Enabling this setting stores all logs from the very start of Jalno's loading. Using the setLevel(string $level): void method at the beginning of a specific method, you can enable logs only for that method and the methods called after it. This method receives one of the log levels as its single argument.
Displaying Logs
The packages.base.logging.quiet setting determines whether logs are also displayed in the browser.
If it is true, logs are only stored in the file and are not displayed in the browser.
'packages.base.logging.quiet' => true // false
Log Levels
Logs have various levels, which include the following:
Leveling the logs means that, for example, when you intend to inspect the program's execution flow you set the log level to info, and when you intend to inspect the program in detail and view more low-level information you set the log level to debug.
For the methods that work with log levels, you can define any number of arguments. The arguments are placed in the log message in order.
| Level | Usage |
|---|---|
| debug | Complete program information, which can be sensitive data or the program's flow |
| info | Descriptions of the program's execution flow |
| warn | Descriptions of the program's flow that require more scrutiny or sensitivity, displayed as a warning |
| error | Used to display the program's errors |
| fatal | Errors that halt the program |
The debug Level
The debug() level is used to record low-level information such as the query sent to the database. debug stores the finest and most detailed information in the log.
Example
use packages\base\Log;
Log::setLevel("debug"); // Log::setLevel("info");
$log = Log::getInstance();
$log->debug("current service status: ", $service->service->status);
if ($service->service->status == Service::SUSPENDED) {
$log->info("unsuspending");
if ($this->dryRun) {
$log->reply("dry-Run");
} else {
$service->service->unsuspend();
$log->reply("Success");
}
}
return;
In the example above, the program's flow is written using the debug and info methods. If the log level is set to debug, the entire program flow is stored; and if the level is set to info, only the program flows written using the info method are stored.
Also, in Jalno, all the important and vital actions needed for troubleshooting and debugging are automatically written using the debug log level. If you have set the log level to debug, this information is also stored or displayed.
Example of a sample log file
2020-10-19 11:37:46.15401000 +00:00 [DEBUG] SQL Query: SELECT value AS retval FROM options WHERE name = 'packages.base.translator.active.langs' LIMIT 1
2020-10-19 11:37:46.15945600 +00:00 [DEBUG] SQL Query: SELECT value AS retval FROM options WHERE name = 'packages.base.cache' LIMIT 1
The info Level
The info() method is used to store descriptions of the program's execution flow. In the example below, you want to find a user in the database by their mobile-number attribute; you store these descriptions in the log using info.
Example 1
use packages\base\Log;
// Log::setLevel("info");
$log = Log::getInstance();
if (!$user and isset($parameters["inputs"]["cellphone"])) {
$log->info("try to find user by cellphone:", $parameters["inputs"]["cellphone"]);
$user = (new User)->where("cellphone", $parameters["inputs"]["cellphone"])->getOne();
if ($user) {
$log->reply("found, #", $user->id);
} else {
$log->reply("notfound");
}
}
Sample of info in the log file
2020-10-17 14:55:48.93523900 +00:00 [INFO] method: get
2020-10-17 14:55:48.93526500 +00:00 [INFO] scheme: http
2020-10-17 14:55:48.93529600 +00:00 [INFO] hostname: www.domain.com
2020-10-17 14:55:48.93533500 +00:00 [INFO] uri: /favicon.ico
2020-10-17 14:55:48.93536400 +00:00 [INFO] url parameters: []
2020-10-17 14:57:35.77234100 +00:00 [INFO] try to find user by cellphone: 09131234567 : notfound
If the log level is set to debug, the sample file will be as follows.
Sample file
2020-10-17 14:57:35.77234100 +00:00 [INFO] try to find user by cellphone: 09131234567
2020-10-17 14:37:36.15945600 +00:00 [DEBUG] SQL Query: SELECT * FROM userpanel_users WHERE cellphone = '09131234567' LIMIT 1
2020-10-17 14:57:36.77234100 +00:00 [INFO] try to find user by cellphone: 09131234567 : notfound
Example 2
use packages\base\Log;
Log::setLevel("info");
$l = Log::getInstance();
$l->info("change teacher name of class", $class->id);
if($inputs['name']){
$class->teacher_name = $inputs['name'];
}
Example 3
use packages\base\Log;
Log::setLevel("info");
$l = Log::getInstance();
$l->info("get logs that has not title");
$type = db::subQuery();
$type->where("name", "userpanel_users_edit");
$types = $type->get("userpanel_usertypes_permissions", null, "userpanel_usertypes_permissions.type");
The error Level
The error() method is used to store the program's errors. These errors can be fatal errors or unwanted responses in the program's flow.
If the log level is set to debug or even info, the logs of this level are also stored.
Example
use packages\base\Log;
use packages\base\View\Error;
$log = Log::getInstance();
$log->info("Get services");
$services = (new Service)->where("server_id", $data["server"])->get();
if (!$services) {
$log->error("Unable to find any service");
throw new Error("Unable to find any service");
}
Sample log file
2020-10-20 13:51:56.41038100 +00:00 [INFO] Get services
2020-10-20 13:51:56.41041900 +00:00 [ERROR] Unable to find any service
The fatal Level
The fatal() method is used to store the description of errors that cause the program's execution to halt.
In the example below, the program halts because the host is not set. The description of the program's halt is stored in the log using fatal.
If the log level is set to debug, error, or even info, the logs of this level are also stored.
If the log level is set to fatal, only fatal logs are stored.
Example
use packages\base\Log;
Log::setLevel("fatal");
$log = Log::getInstance();
if (empty($this->host)) {
$log->fatal('MySQL host is not set');
throw new \Exception('MySQL host is not set');
}
Sample of fatal in the log file
2020-10-17 19:11:17.43898700 +00:00 [FATAL] MySQL host is not set
The warn Level
You use the warn() method to store program flows that are more sensitive or require review, such as queries whose result is not quite acceptable.
If the log level is set to debug or even info, the logs of this level are also stored.
Example
use packages\base\Log;
Log::setLevel("info");
$log = Log::getInstance();
$log->info("FOUND. ip", $dir->basename, "is blong to server", $param->server);
$server = (new Server)->where("status", Server::ACTIVE)->byId($param->server);
if (!$server) {
$log->reply()->warn("is not exists or is disabled");
return;
}
In the example above, you fetch the active services from the database; if you receive no service, the result is illogical, but the program does not leave its flow.
Sample log file
2020-10-20 13:34:43.07133600 +00:00 [INFO] FOUND. ip 127.0.0.2 is blong to server 127.0.0.1
2020-10-20 13:34:43.07137900 +00:00 [WARN] FOUND. ip 127.0.0.2 is blong to server 127.0.0.1: is not exists or is disabled
The reply Level
You can use the reply() method to specify a result.
When, at various log levels, a program-execution flow or low-level information such as a query sent to the database is written that can have a result, the result is specified using the reply method. The reply method stores the result following the last log line.
For this method, you can define any number of input arguments. Finally, the arguments are converted to a string and stored in the log file.
The logs of this level are stored at all configured log levels.
If you want to specify the level of the result designated by reply, you call the desired level on the reply method.
$log->reply()->debug("log message");
$log->reply()->error("log message");
$log->reply()->fatal("log message");
$log->reply()->warn("log message");
Example 1
use packages\base\Log;
public function findUser($data) {
Log::setLevel("info");
$log = Log::getInstance();
$log->info("try to find user by id: '{$data["id"]}'");
$user = User::byId($data['id']);
if($user){
$log->reply("found user");
}else {
$log->reply()->warn("notfound");
}
}
In the example above, info specifies that it is searching for the user with the given id. Then, the result of whether it was found or not is specified in reply.
Sample log file
2020-10-18 16:49:10.61545200 +00:00 [INFO] try to find user by id: '149': found user
In the example below, debug is used to indicate that here you intend to connect to mysql.
Then, the result is specified using reply.
Example 2: Nested logs
use packages\base\Log;
public function mysqli() {
Log::setLevel("debug");
if (!$this->_mysqli) {
$log = log::getInstance();
$log->debug("connect to mysql");
$this->connect();
$log->reply("Success");
}
return $this->_mysqli;
}
public function connect() {
if ($this->isSubQuery) {
return;
}
$log = log::getInstance();
if (false and true or empty($this->host)) {
$log->warn('MySQL host is not set');
throw new \Exception('MySQL host is not set');
}
$log->info('connect to '.$this->username.'@'.$this->host.':'.$this->port.'/'.$this->db);
$this->_mysqli = @new \mysqli($this->host, $this->username, $this->password, $this->db, $this->port);
if ($this->_mysqli->connect_error) {
$mysqli = $this->_mysqli;
$this->_mysqli = null;
$log->reply()->fatal($mysqli->connect_errno . ': ' . $mysqli->connect_error);
throw new \Exception('Connect Error ' . $mysqli->connect_errno . ': ' . $mysqli->connect_error);
}
$log->reply("Success");
if ($this->charset) {
$log->debug("set charset to", $this->charset);
$this->_mysqli->set_charset($this->charset);
$log->reply("Success");
}
}
Sample log file
2020-10-18 18:22:54.89225800 +00:00 [DEBUG] connect to mysql
2020-10-18 18:22:54.89238400 +00:00 [INFO] connect to root@localhost:3306/jalno
2020-10-18 18:22:54.89283600 +00:00 [INFO] connect to root@localhost:3306/jalno: Success
2020-10-18 18:22:54.89292100 +00:00 [DEBUG] set charset to utf8mb4
2020-10-18 18:22:54.89311100 +00:00 [DEBUG] set charset to utf8mb4: Success
2020-10-18 18:22:54.89320200 +00:00 [DEBUG] connect to mysql: Success
In the example above, after debug, the connect method is called, in which the connection details are specified with info, and finally the connection result is specified in reply.
reply is appended following the last method called on the corresponding object.
Example 3: Specifying the level for reply
use packages\base\Log;
use packages\base\Exception;
function check($data) {
$log = Log::getInstance();
$log->info("try to find user by id: '{$data["id"]}'");
$user = User::byId($data['id']);
if($user){
$log->reply("found user");
$log->info("verify status");
if ($user->status != 1) {
$log->reply()->fatal("invalid user status");
throw new Exception("invalid user status");
}
}else {
$log->reply()->warn("notfound");
}
}
Sample log file
2020-10-18 18:22:54.94021900 +00:00 [INFO] try to find user by id: '{149}'
2020-10-18 18:22:54.94025300 +00:00 [INFO] try to find user by id: '{149}': found user
2020-10-18 18:22:54.94028300 +00:00 [INFO] verify status
2020-10-18 18:22:54.94031400 +00:00 [FATAL] verify status: invalid user status
Example 4
public function findUser(string $email) {
$log = Log::getInstance();
$log->info("try to find user by email: ", $email);
$user = User::where('email', $email)->getOne();
if($user){
$log->reply("found user, id: '{$user->id}'");
return $user->id;
}else {
$log->reply()->warn("notfound");
return false;
}
}
public function showInfo($data) {
Log::setLevel("info");
$log = Log::getInstance();
$log->info("get user by email: ", $data["email"]);
$user = $this->findUser($data["email"]);
if ($user) {
$log->info("show user information");
$view->info = $user;
}
}
Log file
2020-10-18 20:59:45.85419300 +00:00 [INFO] get user by email: [email protected]
2020-10-18 20:59:45.85426100 +00:00 [INFO] try to find user by email: [email protected]
2020-10-18 20:59:45.85431200 +00:00 [INFO] try to find user by email: [email protected]: found user, id: '{180}'
2020-10-18 20:59:45.85436200 +00:00 [INFO] show user information
Setting the Log File
Using the setFile(string $path): void method, you can change the file where logs are stored. The input argument of this method is a string, which receives the path of the desired file.
In whichever method the setFile method is called, the logs of that same method are stored in the defined file.
use packages\base\Log;
Log::setFile("packages/my_package/storage/private/logs/logname.log");
Appending Text to a Log
The append() method is used to add text following a log.
The text is added following the last stored log.
use packages\base\Log;
use packages\base\View\Error;
$log = Log::getInstance();
$log->info("Get services");
$services = (new Service)->where("server_id", $data["server"])->get();
if (!$services) {
$log->error("Unable to find any service");
throw new Error("Unable to find any service");
}
$log->reply(count($services), " services found");
if (count($services) > 1) {
$log->append(". Show services to select");
Sample log file
2020-10-20 13:51:56.41038100 +00:00 [INFO] Get services
2020-10-20 13:51:56.41041900 +00:00 [INFO] Get services: 2 services found
2020-10-20 13:51:56.41042300 +00:00 [INFO] Get services: 2 services found. Show services to select