aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2022-06-05 03:15:40 +0200
committerJay Berkenbilt <ejb@ql.org>2022-06-18 15:02:55 +0200
commitf1f711963b8e5f0b2b5a9d80a522cbd616a153a9 (patch)
tree9c5bc0bcf39cd6854786f60e68da42ddf90c3f99
parentf588d74140b2a86026929aa401c9852ec215d4af (diff)
downloadqpdf-f1f711963b8e5f0b2b5a9d80a522cbd616a153a9.tar.zst
Add and test QPDFLogger class
-rw-r--r--include/qpdf/QPDFLogger.hh158
-rw-r--r--libqpdf/CMakeLists.txt1
-rw-r--r--libqpdf/QPDFLogger.cc244
-rw-r--r--libtests/CMakeLists.txt1
-rw-r--r--libtests/logger.cc109
-rw-r--r--libtests/qtest/logger.test33
-rw-r--r--libtests/qtest/logger/exp-stderr7
-rw-r--r--libtests/qtest/logger/exp-stdout6
-rw-r--r--qpdf/sizes.cc2
9 files changed, 561 insertions, 0 deletions
diff --git a/include/qpdf/QPDFLogger.hh b/include/qpdf/QPDFLogger.hh
new file mode 100644
index 00000000..340706c9
--- /dev/null
+++ b/include/qpdf/QPDFLogger.hh
@@ -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
diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt
index 1777bb08..a6b036a7 100644
--- a/libqpdf/CMakeLists.txt
+++ b/libqpdf/CMakeLists.txt
@@ -67,6 +67,7 @@ set(libqpdf_SOURCES
QPDFJob_argv.cc
QPDFJob_config.cc
QPDFJob_json.cc
+ QPDFLogger.cc
QPDFMatrix.cc
QPDFNameTreeObjectHelper.cc
QPDFNumberTreeObjectHelper.cc
diff --git a/libqpdf/QPDFLogger.cc b/libqpdf/QPDFLogger.cc
new file mode 100644
index 00000000..859d6dcd
--- /dev/null
+++ b/libqpdf/QPDFLogger.cc
@@ -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;
+}
diff --git a/libtests/CMakeLists.txt b/libtests/CMakeLists.txt
index 9eb9a490..196cd3eb 100644
--- a/libtests/CMakeLists.txt
+++ b/libtests/CMakeLists.txt
@@ -16,6 +16,7 @@ set(TEST_PROGRAMS
json
json_handler
json_parse
+ logger
lzw
main_from_wmain
matrix
diff --git a/libtests/logger.cc b/libtests/logger.cc
new file mode 100644
index 00000000..f10319f6
--- /dev/null
+++ b/libtests/logger.cc
@@ -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;
+}
diff --git a/libtests/qtest/logger.test b/libtests/qtest/logger.test
new file mode 100644
index 00000000..86767eff
--- /dev/null
+++ b/libtests/qtest/logger.test
@@ -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";
+}
diff --git a/libtests/qtest/logger/exp-stderr b/libtests/qtest/logger/exp-stderr
new file mode 100644
index 00000000..5d42c91a
--- /dev/null
+++ b/libtests/qtest/logger/exp-stderr
@@ -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
diff --git a/libtests/qtest/logger/exp-stdout b/libtests/qtest/logger/exp-stdout
new file mode 100644
index 00000000..a51d446a
--- /dev/null
+++ b/libtests/qtest/logger/exp-stdout
@@ -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
diff --git a/qpdf/sizes.cc b/qpdf/sizes.cc
index ac4bae6e..62eef6a8 100644
--- a/qpdf/sizes.cc
+++ b/qpdf/sizes.cc
@@ -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);