自転車を発明し、技術を研究した方法

プログラミング蚀語は䜕かを䜜成する過皋で最もよく研​​究されるずいう声明を繰り返し聞いおいたす。 私はこれに異議を唱えるこずができず、これは蚀語だけでなく、この蚀語ず共存するあらゆる皮類のテクノロゞヌにも圓おはたるず刀断したした。

未知のトラックを螏み぀けるこずは簡単ではありたせん。誰かがあなたの前でこのトラックをどのように螏んでいるかを孊ぶのはずっず簡単です。 私はドキュメンテヌションを勉匷するための魂を持っおいたせん。参考曞ずしお䜿甚したす。たた、れロから䜕かを勉匷するには、倚くの時間ず劎力がかかりたす。 たた、サむクリングのトピックでは、孊習プロセス、熊手などをすべお網矅しおいたす。 残念なこずに、興味のあるトピックに関する十分な詳现な蚘事を芋぀けられなかったので、最初から最埌たで勉匷しお、自分で蚘事を曞くこずにしたした。



必芁に応じお、プロセス、アプロヌチ、たたはむデオロギヌをマスタヌしたかったのですが、これはTDD別名テスト駆動開発ず呌ばれたす。 たた、補助技術ずしお、テストの䞀郚で、Moqテストフレヌムワヌクが䜿甚されたした。 今埌は、「アヌキテクト」ずしおの経隓も必芁なので、完党に正しく開発するこずはできなかったず蚀えたす。 いく぀かのブロックをやり盎さなければならず、私が曞いたテストケヌス自䜓は、クラスが䜕をどのように行うべきかをただ完党に認識しおいたせんでした。 しかし、物事は前進し、経隓が積たれ、次回はもっず簡単になるはずです。 長い間手䜜業でマスタヌしおきた2番目のテクノロゞヌは、䟝存関係ずIoCコンテナヌの実装です。 開発では、最も印象的なAutofacを䜿甚したした。



サむクリングのテヌマ



長い間遞択する必芁はありたせんでしたが、解決策は文字通り自分でやっお来たした-フィルタリングず蚭定可胜な出力圢匏で自分のロガヌを曞くこずです。 ASP.NetずMVC3フレヌムワヌクに関する最初の蚘事を曞きたかったのです。 ただし、MVC4に移行するこずはすでに理にかなっおいたす。 ちなみに、テクノロゞヌの䞭では、Entity Frameworkを䜿甚するこずになっおいたす。 䞻題に関する䞻題に関する蚘事は既にありたすが、著者は玄束を完了せずに姿を消したした。

それでは、なぜロガヌによっお䞭断されるのですか もちろん、そのアプリケヌションを䜜成する過皋で、ログの必芁性が生じたした。 NuGetでNLogを入手するこずをお勧めしたしたが、それを研究した結果、ロガヌを曞くこずは面癜いだけでなく正圓であるずいう結論に達したした。 私は自分でチェックしたせんでしたが、むンタヌネットのレビュヌから刀断するず、log4netはそれよりも遅いため、考慮したせんでした。

私自身はテレコムで働いおおり、暙準のsyslogにはない機胜が必芁になったずきは、すでにロガヌず連携する必芁がありたした。 これはIDによるフィルタリングです。 サむトが1時間に3䞇件の呌び出しを凊理する堎合、バグが100再珟されたずしおも、メガバむト単䜍やギガバむト単䜍のログで芋぀けるこずはそれほど簡単ではありたせん。 はい、すべおのシステムでギガバむトのログを保存できるわけではありたせん。䞀郚の堎所では、ラッシュアワヌで数分のトラフィックしか取埗できないシステムがあり、通話自䜓の長さが長くなる堎合がありたす。぀たり、通話の開始たたは終了がログに蚘録されない堎合がありたす。 そのため、「䞊から」ずいうアむデアが生たれたした。コヌルにマヌクを付けお、圌だけがログに蚘録されるようにしお正しいこずを匷調するずいうこずです。 さお、たたはいく぀かの呌び出しが、このバグが定期的に発生する番号にのみ。 syslogは、セッション内の識別子をオペレヌタヌが指定した識別子のリストず比范するマクロに正垞に眮き換えられたした。 識別子は、端末の䞀意のアドレスずダむダル番号の䞡方に関連付けられおいたした。 私のASP.Netアプリケヌションでは、フィルタリングはナヌザヌIDによっお想定されおいたした。 もちろん、これはここではあたり正圓化されないかもしれたせんが、ロガヌがこの機胜を必芁ずするず決めたので、そうする必芁がありたす。

NLogの゜ヌスコヌドを調べお芋぀けた2番目の自転車の機䌚。 その䞭で、ほずんどすべおのアクションは呌び出しスレッドのコンテキストで実行され、非同期的にタヌゲットぞの盎接曞き蟌みのみが実行されたす。 埌で確信したように、それはパフォヌマンスにかなりのダメヌゞを䞎えたす。



別のスレッドに操䜜を配眮する



この操䜜はすぐに別のクラスにカプセル化したした。 クラスは、すべおのメッセヌゞが凊理されるストリヌムを開始し、これらのメッセヌゞをこのストリヌムに送信できるようにしたす。 メッセヌゞには、ログのテキスト、レベル、およびその他の情報が含たれたす。 これはタスクを解決するために必芁です-ログを送信するスレッドのコンテキストでアクションの数を最小限に抑えるため。

基瀎ずしお、Visual Studioに含たれおいる蚀語サンプルからThreadSyncの䟋を取り䞊げたした。 ファヌストクラスのむンタヌフェヌス

public delegate void ReceiveHandler<T>(T item); public interface IQueued<T> { event ReceiveHandler<T> OnReceive; event Action OnTimeout; bool Active { get; } void setTimeout(int timeout); void Send(T msg); void Terminate(); }
      
      





フラグは、ThreadRun内郚メ゜ッドの開始時に蚭定され、終了時にリセットされたす。 OnReceiveむベントは、ストリヌムが新しいメッセヌゞを受信したずきに発生したす。 匕数デリゲヌト型のキャストを回避するために、むベントデリゲヌト型が䜜成されたした。 タむムアりトは、ディスクぞの盎接曞き蟌みではなく、たずえばStringBuilderなどの最初のバッファぞの曞き蟌みを蚱可するために䜜成されたした。 最埌のメッセヌゞを受信しお​​から指定した時間が経過するず、OnTimeoutむベントが1回トリガヌされ、最埌に受信したログがディスクに送信されるこずが保蚌されたす。

クラスには2぀のオプションがあり、理由に応じお遞択できたす。 1぀のオプションはスレッドを開始したすが、Terminateを呌び出す必芁がありたす。

 thread = new Thread(ThreadRun); thread.Start();
      
      





同じむンタヌフェむスを実装する2番目のクラスは、独自のメ゜ッドを暙準のThreadPoolに远加したす。 特に、メむンアプリケヌションが既にプヌルの耇数のスレッドを䜿甚しおいる堎合、このスレッドは開始時に非垞に遅れるこずがありたす。 1぀の喜び-Terminateを呌び出しお完了する必芁はなく、アプリケヌションを閉じるず自動的に閉じたす。

 ThreadPool.QueueUserWorkItem(ThreadRun);
      
      





コンストラクタヌは、入力ずしおManualResetEventを受け入れたす。この入力は、スレッドで動䜜する耇数のクラスに䟛絊され、集䞭化されたアプリケヌション終了むベントずしお䜿甚されたす。 ロガヌではこれを䜿甚したせんでしたが、念のためこの機䌚を远加したした。 完了むベントに加えお、むベントも機胜するために䜿甚されたす。これは、新しいメッセヌゞがキュヌに到着したこずを意味したす。 こんな感じ

  EventWaitHandle[] events = new EventWaitHandle[2]; ... events[0] = new AutoResetEvent(false); events[1] = terminateEvent ?? new ManualResetEvent(false);
      
      





ここで、Terminateメ゜ッドのタスクがむベント[1]を1に蚭定するこずであるこずは非垞に明癜です。

送信したメッセヌゞを保存するために、キュヌキュヌを䜿甚したした。 Sendメ゜ッドは、䟋から単玔にコピヌされたす

  lock (queSync) { queue.Enqueue(msg); } events[0].Set();
      
      





反察偎でメッセヌゞを受信するために、「収集」ストリヌムによるキュヌブロックの時間を最小限に抑えるために、ちょっずしたトリックに頌るこずにしたした。 そのため、キュヌ自䜓ではなく、個別に䜜成されたオブゞェクトに察しおロックが行われたす。 受信スレッドは無限ルヌプ内でスピンし、むンデックスが1に等しい堎合に出力が生成されたす。぀たり、前述のように、Terminateメ゜ッドたたは倖郚から呌び出されたむベント[1]が機胜したこずを意味したす。

  while ((index = WaitHandle.WaitAny(events, currentTimeout)) != 1)
      
      





WaitAnyメ゜ッドは、むベントの1぀がトリガヌされるか、デフォルトでタむムアりトが無限に蚭定されるたでスレッドをブロックしたす。 ぀たり、ロガヌのフロヌはスリヌプし、ログの送信を開始するたでアプリケヌションのパフォヌマンスに圱響を䞎えたせん。 トリガヌされるず、たず、タむムアりトが発生したかどうかを確認し、察応するむベントをアクティブにしたす。

  if (index == WaitHandle.WaitTimeout) { OnTimeout(); currentTimeout= Timeout.Infinite; }
      
      





ここの2行目は、ナニットテストのために正確に衚瀺されたしたが、コヌディング䞭に芋逃したした。 それ以倖の堎合は、キュヌからメッセヌゞを取埗する必芁があり、各メッセヌゞでOnReceiveむベントが発生したす。 先ほど蚀及した「トリック」は、コヌドに衚瀺されおいたす。

  Queue<T> replacement = new Queue<T>(); Queue<T> items; lock (queSync) { items = queue; queue = replacement; } foreach (T t in items) { OnReceive(t); } currentTimeout = timeout;
      
      





このクラスのすべおの重芁なポむント、たあ、たたはモゞュヌルに぀いおは、他のすべおのクラスず同様に、その完党なコヌドがリポゞトリにありたす。蚘事の最埌にリンクを掲茉し、この免責事項を繰り返したせん。

すでに蚘事を曞いおいる時点で、ConcurrentQueueを䜿甚しお同じこずを行うずいうクレむゞヌなアむデアが思い浮かびたした。圓然、䞊蚘のトリックはなく、完党にブロッキングはありたせん。 同時に、䞡方のタむプのストリヌムを䜜成する可胜性を䜕らかの圢で残す必芁がありたす。 したがっお、むンタヌフェむスの背埌にストリヌムの䜜成をカプセル化するこずにしたした。これをIStarterず呌びたす。クラスはそれぞれ単䞀のメ゜ッドで構成されたす。 ちなみに、ここではラムダ匏を䜿甚するこずは非垞に可胜ですが、私の゚ステティストはこれに反察したした。 その結果、「ブリッゞのこちら偎」にはロックずむンタヌロックの2぀のクラスがあり、「ブリッゞの反察偎」には2぀のクラスがあり、それらのフロヌを開始する2぀のオプションがありたす。任意の組み合わせで䜿甚できたす。 GoFの「実装ではなく、むンタヌフェむスず察話する」ずいうむデオロギヌの魅力cは、デヌタず察話する他のクラスに觊れる必芁がないずいう事実だけでなく、単䜓テストも倉曎する必芁がないずいう事実で衚されたす。 。 絶察に。 特定のオブゞェクトを䜜成するポリモヌフィックな方法を倉曎するだけで、すべおのオプションにこれらのテストが提䟛されたす。



TDDの始たり



私はこのトピックに぀いお少し理論的です。 最初に、最初のクラスず2番目のクラスが同じクラスの䟋では、TDDでは開発動䜜を芁玄できたせんでした。最初にテストを行い、次にコヌド自䜓を䜜成したした。 私の開発の道筋は、次の段階を経たした。動䜜するコヌドを䜜成し、テストケヌスを䜜成し、最初ず2番目の䞡方を調敎しお、暙準にしたした。 その埌、テストは「真であるず認識」され、埌に続くリファクタリングはおそらくそれらに基づいおいる可胜性がありたす。 少なくずも初心者にずっお、このアプロヌチには生呜の暩利があるず考える理由最も単玔な関数A = B + Cを考慮する堎合、この関数のテストは匏C1 = A-Bで提案され、比范Cに基づくテストの成功の結論== C1。 ここでの゚ラヌは、テストされた機胜ずテストされた機胜の䞡方に含たれおいる可胜性がありたす。 さらに、アクションが倚いため、ミスが発生しやすくなりたす。 この時点で、なぜテストが必芁なのか疑問に思うかもしれたせんが、答えは自然にすでに存圚し、すべおが私たちの前に定匏化されおいたす。 テストで「合栌」を瀺すには、条件の1぀が満たされおいる必芁がありたす。 䞡方の機胜に゚ラヌがないか、あちこちに゚ラヌが同時に存圚しおいたす。 䞡方の゚ラヌが発生する確率は、それぞれの゚ラヌの確率よりも個別に小さくなりたす。特に、ここに存圚するだけでは十分ではないため、゚ラヌが盞乗的である、぀たり盞互の圱響を補償する必芁がありたすが、その確率はさらに䜎くなりたす。 ぀たり、テストを蚘述するすべおの努力は、゚ラヌの可胜性を枛らすこずを盎接目的ずしおいたす。 ただし、同じ人がテストずコヌドの䞡方を曞く堎合、同じ間違いをする確率はわずかに高くなりたす。 さお、倧䞈倫、私にずっおは自転車のようです、そしお䜕がすでに良いですか。

最初のテストの䜜成に移りたしょう。 これを行うには、たずIQueuedむンタヌフェむス名を右クリックし、察応するメニュヌ項目を遞択しお、テストテンプレヌトを䜜成したす。 したがっお、このクラスをテストするには䜕が必芁です。 単独でテストするこずになっおいるので、サブゞェクトクラスず察話するクラスのmoki暡倣者を䜜成する必芁がありたす。 別のポむント-テストの目的はむンタヌフェむスを遞択するこずであり、最初にテストする特定のクラスが2぀あり、埌で2察2の4぀の組み合わせがあったため、新しく䜜成したテストクラスに抜象修食子を付けお汎甚パラメヌタヌを远加するのが理にかなっおいたす。

  [TestClass()] public abstract class IQueuedTest<T>
      
      





クラスは抜象クラスなので、テストはクラスに察しお盎接実行されたせん。もちろん、クラスから継承されたすべおのテストクラスが実行されたす。

たず、サヌビスクラスのコンシュヌマシミュレヌタが必芁です。

  class Tester<T1>
      
      





このクラスは、テストされたクラスのむベントをサブスクラむブし、むベントのトリガヌを怜蚎し、このむベントで䜕が起こったかを蚘憶したす。

  public Tester(IQueued<T1> tested) { tested.OnReceive += Received; tested.OnTimeout += TimeOut; } void TimeOut() { timedCount++; } void Received(T1 item) { Thread.Sleep(delay); receivedCount++; lastItem = item; }
      
      





この内郚クラスに加えお、特定のオブゞェクトを䜜成するメ゜ッドが必芁になりたす。これにより、すべおの盞続人がオヌバヌラむドする必芁がありたす。

  protected abstract IQueued<T> createSubject(); protected abstract T CreateItem();
      
      





だから、実際にテストを曞くこずに。 ちなみに、そのような蚘事を読んでいるず、著者はレヌキを省略し、既成の゜リュヌションを提䟛しおいるこずに気付きたした。 いく぀かのレヌキに぀いお詳しく説明したす。 私が最初に遭遇した問題は、スレッドプヌルでの開始の同じ遅延でした。 アプリケヌションが起動するず、プヌルは少数のスレッドのみを保持し、新しいスレッドを10分の1秒で枬定される遅延でのみ起動したす。 そしお、テストフレヌムワヌクはテストを䞊行しお実行しおいるようです。 このため、「手動」フロヌを䜿甚したテストは成功したしたが、プヌルからのフロヌでは䞀郚が合栌し、䞀郚は萜ちたした。 したがっお、2぀のオプションがありたす。同じ「長い」メ゜ッドですべおのテストを遞択したす。これはより高速に実行されたすが、䞀般的な刀定を1぀だけ発行するか、回避策を考え出したす。 私は次のこずを思い぀きたした

  void ActivationTestHelper(Tester<T> tester, IQueued<T> subject) { int retry = 30; while (!subject.Active && retry > 0) { retry--; Thread.Sleep(SleepDelay); } Assert.AreEqual(true, subject.Active); }
      
      





30は、ストリヌムがアクティブになるたで「埅機」を詊み、その埌はテストのみに進みたす。 ヘルパヌは䜜成埌すぐに呌び出されたす。

そしお、再び-そう、テストを曞くこずに。 最初のテストは、メッセヌゞを送受信するこずです。 クラスのタスクは別のスレッドに転送するこずであるこずがわかっおいるため、テスタヌクラスが送信ストリヌムのコンテキストでメッセヌゞを受信しないこずを確認し、その埌正垞に受信するこずを確認する必芁がありたす。 ちなみに、テスタヌがReceivedメ゜ッドのコヌドで50ミリ秒の遅延を持っおいるのは、このため最初の堎合です。 この遅延が呌び出しスレッドで発生した堎合、最初のアサヌトはテストに倱敗したす。

  [TestMethod()] public void SendTest() { var subject = createSubject(); var tester = new Tester<T>(subject); ActivationTestHelper(tester, subject); T item = CreateItem(); tester.delay = SleepDelay; subject.Send(item); Assert.AreEqual(0, tester.receivedCount);
      
      





次に、メッセヌゞが送信されたこずを確認するために、100ミリ秒埅っおから受信したメッセヌゞを確認したす。 原則ずしお、これらの倀は枛らすこずができたす。䞻なこずは、2番目の遅延が最初の遅延よりも倧きいこずですが、ここでは、これは原理的ではないず思いたす。 以䞋のテストに「道を譲る」ために、フロヌを完了するこずを忘れないでください。

  Thread.Sleep(SleepDelay2); Assert.AreEqual(1, tester.receivedCount); Assert.IsTrue(tester.lastItem.Equals(item)); subject.Terminate(); }
      
      





次に、タむムアりトテスト。 たた、オブゞェクトを䜜成し、アクティベヌションヘルパヌを実行したす。 タむムアりトを蚭定し、呌び出し元スレッドのコンテキストでは呌び出されず、埌で呌び出されるこずも確認したす。 そしお䞀床だけ。

  [TestMethod()] public void TimeoutTest() { var subject = createSubject(); var tester = new Tester<T>(subject); subject.setTimeout(SleepDelay); ActivationTestHelper(tester, subject); T item = CreateItem(); subject.Send(item); Assert.AreEqual(0, tester.timedCount); Thread.Sleep(SleepDelay3); Assert.AreEqual(1, tester.timedCount); }
      
      





このテストでは、タむムアりト倀の4倍である200ミリ秒の遅延が蚭定されたした。最初の゚ラヌである芋萜ずしをキャッチしたのはこのテストでした。 単䜓テストは快適であるこずが刀明したした。これは、゚ラヌの絶察的な倧郚分が「プロダクション」だけでなく、アルファトラむアルにも及ばないこずを意味したす。 もちろん、これらの゚ラヌはそこで怜出されたすが、テストの蚘述に費やされた䜜業は、デバッグのために行われなければなりたせん。 その結果、緊急事態が「怠techniqueな」開発期間にあった期間から䞀定量の䜜業をシフトする䞀皮の時間管理技術ができたした。 そしお、これらすべおは、リファクタリングにおける貎重な助けに蚀及しなくおも、私が途䞭で発明したものです。

残りのテストは、ストリヌムの完了です。 ストリヌムが停止され、どこにも䜕も送信されないこずを確認する必芁がありたす。 すべおのテストに共通の手順を省略するず、次の結果が埗られたす。

  public void TerminateTest() { ... subject.Terminate(); subject.Send(item); Thread.Sleep(SleepDelay3); Assert.AreEqual(0, tester.receivedCount); Assert.AreEqual(0, tester.timedCount); }
      
      





むンタヌフェむスには3぀のテストしかありたせんが、2぀の特定のクラス、ストリヌムを開始するための2぀のオプションがあり、その䞊で、送信メッセヌゞずしおのクラスず構造の動䜜を個別に確認するこずにしたした。 なぜ8぀の特定のテストクラスが䜜成され、そのうちの1぀を指定したす。特定のテストケヌスの数は24です。テスト構造

  public struct TestMessageStruct { public string message; //object reference field public int id; //value field }
      
      





そしお実際には、オプションをテストする特定のテストクラスブロックされたキュヌ、通垞のスレッド、構造。

  [TestClass()] public class IQueuedLockRegTest : IQueuedTest<TestMessageStruct> { protected override IQueued<TestMessageStruct> createSubject() { return new LockQueued<TestMessageStruct>(new RegularThreadStarter()); } protected override TestMessageStruct CreateItem() { var item = new TestMessageStruct(); var rnd = new Random(); item.id = rnd.Next(); item.message = string.Format("message {0}", item.id); return item; } }
      
      





これに関連しおやり盎さなければならない特定の実隓オブゞェクトを䜜成したのは、特定のテストクラスでした。 たた、その数を2倍にしなければなりたせんでしたが、ご芧のずおり、テスト自䜓を蚘述し、熟考するずきよりも倧幅に少ない時間ず劎力しかかかりたせん。



ロガヌむンタヌフェむスず最初のフィルタヌレむダヌ



私は、むンタヌフェヌスをNLogのように䜜成するこずにしたした。 静的クラスLogAccessず呌ばれたのは、䜕らかの理由で、意味の点で、より論理的に思えた。 圌は自分で䜕もせず、プロキシのみが呌び出したす。 このため、圌はテストに参加しないため、ここでは詳しく説明したせん。「倖郚」に衚瀺される䞻芁なメ゜ッドのみをリストしたす。最初のメ゜ッドでは、ロガヌ自䜓をスパムメッセヌゞに取埗できたす。 最初のフィルタリングレむダヌにあるロガヌの名前たたはカテゎリは、前のセクションで説明したクラスを通過する前にこのフィルタリングが実行されるこずを意味したす。぀たり、ログのカテゎリごずにレベルを個別に蚭定できたす。 それぞれ、2番目のフィルタヌレむダヌにカテゎリが存圚しないずいうこずは、このフィルタヌがロガヌストリヌムで既に行われおいるこずを意味したす。

 Logger GetLogger(string category) void SetLevel(string category, int level) void SetLevelForAll(int level) void FilterAddID(int id) void FilterRemoveID(int id)
      
      





倖郚から芋える2番目のクラスはLogLevelです。 テレコムから取った意味のレベル。 その埌、NLogを単玔化し、通垞のintをレベルずしお䜿甚しお、レベルの比范などを賢くしないこずにしたした。

圌は、すべおのレベルのみを远加しお、短瞮ず詳现の2぀のデバッグレベルを䜜成したしたが、実際には䞍足しおいたした。

  public const int Invalid = 0; public const int Always = 1; public const int Fatal = 2; public const int Error = 3; public const int Warning = 4; public const int Info = 5; public const int Event = 6; public const int Debug = 7; public const int All = 8; public const int Total = 9;
      
      





LogAccessからレベルを蚭定するために、クラスにDefaultプロパティも含めたした。

最も重芁なこずは、倖郚から芋えるのはLoggerむンタヌフェヌスです。 意図的にプレフィックスIを远加したせんでした。いく぀かのメ゜ッドを提䟛したす。

 public interface Logger { /// <summary> /// Method for unrecoverable errors. /// </summary> /// <param name="message"></param> /// <param name="ex"></param> void Fatal(string message, Exception ex = null); /// <summary> /// Method for external errors, such as user input or file access. /// </summary> /// <param name="message"></param> /// <param name="id"></param> /// <param name="ex"></param> void Warning(string message, int id = 0, Exception ex = null); /// <summary> /// Method for regular debug logging. /// </summary> /// <param name="message"></param> /// <param name="id"></param> void Debug(string message, int id = null); }
      
      





゚ラヌのメ゜ッドは識別子に関連付けられおおらず、譊告レベル以䞊にのみ関連付けられおいたす。

次に、衚瀺する特定のロガヌのクラスを怜蚎したす。 ロガヌが呌び出しスレッドから取埗するパフォヌマンスの実際の最小化はどのように達成されたすか。 ずころで、ロガヌむンタヌフェむスには拡匵バヌゞョンがあり、倖郚からは芋えないため、SetLevelメ゜ッドが远加されおいたす。

  class CheckingLogger : InternalLogger { bool[] levels = new bool[LogLevel.Total]; Sender send; string category; public CheckingLogger(string category, Sender sender, int level) { this.send = sender; this.category = category; SetLevel(level); } public void SetLevel(int level) { for (int n = LogLevel.Fatal; n < LogLevel.Total; n++) { levels[n] = (n <= level); } } public void Error(string message, Exception ex = null) { if (levels[LogLevel.Error]) { send(new LogItem(category, LogLevel.Error, message, ex: ex)); } }
      
      





1぀のスパム送信方法を怜蚎するために、配列からブヌル倀をチェックし、それをSenderデリゲヌト経由で送信するこずは明らかです。 ロガヌの2番目のバヌゞョンでは、ブヌル配列の代わりに送信者の配列が䜿甚され、空のメ゜ッドぞのデリゲヌトがオフレベルに挿入され、送信メ゜ッドにチェックがなく、すぐに送信されたす。 テスト結果によるず、パフォヌマンスの違いはわずかであり、おそらく異なるプラットフォヌムでテストするこずで答えが埗られたす。 ロガヌテストは非垞に簡単で、メッセヌゞが送信者に送信されたかどうか、送信すべきタむミングず送信すべきでないタむミングを確認したす。 それから、タむプミスやコピヌアンドペヌストの゚ラヌにも遭遇したした。 ほずんど同じメ゜ッドがたくさんありたすが、その違いは䞀芋しただけではわかりたせん。

クラスに入るセクション、たたは実際にログを送信するために䜿甚するLogItem構造を考え出すこずはできたせんでした。ここでは、コンストラクタヌは省略し、コンテンツのみを省略したす。 ちなみに、1぀のメッセヌゞのIDを耇数に蚭定できるこずがわかりたす。これは、ブロヌドキャスト、たたは呌び出しの堎合に、発信者ず呌び出しに応答する2぀の識別子を瀺すために必芁です。 前のクラスのメ゜ッドはこの目的のために耇補されおおり、泚意が奪われおいるだけです。

  struct LogItem { public readonly String category; public readonly String message; public readonly int[] ids; public readonly int level; public readonly Exception ex; public readonly DateTime time; }
      
      







䟝存性泚入



このセクションでは、IoCコンテナヌの䜿甚䟋を瀺したす。 次の行はロガヌのクラスです。 名前はIoCコンテナに関連付けられおおらず、䞀臎するだけです。 LogAccessクラスメ゜ッドがプロキシされるGetLoggerメ゜ッドを提䟛したす。 NLogは、コヌド内のコメントから刀断するず、同じカテゎリで2回呌び出されたGetLoggerメ゜ッドが同じロガヌむンスタンスを䞎えるこずを保蚌したせん。 私はこの方法がボトルネックにならないこずを考え、決定し、䞀般的な同期を蚭定したした。この堎合、むンスタンスは同じものを保蚌する必芁がありたす。 ログコンテナは、以前に䜜成されたすべおのロガヌを保存し、LogAccessからプロキシされたSetLevelを配垃したす。

そのため、IoCコンテナヌを䜜成し、次の2行の間にさらに構成を配眮したす。

 var builder = new ContainerBuilder(); IContainer container = builder.Build();
      
      





䞊蚘から、クラスがシングルトンでなければならないこずは明らかです。 これは非垞に簡単に実珟できたす。

  builder .RegisterType<LoggerContainer>() .SingleInstance();
      
      





次の方法では、シングルトンぞのリンクを取埗できたす。

  Container .Resolve<LoggerContainer>();
      
      





ログコンテナヌ自䜓が䜜成元のIoCコンテナヌのサヌビスを䜿甚できるように、コンストラクタヌでIComponentContextパラメヌタヌを远加したした。 これにより、クラスの䜜成を倖郚に持ち、特定のクラスを、たずえば、䜿甚するコヌドを倉曎せずに、機胜を拡匵したクラスに倉曎できたすが、構成を倉曎するだけです。 コヌドに蚭定するのではなく、xmlから読み蟌むこずができたす。 実際、この内郚䜜成から倖郚䜜成ぞの眮き換えは、䟝存関係の実装ず呌ばれたす。 実際には、コンテキストから特定のロガヌを受け取るメ゜ッド

 private InternalLogger CreateLogger(string category) { return context .Resolve<InternalLogger>(new NamedParameter[]{ new NamedParameter("category", category), new NamedParameter("level",LogLevel.Default) }); }
      
      





, , , , , . , , .

  builder .RegisterType<CheckingLogger>() .As<InternalLogger>();
      
      





, , , . , , , . , , . «» .

, IoC: -. :

  [TestClass()] public class LoggerContainerTest { IContainer testContainer; public LoggerContainerTest() { var builder = new ContainerBuilder(); builder .Register<LoggerContainer>((c, p) => new LoggerContainer(c.Resolve<IComponentContext>())); builder .RegisterType<LoggerMock>() .As<InternalLogger>(); testContainer = builder.Build(); }
      
      





, Autofac, new, Resolve, , , , .

.

  LoggerContainer lc= testContainer .Resolve<LoggerContainer>(); var logger1 = lc.GetLogger(cat1); var logger2 = lc.GetLogger(cat2); var logger3 = lc.GetLogger(cat1); Assert.AreEqual(logger1, logger3); Assert.AreNotEqual(logger1, logger2);
      
      







Moq



, . , LogCollector-. IQueued , , .

, , , , LogItem. , .

NLog: layout="${longdate} ${uppercase:${level}} ${message}".

LogMessageFormat=\d \l \m\r\n.

, , , . , . , , . , StringBuilder LogItem . , .

, , , . , , , . — , HashSet, . , , . Action . filterQue = new ConcurrentQueue<Action<HashSet>>();

, , , , .

  Action<HashSet<int>> refresh; while (!filterQue.IsEmpty) { if (filterQue.TryDequeue(out refresh) && refresh != null) { refresh(filter); } }
      
      





, , .

  filterQue.Enqueue((x)=> { x.Add(id); });
      
      





Moq — , , , , . Moq , . — . , GoF, « », , , - , , , , .

Moq — , , internal , , , Moq , , . AssemblyInfo.cs , , :

 [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("LoggerTest"), System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2")]
      
      





, , , .

, -, , , .

  LogItem message = new LogItem(category, level, msg, ids, GetException()); var qThread = new Mock<IQueued<LogItem>>(MockBehavior.Strict); var writer = new Mock<ILogWriter>(MockBehavior.Strict); qThread .Setup(s => s.Send(It.IsAny<LogItem>())); writer .Setup(s => s.GetTimeout()) .Returns(timeout); qThread .Setup(s => s.SetTimeout(It.IsAny<int>())); LogCollector target = new LogCollector(qThread.Object, writer.Object); qThread .Verify(s => s.SetTimeout(It.Is<int>(a => a == timeout))); target.Send(message); qThread .Verify(s => s.Send(It.Is<LogItem>(i => i.Equals(message))), Times.Once()); }
      
      





, : . Strict , , , , .

Send IQueued , , . , , VerifyAll(). , . , GetTimeout, , . SetTimeout.

, , , GetTimeout . , , , . , , .

, , , , Moq.



負荷詊隓



NLog, , , . , .

.

 var log = SharpLogger.LogAccess.GetLogger("flooder " + tid); foreach (var x in Enumerable.Range(0, messageCount)) { log.Info("Message", x); if (x % 1000 == 0) Thread.Sleep(0); }
      
      





NLog.

  var log = NLog.LogManager.GetLogger("flooder " + tid); foreach (var x in Enumerable.Range(0, messageCount)) { log.Info("Message"); if (x % 1000 == 0) Thread.Sleep(0); }
      
      





tid, , .

, , , , . 200 . . — , , 5 — NLog. , .



.



x64 .



— , Nlog, , , , , . , 40 , .



, — 7.15 ., , 25 . , , , , , NLog , , , . , NLog , .

, — .

, 3 15 , 22.2 17.8. 30, , 1.5 , 23.7 , 17.4, .

, 4 i5 4, windows 8.



https://github.com/repinvv/SharpLogger

, – .



All Articles