次の方法で共有


アプリケーションへのログ機能の追加

ここでは、アプリケーションにログ機能を追加するために、System.IO.Log に用意されている機能の使用方法を説明します。

FileRecordSequence と LogRecordSequence の比較

単純なファイル ベースのログと Common Log File System (CLFS) ベースのログを選択するには、プラットフォームのサポート、機能の豊富さ、信頼性、およびパフォーマンスの 4 つの条件について考慮する必要があります。

単純なファイル ベースのログは、System.IO.Log がサポートされるすべてのプラットフォームで使用できるのに対して、CLFS ベースのログは Windows Server 2003 R2 プラットフォームと Windows Vista プラットフォームでのみ使用できます。加えて、一部の CLFS 機能は Windows Server 2003 R2 では無効になります。これは、自動拡張処理など、アプリケーションによる特定の状況の処理に影響を与える可能性があります。これらの制限の詳細については、LogRecordSequence を参照してください。

ポリシーおよび多重化の 2 つの機能により、ファイル ベースの FileRecordSequence クラスに比べて CLFS ベースの LogRecordSequence クラスは多機能になっています。LogPolicy 構造体を使用してポリシーを設定すると、ログ サイズの自動拡張および自動縮小、最小および最大エクステント コンテナ、および末尾固定しきい値など、保守機能を非常に細かく制御できます。これらの機能により、CLFS ベースの LogRecordSequence クラスを使用した循環ログの作成も可能になります。多重化 (NTFS ファイル ストリームを操作する機能) により、単一のアプリケーション内、または 1 つの単位として動作する複数のアプリケーション内でパフォーマンスや利便性を向上させることができます。このため、長時間にわたって実行されるシナリオや、非常に高いパフォーマンスが要求される状況向けに設計されたアプリケーションは、FileRecordSequence クラスではなく LogRecordSequence クラスの使用により最大の利点を得ることができます。

加えて、CLFS は信頼性やパフォーマンスの点でも有利です。CLFS は、高いパフォーマンスのアプリケーションや、エンタープライズ環境での使用のために設計されています。CLFS をサポートするプラットフォームでの実行がアプリケーションの制約上許容される場合、LogRecordSequence クラスを使用すると、LogPolicy クラスを使用したログ ファイル保守の制御に使用できるオプションが多くなるだけでなく、IO パフォーマンスも向上します。

System.IO.Log の主なクラス

System.IO.Log における最も重要な 3 つのクラスを次に示します。これらの使用方法の詳細については、各クラスの参考資料を参照してください。

System.IO.Log の使用

ログを開いてエクステントを追加する

ログを開いてエクステントを追加する操作は通常、アプリケーションにログ機能を追加するための最初のタスクです。エクステントの追加は、CLFS ベースの LogRecordSequence クラスを使用している場合のみ実行できることに注意してください。

ログの場所に加えて、ログに最初に追加するエクステントの数とサイズを検討する必要があります。格納場所の制限事項は、アプリケーションが実行されているユーザー アカウントによってのみ決まります。有効な場所は、そのユーザーが書き込みアクセスを持つローカル システムの任意の場所です。ただし、エクステントの数とそれらのサイズには、さらにアプリケーション固有の考慮事項があります。最初のエクステントをログに追加するとき、サイズを指定する必要があります。このサイズは、手動であっても、自動拡張処理であっても、ログに追加されるすべての追加のエクステントに使用されます。さらに、LogPolicy クラスに用意されている多くの機能を利用するため、各シーケンスには常に 2 つ以上のエクステントが存在している必要があります。

次の例に、ログを作成して 2 つの 1 MB のエクステント コンテナをそのログに追加する方法を示します。

LogRecordSequence recordSequence = new LogRecordSequence("application.log", FileMode.CreateNew);
recordSequence.LogStore.Extents.Add("app_extent0", 1024 * 1024);
recordSequence.LogStore.Extents.Add("app_extent1");

ポリシーの設定

LogPolicy 構造体を使用してログ ポリシーを設定することは、ログ アプリケーションを開発するための 2 番目のタスクです。このタスクは、CLFS ベースの LogRecordSequence クラスを使用している場合にのみ実行でき、ポリシーは保持されないため、ログを開くたびに設定する必要がある点に注意してください。

ポリシーには、自動拡張および最大エクステント数などの重要なオプションが含まれています。アプリケーションの使用過程全体を通して、変化する負荷によってエクステントの最初のセットが使い果たされ、実行中に SequenceFullException が発生する可能性があります。一般的には、これを回避するため、ログの自動拡張を許可して、この追加の負荷に透過的に対処することが推奨されます。SequenceFullException の発生時にエクステントを手動で追加することもでき、透過的な自動拡張の代わりに使用することもできます。

循環ログを使用する場合は、最大エクステント数も設定する必要があります。加えて、LogPolicy 構造体には、自動縮小率および拡張率設定など、いくつかの一般的な補助設定が用意されています。これらの値を操作すると、アプリケーションのパフォーマンスに大きな影響を与えることがあるため、システム内の特定のログに対する I/O レートに基づいて調整する必要があります。

次の例では、一度に 5 個ずつ増加して最大 100 個のエクステントまで拡張する自動拡張ログ ポリシーを設定する方法を示します。

recordSequence.LogStore.Policy.AutoGrow = true;
recordSequence.LogStore.Policy.MaximumExtentCount = 100;
recordSequence.LogStore.Policy.GrowthRate = new PolicyUnit(5, PolicyUnitType.Extents);
recordSequence.LogStore.Policy.Commit();
recordSequence.LogStore.Policy.Refresh();

レコードの追加

Append メソッドにより解釈される形式でデータを指定するさまざまな手法について検討する必要があります。

現在の Append メソッドは、バイト配列とバイト配列のリストを処理するために最適化されています。ただし、このメソッドはより高レベルのシリアル化クラスと組み合わせて使用することもできます。IRecordSequence クラスの機能を効率的に活用するためには、結果として生成されるレコード形式も理解する必要があります。次の例に、System.Runtime.Serialization.FormattersDataContractAttribute に基づく機能を使用した高レベルのシリアル化コード サンプルを示します。

SomeDataContractClass someClassInstance = new SomeDataContractClass(…);
ArraySegment<byte>[] recordData = new ArraySegment<byte>[1];

using (MemoryStream formatStream = new MemoryStream())
{
   IFormatter formatter = new NetDataContractSerializer();
   formatter.Serialize(formatStream, someClassInstance);
   formatStream.Flush();
   recordData[0] = new ArraySegment<byte>(formatStream.GetBuffer());
}

recordSequence.Append(recordData, …);

レコードをレコード シーケンスに追加するときは、アプリケーションの領域および時間の制約を考慮に入れる必要があります。たとえば、後で ReservationCollection クラスを使用して追加のレコードを書き込む場合に、ログに十分な領域が残っている場合のみ追加処理が成功するよう、アプリケーションにより強制されることがあります。ARIES ベースのトランザクション処理システムは、通常そのようなレコードを補正レコードまたは取り消しレコード (作業をロールバックする必要があるときに使用される) として参照します。同様に、一部のシステムにはレコードをいつ書き込むことができるかを制御する厳格な規則があります。どのレコードをディスクにフラッシュし、どのレコードにレイジーフラッシュを許可するかは、アプリケーションの要件とシステムの制約によってのみ決まります。これらの考慮事項は、パフォーマンスと精度の両方に影響する場合があります。

領域オプションおよび時間オプションはどちらも、各種 Append メソッドによって提供されます。次の例では、3 つのレコードを追加して、後で発生するレコードのための領域を確保することにより、それらのオプションを設定する方法を示します。直前のレコード書き込みだけが見つかるようにフラッシュを強制します。

// Assume recordData is smaller or equal to 1000bytes 
// Reserve space in the log for three 1000byte records ReservationCollection reservations = recordSequence.CreateReservationCollection();
reservations.Add(1000);
reservations.Add(1000);
reservations.Add(1000);

// The following three appends are guaranteed to succeed if execution 
// flows to here
recordSequence.Append(recordData1,
                                   userSqn,
                                   previousSqn,
                                   RecordAppendOptions.None,    // No flush
                                   reservations);
recordSequence.Append(recordData2,
                                   userSqn,
                                   previousSqn,
                                   RecordAppendOptions.None,    // No flush
                                   reservations);
recordSequence.Append(recordData3,
                                   userSqn,
                                   previousSqn,
                                   RecordAppendOptions.ForceFlush,    // Flush
                                   reservations);

RetryAppend プロパティが true であり、シーケンス内に領域がないために追加操作が失敗したときは、レコード シーケンスが領域の解放を試みた後、操作を再試行します。領域の解放は、実際にはさまざまな方法で実装されます。たとえば、LogRecordSequence クラスは、後続のセクション「TailPinnedEvent を使用した領域の解放」で説明するように CLFS ポリシー エンジンを呼び出します。

予約

次の例に示すように、予約は 2 つの方法で実行できます。信頼性の高い処理を行うサンプルでこの方法を採用できます。このタスクは、CLFS ベースの LogRecordSequence クラスを使用している場合のみ実行できることに注意してください。

ReserveAndAppend メソッドの使用

ReservationCollection reservations = recordSequence.CreateReservationCollection();
long[] lengthOfUndoRecords = new long[] { 1000 };
recordSequence.ReserveAndAppend(recordData,
                                                     userSqn,
                                                     previousSqn,
                                                     RecordSequenceAppendOptions.None,
                                                     reservations,
                                                     lengthOfUndoRecords);
recordSequence.Append(undoRecordData,    // If necessary …
                                    userSqn,
                                    previousSqn,
                                    RecordSequenceAppendOptions.ForceFlush,
                                    reservations);

手動での実行

ReservationCollection reservations = recordSequence.CreateReservationCollection();
reservations.Add(lengthOfUndoRecord);
try
{
   recordSequence.Append(recordData, userSqn, previousSqn, RecordAppendOptions.None);
}
catch (Exception)
{
   reservations.Remove(lengthOfUndoRecord);
   throw;
}

recordSequence.Append(undoRecordData, userSqn, previousSqn, RecordAppendOptions.ForceFlush, reservations);

ログの読み取り

ログからの読み取りは、アプリケーションの使用期間のいつでも発生する可能性があります。ただし、状況によってはさまざまな順序でログに格納されたレコードを反復処理する必要が生じます。Next および Previous で指定したログの標準方向に加えて、個々のレコードをシーケンスに追加するときに指定したユーザー定義の順序で反復処理することもできます。ユーザーが、Append 処理中に現在の実行スレッドにより追加された前のレコードを指定できるため、Previous は物理ログの観点からは必ずしも連続していません。

次の例では、シーケンス番号 "startSqn" のレコードから順番に連続してログを読み取ります。

foreach (LogRecord record in recordSequence.ReadLogRecords(startSqn, LogRecordEnumeratorType.Next))
{
   Stream dataStream = record.Data;
   // Process dataStream, which can be done using deserialization
}

TailPinnedEvent を使用した領域の解放

レコード シーケンスが領域の解放に使用できる方法の 1 つは、TailPinned イベントを発生させることです。このイベントは、シーケンスの末尾 (つまり、基本シーケンス番号) が領域の解放のために前方に移動する必要があることを示します。

イベントは、レコード シーケンスが何らかの理由で領域を解放する必要があると決定したとき、いつでも発生させることができます。たとえば、CLFS ポリシー エンジンは、同じログ ファイルを共有する 2 つのログ クライアントの末尾が離れすぎていると判断したときにイベントを発生させます。領域の解放は、再開領域を書き込むか、ログを切り捨てて AdvanceBaseSequenceNumber メソッドを使用して領域を消去することにより行うことができます。次の例に、2 つ目の方法を示します。

recordSequence.RetryAppend = true;
recordSequence.TailPinned += new EventHandler<TailPinnedEventArgs>(HandleTailPinned);

void HandleTailPinned(object sender, TailPinnedEventArgs tailPinnedEventArgs)
{
   // tailPinnedEventArgs.TargetSequenceNumber is the target 
   // sequence number to free up space to.  
   // However, this sequence number is not necessarily valid.  We have
   // to use this sequence number as a starting point for finding a
   // valid point within the log to advance toward. You need to
   // identify a record with a sequence number equal to, or greater
   // than TargetSequenceNumber; let's call this 
   // realTargetSequenceNumber. Once found, move the base

   recordSequence.AdvanceBaseSequenceNumber(realTargetSequenceNumber);

}

TailPinned イベントの外部で WriteRestartArea メソッドを呼び出して、領域を解放することもできます。再開領域は、他のログ処理システムにおけるチェックポイントと同様です。このメソッドの呼び出しは、再開領域が完成し、今後のレコード追加のために使用可能になる前に、アプリケーションが以前のすべてのレコードについて検討していることを示しています。他のレコードと同様に、このメソッドによるレコード書き込みが機能するためには、ログ内に実際の空き領域が必要です。

多重化

複数のアプリケーションを 1 つの単位として同時に実行する場合、CLFS ベースの LogRecordSequence クラスを使用して 1 つの NTFS ファイル ストリームを操作できます。次の例では、2 つのストリームを持つ多重化されたログを作成する方法を示します。ログ レコードへの追加および読み取り処理のインターリーブが実行されます。

namespace MyMultiplexLog
{
    class MyMultiplexLog
    {
        static void Main(string[] args)
        {
            try
            {
                string myLog = "MyMultiplexLog";
                string logStream1 = "MyMultiplexLog::MyLogStream1";
                string logStream2 = "MyMultiplexLog::MyLogStream2";
                int containerSize = 32 * 1024;

                LogRecordSequence sequence1 = null;
                LogRecordSequence sequence2 = null;

                Console.WriteLine("Creating Multiplexed log with two streams");

                // Create log stream 1
                sequence1 = new LogRecordSequence(logStream1, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);

                // Log Extents are shared between the two streams. 
                // Add two extents to sequence1.
                sequence1.LogStore.Extents.Add("MyExtent0", containerSize);
                sequence1.LogStore.Extents.Add("MyExtent1");

                // Create log stream 2
                sequence2 = new LogRecordSequence(logStream2, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);

                // Start Appending in two streams with interleaving appends.

                SequenceNumber previous1 = SequenceNumber.Invalid;
                SequenceNumber previous2 = SequenceNumber.Invalid;

                Console.WriteLine("Appending interleaving records in stream1 and stream2...");
                Console.WriteLine();

                // Append two records in stream1
                previous1 = sequence1.Append(CreateData("MyLogStream1: Hello World!"), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush);
                previous1 = sequence1.Append(CreateData("MyLogStream1: This is my first Logging App"), previous1, previous1, RecordAppendOptions.ForceFlush);

                // Append two records in stream2
                previous2 = sequence2.Append(CreateData("MyLogStream2: Hello World!"), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush);
                previous2 = sequence2.Append(CreateData("MyLogStream2: This is my first Logging App"), previous2, previous2, RecordAppendOptions.ForceFlush);

                // Append the third record in stream1
                previous1 = sequence1.Append(CreateData("MyLogStream1: Using LogRecordSequence..."), previous1, previous1, RecordAppendOptions.ForceFlush);
                
                // Append the third record in stream2
                previous2 = sequence2.Append(CreateData("MyLogStream2: Using LogRecordSequence..."), previous2, previous2, RecordAppendOptions.ForceFlush);
                
                // Read log records from stream1 and stream2

                Encoding enc = Encoding.Unicode;
                Console.WriteLine();
                Console.WriteLine("Reading Log Records from stream1...");
                foreach (LogRecord record in sequence1.ReadLogRecords(sequence1.BaseSequenceNumber, LogRecordEnumeratorType.Next))
                {
                    byte[] data = new byte[record.Data.Length];
                    record.Data.Read(data, 0, (int)record.Data.Length);
                    string mystr = enc.GetString(data);
                    Console.WriteLine("    {0}", mystr);
                }

                Console.WriteLine();             
                Console.WriteLine("Reading the log records from stream2...");
                foreach (LogRecord record in sequence2.ReadLogRecords(sequence2.BaseSequenceNumber, LogRecordEnumeratorType.Next))
                {
                    byte[] data = new byte[record.Data.Length];
                    record.Data.Read(data, 0, (int)record.Data.Length);
                    string mystr = enc.GetString(data);
                    Console.WriteLine("    {0}", mystr);
                }

                Console.WriteLine();

                // Cleanup...
                sequence1.Dispose();
                sequence2.Dispose();

                LogStore.Delete(myLog);

                Console.WriteLine("Done...");
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception thrown {0} {1}", e.GetType(), e.Message);
            }
        }

        // Converts the given data to an Array of ArraySegment<byte> 
        public static IList<ArraySegment<byte>> CreateData(string str)
        {
            Encoding enc = Encoding.Unicode;

            byte[] array = enc.GetBytes(str);

            ArraySegment<byte>[] segments = new ArraySegment<byte>[1];
            segments[0] = new ArraySegment<byte>(array);

            return Array.AsReadOnly<ArraySegment<byte>>(segments);
        }

    }
}

例外処理

System.IO.Log を使用するアプリケーションでは、インフラストラクチャによるフェイルファーストを処理する準備をしておく必要があります。場合によっては、System.IO.Log では、アプリケーションへのエラー通知に例外を使用しません。その代わり、アプリケーション開発者が対処できる回復可能なエラーの通知に例外が使用されます。ただし、さらに実行を続けた場合にログ ファイルが破損したり使用不可能になる可能性があるときは、フェイルファーストが発生します。フェイルファーストが発生した場合、アプリケーション上の処理で問題を修正することはできないため、アプリケーションを終了する準備をする必要があります。

フェイルファーストの詳細については、「FailFast」を参照してください。

System.IO.Log によりスローされる多くの例外は、内部ログ実装から生成されます。次の一覧に、注意する必要がある重要な例外をいくつか示します。

  • SequenceFullException: この例外は、致命的な場合とそうでない場合があります。AutoGrowtrue に設定されており、拡張のための十分な領域がある場合は、やはり SequenceFullException がスローされます。これは、自動拡張の実装が本質的に非同期の分割可能な処理であるためで、一度にすべてを完了する拡張率は保証されません。たとえば、拡張率が 100 個のエクステントに設定された場合、100 個のエクステントすべてがログ ストアに追加されるにはかなりの時間がかかる可能性があります。結果として、この期間内にこの例外が断続的にスローされる場合があります。

  • TailPinned Handler: TailPinned イベント内でスローされる例外が、内部ログ実装に報告されます。これは、アプリケーションが基本シーケンス番号を移動できないことを示しています。TailPinned イベントは、ログがいっぱいになる状況をアプリケーションが回避する最後の機会であるため、SequenceFullException がスローされます。

  • ReservationNotFoundException: この例外は、予約コレクションを使用してレコードを追加しようとしたときに、適切なサイズの予約すべてが既になくなっている場合に発生します。

関連項目

参照

System.IO.Log
LogRecordSequence
FileRecordSequence

その他の技術情報

System.IO.Log のログ サポート

Footer image

このトピックに関するコメントを Microsoft に送信する。

Copyright © 2007 by Microsoft Corporation.All rights reserved.