command line: use both stdout and stderr

Traditionally, the "syncevolution" command line tool mixed its
INFO/ERROR/DEBUG messages into the normal stdout. This has the major
drawback that error messages get lost during operations like
   syncevolution --export - @default addressbook | grep "John Doe"

Now anything which not the expected result of the operation is always
sent to stderr. Obviously this includes ERROR messages. INFO and DEBUG
are harder to decide. Because they usually convey meta information
about the running operation, they are also sent to stderr.

This changes the behavior of
   syncevolution --run foo eds_event | less

"less" will capture only the following output:

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Local data changes to be applied during synchronization:
*** eds_event ***
no changes

Synchronization successful.

Changes applied during synchronization:
+---------------|-----------------------|-----------------------|-CON-+
|               |         LOCAL         |        REMOTE         | FLI |
|        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|     eds_event |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |
|     two-way, 0 KB sent by client, 0 KB received                     |
|     item(s) in database backup: 2 before sync, 2 after it           |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|          start Wed Apr 11 14:34:11 2012, duration 0:03min           |
|               synchronization completed successfully                |
+---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+

Data modified locally during synchronization:
*** eds_event ***
no changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To get the traditional behavior with bash or sh as shell, use
   syncevolution --run foo eds_event 2>&1 | less

Some "normal" output in error messages was changed to INFO and thus
now appears on stderr after the corresponding ERROR message. Empty
lines which were used to separate different parts now also have a tag,
for the sake of consistency with the surrounding output.

The main implementation change is in LogRedirect, which now decides
whether LogStdout shall send the message to stdout or stderr. This has
the desired effect in the "syncevolution" binary, which uses
LogRedirect. Other binaries inherit the same change, although there it
usually doesn't matter.

The Cmdline unit testing mirrors this change in the way how it stores
and checks Cmdline output and also was adapted to the prefix changes.
This commit is contained in:
Patrick Ohly 2012-04-11 14:38:54 +02:00
parent b1f7f781e8
commit 1fe3100c34
5 changed files with 67 additions and 52 deletions

View file

@ -986,11 +986,13 @@ bool Cmdline::run() {
} else if (missing.empty()) {
SE_LOG_INFO(NULL, NULL, "All relevant properties seem to be set, omit the --template parameter to proceed.");
}
SE_LOG_SHOW(NULL, NULL, "");
SE_LOG_INFO(NULL, NULL, "");
SyncConfig::DeviceList devices;
devices.push_back(SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_ALL));
dumpConfigTemplates("Available configuration templates (clients and servers):",
SyncConfig::getPeerTemplates(devices));
SyncConfig::getPeerTemplates(devices),
false,
Logger::INFO);
return false;
}
}
@ -1940,8 +1942,9 @@ void Cmdline::dumpConfigs(const string &preamble,
}
void Cmdline::dumpConfigTemplates(const string &preamble,
const SyncConfig::TemplateList &templates,
bool printRank)
const SyncConfig::TemplateList &templates,
bool printRank,
Logger::Level level)
{
ostringstream out;
out << preamble << endl;
@ -1961,7 +1964,7 @@ void Cmdline::dumpConfigTemplates(const string &preamble,
if (!templates.size()) {
out << " none" << endl;
}
SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
SE_LOG(level, NULL, NULL, "%s", out.str().c_str());
}
void Cmdline::dumpProperties(const ConfigNode &configuredProps,
@ -3339,19 +3342,17 @@ protected:
NULL);
CPPUNIT_ASSERT(cmdline.m_cmdline->parse());
CPPUNIT_ASSERT(!cmdline.m_cmdline->run());
static const char error[] = "[ERROR] No configuration template for 'yahooxyz' available.\n";
static const char hint[] = "\nAvailable configuration templates (clients and servers):\n";
static const char error[] = "[ERROR] No configuration template for 'yahooxyz' available.\n"
"[INFO] \n"
"[INFO] Available configuration templates (clients and servers):\n";
std::string out = cmdline.m_out.str();
std::string err = cmdline.m_err.str();
std::string all = cmdline.m_all.str();
CPPUNIT_ASSERT(boost::starts_with(out, hint));
CPPUNIT_ASSERT(boost::ends_with(out, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(out, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(error),
err);
CPPUNIT_ASSERT(boost::starts_with(all, string(error) + hint));
CPPUNIT_ASSERT(boost::ends_with(all, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(all, "\n\n"));
CPPUNIT_ASSERT(boost::starts_with(err, error));
CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(""), out);
CPPUNIT_ASSERT_EQUAL(all, err);
}
{
TestCmdline cmdline("--configure",
@ -3359,19 +3360,18 @@ protected:
NULL);
CPPUNIT_ASSERT(cmdline.m_cmdline->parse());
CPPUNIT_ASSERT(!cmdline.m_cmdline->run());
static const char error[] = "[ERROR] No configuration template for 'foobar' available.\n";
static const char hint[] = "[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n\nAvailable configuration templates (clients and servers):\n";
static const char error[] = "[ERROR] No configuration template for 'foobar' available.\n"
"[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n"
"[INFO] \n"
"[INFO] Available configuration templates (clients and servers):\n";
std::string out = cmdline.m_out.str();
std::string err = cmdline.m_err.str();
std::string all = cmdline.m_all.str();
CPPUNIT_ASSERT(boost::starts_with(out, hint));
CPPUNIT_ASSERT(boost::ends_with(out, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(out, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(error),
err);
CPPUNIT_ASSERT(boost::starts_with(all, string(error) + hint));
CPPUNIT_ASSERT(boost::ends_with(all, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(all, "\n\n"));
CPPUNIT_ASSERT(boost::starts_with(err, error));
CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(""), out);
CPPUNIT_ASSERT_EQUAL(err, all);
}
#endif
}
@ -3596,19 +3596,18 @@ protected:
TestCmdline failure("--configure", "foo", NULL);
CPPUNIT_ASSERT(failure.m_cmdline->parse());
CPPUNIT_ASSERT(!failure.m_cmdline->run());
static const char error[] = "[ERROR] No configuration template for 'foo@default' available.\n";
static const char hint[] = "[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n\nAvailable configuration templates (clients and servers):\n";
static const char error[] = "[ERROR] No configuration template for 'foo@default' available.\n"
"[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n"
"[INFO] \n"
"[INFO] Available configuration templates (clients and servers):\n";
std::string out = failure.m_out.str();
std::string err = failure.m_err.str();
std::string all = failure.m_all.str();
CPPUNIT_ASSERT(boost::starts_with(out, hint));
CPPUNIT_ASSERT(boost::ends_with(out, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(out, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(error),
err);
CPPUNIT_ASSERT(boost::starts_with(all, string(error) + hint));
CPPUNIT_ASSERT(boost::ends_with(all, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(all, "\n\n"));
CPPUNIT_ASSERT(boost::starts_with(err, error));
CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(""), out);
CPPUNIT_ASSERT_EQUAL(all, err);
}
rm_r(m_testDir);
@ -3620,19 +3619,18 @@ protected:
CPPUNIT_ASSERT(failure.m_cmdline->parse());
CPPUNIT_ASSERT(!failure.m_cmdline->run());
static const char error[] = "[ERROR] No configuration template for 'foo' available.\n";
static const char hint[] = "[INFO] All relevant properties seem to be set, omit the --template parameter to proceed.\n\nAvailable configuration templates (clients and servers):\n";
static const char error[] = "[ERROR] No configuration template for 'foo' available.\n"
"[INFO] All relevant properties seem to be set, omit the --template parameter to proceed.\n"
"[INFO] \n"
"[INFO] Available configuration templates (clients and servers):\n";
std::string out = failure.m_out.str();
std::string err = failure.m_err.str();
std::string all = failure.m_all.str();
CPPUNIT_ASSERT(boost::starts_with(out, hint));
CPPUNIT_ASSERT(boost::ends_with(out, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(out, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(error),
err);
CPPUNIT_ASSERT(boost::starts_with(all, string(error) + hint));
CPPUNIT_ASSERT(boost::ends_with(all, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(all, "\n\n"));
CPPUNIT_ASSERT(boost::starts_with(err, error));
CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
CPPUNIT_ASSERT_EQUAL(string(""), out);
CPPUNIT_ASSERT_EQUAL(all, err);
}
string fooconfig =
@ -4796,7 +4794,7 @@ private:
va_list args)
{
if (level <= INFO) {
ostringstream &out = level <= ERROR ? m_err : m_out;
ostringstream &out = level != SHOW ? m_err : m_out;
std::string str = StringPrintfV(format, args);
if (level != SHOW) {
out << "[" << levelToStr(level) << "] ";

View file

@ -235,8 +235,9 @@ protected:
const SyncConfig::ConfigList &servers);
void dumpConfigTemplates(const std::string &preamble,
const SyncConfig::TemplateList &templates,
bool printRank = false);
const SyncConfig::TemplateList &templates,
bool printRank = false,
Logger::Level level = Logger::SHOW);
enum DumpPropertiesFlags {
DUMP_PROPS_NORMAL = 0,

View file

@ -72,6 +72,7 @@ void LogRedirect::init()
m_buffer = NULL;
m_len = 0;
m_out = NULL;
m_err = NULL;
m_streams = false;
m_stderr.m_original =
m_stderr.m_read =
@ -116,6 +117,12 @@ LogRedirect::LogRedirect(bool both, const char *filename) throw()
perror(filename);
}
}
// Separate FILE, will write into same file as normal output
// if a filename was given (for testing), otherwise to original
// stderr.
m_err = fdopen(dup((filename && m_out) ?
fileno(m_out) :
m_stderr.m_copy), "w");
}
LoggerBase::pushLogger(this);
m_redirect = this;
@ -165,6 +172,9 @@ LogRedirect::~LogRedirect() throw()
if (m_out) {
fclose(m_out);
}
if (m_err) {
fclose(m_err);
}
if (m_buffer) {
free(m_buffer);
}
@ -211,7 +221,11 @@ void LogRedirect::messagev(Level level,
{
// check for other output first
process();
LoggerStdout::messagev(m_out ? m_out : stdout,
// Choose output channel: SHOW goes to original stdout,
// everything else to stderr.
LoggerStdout::messagev(level == SHOW ?
(m_out ? m_out : stdout) :
(m_err ? m_err : stderr),
level, getLevel(),
prefix,
file, line, function,

View file

@ -104,7 +104,8 @@ class LogRedirect : public LoggerStdout
private:
FDs m_stdout, m_stderr;
bool m_streams; /**< using reliable streams instead of UDP */
FILE *m_out; /** a stream for the normal LogStdout which isn't redirected */
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 */

View file

@ -139,8 +139,9 @@ void LoggerStdout::messagev(FILE *file,
break;
}
}
if (pos < output.size()) {
// handle dangling last line
if (pos < output.size() || output.empty()) {
// handle dangling last line or empty chunk (don't
// want empty line for that, print at least the tag)
buffer.append(tag);
buffer.append(output, pos, output.size() - pos);
haveNewline = false;