Commit e0720eaa78a56dafe5f5e572387b3a0cc8c88ec9

Authored by Jay Berkenbilt
1 parent 83be2191

Use the default logger for other writes to stdout/stderr

When there is no context for writing output or error messages, use the
default logger.
... ... @@ -55,30 +55,10 @@ Output Capture
55 55  
56 56 See https://github.com/qpdf/qpdf/issues/691
57 57  
58   -QPDFLogger maintains pipelines for info, warn, error, and save.
59   -
60   -There is a singleton, default QPDFLogger which all QPDF and QPDFJob
61   -objects get on construction. In most cases, it is not necessary to
62   -override this. Allow QPDFJob and QPDF to be passed a new logger
63   -instance. QPDFJob should pass its QPDFLogger to all QPDF objects it
64   -creates. The main uses cases for this would be for multithreading or
65   -for having a library that uses QPDF privately and modifies logger
66   -settings so that it wouldn't interfere with a downstream library or
67   -application also using qpdf.
68   -
69 58 There needs to be a C API to QPDFLogger. Use functions like this:
70 59  
71 60 void set_info((*f)(char* data, unsigned int len, void* udata), void* udata);
72 61  
73   -We should probably deprecate the output/error setters in QPDF and
74   -QPDFJob. In the meantime, document that they won't work anymore to set
75   -different outputs with different QPDF objects. We may need to delete
76   -rather than deprecate these methods.
77   -
78   -Find all places in the library that write to stdout/stderr/cout/cerr.
79   -Also find places that raise exceptions if unable to warn. These should
80   -use the global output writer.
81   -
82 62  
83 63 QPDFPagesTree
84 64 =============
... ...
include/qpdf/QPDFLogger.hh
... ... @@ -33,6 +33,19 @@ class QPDFLogger
33 33 QPDF_DLL
34 34 QPDFLogger();
35 35  
  36 + // Return the default logger. In general, you should use the
  37 + // default logger. You can also create your own loggers and use
  38 + // them QPDF and QPDFJob objects, but there are few reasons to do
  39 + // so. One reason may if you are using multiple QPDF or QPDFJob
  40 + // objects in different threads and want to capture output and
  41 + // errors to different streams. (Note that a single QPDF or
  42 + // QPDFJob can't be safely used from multiple threads, but it is
  43 + // safe to use separate QPDF and QPDFJob objects on separate
  44 + // threads.) Another possible reason would be if you are writing
  45 + // an application that uses the qpdf library directly and qpdf is
  46 + // also used by a downstream library or if you are using qpdf from
  47 + // a library and don't want to interfere with potential uses of
  48 + // qpdf by other libraries or applications.
36 49 QPDF_DLL
37 50 static std::shared_ptr<QPDFLogger> defaultLogger();
38 51  
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -1411,14 +1411,14 @@ class QPDFObjectHandle
1411 1411 // End legacy page helpers
1412 1412  
1413 1413 // Issue a warning about this object if possible. If the object
1414   - // has a description, a warning will be issued. Otherwise, if
1415   - // throw_if_no_description is true, throw an exception. Otherwise
1416   - // do nothing. Objects read normally from the file have
  1414 + // has a description, a warning will be issued using the owning
  1415 + // QPDF as context. Otherwise, a message will be written to the
  1416 + // default logger's error stream, which is standard error if not
  1417 + // overridden. Objects read normally from the file have
1417 1418 // descriptions. See comments on setObjectDescription for
1418 1419 // additional details.
1419 1420 QPDF_DLL
1420   - void warnIfPossible(
1421   - std::string const& warning, bool throw_if_no_description = false);
  1421 + void warnIfPossible(std::string const& warning);
1422 1422  
1423 1423 // Initializers for objects. This Factory class gives the QPDF
1424 1424 // class specific permission to call factory methods without
... ...
libqpdf/QPDFArgParser.cc
1 1 #include <qpdf/QPDFArgParser.hh>
2 2  
3 3 #include <qpdf/QIntC.hh>
  4 +#include <qpdf/QPDFLogger.hh>
4 5 #include <qpdf/QPDFUsage.hh>
5 6 #include <qpdf/QTC.hh>
6 7 #include <qpdf/QUtil.hh>
... ... @@ -235,7 +236,7 @@ QPDFArgParser::argCompletionZsh()
235 236 void
236 237 QPDFArgParser::argHelp(std::string const& p)
237 238 {
238   - std::cout << getHelp(p);
  239 + QPDFLogger::defaultLogger()->info(getHelp(p));
239 240 exit(0);
240 241 }
241 242  
... ...
libqpdf/QPDFJob_argv.cc
... ... @@ -14,6 +14,7 @@
14 14 #include <qpdf/QPDFArgParser.hh>
15 15 #include <qpdf/QPDFCryptoProvider.hh>
16 16 #include <qpdf/QPDFJob.hh>
  17 +#include <qpdf/QPDFLogger.hh>
17 18 #include <qpdf/QTC.hh>
18 19 #include <qpdf/QUtil.hh>
19 20  
... ... @@ -104,10 +105,10 @@ void
104 105 ArgParser::argVersion()
105 106 {
106 107 auto whoami = this->ap.getProgname();
107   - std::cout << whoami << " version " << QPDF::QPDFVersion() << std::endl
108   - << "Run " << whoami
109   - << " --copyright to see copyright and license information."
110   - << std::endl;
  108 + *QPDFLogger::defaultLogger()->getInfo()
  109 + << whoami << " version " << QPDF::QPDFVersion() << "\n"
  110 + << "Run " << whoami
  111 + << " --copyright to see copyright and license information.\n";
111 112 }
112 113  
113 114 void
... ... @@ -117,48 +118,35 @@ ArgParser::argCopyright()
117 118 // Make sure the output looks right on an 80-column display.
118 119 // 1 2 3 4 5 6 7 8
119 120 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
120   - std::cout
  121 + *QPDFLogger::defaultLogger()->getInfo()
121 122 << this->ap.getProgname()
122   - << " version " << QPDF::QPDFVersion() << std::endl
123   - << std::endl
124   - << "Copyright (c) 2005-2022 Jay Berkenbilt"
125   - << std::endl
126   - << "QPDF is licensed under the Apache License, Version 2.0 (the \"License\");"
127   - << std::endl
128   - << "you may not use this file except in compliance with the License."
129   - << std::endl
130   - << "You may obtain a copy of the License at"
131   - << std::endl
132   - << std::endl
133   - << " http://www.apache.org/licenses/LICENSE-2.0"
134   - << std::endl
135   - << std::endl
136   - << "Unless required by applicable law or agreed to in writing, software"
137   - << std::endl
138   - << "distributed under the License is distributed on an \"AS IS\" BASIS,"
139   - << std::endl
140   - << "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied."
141   - << std::endl
142   - << "See the License for the specific language governing permissions and"
143   - << std::endl
144   - << "limitations under the License."
145   - << std::endl
146   - << std::endl
147   - << "Versions of qpdf prior to version 7 were released under the terms"
148   - << std::endl
149   - << "of version 2.0 of the Artistic License. At your option, you may"
150   - << std::endl
151   - << "continue to consider qpdf to be licensed under those terms. Please"
152   - << std::endl
153   - << "see the manual for additional information."
154   - << std::endl;
  123 + << " version " << QPDF::QPDFVersion() << "\n"
  124 + << "\n"
  125 + << "Copyright (c) 2005-2022 Jay Berkenbilt\n"
  126 + << "QPDF is licensed under the Apache License, Version 2.0 (the \"License\");\n"
  127 + << "you may not use this file except in compliance with the License.\n"
  128 + << "You may obtain a copy of the License at\n"
  129 + << "\n"
  130 + << " http://www.apache.org/licenses/LICENSE-2.0\n"
  131 + << "\n"
  132 + << "Unless required by applicable law or agreed to in writing, software\n"
  133 + << "distributed under the License is distributed on an \"AS IS\" BASIS,\n"
  134 + << "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
  135 + << "See the License for the specific language governing permissions and\n"
  136 + << "limitations under the License.\n"
  137 + << "\n"
  138 + << "Versions of qpdf prior to version 7 were released under the terms\n"
  139 + << "of version 2.0 of the Artistic License. At your option, you may\n"
  140 + << "continue to consider qpdf to be licensed under those terms. Please\n"
  141 + << "see the manual for additional information.\n";
155 142 // clang-format on
156 143 }
157 144  
158 145 void
159 146 ArgParser::argJsonHelp()
160 147 {
161   - std::cout << QPDFJob::json_out_schema_v1() << std::endl;
  148 + *QPDFLogger::defaultLogger()->getInfo()
  149 + << QPDFJob::json_out_schema_v1() << "\n";
162 150 }
163 151  
164 152 void
... ... @@ -166,10 +154,10 @@ ArgParser::argShowCrypto()
166 154 {
167 155 auto crypto = QPDFCryptoProvider::getRegisteredImpls();
168 156 std::string default_crypto = QPDFCryptoProvider::getDefaultProvider();
169   - std::cout << default_crypto << std::endl;
  157 + *QPDFLogger::defaultLogger()->getInfo() << default_crypto << "\n";
170 158 for (auto const& iter: crypto) {
171 159 if (iter != default_crypto) {
172   - std::cout << iter << std::endl;
  160 + *QPDFLogger::defaultLogger()->getInfo() << iter << "\n";
173 161 }
174 162 }
175 163 }
... ... @@ -407,7 +395,8 @@ ArgParser::argEndCopyAttachment()
407 395 void
408 396 ArgParser::argJobJsonHelp()
409 397 {
410   - std::cout << QPDFJob::job_json_schema_v1() << std::endl;
  398 + *QPDFLogger::defaultLogger()->getInfo()
  399 + << QPDFJob::job_json_schema_v1() << "\n";
411 400 }
412 401  
413 402 void
... ...
libqpdf/QPDFJob_config.cc
1 1 #include <qpdf/QPDFJob.hh>
2 2  
  3 +#include <qpdf/QPDFLogger.hh>
3 4 #include <qpdf/QTC.hh>
4 5 #include <qpdf/QUtil.hh>
5 6  
... ... @@ -648,9 +649,10 @@ QPDFJob::Config::passwordFile(std::string const&amp; parameter)
648 649 o.m->password = QUtil::make_shared_cstr(lines.front());
649 650  
650 651 if (lines.size() > 1) {
651   - std::cerr << this->o.m->message_prefix
652   - << ": WARNING: all but the first line of"
653   - << " the password file are ignored" << std::endl;
  652 + *QPDFLogger::defaultLogger()->getError()
  653 + << this->o.m->message_prefix
  654 + << ": WARNING: all but the first line of"
  655 + << " the password file are ignored\n";
654 656 }
655 657 }
656 658 return this;
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -5,6 +5,7 @@
5 5 #include <qpdf/Pl_QPDFTokenizer.hh>
6 6 #include <qpdf/QPDF.hh>
7 7 #include <qpdf/QPDFExc.hh>
  8 +#include <qpdf/QPDFLogger.hh>
8 9 #include <qpdf/QPDFMatrix.hh>
9 10 #include <qpdf/QPDFPageObjectHelper.hh>
10 11 #include <qpdf/QPDF_Array.hh>
... ... @@ -578,13 +579,12 @@ QPDFObjectHandle::getIntValueAsInt()
578 579 if (v < INT_MIN) {
579 580 QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MIN");
580 581 warnIfPossible(
581   - "requested value of integer is too small; returning INT_MIN",
582   - false);
  582 + "requested value of integer is too small; returning INT_MIN");
583 583 result = INT_MIN;
584 584 } else if (v > INT_MAX) {
585 585 QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MAX");
586 586 warnIfPossible(
587   - "requested value of integer is too big; returning INT_MAX", false);
  587 + "requested value of integer is too big; returning INT_MAX");
588 588 result = INT_MAX;
589 589 } else {
590 590 result = static_cast<int>(v);
... ... @@ -610,7 +610,7 @@ QPDFObjectHandle::getUIntValue()
610 610 if (v < 0) {
611 611 QTC::TC("qpdf", "QPDFObjectHandle uint returning 0");
612 612 warnIfPossible(
613   - "unsigned value request for negative number; returning 0", false);
  613 + "unsigned value request for negative number; returning 0");
614 614 } else {
615 615 result = static_cast<unsigned long long>(v);
616 616 }
... ... @@ -635,15 +635,12 @@ QPDFObjectHandle::getUIntValueAsUInt()
635 635 if (v < 0) {
636 636 QTC::TC("qpdf", "QPDFObjectHandle uint uint returning 0");
637 637 warnIfPossible(
638   - "unsigned integer value request for negative number; returning 0",
639   - false);
  638 + "unsigned integer value request for negative number; returning 0");
640 639 result = 0;
641 640 } else if (v > UINT_MAX) {
642 641 QTC::TC("qpdf", "QPDFObjectHandle uint returning UINT_MAX");
643   - warnIfPossible(
644   - "requested value of unsigned integer is too big;"
645   - " returning UINT_MAX",
646   - false);
  642 + warnIfPossible("requested value of unsigned integer is too big;"
  643 + " returning UINT_MAX");
647 644 result = UINT_MAX;
648 645 } else {
649 646 result = static_cast<unsigned int>(v);
... ... @@ -3000,16 +2997,15 @@ QPDFObjectHandle::typeWarning(
3000 2997 }
3001 2998  
3002 2999 void
3003   -QPDFObjectHandle::warnIfPossible(
3004   - std::string const& warning, bool throw_if_no_description)
  3000 +QPDFObjectHandle::warnIfPossible(std::string const& warning)
3005 3001 {
3006 3002 QPDF* context = 0;
3007 3003 std::string description;
3008 3004 dereference();
3009 3005 if (this->obj->getDescription(context, description)) {
3010 3006 warn(context, QPDFExc(qpdf_e_damaged_pdf, "", description, 0, warning));
3011   - } else if (throw_if_no_description) {
3012   - throw std::runtime_error(warning);
  3007 + } else {
  3008 + *QPDFLogger::defaultLogger()->getError() << warning << "\n";
3013 3009 }
3014 3010 }
3015 3011  
... ...
libqpdf/qpdf-c.cc
... ... @@ -6,6 +6,7 @@
6 6 #include <qpdf/Pl_Discard.hh>
7 7 #include <qpdf/QIntC.hh>
8 8 #include <qpdf/QPDFExc.hh>
  9 +#include <qpdf/QPDFLogger.hh>
9 10 #include <qpdf/QPDFWriter.hh>
10 11 #include <qpdf/QTC.hh>
11 12 #include <qpdf/QUtil.hh>
... ... @@ -185,8 +186,9 @@ qpdf_cleanup(qpdf_data* qpdf)
185 186 qpdf_oh_release_all(*qpdf);
186 187 if ((*qpdf)->error.get()) {
187 188 QTC::TC("qpdf", "qpdf-c cleanup warned about unhandled error");
188   - std::cerr << "WARNING: application did not handle error: "
189   - << (*qpdf)->error->what() << std::endl;
  189 + *QPDFLogger::defaultLogger()->getWarn()
  190 + << "WARNING: application did not handle error: "
  191 + << (*qpdf)->error->what() << "\n";
190 192 }
191 193 delete *qpdf;
192 194 *qpdf = 0;
... ... @@ -898,7 +900,8 @@ trap_oh_errors(
898 900 " to ERROR HANDLING in qpdf-c.h"));
899 901 qpdf->oh_error_occurred = true;
900 902 }
901   - std::cerr << qpdf->error->what() << std::endl;
  903 + *QPDFLogger::defaultLogger()->getError()
  904 + << qpdf->error->what() << "\n";
902 905 }
903 906 return fallback();
904 907 }
... ...
libqpdf/qpdfjob-c.cc
1 1 #include <qpdf/qpdfjob-c.h>
2 2  
3 3 #include <qpdf/QPDFJob.hh>
  4 +#include <qpdf/QPDFLogger.hh>
4 5 #include <qpdf/QPDFUsage.hh>
5 6 #include <qpdf/QUtil.hh>
6 7  
... ... @@ -19,7 +20,8 @@ qpdfjob_run_from_argv(char const* const argv[])
19 20 j.initializeFromArgv(argv);
20 21 j.run();
21 22 } catch (std::exception& e) {
22   - std::cerr << whoami << ": " << e.what() << std::endl;
  23 + *QPDFLogger::defaultLogger()->getError()
  24 + << whoami << ": " << e.what() << "\n";
23 25 return QPDFJob::EXIT_ERROR;
24 26 }
25 27 return j.getExitCode();
... ... @@ -48,7 +50,8 @@ qpdfjob_run_from_json(char const* json)
48 50 j.initializeFromJson(json);
49 51 j.run();
50 52 } catch (std::exception& e) {
51   - std::cerr << "qpdfjob json: " << e.what() << std::endl;
  53 + *QPDFLogger::defaultLogger()->getError()
  54 + << "qpdfjob json: " << e.what() << "\n";
52 55 return QPDFJob::EXIT_ERROR;
53 56 }
54 57 return j.getExitCode();
... ...
manual/release-notes.rst
... ... @@ -120,6 +120,11 @@ For a detailed list of changes, please see the file
120 120 - See :ref:`breaking-crypto-api` for specific details, and see
121 121 :ref:`weak-crypto` for a general discussion.
122 122  
  123 + - QPDFObjectHandle::warnIfPossible no longer takes an optional
  124 + argument to throw an exception if there is no description. If
  125 + there is no description, it writes to the default logger's error
  126 + stream.
  127 +
123 128 - CLI Enhancements
124 129  
125 130 - ``qpdf --list-attachments --verbose`` include some additional
... ... @@ -139,6 +144,20 @@ For a detailed list of changes, please see the file
139 144  
140 145 - Library Enhancements
141 146  
  147 + - A new object ``QPDFLogger`` has been added. Details are in
  148 + :file:`include/qpdf/QPDFLogger.hh`.
  149 +
  150 + - ``QPDF`` and ``QPDFJob`` both use the default logger by
  151 + default but can have their loggers overridden. The
  152 + ``setOutputStreams`` method is deprecated in both classes.
  153 +
  154 + - A few things from ``QPDFObjectHandle`` that used to be
  155 + exceptions now write errors with the default logger.
  156 +
  157 + - By configuring the default logger, it is possible to capture
  158 + output and errors that slipped through the cracks with
  159 + ``setOutputStreams``.
  160 +
142 161 - New methods ``insertItemAndGet``, ``appendItemAndGet``,
143 162 ``eraseItemAndGet``, ``replaceKeyAndGet``, and
144 163 ``removeKeyAndGet`` return the newly added or removed object.
... ...
qpdf/qtest/error-condition.test
... ... @@ -111,11 +111,11 @@ $td-&gt;runtest(&quot;C API: no recovery&quot;,
111 111  
112 112 $td->runtest("integer type checks",
113 113 {$td->COMMAND => "test_driver 62 minimal.pdf"},
114   - {$td->STRING => "test 62 done\n", $td->EXIT_STATUS => 0},
  114 + {$td->FILE => "test62.out", $td->EXIT_STATUS => 0},
115 115 $td->NORMALIZE_NEWLINES);
116 116 $td->runtest("getValueAs... accessor checks",
117 117 {$td->COMMAND => "test_driver 85 -"},
118   - {$td->STRING => "test 85 done\n", $td->EXIT_STATUS => 0},
  118 + {$td->FILE => "test85.out", $td->EXIT_STATUS => 0},
119 119 $td->NORMALIZE_NEWLINES);
120 120  
121 121 $n_tests += @badfiles + 11;
... ...
qpdf/qtest/qpdf/test62.out 0 → 100644
  1 +requested value of integer is too big; returning INT_MAX
  2 +requested value of unsigned integer is too big; returning UINT_MAX
  3 +unsigned value request for negative number; returning 0
  4 +requested value of integer is too small; returning INT_MIN
  5 +unsigned integer value request for negative number; returning 0
  6 +requested value of integer is too big; returning INT_MAX
  7 +test 62 done
... ...
qpdf/qtest/qpdf/test85.out 0 → 100644
  1 +requested value of integer is too big; returning INT_MAX
  2 +requested value of integer is too small; returning INT_MIN
  3 +unsigned value request for negative number; returning 0
  4 +unsigned integer value request for negative number; returning 0
  5 +requested value of unsigned integer is too big; returning UINT_MAX
  6 +test 85 done
... ...