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


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.