Skip to content

Commit

Permalink
Merge pull request #2012 from pallets/server-log-color
Browse files Browse the repository at this point in the history
fix colors in log on Windows
  • Loading branch information
davidism committed Jan 19, 2021
2 parents 96f3317 + 8b0fdb3 commit 68b7cbd
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 16 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -124,6 +124,9 @@ Unreleased
addresses, instead of ``0.0.0.0`` or ``::``. It also warns about not
running the development server in production in this case.
:issue:`1964`
- Colors in the development server log are displayed if Colorama is
installed on Windows. For all platforms, style support no longer
requires Click. :issue:`1832`


Version 1.0.2
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Expand Up @@ -94,6 +94,9 @@ warn_redundant_casts = True
warn_unused_configs = True
warn_unused_ignores = True

[mypy-colorama.*]
ignore_missing_imports = True

[mypy-cryptography.*]
ignore_missing_imports = True

Expand Down
16 changes: 15 additions & 1 deletion src/werkzeug/_internal.py
Expand Up @@ -183,6 +183,20 @@ def _has_level_handler(logger: logging.Logger) -> bool:
return False


class _ColorStreamHandler(logging.StreamHandler):
"""On Windows, wrap stream with Colorama for ANSI style support."""

def __init__(self):
try:
import colorama
except ImportError:
stream = None
else:
stream = colorama.AnsiToWin32(sys.stderr)

super().__init__(stream)


def _log(type: str, message: str, *args, **kwargs) -> None:
"""Log a message to the 'werkzeug' logger.
Expand All @@ -200,7 +214,7 @@ def _log(type: str, message: str, *args, **kwargs) -> None:
_logger.setLevel(logging.INFO)

if not _has_level_handler(_logger):
_logger.addHandler(logging.StreamHandler())
_logger.addHandler(_ColorStreamHandler())

getattr(_logger, type)(message.rstrip(), *args, **kwargs)

Expand Down
47 changes: 32 additions & 15 deletions src/werkzeug/serving.py
Expand Up @@ -42,10 +42,13 @@ def __getattr__(self, name):

ssl = _SslDummy() # type: ignore

try:
import click
except ImportError:
click = None # type: ignore
_log_add_style = True

if os.name == "nt":
try:
__import__("colorama")
except ImportError:
_log_add_style = False

can_fork = hasattr(os, "fork")

Expand Down Expand Up @@ -392,23 +395,21 @@ def log_request(self, code: t.Union[int, str] = "-", size: t.Union[int, str] = "

code = str(code)

if click:
color = click.style

if _log_add_style:
if code[0] == "1": # 1xx - Informational
msg = color(msg, bold=True)
elif code[0] == "2": # 2xx - Success
msg = color(msg)
msg = _ansi_style(msg, "bold")
elif code == "200": # 2xx - Success
pass
elif code == "304": # 304 - Resource Not Modified
msg = color(msg, fg="cyan")
msg = _ansi_style(msg, "cyan")
elif code[0] == "3": # 3xx - Redirection
msg = color(msg, fg="green")
msg = _ansi_style(msg, "green")
elif code == "404": # 404 - Resource Not Found
msg = color(msg, fg="yellow")
msg = _ansi_style(msg, "yellow")
elif code[0] == "4": # 4xx - Client Error
msg = color(msg, fg="red", bold=True)
msg = _ansi_style(msg, "bold", "red")
else: # 5xx, or any other response
msg = color(msg, fg="magenta", bold=True)
msg = _ansi_style(msg, "bold", "magenta")

self.log("info", '"%s" %s %s', msg, code, size)

Expand All @@ -426,6 +427,22 @@ def log(self, type: str, message: str, *args) -> None:
)


def _ansi_style(value, *styles):
codes = {
"bold": 1,
"red": 31,
"green": 32,
"yellow": 33,
"magenta": 35,
"cyan": 36,
}

for style in styles:
value = f"\x1b[{codes[style]}m{value}"

return f"{value}\x1b[0m"


def generate_adhoc_ssl_pair(
cn: t.Optional[str] = None,
) -> t.Tuple["Certificate", "RSAPrivateKeyWithSerialization"]:
Expand Down

0 comments on commit 68b7cbd

Please sign in to comment.