Errori di registrazione causati da eccezioni in profondità nell'applicazione

6

Quali sono le migliori pratiche per la registrazione in profondità nella sorgente di un'applicazione? È una cattiva pratica avere più voci nel registro eventi per un singolo errore?

Ad esempio, supponiamo di avere un sistema ETL il cui passaggio di trasformazione comporta: un trasformatore, pipeline, algoritmo di elaborazione e motore di elaborazione.

In breve, il trasformatore acquisisce un file di input, analizza i record e invia i record attraverso la pipeline. La pipeline aggrega i risultati dell'algoritmo di elaborazione (che potrebbe eseguire l'elaborazione seriale o parallela). L'algoritmo di elaborazione invia ogni record attraverso uno o più motori di elaborazione. Quindi, ho almeno quattro livelli: Transformer - > Pipeline - > Algoritmo - > Motore.

Il mio codice potrebbe quindi somigliare a quanto segue:

class Transformer {
    void Process(InputSource input) {
        try {
            var inRecords = _parser.Parse(input.Stream);
            var outRecords = _pipeline.Transform(inRecords);
        } catch (Exception ex) {
            var inner = new ProcessException(input, ex);
            _logger.Error("Unable to parse source " + input.Name, inner);
            throw inner;
        }
    }
}

class Pipeline {
    IEnumerable<Result> Transform(IEnumerable<Record> records) {
        // NOTE: no try/catch as I have no useful information to provide
        // at this point in the process
        var results = _algorithm.Process(records);

        // examine and do useful things with results
        return results;
    }
}

class Algorithm {
    IEnumerable<Result> Process(IEnumerable<Record> records) {
        var results = new List<Result>();
        foreach (var engine in Engines) {
            foreach (var record in records) {
                try {
                    engine.Process(record);
                } catch (Exception ex) {
                    var inner = new EngineProcessingException(engine, record, ex);
                    _logger.Error("Engine {0} unable to parse record {1}", engine, record);
                    throw inner;
                }
            }
        }
    }
}


class Engine {
    Result Process(Record record) {
        for (int i=0; i<record.SubRecords.Count; ++i) {
            try { 
                Validate(record.subRecords[i]);
            } catch (Exception ex) {
                var inner = new RecordValidationException(record, i, ex);
                _logger.Error(
                    "Validation of subrecord {0} failed for record {1}",
                    i, record
                    );
            }
        }
    }
}

Ci sono alcune cose importanti da notare:

  • Un singolo errore al livello più profondo causa tre voci di registro (brutto? DOS?)
  • Le eccezioni generate contengono tutte le informazioni importanti e utili
  • La registrazione avviene solo quando l'impossibilità di farlo causerebbe la perdita di informazioni utili a un livello inferiore.

Pensieri e dubbi:

  • Non mi piace avere tante voci di registro per ogni errore
  • Non voglio perdere dati importanti e utili; le eccezioni contengono tutte le informazioni importanti, ma lo stacktrace è in genere l'unica cosa visualizzata oltre al messaggio.
  • Posso accedere a diversi livelli (ad es., avviso, informativo)
  • Le classi di livello superiore dovrebbero essere completamente inconsapevoli della struttura delle eccezioni di livello inferiore (che potrebbero cambiare con la sostituzione delle diverse implementazioni).
  • Le informazioni disponibili ai livelli superiori non devono essere trasmesse ai livelli inferiori.

Quindi, per riformulare le domande principali:

Quali sono le migliori pratiche per la registrazione in profondità nella sorgente di un'applicazione? È una cattiva pratica avere più voci nel registro eventi per un singolo errore?

    
posta Kaleb Pederson 08.06.2012 - 00:29
fonte

4 risposte

9

Preferisco:

  1. Avvolgi le eccezioni con tutte le informazioni utili che posso avere disponibili al livello in cui sono state rilevate.
  2. Sempre annulla le eccezioni se non vengono rimandate.
  3. Solo eccezioni di registro se non vengono rimandate.

Sembra che tu stia già seguendo i numeri 1 e 2, quindi devi solo applicare # 3 e puoi assicurarti che ogni errore venga registrato una sola volta, con la massima quantità possibile di informazioni.

class Transformer {
    void Process(InputSource input) {
        try {
            var inRecords = _parser.Parse(input.Stream);
            var outRecords = _pipeline.Transform(inRecords);
        } catch (Exception ex) {
            // Make sure the message string on this exception class produces
            // a verbose error message like "Unable to parse source " + input.Name
            throw new ProcessException(input, ex);
        }
    }
}

class Pipeline {
    IEnumerable<Result> Transform(IEnumerable<Record> records) {
        // NOTE: no try/catch as I have no useful information to provide
        // at this point in the process
        var results = _algorithm.Process(records);

        // examine and do useful things with results
        return results;
    }
}

class Algorithm {
    IEnumerable<Result> Process(IEnumerable<Record> records) {
        var results = new List<Result>();
        foreach (var engine in Engines) {
            foreach (var record in records) {
                try {
                    engine.Process(record);
                } catch (Exception ex) {
                    // Likewise, ensure the message string for this class records
                    // the specifics, so that logging a stack trace will contain
                    // all the necessary details to reproduce.
                    throw new EngineProcessingException(engine, record, ex);
                }
            }
        }
    }
}


class Engine {
    Result Process(Record record) {
        for (int i=0; i<record.SubRecords.Count; ++i) {
            try { 
                Validate(record.subRecords[i]);
            } catch (Exception ex) {
                var inner = new RecordValidationException(record, i, ex);
                // Since you're not rethrowing, make sure you log this error.
                _logger.Error(
                    ex,
                    "Validation of subrecord {0} failed for record {1}",
                    i, record
                    );
            }
        }
    }
}

Tieni presente che questo apre la possibilità che la tua eccezione possa finire nel codice in cui l'autore non ha seguito la regola n. 2 e non viene prodotto alcun registro. Ma se riesci a garantire che queste tre regole siano seguite in modo coerente, ho trovato che questo è un approccio ideale.

    
risposta data 08.06.2012 - 01:30
fonte
1

Credo che il paradigma Aspect Oriented Programming possa offrire alcune informazioni sulla soluzione generale al tuo problema. Nella ricerca di varie lingue / tecnologie, ho letto che AOP è particolarmente adatto per esigenze "trasversali", comprese idee come "logging"

Logging exemplifies a crosscutting concern because a logging strategy necessarily affects every logged part of the system. Logging thereby crosscuts all logged classes and methods.

Alcune tecnologie lo rendono facile da implementare, altre no. Vorrei poterti offrire più informazioni.

    
risposta data 08.06.2012 - 00:57
fonte
1

Se aggiungi qualche tipo di informazione sui componenti ai tuoi messaggi di log, sarai in grado di filtrare tutto tranne i livelli principali durante la visualizzazione dei log. Quindi se hai bisogno di maggiori informazioni su un errore puoi andare a cercarlo.

Non trovo sbagliato registrare più voci per un singolo errore. Molti programmi lo fanno già.

Ad esempio, se si utilizza Windows e si attivano i controlli degli eventi di sicurezza, è possibile che si verifichi una sequenza di eventi che include un errore di tentativo di accesso non riuscito dal sistema operativo e un errore di servizio per non riuscire ad aprire un file di configurazione o voce di registro. Quindi potresti ottenere un evento dal servizio per non essere in grado di avviarsi a causa della configurazione mancante.

    
risposta data 08.06.2012 - 01:09
fonte
1

Ho usato software di scrittura del lavoro per schede di riferimento. Non disponevamo di servizi di debugger per queste schede (il datore di lavoro era troppo economico per acquistare JTAG HW), ma solo la scrittura dei registri tramite porte seriali. La registrazione come schema di debug è estremamente utile ma è necessario sapere cosa registrare e quando. Ho trovato il seguente abbastanza utile:

Fai la differenza tra i log di 'got here' e i log degli errori molto ovvi. Ho usato i log degli errori di prefisso con 3 asterischi, ovvero " * Call to foo (ptr = 0x00000000, value = 6) non riuscito con -2 * ", così ho potuto cercare rapidamente il file di log per gli errori in un editor di testo (sarebbero di diversi megabyte).

Non limitarti a registrare che qualcosa è andato storto, registra ciò che è andato storto, preferibilmente nella funzione in cui viene lanciata l'eccezione o, se non hai accesso al codice, il più vicino possibile a quel punto.

Non registrare qualcosa per il gusto di farlo; solo se ti sarà utile. Ho usato situazioni di incontro in cui l'intero stack di chiamate veniva continuamente registrato. Sembra utile, ma tutto ciò che ha fatto è stato il polling dei log e quando hai a che fare con il comportamento asincrono risulta che non è poi così utile. La registrazione eccessiva può persino modificare il comportamento di runtime e nascondere problemi multi-thread che vengono visualizzati solo quando si utilizzano build di rilascio. Se si scende lungo il percorso di registrazione della traccia di esecuzione si può anche registrare i valori dei parametri passati alle funzioni laddove possibile.

Come per più registri entrate per lo stesso errore ... non un grosso problema. Il primo è di solito il colpevole e finché lo trovi e ha dei registri che forniscono sufficienti informazioni per poter riprodurre / isolare rapidamente l'errore, il resto dovrebbe solo essere la conferma che il tuo programma non si ripercuote su se stesso quando si verifica un errore.

    
risposta data 08.06.2012 - 01:26
fonte

Leggi altre domande sui tag