I'm assuming that you are using some type of third-party logging utility that supports multiple logging levels that can be changed without recompiling source code (like Log4J or SLF4J). If you're not, do yourself a favor and implement one... yesterday.
A good logging scheme should allow you to diagnose the root cause of any problem without recompiling code. Obviously, just logging the stack trace isn't going to give you much context. On the other hand, logging every logical step is going to kill application performance and eat up a ton of disk space. The trick is to default your logs to the minimal amount of detail, but allow yourself the ability to turn up the logging level in the event of a problem.
For normal processing the minimal amount of logging necessary is probably best to log at the INFO level. Think of this as a you would a Table of Contents in a book: it's not going to tell you the story, per se, but it will give you an idea on where to look to find what you want. Log your high-level process steps, services, or classes with generic informative messages like "Starting validation process" or "Retrieving query data", but use this message level sparingly (or not at all) in lower-level services, classes, or functions to avoid clutter.
I use DEBUG messages more like breadcrumbs. If the INFO level has done it's job, then I should have a pretty good idea what process broke down. From there, I should be able to turn on DEBUG level and figure out what class the offending action took place. To ensure this, I make sure I log a DEBUG level message at every entry and exit point to every method in every class. On entry, I log the name of the method, the names of the formal parameters, and the values passed in for each of those parameters. On exit I again log the method name, and provide the name and value of each variable returned or state that the method returns nothing.
If even more detail is needed, TRACE level can be turned on for the offending class. Trace level logging should allow you to follow every logical step in the class, almost as if you are stepping through the code using a debugger. Keep in mind that most live applications you will not have the luxury of using a debugger, so the TRACE level is often the closest you can get to that level of feedback. Of course, this is also the most tedious and, in my experience, most often overlooked level of logging. In TRACE logging, try to at least include critical decision points in the method (all paths in conditional statements, pointers in loops, etc) to allow you to follow the path the application took towards its demise.
Back to your example, a DTO class is what I would consider pretty low-level code. You probably don't need any INFO level logging here, but DEBUG on the entry/exit points should be added along with TRACE logging for any key processing or decision making events in the DTO. Then, make sure you turn the logging level on the DTOs to INFO so that these events normally aren't logged.
A couple of other hints:
- Default logging to INFO level
- Make sure to use a rolling appender to log your output in multiple files rather than one huge file.
- Output, at minimum, TIMESTAMP, CLASS, THREAD, LINE NUMBER your log appenders
- When adding DEBUG messages logging variable values, keep in mind that the to_string() method of many classes is not overridden so you may not get useful output. Try using the ReflectionToStringBuilder.toString(Object object) static method.