1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-08 21:18:00 +03:00

Refactoring loggging and log manager, and more linting

This commit is contained in:
Adolfo Gómez García 2023-04-20 03:10:46 +02:00
parent 3a0efbb998
commit 9030ff3ab3
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
43 changed files with 228 additions and 232 deletions

View File

@ -91,10 +91,10 @@ class RESTTestCase(test.UDSTransactionTestCase):
)
for user in self.users:
log.doLog(user, log.DEBUG, f'Debug Log for {user.name}')
log.doLog(user, log.INFO, f'Info Log for {user.name}')
log.doLog(user, log.WARNING, f'Warning Log for {user.name}')
log.doLog(user, log.ERROR, f'Error Log for {user.name}')
log.doLog(user, log.LogLevel.DEBUG, f'Debug Log for {user.name}')
log.doLog(user, log.LogLevel.INFO, f'Info Log for {user.name}')
log.doLog(user, log.LogLevel.WARNING, f'Warning Log for {user.name}')
log.doLog(user, log.LogLevel.ERROR, f'Error Log for {user.name}')
self.provider = services_fixtures.createProvider()

View File

@ -147,7 +147,7 @@ class Dispatcher(View):
logger.debug('Path: %s', full_path)
logger.debug('Error: %s', e)
log.log_operation(handler, 500, log.ERROR)
log.logOperation(handler, 500, log.LogLevel.ERROR)
return http.HttpResponseServerError(
f'Invalid parameters invoking {full_path}: {e}',
content_type="text/plain",
@ -157,17 +157,17 @@ class Dispatcher(View):
for n in ['get', 'post', 'put', 'delete']:
if hasattr(handler, n):
allowedMethods.append(n)
log.log_operation(handler, 405, log.ERROR)
log.logOperation(handler, 405, log.LogLevel.ERROR)
return http.HttpResponseNotAllowed(
allowedMethods, content_type="text/plain"
)
except AccessDenied:
log.log_operation(handler, 403, log.ERROR)
log.logOperation(handler, 403, log.LogLevel.ERROR)
return http.HttpResponseForbidden(
'access denied', content_type="text/plain"
)
except Exception:
log.log_operation(handler, 500, log.ERROR)
log.logOperation(handler, 500, log.LogLevel.ERROR)
logger.exception('error accessing attribute')
logger.debug('Getting attribute %s for %s', http_method, full_path)
return http.HttpResponseServerError(
@ -185,28 +185,28 @@ class Dispatcher(View):
for k, val in handler.headers().items():
response[k] = val
log.log_operation(handler, response.status_code, log.INFO)
log.logOperation(handler, response.status_code, log.LogLevel.INFO)
return response
except RequestError as e:
log.log_operation(handler, 400, log.ERROR)
log.logOperation(handler, 400, log.LogLevel.ERROR)
return http.HttpResponseBadRequest(str(e), content_type="text/plain")
except ResponseError as e:
log.log_operation(handler, 500, log.ERROR)
log.logOperation(handler, 500, log.LogLevel.ERROR)
return http.HttpResponseServerError(str(e), content_type="text/plain")
except NotSupportedError as e:
log.log_operation(handler, 501, log.ERROR)
log.logOperation(handler, 501, log.LogLevel.ERROR)
return http.HttpResponseBadRequest(str(e), content_type="text/plain")
except AccessDenied as e:
log.log_operation(handler, 403, log.ERROR)
log.logOperation(handler, 403, log.LogLevel.ERROR)
return http.HttpResponseForbidden(str(e), content_type="text/plain")
except NotFound as e:
log.log_operation(handler, 404, log.ERROR)
log.logOperation(handler, 404, log.LogLevel.ERROR)
return http.HttpResponseNotFound(str(e), content_type="text/plain")
except HandlerError as e:
log.log_operation(handler, 500, log.ERROR)
log.logOperation(handler, 500, log.LogLevel.ERROR)
return http.HttpResponseBadRequest(str(e), content_type="text/plain")
except Exception as e:
log.log_operation(handler, 500, log.ERROR)
log.logOperation(handler, 500, log.LogLevel.ERROR)
# Get ecxeption backtrace
trace_back = traceback.format_exc()
logger.error('Exception processing request: %s', full_path)

View File

@ -32,15 +32,10 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
import typing
from uds import models
from uds.core.util.log import (
REST,
OWNER_TYPE_AUDIT,
DEBUG,
INFO,
WARNING,
ERROR,
CRITICAL,
)
# Import for REST using this module can access constants easily
# pylint: disable=unused-import
from uds.core.util.log import LogLevel, LogSource, doLog
if typing.TYPE_CHECKING:
from .handlers import Handler
@ -68,14 +63,14 @@ def replacePath(path: str) -> str:
uuid = path.split(f'/{type}/')[1].split('/')[0]
name = model.objects.get(uuid=uuid).name # type: ignore
path = path.replace(uuid, f'[{name}]')
except Exception: # nosec: intentionally broad exception
except Exception: # nosec: intentionally broad exception
pass
return path
def log_operation(
handler: typing.Optional['Handler'], response_code: int, level: int = INFO
def logOperation(
handler: typing.Optional['Handler'], response_code: int, level: LogLevel = LogLevel.INFO
):
"""
Logs a request
@ -83,7 +78,7 @@ def log_operation(
if not handler:
return # Nothing to log
path = handler._request.path
path = handler.request.path
# If a common request, and no error, we don't log it because it's useless and a waste of resources
if response_code < 400 and any(
@ -93,15 +88,13 @@ def log_operation(
path = replacePath(path)
username = handler._request.user.pretty_name if handler._request.user else 'Unknown'
# Global log is used without owner nor type
models.Log.objects.create(
owner_id=0,
owner_type=OWNER_TYPE_AUDIT,
created=models.getSqlDatetime(),
username = handler.request.user.pretty_name if handler.request.user else 'Unknown'
doLog(
None,
level=level,
source=REST,
data=f'{handler._request.ip} {username}: [{handler._request.method}/{response_code}] {path}'[
message=f'{handler.request.ip} {username}: [{handler.request.method}/{response_code}] {path}'[
:4096
],
source=LogSource.REST,
avoidDuplicates=False,
)

View File

@ -682,7 +682,7 @@ class Log(ActorV3Action):
userService,
int(self._params['level']) + 10000,
self._params['message'],
log.ACTOR,
log.LogSource.ACTOR,
)
return ActorV3Action.actorResult('ok')

View File

@ -110,12 +110,12 @@ class MetaServicesPool(DetailHandler):
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
("Added" if uuid is None else "Modified")
+ " meta pool member {}/{}/{} by {}".format(
pool.name, priority, enabled, self._user.pretty_name
),
log.ADMIN,
log.LogSource.ADMIN,
)
return self.success()
@ -128,7 +128,7 @@ class MetaServicesPool(DetailHandler):
member.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
class MetaAssignedService(DetailHandler):
@ -243,7 +243,7 @@ class MetaAssignedService(DetailHandler):
else:
raise self.invalidItemException(_('Item is not removable'))
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
# Only owner is allowed to change right now
def saveItem(self, parent: MetaPool, item: typing.Optional[str]):
@ -276,4 +276,4 @@ class MetaAssignedService(DetailHandler):
service.save()
# Log change
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)

View File

@ -116,9 +116,9 @@ class AccessCalendars(DetailHandler):
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'{"Added" if uuid is None else "Updated"} access calendar {calendar.name}/{access} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
def deleteItem(self, parent: 'ServicePool', item: str) -> None:
@ -126,7 +126,7 @@ class AccessCalendars(DetailHandler):
logStr = f'Removed access calendar {calendarAccess.calendar.name} by {self._user.pretty_name}'
calendarAccess.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
class ActionsCalendars(DetailHandler):
@ -219,7 +219,7 @@ class ActionsCalendars(DetailHandler):
params=params,
)
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
def deleteItem(self, parent: 'ServicePool', item: str) -> None:
calendarAction = CalendarAction.objects.get(uuid=processUuid(self._args[0]))
@ -232,7 +232,7 @@ class ActionsCalendars(DetailHandler):
calendarAction.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
def execute(self, parent: 'ServicePool', item: str):
logger.debug('Launching action')
@ -247,7 +247,7 @@ class ActionsCalendars(DetailHandler):
f'{calendarAction.params}" by {self._user.pretty_name}'
)
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
calendarAction.execute()
return self.success()

View File

@ -102,8 +102,8 @@ class TunnelTicket(Handler):
now = models.getSqlDatetimeAsUnix()
totalTime = now - extra.get('b', now - 1)
msg = f'User {user.name} stopped tunnel {extra.get("t", "")[:8]}... to {host}:{port}: u:{sent}/d:{recv}/t:{totalTime}.'
log.doLog(user.manager, log.INFO, msg)
log.doLog(userService, log.INFO, msg)
log.doLog(user.manager, log.LogLevel.INFO, msg)
log.doLog(userService, log.LogLevel.INFO, msg)
# Try to log Close event
try:
@ -131,8 +131,8 @@ class TunnelTicket(Handler):
tunnel=self._args[0],
)
msg = f'User {user.name} started tunnel {self._args[0][:8]}... to {host}:{port} from {self._args[1]}.'
log.doLog(user.manager, log.INFO, msg)
log.doLog(userService, log.INFO, msg)
log.doLog(user.manager, log.LogLevel.INFO, msg)
log.doLog(userService, log.LogLevel.INFO, msg)
# Generate new, notify only, ticket
notifyTicket = models.TicketStore.create_for_tunnel(
userService=userService,

View File

@ -191,7 +191,7 @@ class AssignedService(DetailHandler):
else:
raise self.invalidItemException(_('Item is not removable'))
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
# Only owner is allowed to change right now
def saveItem(self, parent: models.ServicePool, item: typing.Optional[str]) -> None:
@ -219,7 +219,7 @@ class AssignedService(DetailHandler):
userService.save()
# Log change
log.doLog(parent, log.INFO, logStr, log.ADMIN)
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
def reset(self, parent: 'models.ServicePool', item: str) -> typing.Any:
userService = parent.userServices.get(uuid=processUuid(item))
@ -340,9 +340,9 @@ class Groups(DetailHandler):
parent.assignedGroups.add(group)
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'Added group {group.pretty_name} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
def deleteItem(self, parent: models.ServicePool, item: str) -> None:
@ -350,9 +350,9 @@ class Groups(DetailHandler):
parent.assignedGroups.remove(group)
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'Removed group {group.pretty_name} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
@ -399,9 +399,9 @@ class Transports(DetailHandler):
parent.transports.add(transport)
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'Added transport {transport.name} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
def deleteItem(self, parent: models.ServicePool, item: str) -> None:
@ -411,9 +411,9 @@ class Transports(DetailHandler):
parent.transports.remove(transport)
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'Removed transport {transport.name} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
@ -447,9 +447,9 @@ class Publications(DetailHandler):
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'Initiated publication v{parent.current_pub_revision} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
return self.success()
@ -478,9 +478,9 @@ class Publications(DetailHandler):
log.doLog(
parent,
log.INFO,
log.LogLevel.INFO,
f'Canceled publication v{parent.current_pub_revision} by {self._user.pretty_name}',
log.ADMIN,
log.LogSource.ADMIN,
)
return self.success()

View File

@ -525,12 +525,12 @@ def authLogLogin(
]
)
)
level = log.INFO if logStr == 'Logged in' else log.ERROR
level = log.LogLevel.INFO if logStr == 'Logged in' else log.LogLevel.ERROR
log.doLog(
authenticator,
level,
f'user {userName} has {logStr} from {request.ip} where os is {request.os.os.name}',
log.WEB,
log.LogSource.WEB,
)
try:
@ -540,7 +540,7 @@ def authLogLogin(
user,
level,
f'{logStr} from {request.ip} where OS is {request.os.os.name}',
log.WEB,
log.LogSource.WEB,
)
except models.User.DoesNotExist: # pylint: disable=no-member
pass
@ -550,8 +550,8 @@ def authLogLogout(request: 'ExtendedHttpRequest') -> None:
if request.user:
log.doLog(
request.user.manager,
log.INFO,
log.LogLevel.INFO,
f'user {request.user.name} has logged out from {request.ip}',
log.WEB,
log.LogSource.WEB,
)
log.doLog(request.user, log.INFO, f'has logged out from {request.ip}', log.WEB)
log.doLog(request.user, log.LogLevel.INFO, f'has logged out from {request.ip}', log.LogSource.WEB)

View File

@ -26,4 +26,4 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from manager import LogManager
from .manager import LogManager

View File

@ -48,7 +48,6 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
class LogManager(metaclass=singleton.Singleton):
"""
Manager for logging (at database) events
@ -61,7 +60,7 @@ class LogManager(metaclass=singleton.Singleton):
def manager() -> 'LogManager':
return LogManager() # Singleton pattern will return always the same instance
def __log(
def _log(
self,
owner_type: LogObjectType,
owner_id: int,
@ -83,7 +82,9 @@ class LogManager(metaclass=singleton.Singleton):
# If max_elements is greater than 0, database contains equals or more than max_elements, we will delete the oldest ones to ensure we have max_elements - 1
if 0 < max_elements <= current_elements:
# We will delete the oldest ones
for x in qs.order_by('created', 'id')[:current_elements - max_elements + 1]:
for x in qs.order_by('created', 'id')[
: current_elements - max_elements + 1
]:
x.delete()
if avoidDuplicates:
@ -108,7 +109,7 @@ class LogManager(metaclass=singleton.Singleton):
# Some objects will not get logged, such as System administrator objects, but this is fine
pass
def __getLogs(
def _getLogs(
self, owner_type: LogObjectType, owner_id: int, limit: int
) -> typing.List[typing.Dict]:
"""
@ -120,7 +121,7 @@ class LogManager(metaclass=singleton.Singleton):
for x in reversed(qs.order_by('-created', '-id')[:limit]) # type: ignore # Slicing is not supported by pylance right now
]
def __clearLogs(self, owner_type: LogObjectType, owner_id: int):
def _clearLogs(self, owner_type: LogObjectType, owner_id: int):
"""
Clears ALL logs related to user service
"""
@ -148,7 +149,7 @@ class LogManager(metaclass=singleton.Singleton):
if owner_type is not None:
try:
self.__log(
self._log(
owner_type, objectId, level, message, source, avoidDuplicates
)
except Exception:
@ -180,7 +181,11 @@ class LogManager(metaclass=singleton.Singleton):
logger.debug('Getting log: %s -> %s', wichObject, owner_type)
if owner_type: # 0 is valid owner type
return self.__getLogs(owner_type, getattr(wichObject, 'id', -1), limit if limit != -1 else owner_type.get_max_elements())
return self._getLogs(
owner_type,
getattr(wichObject, 'id', -1),
limit if limit != -1 else owner_type.get_max_elements(),
)
logger.debug(
'Requested getLogs for a type of object not covered: %s', wichObject
@ -200,7 +205,7 @@ class LogManager(metaclass=singleton.Singleton):
else LogObjectType.SYSLOG
)
if owner_type:
self.__clearLogs(owner_type, getattr(wichObject, 'id', -1))
self._clearLogs(owner_type, getattr(wichObject, 'id', -1))
else:
logger.debug(
'Requested clearLogs for a type of object not covered: %s: %s',

View File

@ -27,9 +27,8 @@ class LogObjectType(enum.IntEnum):
from uds.core.util.config import GlobalConfig # pylint: disable=import-outside-toplevel
if self == LogObjectType.SYSLOG:
return GlobalConfig.SYSLOG_MAX_ELEMENTS.getInt()
return GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt()
return GlobalConfig.GENERAL_LOG_MAX_ELEMENTS.getInt()
return GlobalConfig.INDIVIDIAL_LOG_MAX_ELEMENTS.getInt()
# Dict for translations
MODEL_TO_TYPE: typing.Mapping[typing.Type['Model'], LogObjectType] = {

View File

@ -344,7 +344,7 @@ class PublicationManager(metaclass=singleton.Singleton):
publication.deployed_service,
log.WARN,
'Forced cancel on publication, you must check uncleaned resources manually',
log.ADMIN,
log.LogSource.ADMIN,
)
publication.setState(State.CANCELED)
publication.save()

View File

@ -566,7 +566,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
servicePool,
log.WARN,
f'Max number of services reached: {servicePool.max_srvs}',
log.INTERNAL,
log.LogSource.INTERNAL,
)
raise MaxServicesReachedError()
@ -854,9 +854,9 @@ class UserServiceManager(metaclass=singleton.Singleton):
serviceNotReadyCode = 0x0002
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
f"User {user.pretty_name} from {srcIp} has initiated access",
log.WEB,
log.LogSource.WEB,
)
# If ready, show transport for this service, if also ready ofc
userServiceInstance = userService.getInstance()
@ -872,7 +872,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
userService,
log.WARN,
f'User service is not accessible due to invalid UUID (user: {user.pretty_name}, ip: {ip})',
log.TRANSPORT,
log.LogSource.TRANSPORT,
)
logger.debug('UUID check failed for user service %s', userService)
else:
@ -889,7 +889,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
transportInstance = transport.getInstance()
if transportInstance.isAvailableFor(userService, ip):
# userService.setConnectionSource(srcIp, 'unknown')
log.doLog(userService, log.INFO, "User service ready", log.WEB)
log.doLog(userService, log.LogLevel.INFO, "User service ready", log.LogSource.WEB)
self.notifyPreconnect(
userService,
transportInstance.processedUser(userService, user),
@ -913,7 +913,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
message = transportInstance.getCustomAvailableErrorMsg(
userService, ip
)
log.doLog(userService, log.WARN, message, log.TRANSPORT)
log.doLog(userService, log.WARN, message, log.LogSource.TRANSPORT)
logger.debug(
'Transport is not ready for user service %s: %s',
userService,
@ -926,7 +926,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
userService,
log.WARN,
f'User {user.pretty_name} from {srcIp} tried to access, but service was not ready',
log.WEB,
log.LogSource.WEB,
)
traceLogger.error(
@ -1131,7 +1131,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
meta,
log.WARN,
f'No user service accessible from device (ip {srcIp}, os: {os.os.name})',
log.SERVICE,
log.LogSource.SERVICE,
)
raise InvalidServiceException(
_('The service is not accessible from this device')

View File

@ -67,7 +67,7 @@ class StateUpdater:
logger.error('Got error on processor: %s', msg)
self.save(State.ERROR)
if msg is not None:
log.doLog(self.userService, log.ERROR, msg, log.INTERNAL)
log.doLog(self.userService, log.LogLevel.ERROR, msg, log.LogSource.INTERNAL)
def save(self, newState: typing.Optional[str] = None):
if newState:
@ -263,7 +263,7 @@ class UserServiceOpChecker(DelayedTask):
except Exception as e:
logger.exception('Checking service state')
log.doLog(userService, log.ERROR, f'Exception: {e}', log.INTERNAL)
log.doLog(userService, log.LogLevel.ERROR, f'Exception: {e}', log.LogSource.INTERNAL)
userService.setState(State.ERROR)
userService.save(update_fields=['data'])
@ -304,7 +304,7 @@ class UserServiceOpChecker(DelayedTask):
# Exception caught, mark service as errored
logger.exception("Error %s, %s :", e.__class__, e)
if uService:
log.doLog(uService, log.ERROR, f'Exception: {e}', log.INTERNAL)
log.doLog(uService, log.LogLevel.ERROR, f'Exception: {e}', log.LogSource.INTERNAL)
try:
uService.setState(State.ERROR)
uService.save(update_fields=['data'])

View File

@ -251,9 +251,9 @@ class OSManager(Module):
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
f'User {userName} has logged in',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
log.useLog(
@ -316,9 +316,9 @@ class OSManager(Module):
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
f'User {userName} has logged out',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
log.useLog(

View File

@ -216,7 +216,7 @@ class ServiceProvider(Module):
if self.getUuid():
log.doLog(
DBProvider.objects.get(uuid=self.getUuid()), level, message, log.SERVICE
DBProvider.objects.get(uuid=self.getUuid()), level, message, log.LogSource.SERVICE
)
def __str__(self):

View File

@ -402,7 +402,7 @@ class Service(Module):
if self.getUuid():
log.doLog(
DBService.objects.get(uuid=self.getUuid()), level, message, log.SERVICE
DBService.objects.get(uuid=self.getUuid()), level, message, log.LogSource.SERVICE
)
@classmethod

View File

@ -234,7 +234,7 @@ class UserDeployment(Environmentable, Serializable):
Logs a message with requested level associated with this user deployment
"""
if self._dbService:
log.doLog(self._dbService, level, message, log.SERVICE)
log.doLog(self._dbService, level, message, log.LogSource.SERVICE)
def macGenerator(self) -> 'UniqueMacGenerator':
"""

View File

@ -529,18 +529,18 @@ class GlobalConfig:
),
)
# Maximum logs per every log-capable administration element
MAX_LOGS_PER_ELEMENT: Config.Value = Config.section(GLOBAL_SECTION).value(
INDIVIDIAL_LOG_MAX_ELEMENTS: Config.Value = Config.section(GLOBAL_SECTION).value(
'maxLogPerElement',
'100',
type=Config.FieldType.NUMERIC,
help=_('Maximum logs per every log-capable administration element'),
)
# Maximum logs per every log-capable administration element
SYSLOG_MAX_ELEMENTS: Config.Value = Config.section(GLOBAL_SECTION).value(
'Max logs for UDS logs',
'10000',
GENERAL_LOG_MAX_ELEMENTS: Config.Value = Config.section(GLOBAL_SECTION).value(
'Max entries for general UDS logs',
'32000',
type=Config.FieldType.NUMERIC,
help=_('Maximum logs entries to keep in database for UDS logs (0 = unlimited, use with care)'),
help=_('Maximum logs entries for general UDS logs (0 = unlimited, use with care)'),
)

View File

@ -33,6 +33,7 @@
import logging
import logging.handlers
import typing
import enum
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
@ -41,64 +42,48 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
useLogger = logging.getLogger('useLog')
# Logging levels
OTHER, DEBUG, INFO, WARNING, ERROR, CRITICAL = (
10000 * (x + 1) for x in range(6)
) # @UndefinedVariable
class LogLevel(enum.IntEnum):
OTHER = 10000
DEBUG = 20000
INFO = 30000
WARNING = 40000
# Alias WARN
WARN = 40000
ERROR = 50000
CRITICAL = 60000
# Alias FATAL
FATAL = 60000
WARN = WARNING
FATAL = CRITICAL
def __str__(self) -> str:
return self.name
# Logging sources
INTERNAL, ACTOR, TRANSPORT, OSMANAGER, UNKNOWN, WEB, ADMIN, SERVICE, REST = (
'internal',
'actor',
'transport',
'osmanager',
'unknown',
'web',
'admin',
'service',
'rest',
)
def __repr__(self) -> str:
return self.name
OTHERSTR, DEBUGSTR, INFOSTR, WARNSTR, ERRORSTR, FATALSTR = (
'OTHER',
'DEBUG',
'INFO',
'WARN',
'ERROR',
'FATAL',
)
@classmethod
def fromStr(cls: typing.Type['LogLevel'], level: str) -> 'LogLevel':
try:
return cls[level.upper()]
except KeyError:
return cls.OTHER
# Names for defined log levels
__nameLevels = {
DEBUGSTR: DEBUG,
INFOSTR: INFO,
WARNSTR: WARN,
ERRORSTR: ERROR,
FATALSTR: FATAL,
OTHERSTR: OTHER,
}
# Reverse dict of names
__valueLevels = {v: k for k, v in __nameLevels.items()}
# Global log owner types:
OWNER_TYPE_GLOBAL = -1
OWNER_TYPE_AUDIT = -2
def logLevelFromStr(level: str) -> int:
"""
Gets the numeric log level from an string.
"""
return __nameLevels.get(level.upper(), OTHER)
def logStrFromLevel(level: int) -> str:
return __valueLevels.get(level, 'OTHER')
@classmethod
def fromInt(cls: typing.Type['LogLevel'], level: int) -> 'LogLevel':
try:
return cls(level)
except ValueError:
return cls.OTHER
class LogSource(enum.StrEnum):
INTERNAL = 'internal'
ACTOR = 'actor'
TRANSPORT = 'transport'
OSMANAGER = 'osmanager'
UNKNOWN = 'unknown'
WEB = 'web'
ADMIN = 'admin'
SERVICE = 'service'
REST = 'rest'
def useLog(
type_: str,
@ -141,10 +126,10 @@ def useLog(
def doLog(
wichObject: 'Model',
level: int,
wichObject: typing.Optional['Model'],
level: LogLevel,
message: str,
source: str = UNKNOWN,
source: LogSource = LogSource.UNKNOWN,
avoidDuplicates: bool = True,
) -> None:
# pylint: disable=import-outside-toplevel
@ -155,22 +140,18 @@ def doLog(
def getLogs(
wichObject: 'Model', limit: typing.Optional[int] = None
wichObject: typing.Optional['Model'], limit: int = -1
) -> typing.List[typing.Dict]:
"""
Get the logs associated with "wichObject", limiting to "limit" (default is GlobalConfig.MAX_LOGS_PER_ELEMENT)
"""
# pylint: disable=import-outside-toplevel
from uds.core.managers.log import LogManager
from uds.core.util.config import GlobalConfig
if limit is None:
limit = GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt()
return LogManager().getLogs(wichObject, limit)
def clearLogs(wichObject: 'Model') -> None:
def clearLogs(wichObject: typing.Optional['Model']) -> None:
"""
Clears the logs associated with the object using the logManager
"""

View File

@ -98,26 +98,26 @@ class HangedCleaner(Job):
): # Removing too long, remark it as removable
log.doLog(
us,
log.ERROR,
log.LogLevel.ERROR,
'User Service hanged on removal process. Restarting removal.',
log.INTERNAL,
log.LogSource.INTERNAL,
)
log.doLog(
servicePool,
log.ERROR,
log.LogLevel.ERROR,
f'User service {us.friendly_name} hanged on removal. Restarting removal.',
)
us.release() # Mark it again as removable, and let's see
else:
log.doLog(
us,
log.ERROR,
log.LogLevel.ERROR,
'User Service seems to be hanged. Removing it.',
log.INTERNAL,
log.LogSource.INTERNAL,
)
log.doLog(
servicePool,
log.ERROR,
log.LogLevel.ERROR,
f'Removing user service {us.friendly_name} because it seems to be hanged'
)
us.releaseOrCancel()

View File

@ -66,7 +66,7 @@ class ServiceCacheUpdater(Job):
servicePool,
log.WARN,
'Service Pool is restrained due to excesive errors',
log.INTERNAL,
log.LogSource.INTERNAL,
)
logger.info('%s is restrained, will check this later', servicePool.name)
@ -259,9 +259,9 @@ class ServiceCacheUpdater(Job):
except MaxServicesReachedError:
log.doLog(
servicePool,
log.ERROR,
log.LogLevel.ERROR,
'Max number of services reached for this service',
log.INTERNAL,
log.LogSource.INTERNAL,
)
logger.warning(
'Max user services reached for %s: %s. Cache not created',

View File

@ -95,7 +95,7 @@ class StuckCleaner(Job):
logger.debug('Found stuck user service %s', stuck)
log.doLog(
servicePool,
log.ERROR,
log.LogLevel.ERROR,
f'User service {stuck.name} has been hard removed because it\'s stuck',
)
# stuck.setState(State.ERROR)

View File

@ -93,8 +93,8 @@ def guacamole(
protocol = 'RDS' if 'remote-app' in val else val['protocol'].upper()
host = val.get('hostname', '0.0.0.0') # nosec: Not a bind, just a placeholder for "no host"
msg = f'User {user.name} started HTML5 {protocol} tunnel to {host}.'
log.doLog(user.manager, log.INFO, msg)
log.doLog(userService, log.INFO, msg)
log.doLog(user.manager, log.LogLevel.INFO, msg)
log.doLog(userService, log.LogLevel.INFO, msg)
events.addEvent(
userService.deployed_service,

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2 on 2023-04-20 03:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("uds", "0044_notification_notifier_servicetokenalias_and_more"),
]
operations = [
migrations.AddField(
model_name="log",
name="name",
field=models.CharField(default="", max_length=64),
),
]

View File

@ -449,7 +449,7 @@ class CalendarAction(UUIDModel):
self.service_pool.log(
f'Executed action {CALENDAR_ACTION_DICT.get(self.action, {}).get("description", self.action)} [{self.prettyParams}]',
level=log.INFO,
level=log.LogLevel.INFO,
)
except Exception:
self.service_pool.log(

View File

@ -58,6 +58,7 @@ class Log(models.Model):
created = models.DateTimeField(db_index=True)
source = models.CharField(max_length=16, default='internal', db_index=True)
level = models.PositiveIntegerField(default=0, db_index=True)
name = models.CharField(max_length=64, default='') # If syslog, log name, else empty
data = models.CharField(max_length=4096, default='')
# "fake" declarations for type checking
@ -74,9 +75,9 @@ class Log(models.Model):
@property
def level_str(self) -> str:
# pylint: disable=import-outside-toplevel
from uds.core.util.log import logStrFromLevel
from uds.core.util.log import LogLevel
return logStrFromLevel(self.level)
return LogLevel.fromInt(self.level).name
def __str__(self) -> str:
return (

View File

@ -699,8 +699,8 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
return bool(self.service) and self.service.testServer(host, port, timeout)
# Utility for logging
def log(self, message: str, level: int = log.INFO) -> None:
log.doLog(self, level, message, log.INTERNAL)
def log(self, message: str, level: int = log.LogLevel.INFO) -> None:
log.doLog(self, level, message, log.LogSource.INTERNAL)
@staticmethod
def beforeDelete(sender, **kwargs) -> None: # pylint: disable=unused-argument

View File

@ -637,8 +637,8 @@ class UserService(UUIDModel):
) or self.publication == self.deployed_service.activePublication()
# Utility for logging
def log(self, message: str, level: int = log.INFO) -> None:
log.doLog(self, level, message, log.INTERNAL)
def log(self, message: str, level: int = log.LogLevel.INFO) -> None:
log.doLog(self, level, message, log.LogSource.INTERNAL)
def testServer(self, host, port, timeout=4) -> bool:
return self.deployed_service.testServer(host, port, timeout)

View File

@ -134,7 +134,7 @@ class LinuxOsManager(osmanagers.OSManager):
"""
return service.getName()
def doLog(self, service, data, origin=log.OSMANAGER):
def doLog(self, service, data, origin=log.LogSource.OSMANAGER):
# Stores a log associated with this service
try:
msg, level = data.split('\t')
@ -142,10 +142,10 @@ class LinuxOsManager(osmanagers.OSManager):
level = int(level)
except Exception:
logger.debug('Do not understand level %s', level)
level = log.INFO
level = log.LogLevel.INFO
log.doLog(service, level, msg, origin)
except Exception:
log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin)
log.doLog(service, log.LogLevel.ERROR, "do not understand {0}".format(data), origin)
def actorData(
self, userService: 'UserService'
@ -160,9 +160,9 @@ class LinuxOsManager(osmanagers.OSManager):
if self.isRemovableOnLogout(userService):
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
'Unused user service for too long. Removing due to OS Manager parameters.',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
userService.remove()

View File

@ -101,9 +101,9 @@ class LinuxRandomPassManager(LinuxOsManager):
service.storeValue('linOsRandomPass', randomPass)
log.doLog(
service,
log.INFO,
log.LogLevel.INFO,
"Password set to \"{}\"".format(randomPass),
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
return randomPass

View File

@ -108,7 +108,7 @@ class TestOSManager(osmanagers.OSManager):
"""
return userService.getName()
def doLog(self, service, data, origin=log.OSMANAGER):
def doLog(self, service, data, origin=log.LogSource.OSMANAGER):
# Stores a log associated with this service
try:
msg, level = data.split('\t')
@ -116,10 +116,10 @@ class TestOSManager(osmanagers.OSManager):
level = int(level)
except Exception:
logger.debug('Do not understand level %s', level)
level = log.INFO
level = log.LogLevel.INFO
log.doLog(service, level, msg, origin)
except Exception:
log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin)
log.doLog(service, log.LogLevel.ERROR, "do not understand {0}".format(data), origin)
def actorData(
self, userService: 'UserService'
@ -134,9 +134,9 @@ class TestOSManager(osmanagers.OSManager):
if self.isRemovableOnLogout(userService):
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
'Unused user service for too long. Removing due to OS Manager parameters.',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
userService.remove()

View File

@ -140,7 +140,7 @@ class WindowsOsManager(osmanagers.OSManager):
def getName(self, userService: 'UserService') -> str:
return userService.getName()
def doLog(self, userService: 'UserService', data: str, origin=log.OSMANAGER):
def doLog(self, userService: 'UserService', data: str, origin=log.LogSource.OSMANAGER):
# Stores a log associated with this service
try:
msg, levelStr = data.split('\t')
@ -148,13 +148,13 @@ class WindowsOsManager(osmanagers.OSManager):
level = int(levelStr)
except Exception:
logger.debug('Do not understand level %s', levelStr)
level = log.INFO
level = log.LogLevel.INFO
log.doLog(userService, level, msg, origin)
except Exception:
logger.exception('WindowsOs Manager message log: ')
log.doLog(
userService, log.ERROR, "do not understand {0}".format(data), origin
userService, log.LogLevel.ERROR, "do not understand {0}".format(data), origin
)
def actorData(
@ -191,9 +191,9 @@ class WindowsOsManager(osmanagers.OSManager):
if self.isRemovableOnLogout(userService):
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
'Unused user service for too long. Removing due to OS Manager parameters.',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
userService.remove()

View File

@ -311,7 +311,7 @@ class WinDomainOsManager(WindowsOsManager):
userService,
log.WARN,
f'Could not remove machine from domain (_ldap._tcp.{self._domain} not found)',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
except ldap.ALREADY_EXISTS: # type: ignore # (valid)
# Already added this machine to this group, pass
@ -325,7 +325,7 @@ class WinDomainOsManager(WindowsOsManager):
# logger.exception('Ldap Exception caught')
if error:
log.doLog(userService, log.WARN, error, log.OSMANAGER)
log.doLog(userService, log.WARN, error, log.LogSource.OSMANAGER)
logger.error(error)
def release(self, userService: 'UserService') -> None:
@ -339,9 +339,9 @@ class WinDomainOsManager(WindowsOsManager):
# logger.info('Releasing from a not FQDN domain is not supported')
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
"Removing a domain machine form a non FQDN domain is not supported.",
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
return
@ -353,7 +353,7 @@ class WinDomainOsManager(WindowsOsManager):
userService,
log.WARN,
f'Could not remove machine from domain (_ldap._tcp.{self._domain} not found)',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
return
except ldaputil.LDAPError as e:
@ -362,7 +362,7 @@ class WinDomainOsManager(WindowsOsManager):
userService,
log.WARN,
f'Could not remove machine from domain ({e})',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
return
except Exception as e:
@ -371,7 +371,7 @@ class WinDomainOsManager(WindowsOsManager):
userService,
log.WARN,
f'Could not remove machine from domain ({e})',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
return

View File

@ -128,9 +128,9 @@ class WinRandomPassManager(WindowsOsManager):
userService.storeValue('winOsRandomPass', randomPass)
log.doLog(
userService,
log.INFO,
log.LogLevel.INFO,
f'Password set to "{randomPass}"',
log.OSMANAGER,
log.LogSource.OSMANAGER,
)
return randomPass

View File

@ -218,7 +218,7 @@ class OVirtLinkedDeployment(services.UserDeployment):
self.cache.put('ready', '1')
except Exception as e:
self.doLog(log.ERROR, f'Error on setReady: {e}')
self.doLog(log.LogLevel.ERROR, f'Error on setReady: {e}')
# Treat as operation done, maybe the machine is ready and we can continue
return State.FINISHED
@ -335,7 +335,7 @@ if sys.platform == 'win32':
"""
reason = str(reason)
logger.debug('Setting error state, reason: %s', reason)
self.doLog(log.ERROR, reason)
self.doLog(log.LogLevel.ERROR, reason)
if self._vmid != '': # Powers off
OVirtDeferredRemoval.remove(self.service().parent(), self._vmid)

View File

@ -228,7 +228,7 @@ class OGDeployment(UserDeployment):
State.ERROR, so we can do "return self.__error(reason)"
"""
logger.debug('Setting error state, reason: %s', reason)
self.doLog(log.ERROR, reason)
self.doLog(log.LogLevel.ERROR, reason)
if self._machineId:
try:
@ -311,7 +311,7 @@ class OGDeployment(UserDeployment):
self._stamp = getSqlDatetimeAsUnix()
self.doLog(
log.INFO,
log.LogLevel.INFO,
f'Reserved machine {self._name}: id: {self._machineId}, mac: {self._mac}, ip: {self._ip}',
)

View File

@ -141,7 +141,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
self.cache.put('ready', '1')
except Exception as e:
self.doLog(log.ERROR, 'Error on setReady: {}'.format(e))
self.doLog(log.LogLevel.ERROR, 'Error on setReady: {}'.format(e))
# Treat as operation done, maybe the machine is ready and we can continue
return State.FINISHED
@ -242,7 +242,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
"""
reason = str(reason)
logger.debug('Setting error state, reason: %s', reason)
self.doLog(log.ERROR, reason)
self.doLog(log.LogLevel.ERROR, reason)
if self._vmid: # Powers off & delete it
try:

View File

@ -157,7 +157,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
self.cache.put('ready', '1')
except Exception as e:
self.doLog(log.ERROR, 'Error on setReady: {}'.format(e))
self.doLog(log.LogLevel.ERROR, 'Error on setReady: {}'.format(e))
# Treat as operation done, maybe the machine is ready and we can continue
return State.FINISHED
@ -247,7 +247,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
self._queue = [opError]
self._reason = str(reason)
self.doLog(log.ERROR, self._reason)
self.doLog(log.LogLevel.ERROR, self._reason)
if self._vmid: # Powers off & delete it
try:

View File

@ -297,7 +297,7 @@ if sys.platform == 'win32':
"""
reason = str(reason)
logger.debug('Setting error state, reason: %s', reason)
self.doLog(log.ERROR, reason)
self.doLog(log.LogLevel.ERROR, reason)
if self._vmid != '': # Powers off
ProxmoxDeferredRemoval.remove(self.service().parent(), int(self._vmid))
@ -532,7 +532,7 @@ if sys.platform == 'win32':
if getSqlDatetimeAsUnix() - shutdown_start > GUEST_SHUTDOWN_WAIT:
logger.debug('Time is consumed, falling back to stop')
self.doLog(
log.ERROR,
log.LogLevel.ERROR,
f'Could not shutdown machine using soft power off in time ({GUEST_SHUTDOWN_WAIT} seconds). Powering off.',
)
# Not stopped by guest in time, but must be stopped normally

View File

@ -154,7 +154,7 @@ class XenLinkedDeployment(UserDeployment):
self.cache.put('ready', '1', 30)
except Exception as e:
# On case of exception, log an an error and return as if the operation was executed
self.doLog(log.ERROR, 'Error setting machine state: {}'.format(e))
self.doLog(log.LogLevel.ERROR, 'Error setting machine state: {}'.format(e))
# return self.__error('Machine is not available anymore')
return State.FINISHED
@ -224,7 +224,7 @@ class XenLinkedDeployment(UserDeployment):
def __error(self, reason: typing.Any) -> str:
logger.debug('Setting error state, reason: %s', reason)
self.doLog(log.ERROR, reason)
self.doLog(log.LogLevel.ERROR, reason)
if self._vmid != '': # Powers off and delete VM
try:

View File

@ -210,11 +210,11 @@ def action(
rebuild = True
log.doLog(
userService.deployed_service,
log.INFO,
log.LogLevel.INFO,
"Removing User Service {} as requested by {} from {}".format(
userService.friendly_name, request.user.pretty_name, request.ip
),
log.WEB,
log.LogSource.WEB,
)
UserServiceManager().requestLogoff(userService)
userService.release()
@ -226,11 +226,11 @@ def action(
rebuild = True
log.doLog(
userService.deployed_service,
log.INFO,
log.LogLevel.INFO,
"Reseting User Service {} as requested by {} from {}".format(
userService.friendly_name, request.user.pretty_name, request.ip
),
log.WEB,
log.LogSource.WEB,
)
# UserServiceManager().requestLogoff(userService)
UserServiceManager().reset(userService)