Skip to content

Commit e705bb1

Browse files
committed
Merge branch 'logging-fixes'
Merge #4
2 parents a68fd11 + 2252655 commit e705bb1

File tree

8 files changed

+168
-307
lines changed

8 files changed

+168
-307
lines changed

backupdb/management/commands/backupdb.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
from optparse import make_option
22
from subprocess import CalledProcessError
3+
import logging
34
import os
45
import time
56

6-
from django.core.management.base import BaseCommand
7-
8-
from backupdb_utils.commands import do_postgresql_backup
7+
from backupdb_utils.commands import BaseBackupDbCommand, do_postgresql_backup
98
from backupdb_utils.exceptions import BackupError
9+
from backupdb_utils.log import section, SectionError, SectionWarning
1010
from backupdb_utils.settings import BACKUP_DIR, BACKUP_CONFIG
11-
from backupdb_utils.streams import err, section, SectionError, set_verbosity
11+
12+
logger = logging.getLogger(__name__)
1213

1314

14-
class Command(BaseCommand):
15+
class Command(BaseBackupDbCommand):
1516
help = 'Backs up each database in settings.DATABASES.'
16-
can_import_settings = True
1717

18-
option_list = BaseCommand.option_list + (
18+
option_list = BaseBackupDbCommand.option_list + (
1919
make_option(
2020
'--backup-name',
2121
help=(
@@ -48,14 +48,14 @@ class Command(BaseCommand):
4848
)
4949

5050
def handle(self, *args, **options):
51+
super(Command, self).handle(*args, **options)
52+
5153
from django.conf import settings
5254

5355
current_time = time.strftime('%F-%s')
5456
backup_name = options['backup_name'] or current_time
5557
show_output = options['show_output']
5658

57-
set_verbosity(int(options['verbosity']))
58-
5959
# Ensure backup dir present
6060
if not os.path.exists(BACKUP_DIR):
6161
os.makedirs(BACKUP_DIR)
@@ -67,7 +67,7 @@ def handle(self, *args, **options):
6767
engine = db_config['ENGINE']
6868
backup_config = BACKUP_CONFIG.get(engine)
6969
if not backup_config:
70-
raise SectionError("! Backup for '{0}' engine not implemented".format(engine))
70+
raise SectionWarning("Backup for '{0}' engine not implemented".format(engine))
7171

7272
# Get backup file name
7373
backup_base_name = '{db_name}-{backup_name}.{backup_extension}.gz'.format(
@@ -90,8 +90,8 @@ def handle(self, *args, **options):
9090
# Run backup command
9191
try:
9292
backup_func(**backup_kwargs)
93-
err("* Backup of '{db_name}' saved in '{backup_file}'".format(
93+
logger.info("Backup of '{db_name}' saved in '{backup_file}'".format(
9494
db_name=db_name,
9595
backup_file=backup_file))
9696
except (BackupError, CalledProcessError) as e:
97-
raise SectionError('! {0}'.format(e))
97+
raise SectionError(e)

backupdb/management/commands/restoredb.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
from optparse import make_option
22
from subprocess import CalledProcessError
3+
import logging
34
import os
45

5-
from django.core.management.base import BaseCommand, CommandError
6+
from django.core.management.base import CommandError
67
from django.conf import settings
78
from django.db import close_connection
89

10+
from backupdb_utils.commands import BaseBackupDbCommand
911
from backupdb_utils.exceptions import RestoreError
1012
from backupdb_utils.files import get_latest_timestamped_file
13+
from backupdb_utils.log import section, SectionError, SectionWarning
1114
from backupdb_utils.settings import BACKUP_DIR, BACKUP_CONFIG
12-
from backupdb_utils.streams import err, set_verbosity, section, SectionError
1315

16+
logger = logging.getLogger(__name__)
1417

15-
class Command(BaseCommand):
18+
19+
class Command(BaseBackupDbCommand):
1620
help = 'Restores each database in settings.DATABASES from latest db backup.'
17-
can_import_settings = True
1821

19-
option_list = BaseCommand.option_list + (
22+
option_list = BaseBackupDbCommand.option_list + (
2023
make_option(
2124
'--backup-name',
2225
help=(
@@ -53,6 +56,8 @@ class Command(BaseCommand):
5356
)
5457

5558
def handle(self, *args, **options):
59+
super(Command, self).handle(*args, **options)
60+
5661
# Django is querying django_content_types in a hanging transaction
5762
# Because of this psql can't drop django_content_types and just hangs
5863
close_connection()
@@ -65,16 +70,14 @@ def handle(self, *args, **options):
6570
drop_tables = options['drop_tables']
6671
show_output = options['show_output']
6772

68-
set_verbosity(int(options['verbosity']))
69-
7073
# Loop through databases
7174
for db_name, db_config in settings.DATABASES.items():
7275
with section("Restoring '{0}'...".format(db_name)):
7376
# Get backup config for this engine type
7477
engine = db_config['ENGINE']
7578
backup_config = BACKUP_CONFIG.get(engine)
7679
if not backup_config:
77-
raise SectionError("! Restore for '{0}' engine not implemented".format(engine))
80+
raise SectionWarning("Restore for '{0}' engine not implemented".format(engine))
7881

7982
# Get backup file name
8083
backup_extension = backup_config['backup_extension']
@@ -89,7 +92,7 @@ def handle(self, *args, **options):
8992
try:
9093
backup_file = get_latest_timestamped_file(backup_extension)
9194
except RestoreError as e:
92-
raise SectionError('! {0}'.format(e))
95+
raise SectionError(e)
9396

9497
# Find restore command and get kwargs
9598
restore_func = backup_config['restore_func']
@@ -103,8 +106,8 @@ def handle(self, *args, **options):
103106
# Run restore command
104107
try:
105108
restore_func(**restore_kwargs)
106-
err("* Restored '{db_name}' from '{backup_file}'".format(
109+
logger.info("Restored '{db_name}' from '{backup_file}'".format(
107110
db_name=db_name,
108111
backup_file=backup_file))
109112
except (RestoreError, CalledProcessError) as e:
110-
raise SectionError('! {0}'.format(e))
113+
raise SectionError(e)

backupdb_utils/commands.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
1+
import logging
12
import os
23
import shlex
34

5+
from django.core.management.base import BaseCommand
6+
47
from .exceptions import RestoreError
58
from .processes import pipe_commands, pipe_commands_to_file
69

710

811
PG_DROP_SQL = """SELECT 'DROP TABLE IF EXISTS "' || tablename || '" CASCADE;' FROM pg_tables WHERE schemaname = 'public';"""
912

13+
14+
class BaseBackupDbCommand(BaseCommand):
15+
can_import_settings = True
16+
17+
LOG_LEVELS = {
18+
0: logging.ERROR,
19+
1: logging.INFO,
20+
2: logging.DEBUG,
21+
3: logging.DEBUG,
22+
}
23+
LOG_FORMAT = '%(asctime)s - %(levelname)-8s: %(message)s'
24+
25+
def _setup_logging(self, level):
26+
level = int(level)
27+
logging.basicConfig(format=self.LOG_FORMAT, level=self.LOG_LEVELS[level])
28+
29+
def handle(self, *args, **options):
30+
self._setup_logging(options['verbosity'])
31+
32+
1033
def apply_arg_values(arg_values):
1134
"""
1235
Apply argument to values.
36+
1337
l = [('--name={0}', 'name'),
1438
('--password={0}', 'password'),
1539
('--level={0}', ''),

backupdb_utils/log.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import contextlib
2+
import logging
3+
4+
logger = logging.getLogger(__name__)
5+
6+
7+
def bar(msg='', width=40, position=None):
8+
r"""
9+
Returns a string with text centered in a bar caption.
10+
11+
Examples:
12+
>>> bar('test', width=10)
13+
'== test =='
14+
>>> bar(width=10)
15+
'=========='
16+
>>> bar('Richard Dean Anderson is...', position='top', width=50)
17+
'//========= Richard Dean Anderson is... ========\\\\'
18+
>>> bar('...MacGyver', position='bottom', width=50)
19+
'\\\\================= ...MacGyver ================//'
20+
"""
21+
if position == 'top':
22+
start_bar = '//'
23+
end_bar = r'\\'
24+
elif position == 'bottom':
25+
start_bar = r'\\'
26+
end_bar = '//'
27+
else:
28+
start_bar = end_bar = '=='
29+
30+
if msg:
31+
msg = ' ' + msg + ' '
32+
33+
width -= 4
34+
35+
return start_bar + msg.center(width, '=') + end_bar
36+
37+
38+
class SectionError(Exception):
39+
pass
40+
41+
42+
class SectionWarning(Exception):
43+
pass
44+
45+
46+
@contextlib.contextmanager
47+
def section(msg):
48+
"""
49+
Context manager that prints a top bar to stderr upon entering and a bottom
50+
bar upon exiting. The caption of the top bar is specified by `msg`. The
51+
caption of the bottom bar is '...done!' if the context manager exits
52+
successfully. If a SectionError or SectionWarning is raised inside of the
53+
context manager, SectionError.message or SectionWarning.message is passed
54+
to logging.error or logging.warning respectively and the bottom bar caption
55+
becomes '...skipped.'.
56+
"""
57+
logger.info(bar(msg, position='top'))
58+
try:
59+
yield
60+
except SectionError as e:
61+
logger.error(e.message)
62+
logger.info(bar('...skipped.', position='bottom'))
63+
except SectionWarning as e:
64+
logger.warning(e.message)
65+
logger.info(bar('...skipped.', position='bottom'))
66+
else:
67+
logger.info(bar('...done!', position='bottom'))

backupdb_utils/processes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from subprocess import Popen, PIPE, CalledProcessError
2+
import logging
23
import os
34
import shutil
45

5-
from .streams import err
6+
logger = logging.getLogger(__name__)
67

78

89
def extend_env(extra_env):
@@ -31,7 +32,7 @@ def pipe_commands(cmds, extra_env=None, show_stderr=False, show_last_stdout=Fals
3132
env_str = (get_env_str(extra_env) + ' ') if extra_env else ''
3233
cmd_strs = [env_str + ' '.join(cmd) for cmd in cmds]
3334

34-
err('* Running `{0}`'.format(' | '.join(cmd_strs)), verbosity=1)
35+
logger.info('Running `{0}`'.format(' | '.join(cmd_strs)))
3536

3637
with open('/dev/null', 'w') as NULL:
3738
# Start processes
@@ -65,7 +66,7 @@ def pipe_commands_to_file(cmds, path, extra_env=None, show_stderr=False):
6566
env_str = (get_env_str(extra_env) + ' ') if extra_env else ''
6667
cmd_strs = [env_str + ' '.join(cmd) for cmd in cmds]
6768

68-
err('* Saving output of `{0}`'.format(' | '.join(cmd_strs)), verbosity=1)
69+
logger.info('Saving output of `{0}`'.format(' | '.join(cmd_strs)))
6970

7071
with open('/dev/null', 'w') as NULL:
7172
# Start processes

0 commit comments

Comments
 (0)