rosros 0.2.5
Simple unified interface to ROS1 / ROS2 Python API
Loading...
Searching...
No Matches
log.py
Go to the documentation of this file.
1"""
2Stand-in for `rospy` logging functionality in ROS2.
3
4Heavily modified copy from ROS1 `rospy.node`,
5at https://github.com/ros/ros_comm (`clients/rospy/src/rospy/core.py`),
6released under the BSD License.
7
8------------------------------------------------------------------------------
9This file is part of rosros - simple unified interface to ROS1 / ROS2.
10Released under the BSD License.
11
12@author Erki Suurjaak
13@created 01.03.2022
14@modified 06.03.2022
15------------------------------------------------------------------------------
16"""
17## @namespace rosros.rospify.log
18
19# Original file copyright notice:
20
21# Software License Agreement (BSD License)
22#
23# Copyright (c) 2008, Willow Garage, Inc.
24# All rights reserved.
25#
26# Redistribution and use in source and binary forms, with or without
27# modification, are permitted provided that the following conditions
28# are met:
29#
30# * Redistributions of source code must retain the above copyright
31# notice, this list of conditions and the following disclaimer.
32# * Redistributions in binary form must reproduce the above
33# copyright notice, this list of conditions and the following
34# disclaimer in the documentation and/or other materials provided
35# with the distribution.
36# * Neither the name of Willow Garage, Inc. nor the names of its
37# contributors may be used to endorse or promote products derived
38# from this software without specific prior written permission.
39#
40# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
41# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
42# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
43# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
44# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
45# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
46# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
47# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
48# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
49# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
50# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
51# POSSIBILITY OF SUCH DAMAGE.
52import hashlib
53import inspect
54import pickle
55
56from .. import ros2
57
58
59def logdebug(msg, *args, **kwargs):
60 _base_logger(msg, args, kwargs, level="debug")
61
62def loginfo(msg, *args, **kwargs):
63 _base_logger(msg, args, kwargs, level="info")
64
65def logwarn(msg, *args, **kwargs):
66 _base_logger(msg, args, kwargs, level="warn")
67
68def logerr(msg, *args, **kwargs):
69 _base_logger(msg, args, kwargs, level="error")
70
71def logfatal(msg, *args, **kwargs):
72 _base_logger(msg, args, kwargs, level="critical")
73
74
75def logdebug_throttle(period, msg, *args, **kwargs):
76 _base_logger(msg, args, kwargs, throttle=period, level="debug")
77
78def loginfo_throttle(period, msg, *args, **kwargs):
79 _base_logger(msg, args, kwargs, throttle=period, level="info")
80
81def logwarn_throttle(period, msg, *args, **kwargs):
82 _base_logger(msg, args, kwargs, throttle=period, level="warn")
83
84def logerr_throttle(period, msg, *args, **kwargs):
85 _base_logger(msg, args, kwargs, throttle=period, level="error")
86
87def logfatal_throttle(period, msg, *args, **kwargs):
88 _base_logger(msg, args, kwargs, throttle=period, level="critical")
89
90
91def logdebug_throttle_identical(period, msg, *args, **kwargs):
92 _base_logger(msg, args, kwargs, throttle=period, throttle_identical=True,
93 level="debug")
94
95def loginfo_throttle_identical(period, msg, *args, **kwargs):
96 _base_logger(msg, args, kwargs, throttle=period, throttle_identical=True,
97 level="info")
98
99def logwarn_throttle_identical(period, msg, *args, **kwargs):
100 _base_logger(msg, args, kwargs, throttle=period, throttle_identical=True,
101 level="warn")
102
103def logerr_throttle_identical(period, msg, *args, **kwargs):
104 _base_logger(msg, args, kwargs, throttle=period, throttle_identical=True,
105 level="error")
106
107def logfatal_throttle_identical(period, msg, *args, **kwargs):
108 _base_logger(msg, args, kwargs, throttle=period, throttle_identical=True,
109 level="critical")
110
111
112def logdebug_once(msg, *args, **kwargs):
113 _base_logger(msg, args, kwargs, once=True, level="debug")
114
115def loginfo_once(msg, *args, **kwargs):
116 _base_logger(msg, args, kwargs, once=True, level="info")
117
118def logwarn_once(msg, *args, **kwargs):
119 _base_logger(msg, args, kwargs, once=True, level="warn")
120
121def logerr_once(msg, *args, **kwargs):
122 _base_logger(msg, args, kwargs, once=True, level="error")
123
124def logfatal_once(msg, *args, **kwargs):
125 _base_logger(msg, args, kwargs, once=True, level="critical")
126
127
128logout = loginfo # alias deprecated name
129
130logerror = logerr # alias logerr
131
132
133
134def _make_caller_id(depth=2):
135 """Returns caller id at specified call stack depth."""
136 frame = inspect.currentframe().f_back
137 while depth and frame and frame.f_back:
138 frame, depth = frame.f_back, depth - 1
139 return pickle.dumps((inspect.getabsfile(frame), frame.f_lineno, frame.f_lasti))
140
141
142def _base_logger(msg, args, kwargs, throttle=None,
143 throttle_identical=False, level=None, once=False):
144 """
145 Issues the logging call, if throttling or once-argument allow for it.
146
147 @param msg log message
148 @param args positional arguments for log message
149 @param kwargs keyword arguments for log message
150 @param throttle seconds to throttle log messages from call site for
151 @param throttle_identical whether to throttle identical consecutive log messages
152 @param level log level name
153 @param once whether to log only once from callsite
154 """
155 do_log = True
156 if once:
157 do_log = LogInhibitor.passes_once(_make_caller_id())
158 elif throttle_identical:
159 caller_id, throttle_elapsed = _make_caller_id(), False
160 if throttle is not None:
161 throttle_elapsed = LogInhibitor.passes_throttle(caller_id, throttle)
162 do_log = LogInhibitor.passes_identical(caller_id, msg) or throttle_elapsed
163 elif throttle:
164 do_log = LogInhibitor.passes_throttle(_make_caller_id(), throttle)
165
166 if do_log: getattr(ros2.get_logger(), level.lower())(msg, *args, **kwargs)
167
168
169class LogInhibitor:
170
171
172 ONCES = set()
174
175 TIMES = {}
177
178 HASHES = {}
180 @classmethod
181 def passes_once(cls, caller_id):
182 """Returns whether the caller ID has not been once()-d before."""
183 result = caller_id not in cls.ONCES
184 cls.ONCES.add(caller_id)
185 return result
186
187 @classmethod
188 def passes_throttle(cls, caller_id, period):
189 """Returns whether time from last throttle() was more than specified seconds ago, if any."""
190 now, last = ros2.get_rostime(), cls.TIMES.get(caller_id)
191 if last is not None and last > now: cls.TIMES.clear() # Reset all on time jump backward
192 last = cls.TIMES.get(caller_id)
193 result = last is None or now - last > ros2.make_duration(period)
194 cls.TIMES[caller_id] = now
195 return result
196
197 @classmethod
198 def passes_identical(cls, caller_id, msg):
199 """Returns whether last message from caller was different, if any."""
200 result, msg_hash = False, hashlib.md5(msg.encode()).hexdigest()
201 if msg_hash != cls.HASHES.get(caller_id):
202 cls.HASHES[caller_id] = msg_hash
203 result = True
204 return result
205
206
207__all__ = [
208 "logdebug", "loginfo", "logout", "logwarn", "logerr", "logfatal",
209 "logdebug_throttle", "loginfo_throttle", "logwarn_throttle",
210 "logerr_throttle", "logfatal_throttle",
211 "logdebug_throttle_identical", "loginfo_throttle_identical",
212 "logwarn_throttle_identical", "logerr_throttle_identical", "logfatal_throttle_identical",
213 "logdebug_once", "loginfo_once", "logwarn_once", "logerr_once", "logfatal_once",
214]
dict HASHES
Caller IDs and log message hashes for logxyz_throttle_identical()
Definition log.py:182
dict TIMES
Caller IDs and last timestamps for logxyz_throttle() and logxyz_throttle_identical()
Definition log.py:179
ONCES
Caller IDs registered for logxyz_once()
Definition log.py:176
passes_identical(cls, caller_id, msg)
Returns whether last message from caller was different, if any.
Definition log.py:202
passes_throttle(cls, caller_id, period)
Returns whether time from last throttle() was more than specified seconds ago, if any.
Definition log.py:192
passes_once(cls, caller_id)
Returns whether the caller ID has not been once()-d before.
Definition log.py:185
_make_caller_id(depth=2)
Returns caller id at specified call stack depth.
Definition log.py:137
_base_logger(msg, args, kwargs, throttle=None, throttle_identical=False, level=None, once=False)
Issues the logging call, if throttling or once-argument allow for it.
Definition log.py:158