Source code for structlog._output

# SPDX-License-Identifier: MIT OR Apache-2.0
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the MIT License.  See the LICENSE file in the root of this
# repository for complete details.

"""
Logger classes responsible for output.
"""

from __future__ import annotations

import copy
import sys
import threading

from pickle import PicklingError
from sys import stderr, stdout
from typing import IO, Any, BinaryIO, TextIO

from structlog._utils import until_not_interrupted


WRITE_LOCKS: dict[IO[Any], threading.Lock] = {}


def _get_lock_for_file(file: IO[Any]) -> threading.Lock:
    lock = WRITE_LOCKS.get(file)
    if lock is None:
        lock = threading.Lock()
        WRITE_LOCKS[file] = lock

    return lock


[docs] class PrintLogger: """ Print events into a file. Args: file: File to print to. (default: `sys.stdout`) >>> from structlog import PrintLogger >>> PrintLogger().info("hello") hello Useful if you follow `current logging best practices <logging-best-practices>`. Also very useful for testing and examples since `logging` is finicky in doctests. .. versionchanged:: 22.1.0 The implementation has been switched to use `print` for better monkeypatchability. """ def __init__(self, file: TextIO | None = None): 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 stdout: return "stdout" if self._file is stderr: return "stderr" raise PicklingError( "Only PrintLoggers to sys.stdout and sys.stderr can be pickled." ) def __setstate__(self, state: Any) -> None: """ Our __getattr__ magic makes this necessary. """ if state == "stdout": self._file = stdout else: self._file = stderr self._lock = _get_lock_for_file(self._file) def __deepcopy__(self, memodict: dict[str, object]) -> PrintLogger: """ Create a new PrintLogger with the same attributes. Similar to pickling. """ 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._lock = _get_lock_for_file(newself._file) return newself def __repr__(self) -> str: return f"<PrintLogger(file={self._file!r})>"
[docs] 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(print, message, file=f, flush=True)
log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg
[docs] class PrintLoggerFactory: r""" Produce `PrintLogger`\ s. To be used with `structlog.configure`\ 's ``logger_factory``. Args: file: File to print to. (default: `sys.stdout`) Positional arguments are silently ignored. .. versionadded:: 0.4.0 """ def __init__(self, file: TextIO | None = None): self._file = file def __call__(self, *args: Any) -> PrintLogger: return PrintLogger(self._file)
[docs] class WriteLogger: """ Write events into a file. Args: file: File to print to. (default: `sys.stdout`) >>> from structlog import WriteLogger >>> WriteLogger().info("hello") hello Useful if you follow `current logging best practices <logging-best-practices>`. Also very useful for testing and examples since `logging` is finicky in doctests. A little faster and a little less versatile than `structlog.PrintLogger`. .. versionadded:: 22.1.0 """ def __init__(self, file: TextIO | None = None): self._file = file or sys.stdout self._write = self._file.write self._flush = self._file.flush self._lock = _get_lock_for_file(self._file) def __getstate__(self) -> str: """ Our __getattr__ magic makes this necessary. """ if self._file is stdout: return "stdout" if self._file is stderr: return "stderr" raise PicklingError( "Only WriteLoggers to sys.stdout and sys.stderr can be pickled." ) def __setstate__(self, state: Any) -> None: """ Our __getattr__ magic makes this necessary. """ if state == "stdout": self._file = stdout else: self._file = stderr self._lock = _get_lock_for_file(self._file) def __deepcopy__(self, memodict: dict[str, object]) -> WriteLogger: """ Create a new WriteLogger with the same attributes. Similar to pickling. """ if self._file not in (sys.stdout, sys.stderr): raise copy.error( "Only WriteLoggers 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 def __repr__(self) -> str: return f"<WriteLogger(file={self._file!r})>"
[docs] def msg(self, message: str) -> None: """ Write and flush *message*. """ with self._lock: until_not_interrupted(self._write, message + "\n") until_not_interrupted(self._flush)
log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg
[docs] class WriteLoggerFactory: r""" Produce `WriteLogger`\ s. To be used with `structlog.configure`\ 's ``logger_factory``. Args: file: File to print to. (default: `sys.stdout`) Positional arguments are silently ignored. .. versionadded:: 22.1.0 """ def __init__(self, file: TextIO | None = None): self._file = file def __call__(self, *args: Any) -> WriteLogger: return WriteLogger(self._file)
[docs] class BytesLogger: r""" Writes bytes into a file. Args: file: File to print to. (default: `sys.stdout`\ ``.buffer``) Useful if you follow `current logging best practices <logging-best-practices>` together with a formatter that returns bytes (e.g. `orjson <https://github.com/ijl/orjson>`_). .. versionadded:: 20.2.0 """ __slots__ = ("_file", "_write", "_flush", "_lock") def __init__(self, file: BinaryIO | None = None): self._file = file or sys.stdout.buffer self._write = self._file.write self._flush = self._file.flush self._lock = _get_lock_for_file(self._file) def __getstate__(self) -> str: """ Our __getattr__ magic makes this necessary. """ if self._file is sys.stdout.buffer: return "stdout" if self._file is sys.stderr.buffer: return "stderr" raise PicklingError( "Only BytesLoggers to sys.stdout and sys.stderr can be pickled." ) def __setstate__(self, state: Any) -> None: """ Our __getattr__ magic makes this necessary. """ if state == "stdout": self._file = sys.stdout.buffer else: self._file = sys.stderr.buffer self._write = self._file.write self._flush = self._file.flush self._lock = _get_lock_for_file(self._file) def __deepcopy__(self, memodict: dict[str, object]) -> BytesLogger: """ Create a new BytesLogger with the same attributes. Similar to pickling. """ if self._file not in (sys.stdout.buffer, sys.stderr.buffer): raise copy.error( "Only BytesLoggers 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 def __repr__(self) -> str: return f"<BytesLogger(file={self._file!r})>"
[docs] def msg(self, message: bytes) -> None: """ Write *message*. """ with self._lock: until_not_interrupted(self._write, message + b"\n") until_not_interrupted(self._flush)
log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg
[docs] class BytesLoggerFactory: r""" Produce `BytesLogger`\ s. To be used with `structlog.configure`\ 's ``logger_factory``. Args: file: File to print to. (default: `sys.stdout`\ ``.buffer``) Positional arguments are silently ignored. .. versionadded:: 20.2.0 """ __slots__ = ("_file",) def __init__(self, file: BinaryIO | None = None): self._file = file def __call__(self, *args: Any) -> BytesLogger: return BytesLogger(self._file)