### Summary

If a module calls `fail_json()` and provides `exception=None`, this will crash every callback that uses `_handle_exception` from `CallbackBase`: if verbosity is < 3, it will try to call `split` on `None`, and otherwise it will try to concatenate a string with `None` (which results in `TypeError`).

### Issue Type

Bug Report

### Component Name

lib/ansible/plugins/callback/__init__.py

### Ansible Version

```console
devel
```


### Configuration

```console
*
```


### OS / Environment

*

### Steps to Reproduce

*

### Expected Results

*

### Actual Results

```console
*
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct
pass
    def playbook_on_play_start(self, name):
        pass
    def playbook_on_stats(self, stats):
        pass
    def on_file_diff(self, host, diff):
        pass
    
    def v2_on_any(self, *args, **kwargs):
        self.on_any(args, kwargs)
    def v2_runner_on_failed(self, result, ignore_errors=False):
        host = result._host.get_name()
        self.runner_on_failed(host, result._result, ignore_errors)
    def v2_runner_on_ok(self, result):
        host = result._host.get_name()
        self.runner_on_ok(host, result._result)
    def v2_runner_on_skipped(self, result):
        if C.DISPLAY_SKIPPED_HOSTS:
            host = result._host.get_name()
            self.runner_on_skipped(host, self._get_item_label(getattr(result._result, 'results', {})))
    def v2_runner_on_unreachable(self, result):
        host = result._host.get_name()
        self.runner_on_unreachable(host, result._result)
    def v2_runner_on_async_poll(self, result):
        host = result._host.get_name()
        jid = result._result.get('ansible_job_id')
        
        clock = 0
        self.runner_on_async_poll(host, result._result, jid, clock)
    def v2_runner_on_async_ok(self, result):