共用方式為


將記錄功能新增至應用程式中

本主題說明如何使用 System.IO.Log 提供的各項功能,將記錄功能新增至您的應用程式。

FileRecordSequence 和 LogRecordSequence 的比較

若要選擇簡單檔案記錄和一般記錄檔系統 (CLFS) 記錄,您必須考慮下列四個準則:平台支援、功能豐富性、強固性和效能。

簡單檔案記錄適用於所有支援 System.IO.Log 的平台,然而 CLFS 記錄則只能在 Windows Server 2003 R2 和 Windows Vista 平台上使用。此外,Windows Server 2003 R2 還停用了某些 CLFS 功能。這可能會影響應用程式對於特定狀況的處理,例如自動擴增處理。如需此類限制的詳細資訊,請參閱 LogRecordSequence

相較於簡單檔案的 FileRecordSequence 類別,原則和多工是兩項可以為 CLFS 的 LogRecordSequence 類別增添功能豐富性的功能。使用 LogPolicy 結構來設定原則,可以對一些維護功能 (例如,對於記錄檔大小、最小和最大延伸區容器以及結尾固定臨界值的自動擴增和自動縮小) 提供非常細微的控制。這些功能也可以讓您使用 CLFS 的 LogRecordSequence 類別來建立循環式記錄檔。多工 (即操作 NTFS 檔案資料流的能力) 可以在單一應用程式中或在共同運作自成一個單元的多個應用程式內增進效能並提供方便性。因此,相對於 FileRecordSequence 類別,針對長時間執行的案例或需要極高效能的情況所設計的應用程式,可以藉由使用 LogRecordSequence 類別而獲得最大效益。

此外,CLFS 還提供了強固性和效能優勢。設計 CLFS 的目的主要是為了在高效能應用程式或企業環境中使用。如果應用程式條件約束允許在 CLFS 支援平台上執行,則 LogRecordSequence 類別不僅會在透過 LogPolicy 類別控制記錄維護時提供更多選項,而且也會產生增進的 IO 效能。

System.IO.Log 中的主要類別

下列類別是 System.IO.Log 中最重要的三個類別。如需其使用方式的詳細資訊,請參閱它們各自的參考文件。

使用 System.IO.Log

開啟記錄檔和新增延伸區

開啟記錄檔並新增延伸區,通常是您將記錄功能新增至應用程式時的第一項工作。請注意,只有在使用 CLFS 的 LogRecordSequence 類別時,才可以新增延伸區。

您應該考慮記錄檔的位置,以及延伸區初次新增至記錄檔時的數目和大小。儲存區位置只受限於執行應用程式所使用的使用者帳戶。有效的位置會是本機系統上的任何地方,只要這個使用者在此有寫入權限即可。不過,延伸區的數目和大小則需要針對特定的應用程式詳加考量。將初始延伸區新增至記錄檔時,您必須提供其大小。這個大小會用於所有新增至記錄檔的額外延伸區,不論是以手動方式或是透過自動擴增行為新增。此外,為了能充分運用 LogPolicy 類別提供的許多功能,指定的順序中隨時都必須有至少兩個延伸區存在。

下列範例示範如何建立記錄檔,並在其中新增兩個 1MB 的延伸區容器。

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

設定原則

使用 LogPolicy 結構設定記錄原則,應該是您開發記錄應用程式時的第二項工作。請注意,您只能在使用 CLFS 的 LogRecordSequence 類別時執行這項工作;因為原則不是持續性的,所以每當開啟記錄檔時都必須設定這項工作。

原則包含一些重要的選項,例如自動擴增和最大延伸區計數。在應用程式的整個存留時間中,變動的負載可以讓一組初始的延伸區耗盡,因而可能會在執行期間造成 SequenceFullException。若要避免發生這種情況,允許記錄檔自動擴增,讓它無障礙地容納這種額外的負載,通常會比較好。請注意,在發生 SequenceFullException 時手動新增延伸區,是一項受支援的作業,因此可以用來替代這種無障礙的自動擴增。

搭配使用循環式記錄檔時,您也應該設定最大延伸區計數。此外,LogPolicy 結構還提供數個常用的輔助設定,例如自動縮小和擴增速率設定。操作這些值可以在應用程式效能上產生顯著效果,而且必須根據系統內任何特定記錄檔的 I/O 速率加以調整。

下列範例示範設定上限為 100 個延伸區、每次擴增 5 個延伸區的自動擴增記錄原則。

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.Formatters 中的 DataContractAttribute 為基礎的功能,說明高階序列化範例。

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 方法公開給使用者。下列範例示範設定此類選項,其方式是附加三筆記錄並預留空間以確保後來附加的記錄會成功。此範例只有在寫入最後一筆記錄時才會強制進行清除。

// 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 類別將會叫用 CLFS 原則引擎,如下面<使用 TailPinnedEvent 釋放空間>一節中所述。

保留

您可以透過兩種方式執行保留,如下列範例所示。您可以採用範例中的做法進行穩固的處理。請注意,您只能在使用 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);

讀取記錄檔

在應用程式存留期當中的任何時間都可能發生讀取記錄檔的作業。不過,視情況而定,可能需要依不同次序逐一查看儲存在記錄檔中的記錄。除了 NextPrevious 為記錄檔指定的標準方向之外,還可以依照您將每一筆個別記錄附加至順序時所指定的使用者定義次序來逐一查看。不過,您也應該注意到,就實體記錄檔而言,Previous 不一定是循序的,因為使用者可以在 Append 作業期間指定目前執行緒所附加的前一筆記錄。

下列範例會依照順向次序,從序號為 'startSqn' 的記錄開始循序讀取記錄檔。

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

使用 TailPinnedEvent 釋放空間

記錄順序可以用來釋放空間的其中一個處理方式為引發 TailPinned 事件。這個事件表示,必須將順序的結尾 (即基底序號) 往前移動才能釋放空間。

當記錄順序基於任何理由決定必須釋放空間時,隨時都可以引發這個事件。例如,當 CLFS 原則引擎判斷兩個共用同一記錄檔之記錄用戶端的結尾相距過遠時,就可能會決定引發事件。寫入重新啟動區域,或是截斷記錄檔並使用 AdvanceBaseSequenceNumber 方法清除空間,都可以達到釋放空間的目的。下列範例示範第二種處理方式。

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 方法來釋放空間。重新啟動區域類似於其他記錄處理系統中的檢查點。呼叫這個方法,就表示應用程式會將所有在重新啟動區域之前的記錄都視為完全完成,而且可供日後附加記錄之用。與任何其他記錄相同,這個方法所寫入的記錄必須在記錄檔中有實際的可用空間,才會正常運作。

多工

如果有多個應用程式共同運作而自成一個單元,您就可以使用 CLFS 的 LogRecordSequence 類別來操作單一 NTFS 檔案資料流。下列範例示範如何建立含有兩個資料流的多工記錄檔。這裡會對記錄檔記錄執行交錯的附加和讀取作業。

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 的應用程式必須經過一些準備動作,以便處理基礎結構所產生的 Failfast。在某些情況下,System.IO.Log 不會使用例外狀況向應用程式傳達錯誤。例外狀況反而主要是用在應用程式開發人員可以立即處理的可修復錯誤。不過,當進一步的執行可能會產生損毀的或可能無法使用的記錄檔時,就會發生 Failfast。如果發生 Failfast,就沒有任何其他應用程式動作可以用來修正問題,因此應用程式必須做好終止的準備。

如需 Failfast 的詳細資訊,請參閱 FailFast

System.IO.Log 所擲回的例外狀況中有許多是來自內部記錄實作。以下列出您應該注意的一些重要例外狀況。

  • SequenceFullException:這個例外狀況可能是也可能不是嚴重的錯誤。如果 AutoGrow 設定為 true 而且有可以擴增的足夠空間,仍然可能擲回 SequenceFullException。這是因為自動擴增實作原本就是非不可部分完成的非同步作業,無法保證擴增速率足以一次完成所有作業。例如,若將擴增速率設定為 100 個延伸區,就可能要花上很長的時間將 100 個延伸區全部新增至記錄存放區。這可能導致此例外狀況偶爾在這個時間範圍當中擲回。

  • TailPinned 處理常式:會向內部記錄實作報告 TailPinned 事件內擲回的例外狀況。這表示應用程式無法移動基底序號。此時會擲回 SequenceFullException,因為 TailPinned 事件是應用程式避免記錄檔已滿狀況的最後機會。

  • ReservationNotFoundException:當您嘗試使用保留區集合來附加記錄,而所有適當大小的保留區都已被佔用時,就會發生這個例外狀況。

請參閱

參考

System.IO.Log
LogRecordSequence
FileRecordSequence

其他資源

System.IO.Log 中的記錄支援

Footer image

請將您對這個主題的意見傳送至 Microsoft。

Copyright © 2007 by Microsoft Corporation. All rights reserved.