log.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. """
  2. This is a custom logfile creation module
  3. """
  4. import datetime
  5. import os
  6. from PyQt4 import QtGui, QtCore
  7. # from backend import board
  8. from backend.board import available_boards
  9. import backendinterface as bif
  10. import numpy as np
  11. import codecs
  12. from .. import config
  13. import storage
  14. import logging
  15. epics_reachable = True
  16. try: # try to import epics
  17. import epics
  18. no_epics = False
  19. os.environ["EPICS_BASE"] = config.epics_base_path
  20. try: # if import was successful, try to find libca and test for connectivity with config.epics_test_pv
  21. # sys.stderr = os.devnull # epics.ca.find_libca() prints a useless None to stderr if libca is not found
  22. epics.ca.find_libca()
  23. # sys.stderr = sys.__stderr__
  24. if epics.caget(config.epics_test_pv) == None:
  25. no_epics = True
  26. epics_reachable = False
  27. logging.error("Epics is not accessible (possible Timeout)")
  28. except epics.ca.ChannelAccessException as e:
  29. if str(e) == "cannot find Epics CA DLL":
  30. no_epics = True
  31. logging.error("Epics CA DLL not found")
  32. except ImportError:
  33. no_epics = True
  34. # after = datetime.datetime.now()
  35. # if (after-before).total_seconds() > 3.0:
  36. # no_epics = True
  37. # logging.error("Epics is not accessible (Timeout)")
  38. # tr = kcgw.tr
  39. tr = lambda _, x: x # log entries will not be translated
  40. class LogLevels: # Keep every new value an integer. Every level with higher value will include those with lower level
  41. """
  42. Used Log Levels Container
  43. """
  44. NONE = -1
  45. INFO = 0
  46. DEBUG = 1
  47. TWO_COLUMNS = False
  48. BOARDID = "BOARD_ID"
  49. class MeasurementLogger(object):
  50. """
  51. Logfile creator class
  52. It will automatically get the info needed to create a logfile entry from registered functions.
  53. """
  54. def __init__(self, filename=None, level=LogLevels.INFO, oneline=False):
  55. """
  56. Initialise the logfile creator
  57. :param filename: (str) filename of the logfile THIS IS IGNORED AS OF NOW
  58. :param level: (int) valid log level (see LogLevels)
  59. :param oneline: (bool) whether to format the logfile entries in multilines or one line
  60. :return: -
  61. """
  62. self.level = level
  63. self.filename = filename
  64. self.parameter_functions = []
  65. self.oneline = oneline
  66. self.dumper = None
  67. self.predefined_parameters = [ # format: [logstring, [function (args,)]]
  68. ["Number of Orbits", [bif.bk_get_config, (BOARDID, 'orbits_observe')]],
  69. ["Number of Skipped Orbits", [bif.bk_get_config, (BOARDID, 'orbits_skip')]],
  70. ["Number of Acquisitions", [bif.bk_get_config, (BOARDID, 'acquisition_count')]],
  71. ["Time between Acquisitions", [bif.bk_get_config, (BOARDID, 'orbits_wait_time')]],
  72. ["Pilot Bunch Simulator", [bif.bk_get_config, (BOARDID, 'pilot_bunch')]],
  73. ["Header saved", [bif.bk_get_config, (BOARDID, 'header')]],
  74. ["T/H Delay", [bif.bk_get_config, (BOARDID, 'th_delay')]],
  75. ["ADC 1 Delay", [bif.bk_get_config, (BOARDID, 'chip_1_delay')]],
  76. ["ADC 2 Delay", [bif.bk_get_config, (BOARDID, 'chip_2_delay')]],
  77. ["ADC 3 Delay", [bif.bk_get_config, (BOARDID, 'chip_3_delay')]],
  78. ["ADC 4 Delay", [bif.bk_get_config, (BOARDID, 'chip_4_delay')]]
  79. ]
  80. self.gui_values_number = len(self.predefined_parameters)
  81. if not no_epics:
  82. for entr in config.epics_log_entry_pvs:
  83. self.predefined_parameters.append([entr[0], [epics.caget, entr[1]]])
  84. def __now(self):
  85. """
  86. Get the current time in %Y-%m-%d %H:%M:%S format
  87. :return: the time
  88. """
  89. return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  90. def _logging_formatter(self, info):
  91. """
  92. Formatter for the logfile entries
  93. Override this to implement custom formatters
  94. """
  95. if self.oneline:
  96. return self.__now() + " " + ", ".join(info)
  97. return self.__now() + "\n\t" + "\n\t".join(info)
  98. def _log(self, board_id, stuff):
  99. """
  100. Write entries to logfile
  101. This will also make shure the directory the logfile is located is available and if not creates it
  102. :param stuff: (str) the entrie to write
  103. :return: -
  104. """
  105. # if not self.filename:
  106. # filename = storage.storage.save_location + '/' + storage.storage.subdirname + "/Measurement.log"
  107. # else:
  108. # filename = self.filename
  109. filename = storage.storage.save_location + '/' + \
  110. storage.storage.subdirname + '/' + "/Measurement_board_" + \
  111. available_boards.get_board_name_from_id(board_id).replace(' ', '_') + ".log"
  112. if not os.path.isdir(str(storage.storage.save_location + '/' + storage.storage.subdirname)):
  113. os.makedirs(str(storage.storage.save_location + '/' + storage.storage.subdirname))
  114. f = codecs.open(filename, 'a', encoding='utf-8')
  115. f.write(stuff+'\n\n')
  116. f.close()
  117. def register_parameter(self, logstring, func, args):
  118. """
  119. This function is used to register parameters for the logfile entries
  120. :param logstring: (str) the string to describe the value in the logfile
  121. :param func: (callable) the function used to get the value
  122. :param args: arguments to pass to func upon gathering of information
  123. :return: -
  124. """
  125. if not isinstance(args, tuple):
  126. args = (args,)
  127. self.parameter_functions.append([func, args, logstring])
  128. # self.parameter_functions[logstring] = [func, args, logstring]
  129. def reset_parameters(self):
  130. """
  131. Resets all parameters (unregisteres all parameters)
  132. """
  133. self.parameter_functions = []
  134. def register_dumper(self, func):
  135. """
  136. Register a dumper that is used to dump a log of data into the logfile
  137. :param func: (callable) Function that is used as dumper
  138. :return: -
  139. """
  140. self.dumper = func
  141. def dump(self, board_id):
  142. """
  143. Perform a dump (see register_dumper)
  144. :return: -
  145. """
  146. self.do_log(board_id=board_id, c=self.__now() + " " + self.dumper())
  147. def do_log(self, c=None, additional=None, board_id=None, level=LogLevels.INFO):
  148. """
  149. Perform a log. This creates a log entry in the logfile
  150. :param c: (str) if this is not None this is the only text written to the logfile (in addition to the current time)
  151. :param additional: (str) This text is written below the current time to customize log entries
  152. :param board_id: the id of the board to log for, if this is not given, all boards are logged
  153. :return: -
  154. """
  155. if level > self.level:
  156. return
  157. if c:
  158. if board_id is not None:
  159. self._log(board_id, c)
  160. else:
  161. for bid in available_boards:
  162. self._log(bid, c)
  163. else:
  164. def do_the_log(bid):
  165. '''Actually perform the log'''
  166. st = []
  167. if additional:
  168. s = additional.split('\n')[0]
  169. for line in additional.split('\n')[1:]:
  170. s += "\n\t"+line
  171. st.append(s)
  172. for par in self.parameter_functions:
  173. par[1] = list(par[1])
  174. for i, p in enumerate(par[1]):
  175. if p == BOARDID:
  176. par[1][i] = bid
  177. st.append(par[2] + ": " + str(par[0](*par[1])))
  178. return st
  179. if board_id is not None:
  180. st = do_the_log(board_id)
  181. self._log(board_id, self._logging_formatter(st))
  182. else:
  183. for bid in available_boards:
  184. st = do_the_log(bid)
  185. self._log(bid, self._logging_formatter(st))
  186. logger = None
  187. dump_general = False
  188. def log(c=None, additional=None, dump=False, board_id=None, level=LogLevels.INFO):
  189. """
  190. Execute logging if logger was registered
  191. :param (str) c: if this is given c is the only entry that is created (additional is ignored)
  192. :param (str) additional: the additional text that is written below the current time
  193. :param (bool) dump: if this is true, a dump of every log value is performed
  194. :param board_id: the board id to log for, if this is not given, all boards are logged
  195. :return: -
  196. """
  197. global logger, dump_general
  198. if logger:
  199. if dump or dump_general:
  200. logger.dump(board_id=board_id)
  201. else:
  202. logger.do_log(c, additional, board_id=board_id, level=level)
  203. else:
  204. print "No Logger registered"
  205. class ConfigureLog(QtGui.QDialog):
  206. """
  207. Class that is basically a QDialog to configure the logger
  208. """
  209. def __init__(self, parent=None): # parameter parent does nothing when adding parent to the folowing __init__
  210. # no entry in taskbar is made
  211. super(ConfigureLog, self).__init__()
  212. self.setWindowTitle("Configure Measurement Log Entries")
  213. self.setWindowIcon(QtGui.QIcon(config.install_path + config.guiIcon))
  214. self.sl = QtGui.QVBoxLayout()
  215. self.setLayout(self.sl)
  216. self.scrollArea = QtGui.QScrollArea()
  217. if TWO_COLUMNS:
  218. self.scrollArea.setMinimumSize(410, 300)
  219. else:
  220. self.scrollArea.setMinimumSize(250, 520)
  221. self.sl.addWidget(self.scrollArea)
  222. self.widget = QtGui.QWidget()
  223. if TWO_COLUMNS:
  224. self.layout = QtGui.QGridLayout()
  225. else:
  226. self.layout = QtGui.QVBoxLayout()
  227. self.widget.setLayout(self.layout)
  228. self.parameter = []
  229. for par in logger.predefined_parameters:
  230. self.parameter.append((QtGui.QCheckBox(par[0]), par[1]))
  231. self.i = 0
  232. def add(w):
  233. '''add a widget to the layout. if w is 'sep' a seperator is added'''
  234. if w == 'sep':
  235. sep = QtGui.QFrame()
  236. sep.setFrameShape(QtGui.QFrame.HLine)
  237. w = sep
  238. self.layout.addWidget(w, self.i//2, self.i%2)
  239. self.i += 1
  240. for i, (cb, _) in enumerate(self.parameter):
  241. if TWO_COLUMNS:
  242. if i == logger.gui_values_number:
  243. # sep1 = QtGui.QFrame()
  244. # sep1.setFrameShape(QtGui.QFrame.HLine)
  245. # sep2 = QtGui.QFrame()
  246. # sep2.setFrameShape(QtGui.QFrame.HLine)
  247. if logger.gui_values_number%2 == 1:
  248. self.i += 1
  249. # add(sep1)
  250. # add(sep2)
  251. add('sep')
  252. add('sep')
  253. add(cb)
  254. else:
  255. if i == logger.gui_values_number:
  256. sep = QtGui.QFrame()
  257. sep.setFrameShape(QtGui.QFrame.HLine)
  258. self.layout.addWidget(sep)
  259. self.layout.addWidget(cb)
  260. self.all = QtGui.QCheckBox(tr("Label", "All of the above"))
  261. self.all.clicked.connect(self.all_changed)
  262. if TWO_COLUMNS:
  263. if (len(logger.predefined_parameters)-1)%2 == 1:
  264. self.i += 1
  265. add('sep')
  266. add('sep')
  267. add(self.all)
  268. else:
  269. sepa = QtGui.QFrame()
  270. sepa.setFrameShape(QtGui.QFrame.HLine)
  271. self.layout.addWidget(sepa)
  272. self.layout.addWidget(self.all)
  273. self.buttonLayout = QtGui.QHBoxLayout()
  274. self.sl.addLayout(self.buttonLayout)
  275. self.ok = QtGui.QPushButton(tr("Button", "OK"))
  276. self.cancel = QtGui.QPushButton(tr("Button", "Cancel"))
  277. self.buttonLayout.addWidget(self.ok)
  278. self.buttonLayout.addWidget(self.cancel)
  279. self.ok.pressed.connect(self.do)
  280. self.cancel.pressed.connect(self.close)
  281. self.scrollArea.setWidget(self.widget)
  282. self.initial_ticks()
  283. def initial_ticks(self):
  284. """
  285. Set the initial ticks.
  286. """
  287. pf = np.array(logger.parameter_functions)
  288. for cb, _ in self.parameter:
  289. cb.setChecked(str(cb.text()) in pf[:, 2])
  290. def all_changed(self):
  291. """
  292. This is called when the "all" checkbox is checked. It de/activates the other checkboxes
  293. """
  294. if self.all.isChecked():
  295. for cb, _ in self.parameter:
  296. cb.setEnabled(False)
  297. else:
  298. for cb, _ in self.parameter:
  299. cb.setEnabled(True)
  300. def do(self):
  301. """
  302. Change the logger instance to the activated log entries
  303. :return:
  304. """
  305. global logger
  306. logger.reset_parameters()
  307. def nif(cb, f, args):
  308. '''actually set the new parameters'''
  309. if cb.isChecked() or self.all.isChecked():
  310. logger.register_parameter(str(cb.text()), f, args)
  311. for cb, par in self.parameter:
  312. nif(cb, par[0], par[1])
  313. self.close()