openrvdas.logger.transforms.timestamp_transform
1#!/usr/bin/env python3 2 3import logging 4 5from logger.utils import timestamp # noqa: E402 6from logger.utils.nmea_timestamp import NMEATimestampExtractor # noqa: E402 7from logger.transforms.transform import Transform # noqa: E402 8 9 10################################################################################ 11class TimestampTransform(Transform): 12 """Prepend a timestamp to a text record. 13 14 By default the system clock is used. When ``use_nmea_timestamp`` is 15 enabled, the transform first attempts to extract the timestamp 16 embedded in NMEA 0183 sentences (GGA, RMC, ZDA, and many others) 17 and only falls back to the system clock when no NMEA time is 18 available. 19 20 Parameters 21 ---------- 22 time_format : str 23 strftime format string for the prepended timestamp. Defaults 24 to ``logger.utils.timestamp.TIME_FORMAT`` 25 (``'%Y-%m-%dT%H:%M:%S.%fZ'``). 26 time_zone : datetime.timezone 27 Timezone applied to the timestamp. Defaults to UTC. 28 sep : str 29 Separator inserted between the timestamp and the record text. 30 Defaults to a single space. 31 32 use_nmea_timestamp : bool 33 When True, attempt to parse the NMEA time field from the 34 incoming record before falling back to the system clock. 35 Defaults to False. 36 nmea_timestamp_timeout : float 37 How many seconds a previously-extracted NMEA timestamp remains 38 valid for records that do not carry their own time field 39 (e.g. VTG, HDT). After this period the system clock is used 40 instead. Defaults to 1 s. 41 nmea_time_drift_threshold : float or None 42 If the absolute difference between the NMEA-derived time and 43 the system clock exceeds this value (in seconds), a warning is 44 logged. Set to None to disable the check. Defaults to 0.1 s. 45 quiet : bool 46 Inherited from Transform (passed via ``**kwargs``). When True, 47 suppresses all warnings from both the transform and the 48 underlying NMEATimestampExtractor (drift, staleness, and 49 fallback warnings). Defaults to False. 50 """ 51 def __init__(self, time_format=timestamp.TIME_FORMAT, 52 time_zone=timestamp.timezone.utc, sep=' ', 53 use_nmea_timestamp=False, 54 nmea_timestamp_timeout=1, 55 nmea_time_drift_threshold=0.1, **kwargs): 56 """Create a TimestampTransform.""" 57 super().__init__(**kwargs) # processes 'quiet' and type hints 58 59 self.time_format = time_format 60 self.time_zone = time_zone 61 self.sep = sep 62 self.use_nmea_timestamp = use_nmea_timestamp 63 self.nmea_extractor = None 64 if use_nmea_timestamp: 65 self.nmea_extractor = NMEATimestampExtractor( 66 timeout=nmea_timestamp_timeout, 67 quiet=self.quiet, 68 time_drift_threshold=nmea_time_drift_threshold) 69 70 ############################ 71 def transform(self, record: str, ts=None) -> str: 72 """Prepend a timestamp""" 73 74 # If they've not given us a timestamp, find one. 75 if ts is None: 76 if self.nmea_extractor is None: 77 # Things are simple if we're not trying to extract a timestamp from NMEA. 78 ts = timestamp.time_str(time_format=self.time_format, 79 time_zone=self.time_zone) 80 else: 81 # If we're going to try to extract ts from NMEA 82 ts = self.nmea_extractor.get_timestamp( 83 record, self.time_format, self.time_zone) 84 # Failed, so fall back to system time. 85 if ts is None and not self.quiet: 86 logging.warning( 87 'TimestampTransform: no NMEA timestamp for record, ' 88 'falling back to system time') 89 ts = ts or timestamp.time_str(time_format=self.time_format, 90 time_zone=self.time_zone) 91 92 # See if it's something we can process, and if not, try digesting 93 if not self.can_process_record(record): # inherited from BaseModule() 94 # Special case: if we can't process it, but it's a list, pass 95 # along the same initial timestamp so all elements in the list 96 # share the same timestamp. 97 if isinstance(record, list): 98 return [self.transform(r, ts) for r in record] 99 # If not str and not list, pass it along to digest_record() 100 # to let it try and/or complain. 101 else: 102 return self.digest_record(record) # inherited from BaseModule() 103 104 # If it is something we can process, put a timestamp on it. 105 return ts + self.sep + record
12class TimestampTransform(Transform): 13 """Prepend a timestamp to a text record. 14 15 By default the system clock is used. When ``use_nmea_timestamp`` is 16 enabled, the transform first attempts to extract the timestamp 17 embedded in NMEA 0183 sentences (GGA, RMC, ZDA, and many others) 18 and only falls back to the system clock when no NMEA time is 19 available. 20 21 Parameters 22 ---------- 23 time_format : str 24 strftime format string for the prepended timestamp. Defaults 25 to ``logger.utils.timestamp.TIME_FORMAT`` 26 (``'%Y-%m-%dT%H:%M:%S.%fZ'``). 27 time_zone : datetime.timezone 28 Timezone applied to the timestamp. Defaults to UTC. 29 sep : str 30 Separator inserted between the timestamp and the record text. 31 Defaults to a single space. 32 33 use_nmea_timestamp : bool 34 When True, attempt to parse the NMEA time field from the 35 incoming record before falling back to the system clock. 36 Defaults to False. 37 nmea_timestamp_timeout : float 38 How many seconds a previously-extracted NMEA timestamp remains 39 valid for records that do not carry their own time field 40 (e.g. VTG, HDT). After this period the system clock is used 41 instead. Defaults to 1 s. 42 nmea_time_drift_threshold : float or None 43 If the absolute difference between the NMEA-derived time and 44 the system clock exceeds this value (in seconds), a warning is 45 logged. Set to None to disable the check. Defaults to 0.1 s. 46 quiet : bool 47 Inherited from Transform (passed via ``**kwargs``). When True, 48 suppresses all warnings from both the transform and the 49 underlying NMEATimestampExtractor (drift, staleness, and 50 fallback warnings). Defaults to False. 51 """ 52 def __init__(self, time_format=timestamp.TIME_FORMAT, 53 time_zone=timestamp.timezone.utc, sep=' ', 54 use_nmea_timestamp=False, 55 nmea_timestamp_timeout=1, 56 nmea_time_drift_threshold=0.1, **kwargs): 57 """Create a TimestampTransform.""" 58 super().__init__(**kwargs) # processes 'quiet' and type hints 59 60 self.time_format = time_format 61 self.time_zone = time_zone 62 self.sep = sep 63 self.use_nmea_timestamp = use_nmea_timestamp 64 self.nmea_extractor = None 65 if use_nmea_timestamp: 66 self.nmea_extractor = NMEATimestampExtractor( 67 timeout=nmea_timestamp_timeout, 68 quiet=self.quiet, 69 time_drift_threshold=nmea_time_drift_threshold) 70 71 ############################ 72 def transform(self, record: str, ts=None) -> str: 73 """Prepend a timestamp""" 74 75 # If they've not given us a timestamp, find one. 76 if ts is None: 77 if self.nmea_extractor is None: 78 # Things are simple if we're not trying to extract a timestamp from NMEA. 79 ts = timestamp.time_str(time_format=self.time_format, 80 time_zone=self.time_zone) 81 else: 82 # If we're going to try to extract ts from NMEA 83 ts = self.nmea_extractor.get_timestamp( 84 record, self.time_format, self.time_zone) 85 # Failed, so fall back to system time. 86 if ts is None and not self.quiet: 87 logging.warning( 88 'TimestampTransform: no NMEA timestamp for record, ' 89 'falling back to system time') 90 ts = ts or timestamp.time_str(time_format=self.time_format, 91 time_zone=self.time_zone) 92 93 # See if it's something we can process, and if not, try digesting 94 if not self.can_process_record(record): # inherited from BaseModule() 95 # Special case: if we can't process it, but it's a list, pass 96 # along the same initial timestamp so all elements in the list 97 # share the same timestamp. 98 if isinstance(record, list): 99 return [self.transform(r, ts) for r in record] 100 # If not str and not list, pass it along to digest_record() 101 # to let it try and/or complain. 102 else: 103 return self.digest_record(record) # inherited from BaseModule() 104 105 # If it is something we can process, put a timestamp on it. 106 return ts + self.sep + record
Prepend a timestamp to a text record.
By default the system clock is used. When use_nmea_timestamp is
enabled, the transform first attempts to extract the timestamp
embedded in NMEA 0183 sentences (GGA, RMC, ZDA, and many others)
and only falls back to the system clock when no NMEA time is
available.
Parameters
time_format : str
strftime format string for the prepended timestamp. Defaults
to logger.utils.timestamp.TIME_FORMAT
('%Y-%m-%dT%H:%M:%S.%fZ').
time_zone : datetime.timezone
Timezone applied to the timestamp. Defaults to UTC.
sep : str
Separator inserted between the timestamp and the record text.
Defaults to a single space.
use_nmea_timestamp : bool
When True, attempt to parse the NMEA time field from the
incoming record before falling back to the system clock.
Defaults to False.
nmea_timestamp_timeout : float
How many seconds a previously-extracted NMEA timestamp remains
valid for records that do not carry their own time field
(e.g. VTG, HDT). After this period the system clock is used
instead. Defaults to 1 s.
nmea_time_drift_threshold : float or None
If the absolute difference between the NMEA-derived time and
the system clock exceeds this value (in seconds), a warning is
logged. Set to None to disable the check. Defaults to 0.1 s.
quiet : bool
Inherited from Transform (passed via **kwargs). When True,
suppresses all warnings from both the transform and the
underlying NMEATimestampExtractor (drift, staleness, and
fallback warnings). Defaults to False.
52 def __init__(self, time_format=timestamp.TIME_FORMAT, 53 time_zone=timestamp.timezone.utc, sep=' ', 54 use_nmea_timestamp=False, 55 nmea_timestamp_timeout=1, 56 nmea_time_drift_threshold=0.1, **kwargs): 57 """Create a TimestampTransform.""" 58 super().__init__(**kwargs) # processes 'quiet' and type hints 59 60 self.time_format = time_format 61 self.time_zone = time_zone 62 self.sep = sep 63 self.use_nmea_timestamp = use_nmea_timestamp 64 self.nmea_extractor = None 65 if use_nmea_timestamp: 66 self.nmea_extractor = NMEATimestampExtractor( 67 timeout=nmea_timestamp_timeout, 68 quiet=self.quiet, 69 time_drift_threshold=nmea_time_drift_threshold)
Create a TimestampTransform.
72 def transform(self, record: str, ts=None) -> str: 73 """Prepend a timestamp""" 74 75 # If they've not given us a timestamp, find one. 76 if ts is None: 77 if self.nmea_extractor is None: 78 # Things are simple if we're not trying to extract a timestamp from NMEA. 79 ts = timestamp.time_str(time_format=self.time_format, 80 time_zone=self.time_zone) 81 else: 82 # If we're going to try to extract ts from NMEA 83 ts = self.nmea_extractor.get_timestamp( 84 record, self.time_format, self.time_zone) 85 # Failed, so fall back to system time. 86 if ts is None and not self.quiet: 87 logging.warning( 88 'TimestampTransform: no NMEA timestamp for record, ' 89 'falling back to system time') 90 ts = ts or timestamp.time_str(time_format=self.time_format, 91 time_zone=self.time_zone) 92 93 # See if it's something we can process, and if not, try digesting 94 if not self.can_process_record(record): # inherited from BaseModule() 95 # Special case: if we can't process it, but it's a list, pass 96 # along the same initial timestamp so all elements in the list 97 # share the same timestamp. 98 if isinstance(record, list): 99 return [self.transform(r, ts) for r in record] 100 # If not str and not list, pass it along to digest_record() 101 # to let it try and/or complain. 102 else: 103 return self.digest_record(record) # inherited from BaseModule() 104 105 # If it is something we can process, put a timestamp on it. 106 return ts + self.sep + record
Prepend a timestamp