2013-06-05 45 views
15

Tôi có mô-đun log.py, được sử dụng trong ít nhất hai mô-đun khác (server.pydevice.py).Ghi nhật ký bằng Python từ nhiều chủ đề

Nó có những globals:

fileLogger = logging.getLogger() 
fileLogger.setLevel(logging.DEBUG) 
consoleLogger = logging.getLogger() 
consoleLogger.setLevel(logging.DEBUG) 

file_logging_level_switch = { 
    'debug': fileLogger.debug, 
    'info':  fileLogger.info, 
    'warning': fileLogger.warning, 
    'error': fileLogger.error, 
    'critical': fileLogger.critical 
} 

console_logging_level_switch = { 
    'debug': consoleLogger.debug, 
    'info':  consoleLogger.info, 
    'warning': consoleLogger.warning, 
    'error': consoleLogger.error, 
    'critical': consoleLogger.critical 
} 

Nó có hai chức năng:

def LoggingInit(logPath, logFile, html=True): 
    global fileLogger 
    global consoleLogger 

    logFormatStr = "[%(asctime)s %(threadName)s, %(levelname)s] %(message)s" 
    consoleFormatStr = "[%(threadName)s, %(levelname)s] %(message)s" 

    if html: 
     logFormatStr = "<p>" + logFormatStr + "</p>" 

    # File Handler for log file 
    logFormatter = logging.Formatter(logFormatStr) 
    fileHandler = logging.FileHandler( 
     "{0}{1}.html".format(logPath, logFile)) 
    fileHandler.setFormatter(logFormatter) 
    fileLogger.addHandler(fileHandler) 

    # Stream Handler for stdout, stderr 
    consoleFormatter = logging.Formatter(consoleFormatStr) 
    consoleHandler = logging.StreamHandler() 
    consoleHandler.setFormatter(consoleFormatter) 
    consoleLogger.addHandler(consoleHandler) 

Và:

def WriteLog(string, print_screen=True, remove_newlines=True, 
     level='debug'): 

    if remove_newlines: 
     string = string.replace('\r', '').replace('\n', ' ') 

    if print_screen: 
     console_logging_level_switch[level](string) 

    file_logging_level_switch[level](string) 

tôi gọi LoggingInit từ server.py, khởi tạo các tập tin và giao diện điều khiển logger . Sau đó, tôi gọi WriteLog từ khắp nơi, vì vậy, nhiều chuỗi đang truy cập fileLoggerconsoleLogger.

Tôi có cần bảo vệ thêm cho tệp nhật ký của mình không? Tài liệu nói rằng các khóa chuỗi được xử lý bởi trình xử lý.

+0

Nếu bạn không đặt khóa khi bạn muốn viết, bản ghi nhật ký của bạn có thể trộn và tạo nhật ký không đọc được. – AliBZ

+0

@AliBZ - bản ghi nhật ký nào? ở cấp độ khai thác gỗ? chúng không được bảo vệ bởi dịch vụ ghi nhật ký python? http://stackoverflow.com/questions/2973900/is-pythons-logging-module-thread-safe –

+0

khi bạn sử dụng "fileLogger.info ('1 2 3 4')" trong hai chủ đề khác nhau, nhật ký cuối cùng của bạn có thể là một kết hợp của họ, một cái gì đó như thế này "1 2 1 2 3 3 4 4" – AliBZ

Trả lời

38

Tin vui là bạn không cần phải làm gì thêm để đảm bảo an toàn cho luồng, và bạn không cần phải làm gì thêm hoặc điều gì đó gần như tầm thường để tắt máy sạch. Tôi sẽ đến chi tiết sau.

Tin xấu là mã của bạn có vấn đề nghiêm trọng ngay cả trước khi bạn đến điểm đó: fileLoggerconsoleLogger là cùng một đối tượng. Từ the documentation for getLogger():

Trả lại trình ghi nhật ký với tên được chỉ định hoặc, nếu không có tên được chỉ định, hãy trả lại nhật ký là trình ghi gốc của cấu trúc phân cấp.

Vì vậy, bạn sẽ nhận được bộ ghi gốc và lưu trữ nó dưới dạng fileLogger và sau đó bạn sẽ nhận được bộ ghi gốc và lưu trữ nó là consoleLogger. Vì vậy, trong LoggingInit, bạn khởi tạo fileLogger, sau đó khởi tạo lại cùng một đối tượng dưới một tên khác với các giá trị khác nhau.

Bạn có thể thêm nhiều trình xử lý vào cùng một trình ghi - và do khởi tạo duy nhất bạn thực sự làm cho mỗi lần là addHandler, mã của bạn sẽ hoạt động như dự định, nhưng chỉ do ngẫu nhiên. Và chỉ là loại. Bạn sẽ nhận được hai bản sao của mỗi thư trong cả hai nhật ký nếu bạn vượt qua print_screen=True và bạn sẽ nhận được bản sao trong bảng điều khiển ngay cả khi bạn vượt qua print_screen=False.

Thực sự không có lý do gì cho các biến toàn cầu; toàn bộ các điểm của getLogger() là bạn có thể gọi nó mỗi khi bạn cần nó và nhận được logger gốc toàn cầu, vì vậy bạn không cần phải lưu trữ nó bất cứ nơi nào.


Một vấn đề nhỏ nữa là bạn không thoát văn bản bạn chèn vào HTML. Tại một số điểm bạn sẽ cố gắng để đăng nhập chuỗi "a < b" và kết thúc trong rắc rối.

Ít nghiêm trọng hơn, một chuỗi <p> thẻ không nằm trong một sốbên trong một <html> không phải là tài liệu HTML hợp lệ. Nhưng nhiều người xem sẽ tự động xử lý điều đó hoặc bạn có thể xử lý nhật ký của mình một cách trivially trước khi hiển thị chúng.Nhưng nếu bạn thực sự muốn điều này là chính xác, bạn cần phải phân loại FileHandler và thêm __init__ thêm tiêu đề nếu được cung cấp tệp trống và xóa chân trang nếu có, sau đó thêm close thêm chân trang.


Bắt trở lại câu hỏi thực tế của bạn:

Bạn không cần bất kỳ khóa bổ sung. Nếu trình xử lý thực hiện chính xác createLock, acquirerelease (và được gọi trên nền tảng có chủ đề), máy ghi nhật ký sẽ tự động đảm bảo có được khóa khi cần để đảm bảo mỗi thư được ghi nguyên tử.

Theo như tôi biết, các tài liệu không trực tiếp nói rằng StreamHandlerFileHandler thực hiện phương pháp này, nó không bao hàm sự mạnh mẽ nó (the text you mentioned in the question nói "Các module khai thác gỗ được thiết kế để được thread-safe mà không cần bất kỳ công việc đặc biệt cần được thực hiện bởi khách hàng của mình ", v.v ...). Và bạn có thể xem nguồn để triển khai (ví dụ: CPython 3.3) và thấy rằng cả hai đều kế thừa các phương pháp được triển khai chính xác từ logging.Handler.


Tương tự, nếu một handler thực hiện một cách chính xác flushclose, các máy móc thiết bị khai thác gỗ sẽ đảm bảo nó hoàn thành một cách chính xác trong quá trình tắt máy bình thường.

Ở đây, tài liệu hướng dẫn giải thích những gì StreamHandler.flush(), FileHandler.flush()FileHandler.close(). Chúng hầu hết là những gì bạn mong đợi, ngoại trừ việc StreamHandler.close() là một không-op, có nghĩa là các thông điệp tường trình cuối cùng có thể bị mất. Từ các tài liệu:

Lưu ý rằng phương pháp close() được thừa hưởng từ Handler và do đó, không có đầu ra, vì vậy một rõ ràng flush() cuộc gọi có thể cần thiết vào những thời điểm.

Nếu đây quan trọng đối với bạn, và bạn muốn sửa chữa nó, bạn cần phải làm điều gì đó như thế này:

class ClosingStreamHandler(logging.StreamHandler): 
    def close(self): 
     self.flush() 
     super().close() 

Và sau đó sử dụng ClosingStreamHandler() thay vì StreamHandler().

FileHandler không có vấn đề gì như vậy.


Cách thông thường để gửi nhật ký tới hai địa điểm là chỉ sử dụng trình ghi gốc với hai trình xử lý, mỗi trình xử lý có định dạng riêng.

Ngoài ra, ngay cả khi bạn muốn có hai trình ghi nhật ký, bạn không cần các bản đồ console_logging_level_switchfile_logging_level_switch riêng biệt; gọi số Logger.debug(msg) chính xác giống như gọi số Logger.log(DEBUG, msg). Bạn vẫn sẽ cần một số cách để ánh xạ tên cấp tùy chỉnh debug, v.v. thành các tên chuẩn DEBUG, v.v., nhưng bạn chỉ có thể thực hiện một lần tra cứu, thay vì thực hiện một lần cho mỗi lần ghi nhật ký (cộng với, nếu tên của bạn chỉ là tên tiêu chuẩn với dàn diễn viên khác nhau, bạn có thể ăn gian).

Đây là tất cả được mô tả khá tốt trong phần `Multiple handlers and formatters và phần còn lại của sổ tay ghi nhật ký.

Vấn đề duy nhất với cách làm tiêu chuẩn này là bạn không thể dễ dàng tắt đăng nhập bàn điều khiển trên cơ sở từng tin nhắn. Đó là bởi vì nó không phải là một điều bình thường để làm. Thông thường, bạn chỉ cần đăng nhập theo cấp độ, và đặt mức nhật ký cao hơn trên nhật ký tệp.

Nhưng nếu muốn kiểm soát nhiều hơn, bạn có thể sử dụng bộ lọc. Ví dụ: cung cấp cho bạn FileHandler bộ lọc chấp nhận mọi thứ và ConsoleHandler bộ lọc yêu cầu thứ gì đó bắt đầu bằng console, sau đó sử dụng bộ lọc 'console' if print_screen else ''. Điều đó làm giảm WriteLog xuống gần một lớp lót.

Bạn vẫn cần thêm hai dòng để xóa dòng mới — nhưng thậm chí bạn có thể làm rằng trong bộ lọc hoặc qua bộ điều hợp, nếu bạn muốn. (Một lần nữa, xem sách dạy nấu ăn.) Và sau đó WriteLog thực sự một lớp lót.

+0

Cảm ơn các mẹo bổ sung. Điều này có nghĩa là tất cả những gì tôi cần làm là chỉ định tên trong 'getLogger()' và nó sẽ hoạt động như dự định? – nckturner

+0

và, @abarnert nếu tôi muốn tiếp tục đăng nhập vào hai địa điểm khác nhau (bằng cách chỉ định tên trong 'getLogger()', tôi vẫn cần các hình cầu, đúng không? – nckturner

+0

@Raskol: Vâng, nó phụ thuộc vào ý nghĩa của "ý định" , nhưng đó có thể không phải là những gì bạn thực sự muốn.Ý tưởng đằng sau 'getLogger' là bạn phải sử dụng nó cho một số loại cấu trúc nhật ký phân cấp; chú ý rằng tài liệu cho "Logger Objects" nói về "logging.getLogger (__ name __)' để làm cho hệ thống phân cấp đăng nhập của bạn phù hợp với hệ thống phân cấp mô-đun của bạn. – abarnert

Các vấn đề liên quan