> almost always just maps to logging anyway

Yes, that’s the point. You log it until you encounter it for the first time, then you know more and can do something meaningful. E.g. let’s say you build an API client and library offers callback for HTTP 429. You don’t expect it to happen, so just log the errors in a generic handler in client code, but then after some business logic change you hit 429 for the first time. If library offers you control over what is going to happen next, you may decide how exactly you will retry and what happens to your state in between the attempts. If library just logs and starts retry cycle, you may get a performance hit that will be harder to fix.

Defining a callback for every situation where a library might encounter an unexpected condition and pointing them all at the logs seems like a massive waste of time.

I would much prefer a library have sane defaults, reasonable logging, and a way for me to plug in callbacks where needed. Writing On429 and a hundred other functions that just point to Logger.Log is not a good use of time.

This sub-thread in my understanding is about a special case (a non-error mode that client may want to avoid, in which case explicit callback makes sense), not about all possible unexpected errors. I’m not suggesting hooks as the best approach. And of course “on429” is the last thing I would think about when designing this. There are better ways.

If the statement is just that sometimes it’s appropriate to have callbacks, absolutely. A library that only logs in places where it really needs a callback is poorly designed.

I still don’t want to have to provide a 429 callback just to log, though. The library should log by default if the callback isn’t registered.

It doesn’t have to provide a specific callback. This can be a starting point (Java):

  var client = aClient()
   .onError((request,response) -> { 
      LOG.debug(…); 
      return FAIL; 
   }).build();
And eventually you do this:

   return switch(response.code()) {
      case 429 -> RETRY;
      default -> FAIL;
   }
Or something more interesting, e.g. with more details of retry strategy.