Aggiunta di funzionalità di registrazione a un'applicazione

Questo argomento descrive come è possibile utilizzare le funzionalità fornite da System.IO.Log per aggiungere funzionalità di registrazione all'applicazione.

FileRecordSequence contro LogRecordSequence

Per scegliere un semplice log basato su file e un log basato su Common Log File System (CLFS), è necessario considerare i seguenti quattro criteri: supporto della piattaforma, ricchezza della funzionalità, robustezza e prestazioni.

I semplici log basati su file sono disponibili su tutte le piattaforme per le quali è supportato System.IO.Log, mentre i log basati su CLFS sono disponibili solo su piattaforme Windows Server 2003 R2 e Windows Vista. In aggiunta, alcune funzionalità CLFS sono disattivate su Windows Server 2003 R2. Ciò può influire sul modo in cui l'applicazione gestisce condizioni specifiche, ad esempio la gestione di aumento automatico. Per ulteriori informazioni su tali limitazioni, vedere LogRecordSequence.

I criteri e il multiplexing rappresentano due funzionalità che contribuiscono alla ricchezza della funzionalità della classe LogRecordSequence basata su CLFS, rispetto alla semplice classe FileRecordSequence basata su file. L'impostazione dei criteri tramite la struttura LogPolicy consente di avere un controllo molto dettagliato sulle funzionalità di manutenzione, ad esempio l'aumento e la riduzione automatica delle dimensioni del log, i contenitori di extent minimi e massimi e le soglie bloccate nella parte finale. Queste funzionalità consentono anche di creare log circolari utilizzando la classe LogRecordSequence basata su CLFS. Il multiplexing o la possibilità di modificare flussi di file NTFS, possono portare a un miglioramento delle prestazioni e della semplicità di utilizzo all'interno di una sola applicazione o di più applicazioni che operano come un'unità. Pertanto, le applicazioni progettate per scenari che richiedono esecuzioni prolungate o prestazioni estremamente elevate, possono trarre maggior vantaggio dall'utilizzo della classe LogRecordSequence rispetto alla classe FileRecordSequence.

In aggiunta, CLFS fornisce vantaggi in termini di robustezza e prestazioni. CLFS è progettato per essere utilizzato da applicazioni a elevate prestazioni o in ambienti aziendali. Se i vincoli dell'applicazione ne consentono l'esecuzione su una piattaforma supportata da CLFS, la classe LogRecordSequence non solo lascerà spazio a più opzioni durante la manutenzione del file di log tramite la classe LogPolicy ma migliorerà anche le prestazioni di IO.

Classi principali in System.IO.Log

Di seguito vengono riportate le tre classi principali in System.IO.Log. Per ulteriori informazioni sul loro utilizzo, vedere la rispettiva documentazione di riferimento.

Utilizzo di System.IO.Log

Apertura di un log e aggiunta di extent

L'apertura di un log e l'aggiunta di extent sono in genere le prime attività per l'aggiunta di funzionalità di registrazione all'applicazione. L'aggiunta di extent può essere eseguita solo se si utilizza la classe LogRecordSequence basata su CLFS.

È necessario tenere presente il percorso del log, nonché il numero e la dimensione degli extent da aggiungere inizialmente al log. Il percorso di archiviazione è limitato solo dall'account utente per il quale è in esecuzione l'applicazione. Un percorso valido può essere una qualsiasi posizione sul sistema locale in cui l'utente dispone dell'accesso in scrittura. Il numero di extent e la loro dimensione, tuttavia, richiedono considerazioni relative all'applicazione più specifiche. Durante l'aggiunta dell'extent iniziale al log, è necessario specificare una dimensione. La dimensione viene utilizzata per tutti gli ambiti aggiuntivi aggiunti al log sia manualmente che tramite il comportamento di aumento automatico. Inoltre, per trarre vantaggio dalle numerose funzionalità fornite dalla classe LogPolicy, per una data sequenza è necessario che siano sempre presenti almeno due extent.

Nell'esempio riportato di seguito viene illustrato come creare un log e come aggiungere due contenitori di extent da 1MB.

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

Impostazione di criteri

L'impostazione dei criteri di log tramite la struttura LogPolicy è la seconda attività da svolgere per lo sviluppo dell'applicazione di registrazione. Questa attività può essere eseguita solo in caso di utilizzo della classe LogRecordSequence basata su CLFS e deve essere impostata ogni volta che viene aperto un log, poiché i criteri non vengono salvati in modo permanente.

Un criterio contiene importanti opzioni, ad esempio l'aumento automatico e il conteggio di extent massimi. Nel ciclo di vita di un'applicazione, la variazione dei carichi può rendere insufficiente l'insieme iniziale di extent, con la possibilità che durante l'esecuzione venga generata una SequenceFullException. Per evitare che ciò si verifichi, è spesso preferibile consentire l'aumento automatico del log in modo da collocare in modo trasparente i carichi aggiuntivi. Notare che l'aggiunta manuale degli extent durante SequenceFullException è supportata e può essere utilizzata al posto dell'aumento automatico trasparente.

Se si utilizza un log circolare, è necessario inoltre impostare un conteggio di extent massimi. Inoltre, la struttura LogPolicy offre molte impostazioni aggiuntive comuni, ad esempio le impostazioni di riduzione automatica e di frequenza di aumento. La modifica di questi valori può avere effetti significativi sulle prestazioni dell'applicazione e deve essere gestita in base alla frequenza di I/O di qualsiasi log all'interno del sistema.

Nell'esempio riportato di seguito viene illustrata l'impostazione di un criterio di log per l'aumento automatico fino a un massimo di 100 extent, con un aumento di 5 extent per volta.

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();

Aggiunta di un record

È necessario considerare le varie tecniche disponibili per fornire dati in un formato comprensibile ai metodi Append.

I metodi Append correnti sono stati ottimizzati per la gestione di matrici di byte ed elenchi di matrici di byte. Tuttavia, è anche possibile utilizzarli congiuntamente a classi di serializzazione di livello superiore. Il formato di record risultante deve restare comunque comprensibile in modo da utilizzare in modo efficace le funzionalità fornite dalla classe IRecordSequence. Gli elementi riportati di seguito illustrano un esempio di codice di serializzazione di alto livello con l'utilizzo delle funzionalità basate su DataContractAttribute in System.Runtime.Serialization.Formatters.

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, …);

Quando si aggiungono record a una sequenza di record, si devono considerare attentamente i vincoli di spazio e tempo dell'applicazione. Ad esempio, l'applicazione può fare in modo che un'operazione di aggiunta venga completata solo se è presente spazio sufficiente nel log per la scrittura successiva di record aggiuntivi tramite l'utilizzo della classe ReservationCollection. Generalmente, i sistemi di elaborazione delle transazioni basati su ARIES possono fare riferimento a tali record, che svolgono funzioni di record di compensazione o di annullamento, utilizzati se è necessario eseguire il rollback del lavoro. Analogamente,alcuni sistemi dispongono di regole rigide che gestiscono l'ora in cui vanno scritti i record. La selezione dei record da cancellare dal disco e di quelli per cui consentire lo svuotamento lento, si basa esclusivamente sui requisiti dell'applicazione e sui vincoli del sistema. Tali considerazioni possono influire sia sulle prestazioni che sulla correttezza.

Le opzioni di spazio e tempo vengono esposte all'utente tramite i vari metodi Append. Nell'esempio riportato di seguito, viene illustrata l'impostazione di tali opzioni tramite l'aggiunta di tre record e la concessione di spazio per i record successivi. Viene effettuato solamente uno svuotamento al momento della scrittura dell'ultimo record.

// 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);

Se la proprietà RetryAppend è true e un'operazione di aggiunta non riesce per mancanza di spazio disponibile nella sequenza, la sequenza di record tenta di liberare spazio ed esegue nuovamente l'operazione di accodamento. L'implementazione corretta per liberare spazio varia. Ad esempio, la classe LogRecordSequence richiamerà il modulo criteri CLFS, come descritto nella sezione "Liberare spazio tramite TailPinnedEvent" riportata di seguito.

Prenotazioni

Le prenotazioni possono essere eseguite in due modalità come illustrato negli esempi riportati di seguito. È possibile adottare le pratiche negli esempi per un'elaborazione affidabile. Questa attività può essere eseguita solo se si utilizza la classe LogRecordSequence basata su CLFS.

Utilizzo del metodo 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);

Utilizzo dell'approccio manuale

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);

Lettura del log

La lettura dal log può verificarsi in qualsiasi momento nel ciclo di vita di un'applicazione. A seconda della situazione, tuttavia, può essere necessario eseguire l'iterazione dei record archiviati nel log in ordini diversi. Oltre alle direttive standard specificate da Next e Previous per il log, è anche possibile eseguire l'iterazione in un ordine definito dall'utente, specificato in caso di aggiunta di record singoli alla sequenza. Notare anche che Previous non è sempre sequenziale in termini di log fisico, poiché l'utente può specificare il record precedente aggiunto dal thread corrente di esecuzione durante un'operazione Append.

Nell'esempio riportato di seguito viene letto un log in ordine sequenziale, a partire dal record col numero sequenziale 'startSqn'.

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

Liberare spazio tramite TailPinnedEvent

Uno degli approcci utilizzabili da una sequenza di record per liberare spazio è la generazione dell'evento TailPinned. Tale evento indica che la coda della sequenza (ovvero il numero di sequenza di base) deve essere spostata in avanti per liberare spazio.

L'evento può essere generato in qualsiasi momento in cui la sequenza di record decide che è necessario liberare spazio, per una qualsiasi ragione. Ad esempio, il modulo criteri CLFS può decidere di generare l'evento quando determina che le code di due client di log che condividono lo stesso file di log sono troppo distanti. Il recupero dello spazio può essere effettuato scrivendo aree di riavvio o troncando il log e utilizzando il metodo AdvanceBaseSequenceNumber per il recupero dello spazio. Nell'esempio riportato di seguito viene illustrato il secondo approccio.

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);

}

È inoltre possibile chiamare il metodo WriteRestartArea all'esterno dell'evento TailPinned per recuperare spazio. Un'area di riavvio è simile a un checkpoint in altri sistemi di elaborazione di log. La chiamata di questo metodo indica che l'applicazione considera tutti i record precedenti prima dell'area di riavvio come completi e utilizzabili per successive aggiunte di record. Analogamente agli altri record, per poter funzionare, il record scritto da questo metodo richiede spazio disponibile effettivo nel log.

Multiplexing

Se si dispone di più applicazioni che lavorano come un'unità, è possibile utilizzare la classe LogRecordSequence basata su CLFS per modificare un unico flusso di file NTFS. Nell'esempio riportato di seguito viene illustrato come creare un log in multiplexing con due flussi. Vengono eseguite le operazioni di aggiunta e lettura con interleaving ai record del log.

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);
        }

    }
}

Gestione delle eccezioni

Le applicazioni che utilizzano System.IO.Log devono essere preparate in modo da gestire eventi failfast derivanti dall'infrastruttura. In alcune situazioni, System.IO.Log non utilizza le eccezioni per comunicare gli errori a un'applicazione. Al contrario, le eccezioni vengono utilizzate principalmente per errori reversibili sui quali un sviluppatore di applicazioni può agire. Tuttavia, si verificheranno eventi failfast se l'ulteriore esecuzione produce file di log corrotti o potenzialmente inutilizzabili. Quando si verificano eventi failfast, non è disponibile nessun'altra applicazione per correggere il problema e un'applicazione deve essere preparata per terminare.

Per ulteriori informazioni su failfast, vedere FailFast.

Molte delle eccezioni generate da System.IO.Log, provengono dalle implementazioni del log interne. Gli elementi riportati di seguito elencano alcune delle eccezioni più importanti di cui essere a conoscenza.

  • SequenceFullException: questa eccezione potrebbe essere o non essere fatale. Se AutoGrow è impostato su true ed è disponibile sufficiente spazio per l'aumento, può essere comunque generata una SequenceFullException. Ciò avviene perché l'implementazione dell'aumento automatico è implicitamente un'operazione asincrona, non atomica e non è possibile garantire il completamento della frequenza di aumento in un'unica volta. Ad esempio, se la frequenza di aumento è impostata sui 100 extent, potrebbe essere necessario del tempo considerevole per l'aggiunta di tutti i 100 ambiti all'archivio del log. Ciò può comportare la generazione di questa eccezione in modo intermittente durante quest'arco di tempo.

  • Gestore TailPinned: un'eccezione generata all'interno dell'evento TailPinned viene segnalata all'implementazione del log interna. Ciò indica l'incapacità di un'applicazione di spostare il numero di sequenza di base. Viene generata una SequenceFullException, dal momento che l'evento TailPinned rappresenta l'ultima possibilità di un'applicazione di impedire condizioni di log pieno.

  • ReservationNotFoundException: questa eccezione si verifica quando si tenta di aggiungere record utilizzando un insieme di prenotazioni e tutte le prenotazioni di dimensione appropriata sono già occupate.

Vedere anche

Riferimenti

System.IO.Log
LogRecordSequence
FileRecordSequence

Altre risorse

Supporto di registrazione in System.IO.Log

Footer image

Invia commenti su questo argomento a Microsoft.

Copyright © 2007 Microsoft Corporation. Tutti i diritti riservati.