Có phải là một thực hành tốt để có trình ghi nhật ký như một singleton?


81

Tôi có thói quen chuyển trình ghi nhật ký cho hàm tạo, như:

public class OrderService : IOrderService {
     public OrderService(ILogger logger) {
     }
}

Nhưng điều đó khá khó chịu, vì vậy tôi đã sử dụng nó một thuộc tính sau:

private ILogger logger = NullLogger.Instance;
public ILogger Logger
{
    get { return logger; }
    set { logger = value; }
}

Điều này cũng đang trở nên khó chịu - nó không khô khan, tôi cần phải lặp lại điều này trong mọi lớp học. Tôi có thể sử dụng lớp cơ sở, nhưng sau đó một lần nữa - tôi đang sử dụng lớp Biểu mẫu, vì vậy sẽ cần FormBase, v.v. Vì vậy, tôi nghĩ rằng, mặt trái của việc để singleton với ILogger bị lộ ra ngoài, vì vậy rất nhiều người sẽ biết nơi lấy trình ghi nhật ký:

    Infrastructure.Logger.Info("blabla");

CẬP NHẬT: Như Merlyn đã nhận thấy một cách chính xác, tôi nên đề cập rằng trong ví dụ thứ nhất và thứ hai, tôi đang sử dụng DI.



1
Từ những câu trả lời mà tôi đang thấy, có vẻ như mọi người đã không nhận ra bạn đang tiêm vào cả hai ví dụ đó. Bạn có thể làm rõ hơn điều này trong câu hỏi? Tôi đã thêm các thẻ để xử lý một số trong số đó.
Merlyn Morgan-Graham

1
Đọc các bình luận của bài viết này Huyền thoại về tiêm phụ thuộc: Tham khảo được truyền bởi Miško Hevery của Google, người khá quan tâm đến việc thử nghiệm và tiêm phụ thuộc. Anh ấy nói rằng việc ghi nhật ký là một loại ngoại lệ trong đó một singleton là được trừ khi bạn cần kiểm tra đầu ra của bộ ghi nhật ký (trong trường hợp đó sử dụng DI).
Người dùng

Câu trả lời:


35

Điều này cũng trở nên khó chịu - nó không KHÔ

Đúng. Nhưng bạn chỉ có thể làm được rất nhiều điều cho mối quan tâm xuyên suốt trải khắp mọi loại hình bạn có. Bạn phải sử dụng trình ghi nhật ký ở khắp mọi nơi, vì vậy bạn phải có thuộc tính trên các loại đó.

Vì vậy, hãy xem chúng ta có thể làm gì với nó.

Singleton

Singleton thật khủng khiếp <flame-suit-on>.

Tôi khuyên bạn nên gắn bó với việc tiêm thuộc tính như bạn đã làm với ví dụ thứ hai của mình. Đây là cách tính toán tốt nhất mà bạn có thể làm mà không cần dùng đến phép thuật. Tốt hơn là có một phụ thuộc rõ ràng hơn là ẩn nó qua một singleton.

Nhưng nếu những chiếc đĩa đơn giúp bạn tiết kiệm đáng kể thời gian, bao gồm tất cả việc tái cấu trúc mà bạn sẽ phải làm (thời gian quả cầu pha lê!), Tôi cho rằng bạn có thể sống chung với chúng. Nếu có một lần sử dụng Singleton, thì đây có thể là nó. Hãy ghi nhớ những chi phí nếu bạn từng muốn thay đổi tâm trí của bạn sẽ vào khoảng cao như nó được.

Nếu bạn làm điều này, hãy kiểm tra câu trả lời của những người khác sử dụng các Registrymô hình (xem mô tả), và những người đăng ký một (resetable) singleton nhà máy chứ không phải là một trường hợp singleton logger.

Có những lựa chọn thay thế khác có thể hoạt động tốt mà không ảnh hưởng nhiều, vì vậy bạn nên kiểm tra chúng trước.

Đoạn mã Visual Studio

Bạn có thể sử dụng các đoạn mã Visual Studio để tăng tốc độ nhập của mã lặp lại đó. Bạn sẽ có thể gõ một cái gì đó giống như loggertab, và mã sẽ xuất hiện một cách kỳ diệu cho bạn.

Sử dụng AOP để KHÔ

Bạn có thể loại bỏ một chút mã chèn thuộc tính đó bằng cách sử dụng khuôn khổ Lập trình hướng theo khía cạnh (AOP) như PostSharp để tự động tạo một số mã.

Nó có thể trông giống như thế này khi bạn hoàn thành:

[InjectedLogger]
public ILogger Logger { get; set; }

Bạn cũng có thể sử dụng mã mẫu theo dõi phương pháp của họ để tự động theo dõi vào và ra của phương thức, điều này có thể loại bỏ sự cần thiết phải thêm tất cả các thuộc tính của trình ghi nhật ký lại với nhau. Bạn có thể áp dụng thuộc tính ở cấp độ lớp hoặc rộng không gian tên:

[Trace]
public class MyClass
{
    // ...
}

// or

#if DEBUG
[assembly: Trace( AttributeTargetTypes = "MyNamespace.*",
    AttributeTargetTypeAttributes = MulticastAttributes.Public,
    AttributeTargetMemberAttributes = MulticastAttributes.Public )]
#endif

3
+1 cho Singleton là khủng khiếp. Hãy thử sử dụng singleton với nhiều trình tải lớp, tức là môi trường máy chủ ứng dụng trong đó người điều phối là khách hàng, nơi tôi đã trải qua các ví dụ khủng khiếp về ghi nhật ký kép (nhưng không quá khủng khiếp như các câu lệnh ghi nhật ký đến từ sai nơi mà một số lập trình viên C ++ đã nói với tôi)
Niklas R.

1
@NickRosencrantz +1 cho câu chuyện kinh dị C ++; Tôi thích dành vài phút để chiêm ngưỡng sự khủng khiếp của những chiếc đĩa đơn chỉ để cuộn xuống và đi "Haha lol ít nhất tôi không có vấn đề với chúng."
Domenic

32
</flame-suit-on> Tôi không biết bạn đã sống trong bộ đồ lửa 5 năm như thế nào nhưng tôi hy vọng điều này sẽ giúp ích cho bạn.
Brennen Sprimont

39

Tôi đặt một thể hiện trình ghi nhật ký vào vùng chứa phụ thuộc của mình, sau đó sẽ đưa trình ghi nhật ký vào các lớp cần một.


8
Bạn có thể đặc sắc hơn không? Vì tôi nghĩ đó là những gì tôi đã làm / làm trong mẫu mã 1 và 2 được đề cập?
Giedrius

2
Lớp "using" về cơ bản sẽ giống như những gì bạn đã có. Tuy nhiên, giá trị thực tế không phải được cấp theo cách thủ công cho lớp của bạn, vì vùng chứa DI thực hiện điều đó cho bạn. Điều này giúp việc thay thế trình ghi nhật ký trong thử nghiệm dễ dàng hơn nhiều so với việc có một trình ghi nhật ký singleton tĩnh.
Daniel Rose

2
Không ủng hộ, mặc dù đây là câu trả lời đúng (IMO), bởi vì OP đã nói rằng họ đang làm điều này. Ngoài ra, cơ sở lý luận của bạn để sử dụng điều này so với sử dụng Singleton là gì? Còn những vấn đề mà OP đang gặp phải với mã lặp lại - câu trả lời này được giải quyết như thế nào?
Merlyn Morgan-Graham

1
@Merlyn OP cho biết anh ta có một trình ghi nhật ký như một phần của phương thức khởi tạo hoặc là tài sản công cộng. Anh ta đã không nói rằng anh ta có một hộp chứa DI tiêm giá trị chính xác. Vì vậy, tôi giả định đây không phải là trường hợp. Có, có một số mã lặp lại (nhưng với thuộc tính tự động được quy, chẳng hạn, rất ít), nhưng IMHO tốt hơn nhiều là hiển thị rõ ràng sự phụ thuộc hơn là ẩn nó qua một singleton (toàn cục).
Daniel Rose

1
@DanielRose: Và bạn đã thay đổi suy nghĩ của tôi với "tốt hơn là hiển thị rõ ràng sự phụ thuộc hơn là ẩn nó qua một singleton (toàn cục)". +1
Merlyn Morgan-Graham

25

Câu hỏi hay. Tôi tin rằng trong hầu hết các dự án, trình ghi nhật ký là một singleton.

Một số ý tưởng chỉ nảy ra trong đầu tôi:

  • Sử dụng ServiceLocator (hoặc một vùng chứa Dependency Injection khác nếu bạn đã sử dụng bất kỳ) cho phép bạn chia sẻ trình ghi nhật ký qua các dịch vụ / lớp, theo cách này, bạn có thể khởi tạo trình ghi nhật ký hoặc thậm chí nhiều trình ghi nhật ký khác nhau và chia sẻ qua ServiceLocator, đây rõ ràng sẽ là một singleton , một số kiểu Kiểm soát Nghịch đảo . Cách tiếp cận này cho phép bạn linh hoạt hơn nhiều so với quá trình khởi tạo và khởi tạo trình ghi nhật ký.
  • Nếu bạn cần Logger gần như ở khắp mọi nơi - thực hiện phương pháp khuyến nông cho Objectloại vì vậy mỗi lớp sẽ có thể gọi các phương thức logger của thích LogInfo(), LogDebug(),LogError()

Bạn có thể nói cụ thể hơn, những gì bạn đã nghĩ về các phương pháp mở rộng? Về sử dụng ServiceLocator, tôi tự hỏi tại sao nó sẽ là tốt hơn, so với tiêm bất động sản, như trong mẫu 2.
Giedrius

1
@Giedrius: Về các phương thức mở rộng - bạn có thể tạo các phương thức mở rộng giống như public static void LogInfo(this Object instance, string message)vậy mỗi lớp sẽ chọn nó, Về phần ServiceLocator- điều này cho phép bạn có trình ghi nhật ký như một cá thể lớp thông thường chứ không phải là một đơn lẻ, vì vậy bạn sẽ linh hoạt hơn nhiều
sll

@Giedrius nếu bạn triển khai IoC bằng cách chèn hàm tạo và khung DI mà bạn đang sử dụng có khả năng quét các hàm tạo của bạn và tự động chèn các phần phụ thuộc của bạn (do bạn đã định cấu hình các phần phụ thuộc đó trên quy trình khởi động khung DI của mình), thì theo cách bạn đã sử dụng làm nó sẽ hoạt động tốt. Ngoài ra, hầu hết các khung DI đều cho phép bạn đặt phạm vi vòng đời của từng đối tượng.
GR7 12/12/11

7
ServiceLocator không phải là DI Container. Và nó được coi là phản mẫu.
Piotr Perak

1
OP đã sử dụng DI với hộp chứa DI. Ví dụ đầu tiên là tiêm ctor, ví dụ thứ hai là tiêm thuộc tính. Xem chỉnh sửa của họ.
Merlyn Morgan-Graham

16

Một singleton là một ý kiến ​​hay. Một ý tưởng tốt hơn nữa là sử dụng mẫu Registry , cho phép kiểm soát nhiều hơn một chút đối với việc khởi tạo. Theo tôi, mô hình singleton quá gần với các biến toàn cục. Với việc tạo hoặc sử dụng lại đối tượng xử lý sổ đăng ký, có chỗ cho những thay đổi trong tương lai đối với các quy tắc khởi tạo.

Bản thân Registry có thể là một lớp tĩnh để cung cấp cú pháp đơn giản để truy cập nhật ký:

Registry.Logger.Info("blabla");

6
Lần đầu tiên tôi gặp mẫu Registry. Có phải là đơn giản hóa quá mức khi nói rằng về cơ bản tất cả các biến & hàm toàn cục được đặt dưới một cái ô không?
Joel Goodwin

Ở dạng đơn giản nhất, nó chỉ là một chiếc ô, nhưng việc đặt nó ở vị trí trung tâm giúp bạn có thể thay đổi nó thành một thứ khác (có thể là một nhóm các đối tượng, trường hợp mỗi lần sử dụng, trường hợp luồng cục bộ) khi các yêu cầu thay đổi.
Anders Abel

5
Registry và ServiceLocator là một và giống nhau. Hầu hết các khuôn khổ IoC là cốt lõi của chúng là việc triển khai Registry.
Michael Brown

1
@MikeBrown: Có vẻ như sự khác biệt có thể là vùng chứa DI (nội bộ, mẫu định vị dịch vụ) có xu hướng là một lớp không ổn định, trong khi mẫu đăng ký sử dụng một lớp tĩnh. Nghe có đúng không?
Merlyn Morgan-Graham

1
@MichaelBrown Sự khác biệt là nơi bạn sử dụng bộ định vị đăng ký / dịch vụ. Nếu nó chỉ nằm trong thư mục gốc, thì tốt; nếu bạn sử dụng ở nhiều nơi thì rất tệ.
Onur

10

Một singleton đơn thuần không phải là một ý kiến ​​hay. Rất khó để thay thế bộ ghi nhật ký. Tôi có xu hướng sử dụng các bộ lọc cho trình ghi nhật ký của mình (một số lớp "ồn ào" có thể chỉ ghi cảnh báo / lỗi).

Tôi sử dụng mẫu singleton kết hợp với mẫu proxy cho nhà máy ghi nhật ký:

public class LogFactory
{
    private static LogFactory _instance;

    public static void Assign(LogFactory instance)
    {
        _instance = instance;
    }

    public static LogFactory Instance
    {
        get { _instance ?? (_instance = new LogFactory()); }
    }

    public virtual ILogger GetLogger<T>()
    {
        return new SystemDebugLogger();
    }
}

Điều này cho phép tôi tạo một FilteringLogFactoryhoặc chỉ một SimpleFileLogFactorymà không cần thay đổi bất kỳ mã nào (và do đó tuân thủ nguyên tắc Mở / Đóng).

Phần mở rộng mẫu

public class FilteredLogFactory : LogFactory
{
    public override ILogger GetLogger<T>()
    {
        if (typeof(ITextParser).IsAssignableFrom(typeof(T)))
            return new FilteredLogger(typeof(T));

        return new FileLogger(@"C:\Logs\MyApp.log");
    }
}

Và sử dụng nhà máy mới

// and to use the new log factory (somewhere early in the application):
LogFactory.Assign(new FilteredLogFactory());

Trong lớp học của bạn sẽ ghi:

public class MyUserService : IUserService
{
    ILogger _logger = LogFactory.Instance.GetLogger<MyUserService>();

    public void SomeMethod()
    {
        _logger.Debug("Welcome world!");
    }
}

Đừng bận tâm đến nhận xét trước đây của tôi. Tôi nghĩ tôi thấy những gì bạn đang làm ở đây. Bạn có thể cung cấp một ví dụ về một lớp thực sự đăng nhập vào điều này không?
Merlyn Morgan-Graham

+1; Chắc chắn đánh bại một singleton trần trụi :) Vẫn có một số khớp nối nhỏ và tiềm ẩn các vấn đề về phạm vi tĩnh / luồng do sử dụng các đối tượng tĩnh, nhưng tôi tưởng tượng những điều đó rất hiếm (bạn muốn đặt Instancetrong thư mục gốc của ứng dụng và tôi không biết lý do tại sao bạn muốn thiết lập lại nó)
Merlyn Morgan-Graham

1
@ MerlynMorgan-Graham: Bạn không thể thay đổi nhà máy sau khi đặt nó. Mọi sửa đổi sẽ được thực hiện trong nhà máy triển khai và bạn có toàn quyền kiểm soát điều đó. Tôi sẽ không đề xuất mẫu này như một giải pháp phổ biến cho các đơn lẻ, nhưng nó hoạt động tốt cho các nhà máy vì API của chúng hiếm khi thay đổi. (do đó bạn có thể gọi nó là proxy trừu tượng singleton nhà máy, hehe, kiểu mash-up)
jgauffin

3

Có một cuốn sách Dependency Injection trong .NET. Dựa trên những gì bạn cần, bạn nên sử dụng đánh chặn.

Trong cuốn sách này có một sơ đồ giúp quyết định xem có nên sử dụng Hàm chèn khối dẫn, tiêm thuộc tính, tiêm phương pháp, Bối cảnh xung quanh, Đánh chặn hay không.

Đó là cách một lý do sử dụng sơ đồ này:

  1. Bạn có phụ thuộc hoặc cần nó? - Cần nó
  2. Đó có phải là mối quan tâm xuyên suốt không? - Đúng
  3. Bạn cần câu trả lời từ nó? - Không

Sử dụng Đánh chặn


Tôi nghĩ rằng có một sai lầm trong câu hỏi suy luận đầu tiên (tôi nghĩ nó phải là "Bạn có phụ thuộc hay cần nó?"). Dù sao, +1 cho việc đề cập đến Đánh chặn, nghiên cứu các khả năng của nó trong Windsor và nó trông rất thú vị. Nếu bạn có thể đưa ra thêm ví dụ về cách bạn tưởng tượng nó sẽ phù hợp ở đây, điều đó thật tuyệt.
Giedrius

+1; Đề xuất thú vị - về cơ bản sẽ cung cấp chức năng giống AOP mà không cần phải chạm vào các loại. Ý kiến của tôi (dựa trên sự tiếp xúc rất hạn chế) là việc đánh chặn thông qua tạo proxy (loại thư viện DI có thể cung cấp trái ngược với thư viện dựa trên thuộc tính AOP) giống như ma thuật đen và có thể khiến bạn hơi khó hiểu đang xảy ra. Có phải sự hiểu biết của tôi về cách thức hoạt động này không chính xác? Bất cứ ai đã có một kinh nghiệm khác nhau với nó? Nó có ít đáng sợ hơn nó âm thanh?
Merlyn Morgan-Graham

2
Nếu bạn cảm thấy Interception là quá 'ma thuật' thì bạn có thể chỉ cần sử dụng mẫu thiết kế trang trí và tự thực hiện nó. Nhưng sau đó có lẽ bạn sẽ nhận ra rằng thật lãng phí thời gian.
Piotr Perak

Tôi giả định rằng gợi ý này sẽ tự động kết thúc mỗi cuộc gọi trong việc ghi nhật ký, trái ngược với việc để (tạo) lớp tự ghi nhật ký. Đúng không?
Merlyn Morgan-Graham

Đúng. Đó là những gì người trang trí làm.
Piotr Perak

0

Một giải pháp khác mà cá nhân tôi thấy dễ dàng nhất là sử dụng lớp Logger tĩnh. Bạn có thể gọi nó từ bất kỳ phương thức lớp nào mà không cần phải thay đổi lớp, ví dụ như thêm thuộc tính vào, v.v. Nó khá đơn giản và dễ sử dụng.

Logger::initialize ("filename.log", Logger::LEVEL_ERROR); // only need to be called once in your application

Logger::log ("my error message", Logger::LEVEL_ERROR); // to be used in every method where needed

-1

Nếu bạn muốn xem xét một giải pháp tốt để đăng nhập Tôi đề nghị bạn nhìn vào động cơ ứng dụng google với python nơi khai thác gỗ cũng đơn giản như import loggingvà sau đó bạn có thể chỉ logging.debug("my message")hay logging.info("my message")mà thực sự giữ nó đơn giản như nó phải.

Java không có một giải pháp tốt để ghi nhật ký, tức là nên tránh log4j vì nó thực tế buộc bạn phải sử dụng các singleton mà như đã trả lời ở đây là "khủng khiếp" và tôi đã có kinh nghiệm khủng khiếp khi cố gắng tạo ra kết quả ghi nhật ký cùng một câu lệnh ghi nhật ký chỉ một lần khi tôi nghi ngờ rằng lý do ghi nhật ký kép là do tôi có một Singleton của đối tượng ghi nhật ký trong hai bộ nạp lớp trong cùng một máy ảo (!)

Tôi cầu xin sự tha thứ của bạn vì không quá cụ thể cho C # nhưng từ những gì tôi đã thấy các giải pháp với C # trông giống với Java nơi chúng ta đã có log4j và chúng ta cũng nên biến nó thành một singleton.

Đó là lý do tại sao tôi thực sự thích giải pháp với GAE / python , nó đơn giản nhất có thể và bạn không phải lo lắng về bộ tải lớp, nhận câu lệnh ghi nhật ký kép hoặc bất kỳ mẫu thiết kế nào cho vấn đề đó.

Tôi hy vọng một số thông tin này có thể liên quan đến bạn và tôi hy vọng rằng bạn muốn xem xét giải pháp ghi nhật ký của tôi mà tôi đề xuất thay vì tôi bắt nạt về mức độ vấn đề mà Singleton bị nghi ngờ do không thể có một singleton thực sự khi nó phải được cài đặt trong một số bộ nạp lớp.


không phải mã tương đương trong C # là Singleton (hoặc một lớp tĩnh, có khả năng giống nhau, có khả năng tệ hơn, vì nó cung cấp ít tính linh hoạt hơn)? using Logging; /* ... */ Logging.Info("my message");
Merlyn Morgan-Graham

Và bạn có thể tránh các singleton với mẫu log4j nếu bạn sử dụng phương thức tiêm phụ thuộc để tiêm các trình ghi nhật ký của mình. Rất nhiều thư viện làm điều này. Bạn cũng có thể sử dụng các trình bao bọc cung cấp giao diện chung để bạn có thể hoán đổi việc triển khai ghi nhật ký của mình vào một ngày sau đó, như netcommon.sourceforge.net hoặc một trong các tiện ích mở rộng được cung cấp bởi các thư viện vùng chứa DI như Castle.Windsor hoặc Ninject
Merlyn Morgan-Graham

Đó chính là vấn đề! Không ai không bao giờ sử dụng logging.info("my message")trong một chương trình phức tạp hơn hello world. Thông thường, bạn thực hiện khá nhiều thao tác khởi tạo trình ghi nhật ký - thiết lập trình định dạng, cấp độ, thiết lập cả trình xử lý tệp và bảng điều khiển. Nó không bao giờ bao giờ logging.info("my message")!
The Godfather
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.