openrvdas.logger.writers.tcp_writer

No module-level documentation available.
  1#!/usr/bin/env python3
  2
  3import logging
  4import socket
  5
  6from typing import Union
  7
  8from logger.writers.writer import Writer  # noqa: E402
  9
 10
 11class TCPWriter(Writer):
 12    """Write TCP packtes to network."""
 13    def __init__(self, destination, port,
 14                 num_retry=2, warning_limit=5, eol='',
 15                 reuseaddr=False, reuseport=False, **kwargs):
 16        """
 17        Write records to a TCP network socket.
 18
 19        ```
 20        destination  The destination to send TCP packets to.  Can be resolvable hostname
 21                     or valid IP address.
 22
 23        port         Port to which packets should be sent
 24
 25        num_retry    Number of times to retry if write fails.
 26
 27        warning_limit  Number of times the writer gives up on writing a message
 28                     (without any intervening successes) before it gives up complaining
 29                     about failures.
 30
 31        eol          If specified, an end of line string to append to record
 32                     before sending
 33
 34        reuseaddr    Specifies wether to set SO_REUSEADDR on the created socket.  If
 35                     you don't know you need this, don't enable it.
 36
 37        reuseport    Specifies wether to set SO_REUSEPORT on the created socket.  If
 38                     you don't know you need this, don't enable it.
 39
 40        encoding - 'utf-8' by default. If empty or None, do not attempt any
 41                decoding and return raw bytes. Other possible encodings are
 42                listed in online documentation here:
 43                https://docs.python.org/3/library/codecs.html#standard-encodings
 44
 45        encoding_errors - 'ignore' by default. Other error strategies are
 46                'strict', 'replace', and 'backslashreplace', described here:
 47                https://docs.python.org/3/howto/unicode.html#encodings
 48
 49        ```
 50        """
 51        super().__init__(**kwargs)  # processes 'quiet', encodings and type hints
 52
 53        self.num_retry = num_retry
 54        self.warning_limit = warning_limit
 55        self.num_warnings = 0
 56
 57        # 'eol' comes in as a (probably escaped) string. We need to
 58        # unescape it, which means converting to bytes and back.
 59        if eol is not None and self.encoding:
 60            eol = self._unescape_str(eol)
 61        self.eol = eol
 62
 63        # do name resolution once in the constructor
 64        #
 65        # NOTE: This means the hostname must be valid when we start, otherwise
 66        #       the config_check code will puke.  That's fine.  The alternative
 67        #       is we let name resolution happen while we're running, but then
 68        #       each failed lookup is going to block our write() routine for a
 69        #       few seconds - not good.
 70        #
 71        # NOTE: This also catches specifying impropperly formatted IP
 72        #       addresses.  The only way through gethostbyname() w/out throwing
 73        #       an exception is to provide a valid hostname or IP address.
 74        #       Propperly formatted IPs just get returned.
 75        #
 76        self.destination = socket.gethostbyname(destination)
 77
 78        # make sure port gets stored as an int, even if passed in as a string
 79        self.port = int(port)
 80
 81        self.reuseaddr = reuseaddr
 82        self.reuseport = reuseport
 83
 84        # socket gets initialized on-demand in write()
 85        #
 86        # NOTE: Since connect() can actually fail w/ a TCP socket, we don't try
 87        #       that here.  Let's just do safe things.
 88        #
 89        self.socket = None
 90
 91    ############################
 92    def __del__(self):
 93        if self.socket:
 94            logging.debug('__del__: closing socket')
 95            self._close_socket(self.socket)
 96
 97    ############################
 98    def _open_socket(self):
 99        """Do socket prep so we're ready to write().  Returns socket object or None on
100        failure.
101        """
102        this_socket = socket.socket(family=socket.AF_INET,
103                                    type=socket.SOCK_STREAM,
104                                    proto=socket.IPPROTO_TCP)
105        if self.reuseaddr:
106            this_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
107        if self.reuseport:
108            try:  # Raspbian doesn't recognize SO_REUSEPORT
109                this_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
110            except AttributeError:
111                logging.warning('Unable to set socket REUSEPORT; may be unsupported')
112
113        # Try connecting
114        try:
115            this_socket.connect((self.destination, self.port))
116        except OSError as e:
117            if self.num_warnings < self.warning_limit:
118                logging.error('Unable to connect to %s:%d: %s', self.destination, self.port, e)
119                self.num_warnings += 1
120                if self.num_warnings == self.warning_limit:
121                    logging.error('TCPWriter._open_socket() - muting errors')
122            return None
123
124        # success, reset warning counter
125        if self.num_warnings == self.warning_limit:
126            logging.info('TCPWriter._open_socket() successfully connected after a series of '
127                         'failures; restting warnings.')
128        self.num_warnings = 0
129        return this_socket
130
131    ############################
132    def _close_socket(self, s):
133        try:
134            s.shutdown(socket.SHUT_RDWR)
135            s.close()
136        except OSError:
137            logging.debug('Unable to close socket')
138
139    ############################
140    def write(self, record: Union[str, bytes]):
141        """Write the record to the network."""
142
143        # See if it's something we can process, and if not, try digesting
144        if not self.can_process_record(record):  # inherited from BaseModule()
145            self.digest_record(record)  # inherited from BaseModule()
146            return
147
148        # Append eol if configured
149        if self.eol:
150            record += self.eol
151
152        # NOTE: Unlike UDP socket, which really only can detect failure during
153        #       send() (and even then only very poorly), a TCP connect() can
154        #       fail, so we need to track attempts to connect in order to honor
155        #       `num_retry` on a disconnected socket.
156        #
157        #       We also need to tear down and start over with a fresh connect()
158        #       if send() fails.
159        #
160        num_tries = 0
161        bytes_sent = 0
162        rec_len = len(record)
163        while num_tries <= self.num_retry and bytes_sent < rec_len:
164            num_tries += 1
165            # attempt to connect socket if needed, up to `num_tries` times
166            if not self.socket:
167                self.socket = self._open_socket()
168            if not self.socket:
169                # no need for further error messages, _open_socket() will have
170                # already complained sufficiently
171                continue
172
173            # attempt to verify we're really connected
174            #
175            # NOTE: This is because the only way to detect a closed TCP socket
176            #       is via recv(), which is awkward when we're trying to
177            #       guarantee a write was successful.
178            #
179            #         https://stackoverflow.com/questions/49457631/c-server-socket-closed-but-client-socket-still-able-send-two-more-packages
180            #
181            #         "send() just puts the data in the kernel socket buffer,
182            #         it doesn't wait for the data to be transmitted or the
183            #         server to acknowledge receipt of it.
184            #
185            #         You don't get SIGPIPE until the data is transmitted and
186            #         the server rejects it by sending a RST segment.
187            #
188            #         It works this way because each direction of a TCP
189            #         connection is treated independently. When the server
190            #         closes the socket, it sends a FIN segment. This just
191            #         tells the client that the server is done sending data, it
192            #         doesn't mean that the server cannot receive data. There's
193            #         nothing in the TCP protocol that allows the server to
194            #         inform the client of this. So the only way to find out
195            #         that it's not accepting any more data is when the client
196            #         gets that RST response.
197            #
198            #         Informing the client that they shouldn't send anything
199            #         more is usually done in the application protocol, since
200            #         it's not available in TCP."
201            #
202            #       So it might take a couple "successful" send() calls before
203            #       the socket layer notices nobody's home on the other end.
204            #
205            #       Fine then, let's do a 1 byte recv() w/ flags arranged so we
206            #       don't really read off the socket forcing it to raise
207            #       BlockingIOError if the socket is still connected.  Not
208            #       perfect, because the remote side could still close after
209            #       this call but before our send() below, but it's something.
210            #
211            try:
212                # If the socket is still connected, this will raise
213                # BlockingIOError.  If it's not, it returns 0 bytes.
214                msg = self.socket.recv(1, socket.MSG_DONTWAIT | socket.MSG_PEEK)  # noqa F841
215                logging.error('TCPWriter: connection closed')
216                self.num_warnings += 1
217                self._close_socket(self.socket)
218                self.socket = None
219                continue
220            except BlockingIOError:
221                pass
222
223            # we're connected, try sending
224            try:
225                bytes_sent = self.socket.send(self._encode_str(record))
226            except OSError as e:
227                # send failed, we need to disconnect and start over
228                #
229                # NOTE: In order to get this far, we have to have successfully
230                #       connected, which means we JUST reset self.num_warnings
231                #
232                logging.error('TCPWriter: send() error: %s:%d: %s',
233                              self.destination, self.port, str(e))
234                self.num_warnings += 1
235                self._close_socket(self.socket)
236                self.socket = None
237                continue
238
239            # check to see if we really wrote it all
240            if bytes_sent < rec_len and not self.quiet:
241                logging.warning('TCPWriter: send() did not send the whole record: '
242                                'bytes_sent=%d, rec_len=%d', bytes_sent, rec_len)
243
244        logging.debug('TCPWriter.write() wrote %d/%d bytes after %d tries',
245                      bytes_sent, rec_len, num_tries)
class TCPWriter(logger.writers.writer.Writer):
 12class TCPWriter(Writer):
 13    """Write TCP packtes to network."""
 14    def __init__(self, destination, port,
 15                 num_retry=2, warning_limit=5, eol='',
 16                 reuseaddr=False, reuseport=False, **kwargs):
 17        """
 18        Write records to a TCP network socket.
 19
 20        ```
 21        destination  The destination to send TCP packets to.  Can be resolvable hostname
 22                     or valid IP address.
 23
 24        port         Port to which packets should be sent
 25
 26        num_retry    Number of times to retry if write fails.
 27
 28        warning_limit  Number of times the writer gives up on writing a message
 29                     (without any intervening successes) before it gives up complaining
 30                     about failures.
 31
 32        eol          If specified, an end of line string to append to record
 33                     before sending
 34
 35        reuseaddr    Specifies wether to set SO_REUSEADDR on the created socket.  If
 36                     you don't know you need this, don't enable it.
 37
 38        reuseport    Specifies wether to set SO_REUSEPORT on the created socket.  If
 39                     you don't know you need this, don't enable it.
 40
 41        encoding - 'utf-8' by default. If empty or None, do not attempt any
 42                decoding and return raw bytes. Other possible encodings are
 43                listed in online documentation here:
 44                https://docs.python.org/3/library/codecs.html#standard-encodings
 45
 46        encoding_errors - 'ignore' by default. Other error strategies are
 47                'strict', 'replace', and 'backslashreplace', described here:
 48                https://docs.python.org/3/howto/unicode.html#encodings
 49
 50        ```
 51        """
 52        super().__init__(**kwargs)  # processes 'quiet', encodings and type hints
 53
 54        self.num_retry = num_retry
 55        self.warning_limit = warning_limit
 56        self.num_warnings = 0
 57
 58        # 'eol' comes in as a (probably escaped) string. We need to
 59        # unescape it, which means converting to bytes and back.
 60        if eol is not None and self.encoding:
 61            eol = self._unescape_str(eol)
 62        self.eol = eol
 63
 64        # do name resolution once in the constructor
 65        #
 66        # NOTE: This means the hostname must be valid when we start, otherwise
 67        #       the config_check code will puke.  That's fine.  The alternative
 68        #       is we let name resolution happen while we're running, but then
 69        #       each failed lookup is going to block our write() routine for a
 70        #       few seconds - not good.
 71        #
 72        # NOTE: This also catches specifying impropperly formatted IP
 73        #       addresses.  The only way through gethostbyname() w/out throwing
 74        #       an exception is to provide a valid hostname or IP address.
 75        #       Propperly formatted IPs just get returned.
 76        #
 77        self.destination = socket.gethostbyname(destination)
 78
 79        # make sure port gets stored as an int, even if passed in as a string
 80        self.port = int(port)
 81
 82        self.reuseaddr = reuseaddr
 83        self.reuseport = reuseport
 84
 85        # socket gets initialized on-demand in write()
 86        #
 87        # NOTE: Since connect() can actually fail w/ a TCP socket, we don't try
 88        #       that here.  Let's just do safe things.
 89        #
 90        self.socket = None
 91
 92    ############################
 93    def __del__(self):
 94        if self.socket:
 95            logging.debug('__del__: closing socket')
 96            self._close_socket(self.socket)
 97
 98    ############################
 99    def _open_socket(self):
100        """Do socket prep so we're ready to write().  Returns socket object or None on
101        failure.
102        """
103        this_socket = socket.socket(family=socket.AF_INET,
104                                    type=socket.SOCK_STREAM,
105                                    proto=socket.IPPROTO_TCP)
106        if self.reuseaddr:
107            this_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
108        if self.reuseport:
109            try:  # Raspbian doesn't recognize SO_REUSEPORT
110                this_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
111            except AttributeError:
112                logging.warning('Unable to set socket REUSEPORT; may be unsupported')
113
114        # Try connecting
115        try:
116            this_socket.connect((self.destination, self.port))
117        except OSError as e:
118            if self.num_warnings < self.warning_limit:
119                logging.error('Unable to connect to %s:%d: %s', self.destination, self.port, e)
120                self.num_warnings += 1
121                if self.num_warnings == self.warning_limit:
122                    logging.error('TCPWriter._open_socket() - muting errors')
123            return None
124
125        # success, reset warning counter
126        if self.num_warnings == self.warning_limit:
127            logging.info('TCPWriter._open_socket() successfully connected after a series of '
128                         'failures; restting warnings.')
129        self.num_warnings = 0
130        return this_socket
131
132    ############################
133    def _close_socket(self, s):
134        try:
135            s.shutdown(socket.SHUT_RDWR)
136            s.close()
137        except OSError:
138            logging.debug('Unable to close socket')
139
140    ############################
141    def write(self, record: Union[str, bytes]):
142        """Write the record to the network."""
143
144        # See if it's something we can process, and if not, try digesting
145        if not self.can_process_record(record):  # inherited from BaseModule()
146            self.digest_record(record)  # inherited from BaseModule()
147            return
148
149        # Append eol if configured
150        if self.eol:
151            record += self.eol
152
153        # NOTE: Unlike UDP socket, which really only can detect failure during
154        #       send() (and even then only very poorly), a TCP connect() can
155        #       fail, so we need to track attempts to connect in order to honor
156        #       `num_retry` on a disconnected socket.
157        #
158        #       We also need to tear down and start over with a fresh connect()
159        #       if send() fails.
160        #
161        num_tries = 0
162        bytes_sent = 0
163        rec_len = len(record)
164        while num_tries <= self.num_retry and bytes_sent < rec_len:
165            num_tries += 1
166            # attempt to connect socket if needed, up to `num_tries` times
167            if not self.socket:
168                self.socket = self._open_socket()
169            if not self.socket:
170                # no need for further error messages, _open_socket() will have
171                # already complained sufficiently
172                continue
173
174            # attempt to verify we're really connected
175            #
176            # NOTE: This is because the only way to detect a closed TCP socket
177            #       is via recv(), which is awkward when we're trying to
178            #       guarantee a write was successful.
179            #
180            #         https://stackoverflow.com/questions/49457631/c-server-socket-closed-but-client-socket-still-able-send-two-more-packages
181            #
182            #         "send() just puts the data in the kernel socket buffer,
183            #         it doesn't wait for the data to be transmitted or the
184            #         server to acknowledge receipt of it.
185            #
186            #         You don't get SIGPIPE until the data is transmitted and
187            #         the server rejects it by sending a RST segment.
188            #
189            #         It works this way because each direction of a TCP
190            #         connection is treated independently. When the server
191            #         closes the socket, it sends a FIN segment. This just
192            #         tells the client that the server is done sending data, it
193            #         doesn't mean that the server cannot receive data. There's
194            #         nothing in the TCP protocol that allows the server to
195            #         inform the client of this. So the only way to find out
196            #         that it's not accepting any more data is when the client
197            #         gets that RST response.
198            #
199            #         Informing the client that they shouldn't send anything
200            #         more is usually done in the application protocol, since
201            #         it's not available in TCP."
202            #
203            #       So it might take a couple "successful" send() calls before
204            #       the socket layer notices nobody's home on the other end.
205            #
206            #       Fine then, let's do a 1 byte recv() w/ flags arranged so we
207            #       don't really read off the socket forcing it to raise
208            #       BlockingIOError if the socket is still connected.  Not
209            #       perfect, because the remote side could still close after
210            #       this call but before our send() below, but it's something.
211            #
212            try:
213                # If the socket is still connected, this will raise
214                # BlockingIOError.  If it's not, it returns 0 bytes.
215                msg = self.socket.recv(1, socket.MSG_DONTWAIT | socket.MSG_PEEK)  # noqa F841
216                logging.error('TCPWriter: connection closed')
217                self.num_warnings += 1
218                self._close_socket(self.socket)
219                self.socket = None
220                continue
221            except BlockingIOError:
222                pass
223
224            # we're connected, try sending
225            try:
226                bytes_sent = self.socket.send(self._encode_str(record))
227            except OSError as e:
228                # send failed, we need to disconnect and start over
229                #
230                # NOTE: In order to get this far, we have to have successfully
231                #       connected, which means we JUST reset self.num_warnings
232                #
233                logging.error('TCPWriter: send() error: %s:%d: %s',
234                              self.destination, self.port, str(e))
235                self.num_warnings += 1
236                self._close_socket(self.socket)
237                self.socket = None
238                continue
239
240            # check to see if we really wrote it all
241            if bytes_sent < rec_len and not self.quiet:
242                logging.warning('TCPWriter: send() did not send the whole record: '
243                                'bytes_sent=%d, rec_len=%d', bytes_sent, rec_len)
244
245        logging.debug('TCPWriter.write() wrote %d/%d bytes after %d tries',
246                      bytes_sent, rec_len, num_tries)

Write TCP packtes to network.

TCPWriter( destination, port, num_retry=2, warning_limit=5, eol='', reuseaddr=False, reuseport=False, **kwargs)
14    def __init__(self, destination, port,
15                 num_retry=2, warning_limit=5, eol='',
16                 reuseaddr=False, reuseport=False, **kwargs):
17        """
18        Write records to a TCP network socket.
19
20        ```
21        destination  The destination to send TCP packets to.  Can be resolvable hostname
22                     or valid IP address.
23
24        port         Port to which packets should be sent
25
26        num_retry    Number of times to retry if write fails.
27
28        warning_limit  Number of times the writer gives up on writing a message
29                     (without any intervening successes) before it gives up complaining
30                     about failures.
31
32        eol          If specified, an end of line string to append to record
33                     before sending
34
35        reuseaddr    Specifies wether to set SO_REUSEADDR on the created socket.  If
36                     you don't know you need this, don't enable it.
37
38        reuseport    Specifies wether to set SO_REUSEPORT on the created socket.  If
39                     you don't know you need this, don't enable it.
40
41        encoding - 'utf-8' by default. If empty or None, do not attempt any
42                decoding and return raw bytes. Other possible encodings are
43                listed in online documentation here:
44                https://docs.python.org/3/library/codecs.html#standard-encodings
45
46        encoding_errors - 'ignore' by default. Other error strategies are
47                'strict', 'replace', and 'backslashreplace', described here:
48                https://docs.python.org/3/howto/unicode.html#encodings
49
50        ```
51        """
52        super().__init__(**kwargs)  # processes 'quiet', encodings and type hints
53
54        self.num_retry = num_retry
55        self.warning_limit = warning_limit
56        self.num_warnings = 0
57
58        # 'eol' comes in as a (probably escaped) string. We need to
59        # unescape it, which means converting to bytes and back.
60        if eol is not None and self.encoding:
61            eol = self._unescape_str(eol)
62        self.eol = eol
63
64        # do name resolution once in the constructor
65        #
66        # NOTE: This means the hostname must be valid when we start, otherwise
67        #       the config_check code will puke.  That's fine.  The alternative
68        #       is we let name resolution happen while we're running, but then
69        #       each failed lookup is going to block our write() routine for a
70        #       few seconds - not good.
71        #
72        # NOTE: This also catches specifying impropperly formatted IP
73        #       addresses.  The only way through gethostbyname() w/out throwing
74        #       an exception is to provide a valid hostname or IP address.
75        #       Propperly formatted IPs just get returned.
76        #
77        self.destination = socket.gethostbyname(destination)
78
79        # make sure port gets stored as an int, even if passed in as a string
80        self.port = int(port)
81
82        self.reuseaddr = reuseaddr
83        self.reuseport = reuseport
84
85        # socket gets initialized on-demand in write()
86        #
87        # NOTE: Since connect() can actually fail w/ a TCP socket, we don't try
88        #       that here.  Let's just do safe things.
89        #
90        self.socket = None

Write records to a TCP network socket.

destination  The destination to send TCP packets to.  Can be resolvable hostname
             or valid IP address.

port         Port to which packets should be sent

num_retry    Number of times to retry if write fails.

warning_limit  Number of times the writer gives up on writing a message
             (without any intervening successes) before it gives up complaining
             about failures.

eol          If specified, an end of line string to append to record
             before sending

reuseaddr    Specifies wether to set SO_REUSEADDR on the created socket.  If
             you don't know you need this, don't enable it.

reuseport    Specifies wether to set SO_REUSEPORT on the created socket.  If
             you don't know you need this, don't enable it.

encoding - 'utf-8' by default. If empty or None, do not attempt any
        decoding and return raw bytes. Other possible encodings are
        listed in online documentation here:
        https://docs.python.org/3/library/codecs.html#standard-encodings

encoding_errors - 'ignore' by default. Other error strategies are
        'strict', 'replace', and 'backslashreplace', described here:
        https://docs.python.org/3/howto/unicode.html#encodings

num_retry
warning_limit
num_warnings
eol
destination
port
reuseaddr
reuseport
socket
def write(self, record: Union[str, bytes]):
141    def write(self, record: Union[str, bytes]):
142        """Write the record to the network."""
143
144        # See if it's something we can process, and if not, try digesting
145        if not self.can_process_record(record):  # inherited from BaseModule()
146            self.digest_record(record)  # inherited from BaseModule()
147            return
148
149        # Append eol if configured
150        if self.eol:
151            record += self.eol
152
153        # NOTE: Unlike UDP socket, which really only can detect failure during
154        #       send() (and even then only very poorly), a TCP connect() can
155        #       fail, so we need to track attempts to connect in order to honor
156        #       `num_retry` on a disconnected socket.
157        #
158        #       We also need to tear down and start over with a fresh connect()
159        #       if send() fails.
160        #
161        num_tries = 0
162        bytes_sent = 0
163        rec_len = len(record)
164        while num_tries <= self.num_retry and bytes_sent < rec_len:
165            num_tries += 1
166            # attempt to connect socket if needed, up to `num_tries` times
167            if not self.socket:
168                self.socket = self._open_socket()
169            if not self.socket:
170                # no need for further error messages, _open_socket() will have
171                # already complained sufficiently
172                continue
173
174            # attempt to verify we're really connected
175            #
176            # NOTE: This is because the only way to detect a closed TCP socket
177            #       is via recv(), which is awkward when we're trying to
178            #       guarantee a write was successful.
179            #
180            #         https://stackoverflow.com/questions/49457631/c-server-socket-closed-but-client-socket-still-able-send-two-more-packages
181            #
182            #         "send() just puts the data in the kernel socket buffer,
183            #         it doesn't wait for the data to be transmitted or the
184            #         server to acknowledge receipt of it.
185            #
186            #         You don't get SIGPIPE until the data is transmitted and
187            #         the server rejects it by sending a RST segment.
188            #
189            #         It works this way because each direction of a TCP
190            #         connection is treated independently. When the server
191            #         closes the socket, it sends a FIN segment. This just
192            #         tells the client that the server is done sending data, it
193            #         doesn't mean that the server cannot receive data. There's
194            #         nothing in the TCP protocol that allows the server to
195            #         inform the client of this. So the only way to find out
196            #         that it's not accepting any more data is when the client
197            #         gets that RST response.
198            #
199            #         Informing the client that they shouldn't send anything
200            #         more is usually done in the application protocol, since
201            #         it's not available in TCP."
202            #
203            #       So it might take a couple "successful" send() calls before
204            #       the socket layer notices nobody's home on the other end.
205            #
206            #       Fine then, let's do a 1 byte recv() w/ flags arranged so we
207            #       don't really read off the socket forcing it to raise
208            #       BlockingIOError if the socket is still connected.  Not
209            #       perfect, because the remote side could still close after
210            #       this call but before our send() below, but it's something.
211            #
212            try:
213                # If the socket is still connected, this will raise
214                # BlockingIOError.  If it's not, it returns 0 bytes.
215                msg = self.socket.recv(1, socket.MSG_DONTWAIT | socket.MSG_PEEK)  # noqa F841
216                logging.error('TCPWriter: connection closed')
217                self.num_warnings += 1
218                self._close_socket(self.socket)
219                self.socket = None
220                continue
221            except BlockingIOError:
222                pass
223
224            # we're connected, try sending
225            try:
226                bytes_sent = self.socket.send(self._encode_str(record))
227            except OSError as e:
228                # send failed, we need to disconnect and start over
229                #
230                # NOTE: In order to get this far, we have to have successfully
231                #       connected, which means we JUST reset self.num_warnings
232                #
233                logging.error('TCPWriter: send() error: %s:%d: %s',
234                              self.destination, self.port, str(e))
235                self.num_warnings += 1
236                self._close_socket(self.socket)
237                self.socket = None
238                continue
239
240            # check to see if we really wrote it all
241            if bytes_sent < rec_len and not self.quiet:
242                logging.warning('TCPWriter: send() did not send the whole record: '
243                                'bytes_sent=%d, rec_len=%d', bytes_sent, rec_len)
244
245        logging.debug('TCPWriter.write() wrote %d/%d bytes after %d tries',
246                      bytes_sent, rec_len, num_tries)

Write the record to the network.