Introdução ao Módulo de Logging

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Esta seção apresenta brevemente o módulo de logging (registro).

Módulo logging

O módulo logging é um módulo da biblioteca padrão para registrar informações de diagnóstico. É também um módulo muito grande com muita funcionalidade sofisticada. Mostraremos um exemplo simples para ilustrar sua utilidade.

Exceções Revisadas

Nos exercícios, escrevemos uma função parse() que se parecia com isto:

## fileparse.py
def parse(f, types=None, names=None, delimiter=None):
    records = []
    for line in f:
        line = line.strip()
        if not line: continue
        try:
            records.append(split(line,types,names,delimiter))
        except ValueError as e:
            print("Couldn't parse :", line)
            print("Reason :", e)
    return records

Concentre-se na instrução try-except. O que você deve fazer no bloco except?

Você deve imprimir uma mensagem de aviso?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    print("Couldn't parse :", line)
    print("Reason :", e)

Ou você ignora silenciosamente?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    pass

Nenhuma das soluções é satisfatória porque você frequentemente quer ambos os comportamentos (selecionáveis pelo usuário).

Usando logging

O módulo logging pode resolver isso.

## fileparse.py
import logging
log = logging.getLogger(__name__)

def parse(f,types=None,names=None,delimiter=None):
    ...
    try:
        records.append(split(line,types,names,delimiter))
    except ValueError as e:
        log.warning("Couldn't parse : %s", line)
        log.debug("Reason : %s", e)

O código é modificado para emitir mensagens de aviso ou um objeto Logger especial. Aquele criado com logging.getLogger(__name__).

Fundamentos de Logging

Crie um objeto logger.

log = logging.getLogger(name)   ## name is a string

Emitindo mensagens de log.

log.critical(message [, args])
log.error(message [, args])
log.warning(message [, args])
log.info(message [, args])
log.debug(message [, args])

Cada método representa um nível diferente de severidade.

Todos eles criam uma mensagem de log formatada. args é usado com o operador % para criar a mensagem.

logmsg = message % args ## Written to the log

Configuração de Logging

O comportamento de logging é configurado separadamente.

## main.py

...

if __name__ == '__main__':
    import logging
    logging.basicConfig(
        filename  = 'app.log',      ## Log output file
        level     = logging.INFO,   ## Output level
    )

Tipicamente, esta é uma configuração única na inicialização do programa. A configuração é separada do código que faz as chamadas de logging.

Comentários

O logging é altamente configurável. Você pode ajustar todos os seus aspectos: arquivos de saída, níveis, formatos de mensagem, etc. No entanto, o código que usa o logging não precisa se preocupar com isso.

Exercício 8.2: Adicionando logging a um módulo

Em fileparse.py, há algum tratamento de erros relacionado a exceções causadas por entrada incorreta. Ele se parece com isto:

## fileparse.py
import csv

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Parse a CSV file into a list of records with type conversion.
    '''
    if select and not has_headers:
        raise RuntimeError('select requires column headers')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Read the file headers (if any)
    headers = next(rows) if has_headers else []

    ## If specific columns have been selected, make indices for filtering and set output columns
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Skip rows with no data
            continue

        ## If specific column indices are selected, pick them out
        if select:
            row = [ row[index] for index in indices]

        ## Apply type conversion to the row
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    print(f"Row {rowno}: Couldn't convert {row}")
                    print(f"Row {rowno}: Reason {e}")
                continue

        ## Make a dictionary or a tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

Observe as instruções print que emitem mensagens de diagnóstico. Substituir esses prints por operações de logging é relativamente simples. Altere o código assim:

## fileparse.py
import csv
import logging
log = logging.getLogger(__name__)

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Parse a CSV file into a list of records with type conversion.
    '''
    if select and not has_headers:
        raise RuntimeError('select requires column headers')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Read the file headers (if any)
    headers = next(rows) if has_headers else []

    ## If specific columns have been selected, make indices for filtering and set output columns
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Skip rows with no data
            continue

        ## If specific column indices are selected, pick them out
        if select:
            row = [ row[index] for index in indices]

        ## Apply type conversion to the row
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    log.warning("Row %d: Couldn't convert %s", rowno, row)
                    log.debug("Row %d: Reason %s", rowno, e)
                continue

        ## Make a dictionary or a tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

Agora que você fez essas alterações, tente usar um pouco do seu código em dados ruins.

>>> import report
>>> a = report.read_portfolio('missing.csv')
Row 4: Bad row: ['MSFT', '', '51.23']
Row 7: Bad row: ['IBM', '', '70.44']
>>>

Se você não fizer nada, você só receberá mensagens de logging para o nível WARNING e acima. A saída se parecerá com instruções print simples. No entanto, se você configurar o módulo logging, você obterá informações adicionais sobre os níveis de logging, módulo e muito mais. Digite estas etapas para ver isso:

>>> import logging
>>> logging.basicConfig()
>>> a = report.read_portfolio('missing.csv')
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
>>>

Você notará que não vê a saída da operação log.debug(). Digite isto para alterar o nível.

>>> logging.getLogger('fileparse').setLevel(logging.DEBUG)
>>> a = report.read_portfolio('missing.csv')
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
DEBUG:fileparse:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
DEBUG:fileparse:Row 7: Reason: invalid literal for int() with base 10: ''
>>>

Desligue todas, exceto as mensagens de logging mais críticas:

>>> logging.getLogger('fileparse').setLevel(logging.CRITICAL)
>>> a = report.read_portfolio('missing.csv')
>>>

Exercício 8.3: Adicionando Logging a um Programa

Para adicionar logging a uma aplicação, você precisa ter algum mecanismo para inicializar o módulo logging no módulo principal. Uma maneira de fazer isso é incluir algum código de configuração que se parece com isto:

## Este arquivo configura a configuração básica do módulo logging.
## Altere as configurações aqui para ajustar a saída de logging conforme necessário.
import logging
logging.basicConfig(
    filename = 'app.log',            ## Nome do arquivo de log (omita para usar stderr)
    filemode = 'w',                  ## Modo do arquivo (use 'a' para anexar)
    level    = logging.WARNING,      ## Nível de logging (DEBUG, INFO, WARNING, ERROR, ou CRITICAL)
)

Novamente, você precisaria colocar isso em algum lugar nos passos de inicialização do seu programa. Por exemplo, onde você colocaria isso no seu programa report.py?

Resumo

Parabéns! Você concluiu o laboratório de Logging. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.