2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-05-30 00:40:52 +00:00

Add and test QPDFLogger class

This commit is contained in:
Jay Berkenbilt 2022-06-04 21:15:40 -04:00
parent f588d74140
commit f1f711963b
9 changed files with 561 additions and 0 deletions

158
include/qpdf/QPDFLogger.hh Normal file
View File

@ -0,0 +1,158 @@
// Copyright (c) 2005-2022 Jay Berkenbilt
//
// This file is part of qpdf.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Versions of qpdf prior to version 7 were released under the terms
// of version 2.0 of the Artistic License. At your option, you may
// continue to consider qpdf to be licensed under those terms. Please
// see the manual for additional information.
#ifndef QPDFLOGGER_HH
#define QPDFLOGGER_HH
#include <qpdf/DLL.h>
#include <qpdf/Pipeline.hh>
#include <iostream>
#include <memory>
class QPDFLogger
{
public:
QPDF_DLL
QPDFLogger();
QPDF_DLL
static std::shared_ptr<QPDFLogger> defaultLogger();
// Defaults:
//
// info -- if save is standard output, standard error, else standard output
// warn -- whatever error points to
// error -- standard error
// save -- undefined unless set
//
// On deletion, finish() is called for the standard output and
// standard error pipelines, which flushes output. If you supply
// any custom pipelines, you must call finish() on them yourself.
// Note that calling finish is not needed for string, stdio, or
// ostream pipelines.
//
// NOTES ABOUT THE SAVE PIPELINE
//
// You should never set the save pipeline to the same destination
// as something else. Doing so will corrupt your save output. If
// you want to save to standard output, use the method
// saveToStandardOutput(). In addition to setting the save
// pipeline, that does the following extra things:
//
// * If standard output has been used, a logic error is thrown
// * If info is set to standard output at the time of the set save
// call, it is switched to standard error.
//
// This is not a guarantee. You can still mess this up in ways
// that are not checked. Here are a few examples:
//
// * Don't set any pipeline to standard output *after* passing it
// to setSave()
// * Don't use a separate mechanism to write stdout/stderr other
// than QPDFLogger::standardOutput()
// * Don't set anything to the same custom pipeline that save is
// set to.
//
// Just be sure that if you change pipelines around, you should
// avoid having the save pipeline also be used for any other
// purpose. The special case for saving to standard output allows
// you to call saveToStandardOutput() early without having to
// worry about the info pipeline.
QPDF_DLL
void info(char const*);
QPDF_DLL
void info(std::string const&);
QPDF_DLL
std::shared_ptr<Pipeline> getInfo(bool null_okay = false);
QPDF_DLL
void warn(char const*);
QPDF_DLL
void warn(std::string const&);
QPDF_DLL
std::shared_ptr<Pipeline> getWarn(bool null_okay = false);
QPDF_DLL
void error(char const*);
QPDF_DLL
void error(std::string const&);
QPDF_DLL
std::shared_ptr<Pipeline> getError(bool null_okay = false);
QPDF_DLL
std::shared_ptr<Pipeline> getSave(bool null_okay = false);
QPDF_DLL
std::shared_ptr<Pipeline> standardOutput();
QPDF_DLL
std::shared_ptr<Pipeline> standardError();
QPDF_DLL
std::shared_ptr<Pipeline> discard();
// Passing a null pointer resets to default
QPDF_DLL
void setInfo(std::shared_ptr<Pipeline>);
QPDF_DLL
void setWarn(std::shared_ptr<Pipeline>);
QPDF_DLL
void setError(std::shared_ptr<Pipeline>);
// See notes above about the save pipeline
QPDF_DLL
void setSave(std::shared_ptr<Pipeline>);
QPDF_DLL
void saveToStandardOutput();
// Shortcut for logic to reset output to new output/error streams.
// out_stream is used for info, err_stream is used for error, and
// warning is cleared so that it follows error.
QPDF_DLL
void setOutputStreams(std::ostream* out_stream, std::ostream* err_stream);
private:
std::shared_ptr<Pipeline>
throwIfNull(std::shared_ptr<Pipeline>, bool null_okay);
class Members
{
friend class QPDFLogger;
public:
QPDF_DLL
~Members();
private:
Members();
Members(Members const&) = delete;
std::shared_ptr<Pipeline> p_discard;
std::shared_ptr<Pipeline> p_real_stdout;
std::shared_ptr<Pipeline> p_stdout;
std::shared_ptr<Pipeline> p_stderr;
std::shared_ptr<Pipeline> p_info;
std::shared_ptr<Pipeline> p_warn;
std::shared_ptr<Pipeline> p_error;
std::shared_ptr<Pipeline> p_save;
};
std::shared_ptr<Members> m;
};
#endif // QPDFLOGGER_HH

View File

@ -67,6 +67,7 @@ set(libqpdf_SOURCES
QPDFJob_argv.cc
QPDFJob_config.cc
QPDFJob_json.cc
QPDFLogger.cc
QPDFMatrix.cc
QPDFNameTreeObjectHelper.cc
QPDFNumberTreeObjectHelper.cc

244
libqpdf/QPDFLogger.cc Normal file
View File

@ -0,0 +1,244 @@
#include <qpdf/QPDFLogger.hh>
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_OStream.hh>
#include <iostream>
#include <stdexcept>
namespace
{
class Pl_Track: public Pipeline
{
public:
Pl_Track(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
used(false)
{
}
virtual void
write(unsigned char const* data, size_t len) override
{
this->used = true;
getNext()->write(data, len);
}
virtual void
finish() override
{
getNext()->finish();
}
bool
getUsed() const
{
return used;
}
private:
bool used;
};
}; // namespace
QPDFLogger::Members::Members() :
p_discard(new Pl_Discard()),
p_real_stdout(new Pl_OStream("standard output", std::cout)),
p_stdout(new Pl_Track("track stdout", p_real_stdout.get())),
p_stderr(new Pl_OStream("standard error", std::cerr)),
p_info(p_stdout),
p_warn(nullptr),
p_error(p_stderr),
p_save(nullptr)
{
}
QPDFLogger::Members::~Members()
{
p_stdout->finish();
p_stderr->finish();
}
QPDFLogger::QPDFLogger() :
m(new Members())
{
}
std::shared_ptr<QPDFLogger>
QPDFLogger::defaultLogger()
{
static auto l = std::make_shared<QPDFLogger>();
return l;
}
void
QPDFLogger::info(char const* s)
{
getInfo(false)->writeCStr(s);
}
void
QPDFLogger::info(std::string const& s)
{
getInfo(false)->writeString(s);
}
std::shared_ptr<Pipeline>
QPDFLogger::getInfo(bool null_okay)
{
return throwIfNull(this->m->p_info, null_okay);
}
void
QPDFLogger::warn(char const* s)
{
getWarn(false)->writeCStr(s);
}
void
QPDFLogger::warn(std::string const& s)
{
getWarn(false)->writeString(s);
}
std::shared_ptr<Pipeline>
QPDFLogger::getWarn(bool null_okay)
{
if (this->m->p_warn) {
return this->m->p_warn;
}
return getError(null_okay);
}
void
QPDFLogger::error(char const* s)
{
getError(false)->writeCStr(s);
}
void
QPDFLogger::error(std::string const& s)
{
getError(false)->writeString(s);
}
std::shared_ptr<Pipeline>
QPDFLogger::getError(bool null_okay)
{
return throwIfNull(this->m->p_error, null_okay);
}
std::shared_ptr<Pipeline>
QPDFLogger::getSave(bool null_okay)
{
return throwIfNull(this->m->p_save, null_okay);
}
std::shared_ptr<Pipeline>
QPDFLogger::standardOutput()
{
return this->m->p_stdout;
}
std::shared_ptr<Pipeline>
QPDFLogger::standardError()
{
return this->m->p_stderr;
}
std::shared_ptr<Pipeline>
QPDFLogger::discard()
{
return this->m->p_discard;
}
void
QPDFLogger::setInfo(std::shared_ptr<Pipeline> p)
{
if (p == nullptr) {
if (this->m->p_save == this->m->p_stdout) {
p = this->m->p_stderr;
} else {
p = this->m->p_stdout;
}
}
this->m->p_info = p;
}
void
QPDFLogger::setWarn(std::shared_ptr<Pipeline> p)
{
this->m->p_warn = p;
}
void
QPDFLogger::setError(std::shared_ptr<Pipeline> p)
{
if (p == nullptr) {
p = this->m->p_stderr;
}
this->m->p_error = p;
}
void
QPDFLogger::setSave(std::shared_ptr<Pipeline> p)
{
if (p == this->m->p_stdout) {
auto pt = dynamic_cast<Pl_Track*>(p.get());
if (pt->getUsed()) {
throw std::logic_error(
"QPDFLogger: called setSave on standard output after standard"
" output has already been used");
}
if (this->m->p_info == this->m->p_stdout) {
this->m->p_info = this->m->p_stderr;
}
}
this->m->p_save = p;
}
void
QPDFLogger::saveToStandardOutput()
{
setSave(standardOutput());
}
void
QPDFLogger::setOutputStreams(std::ostream* out_stream, std::ostream* err_stream)
{
if (out_stream == &std::cout) {
out_stream = nullptr;
}
if (err_stream == &std::cerr) {
err_stream = nullptr;
}
std::shared_ptr<Pipeline> new_out;
std::shared_ptr<Pipeline> new_err;
if (out_stream == nullptr) {
if (this->m->p_save == this->m->p_stdout) {
new_out = this->m->p_stderr;
} else {
new_out = this->m->p_stdout;
}
} else {
new_out = std::make_shared<Pl_OStream>("output", *out_stream);
}
if (err_stream == nullptr) {
new_err = this->m->p_stderr;
} else {
new_err = std::make_shared<Pl_OStream>("error output", *err_stream);
}
this->m->p_info = new_out;
this->m->p_warn = nullptr;
this->m->p_error = new_err;
}
std::shared_ptr<Pipeline>
QPDFLogger::throwIfNull(std::shared_ptr<Pipeline> p, bool null_okay)
{
if (!(null_okay || p)) {
throw std::logic_error(
"QPDFLogger: requested a null pipeline without null_okay == true");
}
return p;
}

View File

@ -16,6 +16,7 @@ set(TEST_PROGRAMS
json
json_handler
json_parse
logger
lzw
main_from_wmain
matrix

109
libtests/logger.cc Normal file
View File

@ -0,0 +1,109 @@
#include <qpdf/assert_test.h>
#include <qpdf/Pl_String.hh>
#include <qpdf/QPDFLogger.hh>
#include <stdexcept>
static void
test1()
{
// Standard behavior
auto logger = QPDFLogger::defaultLogger();
logger->info("info to stdout\n");
logger->warn("warn to stderr\n");
logger->error("error to stderr\n");
assert(logger->getSave(true) == nullptr);
try {
logger->getSave();
assert(false);
} catch (std::logic_error& e) {
*(logger->getInfo()) << "getSave exception: " << e.what() << "\n";
}
try {
logger->saveToStandardOutput();
assert(false);
} catch (std::logic_error& e) {
*(logger->getInfo())
<< "saveToStandardOutput exception: " << e.what() << "\n";
}
logger->setWarn(logger->discard());
logger->warn("warning not seen\n");
logger->setWarn(nullptr);
logger->warn("restored warning to stderr\n");
}
static void
test2()
{
// First call saveToStandardOutput. Then use info, which then to
// go stderr.
QPDFLogger l;
l.saveToStandardOutput();
l.info(std::string("info to stderr\n"));
*(l.getSave()) << "save to stdout\n";
l.setInfo(nullptr);
l.info("info still to stderr\n");
l.setSave(nullptr);
l.setInfo(nullptr);
l.info("info back to stdout\n");
}
static void
test3()
{
// Error/warning
QPDFLogger l;
// Warning follows error when error is set explicitly.
std::string errors;
auto pl_error = std::make_shared<Pl_String>("errors", nullptr, errors);
l.setError(pl_error);
l.warn("warn follows error\n");
assert(errors == "warn follows error\n");
l.error("error too\n");
assert(errors == "warn follows error\nerror too\n");
// Set warnings -- now they're separate
std::string warnings;
auto pl_warn = std::make_shared<Pl_String>("warnings", nullptr, warnings);
l.setWarn(pl_warn);
l.warn(std::string("warning now separate\n"));
l.error(std::string("new error\n"));
assert(warnings == "warning now separate\n");
assert(errors == "warn follows error\nerror too\nnew error\n");
std::string errors2;
pl_error = std::make_shared<Pl_String>("errors", nullptr, errors2);
l.setError(pl_error);
l.warn("new warning\n");
l.error("another new error\n");
assert(warnings == "warning now separate\nnew warning\n");
assert(errors == "warn follows error\nerror too\nnew error\n");
assert(errors2 == "another new error\n");
// Restore warnings to default -- follows error again
l.setWarn(nullptr);
l.warn("warning 3\n");
l.error("error 3\n");
assert(warnings == "warning now separate\nnew warning\n");
assert(errors == "warn follows error\nerror too\nnew error\n");
assert(errors2 == "another new error\nwarning 3\nerror 3\n");
// Restore everything to default
l.setInfo(nullptr);
l.setWarn(nullptr);
l.setError(nullptr);
l.info("after reset, info to stdout\n");
l.warn("after reset, warn to stderr\n");
l.error("after reset, error to stderr\n");
}
int
main()
{
test1();
test2();
test3();
return 0;
}

View File

@ -0,0 +1,33 @@
#!/usr/bin/env perl
require 5.008;
use warnings;
use strict;
chdir("logger") or die "chdir testdir failed: $!\n";
require TestDriver;
my $td = new TestDriver('logger');
cleanup();
$td->runtest("logger",
{$td->COMMAND => "logger >stdout 2>stderr"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check stdout",
{$td->FILE => "stdout"},
{$td->FILE => "exp-stdout"},
$td->NORMALIZE_NEWLINES);
$td->runtest("check stderr",
{$td->FILE => "stderr"},
{$td->FILE => "exp-stderr"},
$td->NORMALIZE_NEWLINES);
cleanup();
$td->report(3);
sub cleanup
{
unlink "stdout", "stderr";
}

View File

@ -0,0 +1,7 @@
warn to stderr
error to stderr
restored warning to stderr
info to stderr
info still to stderr
after reset, warn to stderr
after reset, error to stderr

View File

@ -0,0 +1,6 @@
info to stdout
getSave exception: QPDFLogger: requested a null pipeline without null_okay == true
saveToStandardOutput exception: QPDFLogger: called setSave on standard output after standard output has already been used
save to stdout
info back to stdout
after reset, info to stdout

View File

@ -31,6 +31,7 @@
#include <qpdf/QPDFFileSpecObjectHelper.hh>
#include <qpdf/QPDFFormFieldObjectHelper.hh>
#include <qpdf/QPDFJob.hh>
#include <qpdf/QPDFLogger.hh>
#include <qpdf/QPDFMatrix.hh>
#include <qpdf/QPDFNameTreeObjectHelper.hh>
#include <qpdf/QPDFNumberTreeObjectHelper.hh>
@ -98,6 +99,7 @@ main()
print_size(QPDFJob::EncConfig);
print_size(QPDFJob::PagesConfig);
print_size(QPDFJob::UOConfig);
print_size(QPDFLogger);
print_size(QPDFMatrix);
print_size(QPDFNameTreeObjectHelper);
print_size(QPDFNameTreeObjectHelper::iterator);