openrvdas.logger.writers.composed_writer
1#!/usr/bin/env python3 2 3import logging 4import threading 5 6from logger.writers.writer import Writer # noqa: E402 7 8 9class ComposedWriter(Writer): 10 ############################ 11 def __init__(self, transforms=[], writers=[], **kwargs): 12 """ 13 Apply zero or more Transforms (in series) to passed records, then 14 write them (in parallel threads) using the specified Writers. 15 16 ``` 17 transforms A single Transform, a list of Transforms, or None. 18 19 writers A single Writer or a list of Writers. 20 ``` 21 Example: 22 ``` 23 writer = ComposedWriter(transforms=[TimestampTransform(), 24 PrefixTransform('gyr1')], 25 writers=[NetworkWriter(':6221'), 26 LogfileWriter('/logs/gyr1')], 27 ) 28 ``` 29 NOTE: we make the rash assumption that transforms are thread-safe, 30 that is, that no mischief or corrupted internal state will result if 31 more than one thread calls a transform at the same time. To be 32 thread-safe, a transform must protect any changes to its internal 33 state with a non-re-entrant thread lock, as described in the threading 34 module. We do *not* make this assumption of our writers, and impose a 35 lock to prevent a writer's write() method from being called a second 36 time if the first has not yet completed. 37 """ 38 super().__init__(**kwargs) # processes 'quiet' and type hints 39 40 # Make transforms a list if it's not. Even if it's only one transform. 41 if not isinstance(transforms, list): 42 self.transforms = [transforms] 43 else: 44 self.transforms = transforms 45 46 # Make writers a list if it's not. Even if it's only one writer. 47 if not isinstance(writers, list): 48 self.writers = [writers] 49 else: 50 self.writers = writers 51 52 # One lock per writer, to prevent us from accidental re-entry if a 53 # new write is requested before the previous one has completed. 54 self.writer_lock = [threading.Lock() for w in self.writers] 55 self.exceptions = [None for w in self.writers] 56 57 ############################ 58 59 def _run_writer(self, index, record): 60 """Internal: grab the appropriate lock and call the appropriate 61 write() method. If there's an exception, save it.""" 62 with self.writer_lock[index]: 63 try: 64 self.writers[index].write(record) 65 except Exception as e: 66 self.exceptions[index] = e 67 68 ############################ 69 def apply_transforms(self, record): 70 """Internal: apply the transforms in series.""" 71 if record: 72 for t in self.transforms: 73 record = t.transform(record) 74 if not record: 75 break 76 return record 77 78 ############################ 79 def write(self, record): 80 """Transform the passed record and dispatch it to writers.""" 81 # Transforms run in series 82 record = self.apply_transforms(record) 83 if record is None: 84 return 85 86 # No idea why someone would instantiate without writers, but it's 87 # plausible. Try to be accommodating. 88 if not self.writers: 89 return 90 91 # If we only have one writer, there's no point making things 92 # complicated. Just write and return. 93 if len(self.writers) == 1: 94 self.writers[0].write(record) 95 return 96 97 # Fire record off to write() requests for each writer. 98 writer_threads = [] 99 for i in range(len(self.writers)): 100 try: 101 writer_name = str(type(self.writers[i])) 102 t = threading.Thread(target=self._run_writer, args=(i, record), 103 name=writer_name, daemon=True) 104 t.start() 105 except (OSError, RuntimeError) as e: 106 logging.error('ComposedWriter failed to write to %s: %s', 107 writer_name, e) 108 t = None 109 writer_threads.append(t) 110 111 # Wait for all writes to complete 112 for t in writer_threads: 113 if t: 114 t.join() 115 116 # Were there any exceptions? Arbitrarily raise the first one in list 117 exceptions = [e for e in self.exceptions if e] 118 for e in exceptions: 119 logging.error(e) 120 if exceptions: 121 raise exceptions[0]
10class ComposedWriter(Writer): 11 ############################ 12 def __init__(self, transforms=[], writers=[], **kwargs): 13 """ 14 Apply zero or more Transforms (in series) to passed records, then 15 write them (in parallel threads) using the specified Writers. 16 17 ``` 18 transforms A single Transform, a list of Transforms, or None. 19 20 writers A single Writer or a list of Writers. 21 ``` 22 Example: 23 ``` 24 writer = ComposedWriter(transforms=[TimestampTransform(), 25 PrefixTransform('gyr1')], 26 writers=[NetworkWriter(':6221'), 27 LogfileWriter('/logs/gyr1')], 28 ) 29 ``` 30 NOTE: we make the rash assumption that transforms are thread-safe, 31 that is, that no mischief or corrupted internal state will result if 32 more than one thread calls a transform at the same time. To be 33 thread-safe, a transform must protect any changes to its internal 34 state with a non-re-entrant thread lock, as described in the threading 35 module. We do *not* make this assumption of our writers, and impose a 36 lock to prevent a writer's write() method from being called a second 37 time if the first has not yet completed. 38 """ 39 super().__init__(**kwargs) # processes 'quiet' and type hints 40 41 # Make transforms a list if it's not. Even if it's only one transform. 42 if not isinstance(transforms, list): 43 self.transforms = [transforms] 44 else: 45 self.transforms = transforms 46 47 # Make writers a list if it's not. Even if it's only one writer. 48 if not isinstance(writers, list): 49 self.writers = [writers] 50 else: 51 self.writers = writers 52 53 # One lock per writer, to prevent us from accidental re-entry if a 54 # new write is requested before the previous one has completed. 55 self.writer_lock = [threading.Lock() for w in self.writers] 56 self.exceptions = [None for w in self.writers] 57 58 ############################ 59 60 def _run_writer(self, index, record): 61 """Internal: grab the appropriate lock and call the appropriate 62 write() method. If there's an exception, save it.""" 63 with self.writer_lock[index]: 64 try: 65 self.writers[index].write(record) 66 except Exception as e: 67 self.exceptions[index] = e 68 69 ############################ 70 def apply_transforms(self, record): 71 """Internal: apply the transforms in series.""" 72 if record: 73 for t in self.transforms: 74 record = t.transform(record) 75 if not record: 76 break 77 return record 78 79 ############################ 80 def write(self, record): 81 """Transform the passed record and dispatch it to writers.""" 82 # Transforms run in series 83 record = self.apply_transforms(record) 84 if record is None: 85 return 86 87 # No idea why someone would instantiate without writers, but it's 88 # plausible. Try to be accommodating. 89 if not self.writers: 90 return 91 92 # If we only have one writer, there's no point making things 93 # complicated. Just write and return. 94 if len(self.writers) == 1: 95 self.writers[0].write(record) 96 return 97 98 # Fire record off to write() requests for each writer. 99 writer_threads = [] 100 for i in range(len(self.writers)): 101 try: 102 writer_name = str(type(self.writers[i])) 103 t = threading.Thread(target=self._run_writer, args=(i, record), 104 name=writer_name, daemon=True) 105 t.start() 106 except (OSError, RuntimeError) as e: 107 logging.error('ComposedWriter failed to write to %s: %s', 108 writer_name, e) 109 t = None 110 writer_threads.append(t) 111 112 # Wait for all writes to complete 113 for t in writer_threads: 114 if t: 115 t.join() 116 117 # Were there any exceptions? Arbitrarily raise the first one in list 118 exceptions = [e for e in self.exceptions if e] 119 for e in exceptions: 120 logging.error(e) 121 if exceptions: 122 raise exceptions[0]
Base class Writer about which we know nothing else. By default the input format is Unknown unless overridden.
Passes arguments quiet, encoding and encoding_errors up to BaseModule
12 def __init__(self, transforms=[], writers=[], **kwargs): 13 """ 14 Apply zero or more Transforms (in series) to passed records, then 15 write them (in parallel threads) using the specified Writers. 16 17 ``` 18 transforms A single Transform, a list of Transforms, or None. 19 20 writers A single Writer or a list of Writers. 21 ``` 22 Example: 23 ``` 24 writer = ComposedWriter(transforms=[TimestampTransform(), 25 PrefixTransform('gyr1')], 26 writers=[NetworkWriter(':6221'), 27 LogfileWriter('/logs/gyr1')], 28 ) 29 ``` 30 NOTE: we make the rash assumption that transforms are thread-safe, 31 that is, that no mischief or corrupted internal state will result if 32 more than one thread calls a transform at the same time. To be 33 thread-safe, a transform must protect any changes to its internal 34 state with a non-re-entrant thread lock, as described in the threading 35 module. We do *not* make this assumption of our writers, and impose a 36 lock to prevent a writer's write() method from being called a second 37 time if the first has not yet completed. 38 """ 39 super().__init__(**kwargs) # processes 'quiet' and type hints 40 41 # Make transforms a list if it's not. Even if it's only one transform. 42 if not isinstance(transforms, list): 43 self.transforms = [transforms] 44 else: 45 self.transforms = transforms 46 47 # Make writers a list if it's not. Even if it's only one writer. 48 if not isinstance(writers, list): 49 self.writers = [writers] 50 else: 51 self.writers = writers 52 53 # One lock per writer, to prevent us from accidental re-entry if a 54 # new write is requested before the previous one has completed. 55 self.writer_lock = [threading.Lock() for w in self.writers] 56 self.exceptions = [None for w in self.writers]
Apply zero or more Transforms (in series) to passed records, then write them (in parallel threads) using the specified Writers.
transforms A single Transform, a list of Transforms, or None.
writers A single Writer or a list of Writers.
Example:
writer = ComposedWriter(transforms=[TimestampTransform(),
PrefixTransform('gyr1')],
writers=[NetworkWriter(':6221'),
LogfileWriter('/logs/gyr1')],
)
NOTE: we make the rash assumption that transforms are thread-safe, that is, that no mischief or corrupted internal state will result if more than one thread calls a transform at the same time. To be thread-safe, a transform must protect any changes to its internal state with a non-re-entrant thread lock, as described in the threading module. We do not make this assumption of our writers, and impose a lock to prevent a writer's write() method from being called a second time if the first has not yet completed.
70 def apply_transforms(self, record): 71 """Internal: apply the transforms in series.""" 72 if record: 73 for t in self.transforms: 74 record = t.transform(record) 75 if not record: 76 break 77 return record
Internal: apply the transforms in series.
80 def write(self, record): 81 """Transform the passed record and dispatch it to writers.""" 82 # Transforms run in series 83 record = self.apply_transforms(record) 84 if record is None: 85 return 86 87 # No idea why someone would instantiate without writers, but it's 88 # plausible. Try to be accommodating. 89 if not self.writers: 90 return 91 92 # If we only have one writer, there's no point making things 93 # complicated. Just write and return. 94 if len(self.writers) == 1: 95 self.writers[0].write(record) 96 return 97 98 # Fire record off to write() requests for each writer. 99 writer_threads = [] 100 for i in range(len(self.writers)): 101 try: 102 writer_name = str(type(self.writers[i])) 103 t = threading.Thread(target=self._run_writer, args=(i, record), 104 name=writer_name, daemon=True) 105 t.start() 106 except (OSError, RuntimeError) as e: 107 logging.error('ComposedWriter failed to write to %s: %s', 108 writer_name, e) 109 t = None 110 writer_threads.append(t) 111 112 # Wait for all writes to complete 113 for t in writer_threads: 114 if t: 115 t.join() 116 117 # Were there any exceptions? Arbitrarily raise the first one in list 118 exceptions = [e for e in self.exceptions if e] 119 for e in exceptions: 120 logging.error(e) 121 if exceptions: 122 raise exceptions[0]
Transform the passed record and dispatch it to writers.