I used work writing software for reference boards. We had no debugger facilities for these boards (employer too cheap to buy JTAG HW) only writing logs out through serial ports. Logging as a debugging scheme is extremely useful but you need to know what to log and when. I've found the following quite useful:
Make the difference between 'got here' logs and error logs very obvious. I used prefix error logs with 3 asterixes, i.e. "*** Call to foo(ptr=0x00000000, value=6) failed with -2 ***", so I could search the log file quickly for errors in a text editor (they'd be several megabytes in size).
Don't just record that something went wrong, record what went wrong, preferably in the function where the exception is thrown or, if you don't have access to the code, as close as possible to that point.
Don't record something for the sake of it; only if it'll be of use to you. I used encounter situations where the entire call stack would continually be logged. It sounds useful but all it did was pollute logs and when you're dealing with asynchronous behaviour it turns out not to be all that useful. Excessive logging can even change the runtime behaviour and hide multi-threaded issues that only appear when you use Release builds. If you do go down the route of logging the execution trace you may as well log the parameter values passed to functions where possible.
As for multiple logs entires for the same error... no big deal. The first one is usually the culprit and as long as you can find it, and have logs that provide enough information for you to quickly reproduce/isolate the error, the rest should only be confirmation that your program doesn't fall over itself when an error does occur.