214 lines
8.1 KiB
C++
214 lines
8.1 KiB
C++
/*
|
|
* Copyright (C) 2009 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#ifndef INCL_LOGREDIRECT
|
|
#define INCL_LOGREDIRECT
|
|
|
|
#include <syncevo/LogStdout.h>
|
|
#include <syncevo/util.h>
|
|
|
|
#include <string>
|
|
#include <set>
|
|
|
|
#include <syncevo/declarations.h>
|
|
SE_BEGIN_CXX
|
|
|
|
/**
|
|
* Intercepts all text written to stdout or stderr and passes it
|
|
* through the currently active logger, which may or may not be
|
|
* this instance itself. In addition, it catches SIGSEGV, SIGABRT,
|
|
* SIGBUS and processes pending output before shutting down
|
|
* by raising these signals again.
|
|
*
|
|
* The interception is done by replacing the file descriptors
|
|
* 1 and 2. The original file descriptors are preserved; the
|
|
* original FD 1 is used for writing log messages that are
|
|
* intended to reach the user.
|
|
*
|
|
* This class tries to be simple and therefore avoids threads
|
|
* and forking. It intentionally doesn't protect against multiple
|
|
* threads accessing it. This is something that has to be avoided
|
|
* by the user. The redirected output has to be read whenever
|
|
* possible, ideally before producing other log output (process()).
|
|
*
|
|
* Because the same thread that produces the output also reads it,
|
|
* there can be a deadlock if more output is produced than the
|
|
* in-kernel buffers allow. Pipes and stream sockets therefore cannot
|
|
* be used. Unreliable datagram sockets work:
|
|
* - normal write() calls produce packets
|
|
* - if the sender always writes complete lines, the reader
|
|
* will not split them because it can receive the complete packet
|
|
*
|
|
* Unix Domain datagram sockets would be nice:
|
|
* - socketpair() creates an anonymous connection, no-one else
|
|
* can send us unwanted data (in contrast to, say, UDP)
|
|
* - unlimited chunk size
|
|
* - *but* packets are *not* dropped if too much output is produced
|
|
* (found with LogRedirectTest::overload test and confirmed by
|
|
* "man unix")
|
|
*
|
|
* To avoid deadlocks, UDP sockets have to be used. It has drawbacks:
|
|
* - chunk size limited by maximum size of IP4 packets
|
|
* - more complex to set up (currently assumes that 127.0.0.1 is the
|
|
* local interface)
|
|
* - anyone running locally can send us log data
|
|
*
|
|
* The implementation contains code for both; UDP is active by default
|
|
* because the potential deadlock is considered more severe than UDP's
|
|
* disadvantages.
|
|
*
|
|
* Because this class is to be used early in the startup
|
|
* of the application and in low-level error scenarios, it
|
|
* must not throw exceptions or return errors. If something
|
|
* doesn't work, it stops redirecting output.
|
|
*
|
|
* Redirection and signal handlers are disabled if the environment
|
|
* variable SYNCEVOLUTION_DEBUG is set (regardless of its value).
|
|
*
|
|
* In contrast to stderr, stdout is only passed into the logging
|
|
* system as complete lines. That's because it may include data (like
|
|
* synccompare output) which is not printed line-oriented and
|
|
* inserting line breaks (as the logging system does) is undesirable.
|
|
* If an output packet does not end in a line break, that last line
|
|
* is buffered and written together with the next packet, or in flush().
|
|
*/
|
|
class LogRedirect : public LoggerStdout
|
|
{
|
|
public:
|
|
struct FDs {
|
|
int m_original; /** the original output FD, 2 for stderr */
|
|
int m_copy; /** a duplicate of the original output file descriptor */
|
|
int m_write; /** the write end of the replacement */
|
|
int m_read; /** the read end of the replacement */
|
|
};
|
|
|
|
/** ignore any error output containing "error" */
|
|
static void addIgnoreError(const std::string &error);
|
|
|
|
/**
|
|
* Messages containing text listed in
|
|
* SYNCEVOLUTION_SUPPRESS_ERRORS env variable (new-line separated)
|
|
* or registered via addIgnoreError() are not real errors and
|
|
* should only be logged for developers.
|
|
*/
|
|
static bool ignoreError(const std::string &text);
|
|
|
|
private:
|
|
FDs m_stdout, m_stderr;
|
|
bool m_streams; /**< using reliable streams instead of UDP */
|
|
FILE *m_out; /** a stream for Logger::SHOW output which isn't redirected */
|
|
FILE *m_err; /** corresponding stream for any other output */
|
|
char *m_buffer; /** typically fairly small buffer for reading */
|
|
std::string m_stdoutData; /**< incomplete stdout line */
|
|
size_t m_len; /** total length of buffer */
|
|
bool m_processing; /** flag to detect recursive process() calls */
|
|
static LogRedirect *m_redirect; /**< single active instance, for signal handler */
|
|
static std::set<std::string> m_knownErrors; /** texts contained in errors which are to be ignored */
|
|
|
|
// non-virtual helper functions which can always be called,
|
|
// including the constructor and destructor
|
|
void redirect(int original, FDs &fds) throw();
|
|
void restore(FDs &fds) throw();
|
|
void restore() throw();
|
|
/** @return true if data was available */
|
|
bool process(FDs &fds) throw();
|
|
static void abortHandler(int sig) throw();
|
|
|
|
void init();
|
|
|
|
public:
|
|
enum Mode {
|
|
STDERR_AND_STDOUT,
|
|
STDERR
|
|
};
|
|
|
|
/**
|
|
* Redirect both stderr and stdout or just stderr,
|
|
* using UDP so that we don't block when not reading
|
|
* redirected output.
|
|
*
|
|
* messagev() only writes messages to the previous stdout
|
|
* or the optional file which pass the filtering (relevant,
|
|
* suppress known errors, ...).
|
|
*
|
|
* May only be called when there is no other active LogRedirect
|
|
* instance. Not thread-safe, in contrast to the actual logging
|
|
* method and redirect handling.
|
|
*
|
|
* Does not add or remove the logger from the logger stack.
|
|
* That must be done by the caller.
|
|
*/
|
|
LogRedirect(Mode mode, const char *filename = NULL);
|
|
~LogRedirect() throw();
|
|
|
|
virtual void remove() throw();
|
|
|
|
/**
|
|
* Remove redirection (if any) after a fork and before an exec.
|
|
*/
|
|
static void removeRedirect() throw();
|
|
|
|
/**
|
|
* Meant to be used for redirecting output of a specific command
|
|
* via fork()/exec(). Prepares reliable streams, as determined by
|
|
* ExecuteFlags, without touch file descriptor 1 and 2 and without
|
|
* installing itself as logger. In such an instance, process()
|
|
* will block until both streams get closed on the writing end.
|
|
*/
|
|
LogRedirect(ExecuteFlags flags);
|
|
|
|
/** true if stdout is redirected */
|
|
static bool redirectingStdout() { return m_redirect && m_redirect->m_stdout.m_read > 0; }
|
|
|
|
/** true if stderr is redirected */
|
|
static bool redirectingStderr() { return m_redirect && m_redirect->m_stderr.m_read > 0; }
|
|
|
|
/** reset any redirection, if active */
|
|
static void reset() {
|
|
if (m_redirect) {
|
|
m_redirect->flush();
|
|
m_redirect->restore();
|
|
}
|
|
}
|
|
|
|
const FDs &getStdout() { return m_stdout; }
|
|
const FDs &getStderr() { return m_stderr; }
|
|
|
|
/**
|
|
* Read currently available redirected output and handle it.
|
|
*
|
|
* When using unreliable output redirection, it will always
|
|
* keep going without throwing exceptions. When using reliable
|
|
* redirection and a fatal error occurs, then and exception
|
|
* is thrown.
|
|
*/
|
|
void process();
|
|
|
|
/** same as process(), but also dump all cached output */
|
|
void flush() throw();
|
|
|
|
/** format log messages via normal LogStdout and print to a valid stream owned by us */
|
|
virtual void messagev(const MessageOptions &options,
|
|
const char *format,
|
|
va_list args);
|
|
};
|
|
|
|
SE_END_CXX
|
|
#endif // INCL_LOGREDIRECT
|