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:
Outdated
Review

Can we put here an example with error object? So that the stack trace of the exception gets logged.

Can we put here an example with error object? So that the stack trace of the exception gets logged.

Base logger has been enchanced and example for error logging has been added.

Base logger has been enchanced and example for error logging has been added.
% 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()
asia marked this conversation as resolved Outdated
Outdated
Review

Can we use a different name? Something that we would consider a log file at first glance ('fallbackPath' is something I wouldn't) - if we don't want to make it too simple, to avoid name collision with other things, maybe e.g. base-app-log.log or base-logger-log.log or something like that

Can we use a different name? Something that we would consider a log file at first glance ('fallbackPath' is something I wouldn't) - if we don't want to make it too simple, to avoid name collision with other things, maybe e.g. base-app-log.log or base-logger-log.log or something like that
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,
Outdated
Review

I think the name was changed to 'APP_LOG_FILE'

I think the name was changed to 'APP_LOG_FILE'
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
Outdated
Review

An example with an error with logging stack trace would be convenient here

An example with an error with logging stack trace would be convenient here

Example for error with stack trace logging has been added.

Example for error with stack trace logging has been added.
except Exception:
logger.exception('An error occurred')
Outdated
Review

Maybe just put here some placeholder meaning that this is a code that may produce an error

Maybe just put here some placeholder meaning that this is a code that may produce an error

Ok, I will do the same for Matlab and Octave versions.

Ok, I will do the same for Matlab and Octave versions.
Notes:
Outdated
Review

We have except Exception as e but the e is not used anywhere... is this correct?

We have `except Exception as e` but the `e` is not used anywhere... is this correct?

Maybe it is good to use here the logger.exception() (doc) method as a showcase.

Maybe it is good to use here the `logger.exception()` ([doc](https://docs.python.org/3/library/logging.html#logging.Logger.exception)) method as a showcase.

e could be used to process exception further, but it is not necessary for example, I will remove it.
Would adding logger.exception() as a additional option be a good idea (to still showcase how to log exception at chosen log level)? For now I will change to logger.exception().

`e` could be used to process exception further, but it is not necessary for example, I will remove it. Would adding `logger.exception()` as a additional option be a good idea (to still showcase how to log exception at chosen log level)? For now I will change to `logger.exception()`.
------
- 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