Repository: iterative/dvc

Issue:
Remove / silence special codes that colorize output
At some point DVC has started spitting colorization codes even on terminals that are not prepared for this, logs, etc. It leads to situations like this:

<img width="347" alt="Screenshot 2023-02-05 at 10 44 26 AM" src="https://user-images.githubusercontent.com/3659196/216838615-73e488ab-551d-44ed-87bc-5f171e810819.png">

and this:

https://github.com/iterative/dvc/issues/8956

I suggest we disable it completely for now if the proper fix takes >1day.

DVC version:

```
DVC version: 2.43.2 (pip)
-------------------------
Platform: Python 3.9.12 on macOS-13.2-arm64-arm-64bit
Subprojects:
        dvc_data = 0.37.3
        dvc_objects = 0.19.1
        dvc_render = 0.1.0
        dvc_task = 0.1.11
        dvclive = 2.0.1
        scmrepo = 0.1.7
Supports:
        azure (adlfs = 2022.9.1, knack = 0.9.0, azure-identity = 1.10.0),
        gdrive (pydrive2 = 1.14.0),
        gs (gcsfs = 2022.8.2),
        hdfs (fsspec = 2022.11.0, pyarrow = 9.0.0),
        http (aiohttp = 3.8.1, aiohttp-retry = 2.7.0),
        https (aiohttp = 3.8.1, aiohttp-retry = 2.7.0),
        oss (ossfs = 2021.8.0),
        s3 (s3fs = 2022.5.0, boto3 = 1.21.21),
        ssh (sshfs = 2022.6.0),
        webdav (webdav4 = 0.9.7),
        webdavs (webdav4 = 0.9.7),
        webhdfs (fsspec = 2022.11.0)
Cache types: reflink, hardlink, symlink
Cache directory: apfs on /dev/disk3s1s1
Caches: local
Remotes: local
Workspace directory: apfs on /dev/disk3s1s1
Repo: dvc, git
```


Discussion:
I think this was always the case, logging code has not changed much for 3 years now.
@shcheklein, do you know why vscode is reading stderr output, and interpreting it?
Yes, there are no other ways to get a message to show users if something goes wrong afaik. Also, this is not about VS Code alone I think.
This requires us to figure out a way to pass `isatty` information to `ColorFormatter`, which only `LoggerHandler` has. We instantiate all of this through `dictConfig`, so need to figure out how to do that using it or get rid of it.

https://github.com/iterative/dvc/blob/8e1b75feb5033735e6588d745fc7e00be3b3919a/dvc/logger.py#L219
We can however easily disable color completely if stdout is not a tty. But disabling colors per-stream based on if stdout or stderr is redirected does not seem straightforward.
I think we should also add support for `NO_COLOR` and `DVC_NO_COLOR` environment variables.
This is a regression due to https://github.com/iterative/dvc/pull/8395.
Looking at it, I blame `colorama` on how it wraps stderr/stdout. This causes some issues with abseil-logging that tensorflow uses, because it tries to close streams other than `sys.stderr`/`sys.stdout`, which gets blocked in `ipykernel`.

So, we should gradually try to stop depending on `colorama.init()` for stripping ansi codes if not a tty, and use new API for Windows support:
```python
from colorama import just_fix_windows_console

just_fix_windows_console()
```

And, eventually just stop using `colorama` altogether and use `rich`. I haven't been able to make a decision on rich, because it's slow but seems like we should bite the bullet and start using rich. 

Resolving patch:
diff --git a/dvc/logger.py b/dvc/logger.py
--- a/dvc/logger.py
+++ b/dvc/logger.py
@@ -203,7 +203,22 @@ def set_loggers_level(level: int = logging.INFO) -> None:
         logging.getLogger(name).setLevel(level)
 
 
+def _is_jupyter() -> bool:
+    try:
+        ipython = get_ipython()  # type: ignore[name-defined]
+    except (NameError, TypeError):
+        return False
+
+    shell = ipython.__class__.__name__
+    return shell == "ZMQInteractiveShell" or "google.colab" in str(ipython.__class__)
+
+
 def setup(level: int = logging.INFO) -> None:
+    is_jupyter = _is_jupyter()
+    if not is_jupyter:
+        # initialize colorama, so that tty detection works if we are not inside jupyter
+        colorama.init()
+
     if level >= logging.DEBUG:
         # Unclosed session errors for asyncio/aiohttp are only available
         # on the tracing mode for extensive debug purposes. They are really
@@ -287,7 +302,8 @@ def setup(level: int = logging.INFO) -> None:
         }
     )
 
-    # NOTE: colorama init needs to go after logging config to avoid potential
-    # conflicts with libs that modify logging (like tensorflow/abseil)
-    # https://github.com/iterative/dvc/issues/8387
-    colorama.init()
+    if is_jupyter:
+        # NOTE: colorama init needs to go after logging config to avoid potential
+        # conflicts with libs that modify logging (like tensorflow/abseil)
+        # https://github.com/iterative/dvc/issues/8387
+        colorama.init()


Test patch: