로거를 싱글 톤으로 사용하는 것이 좋은 방법입니까?


81

로거를 생성자에게 전달하는 습관이 있습니다.

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

그러나 그것은 매우 성가신 일이므로 한동안 이것을 속성으로 사용했습니다.

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

이것도 짜증이납니다. 건조하지 않습니다. 모든 수업에서 이것을 반복해야합니다. 기본 클래스를 사용할 수 있지만 다시-Form 클래스를 사용하고 있으므로 FormBase 등이 필요합니다. 따라서 ILogger가 노출 된 싱글 톤을 사용하는 것의 단점이 무엇인지, 그래서 누구나 로거를 얻을 수있는 위치를 알 것입니다.

    Infrastructure.Logger.Info("blabla");

업데이트 : Merlyn이 올바르게 알아 차렸 듯이 첫 번째와 두 번째 예제에서 DI를 사용하고 있음을 언급해야합니다.



1
내가보고있는 답변에서 사람들은 두 가지 예에서 당신이 주사 하고 있다는 것을 깨닫지 못한 것 같습니다 . 질문에서 이것을 더 명확히 할 수 있습니까? 일부를 처리하기 위해 태그를 추가했습니다.
— Merlyn Morgan-Graham

1
이 기사의 댓글을 읽어 보세요. 의존성 주입 신화 : 테스트와 의존성 주입에 꽤 큰 Google의 Miško Hevery의 참조 전달 . 그는 로깅은 로거의 출력을 테스트 할 필요가없는 한 싱글 톤이 괜찮은 예외라고 말합니다 (이 경우 DI 사용).
— User

답변:


35

이것도 짜증이납니다- 건조 하지 않습니다

사실입니다. 그러나 당신이 가진 모든 유형에 널리 퍼져있는 교차 절단 문제에 대해 당신이 할 수있는 일은 너무나 많습니다. 당신은해야 사용할 당신이 그 유형에 대한 속성을 가지고 있어야하므로, 사방 로거.

그래서 우리가 그것에 대해 무엇을 할 수 있는지 봅시다.

하나씩 일어나는 것

싱글 톤은 끔찍 <flame-suit-on>합니다.

두 번째 예제에서 한 것처럼 속성 주입을 고수하는 것이 좋습니다. 이것은 마법에 의지하지 않고 할 수있는 최고의 팩토링입니다. 싱글 톤을 통해 숨기는 것보다 명시적인 종속성을 갖는 것이 좋습니다.

그러나 싱글 톤이 당신이해야 할 모든 리팩토링을 포함하여 상당한 시간을 절약한다면 (수정 구슬 시간!), 나는 당신이 그들과 함께 살 수있을 것이라고 생각합니다. Singleton에 대한 용도가 있다면 이것이 그럴 수도 있습니다. 당신이 경우 마음에 비용을 유지 이제까지 이수록 높은 약이 될 것입니다 당신의 마음을 변경하고 싶습니다.

이렇게하면 패턴 (설명 참조)을 사용 하는Registry 다른 사람들의 답변 과 싱글 톤 로거 인스턴스가 아닌 (재설정 가능한) 싱글 톤 팩토리를 등록하는 사람들의 답변을 확인하십시오 .

많은 타협없이 잘 작동 할 수있는 다른 대안이 있으므로 먼저 확인해야합니다.

Visual Studio 코드 조각

Visual Studio 코드 조각 을 사용 하여 반복되는 코드의 진입 속도를 높일 수 있습니다. 와 같은 것을 입력 할 수 loggertab있으며 코드가 마술처럼 나타납니다.

AOP를 사용하여 건조

PostSharp와 같은 AOP (Aspect Oriented Programming) 프레임 워크를 사용 하여 일부를 자동 생성 하여 속성 주입 코드를 약간 제거 할 수 있습니다.

완료되면 다음과 같이 보일 수 있습니다.

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

또한 메서드 추적 샘플 코드 를 사용 하여 메서드 시작 및 종료 코드 를 자동으로 추적 할 수 있으므로 일부 로거 속성을 모두 함께 추가 할 필요가 없습니다. 클래스 수준 또는 네임 스페이스 전체에서 속성을 적용 할 수 있습니다.

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

// or

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

3
싱글 톤의 경우 +1은 끔찍합니다. 여러 클래스 로더와 함께 싱글 톤을 사용해보십시오. 즉, 디스패처가 이중 로깅의 끔찍한 예를 경험 한 클라이언트 인 애플리케이션 서버 환경 (하지만 일부 C ++ 프로그래머가 말한 잘못된 위치에서 오는 로깅 문만큼 끔찍하지는 않습니다)
— Niklas R.

1
@NickRosencrantz +1 : C ++ 공포 이야기; 나는 싱글 톤의 끔찍함을 생각하는 데 몇 분을 보내는 것을 좋아한다. 스크롤을 내리고 "하하 롤 적어도 나는 그들의 문제가 없다."
— Domenic 2011

32
</flame-suit-on> 5 년 동안 화염 복을 입고 어떻게 살았는지 모르겠지만 도움이 되었으면합니다.
— Brennen Sprimont

39

내 종속성 주입 컨테이너에 로거 인스턴스를 넣은 다음 필요한 클래스에 로거를 주입합니다.


8
좀 더 구체적으로 말씀해 주시겠습니까? 나는 그것이 문제의 1 개 및 2 개 코드 샘플에서 내가 한 일이라고 생각하기 때문에?
— Giedrius

2
"using"클래스는 기본적으로 이미 가지고있는 것과 동일하게 보일 것입니다. 그러나 실제 값은 DI 컨테이너가 자동으로 수행하므로 클래스에 수동으로 제공 할 필요가 없습니다. 이렇게하면 정적 싱글 톤 로거를 사용하는 것보다 테스트에서 로거를 교체하는 것이 훨씬 쉽습니다.
— Daniel Rose

2
이것이 정답 (IMO) 임에도 불구하고, OP는 이미이 일을하고 있다고 말했기 때문입니다. 또한 이것을 사용하는 것과 Singleton을 사용하는 이유는 무엇입니까? 반복적 인 코드로 OP가 갖는 문제는 어떻습니까?이 답변으로 어떻게 해결됩니까?
— Merlyn Morgan-Graham

1
@Merlyn OP는 생성자의 일부 또는 공용 속성으로 로거가 있다고 말했습니다. 그는 DI 컨테이너가 올바른 값을 주입한다고 말하지 않았습니다. 그래서 나는 이것이 사실이 아니라고 가정하고 있습니다. 예, 몇 가지 반복되는 코드가 있지만 (예를 들어 속성이있는 auto 속성은 매우 적습니다) IMHO는 (전역) 싱글 톤을 통해 숨기는 것보다 종속성을 명시 적으로 표시하는 것이 훨씬 낫습니다.
— 다니엘 로즈

1
@DanielRose : "(전역) 싱글 톤을 통해 종속성을 숨기는 것보다 명시 적으로 종속성을 표시하는 것이 훨씬 낫습니다"로 제 마음을 바꿨습니다. +1
— Merlyn Morgan-Graham

25

좋은 질문. 나는 대부분의 프로젝트 로거가 싱글 톤이라고 믿습니다.

몇 가지 아이디어가 떠 오릅니다.

  • 사용 ServiceLocator (또는 다른 의존성 삽입 이미 하나를 사용하는 경우 컨테이너) 싱글 것이 분명 당신이 ServiceLocator를 통해 로거 또는 여러 다른 로거 및 공유를 인스턴스화 할 수있는이 방법으로, 서비스 / 클래스에서 공유 로거을 수행 할 수 있습니다 , 일종의 반전 제어 . 이 접근 방식은 로거 인스턴스화 및 초기화 프로세스에 대해 많은 유연성을 제공합니다.
  • 당신은 거의 모든 곳에서 로거 필요가있는 경우 -에 대한 확장 메서드를 구현하는 Object각 클래스 로거의 방법처럼 호출 할 수있을 것입니다, 그래서 유형 LogInfo(), LogDebug(),LogError()

좀 더 구체적으로 말씀해 주시겠습니까? 확장 방법에 대해 염두에 두었던 것이 있습니까? ServiceLocator 사용과 관련하여 샘플 2와 같이 속성 주입보다 왜 더 나은지 궁금합니다.
— Giedrius

1
@Giedrius : 확장 메소드와 public static void LogInfo(this Object instance, string message)관련하여 ServiceLocator- 각 클래스가 선택할 수 있도록 확장 메소드를 생성 할 수 있습니다 . 이와 관련하여 -로거를 싱글 톤이 아닌 일반 클래스 인스턴스로 사용할 수 있으므로 많은 유연성을 제공 할 것입니다
— sll

생성자 주입으로 IoC를 구현하고 사용중인 DI 프레임 워크에 생성자를 스캔하고 종속성을 자동으로 주입 할 수있는 기능이있는 경우 @Giedrius (DI 프레임 워크 부트 스트랩 프로세스에서 이러한 종속성을 구성한 경우) 잘 작동 할 것입니다. 또한 대부분의 DI 프레임 워크에서는 각 개체의 수명주기 범위를 설정할 수 있습니다.
— GR7

7
ServiceLocator는 DI 컨테이너가 아닙니다. 그리고 안티 패턴으로 간주됩니다.
— Piotr Perak 2011

1
OP는 이미 DI 컨테이너와 함께 DI를 사용하고 있습니다. 첫 번째 예제는 ctor 주입이고 두 번째 예제는 속성 주입입니다. 그들의 편집을보십시오.
— Merlyn Morgan-Graham

16

싱글 톤은 좋은 생각입니다. 더 나은 아이디어는 레지스트리 패턴 을 사용하는 것입니다.이 패턴은 인스턴스화를 좀 더 제어 할 수 있습니다. 제 생각에는 싱글 톤 패턴이 전역 변수에 너무 가깝습니다. 개체 생성 또는 재사용을 처리하는 레지스트리를 사용하면 나중에 인스턴스화 규칙을 변경할 여지가 있습니다.

레지스트리 자체는 로그에 액세스하기위한 간단한 구문을 제공하는 정적 클래스가 될 수 있습니다.

Registry.Logger.Info("blabla");

6
처음으로 레지스트리 패턴을 발견했습니다. 기본적으로 모든 전역 변수와 함수가 하나의 우산 아래 놓인다고 말하는 것이 지나치게 단순화 된 것입니까?
— Joel Goodwin 2011

가장 단순한 형태에서는 우산 일 뿐이지 만 중앙에두면 요구 사항이 변경 될 때 다른 것으로 변경할 수 있습니다 (객체 풀, 사용 당 인스턴스, 스레드-로컬 인스턴스).
— Anders Abel 2011

5
레지스트리와 ServiceLocator는 거의 하나이며 동일합니다. 대부분의 IoC 프레임 워크는 레지스트리 구현의 핵심입니다.
— Michael Brown

1
@MikeBrown : 차이점은 DI 컨테이너 (내부적으로 서비스 로케이터 패턴)는 인스턴스화 된 클래스 인 반면 레지스트리 패턴은 정적 클래스를 사용한다는 것입니다. 그게 맞나요?
— Merlyn Morgan-Graham

1
@MichaelBrown 차이점은 레지스트리 / 서비스 로케이터를 사용하는 곳입니다. 컴포지션 루트에 있으면 괜찮습니다. 여러 곳에서 사용하면 나쁘다.
— Onur

10

평범한 싱글 톤은 좋은 생각이 아닙니다. 로거 교체가 어렵습니다. 로거에 필터를 사용하는 경향이 있습니다 (일부 "시끄러운"클래스는 경고 / 오류 만 기록 할 수 있습니다).

로거 팩토리의 프록시 패턴과 결합 된 싱글 톤 패턴을 사용합니다.

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();
    }
}

이 날은 만들 수 있습니다 FilteringLogFactory또는 단지를SimpleFileLogFactory 코드를 변경 (따라서 오픈 / 청산 원칙을 준수)없이.

샘플 확장

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");
    }
}

그리고 새 공장을 사용하려면

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

기록해야하는 수업에서 :

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

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

내 이전 댓글은 신경 쓰지 마세요. 당신이 여기서 뭘하는지 알 것 같아요. 실제로 여기에 로깅하는 클래스의 예를 제공 할 수 있습니까?
— Merlyn Morgan-Graham

+1; 확실히 네이 키드 싱글 톤을 능가합니다. :) 정적 객체 사용으로 인해 여전히 약간의 결합과 정적 범위 / 스레딩 문제가 발생할 가능성이 있지만 이러한 문제는 드물다고 생각 Instance합니다 (응용 프로그램 루트 에 설정하고 싶습니다. 왜)를 재설정 할 것
— Merlyn 모건 - 그레이엄에게

1
@ MerlynMorgan-Graham : 공장을 설정 한 후에는 공장을 변경할 가능성이 거의 없습니다. 모든 수정은 구현 공장에서 수행되며이를 완전히 제어 할 수 있습니다. 이 패턴을 싱글 톤에 대한 범용 솔루션으로 권장하지는 않지만 API가 거의 변경되지 않기 때문에 공장에서는 잘 작동합니다. (당신이 호출 할 수 있도록 그것은, 도니는 다르게, 추상적 인 공장 싱글을 프록시 패턴 매쉬업)
— jgauffin

3

.NET에는 책 종속성 주입이 있습니다. 필요한 것에 따라 차단을 사용해야합니다.

이 책에는 Constructor injection, property injection, method injection, Ambient Context, Interception을 사용할지 여부를 결정하는 데 도움이되는 다이어그램이 있습니다.

이것이이 다이어그램을 사용하는 한 가지 이유입니다.

  1. 의존성이 있거나 필요합니까? - 필요해
  2. 교차 절단 문제입니까? - 예
  3. 답변이 필요하십니까? - 아니

차단 사용


나는 첫 번째 추론 질문에 실수가 있다고 생각합니다 ( "당신은 의존성이 있습니까, 아니면 필요합니까?"라고 생각합니다). 어쨌든, Interception을 언급하고 윈저에서 가능성을 연구하는 데 +1은 매우 흥미로워 보입니다. 여기에 어떻게 들어 맞을지 예를 들어 설명해 주시면 좋을 것입니다.
— Giedrius

+1; 흥미로운 제안-기본적으로 유형을 건드리지 않고도 AOP와 유사한 기능을 제공합니다. 내 의견 ( 매우 제한된 노출을 기반으로 함 )은 프록시 생성 (DI 라이브러리가 AOP 속성 기반 라이브러리와 달리 제공 할 수있는 유형)을 통한 가로 채기가 흑 마법처럼 느껴지며 무엇이 무엇인지 이해하기 어렵게 만들 수 있다는 것입니다. 계속. 이것이 어떻게 작동하는지에 대한 나의 이해가 잘못 되었습니까? 누구 와 다른 경험이 있습니까? 소리보다 덜 무서운가요?
— Merlyn Morgan-Graham 2011

2
Interception이 훨씬 '마법'이라고 느끼면 데코레이터 디자인 패턴을 사용하여 직접 구현할 수 있습니다. 그러나 그 후에는 아마도 시간 낭비라는 것을 깨닫게 될 것입니다.
— Piotr Perak 2011

이 제안은 클래스 로그 자체를 작성 (만들기)하는 것과는 반대로 로깅에서 각 호출을 자동으로 래핑한다고 가정했습니다. 그 맞습니까?
— Merlyn Morgan-Graham

예. 그것이 데코레이터가하는 일입니다.
— Piotr Perak 2011

0

개인적으로 가장 쉬운 또 다른 해결책은 정적 Logger 클래스를 사용하는 것입니다. 클래스를 변경하지 않고도 모든 클래스 메소드에서 호출 할 수 있습니다 (예 : 속성 주입 추가 등). 매우 간단하고 사용하기 쉽습니다.

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

당신이 로그인하기위한 좋은 해결책을보고 싶은 경우에 당신이 로깅 한 간단하다 파이썬과 구글 앱 엔진을보고 제안 import logging하고 할 수 있습니다 단지 logging.debug("my message")또는 logging.info("my message")예상대로 정말 간단로 유지한다.

Java는 로깅을위한 좋은 솔루션이 없었습니다. 즉, log4j는 실제로 여기에 "끔찍한"싱글 톤을 사용하도록 강제하기 때문에 피해야합니다. 로깅 출력을 동일한 로깅 명령문으로 한 번만 만들려고 시도한 끔찍한 경험이 있습니다. 이중 로깅의 이유는 동일한 가상 머신 (!)의 두 클래스 로더에 로깅 개체의 Singleton이 하나 있다는 것입니다.

C #에 그다지 구체적이지 않은 것에 대해 죄송하지만 C #을 사용한 솔루션은 log4j가있는 Java와 비슷하며 싱글 톤으로 만들어야합니다.

그래서 GAE / python을 사용한 솔루션이 정말 마음에 들었습니다. 가능한 한 간단하며 클래스 로더에 대해 걱정할 필요가 없습니다.

이 정보 중 일부가 귀하와 관련이있을 수 있기를 바라며, 실제 싱글 톤을 가질 수 없기 때문에 싱글 톤이 얼마나 많은 문제를 의심하는지에 대해 협박하는 대신 제가 추천하는 로깅 솔루션을 살펴보고 싶습니다. 여러 클래스 로더에서 인스턴스화되어야합니다.


C #의 동등한 코드가 Singleton (또는 더 적은 유연성을 제공하기 때문에 잠재적으로 동일하거나 잠재적으로 더 나쁜 정적 클래스)이 아닙니까? using Logging; /* ... */ Logging.Info("my message");
— Merlyn Morgan-Graham

또한 종속성 주입을 사용하여 로거를 주입하는 경우 log4j 패턴으로 싱글 톤을 피할 수 있습니다. 많은 도서관이이를 수행합니다. 또한 netcommon.sourceforge.net 또는 Castle.Windsor 또는 Ninject
— Merlyn Morgan-Graham

그게 바로 문제 야! logging.info("my message")Hello World보다 복잡한 프로그램을 사용 하는 사람은 없습니다 . 일반적으로 로거를 초기화하는 많은 상용구 (포맷터, 레벨 설정, 파일 및 콘솔 핸들러 설정)를 수행합니다. 절대로 logging.info("my message")!
— The Godfather
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.