2012-03-09 36 views
8

Tôi có một trường hợp Logger:Redirect stderr để Logger dụ

require 'logger' 
logger = Logger.new('foo.log', 'weekly') 

tôi muốn chuyển hướng lỗi runtime (đầu ra stderr) vào các bản ghi là tốt. Tôi thấy this forum thread trong đó có những lời khuyên:

new_fd = logger.get_logger_file_descriptor 
$stderr.reopen new_fd 

Tuy nhiên, Logger không có một phương pháp dụ get_logger_file_descriptor, cũng không phải tôi có thể tìm thấy bất kỳ phương pháp tiếp xúc xung quanh tiếp cận với thiết bị ghi hoặc tập tin.

Làm cách nào tôi có thể làm cho tất cả đầu ra $ stderr đi vào nhật ký?

Trả lời

15

Nếu bạn đang tạo các logger chính mình, bạn có thể tạo ra các đối tượng File đầu tiên, sau đó sử dụng nó để tạo ra các logger và gán nó vào $stderr:

log_file = File.new('foo.log', 'a') 
logger = Logger.new(log_file, 'weekly') 
$stderr = log_file #usually no need to use reopen 

Lưu ý rằng điều này sẽ dẫn đến việc dữ liệu ghi nhận được trộn lẫn với đầu ra của $stderr, điều này có thể gây ra sự cố nếu bạn phân tích cú pháp tệp nhật ký mong muốn nó ở định dạng nhất định (điều này cũng sẽ xảy ra với giải pháp của bạn).

Nếu bạn không có tệp cơ bản nhưng chỉ nhận được logger từ một nơi khác, điều này phức tạp hơn một chút. Điều cần thiết là một đối tượng như IO có thể được gán cho $stderr và chuyển bất kỳ thứ gì được ghi vào nhật ký. Lớp IO trong Ruby không may gắn chặt chẽ với hệ thống i/o cơ bản (các bộ mô tả tệp và các loại tương tự), và không có giao diện chung có thể được sử dụng để tạo luồng đầu vào và đầu ra. (StringIO là ngoại lệ đáng chú ý).

Tuy nhiên hầu hết, nếu không phải tất cả các phương pháp sản lượng trên IO cuối cùng đi qua #write, vì vậy bằng cách ghi đè một phương pháp này, bạn có thể nhận được gần với những gì bạn đang sau:

class IOToLog < IO 

    def initialize(logger) 
    @logger = logger 
    end 

    def write(string) 
    #assume anything written to stderr is an error 
    @logger.error(message) 
    end 

end 

logger = get_logger_from_somewhere 

$stderr = IOToLog.new(logger) 

Bây giờ bất cứ điều gì để viết $stderr sẽ kết thúc vào tệp nhật ký. Tuy nhiên, định dạng sẽ hơi lạ. Bất cứ lúc nào bất kỳ phương thức viết nào đều gọi #write một mục mới sẽ được thực hiện trong tệp nhật ký. Ví dụ, #puts được gọi với một mảng sẽ gọi #write cho mỗi mục nhập của mảng, và một lần nữa với một ký tự dòng mới giữa mỗi mục, kết quả là 2n - 1 mục nhập nhật ký, n - 1 trong số đó sẽ để trống.

Bạn có thể làm cho phương pháp #write bị ghi đè phức tạp hơn để xử lý việc này, có thể sử dụng bộ đệm bên trong và chỉ gọi trình ghi nhật ký khi bạn cho rằng mình có thông báo đầy đủ. Ngoài ra, bạn có thể ghi đè lên các phương thức riêng lẻ để tự ghi vào nhật ký. Nếu bạn đã làm điều này, lớp học IOToLog sẽ không nhất thiết phải kế thừa từ IO. Giải pháp tốt nhất của bạn sẽ phụ thuộc vào cách bạn muốn đầu ra lỗi chuẩn xuất hiện trong logfile, cách chương trình của bạn sử dụng $stderr và công việc bạn muốn thực hiện các phương thức từ IO là bao nhiêu.

+0

Câu trả lời hay, cảm ơn! Trong trường hợp của tôi, tôi đang tạo logger để giải pháp đầu tiên của bạn sạch sẽ và thanh lịch. Tôi đặc biệt thích rằng bạn đã bao gồm cách giải quyết cho trường hợp khác. (Mặc dù trong trường hợp này, tôi có thể ủng hộ việc hack của tôi vào và lấy trực tiếp tập tin.) – Phrogz

+0

Phương pháp thứ hai không hoạt động đối với tôi trên jruby. Tôi nhận được lỗi sau: (Errno :: EBADF) Mô tả tập tin sai –

+0

Nên có '@ logger.error (chuỗi)' Tôi nghĩ – lojza

2

Điều tốt nhất tôi đã có thể đưa ra là tầm xung quanh này hack:

$stderr.reopen logger.instance_variable_get(:@logdev).dev 

Nó hoạt động, nhưng dĩ nhiên phá vỡ đóng gói của Logger mà tôi giả định là có một lý do.