178 lines
5.2 KiB
Markdown
178 lines
5.2 KiB
Markdown
---
|
|
id: practical-python-8.2
|
|
source_exercise_id: "8.2"
|
|
title: "Adding logging to a module"
|
|
section: "8.2 Logging"
|
|
source_path: "08_Testing_debugging/02_Logging.md"
|
|
source_repo: "https://github.com/dabeaz-course/practical-python"
|
|
source_commit: "93dca856b41c61a0a0f85ae334116e4c125629ea"
|
|
student_visible_solution: false
|
|
has_private_solution: true
|
|
skip: false
|
|
---
|
|
|
|
# Exercise 8.2: Adding logging to a module
|
|
|
|
> Source: Practical Python Programming, `08_Testing_debugging/02_Logging.md`.
|
|
|
|
### Exercise 8.2: Adding logging to a module
|
|
|
|
In `fileparse.py`, there is some error handling related to
|
|
exceptions caused by bad input. It looks like this:
|
|
|
|
```python
|
|
# 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
|
|
```
|
|
|
|
Notice the print statements that issue diagnostic messages. Replacing those
|
|
prints with logging operations is relatively simple. Change the code like this:
|
|
|
|
```python
|
|
# 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
|
|
```
|
|
|
|
Now that you've made these changes, try using some of your code on
|
|
bad data.
|
|
|
|
```python
|
|
>>> import report
|
|
>>> a = report.read_portfolio('Data/missing.csv')
|
|
Row 4: Bad row: ['MSFT', '', '51.23']
|
|
Row 7: Bad row: ['IBM', '', '70.44']
|
|
>>>
|
|
```
|
|
|
|
If you do nothing, you'll only get logging messages for the `WARNING`
|
|
level and above. The output will look like simple print statements.
|
|
However, if you configure the logging module, you'll get additional
|
|
information about the logging levels, module, and more. Type these
|
|
steps to see that:
|
|
|
|
```python
|
|
>>> import logging
|
|
>>> logging.basicConfig()
|
|
>>> a = report.read_portfolio('Data/missing.csv')
|
|
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
|
|
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
|
|
>>>
|
|
```
|
|
|
|
You will notice that you don't see the output from the `log.debug()`
|
|
operation. Type this to change the level.
|
|
|
|
```
|
|
>>> logging.getLogger('fileparse').setLevel(logging.DEBUG)
|
|
>>> a = report.read_portfolio('Data/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: ''
|
|
>>>
|
|
```
|
|
|
|
Turn off all, but the most critical logging messages:
|
|
|
|
```
|
|
>>> logging.getLogger('fileparse').setLevel(logging.CRITICAL)
|
|
>>> a = report.read_portfolio('Data/missing.csv')
|
|
>>>
|
|
```
|
|
|
|
## 关联来源
|
|
|
|
- [[summaries/02_Logging]]
|