ISEPOS-2280 Added base loggers scripts #1

Open
ymlesni wants to merge 8 commits from ymlesni/shared-snippets:feature/ISEPOS-2280-mechanizm-raportowania-informacji-z-aplikacji-do-pliku-z-logami into main
3 changed files with 337 additions and 0 deletions

View File

@ -0,0 +1,138 @@
%
% -----------------
% Copyright © 2024 ACK Cyfronet AGH, Poland.
% -----------------
%
% BASE_LOGGER A singleton logger class for logging messages to a file.
%
% This class implements a simple logging mechanism with different log levels
% (TRACE, DEBUG, INFO, WARNING, ERROR) and writes log entries to a file.
% It follows the Singleton pattern to ensure that only one instance of the logger
% exists throughout the application.
%
% The log file is specified by the environment variable 'APP_LOG_FILE'. If
% the variable is not set, a default file 'base-logger-log.log' is used.
%
% Properties:
% fid - (private) The file identifier for the log file. This is used to write logs.
%
% Methods:
%
% getInstance() - Retrieves the singleton instance of the logger.
%
% trace(varargin) - Logs one or more messages with TRACE level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% debug(varargin) - Logs one or more messages with DEBUG level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% info(varargin) - Logs one or more messages with INFO level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% warning(varargin) - Logs one or more messages with WARNING level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% error(varargin) - Logs one or more messages with ERROR level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% delete() - Destructor method that closes the file identifier when the logger is deleted.
%
% Example Usage:
% logger = base_logger.getInstance();
% logger.info('Some info')
%
% try
% % some code causing exception
% catch err
% logger.error('An error occurred:', err);
% end
%
% See also: fopen, fclose, dbstack, fprintf
classdef base_logger < handle
properties(Access=private)
fid;
end
methods(Static, Access = public)
function obj = getInstance()
persistent instance;
if isempty(instance)
instance = base_logger();
end
obj = instance;
end
end
methods(Access=private)
function this = base_logger()
logFileName = getenv("APP_LOG_FILE");
if isempty(logFileName)
logFileName = 'base-logger-log.log';
end
this.fid = fopen(logFileName, 'a');
if this.fid == -1
error('Failed to open log file');
end
end
function log(this, level, varargin)
current_time = datestr(now, 'yyyy-mm-dd HH:MM:SS');
stack = dbstack('-completenames');
if length(stack) > 2
script_name = stack(3).name;
else
script_name = 'Unknown';
end
message = "";
for i = 1:numel(varargin)
if isa(varargin{i}, 'MException')
message = sprintf('%sError: %s\n', message, varargin{i}.message);
for j = 1:length(varargin{i}.stack)
message = sprintf('%s at %s (line %d)\n', message, varargin{i}.stack(j).name, varargin{i}.stack(j).line);
end
elseif isnumeric(varargin{i}) || isenum(varargin{i}) || islogical(varargin{i})
message = strcat(message, string(varargin{i}));
elseif ischar(varargin{i}) || isstring(varargin{i})
message = strcat(message, varargin{i});
else
message = strcat(message, 'Unsupported data type');
end
if i < numel(varargin)
message = strcat(message, ", ");
end
end
fprintf(this.fid, '%s %s %s %s\n', current_time, level, script_name, message);
end
end
methods(Access=public)
function trace(this, varargin)
this.log('TRACE', varargin{:});
end
function debug(this, varargin)
this.log('DEBUG', varargin{:});
end
function info(this, varargin)
this.log('INFO', varargin{:});
end
function warning(this, varargin)
this.log('WARNING', varargin{:});
end
function error(this, varargin)
this.log('ERROR', varargin{:});
end
function delete(this)
if this.fid ~= -1
fclose(this.fid);
end
end
end
end

View File

@ -0,0 +1,137 @@
%
% -----------------
% Copyright © 2024 ACK Cyfronet AGH, Poland.
% -----------------
%
% BASE_LOGGER A singleton logger class for logging messages to a file.
%
% This class implements a simple logging mechanism with different log levels
% (TRACE, DEBUG, INFO, WARNING, ERROR) and writes log entries to a file.
% It follows the Singleton pattern to ensure that only one instance of the logger
% exists throughout the application.
%
% The log file is specified by the environment variable 'APP_LOG_FILE'. If
% the variable is not set, a default file 'base-logger-log.log' is used.
%
% Properties:
% fid - (private) The file identifier for the log file. This is used to write logs.
%
% Methods:
%
% getInstance() - Retrieves the singleton instance of the logger.
%
% trace(varargin) - Logs one or more messages with TRACE level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% debug(varargin) - Logs one or more messages with DEBUG level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% info(varargin) - Logs one or more messages with INFO level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% warning(varargin) - Logs one or more messages with WARNING level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% error(varargin) - Logs one or more messages with ERROR level.
% @param varargin The messages to log. Can be strings, numbers, or MException objects.
%
% delete() - Destructor method that closes the file identifier when the logger is deleted.
%
% Example Usage:
% logger = base_logger.getInstance();
% logger.info('Some info')
%
% try
% % some code causing exception
% catch err
% logger.error('An error occurred:', err);
% end
%
% See also: fopen, fclose, dbstack, fprintf
classdef base_logger < handle
properties
fid;
end
methods(Static)
function obj = getInstance()
persistent instance;
if isempty(instance)
instance = base_logger();
end
obj = instance;
end
end
methods
function this = base_logger()
logFileName = getenv("APP_LOG_FILE");
if isempty(logFileName)
logFileName = 'base-logger-log.log';
end
this.fid = fopen(logFileName, 'a');
if this.fid == -1
error('Failed to open log file');
end
end
function log(this, level, varargin)
current_time = strftime('%Y-%m-%d %H:%M:%S', localtime(time()));
stack = dbstack('-completenames');
if length(stack) > 2
script_name = stack(3).name;
else
script_name = 'Unknown';
end
message = sprintf('');
for i = 1:numel(varargin)
if isstruct(varargin{i}) && isfield(varargin{i}, 'message') && isfield(varargin{i}, 'stack')
error_info = varargin{i};
message = sprintf('%sError: %s\n', message, error_info.message);
for j = 1:length(error_info.stack)
message = sprintf('%s at %s (line %d)\n', message, error_info.stack(j).name, error_info.stack(j).line);
end
elseif isnumeric(varargin{i}) || islogical(varargin{i})
message = sprintf('%s%s', message, num2str(varargin{i}));
elseif ischar(varargin{i}) || isstring(varargin{i})
message = sprintf('%s%s', message, varargin{i});
else
message = sprintf('%sUnsupported data type', message);
end
if i < numel(varargin)
message = sprintf('%s, ', message);
end
end
fprintf(this.fid, '%s %s %s %s\n', current_time, level, script_name, message);
end
function trace(this, varargin)
this.log('TRACE', varargin{:});
end
function debug(this, varargin)
this.log('DEBUG', varargin{:});
end
function info(this, varargin)
this.log('INFO', varargin{:});
end
function warning(this, varargin)
this.log('WARNING', varargin{:});
end
function error(this, varargin)
this.log('ERROR', varargin{:});
end
function delete(this)
if this.fid ~= -1
fclose(this.fid);
end
end
end
end

View File

@ -0,0 +1,62 @@
#
# -----------------
# Copyright © 2024 ACK Cyfronet AGH, Poland.
# -----------------
#
import os
import logging
def getDefaultLogger(name):
"""
Retrieves or creates a logger with the specified name and sets it up with a file handler.
The logger is configured to write log messages to the file path specified by the
'APP_LOG_FILE' environment variable. If the environment variable is not set,
the logger will write to the file 'base-logger-log.log' in the current
working directory. The logger uses the 'INFO' level as the default logging level
and writes log entries in the following format:
'YYYY-MM-DD HH:MM:SS,ms LEVEL logger_name message'
If the logger does not already have handlers, a file handler is created, and the
logging output is appended to the file. The log format includes the timestamp with
milliseconds, log level, logger name, and the log message.
Parameters:
-----------
name : str
The name of the logger. This can be the name of the module or any identifier
that you want to associate with the logger.
Returns:
--------
logger : logging.Logger
A logger instance with the specified name. The logger is configured with a
file handler that writes to the file specified by the 'APP_LOG_FILE'
environment variable, or to 'base-logger-log.log' if the environment
variable is not set.
Example:
Review

Here, again, the name of the variable should be updated. But I thought also that instead of throwing an error, we might use some default file name, to allow users to run the app also outside of the platform. But, I would set the default log file name to something different than "application.log", so that we know that we could notice if something is wrong with setting the environment variable.

Here, again, the name of the variable should be updated. But I thought also that instead of throwing an error, we might use some default file name, to allow users to run the app also outside of the platform. But, I would set the default log file name to something different than "application.log", so that we know that we could notice if something is wrong with setting the environment variable.
--------
logger = getDefaultLogger(__name__)
logger.info("This is an info message.")
try:
# some code causing exception
except Exception:
logger.exception('An error occurred')
Notes:
------
- The 'APP_LOG_FILE' environment variable should specify the full path to the log file.
- If 'APP_LOG_FILE' is not set, logs will be written to 'base-logger-log.log'.
"""
logger = logging.getLogger(name)
if not logger.hasHandlers():
file_handler = logging.FileHandler(os.environ.get('APP_LOG_FILE', 'base-logger-log.log'), mode='a')
formatter = logging.Formatter('%(asctime)s,%(msecs)d %(levelname)s %(name)s %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.setLevel(logging.INFO)
return logger