Mẫu đồng thời của logger trong ứng dụng đa luồng


8

Bối cảnh: Chúng tôi đang làm việc trên một ứng dụng đa luồng (Linux-C) theo mô hình đường ống.

Mỗi mô-đun có một luồng riêng và các đối tượng được đóng gói để xử lý dữ liệu; và mỗi giai đoạn có một hình thức trao đổi dữ liệu tiêu chuẩn với đơn vị tiếp theo.

Ứng dụng này không bị rò rỉ bộ nhớ và an toàn cho các luồng sử dụng các khóa tại điểm mà chúng trao đổi dữ liệu. Tổng số luồng là khoảng 15- và mỗi luồng có thể có từ 1 đến 4 đối tượng. Làm khoảng 25 - 30 đối tượng kỳ lạ mà tất cả đều có một số ghi nhật ký quan trọng phải làm.

Hầu hết các cuộc thảo luận tôi đã thấy về các cấp độ khác nhau như trong Log4J và đó là các bản dịch khác. Các câu hỏi lớn thực sự là về việc ghi nhật ký tổng thể nên thực sự xảy ra như thế nào?

Một cách tiếp cận là tất cả khai thác gỗ địa phương không fprintfđể stderr. Stderr được chuyển hướng đến một số tập tin. Cách tiếp cận này rất tệ khi các bản ghi trở nên quá lớn.

Nếu tất cả các đối tượng khởi tạo logger cá nhân của họ - (khoảng 30-40 trong số họ) sẽ có quá nhiều tệp. Và không giống như ở trên, người ta sẽ không có ý tưởng về thứ tự thực sự của các sự kiện. Dấu thời gian là một khả năng - nhưng nó vẫn là một mớ hỗn độn để đối chiếu.

Nếu có một mẫu logger toàn cầu (singleton) duy nhất - nó gián tiếp chặn rất nhiều luồng trong khi một người đang bận ghi nhật ký. Điều này là không thể chấp nhận khi xử lý các chủ đề nặng.

Vì vậy, đâu là cách lý tưởng để cấu trúc các đối tượng đăng nhập? Một số thực hành tốt nhất trong các ứng dụng quy mô lớn thực tế là gì?

Tôi cũng rất thích học hỏi từ một số thiết kế thực tế của các ứng dụng quy mô lớn để lấy cảm hứng từ!

======

BIÊN TẬP:

Dựa trên cả hai câu trả lời ở đây là câu hỏi tôi còn lại với:

Cách thực hành tốt nhất về việc gán logger (hàng đợi ghi nhật ký) cho đối tượng: họ nên gọi một số global_api () hoặc logger nên được gán cho chúng trong hàm tạo. Khi các đối tượng ở một số thứ bậc sâu, cách tiếp cận sau này trở nên tẻ nhạt. Nếu họ đang gọi một số global_api () thì đó là loại khớp với Ứng dụng, vì vậy cố gắng sử dụng đối tượng này trong ứng dụng khác sẽ ném sự phụ thuộc này. Có cách nào sạch hơn cho việc này?


1
Chào mừng trở lại! Chúng tôi đã nhớ bạn cả ở đây và trên Nơi làm việc.
yannis

Câu trả lời:


10

một cách có thể chấp nhận được để sử dụng trình ghi nhật ký đơn lẻ, ủy nhiệm việc ghi nhật ký thực tế cho luồng của chính nó

sau đó bạn có thể sử dụng bất kỳ giải pháp sản xuất-tiêu dùng hiệu quả nào (như danh sách được liên kết không chặn dựa trên CaS nguyên tử) để thu thập các thông điệp tường trình mà không phải lo lắng rằng đó là một khóa toàn cầu ngầm

Cuộc gọi nhật ký trước tiên sẽ lọc và xây dựng thông điệp tường trình và sau đó chuyển nó đến người tiêu dùng, người tiêu dùng sau đó sẽ lấy nó và viết nó ra (và giải phóng tài nguyên của tin nhắn cá nhân)


Cảm ơn câu trả lời. Trong nội bộ tôi đã suy nghĩ theo cùng một hướng. Câu hỏi duy nhất của tôi là - Nếu bạn muốn tạo một mục đích chung cho đối tượng - không phải nó giới hạn rằng nó truy cập vào một thế giới bên ngoài dự kiến ​​từ Ứng dụng? Bạn có thể giải thích rõ hơn về cách bạn sẽ phân bổ cho mỗi mô-đun và bộ ghi nhật ký toàn cầu không?
Dipan Mehta

6

Câu trả lời của Ratchet Freak là những gì tôi nghĩ ban đầu.

Một phương pháp thay thế có thể là cung cấp cho mỗi mô-đun của bạn hàng đợi nhà sản xuất-người tiêu dùng của riêng nó và sau đó để cơ chế logger của bạn quét các hàng đợi này trên luồng của chính nó.

Điều này có thể trở nên linh hoạt hơn bởi vì bạn có thể chuyển đổi số lượng logger bạn sử dụng - bạn có thể có một cho tất cả mọi thứ, một cho mỗi mô-đun hoặc chia các mô-đun thành các nhóm và có một cho mỗi nhóm.

Chỉnh sửa: Xây dựng

(đừng bận tâm đến chữ C của tôi - đó là những gì bạn nói bạn đang mã hóa, phải không?)

Vì vậy, ý tưởng này có một hàng đợi / danh sách nhà sản xuất-người tiêu dùng cho mỗi mô-đun của bạn. Một hàng đợi như vậy có lẽ sẽ giống như thế này:

enum LogItemType {INFO, ERROR};

struct LogItem
{
    LogItemType type;
    char *msg;
};

struct LogQueue {...}; // implementation details -- holds an array/list of LogItem

bool queueLogItem(log_queue *, log_item *);
bool queueHasItems(log_queue *);
bool queueIsFull(log_queue *);
LogItem *dequeueLogItem(log_queue *);

Mỗi mô-đun sẽ cần phải khởi tạo hàng đợi như vậy của riêng mình hoặc được truyền qua mã khởi tạo để thiết lập các luồng, v.v. Mã init có thể sẽ giữ các tham chiếu đến tất cả các hàng đợi:

LogQueue *module_queues = {module_1_queue_ptr, module_2_queue_ptr, ... , module_N_queue_ptr};

setModuleLoggingQueue(module1, module_1_queue_ptr);
// .
// .
// .

Bên trong các mô-đun, bạn có chúng tạo LogItems và xếp hàng chúng cho mỗi tin nhắn.

LogItem *item = malloc(sizeof(LogItem));
item->type = INFO;
item->msg = malloc(MSG_SIZE)
memcpy("MSG", item->msg);
queueLogItem(module_queue, item);

Sau đó, bạn sẽ có một hoặc nhiều người tiêu dùng của hàng đợi nhận tin nhắn và thực sự viết nhật ký theo một vòng lặp chính như vậy:

void loggingThreadBody()
{
    while (true)
    {
        for (i = 0; i < N; i++)
        {
            if (queueHasItems(module_queues[i]))
                writeLogItem(dequeueLogItem(module_queues[i]));
        }

        threadSleep(200);
    }
}

hoặc điều tương tự.

Điều này sẽ linh hoạt trong việc cho phép bạn có những người tiêu dùng khác nhau của hàng đợi, ví dụ:

// For one logger:

LogQueue *module_queues = {module_1_queue_ptr, module_2_queue_ptr, ... , module_N_queue_ptr};

initLoggerThread(module_queues);


// -OR-
// For multiple loggers:

LogQueue *group1_queues = {module_1_queue_ptr, ..., module_4_queue_ptr};
LogQueue *group2_queues = {module_5_queue_ptr, ... , module_Nmin7_queue_ptr};
LogQueue *group3_queues = {module_Nmin7_queue_ptr, ... , module_N_queue_ptr};

initLoggerThread(group1_queues);
initLoggerThread(group2_queues);
initLoggerThread(group3_queues);

Lưu ý: Tôi đoán bạn muốn phân bổ bộ nhớ cho thông điệp tường trình tại thời điểm xếp hàng và phân bổ nó vào thời gian tiêu thụ (giả sử tin nhắn của bạn sẽ chứa nội dung động).

Chỉnh sửa khác:

Quên đề cập đến: nếu bạn đang mong đợi nhiều hoạt động trên các chuỗi mô-đun của mình, bạn có thể thấy nếu bạn có thể thực hiện ghi nhật ký không đồng bộ để mọi thứ không bị chặn.

Một chỉnh sửa khác:

Có lẽ bạn cũng muốn đặt dấu thời gian như một phần của LogItem; với (các) luồng logger đi qua hàng đợi một cách tuần tự, các câu lệnh log có thể bị lỗi khi chúng xảy ra theo trình tự thời gian trong các mô-đun.

Và một chỉnh sửa khác:

Với thiết lập này, sẽ khá dễ dàng để vượt qua tất cả các mô-đun trong cùng một hàng đợi và chỉ cần người tiêu dùng nhìn vào một hàng đợi đó sẽ đưa bạn trở lại câu trả lời của Ratchet Freak.

Geeze, bạn sẽ ngừng chỉnh sửa!?:

Ngoài ra, bạn có thể có một hàng đợi cho mỗi nhóm mô-đun và có một logger cho mỗi hàng đợi.

Ok, tôi sẽ dừng ngay bây giờ.


Hay đấy. Bạn có thể xây dựng thêm thiết kế này?
Dipan Mehta

@DipanMehta chắc chắn, nhưng tôi không thể làm như vậy vào lúc này. Tôi sẽ cố gắng cập nhật nó tối nay - chắc chắn vào tối mai.
paul

@DipanMehta Ok, tôi đã cập nhật câu trả lời của mình :)
paul

@DipanMehta Đã thêm một chỉnh sửa khác ....
paul

Cảm ơn, Paul. Điều này có vẻ tốt nhất và thực sự trả lời câu hỏi của tôi. Tôi đã thêm một nhận xét bổ sung nhỏ trong câu hỏi - hãy để tôi xem nếu ai đó có thể làm sáng tỏ điều này.
Dipan Mehta
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.