richのprettyな出力をstrにしてloggingに渡す方法(達成感なし)

## TL;DR

rich(<https://rich.readthedocs.io/en/stable/index.html>)で出力されたstack traceが見やすいので、雑にdebugログをlogging.FileHandler等で吐いたときにログでも見れるようにしたい。このため、prettyなTracebackをstrで取得したい。
調べてはみたが、Traceback含めprettyな出力をstrで返してくれる関数がみあたらなかったので試行錯誤してみたがいい方法が見つからず、結局`io.StringIO`に頼ったよ。

## モチベーション

ほぼ上述の通りだが、端的にいうと、loggingのRotatingFileHandlerでファイルにdebug logをダダ流しできると不具合調査が楽だよな、と。
richはコンソールアプリでの出力をかなりきれいにしてくれる。ただ、サクッと書いたツール等を人に使ってもらうとき、「不具合が出たらこのログ送って」で済ませたく、色はつかなくていいからなんとかloggingのFileHandlerで出力できないかなと思った。

## コード

結局いい方法が見つからず、io.StringIO()を`rich.print()`の`file`引数に渡して出力をstrで取得することで期待した動作にはなった。
以下サンプルコード。

- Python 3.11.4
- rich==13.7.0

```python
import traceback
import rich
import rich.traceback


def get_logger(name="main"):
    import logging
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler()
    formatter = logging.Formatter(fmt='%(asctime)-15s [%(name)s] %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    return logger

def main():
    logger = get_logger()
    try:
        hoge=1/0
    except Exception as e:
        e_args={"exc_type":type(e), "exc_value":e, "traceback":e.__traceback__, "show_locals":True}
        tb = rich.traceback.Traceback.from_exception(**e_args)
        import io
        with io.StringIO() as s:
            rich.print(tb, file=s)
            c = "\n" + s.getvalue()
            logger.debug(c)


if __name__ == "__main__":
    main()
```

結果は以下。
これができるならFileHandler系に出力できるでしょう。

```text
2024-01-05 04:11:58,060 [main]
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/bounoki/work/20231229_rich/test1.py:19 in main                                             │
│                                                                                                  │
│   16 def main():                                                                                 │
│   17 │   logger = get_logger()                                                                   │
│   18 │   try:                                                                                    │
│ ❱ 19 │   │   hoge=1/0                                                                            │
│   20 │   except Exception as e:                                                                  │
│   21 │   │   e_args={"exc_type":type(e), "exc_value":e, "traceback":e.__traceback__, "show_lo    │
│   22 │   │   tb = rich.traceback.Traceback.from_exception(**e_args)                              │
│                                                                                                  │
│ ╭───────────────────────────── locals ─────────────────────────────╮                             │
│ │      e = ZeroDivisionError('division by zero')                   │                             │
│ │ e_args = {                                                       │                             │
│ │          │   'exc_type': <class 'ZeroDivisionError'>,            │                             │
│ │          │   'exc_value': ZeroDivisionError('division by zero'), │                             │
│ │          │   'traceback': <traceback object at 0x7f0c7d28b040>,  │                             │
│ │          │   'show_locals': True                                 │                             │
│ │          }                                                       │                             │
│ │ logger = <Logger main (DEBUG)>                                   │                             │
│ ╰──────────────────────────────────────────────────────────────────╯                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ZeroDivisionError: division by zero

```

サンプルはStreamHandlerでやってるけど、StreamHandlerで処理したいならrichに積まれてる`rich.logging.RichHandler`を使えばいいと思う。