program error reporting
Various mechanisms are in place to deal with problematic conditions that occur in software execution. The problems include: handling bad program input, unexpected configurations of the system, and programming mistakes on the part of the software's author.
The mechanisms include: sending text messages to standard streams, assertions, exception handling, and logging. Each has their proper place, and there are broad gray areas in their application.
It is sometimes difficult to decide which mechanism to apply in error handling. This page gives sage advice on the topic.
standard output/error streams
Command-line programs and text filters, by nature, write strings to standard output. It is rarely appropriate for program types, aside from those, to write to standard output.
Of course, there are two output streams: `stdout` and `stderr`, the former is meant for normal output, and the latter is meant for error messages. The error stream should be reserved for exceptional output, for debugging something that has gone wrong.
Standard stream output is useful for quick-n-dirty debugging, but all trace of this ought to be removed before a program runs for end users. Standard streams are *never* suitable for logging of program status.
Aside from command-line programs meant for textual interaction with a user, libraries, servers, and applications should *never* write strings to standard output. It causes untold problems --- and there is always a better way.
assertions
Assertions are useful and appropriate for sanity checks: they
Typically, assertion calls are compiled out of production programs, that is, the code of the assertion is erased from the final product. So end users usually will never see an assertion failure.
Assertions can save a lot of time in debugging, by reporting an erroneous condition early on. They also serve to emphasize the programmer's assumptions about function input, that conditions have been guaranteed before the point of the assertion.
API documentation should reflect any asserted restrictions.
exceptions
In environments that support them, exceptions are the best means of passing information about an exceptional condition or error to higher levels. The higher level might be something that called the code that threw an exception, or ultimately the end user of the program.
The idea is: the most information as to what happened is available where the error occurs, (e.g. a file couldn't be opened due to permissions), a middle layer might have more information as to why it happened, and a higher layer would know how best to deal with the situation (Open a dialog for the user, or log something to a file, or take some other action, or just ignore it.) The program throws an exception at the point where an issue arose, with information about what happened, middle layers might add information to that, and throw their own exceptions, and finally, some layer will decide what to do about it.
Exceptions are used for many other purposes, but this is the main use. Both Java and Python are highly optimized for exceptions, to the extent that exceptions are used as a general program flow control mechanism.
Anything that an end user, or higher software levels, might need to deal with, should be handled by exceptions, if possible.
The quality of implementation of exceptions varies greatly by computer language. Java's implementation is outstanding: it requires the programmer to handle non-fatal exceptions, and relieves the burden of post-exception cleanup by means of excellent garbage collection.
Python also has excellent garbage collection, but the language does not complain if the programmer fails to handle an exception. For smaller projects, this may be fine, but it may be too loose for large applications, written by many programmers.
In languages that lack native garbage collection, exceptions require a great deal of discipline on the part of programmers, to clean up any resources that were allocated by the code that throws the exception.
logging
For routine reporting, especially of information that might be of use to system administrators or help-line personnel, a logging service is the ticket.
The idea is, the code writes log messages (with a time stamp and the severity level) to a standard log file.
Typically, logging systems facilitate a grading of severity, into levels like
debug → info → warning → error → fatal
(The granularity of the levels depends on the system, as do their names.)
Application software facilitates filtering messages according to severity level. Also, logging systems may be set to track messages only of certain severity, say warnings, errors, and fatal messages.
In particular, this is the place to write information about a condition that is so bad that the software cannot continue to function properly: "fatal" severity. This can give investigators useful clues.
The lower severity levels are useful to track progress through a program. It is important only not to write too many 'debug' messages per second.
ignore the problem
This is the approach to take, if you want users of your software to hate you, if you want coworkers who clean up your messes to hate you and think you are an incompetent fool, and if you want to hate yourself two years from now when you find yourself working on the software again.
To each their own!