Commit f1f711963b8e5f0b2b5a9d80a522cbd616a153a9

Authored by Jay Berkenbilt
1 parent f588d741

Add and test QPDFLogger class

include/qpdf/QPDFLogger.hh 0 → 100644
  1 +// Copyright (c) 2005-2022 Jay Berkenbilt
  2 +//
  3 +// This file is part of qpdf.
  4 +//
  5 +// Licensed under the Apache License, Version 2.0 (the "License");
  6 +// you may not use this file except in compliance with the License.
  7 +// You may obtain a copy of the License at
  8 +//
  9 +// http://www.apache.org/licenses/LICENSE-2.0
  10 +//
  11 +// Unless required by applicable law or agreed to in writing, software
  12 +// distributed under the License is distributed on an "AS IS" BASIS,
  13 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +// See the License for the specific language governing permissions and
  15 +// limitations under the License.
  16 +//
  17 +// Versions of qpdf prior to version 7 were released under the terms
  18 +// of version 2.0 of the Artistic License. At your option, you may
  19 +// continue to consider qpdf to be licensed under those terms. Please
  20 +// see the manual for additional information.
  21 +
  22 +#ifndef QPDFLOGGER_HH
  23 +#define QPDFLOGGER_HH
  24 +
  25 +#include <qpdf/DLL.h>
  26 +#include <qpdf/Pipeline.hh>
  27 +#include <iostream>
  28 +#include <memory>
  29 +
  30 +class QPDFLogger
  31 +{
  32 + public:
  33 + QPDF_DLL
  34 + QPDFLogger();
  35 +
  36 + QPDF_DLL
  37 + static std::shared_ptr<QPDFLogger> defaultLogger();
  38 +
  39 + // Defaults:
  40 + //
  41 + // info -- if save is standard output, standard error, else standard output
  42 + // warn -- whatever error points to
  43 + // error -- standard error
  44 + // save -- undefined unless set
  45 + //
  46 + // On deletion, finish() is called for the standard output and
  47 + // standard error pipelines, which flushes output. If you supply
  48 + // any custom pipelines, you must call finish() on them yourself.
  49 + // Note that calling finish is not needed for string, stdio, or
  50 + // ostream pipelines.
  51 + //
  52 + // NOTES ABOUT THE SAVE PIPELINE
  53 + //
  54 + // You should never set the save pipeline to the same destination
  55 + // as something else. Doing so will corrupt your save output. If
  56 + // you want to save to standard output, use the method
  57 + // saveToStandardOutput(). In addition to setting the save
  58 + // pipeline, that does the following extra things:
  59 + //
  60 + // * If standard output has been used, a logic error is thrown
  61 + // * If info is set to standard output at the time of the set save
  62 + // call, it is switched to standard error.
  63 + //
  64 + // This is not a guarantee. You can still mess this up in ways
  65 + // that are not checked. Here are a few examples:
  66 + //
  67 + // * Don't set any pipeline to standard output *after* passing it
  68 + // to setSave()
  69 + // * Don't use a separate mechanism to write stdout/stderr other
  70 + // than QPDFLogger::standardOutput()
  71 + // * Don't set anything to the same custom pipeline that save is
  72 + // set to.
  73 + //
  74 + // Just be sure that if you change pipelines around, you should
  75 + // avoid having the save pipeline also be used for any other
  76 + // purpose. The special case for saving to standard output allows
  77 + // you to call saveToStandardOutput() early without having to
  78 + // worry about the info pipeline.
  79 +
  80 + QPDF_DLL
  81 + void info(char const*);
  82 + QPDF_DLL
  83 + void info(std::string const&);
  84 + QPDF_DLL
  85 + std::shared_ptr<Pipeline> getInfo(bool null_okay = false);
  86 +
  87 + QPDF_DLL
  88 + void warn(char const*);
  89 + QPDF_DLL
  90 + void warn(std::string const&);
  91 + QPDF_DLL
  92 + std::shared_ptr<Pipeline> getWarn(bool null_okay = false);
  93 +
  94 + QPDF_DLL
  95 + void error(char const*);
  96 + QPDF_DLL
  97 + void error(std::string const&);
  98 + QPDF_DLL
  99 + std::shared_ptr<Pipeline> getError(bool null_okay = false);
  100 +
  101 + QPDF_DLL
  102 + std::shared_ptr<Pipeline> getSave(bool null_okay = false);
  103 +
  104 + QPDF_DLL
  105 + std::shared_ptr<Pipeline> standardOutput();
  106 + QPDF_DLL
  107 + std::shared_ptr<Pipeline> standardError();
  108 + QPDF_DLL
  109 + std::shared_ptr<Pipeline> discard();
  110 +
  111 + // Passing a null pointer resets to default
  112 + QPDF_DLL
  113 + void setInfo(std::shared_ptr<Pipeline>);
  114 + QPDF_DLL
  115 + void setWarn(std::shared_ptr<Pipeline>);
  116 + QPDF_DLL
  117 + void setError(std::shared_ptr<Pipeline>);
  118 + // See notes above about the save pipeline
  119 + QPDF_DLL
  120 + void setSave(std::shared_ptr<Pipeline>);
  121 + QPDF_DLL
  122 + void saveToStandardOutput();
  123 +
  124 + // Shortcut for logic to reset output to new output/error streams.
  125 + // out_stream is used for info, err_stream is used for error, and
  126 + // warning is cleared so that it follows error.
  127 + QPDF_DLL
  128 + void setOutputStreams(std::ostream* out_stream, std::ostream* err_stream);
  129 +
  130 + private:
  131 + std::shared_ptr<Pipeline>
  132 + throwIfNull(std::shared_ptr<Pipeline>, bool null_okay);
  133 +
  134 + class Members
  135 + {
  136 + friend class QPDFLogger;
  137 +
  138 + public:
  139 + QPDF_DLL
  140 + ~Members();
  141 +
  142 + private:
  143 + Members();
  144 + Members(Members const&) = delete;
  145 +
  146 + std::shared_ptr<Pipeline> p_discard;
  147 + std::shared_ptr<Pipeline> p_real_stdout;
  148 + std::shared_ptr<Pipeline> p_stdout;
  149 + std::shared_ptr<Pipeline> p_stderr;
  150 + std::shared_ptr<Pipeline> p_info;
  151 + std::shared_ptr<Pipeline> p_warn;
  152 + std::shared_ptr<Pipeline> p_error;
  153 + std::shared_ptr<Pipeline> p_save;
  154 + };
  155 + std::shared_ptr<Members> m;
  156 +};
  157 +
  158 +#endif // QPDFLOGGER_HH
libqpdf/CMakeLists.txt
@@ -67,6 +67,7 @@ set(libqpdf_SOURCES @@ -67,6 +67,7 @@ set(libqpdf_SOURCES
67 QPDFJob_argv.cc 67 QPDFJob_argv.cc
68 QPDFJob_config.cc 68 QPDFJob_config.cc
69 QPDFJob_json.cc 69 QPDFJob_json.cc
  70 + QPDFLogger.cc
70 QPDFMatrix.cc 71 QPDFMatrix.cc
71 QPDFNameTreeObjectHelper.cc 72 QPDFNameTreeObjectHelper.cc
72 QPDFNumberTreeObjectHelper.cc 73 QPDFNumberTreeObjectHelper.cc
libqpdf/QPDFLogger.cc 0 → 100644
  1 +#include <qpdf/QPDFLogger.hh>
  2 +
  3 +#include <qpdf/Pl_Discard.hh>
  4 +#include <qpdf/Pl_OStream.hh>
  5 +#include <iostream>
  6 +#include <stdexcept>
  7 +
  8 +namespace
  9 +{
  10 + class Pl_Track: public Pipeline
  11 + {
  12 + public:
  13 + Pl_Track(char const* identifier, Pipeline* next) :
  14 + Pipeline(identifier, next),
  15 + used(false)
  16 + {
  17 + }
  18 +
  19 + virtual void
  20 + write(unsigned char const* data, size_t len) override
  21 + {
  22 + this->used = true;
  23 + getNext()->write(data, len);
  24 + }
  25 +
  26 + virtual void
  27 + finish() override
  28 + {
  29 + getNext()->finish();
  30 + }
  31 +
  32 + bool
  33 + getUsed() const
  34 + {
  35 + return used;
  36 + }
  37 +
  38 + private:
  39 + bool used;
  40 + };
  41 +}; // namespace
  42 +
  43 +QPDFLogger::Members::Members() :
  44 + p_discard(new Pl_Discard()),
  45 + p_real_stdout(new Pl_OStream("standard output", std::cout)),
  46 + p_stdout(new Pl_Track("track stdout", p_real_stdout.get())),
  47 + p_stderr(new Pl_OStream("standard error", std::cerr)),
  48 + p_info(p_stdout),
  49 + p_warn(nullptr),
  50 + p_error(p_stderr),
  51 + p_save(nullptr)
  52 +{
  53 +}
  54 +
  55 +QPDFLogger::Members::~Members()
  56 +{
  57 + p_stdout->finish();
  58 + p_stderr->finish();
  59 +}
  60 +
  61 +QPDFLogger::QPDFLogger() :
  62 + m(new Members())
  63 +{
  64 +}
  65 +
  66 +std::shared_ptr<QPDFLogger>
  67 +QPDFLogger::defaultLogger()
  68 +{
  69 + static auto l = std::make_shared<QPDFLogger>();
  70 + return l;
  71 +}
  72 +
  73 +void
  74 +QPDFLogger::info(char const* s)
  75 +{
  76 + getInfo(false)->writeCStr(s);
  77 +}
  78 +
  79 +void
  80 +QPDFLogger::info(std::string const& s)
  81 +{
  82 + getInfo(false)->writeString(s);
  83 +}
  84 +
  85 +std::shared_ptr<Pipeline>
  86 +QPDFLogger::getInfo(bool null_okay)
  87 +{
  88 + return throwIfNull(this->m->p_info, null_okay);
  89 +}
  90 +
  91 +void
  92 +QPDFLogger::warn(char const* s)
  93 +{
  94 + getWarn(false)->writeCStr(s);
  95 +}
  96 +
  97 +void
  98 +QPDFLogger::warn(std::string const& s)
  99 +{
  100 + getWarn(false)->writeString(s);
  101 +}
  102 +
  103 +std::shared_ptr<Pipeline>
  104 +QPDFLogger::getWarn(bool null_okay)
  105 +{
  106 + if (this->m->p_warn) {
  107 + return this->m->p_warn;
  108 + }
  109 + return getError(null_okay);
  110 +}
  111 +
  112 +void
  113 +QPDFLogger::error(char const* s)
  114 +{
  115 + getError(false)->writeCStr(s);
  116 +}
  117 +
  118 +void
  119 +QPDFLogger::error(std::string const& s)
  120 +{
  121 + getError(false)->writeString(s);
  122 +}
  123 +
  124 +std::shared_ptr<Pipeline>
  125 +QPDFLogger::getError(bool null_okay)
  126 +{
  127 + return throwIfNull(this->m->p_error, null_okay);
  128 +}
  129 +
  130 +std::shared_ptr<Pipeline>
  131 +QPDFLogger::getSave(bool null_okay)
  132 +{
  133 + return throwIfNull(this->m->p_save, null_okay);
  134 +}
  135 +
  136 +std::shared_ptr<Pipeline>
  137 +QPDFLogger::standardOutput()
  138 +{
  139 + return this->m->p_stdout;
  140 +}
  141 +
  142 +std::shared_ptr<Pipeline>
  143 +QPDFLogger::standardError()
  144 +{
  145 + return this->m->p_stderr;
  146 +}
  147 +
  148 +std::shared_ptr<Pipeline>
  149 +QPDFLogger::discard()
  150 +{
  151 + return this->m->p_discard;
  152 +}
  153 +
  154 +void
  155 +QPDFLogger::setInfo(std::shared_ptr<Pipeline> p)
  156 +{
  157 + if (p == nullptr) {
  158 + if (this->m->p_save == this->m->p_stdout) {
  159 + p = this->m->p_stderr;
  160 + } else {
  161 + p = this->m->p_stdout;
  162 + }
  163 + }
  164 + this->m->p_info = p;
  165 +}
  166 +
  167 +void
  168 +QPDFLogger::setWarn(std::shared_ptr<Pipeline> p)
  169 +{
  170 + this->m->p_warn = p;
  171 +}
  172 +
  173 +void
  174 +QPDFLogger::setError(std::shared_ptr<Pipeline> p)
  175 +{
  176 + if (p == nullptr) {
  177 + p = this->m->p_stderr;
  178 + }
  179 + this->m->p_error = p;
  180 +}
  181 +
  182 +void
  183 +QPDFLogger::setSave(std::shared_ptr<Pipeline> p)
  184 +{
  185 + if (p == this->m->p_stdout) {
  186 + auto pt = dynamic_cast<Pl_Track*>(p.get());
  187 + if (pt->getUsed()) {
  188 + throw std::logic_error(
  189 + "QPDFLogger: called setSave on standard output after standard"
  190 + " output has already been used");
  191 + }
  192 + if (this->m->p_info == this->m->p_stdout) {
  193 + this->m->p_info = this->m->p_stderr;
  194 + }
  195 + }
  196 + this->m->p_save = p;
  197 +}
  198 +
  199 +void
  200 +QPDFLogger::saveToStandardOutput()
  201 +{
  202 + setSave(standardOutput());
  203 +}
  204 +
  205 +void
  206 +QPDFLogger::setOutputStreams(std::ostream* out_stream, std::ostream* err_stream)
  207 +{
  208 + if (out_stream == &std::cout) {
  209 + out_stream = nullptr;
  210 + }
  211 + if (err_stream == &std::cerr) {
  212 + err_stream = nullptr;
  213 + }
  214 + std::shared_ptr<Pipeline> new_out;
  215 + std::shared_ptr<Pipeline> new_err;
  216 +
  217 + if (out_stream == nullptr) {
  218 + if (this->m->p_save == this->m->p_stdout) {
  219 + new_out = this->m->p_stderr;
  220 + } else {
  221 + new_out = this->m->p_stdout;
  222 + }
  223 + } else {
  224 + new_out = std::make_shared<Pl_OStream>("output", *out_stream);
  225 + }
  226 + if (err_stream == nullptr) {
  227 + new_err = this->m->p_stderr;
  228 + } else {
  229 + new_err = std::make_shared<Pl_OStream>("error output", *err_stream);
  230 + }
  231 + this->m->p_info = new_out;
  232 + this->m->p_warn = nullptr;
  233 + this->m->p_error = new_err;
  234 +}
  235 +
  236 +std::shared_ptr<Pipeline>
  237 +QPDFLogger::throwIfNull(std::shared_ptr<Pipeline> p, bool null_okay)
  238 +{
  239 + if (!(null_okay || p)) {
  240 + throw std::logic_error(
  241 + "QPDFLogger: requested a null pipeline without null_okay == true");
  242 + }
  243 + return p;
  244 +}
libtests/CMakeLists.txt
@@ -16,6 +16,7 @@ set(TEST_PROGRAMS @@ -16,6 +16,7 @@ set(TEST_PROGRAMS
16 json 16 json
17 json_handler 17 json_handler
18 json_parse 18 json_parse
  19 + logger
19 lzw 20 lzw
20 main_from_wmain 21 main_from_wmain
21 matrix 22 matrix
libtests/logger.cc 0 → 100644
  1 +#include <qpdf/assert_test.h>
  2 +
  3 +#include <qpdf/Pl_String.hh>
  4 +#include <qpdf/QPDFLogger.hh>
  5 +#include <stdexcept>
  6 +
  7 +static void
  8 +test1()
  9 +{
  10 + // Standard behavior
  11 +
  12 + auto logger = QPDFLogger::defaultLogger();
  13 +
  14 + logger->info("info to stdout\n");
  15 + logger->warn("warn to stderr\n");
  16 + logger->error("error to stderr\n");
  17 + assert(logger->getSave(true) == nullptr);
  18 + try {
  19 + logger->getSave();
  20 + assert(false);
  21 + } catch (std::logic_error& e) {
  22 + *(logger->getInfo()) << "getSave exception: " << e.what() << "\n";
  23 + }
  24 + try {
  25 + logger->saveToStandardOutput();
  26 + assert(false);
  27 + } catch (std::logic_error& e) {
  28 + *(logger->getInfo())
  29 + << "saveToStandardOutput exception: " << e.what() << "\n";
  30 + }
  31 + logger->setWarn(logger->discard());
  32 + logger->warn("warning not seen\n");
  33 + logger->setWarn(nullptr);
  34 + logger->warn("restored warning to stderr\n");
  35 +}
  36 +
  37 +static void
  38 +test2()
  39 +{
  40 + // First call saveToStandardOutput. Then use info, which then to
  41 + // go stderr.
  42 + QPDFLogger l;
  43 + l.saveToStandardOutput();
  44 + l.info(std::string("info to stderr\n"));
  45 + *(l.getSave()) << "save to stdout\n";
  46 + l.setInfo(nullptr);
  47 + l.info("info still to stderr\n");
  48 + l.setSave(nullptr);
  49 + l.setInfo(nullptr);
  50 + l.info("info back to stdout\n");
  51 +}
  52 +
  53 +static void
  54 +test3()
  55 +{
  56 + // Error/warning
  57 + QPDFLogger l;
  58 +
  59 + // Warning follows error when error is set explicitly.
  60 + std::string errors;
  61 + auto pl_error = std::make_shared<Pl_String>("errors", nullptr, errors);
  62 + l.setError(pl_error);
  63 + l.warn("warn follows error\n");
  64 + assert(errors == "warn follows error\n");
  65 + l.error("error too\n");
  66 + assert(errors == "warn follows error\nerror too\n");
  67 +
  68 + // Set warnings -- now they're separate
  69 + std::string warnings;
  70 + auto pl_warn = std::make_shared<Pl_String>("warnings", nullptr, warnings);
  71 + l.setWarn(pl_warn);
  72 + l.warn(std::string("warning now separate\n"));
  73 + l.error(std::string("new error\n"));
  74 + assert(warnings == "warning now separate\n");
  75 + assert(errors == "warn follows error\nerror too\nnew error\n");
  76 + std::string errors2;
  77 + pl_error = std::make_shared<Pl_String>("errors", nullptr, errors2);
  78 + l.setError(pl_error);
  79 + l.warn("new warning\n");
  80 + l.error("another new error\n");
  81 + assert(warnings == "warning now separate\nnew warning\n");
  82 + assert(errors == "warn follows error\nerror too\nnew error\n");
  83 + assert(errors2 == "another new error\n");
  84 +
  85 + // Restore warnings to default -- follows error again
  86 + l.setWarn(nullptr);
  87 + l.warn("warning 3\n");
  88 + l.error("error 3\n");
  89 + assert(warnings == "warning now separate\nnew warning\n");
  90 + assert(errors == "warn follows error\nerror too\nnew error\n");
  91 + assert(errors2 == "another new error\nwarning 3\nerror 3\n");
  92 +
  93 + // Restore everything to default
  94 + l.setInfo(nullptr);
  95 + l.setWarn(nullptr);
  96 + l.setError(nullptr);
  97 + l.info("after reset, info to stdout\n");
  98 + l.warn("after reset, warn to stderr\n");
  99 + l.error("after reset, error to stderr\n");
  100 +}
  101 +
  102 +int
  103 +main()
  104 +{
  105 + test1();
  106 + test2();
  107 + test3();
  108 + return 0;
  109 +}
libtests/qtest/logger.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("logger") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +my $td = new TestDriver('logger');
  11 +
  12 +cleanup();
  13 +
  14 +$td->runtest("logger",
  15 + {$td->COMMAND => "logger >stdout 2>stderr"},
  16 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  17 + $td->NORMALIZE_NEWLINES);
  18 +$td->runtest("check stdout",
  19 + {$td->FILE => "stdout"},
  20 + {$td->FILE => "exp-stdout"},
  21 + $td->NORMALIZE_NEWLINES);
  22 +$td->runtest("check stderr",
  23 + {$td->FILE => "stderr"},
  24 + {$td->FILE => "exp-stderr"},
  25 + $td->NORMALIZE_NEWLINES);
  26 +
  27 +cleanup();
  28 +$td->report(3);
  29 +
  30 +sub cleanup
  31 +{
  32 + unlink "stdout", "stderr";
  33 +}
libtests/qtest/logger/exp-stderr 0 → 100644
  1 +warn to stderr
  2 +error to stderr
  3 +restored warning to stderr
  4 +info to stderr
  5 +info still to stderr
  6 +after reset, warn to stderr
  7 +after reset, error to stderr
libtests/qtest/logger/exp-stdout 0 → 100644
  1 +info to stdout
  2 +getSave exception: QPDFLogger: requested a null pipeline without null_okay == true
  3 +saveToStandardOutput exception: QPDFLogger: called setSave on standard output after standard output has already been used
  4 +save to stdout
  5 +info back to stdout
  6 +after reset, info to stdout
qpdf/sizes.cc
@@ -31,6 +31,7 @@ @@ -31,6 +31,7 @@
31 #include <qpdf/QPDFFileSpecObjectHelper.hh> 31 #include <qpdf/QPDFFileSpecObjectHelper.hh>
32 #include <qpdf/QPDFFormFieldObjectHelper.hh> 32 #include <qpdf/QPDFFormFieldObjectHelper.hh>
33 #include <qpdf/QPDFJob.hh> 33 #include <qpdf/QPDFJob.hh>
  34 +#include <qpdf/QPDFLogger.hh>
34 #include <qpdf/QPDFMatrix.hh> 35 #include <qpdf/QPDFMatrix.hh>
35 #include <qpdf/QPDFNameTreeObjectHelper.hh> 36 #include <qpdf/QPDFNameTreeObjectHelper.hh>
36 #include <qpdf/QPDFNumberTreeObjectHelper.hh> 37 #include <qpdf/QPDFNumberTreeObjectHelper.hh>
@@ -98,6 +99,7 @@ main() @@ -98,6 +99,7 @@ main()
98 print_size(QPDFJob::EncConfig); 99 print_size(QPDFJob::EncConfig);
99 print_size(QPDFJob::PagesConfig); 100 print_size(QPDFJob::PagesConfig);
100 print_size(QPDFJob::UOConfig); 101 print_size(QPDFJob::UOConfig);
  102 + print_size(QPDFLogger);
101 print_size(QPDFMatrix); 103 print_size(QPDFMatrix);
102 print_size(QPDFNameTreeObjectHelper); 104 print_size(QPDFNameTreeObjectHelper);
103 print_size(QPDFNameTreeObjectHelper::iterator); 105 print_size(QPDFNameTreeObjectHelper::iterator);