Làm cách nào để thêm trường tùy chỉnh vào chuỗi định dạng nhật ký Python?


91

Chuỗi định dạng hiện tại của tôi là:

formatter = logging.Formatter('%(asctime)s : %(message)s')

và tôi muốn thêm một trường mới được gọi là trường app_namesẽ có giá trị khác nhau trong mỗi tập lệnh chứa bộ định dạng này.

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

Nhưng tôi không chắc làm thế nào để chuyển app_namegiá trị đó cho trình ghi để nội suy vào chuỗi định dạng. Tôi rõ ràng có thể làm cho nó xuất hiện trong thông báo nhật ký bằng cách chuyển nó mỗi lần nhưng điều này thật lộn xộn.

Tôi đã thử:

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

nhưng không có tác dụng.


Bạn có thực sự muốn chuyển điều này cho mọi logcuộc gọi không? Nếu vậy, hãy xem các tài liệu có ghi "Chức năng này có thể được sử dụng để đưa các giá trị của riêng bạn vào LogRecord…" Nhưng đây có vẻ như là một trường hợp cơ bản để sử dụng logger = logging.getLogger('myapp')và đưa nó vào logger.infocuộc gọi.
abarnert,

đăng nhập python đã có thể làm điều đó afaik. nếu bạn sử dụng một khác nhau loggerđối tượng trong mỗi ứng dụng, bạn có thể làm cho mỗi người sử dụng một tên khác nhau bởi instantiating của bạn loggergiống như vậy: logger = logging.getLogger(myAppName). lưu ý rằng đó __name__là tên mô-đun python, vì vậy nếu mỗi ứng dụng là mô-đun python của riêng nó, thì điều đó cũng sẽ hoạt động.
Florian Castellane

Câu trả lời:


131

Bạn có thể sử dụng LoggerAdapter để không phải chuyển thêm thông tin với mỗi cuộc gọi ghi nhật ký:

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

nhật ký (đại loại như)

2013-07-09 17:39:33,596 Super App : The sky is so blue

Bộ lọc cũng có thể được sử dụng để thêm thông tin theo ngữ cảnh.

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

tạo ra một bản ghi nhật ký tương tự.


3
Làm thế nào chúng ta có thể chỉ định điều đó trong một config.initệp? Tôi muốn thêm tên máy chủ hiện tại socket.gethostname().
Laurent LAPORTE

Tôi có mẫu này không làm việc cho tôi. import uuid uniqueId = str(uuid.uuid4()) extra = {"u_id" : uniqueId} RotatingHandler = RotatingFileHandler(LOG_FILENAME,encoding='utf-8',maxBytes=maxSize, backupCount=batchSize) logger.basicConfig(handlers=[RotatingHandler],level=logLevel.upper(),format='%(levelname)s %(u_id)s %(funcName)s %(asctime)s %(message)s ',datefmt='%m/%d/%Y %I:%M:%S %p') logger = logger.LoggerAdapter(logger=logger, extra=extra)
Hayat


2
Tôi có thể chuyển một chuỗi Thông tin bổ sung không. Một cái gì đó như thế này: "Đã xảy ra lỗi cho ID nhân viên 1029382" Nếu không tạo bất kỳ từ điển nào.
shreesh katti

50

Bạn cần chuyển dict dưới dạng tham số cho thêm để thực hiện theo cách đó.

logging.info('Log message', extra={'app_name': 'myapp'})

Bằng chứng:

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

Ngoài ra, cũng cần lưu ý, nếu bạn cố gắng ghi nhật ký một tin nhắn mà không chuyển đoạn dict, thì nó sẽ không thành công.

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

Điều này cũng sẽ làm việc cho logging.info()? Nó không thành công khi tôi thử lần cuối cùng. : /
Prakhar Mohan Srivastava

2
Tôi thích câu trả lời của @ mr2ert. Bạn có thể cung cấp giá trị mặc định cho trường bổ sung bằng cách mở rộng logging.Formatterlớp: class CustomFormatter (logging.Formatter): định dạng def (self, record): if not hasattr (record, 'foo'): record.foo = 'default_foo' return super (CustomFormatter, self.format (record) h = loggin.StreamHandler () h.setFormatter (CustomFormatter ('% (foo) s% (message) s') logger = logging.getLogger ('bar') logger.addHandler ( h) logger.error ('hey!', extra = {'foo': 'FOO'}) logger.error ('hey!')
loutre Ngày

Phương pháp này nhanh hơn nhưng bạn cần thêm các dòng thừa trên mỗi thông báo nhật ký, điều này rất dễ quên và dễ bị lỗi. Việc thay thế các lệnh gọi super () phức tạp hơn so với câu trả lời từ unutbu.
pevogam

@Prakhar Mohan Srivastava Có Nó cũng sẽ hoạt động tốt cho logging.info (). Lỗi tin nhắn nào bạn đang gặp phải ?
shreesh katti

Tôi có thể chuyển một chuỗi Thông tin bổ sung không. Một cái gì đó như thế này: "Xảy ra lỗi lầm cho nhân viên ID 1029382" Nếu không tạo ra bất kỳ từ điển và đi qua các phím
shreesh katti

23

Python3

Kể từ Python3.2, bây giờ bạn có thể sử dụng LogRecordFactory

>>> import logging
>>> logging.basicConfig(format="%(custom_attribute)s - %(message)s")
>>> old_factory = logging.getLogRecordFactory()
>>> def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.custom_attribute = "my-attr"
        return record

>>> logging.setLogRecordFactory(record_factory)
>>> logging.info("hello")
my-attr - hello

Tất nhiên record_factorycó thể được tùy chỉnh để có thể gọi bất kỳ và giá trị của custom_attributecó thể được cập nhật nếu bạn giữ một tham chiếu đến nhà máy có thể gọi.

Tại sao điều đó lại tốt hơn việc sử dụng Bộ điều hợp / Bộ lọc?

  • Bạn không cần phải vượt qua trình ghi nhật ký của mình xung quanh ứng dụng
  • Nó thực sự hoạt động với các thư viện của bên thứ 3 sử dụng trình ghi nhật ký của riêng họ (chỉ bằng cách gọi logger = logging.getLogger(..)) giờ sẽ có cùng định dạng nhật ký. (đây không phải là trường hợp với Bộ lọc / Bộ điều hợp nơi bạn cần sử dụng cùng một đối tượng trình ghi nhật ký)
  • Bạn có thể xếp chồng / chuỗi nhiều nhà máy

Có bất kỳ thay thế nào cho python 2.7 không?
karolch

Không mang lại lợi ích tương tự, với 2.7, bạn sẽ phải sử dụng Bộ điều hợp hoặc Bộ lọc.
Ahmad

5
Đây là câu trả lời hay nhất của python3 hiện tại
Stéphane

Theo docs.python.org/3/howto/logging-cookbook.html : Mẫu này cho phép các thư viện khác nhau liên kết các nhà máy lại với nhau và miễn là chúng không ghi đè các thuộc tính của nhau hoặc vô tình ghi đè các thuộc tính được cung cấp theo tiêu chuẩn, ở đó không có gì ngạc nhiên. Tuy nhiên, cần lưu ý rằng mỗi liên kết trong chuỗi thêm chi phí thời gian chạy cho tất cả các hoạt động ghi nhật ký và kỹ thuật này chỉ nên được sử dụng khi việc sử dụng Bộ lọc không mang lại kết quả mong muốn.
steve0hh

1
@ steve0hh một trong những kết quả mong muốn chính là khả năng ghi thông tin theo ngữ cảnh qua các thư viện / mô-đun khác nhau, chỉ có thể đạt được bằng cách này. Trong hầu hết các trường hợp, các thư viện không nên chạm vào cấu hình trình ghi nhật ký, đó là trách nhiệm của ứng dụng mẹ.
Ahmad

9

Một cách khác là tạo LoggerAdapter tùy chỉnh. Điều này đặc biệt hữu ích khi bạn không thể thay đổi định dạng HOẶC nếu định dạng của bạn được chia sẻ với mã không gửi khóa duy nhất (trong trường hợp của bạn là app_name ):

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

Và trong mã của bạn, bạn sẽ tạo và khởi chạy trình ghi nhật ký của mình như bình thường:

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

Cuối cùng, bạn sẽ tạo bộ điều hợp trình bao bọc để thêm tiền tố nếu cần:

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

Đầu ra sẽ giống như sau:

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

1

Tôi đã tìm thấy câu hỏi SO này sau khi tự thực hiện nó. Hy vọng nó sẽ giúp một ai đó. Trong đoạn mã dưới đây, tôi đang tạo một khóa bổ sung được gọi claim_idở định dạng trình ghi nhật ký. Nó sẽ ghi lại yêu cầu bảo hiểm bất cứ khi nào có một claim_idkhóa hiện diện trong môi trường. Trong trường hợp sử dụng của tôi, tôi cần ghi lại thông tin này cho một hàm AWS Lambda.

import logging
import os

LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(funcName)s %(lineno)s ClaimID: %(claim_id)s: %(message)s'


class AppLogger(logging.Logger):

    # Override all levels similarly - only info overriden here

    def info(self, msg, *args, **kwargs):
        return super(AppLogger, self).info(msg, extra={"claim_id": os.getenv("claim_id", "")})


def get_logger(name):
    """ This function sets log level and log format and then returns the instance of logger"""
    logging.setLoggerClass(AppLogger)
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    return logger


LOGGER = get_logger(__name__)

LOGGER.info("Hey")
os.environ["claim_id"] = "12334"
LOGGER.info("Hey")

Gist: https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652


0

Sử dụng câu trả lời của mr2ert, tôi đã đưa ra giải pháp thoải mái này (Mặc dù tôi đoán nó không được khuyến khích) - Ghi đè các phương thức ghi nhật ký tích hợp để chấp nhận đối số tùy chỉnh và tạo extratừ điển bên trong các phương thức:

import logging

class CustomLogger(logging.Logger):

   def debug(self, msg, foo, *args, **kwargs):
       extra = {'foo': foo}

       if self.isEnabledFor(logging.DEBUG):
            self._log(logging.DEBUG, msg, args, extra=extra, **kwargs)

   *repeat for info, warning, etc*

logger = CustomLogger('CustomLogger', logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(foo)s] %(message)s') 
handler = logging.StreamHandler()
handler.setFormatter(formatter) 
logger.addHandler(handler)

logger.debug('test', 'bar')

Đầu ra:

2019-03-02 20:06:51,998 [bar] test

Đây là hàm tích hợp để tham khảo:

def debug(self, msg, *args, **kwargs):
    """
    Log 'msg % args' with severity 'DEBUG'.

    To pass exception information, use the keyword argument exc_info with
    a true value, e.g.

    logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
    """
    if self.isEnabledFor(DEBUG):
        self._log(DEBUG, msg, args, **kwargs)

0

nhập nhật ký;

lớp LogFilter (logging.Filter):

def __init__(self, code):
    self.code = code

def filter(self, record):
    record.app_code = self.code
    return True

logging.basicConfig (format = '[% (asctime) s:% (levelname) s] :: [% (module) s ->% (name) s] - APP_CODE:% (app_code) s - MSG:% (message )S');

Logger lớp:

def __init__(self, className):
    self.logger = logging.getLogger(className)
    self.logger.setLevel(logging.ERROR)

@staticmethod
def getLogger(className):
    return Logger(className)

def logMessage(self, level, code, msg):
    self.logger.addFilter(LogFilter(code))

    if level == 'WARN':        
        self.logger.warning(msg)
    elif level == 'ERROR':
        self.logger.error(msg)
    else:
        self.logger.info(msg)

Kiểm tra lớp: logger = Logger.getLogger ('Kiểm tra')

if __name__=='__main__':
    logger.logMessage('ERROR','123','This is an error')

Việc triển khai này sẽ rất kém hiệu quả.
blakev
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.