openrvdas.logger.writers.composed_writer

No module-level documentation available.
  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]
class ComposedWriter(logger.writers.writer.Writer):
 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

ComposedWriter(transforms=[], writers=[], **kwargs)
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.

writer_lock
exceptions
def apply_transforms(self, record):
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.

def write(self, record):
 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.