diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e9d9959b..16d8f13e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
language_version: python3.8
- repo: https://github.com/PyCQA/isort
- rev: 5.9.2
+ rev: 5.9.3
hooks:
- id: isort
additional_dependencies: [toml]
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index bfcd2b18..6f5bd80b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -34,6 +34,9 @@ Changes:
This only works if ``format_exc_info`` is **absent** in the processor chain.
- ``structlog.threadlocal.get_threadlocal()`` and ``structlog.contextvars.get_threadlocal()`` can now be used to get a copy of the current thread-local/context-local context that has been bound using ``structlog.threadlocal.bind_threadlocal()`` and ``structlog.contextvars.bind_contextvars()``.
- ``structlog.threadlocal.get_merged_threadlocal(bl)`` and ``structlog.contextvars.get_merged_contextvars(bl)`` do the same, but also merge the context from a bound logger *bl*.
+- All use of ``colorama`` on non-Windows systems has been excised.
+ Thus, colors are now enabled by default in ``structlog.dev.ConsoleRenderer`` on non-Windows systems.
+ You can keep using ``colorama`` to customize colors, of course.
----
diff --git a/docs/development.rst b/docs/development.rst
index dcd73da8..4edfc8a0 100644
--- a/docs/development.rst
+++ b/docs/development.rst
@@ -3,7 +3,7 @@ Development
To make development a more pleasurable experience, ``structlog`` comes with the `structlog.dev` module.
-The highlight is `structlog.dev.ConsoleRenderer` that offers nicely aligned and colorful (requires the `colorama package `_ installed) console output.
+The highlight is `structlog.dev.ConsoleRenderer` that offers nicely aligned and colorful (requires the `colorama package `_ if on Windows) console output.
If the `better-exceptions `_ package is installed, it will also pretty-print exceptions with helpful contextual data.
.. figure:: _static/console_renderer.png
diff --git a/docs/getting-started.rst b/docs/getting-started.rst
index 0241f902..99cb6973 100644
--- a/docs/getting-started.rst
+++ b/docs/getting-started.rst
@@ -34,7 +34,8 @@ Here, ``structlog`` takes full advantage of its hopefully useful default setting
- All keywords are formatted using `structlog.dev.ConsoleRenderer`.
That in turn uses `repr` to serialize all values to strings.
Thus, it's easy to add support for logging of your own objects\ [*]_.
-- If you have `colorama `_ installed, it's rendered in nice `colors `.
+- On Windows, if you have `colorama `_ installed, it's rendered in nice `colors `.
+ Other OSes do not need colorama for nice colors.
- If you have `better-exceptions `_ installed, exceptions will be rendered in colors and with additional helpful information.
It should be noted that even in most complex logging setups the example would still look just like that thanks to `configuration`.
diff --git a/docs/thread-local.rst b/docs/thread-local.rst
index efe69934..3d97eeb1 100644
--- a/docs/thread-local.rst
+++ b/docs/thread-local.rst
@@ -1,4 +1,4 @@
-Thread Local-Context
+Thread-Local Context
====================
.. testsetup:: *
@@ -50,6 +50,8 @@ The general flow of using these functions is:
>>> from structlog.threadlocal import (
... bind_threadlocal,
... clear_threadlocal,
+ ... get_merged_threadlocal,
+ ... get_threadlocal,
... merge_threadlocal,
... )
>>> from structlog import configure
@@ -61,7 +63,7 @@ The general flow of using these functions is:
... )
>>> log = structlog.get_logger()
>>> # At the top of your request handler (or, ideally, some general
- >>> # middleware), clear the threadlocal context and bind some common
+ >>> # middleware), clear the thread-local context and bind some common
>>> # values:
>>> clear_threadlocal()
>>> bind_threadlocal(a=1)
@@ -69,7 +71,13 @@ The general flow of using these functions is:
>>> # (perhaps by using structlog.get_logger() to create them).
>>> log.msg("hi")
a=1 event='hi'
- >>> # And when we clear the threadlocal state again, it goes away.
+ >>> # You can access the current thread-local state.
+ >>> get_threadlocal()
+ {'a': 1}
+ >>> # Or get it merged with a bound logger.
+ >>> get_merged_threadlocal(log.bind(example=True))
+ {'a': 1, 'example': True}
+ >>> # And when we clear the thread-local state again, it goes away.
>>> clear_threadlocal()
>>> log.msg("hi there")
event='hi there'
diff --git a/src/structlog/_config.py b/src/structlog/_config.py
index a3e61a04..af3a1360 100644
--- a/src/structlog/_config.py
+++ b/src/structlog/_config.py
@@ -23,7 +23,7 @@
from ._log_levels import make_filtering_bound_logger
from ._loggers import PrintLoggerFactory
-from .dev import ConsoleRenderer, _has_colorama, set_exc_info
+from .dev import ConsoleRenderer, _use_colors, set_exc_info
from .processors import StackInfoRenderer, TimeStamper, add_log_level
from .types import BindableLogger, Context, Processor, WrappedLogger
@@ -39,7 +39,7 @@
set_exc_info,
TimeStamper(fmt="%Y-%m-%d %H:%M.%S", utc=False),
ConsoleRenderer(
- colors=_has_colorama and sys.stdout is not None and sys.stdout.isatty()
+ colors=_use_colors and sys.stdout is not None and sys.stdout.isatty()
),
]
_BUILTIN_DEFAULT_CONTEXT_CLASS = cast(Type[Context], dict)
diff --git a/src/structlog/dev.py b/src/structlog/dev.py
index 46d861f1..5cb7ca2f 100644
--- a/src/structlog/dev.py
+++ b/src/structlog/dev.py
@@ -47,8 +47,6 @@ def _pad(s: str, length: int) -> str:
if colorama is not None:
- _has_colorama = True
-
RESET_ALL = colorama.Style.RESET_ALL
BRIGHT = colorama.Style.BRIGHT
DIM = colorama.Style.DIM
@@ -60,11 +58,27 @@ def _pad(s: str, length: int) -> str:
GREEN = colorama.Fore.GREEN
RED_BACK = colorama.Back.RED
else:
- _has_colorama = False
-
- RESET_ALL = (
- BRIGHT
- ) = DIM = RED = BLUE = CYAN = MAGENTA = YELLOW = GREEN = RED_BACK = ""
+ # These are the same values as the colorama color codes. Redefining them
+ # here allows users to specify that they want color without having to
+ # install colorama, which is only supposed to be necessary in Windows.
+ RESET_ALL = "\033[0m"
+ BRIGHT = "\033[1m"
+ DIM = "\033[2m"
+ RED = "\033[31m"
+ BLUE = "\033[34m"
+ CYAN = "\033[36m"
+ MAGENTA = "\033[35m"
+ YELLOW = "\033[33m"
+ GREEN = "\033[32m"
+ RED_BACK = "\033[41m"
+
+
+if _IS_WINDOWS: # pragma: no cover
+ # On Windows, use colors by default only if colorama is installed.
+ _use_colors = colorama is not None
+else:
+ # On other OSes, use colors by default.
+ _use_colors = True
class _Styles(Protocol):
@@ -174,34 +188,39 @@ class ConsoleRenderer:
`structlog.processors.format_exc_info` processor together with
`ConsoleRenderer` anymore! It will keep working, but you can't have
pretty exceptions and a warning will be raised if you ask for them.
+ .. versionchanged:: 21.3 The colors keyword now defaults to True on
+ non-Windows systems, and either True or False in Windows depending on
+ whether colorama is installed.
"""
def __init__(
self,
pad_event: int = _EVENT_WIDTH,
- colors: bool = _has_colorama,
+ colors: bool = _use_colors,
force_colors: bool = False,
repr_native_str: bool = False,
level_styles: Optional[Styles] = None,
pretty_exceptions: bool = (better_exceptions is not None),
):
- self._force_colors = self._init_colorama = False
styles: Styles
- if colors is True:
- if colorama is None:
- raise SystemError(
- _MISSING.format(
- who=self.__class__.__name__ + " with `colors=True`",
- package="colorama",
- )
- )
-
+ if colors:
if _IS_WINDOWS: # pragma: no cover
- _init_colorama(self._force_colors)
- else:
- self._init_colorama = True
+ # On Windows, we can't do colorful output without colorama.
+ if colorama is None:
+ classname = self.__class__.__name__
+ raise SystemError(
+ _MISSING.format(
+ who=classname + " with `colors=True`",
+ package="colorama",
+ )
+ )
+ # Colorama must be init'd on Windows, but must NOT be
+ # init'd on other OSes, because it can break colors.
if force_colors:
- self._force_colors = True
+ colorama.deinit()
+ colorama.init(strip=False)
+ else:
+ colorama.init()
styles = _ColorfulStyles
else:
@@ -241,10 +260,6 @@ def __call__(
self, logger: WrappedLogger, name: str, event_dict: EventDict
) -> str:
- # Initialize lazily to prevent import side-effects.
- if self._init_colorama:
- _init_colorama(self._force_colors)
- self._init_colorama = False
sio = StringIO()
ts = event_dict.pop("timestamp", None)
@@ -366,14 +381,6 @@ def get_default_level_styles(colors: bool = True) -> Any:
}
-def _init_colorama(force: bool) -> None:
- if force:
- colorama.deinit()
- colorama.init(strip=False)
- else:
- colorama.init()
-
-
_SENTINEL = object()
diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py
index df86e5ba..0da98eae 100644
--- a/src/structlog/stdlib.py
+++ b/src/structlog/stdlib.py
@@ -367,13 +367,6 @@ class AsyncBoundLogger:
the processor chain (e.g. JSON serialization) and I/O won't block your
whole application.
- .. warning: Since the processor pipeline runs in a separate thread,
- `structlog.contextvars.merge_contextvars` does **nothing** and should
- be removed from you processor chain.
-
- Instead it's merged within **this logger** before handing off log
- processing to the thread.
-
Only available for Python 3.7 and later.
:ivar structlog.stdlib.BoundLogger sync_bl: The wrapped synchronous logger.
diff --git a/tests/test_dev.py b/tests/test_dev.py
index 837b374b..3eca486a 100644
--- a/tests/test_dev.py
+++ b/tests/test_dev.py
@@ -26,7 +26,7 @@ def test_negative(self):
@pytest.fixture(name="cr")
def _cr():
- return dev.ConsoleRenderer(colors=dev._has_colorama)
+ return dev.ConsoleRenderer(colors=dev._use_colors)
@pytest.fixture(name="styles")
@@ -47,11 +47,14 @@ def _unpadded(styles):
class TestConsoleRenderer:
- @pytest.mark.skipif(dev._has_colorama, reason="Colorama must be missing.")
+ @pytest.mark.skipif(dev.colorama, reason="Colorama must be missing.")
+ @pytest.mark.skipif(
+ not dev._IS_WINDOWS, reason="Must be running on Windows."
+ )
def test_missing_colorama(self):
"""
ConsoleRenderer(colors=True) raises SystemError on initialization if
- colorama is missing.
+ colorama is missing and _IS_WINDOWS is True.
"""
with pytest.raises(SystemError) as e:
dev.ConsoleRenderer(colors=True)
@@ -117,11 +120,11 @@ def test_init_accepts_overriding_levels(self, styles, padded):
Stdlib levels are rendered aligned, in brackets, and color coded.
"""
my_styles = dev.ConsoleRenderer.get_default_level_styles(
- colors=dev._has_colorama
+ colors=dev._use_colors
)
my_styles["MY_OH_MY"] = my_styles["critical"]
cr = dev.ConsoleRenderer(
- colors=dev._has_colorama, level_styles=my_styles
+ colors=dev._use_colors, level_styles=my_styles
)
# this would blow up if the level_styles override failed
@@ -234,7 +237,7 @@ def test_pad_event_param(self, styles):
"""
`pad_event` parameter works.
"""
- rv = dev.ConsoleRenderer(42, dev._has_colorama)(
+ rv = dev.ConsoleRenderer(42, dev._use_colors)(
None, None, {"event": "test", "foo": "bar"}
)
@@ -350,7 +353,7 @@ def test_colorama_force_colors(self, styles, padded):
If force_colors is True, use colors even if the destination is non-tty.
"""
cr = dev.ConsoleRenderer(
- colors=dev._has_colorama, force_colors=dev._has_colorama
+ colors=dev._use_colors, force_colors=dev._use_colors
)
rv = cr(
@@ -374,7 +377,7 @@ def test_colorama_force_colors(self, styles, padded):
+ styles.reset
) == rv
- assert not dev._has_colorama or dev._ColorfulStyles is cr._styles
+ assert not dev._use_colors or dev._ColorfulStyles is cr._styles
@pytest.mark.parametrize("rns", [True, False])
def test_repr_native_str(self, rns):