diem_logger/
struct_log.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8//! Implementations for sending logs to external log processes e.g. Logstash
9//!
10//! Handles sending logs under disconnects, and retries.  Tries to continue to
11//! make progress on a log but eventually drops older logs to continue to make
12//! progress on newer logs.
13
14use crate::counters::{
15    STRUCT_LOG_CONNECT_ERROR_COUNT, STRUCT_LOG_TCP_CONNECT_COUNT,
16};
17use std::{
18    io::{self, Write},
19    net::{TcpStream, ToSocketAddrs},
20    time::Duration,
21};
22
23const WRITE_TIMEOUT_MS: u64 = 2000;
24const CONNECTION_TIMEOUT_MS: u64 = 5000;
25
26/// A wrapper for `TcpStream` that handles reconnecting to the endpoint
27/// automatically
28///
29/// `TcpWriter::write()` will block on the message until it is connected.
30pub(crate) struct TcpWriter {
31    /// The DNS name or IP address logs are being sent to
32    endpoint: String,
33    /// The `TCPStream` to write to, which will be `None` when disconnected
34    stream: Option<TcpStream>,
35}
36
37impl TcpWriter {
38    pub fn new(endpoint: String) -> Self {
39        Self {
40            endpoint,
41            stream: None,
42        }
43    }
44
45    pub fn endpoint(&self) -> &str { &self.endpoint }
46
47    /// Ensure that we get a connection, no matter how long it takes
48    /// This will block until there is a connection
49    fn refresh_connection(&mut self) {
50        loop {
51            match self.connect() {
52                Ok(stream) => {
53                    self.stream = Some(stream);
54                    return;
55                }
56                Err(e) => {
57                    eprintln!("[Logging] Failed to connect: {}", e);
58                    STRUCT_LOG_CONNECT_ERROR_COUNT.inc();
59                }
60            }
61
62            // Sleep a second so this doesn't just spin as fast as possible
63            std::thread::sleep(Duration::from_millis(1000));
64        }
65    }
66
67    /// Connect and ensure the write timeout is set
68    fn connect(&mut self) -> io::Result<TcpStream> {
69        STRUCT_LOG_TCP_CONNECT_COUNT.inc();
70
71        let mut last_error = io::Error::new(
72            io::ErrorKind::Other,
73            format!("Unable to resolve and connect to {}", self.endpoint),
74        );
75
76        // resolve addresses to handle DNS names
77        for addr in self.endpoint.to_socket_addrs()? {
78            match TcpStream::connect_timeout(
79                &addr,
80                Duration::from_millis(CONNECTION_TIMEOUT_MS),
81            ) {
82                Ok(stream) => {
83                    // Set the write timeout
84                    if let Err(err) = stream.set_write_timeout(Some(
85                        Duration::from_millis(WRITE_TIMEOUT_MS),
86                    )) {
87                        STRUCT_LOG_CONNECT_ERROR_COUNT.inc();
88                        eprintln!(
89                            "[Logging] Failed to set write timeout: {}",
90                            err
91                        );
92                        continue;
93                    }
94                    return Ok(stream);
95                }
96                Err(err) => last_error = err,
97            }
98        }
99
100        Err(last_error)
101    }
102}
103
104impl Write for TcpWriter {
105    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
106        // Refresh the connection if it's missing
107        if self.stream.is_none() {
108            self.refresh_connection();
109        }
110
111        // Attempt to write, and if it fails clear underlying stream
112        // This doesn't guarantee a message cut off mid send will work, but it
113        // does guarantee that we will connect first
114        self.stream
115            .as_mut()
116            .ok_or_else(|| {
117                io::Error::new(io::ErrorKind::NotConnected, "No stream")
118            })
119            .and_then(|stream| stream.write(buf))
120            .map_err(|e| {
121                self.stream = None;
122                e
123            })
124    }
125
126    fn flush(&mut self) -> io::Result<()> {
127        if let Some(mut stream) = self.stream.as_ref() {
128            stream.flush()
129        } else {
130            Err(io::Error::new(
131                io::ErrorKind::NotConnected,
132                "Can't flush, not connected",
133            ))
134        }
135    }
136}