c# 프로젝트 진행 시작부터 막히고 있습니다..ㅜ

현재 회사에서 c#을 사용하고 있는데, 현재 회사에서 c#을 처음 사용하는 것이어서 많이 서툽니다…ㅜ

지금 로그파서 플러그인 개발 프로젝트를 진행 중인데,
나중에 여러 다른 업체에 대해서도 로그파서를 만들어주는 프로젝트를 진행하게 될 수도 있으니
그때마다 처음부터 개발하는건 비효율적이니
바뀌지 않는 코어 코드를 작성해서 프로젝트를 진행할 때마다 업체에 커스터마이징만 해서 개발 가능하도록 구조를 짜달라는데…

솔직히 어떻게 하라는 건지 잘 모르겠습니다…
관련 라이브러리…를 구성하라는 걸까요…?ㅜ

요구하신 분도 정확히 어떻게 구성하기를 바라는 것이 아니고 그냥 그런식으로 만들 수 없냐…라고만 말씀하시는거라…

c#을 접한게 고작 1년인 저는 도움이 절실합니다…ㅠㅠ

2개의 좋아요

로그 파서가 정확히 어떤것을 말씀하시는지 잘모르곘지만
일단 개인적으로 바뀌지 않는 코어 코드 라는 개념부터 좀 이상합니다.
pseudo code 개념이면 모를까요

일단 업체 입장으로 프로토 타입을 만들어보고
필요 정보를 표시하는 화면을 만든다음
그 해당 정보 마다 일일이 method화 해서
library 구성을 하면 되는데??
이걸 dll로 제공한다는것 비효율적이고 보안상 문제도 많아요

rest api 로 특정 전문을 보내면 retrun 하는 개념이나

Saas 개념이 맞는것 같은데 좀 난이도가 있는 시스템 구축입니다.
그리고 단순 log라면 elastic search 로 코드 없이 구성 하는 방법도 있습니다.

3개의 좋아요

정확한 상황을 알 수 없기 때문에 추측을 근거로 이야기해야 하는 점 먼저 양해 구하겠습니다.

로그 파서라는게 제가 보기에는 데이터 애널리틱스를 위해서 서버나 클라이언트가 만들어낸 로그를 구조화된 자료로 분석하는 도구를 말하는 것 같은데요, 로그 파서가 다루어야 할 로그의 종류가 정형화되어있지 않다면, 그 때 그 때 로그의 변화폭은 “플러그인” 형태로 개발해서 추가하거나 제거할 수 있도록 하기 위함으로 보입니다.

이 때 코어 코드가 바뀌지 않는다는 의미는, 의존성 주입 (Dependency Injection)을 아키텍처로 채택해서 로그 파서 인터페이스만 외부에 제공하면, 외부 코드는 이 인터페이스만 보고 호출하면 원하는 결과를 얻을 수 있다는 "가정"을 제공하는 것을 말하는 것 같습니다. 그리고 이것을 대행해주는 핵심부가 아마 고객이 이야기한 코어 코드에 해당이 될 것 같고요!

의존성 주입을 넘어서서 실제로 눈에 보이는 실체를 만들려면, 닷넷 기준으로는 어셈블리 불러오기 기능을 구현해서 플러그인 모듈을 더하거나 뺄 수 있게 만드는 것이 필요할 수 있습니다.

그리고 API를 소비하는 컨슈머 입장에서는 호출을 일반화하는 것 말고도, 만들어져서 나올 데이터에 대한 정의도 서로 말을 맞출 필요가 있을 것인데요, 이것은 추상화가 가능한 영역은 아니고, 상호간에 명세나 프로토콜 정의를 잘 해서 문서화 해두는 것이 중요해 보이고, 문서화한 내용을 근거로 개발과 테스트가 이루어져야 할 것 같습니다.

3개의 좋아요

답변 너무 감사합니다.
코어 코드를 말씀하신건 고객사는 아니고 저희 팀장님께서 이번 플젝부터 그렇게 구성해라! 라고 요청하신 사항이라 저희 내부적으로만 가지고 있게 될 것 같긴 합니다!! (코어코드라는 말 자체도 저도 이상하다고 생각하지만요…ㅜㅜ)
말씀해주신 개념들도 더 조사해서 참고하겠습니다!!
감사합니다ㅠㅠ

2개의 좋아요

네네 대락 맞습니다!! 다만 제가 담당하는 부분은 고객사 컴퓨터에서 발생하는 로그 파일을 수집해서 저희 서버로 저장하는 기능만을 하는 플러그인 개발입니다!!

말씀해주신 "의존성 주입"에 대해서는 고객사는 아니고 저희 팀장님께서 내부적으로 지시하신 사항입니다, 이후 비슷한 프로젝트를 진행할 경우 그렇게 해야하지 않겠냐…라는 의미로…

말씀해주신 부분이 거의 정확히 저희 팀장님께서 바라시는 개념인 것 같아요!!ㅠㅠ 혹시 인터페이스를 제공한다는 것이 어떤 형태로 구성되어 외부에 제공이 가능한 것인지 여쭤볼 수 있을까요??

2개의 좋아요

닷넷에서 로그는 “카테고리”, “레벨”, "메시지"로 구성됩니다.

  • 카테고리: string 형식으로 비정형적인 문자열인데, 보통 호출코드가 속한 클래스의 Full name을 사용합니다.
  • 레벨: 보통 Information, Warning, Debug 등으로 사전에 지정합니다.
  • 메시지: 역시 string 형식으로 비정형적입니다.

아래는 Asp.Net Core 웹이 콘솔에 남긴 로그의 일부입니다.

info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\C#\Biz\UI.Web

warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
      Unhandled exception rendering component: InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': 'col-auto""' is not a valid attribute name.
      System.InvalidOperationException: InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': 'col-auto""' is not a valid attribute name.
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.InvokeRenderCompletedCallsAfterUpdateDisplayTask(Task updateDisplayTask, Int32[] updatedComponents)

fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
      Unhandled exception in circuit 'DF8JgtUw_z6wObgr6rnMgCckfYyZzXziXX_64q4VnPY'.
      System.AggregateException: One or more errors occurred. (InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': 'col-auto""' is not a valid attribute name.)
       ---> System.InvalidOperationException: InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': 'col-auto""' is not a valid attribute name.
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.InvokeRenderCompletedCallsAfterUpdateDisplayTask(Task updateDisplayTask, Int32[] updatedComponents)
         --- End of inner exception stack trace ---

위 로그는 닷넷의 기본 로그 제공자 중에 하나인 콘솔 로그 제공자가 남긴 것으로, 콘솔 로그 제공자는 로그를 화면에 출력하도록 설정되어 있습니다. 그 형식은

{레벨항목}:{빈칸}{카테고리}{/n}
{}{메시지}{/n}

info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\C#\Biz\UI.Web

만약 파일로 저장하도록 설정되어 있다면, 특정 파일에 아래와 같이 남길 것입니다.

info: Microsoft.Hosting.Lifetime[0]\n    Content root path: C:\C#\Biz\UI.Web\nwarn: ...

로그 파서의 역할은 고객사의 로그에서 레벨, 카테고리, 메시지를 구별하는 것고, 커스터마이징이라고 하면, "로그 레벨"을 사전에 정의하는 것을 의미할 것 같습니다.

레벨 정보는 그때 그때 고객사에 물어 보고, 이를 파서 앱의 설정 파일(.json)에 저장하면 될 것 같습니다. 예를 들면,

//logstructure.json for a customer
{
    "levels" : ["warning", "information", ... ]
}

만약, 각 레벨에 대한 Regex 를 사전에 정의해 놓으면 파서의 코드는 더욱 정형적이 될 것입니다.

//levelregex.json
{
    "Information" : "^info:.*$",
    ...
}

파서가 고객사 로그 파싱을 완료하면, 이를 다시 닷넷의 로깅 도구를 사용하여 로깅합니다. 이때, 커스텀 로깅 제공자를 정의하여, 회사의 로그 저장소로 바로 저장하도록 만들면 될 것 같습니다.

Implement a custom logging provider - .NET | Microsoft Learn

콘솔 로그 제공자는 로그를 화면에 뿌려준다.
=> 커스텀 로그 제공자는 로그를 회사의 저장소에 저장한다.

이러한 구조로 파서를 정의해 두면, 나중에 할 일은 각 회사 마다 .json 파일을 정의하는 거 빼곤 할 일이 없습니다. 모든 케이스를 전부 커버할 수는 없겠지만, 대부분의 케이스에는 적용될 것 같네요.

4개의 좋아요

저도 초보를 막 벗어 났습니다만, @BigSquare 님의 의견에 덧붙혀서,
저는 개인적으로 Serilog를 사용하고 있는데요.
닷네에서 제공하는 로그 기능외에도 Serilog는 Sink라는 기능을 제공합니다.
즉, 로그를 출력하는 대상을 여러가지로 지정할 수 있더라고요.
콘솔, 파일 외에도 많이 지원을 합니다.

또한, json 직렬화를 지원해서 로그를 남길 수 있어서 좋았습니다.

var fruit = new Dictionary<string,int> {{ "Apple", 1}, { "Pear", 5 }};
Log.Information("In my bowl I have {Fruit}", fruit);

{ “Fruit”: { “Apple”: 1, “Pear”: 5 }}

5개의 좋아요

의존성 주입은 크게 두 가지 형태로 작동하게 되는데요,

  1. 어떤 장치를 원격에서 제어하는 코드를 만든다고 가정해보겠습니다. 장치의 종류와 특성은 알지 못하지만, 어쨌든 예약된 시간에 "무엇인가 일을 하게 한다"는 요구 사항이 있을 수 있습니다. 이 때, 각 장치를 제어하는 코드를 클래스로 만들고, 이 클래스는 미리 약속된 IDevice라는 인터페이스를 구현하고 있다고 가정하겠습니다. 그러면, 예약된 시간에 무엇인가 일을 하게 하는 행위의 주체는 장치의 종류나 특성을 알지 못한다고 해도, IDevice.DoTask() 라는 함수와 약속된 인자를 주기만 하면 됩니다. 여기서 IDevice 인터페이스를 "제공"한다고 볼 수 있습니다.

  2. 반대로 IDevice 인터페이스를 구현하는 GEWeavingMachine (GE 직물기)라는 어떤 장치용 드라이버 클래스가 있다고 가정해보겠습니다. 이 클래스는 GE 직물기를 동작시키기 위한 디테일한 내용을 담고 있을 것인데, 이를 구현하기 위해서는 여러 가지 부가적인 구성 요소들이 필요할 수 있습니다. 이 때, 매번 필요한 구성 요소들을 생성자나 함수 인자 등으로 받으려고 하면 코드도 불필요하게 복잡해지고 커지게 됩니다. 이 때, 의존성 주입을 이용해서 기본적으로는 어떤 클래스나 인터페이스 타입의 인스턴스가 필요함을 명시하는 것부터, 별도로 라벨링해둔 조건과 일치하는 인스턴스를 정확히 찾아 연결할 것을 지시하는 등 실제 코드와 관련이 없는 초기화에 관련된 작업을 크게 줄일 수 있습니다.

위의 두 가지 관점에서 의존성 주입을 활용할 수 있을 것이고, 질문해주신 "인터페이스 제공"은 1의 유스케이스가 아닐까 생각합니다.

4개의 좋아요

제가 파싱 후 닷넷의 로깅을 이용하도록 추천한 것은 범용성과 간편성 때문입니다.

이는 고객사에 개발자가 있는 케이스에 대비하기 위한 것으로 이 때는 파서 모듈을 주는 것이 아니라, 로그 제공자만 주면 되기 때문입니다.

이 때, 고객사의 개발자 입장에서는, 외부 패키지(Serilog)에 대한 의존성이 있는 형태보다는 없는 형태가 더 간편할 것이기 때문입니다.

2개의 좋아요