state_logger.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 import rospy
3 import os
4 import time
5 import yaml
6 import pickle
7 import logging
8 import logging.config
9 from functools import wraps, partial
10 from flexbe_core.proxy import ProxyPublisher
11 from std_msgs.msg import String
12 
13 
14 class StateLogger(object):
15  '''
16  Realizes logging of active states.
17  '''
18 
19  enabled = False
20  _serialize_impl = 'yaml'
21 
22  @staticmethod
23  def initialize(be_name=None):
24  log_folder = os.path.expanduser(rospy.get_param("~log_folder", "~/.flexbe_logs"))
25 
26  if log_folder == "" or not rospy.get_param("~log_enabled", False):
27  StateLogger.enabled = False
28  return
29  StateLogger.enabled = True
30 
31  if not os.path.exists(log_folder):
32  os.makedirs(log_folder)
33 
34  name = "states"
35  if be_name is not None:
36  name = be_name.replace(" ", "_").replace(",", "_").replace(".", "_").replace("/", "_").lower()
37 
38  StateLogger._serialize_impl = rospy.get_param('~log_serialize', 'yaml')
39 
40  logger_config = dict({
41  'version': 1,
42  'disable_existing_loggers': False,
43  'formatters': {'yaml': {'()': 'flexbe_core.state_logger.YamlFormatter'}},
44  'handlers': {
45  'file': {
46  'class': 'logging.FileHandler',
47  'filename': '%(log_folder)s/%(behavior)s_%(timestamp)s.yaml',
48  'formatter': 'yaml'
49  },
50  'publish': {
51  'class': 'flexbe_core.state_logger.PublishBehaviorLogMessage',
52  'topic': 'flexbe/state_logger',
53  'formatter': 'yaml'
54  }
55  },
56  'loggers': {'flexbe': {'level': 'INFO', 'handlers': ['file']}}
57  }, **rospy.get_param('~log_config', {}))
58  if ('handlers' in logger_config and 'file' in logger_config['handlers'] and
59  'filename' in logger_config['handlers']['file']):
60  logger_config['handlers']['file']['filename'] %= {
61  'log_folder': log_folder,
62  'behavior': name,
63  'timestamp': time.strftime("%Y-%m-%d-%H_%M_%S")
64  }
65  if 'loggers' in logger_config and 'flexbe' in logger_config['loggers']:
66  logger_config['loggers']['flexbe']['level'] = rospy.get_param('~log_level', 'INFO').upper()
67  logging.config.dictConfig(logger_config)
68 
69  @staticmethod
70  def shutdown():
71  if not StateLogger.enabled:
72  return
73  logging.shutdown()
74  StateLogger.enabled = False
75 
76  @staticmethod
77  def get(name):
78  """ Obtain a reference to the named logger. """
79  return logging.getLogger(name)
80 
81  @staticmethod
82  def log(name, state, **kwargs):
83  """ Log custom data as given by the keyword arguments. """
84  if StateLogger.enabled:
85  StateLogger.get(name).log(kwargs.get('loglevel', logging.INFO), dict(StateLogger._basic(state), **kwargs))
86 
87  # state decorators
88 
89  @staticmethod
90  def log_events(name, **events):
91  """ Log whenever any of the specified events of the state is activated. """
92  def decorator(cls):
93  cls_init = cls.__init__
94  @wraps(cls.__init__)
95  def log_events_init(self, *args, **kwargs):
96  cls_init(self, *args, **kwargs)
97  for event, method in events.items():
98  def wrap_event_method(event, method):
99  if hasattr(self, method):
100  event_method = getattr(self, method)
101  @wraps(event_method)
102  def event_wrapper(*args, **kwargs):
103  time_start = rospy.get_time()
104  try:
105  event_method(*args, **kwargs)
106  finally:
107  if StateLogger.enabled:
108  StateLogger.get(name).info(dict(
109  StateLogger._basic(self),
110  event=event,
111  duration=rospy.get_time() - time_start))
112  setattr(self, method, event_wrapper)
113  wrap_event_method(event, method)
114  cls.__init__ = log_events_init
115  return cls
116  return decorator
117 
118  @staticmethod
119  def log_outcomes(name):
120  """ Log all outcomes of the state. """
121  def decorator(cls):
122  cls_init = cls.__init__
123  @wraps(cls.__init__)
124  def log_outcomes_init(self, *args, **kwargs):
125  cls_init(self, *args, **kwargs)
126  execute_method = getattr(self, 'execute')
127  @wraps(execute_method)
128  def execute_wrapper(*args, **kwargs):
129  outcome = None
130  try:
131  outcome = execute_method(*args, **kwargs)
132  return outcome
133  finally:
134  if StateLogger.enabled and outcome is not None:
135  StateLogger.get(name).info(dict(
136  StateLogger._basic(self),
137  outcome=outcome))
138  setattr(self, 'execute', execute_wrapper)
139  cls.__init__ = log_outcomes_init
140  return cls
141  return decorator
142 
143  @staticmethod
144  def log_userdata(name, keys=None):
145  """ Log all userdata that is passed to the state. """
146  def decorator(cls):
147  cls_init = cls.__init__
148  @wraps(cls.__init__)
149  def log_userdata_init(self, *args, **kwargs):
150  cls_init(self, *args, **kwargs)
151  input_keys = kwargs.get('input_keys', [])
152  on_enter_method = getattr(self, 'on_enter')
153  @wraps(on_enter_method)
154  def on_enter_wrapper(userdata):
155  logger = StateLogger.get(name)
156  if StateLogger.enabled and logger.isEnabledFor(logging.DEBUG) and input_keys:
157  logdata = dict(StateLogger._basic(self), userdata=dict())
158  for key in input_keys:
159  if keys is not None and key not in keys:
160  continue
161  try:
162  logdata['userdata'][key] = StateLogger._serialize(userdata[key])
163  except Exception as e:
164  rospy.logwarn('State %s failed to log userdata for key %s: %s' %
165  (self.name, key, str(e)))
166  logger.debug(logdata)
167  on_enter_method(userdata)
168  setattr(self, 'on_enter', on_enter_wrapper)
169  cls.__init__ = log_userdata_init
170  return cls
171  return decorator
172 
173  # helpers
174 
175  @staticmethod
176  def _serialize(obj):
177  return {
178  'yaml': partial(yaml.dump, default_flow_style=True),
179  'str': str,
180  'repr': repr,
181  'pickle': pickle.dumps,
182  }.get(StateLogger._serialize_impl, lambda o: eval(StateLogger._serialize_impl, locals={'object': o}))(obj)
183 
184  @staticmethod
185  def _basic(state):
186  result = {'time': rospy.get_time()}
187  if state is not None:
188  result.update({
189  'name': state.name,
190  'state': state.__class__.__name__,
191  'path': state.path
192  })
193  return result
194 
195 
196 class YamlFormatter(logging.Formatter):
197 
198  def format(self, record):
199  record.msg.update(logger=record.name, loglevel=record.levelname)
200  return '- %s' % super(YamlFormatter, self).format(record)
201 
202 
203 class PublishBehaviorLogMessage(logging.Handler):
204 
205  def __init__(self, level=logging.NOTSET, topic='flexbe/state_logger'):
206  super(PublishBehaviorLogMessage, self).__init__(level)
207  self._topic = topic
208  self._pub = ProxyPublisher({self._topic: String})
209 
210  def emit(self, record):
211  message = self.format(record)
212  self._pub.publish(self._topic, String(message))
def log(name, state, kwargs)
Definition: state_logger.py:82
def __init__(self, level=logging.NOTSET, topic='flexbe/state_logger')


flexbe_core
Author(s): Philipp Schillinger
autogenerated on Sun Dec 13 2020 04:01:39