Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PrintLogger: switch to print #399

Merged
merged 2 commits into from Feb 11, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 9 additions & 14 deletions src/structlog/_loggers.py
Expand Up @@ -12,6 +12,7 @@
import threading

from pickle import PicklingError
from sys import stderr, stdout
hynek marked this conversation as resolved.
Show resolved Hide resolved
from typing import IO, Any, BinaryIO, Dict, Optional, TextIO

from structlog._utils import until_not_interrupted
Expand Down Expand Up @@ -49,20 +50,18 @@ class PrintLogger:
"""

def __init__(self, file: Optional[TextIO] = None):
self._file = file or sys.stdout
self._write = self._file.write
self._flush = self._file.flush
self._file = file or stdout

self._lock = _get_lock_for_file(self._file)

def __getstate__(self) -> str:
"""
Our __getattr__ magic makes this necessary.
"""
if self._file is sys.stdout:
if self._file is stdout:
return "stdout"

elif self._file is sys.stderr:
elif self._file is stderr:
return "stderr"

raise PicklingError(
Expand All @@ -74,28 +73,24 @@ def __setstate__(self, state: Any) -> None:
Our __getattr__ magic makes this necessary.
"""
if state == "stdout":
self._file = sys.stdout
self._file = stdout
else:
self._file = sys.stderr
self._file = stderr

self._write = self._file.write
self._flush = self._file.flush
self._lock = _get_lock_for_file(self._file)

def __deepcopy__(self, memodict: Dict[Any, Any] = {}) -> "PrintLogger":
"""
Create a new PrintLogger with the same attributes. Similar to pickling.
"""
if self._file not in (sys.stdout, sys.stderr):
if self._file not in (stdout, stderr):
raise copy.error(
"Only PrintLoggers to sys.stdout and sys.stderr "
"can be deepcopied."
)

newself = self.__class__(self._file)

newself._write = newself._file.write
newself._flush = newself._file.flush
newself._lock = _get_lock_for_file(newself._file)

return newself
Expand All @@ -107,9 +102,9 @@ def msg(self, message: str) -> None:
"""
Print *message*.
"""
f = self._file if self._file is not stdout else None
with self._lock:
until_not_interrupted(self._write, message + "\n")
until_not_interrupted(self._flush)
until_not_interrupted(print, message, file=f, flush=True)

log = debug = info = warn = warning = msg
fatal = failure = err = error = critical = exception = msg
Expand Down
16 changes: 15 additions & 1 deletion tests/test_loggers.py
Expand Up @@ -7,7 +7,7 @@
import pickle
import sys

from io import BytesIO
from io import BytesIO, StringIO

import pytest

Expand Down Expand Up @@ -125,6 +125,20 @@ def test_deepcopy_no_stdout(self, tmp_path):

assert "hello\n" == p.read_text()

def test_stdout_monkeypatch(self, monkeypatch, capsys):
"""
If stdout gets monkeypatched, the new instance receives the output.
"""
p = PrintLogger()
new_stdout = StringIO()
monkeypatch.setattr(sys, "stdout", new_stdout)
p.msg("hello")

out, err = capsys.readouterr()
assert "hello\n" == new_stdout.getvalue()
assert "" == out
assert "" == err


class TestPrintLoggerFactory:
def test_does_not_cache(self):
Expand Down