Ghi dữ liệu biến với chuỗi định dạng mới


85

Tôi sử dụng cơ sở ghi nhật ký cho python 2.7.3. Tài liệu cho phiên bản Python này nói :

gói ghi nhật ký trước các tùy chọn định dạng mới hơn như str.format () và string.Template. Các tùy chọn định dạng mới hơn này được hỗ trợ ...

Tôi thích định dạng 'mới' với dấu ngoặc nhọn. Vì vậy, tôi đang cố gắng làm điều gì đó như:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

Và gặp lỗi:

TypeError: không phải tất cả các đối số được chuyển đổi trong quá trình định dạng chuỗi

Tôi nhớ gì ở đây?

Tái bút tôi không muốn sử dụng

log.debug("format this message {0}".format(1))

bởi vì trong trường hợp này thông báo luôn được định dạng bất kể cấp trình ghi.


1
Bạn có thể làm điều này: log.debug("format this message%d" % 1)
ronak 30/10/12

1
bạn cần phải cấu hình Formatterđể sử dụng '{' như phong cách
mata

2
@ronak Cảm ơn vì lời khuyên nhưng không. Vui lòng xem phần "ps" tại sao. BTW log.debug ("định dạng tin nhắn này% d", 1) - hoạt động tốt.
MajesticRa

@mata Làm thế nào để định cấu hình nó? Có tài liệu trực tiếp về việc đó không?
MajesticRa

@mata Tôi đã tìm thấy nó. Hãy làm cho nó một câu trả lời vì vậy tôi có thể thiết lập nó như là "câu trả lời đúng Cảm ơn bạn một lần nữa..
MajesticRa

Câu trả lời:


38

CHỈNH SỬA: hãy xem StyleAdaptercách tiếp cận trong câu trả lời của @Dunes không giống như câu trả lời này; nó cho phép sử dụng các kiểu định dạng thay thế mà không có bảng soạn sẵn trong khi gọi các phương thức của trình ghi (debug (), info (), error (), v.v.).


Từ tài liệu - Sử dụng các kiểu định dạng thay thế :

Lệnh gọi ghi nhật ký (logger.debug (), logger.info (), v.v.) chỉ nhận các tham số vị trí cho chính thông báo ghi nhật ký thực tế, với các tham số từ khóa chỉ được sử dụng để xác định các tùy chọn về cách xử lý lệnh gọi ghi nhật ký thực tế (ví dụ: tham số từ khóa exc_info để chỉ ra rằng thông tin theo dõi phải được ghi lại hoặc tham số từ khóa bổ sung để chỉ ra thông tin ngữ cảnh bổ sung được thêm vào nhật ký). Vì vậy, bạn không thể trực tiếp thực hiện các cuộc gọi ghi nhật ký bằng cú pháp str.format () hoặc string.Template, vì bên trong gói ghi nhật ký sử dụng% -formatting để hợp nhất chuỗi định dạng và các đối số biến. Sẽ không thay đổi điều này trong khi vẫn duy trì khả năng tương thích ngược, vì tất cả các lệnh gọi ghi nhật ký có trong mã hiện có sẽ sử dụng chuỗi% -format.

Và:

Tuy nhiên, có một cách mà bạn có thể sử dụng định dạng {} - và $ - để tạo thông báo nhật ký riêng lẻ của mình. Nhớ lại rằng đối với một thông báo, bạn có thể sử dụng một đối tượng tùy ý làm chuỗi định dạng thông báo và gói ghi nhật ký sẽ gọi str () trên đối tượng đó để lấy chuỗi định dạng thực tế.

Sao chép-dán cái này vào wherevermô-đun:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

Sau đó:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

Lưu ý: việc định dạng thực tế bị trì hoãn cho đến khi cần thiết, ví dụ, nếu thông báo GỬI KHÔNG được ghi lại thì quá trình định dạng sẽ không được thực hiện.


4
Kể từ Python 3.6, bạn có thể sử dụng f-string như vậy:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

11
@ P1h3r1e3d13 không giống như mã ghi nhật ký trong câu trả lời, f '' - chuỗi thực hiện định dạng ngay lập tức.
jfs

1
Đúng. Chúng hoạt động ở đây vì chúng định dạng và trả về một chuỗi thông thường trước khi gọi phương thức nhật ký. Điều đó có thể liên quan hoặc không liên quan đến ai đó, vì vậy tôi nghĩ nó đáng được đề cập như một lựa chọn.
Jacktose

3
@Jacktose Tôi nghĩ người dùng không nên đăng nhập bằng f-string, nó đánh bại các dịch vụ tổng hợp nhật ký (ví dụ: sentry). Có một lý do chính đáng mà việc ghi nhật ký stdlib làm hỏng việc tạo mẫu chuỗi.
wim

31

Đây là một tùy chọn khác không có các vấn đề về từ khóa được đề cập trong câu trả lời của Dunes. Nó chỉ có thể xử lý các {0}đối số positional ( ) chứ không phải đối số keyword ( {foo}). Nó cũng không yêu cầu hai lệnh gọi để định dạng (sử dụng dấu gạch dưới). Nó có hệ số ick của phân lớp str:

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

Bạn sử dụng nó như thế này:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

Tất nhiên, bạn có thể xóa dấu kiểm được ghi chú # optionalđể buộc tất cả các thư thông qua bộ điều hợp sử dụng định dạng kiểu mới.


Lưu ý cho bất kỳ ai đọc câu trả lời này nhiều năm sau : Bắt đầu với Python 3.2 , bạn có thể sử dụng tham số kiểu với Formattercác đối tượng:

Ghi nhật ký (kể từ 3.2) cung cấp hỗ trợ được cải thiện cho hai kiểu định dạng bổ sung này. Lớp Formatter được nâng cao để có thêm một tham số từ khóa tùy chọn được đặt tên style. Giá trị này mặc định là '%', nhưng các giá trị có thể có khác là '{''$', tương ứng với hai kiểu định dạng khác. Tính tương thích ngược được duy trì theo mặc định (như bạn mong đợi), nhưng bằng cách chỉ định rõ ràng một tham số kiểu, bạn có khả năng chỉ định các chuỗi định dạng hoạt động với str.format()hoặc string.Template.

Tài liệu cung cấp ví dụ logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Lưu ý rằng trong trường hợp này, bạn vẫn không thể gọi loggervới định dạng mới. Tức là, những điều sau vẫn sẽ không hoạt động:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either

5
Tuyên bố của bạn về Python 3 không chính xác. Tham số kiểu chỉ áp dụng cho chuỗi định dạng Formatter, không áp dụng cho các thông báo nhật ký riêng lẻ. Trang bạn đã liên kết đến có nội dung rõ ràng: "Sẽ không thay đổi điều này trong khi vẫn duy trì khả năng tương thích ngược".
mhsmith

1
Cảm ơn vì đã giữ cho tôi trung thực. Phần đầu tiên bây giờ ít hữu ích hơn, nhưng tôi đã diễn đạt lại nó về mặt Formatter, bây giờ là chính xác (tôi nghĩ). Các StyleAdapter công trình vẫn còn,
Felipe

@falstro - cảm ơn bạn đã chỉ ra điều đó. Phiên bản cập nhật bây giờ sẽ hoạt động. Kể từ khi BraceStringlà một chuỗi phân lớp, nó an toàn để trở lại bản thân từ__str__
Felipe

1
chỉ câu trả lời đề cập đến style = "{", +1
Tom S.

24

Đây là giải pháp của tôi cho vấn đề khi tôi thấy ghi nhật ký chỉ sử dụng định dạng kiểu printf. Nó cho phép ghi nhật ký các cuộc gọi được giữ nguyên - không có cú pháp đặc biệt như log.info(__("val is {}", "x")). Thay đổi bắt buộc đối với mã là bọc trình ghi nhật ký trong một StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

Cách sử dụng là:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

Nó đáng chú ý là thực hiện này có vấn đề nếu các từ khóa được sử dụng để thay thế giằng bao gồm level, msg, args, exc_info, extrahoặc stack_info. Đây là những tên đối số được sử dụng bởi logphương thức của Logger. Nếu bạn cần một trong những tên này, hãy sửa đổi processđể loại trừ những tên này hoặc chỉ xóa log_kwargskhỏi _logcuộc gọi. Lưu ý thêm, việc triển khai này cũng âm thầm bỏ qua các từ khóa sai chính tả dành cho Trình ghi (ví dụ ectra:).


4
Bằng cách này là giới thiệu bởi python doc, docs.python.org/3/howto/...
eshizhan

23

Giải pháp dễ dàng hơn sẽ là sử dụng mô-đun tuyệt vờilogbook

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

Hoặc đầy đủ hơn:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1

Điều này trông tuyệt vời, nhưng có cách nào để có mili giây thay vì chỉ giây?
Jeff,

@Jeff chắc chắn, sổ nhật ký cho phép bạn xác định các trình xử lý tùy chỉnh với và sử dụng các định dạng chuỗi tùy chỉnh.
Thomas Orozco,

5
@Jeff Vài năm sau - độ chính xác thời gian mặc định là mili giây.
Jan Vlcinsky

12

Như các câu trả lời khác đã đề cập, định dạng kiểu dấu ngoặc nhọn được giới thiệu trong Python 3.2 chỉ được sử dụng trên chuỗi định dạng, không phải thông báo nhật ký thực tế.

Để kích hoạt định dạng kiểu dấu ngoặc nhọn trên thông báo nhật ký thực, chúng ta có thể vá một chút mã trình ghi.

Các bản vá lỗi sau đây loggingmô-đun để tạo một get_loggerhàm sẽ trả về một trình ghi nhật ký sử dụng định dạng kiểu mới cho mọi bản ghi nhật ký mà nó xử lý.

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

Sử dụng:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

Ghi chú:

  • Hoàn toàn tương thích với các phương pháp ghi nhật ký thông thường (chỉ cần thay thế logging.getLoggerbằng get_logger)
  • Sẽ chỉ ảnh hưởng đến các trình ghi cụ thể được tạo bởi get_loggerhàm (không phá vỡ các gói của bên thứ 3).
  • Nếu trình ghi nhật ký được truy cập lại từ logging.getLogger()cuộc gọi bình thường , định dạng kiểu mới sẽ vẫn được áp dụng.
  • kwargs không được hỗ trợ (làm cho nó không thể mâu thuẫn với built-in exc_info, stack_info, stacklevelextra).
  • Lần truy cập hiệu suất phải ở mức tối thiểu (viết lại một con trỏ hàm duy nhất cho mỗi thông báo nhật ký).
  • Việc định dạng thông báo bị trì hoãn cho đến khi nó được xuất ra (hoặc hoàn toàn không nếu thông báo nhật ký được lọc).
  • Args được lưu trữ trên logging.LogRecordcác đối tượng như bình thường (hữu ích trong một số trường hợp với trình xử lý nhật ký tùy chỉnh).
  • Từ việc nhìn vào loggingmã nguồn của mô-đun , có vẻ như nó sẽ hoạt động trở lại Python 2.6 khi str.formatđược giới thiệu (nhưng tôi chỉ thử nghiệm nó trong 3.5 trở lên)

2
Câu trả lời duy nhất cho rằng chuỗi gỡ lỗi chỉ nên được tính nếu thông báo trình gỡ lỗi được in. Cảm ơn!
Fafaman

2

Hãy thử logging.setLogRecordFactorytrong Python 3.2+:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)

Nó hoạt động nhưng vấn đề là bạn phá vỡ các mô-đun của bên thứ ba đang sử dụng %định dạng như nhà máy sản xuất bản ghi là toàn cầu cho mô-đun ghi nhật ký.
jtaylor

1

Tôi đã tạo một Định dạng tùy chỉnh, được gọi là ColorFormatter để xử lý vấn đề như sau:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

Điều này giúp nó tương thích với các thư viện khác nhau. Hạn chế là nó có thể không hoạt động do có khả năng định dạng chuỗi hai lần.


0

Đây là một cái gì đó thực sự đơn giản mà hoạt động:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

Sau đó:

mydebuglog("hello {} {val}", "Python", val="World")

0

Giải pháp tương tự để pR0Ps', gói getMessagetrong LogRecordbởi gói makeRecord(thay vì handletrong câu trả lời của họ) trong các trường hợp Loggerđó nên mới định dạng cho phép:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

Tôi đã thử nghiệm điều này với Python 3.5.3.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.