Logging
Let me start with the logging. Well as I'm sure you're aware, there are various logging levels which most libraries support: Debug, Info, Warning, and Error.
My approach is that Debug output should show all information that I, the programmer, would want to see. The user using your program isn't likely going to want to see that you entered a specific method in the GUI, but I would in the case of a crash. If you have no way of retrieving the stack trace of the crash, then the next best thing is to print out to the log the entry and exit of every method in your program with log level debug.
In your example, that means a log with the following output:
DEBUG <timestamp>: ClassA::A() : Entering A
DEBUG <timestamp>: ClassB::B() : Entering B
DEBUG <timestamp>: ClassC::C() : Entering C
DEBUG <timestamp>: ClassC::C() : Exiting C // These don't show
DEBUG <timestamp>: ClassB::B() : Exiting B // in the case of
DEBUG <timestamp>: ClassA::A() : Exiting A // an error in C.
Log level Info is information that I would consider to be relevant to functionality of the program. Suppose for the purposes of this example that the objective of A is to perform an operation using a selected database, B is a means to view existing database connections and to create new database connections, and C is the specific information pertaining to a new database and a way of testing said connection.
Relevant Info level log messages for me would be:
INFO <timestamp>: ClassC::C() : Saved database connection "localhost".
INFO <timestamp>: ClassA::A() : Performing transaction using database connection "localhost".
INFO <timestamp>: ClassA::A() : Transaction complete using database connection "localhost".
Notice that these are messages you would also likely want to show to the user throughout the program (albeit a little verbose). For me that is the difference between Debug and Info: whether or not the user may also want to see this information. Obviously logging levels warning and error should also be both logged and shown to the user.
WARNING <timestamp>: ClassC::C() : Connection test for "localhost" failed.
What I typically do in my programs is take an optional parameter which determines at what logging level I should present messages to the user with a default value of Info. If the user does not want a verbose interface, he or she can set it to Warning instead and see only warnings and errors, and I can use it in order to output debugging information, for example. In that case, just be sure to provide an alternative indicator that a particular task is finished (execute button becomes disabled during elaboration then afterwards becomes again enabled, for instance).
Exceptions
That said, how do I approach exceptions? It depends on the context of the error. If ClassC handles creation of new database connections and upon testing the connection, throws an exception, I would want to handle that sql exception inside ClassC since I am expecting potential problems. Notice that I would only handle a sql exception. If the problem were another that I wasn't expecting, it would slip through Class C and consequently move to ClassB. ClassB may decide to catch exceptions related to missing drivers, since that is not entirely unexpected, and I wouldn't want such problems to be caught at lower levels.
However, assuming it is not either, we hit ClassA who's only role in calling ClassB was to allow the user the possibility to create new database connections. If an exception hits ClassA, something serious has happened. In such instances, what I typically do is to catch any exception, write the error and its stacktrace (if possible) to the log, show an unexpected error message to the user, then rethrow the exception. In my personal opinion, it is irresponsible to catch all exceptions without being prepared to rethrow it, since it is correct that unexpected errors are still thrown.
The important point to note here is the level of responsibility. The exceptions that I'm expecting are handled locally while unexpected exceptions in the context of my class are allowed to move up the chain. In the absolute worst case, be sure to try to log the error and show an error message to the user. Combine this with precision logging, and you're sure to be able to trace your steps to the place of error in most cases.