db_log.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import logging
  2. import os
  3. import sqlite3 as db
  4. import sys
  5. import time
  6. from math import inf
  7. from shutil import copyfile
  8. from typing import Optional
  9. import git
  10. DBName = str
  11. connected_dbs = [DBName]
  12. # get current commit at start time of program
  13. repo = git.Repo(search_parent_directories=True)
  14. CURRENT_SHA = repo.head.object.hexsha
  15. class DBLog:
  16. def __init__(self, db_name='log.db', create_if_not_exists=True):
  17. if db_name in connected_dbs:
  18. raise ValueError(f'There is already a connection to {db_name}.'
  19. 'If you want to re-use the same connection you can get it from `db_log.connected_dbs`.'
  20. 'If you want to disconnect you can call log.disconnect().')
  21. self.connection: Optional[db.Connection] = None
  22. self.cursor: Optional[db.Cursor] = None
  23. self.db_name: Optional[DBName] = None
  24. self.skip_db_logging = False
  25. db_name = db_name.lower()
  26. if not os.path.isfile(db_name) and not create_if_not_exists:
  27. raise FileNotFoundError('There is no database with this name.')
  28. creating_new_db = not os.path.isfile(db_name)
  29. try:
  30. db_connection = db.connect(db_name, check_same_thread=False)
  31. # db_setup.create_functions(db_connection)
  32. # db_setup.set_pragmas(db_connection.cursor())
  33. # connection.text_factory = lambda x: x.encode('latin-1')
  34. except db.Error as e:
  35. print("Database error %s:" % e.args[0])
  36. raise
  37. self.connection = db_connection
  38. self.cursor = self.connection.cursor()
  39. self.db_name = db_name
  40. if creating_new_db:
  41. try:
  42. if os.path.isfile('/test-db/' + db_name):
  43. print('Using test logs')
  44. copyfile('/test-db/' + db_name, db_name)
  45. else:
  46. self.setup()
  47. except Exception:
  48. if self.connection is not None:
  49. self.connection.rollback()
  50. os.remove(db_name)
  51. raise
  52. self.connected = True
  53. self.min_level = -inf
  54. def disconnect(self, rollback=True):
  55. if rollback:
  56. self.connection.rollback()
  57. else:
  58. self.connection.commit()
  59. self.connection.close()
  60. self.connected = False
  61. def setup(self):
  62. self.cursor.execute('''
  63. CREATE TABLE IF NOT EXISTS entries(
  64. rowid INTEGER PRIMARY KEY,
  65. dt_created TIMESTAMP NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)),
  66. message TEXT NOT NULL,
  67. data BLOB, -- can be null
  68. pid INTEGER NOT NULl,
  69. message_type VARCHAR (25) NOT NULL, -- a plain text message title
  70. level INTEGER NOT NULL, -- relates to logging.ERROR and similar ones
  71. head_hex_sha VARCHAR-- SHA of currently checked out commit
  72. )
  73. ''')
  74. def log(self,
  75. message,
  76. level,
  77. message_type='generic',
  78. data=None,
  79. dt_created=None,
  80. current_pid=None,
  81. current_head_hex_sha=CURRENT_SHA,
  82. data_serialization_method=lambda x: x):
  83. if level < self.min_level or self.skip_db_logging:
  84. return
  85. try:
  86. if dt_created is None:
  87. dt_created = round(time.time())
  88. if current_pid is None:
  89. current_pid = os.getpid()
  90. data: str = data_serialization_method(data)
  91. self.cursor.execute('''
  92. INSERT INTO entries(message, data, dt_created, pid, head_hex_sha, message_type, level)
  93. VALUES (?, ?, ?, ?, ?, ?, ?)
  94. ''', (message, data, dt_created, current_pid, current_head_hex_sha, message_type, level))
  95. except db.OperationalError as e:
  96. if 'database is locked' in str(e):
  97. print('WARNING: Unable to write to log database. No logs will be written.')
  98. self.skip_db_logging = True
  99. def debug(self, message, *args, **kwargs):
  100. self.log(message, logging.DEBUG, *args, **kwargs)
  101. def info(self, message, *args, **kwargs):
  102. self.log(message, logging.INFO, *args, **kwargs)
  103. def warning(self, message, *args, **kwargs):
  104. self.log(message, logging.WARNING, *args, **kwargs)
  105. warn = warning
  106. def error(self, message, *args, **kwargs):
  107. self.log(message, logging.ERROR, *args, **kwargs)
  108. def critical(self, message, *args, **kwargs):
  109. self.log(message, logging.CRITICAL, *args, **kwargs)
  110. fatal = critical
  111. def exception(self, msg, *args, data=None, **kwargs):
  112. if data is None:
  113. data = sys.exc_info()
  114. self.error(msg, *args, data=data, **kwargs)
  115. def commit(self):
  116. c = time.clock()
  117. self.connection.commit()
  118. delta = time.clock() - c
  119. print(f'Committing log files took {delta} seconds')
  120. return delta