Interesting question. Without getting into low-level apache stuff, there's no perfect way to do this. The only way I can imagine is this: Write a script that uses header() to masquerade as the filetype of the file being downloaded. Then make the script pass the contents of the file to the browser. And once it's done doing so, perform your logging functions. Essentially, as far as the browser is concerned, the PHP script is the file. This will work, but there are at least two important caveats:
First, the filename of the downloaded file will be the filename of the PHP script, not the original file. There's a slick way around this, though: Instead of sending the user to, say /script.php?filename.zip, send them to /script.php/filename.zip .. now, without some server tweaking, this will probably be logged by your server as a 404, but (at least in Apache), script.php will still be processed and you can get the name of the requested file by parsing $_SERVER['REQUEST_URI'], and the browser will assume the name of the file being downloaded is filename.zip. Clever, no?
Second, it's unreliable. Depending on your server and PHP settings, the transfer might time out. Also, if they user is using any sort of download manager, all hell might break loose unless you're far cleverer at scripting than I. You could probably just eliminate download managers entirely by creating the script such that a single IP may only do one download at a time (download managers work by initiating several simultaneous downloads of different sections of the same file).
Well, if you get it working, I'd like to see it in action, especially if you manage to elude the above pitfalls. Good luck.