Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ jobs:
# Exercise the terminal initialization that we don't test otherwise.
- run: uv pip install colorama
if: runner.os == 'Windows'
- run: .\.venv\Scripts\python.exe -Ic 'import structlog; structlog.get_logger().info("test")'
- run: .\.venv\Scripts\python.exe -Ic 'import structlog; log = structlog.get_logger(); log.info("colors"); structlog.dev.ConsoleRenderer.get_active().colors = False; log.info("bw")'
if: runner.os == 'Windows'

required-checks-pass:
Expand Down
5 changes: 2 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
```
[#749](https://github.com/hynek/structlog/pull/749)

- Same for *sort_keys*.
- Same for *sort_keys*, *columns*, *colors*, *force_colors*, and *level_styles*.
[#756](https://github.com/hynek/structlog/pull/756)

- Same for *columns*.
[#757](https://github.com/hynek/structlog/pull/757)
[#759](https://github.com/hynek/structlog/pull/759)

- Added `structlog.dev.ConsoleRenderer.get_default_column_styles` for reuse the default column styles.
[#741](https://github.com/hynek/structlog/pull/741)
Expand Down
18 changes: 15 additions & 3 deletions docs/console-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,30 @@ strict about immutability than the rest of *structlog* for better
ergonomics.
Notably, the currently active instance can be obtained by calling {meth}`ConsoleRenderer.get_active() <structlog.dev.ConsoleRenderer.get_active>` and it offers properties to configure its behavior after instantiation.

Roughly speaking, there are two ways to configure the console output.

:::{important}
Roughly speaking, there are two ways to configure console output.

1. Using our defaults and tweak some settings.
2. Explicit columns configuration.

You should pick one and not mix the two.
For example, if you pass *columns* to {class}`~structlog.dev.ConsoleRenderer`, all other output-related parameters are stored but otherwise ignored.
But if you set one of the non-*columns* properties, the columns will be reconfigured accordingly and *your* columns settings will be lost.
:::


### Defaults plus tweaking

The easier way where you're mostly using our defaults and just tweak a few things here and there by passing arguments or setting properties (as of 25.5.0).

For example, you can easily change the colors of the the log levels by passing the *level_styles* parameter or switch *colors* on and off completely.
For example, you can easily change the colors of the the log levels by passing the *level_styles*[^styles] parameter or switch *colors* on and off completely.

When the API talks about "styles", it means ANSI control strings.
[^styles]: When the API talks about "styles", it means ANSI control strings.
You can find them, for example, in [Colorama](https://github.com/tartley/colorama).

You can find the relevant arguments and properties in the {class}`~structlog.dev.ConsoleRenderer` documentation.


### Explicit columns configuration

Expand Down
4 changes: 4 additions & 0 deletions show_off.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class SomeClass:
log.warning("uh-uh!")
log.error("omg", a_dict={"a": 42, "b": "foo"})
log.critical("wtf", what=SomeClass(x=1, y="z"))
structlog.dev.ConsoleRenderer.get_active().colors = False
log.info("where are the colors!?", colors="gone")
structlog.dev.ConsoleRenderer.get_active().colors = True
log.info("there they are!", colors="back")


log2 = structlog.get_logger("another_logger")
Expand Down
156 changes: 116 additions & 40 deletions src/structlog/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,60 +642,60 @@ def __init__(
columns: list[Column] | None = None,
pad_level: bool = True,
):
# Store all settings in case the user later switches from columns to
# defaults.
self.exception_formatter = exception_formatter
self._sort_keys = sort_keys
self._repr_native_str = repr_native_str
self._styles = self.get_default_column_styles(colors, force_colors)
self._colors = colors
self._force_colors = force_colors
self._level_styles = (
self.get_default_level_styles(colors)
if level_styles is None
else level_styles
)
self._pad_event = pad_event
self._timestamp_key = timestamp_key
self._event_key = event_key
self._pad_level = pad_level

if columns is not None:
to_warn = []

def add_meaningless_arg(arg: str) -> None:
to_warn.append(
f"The `{arg}` argument is ignored when passing `columns`.",
)
if columns is None:
self._configure_columns()
return

if pad_event != _EVENT_WIDTH:
add_meaningless_arg("pad_event")
self.columns = columns

if colors != _has_colors:
add_meaningless_arg("colors")
to_warn = []

if force_colors is not False:
add_meaningless_arg("force_colors")
def add_meaningless_arg(arg: str) -> None:
to_warn.append(
f"The `{arg}` argument is ignored when passing `columns`.",
)

if repr_native_str is not False:
add_meaningless_arg("repr_native_str")
if pad_event != _EVENT_WIDTH:
add_meaningless_arg("pad_event")

if level_styles is not None:
add_meaningless_arg("level_styles")
if colors != _has_colors:
add_meaningless_arg("colors")

if event_key != "event":
add_meaningless_arg("event_key")
if force_colors is not False:
add_meaningless_arg("force_colors")

if timestamp_key != "timestamp":
add_meaningless_arg("timestamp_key")
if repr_native_str is not False:
add_meaningless_arg("repr_native_str")

for w in to_warn:
warnings.warn(w, stacklevel=2)
if level_styles is not None:
add_meaningless_arg("level_styles")

self.columns = columns
if event_key != "event":
add_meaningless_arg("event_key")

return
if timestamp_key != "timestamp":
add_meaningless_arg("timestamp_key")

styles = self.get_default_column_styles(colors, force_colors)

self._repr_native_str = repr_native_str
self._styles = styles
self._level_styles = (
self.get_default_level_styles(colors)
if level_styles is None
else level_styles
)
self._pad_event = pad_event
self._timestamp_key = timestamp_key
self._event_key = event_key
self._pad_level = pad_level

self._configure_columns()
for w in to_warn:
warnings.warn(w, stacklevel=2)

@classmethod
def get_default_column_styles(
Expand Down Expand Up @@ -913,6 +913,7 @@ def sort_keys(self, value: bool) -> None:
"""
.. versionadded:: 25.5.0
"""
# _sort_keys is a format-time setting, so we can just set it directly.
self._sort_keys = value

@property
Expand Down Expand Up @@ -955,6 +956,56 @@ def columns(self, value: list[Column]) -> None:
self._default_column_formatter = defaults[0].formatter
self._columns = [col for col in value if col.key]

@property
def colors(self) -> bool:
"""
Whether to use colorful output styles.

Setting this will update the renderer's styles immediately and reset
level styles to the defaults according to the new color setting -- even
if the color value is the same.

.. versionadded:: 25.5.0
"""
return self._colors

@colors.setter
def colors(self, value: bool) -> None:
"""
.. versionadded:: 25.5.0
"""
self._colors = value
self._styles = self.get_default_column_styles(
value, self._force_colors
)
self._level_styles = self.get_default_level_styles(value)

self._configure_columns()

@property
def force_colors(self) -> bool:
"""
Force colorful output even in non-interactive environments.

Setting this will update the renderer's styles immediately and reset
level styles to the defaults according to the current color setting --
even if the value is the same.

.. versionadded:: 25.5.0
"""
return self._force_colors

@force_colors.setter
def force_colors(self, value: bool) -> None:
"""
.. versionadded:: 25.5.0
"""
self._force_colors = value
self._styles = self.get_default_column_styles(self._colors, value)
self._level_styles = self.get_default_level_styles(self._colors)

self._configure_columns()

@classmethod
def get_active(cls) -> ConsoleRenderer:
"""
Expand Down Expand Up @@ -987,6 +1038,31 @@ def get_active(cls) -> ConsoleRenderer:

return cr

@property
def level_styles(self) -> dict[str, str]:
"""
The level styles mapping for this console renderer.

Setting this property will reset to defaults if set to None, otherwise
it applies the provided mapping. In all cases, columns are rebuilt to
reflect the change.

.. versionadded:: 25.5.0
"""
return self._level_styles

@level_styles.setter
def level_styles(self, value: dict[str, str] | None) -> None:
"""
.. versionadded:: 25.5.0
"""
self._level_styles = (
self.get_default_level_styles(self._colors)
if value is None
else value
)
self._configure_columns()


_SENTINEL = object()

Expand Down
Loading
Loading