Skip to content

Commit 3f97c9f

Browse files
Allow for custom log formats to be defined in stacks.yaml (#705)
Allow for custom log formats to be defined in stacker.yaml #705 Docs and config model adjustment. modified: .gitignore modified: docs/config.rst modified: stacker/commands/stacker/__init__.py modified: stacker/commands/stacker/base.py modified: stacker/config/__init__.py modified: stacker/logger/__init__.py new file: stacker/tests/fixtures/not-basic.env new file: stacker/tests/fixtures/vpc-custom-log-format-info.yaml modified: stacker/tests/test_stacker.py
1 parent 22e62f1 commit 3f97c9f

File tree

9 files changed

+97
-20
lines changed

9 files changed

+97
-20
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,5 @@ dev.yaml
6868
dev.env
6969

7070
tests/fixtures/blueprints/*-result
71+
72+
FakeKey.pem

docs/config.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,28 @@ Here's an example of a target that will execute all "database" stacks::
469469
required_by:
470470
- databases
471471

472+
Custom Log Formats
473+
------------------
474+
475+
By default, stacker uses the following `log_formats`::
476+
477+
log_formats:
478+
info: "[%(asctime)s] %(message)s"
479+
color: "[%(asctime)s] \033[%(color)sm%(message)s\033[39m"
480+
debug: "[%(asctime)s] %(levelname)s %(threadName)s %(name)s:%(lineno)d(%(funcName)s): %(message)s"
481+
482+
You may optionally provide custom `log_formats`.
483+
484+
You may use any of the standard Python
485+
[logging module format attributes](https://docs.python.org/2.7/library/logging.html#logrecord-attributes)
486+
when building your `log_formats`.
487+
488+
In this example, we add the environment name to each log line::
489+
490+
log_formats:
491+
info: "[%(asctime)s] ${environment} %(message)s"
492+
color: "[%(asctime)s] ${environment} \033[%(color)sm%(message)s\033[39m"
493+
472494
Variables
473495
==========
474496

stacker/commands/stacker/__init__.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,37 @@ class Stacker(BaseCommand):
2424
subcommands = (Build, Destroy, Info, Diff, Graph)
2525

2626
def configure(self, options, **kwargs):
27-
super(Stacker, self).configure(options, **kwargs)
28-
if options.interactive:
29-
logger.info("Using interactive AWS provider mode.")
30-
else:
31-
logger.info("Using default AWS provider mode")
3227

3328
session_cache.default_profile = options.profile
3429

35-
config = load_config(
30+
self.config = load_config(
3631
options.config.read(),
3732
environment=options.environment,
38-
validate=True)
33+
validate=True,
34+
)
3935

4036
options.provider_builder = default.ProviderBuilder(
4137
region=options.region,
4238
interactive=options.interactive,
4339
replacements_only=options.replacements_only,
4440
recreate_failed=options.recreate_failed,
45-
service_role=config.service_role,
41+
service_role=self.config.service_role,
4642
)
4743

4844
options.context = Context(
4945
environment=options.environment,
50-
config=config,
46+
config=self.config,
5147
# Allow subcommands to provide any specific kwargs to the Context
5248
# that it wants.
5349
**options.get_context_kwargs(options)
5450
)
5551

52+
super(Stacker, self).configure(options, **kwargs)
53+
if options.interactive:
54+
logger.info("Using interactive AWS provider mode.")
55+
else:
56+
logger.info("Using default AWS provider mode")
57+
5658
def add_arguments(self, parser):
5759
parser.add_argument("--version", action="version",
5860
version="%%(prog)s %s" % (__version__,))

stacker/commands/stacker/base.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,6 @@ def add_subcommands(self, parser):
107107
subparser.set_defaults(
108108
get_context_kwargs=subcommand.get_context_kwargs)
109109

110-
@property
111-
def logger(self):
112-
if not hasattr(self, "_logger"):
113-
self._logger = logging.getLogger(self.name)
114-
return self._logger
115-
116110
def parse_args(self, *vargs):
117111
parser = argparse.ArgumentParser(description=self.description)
118112
self.add_subcommands(parser)
@@ -126,7 +120,7 @@ def run(self, options, **kwargs):
126120

127121
def configure(self, options, **kwargs):
128122
if self.setup_logging:
129-
self.setup_logging(options.verbose)
123+
self.setup_logging(options.verbose, self.config.log_formats)
130124

131125
def get_context_kwargs(self, options, **kwargs):
132126
"""Return a dictionary of kwargs that will be used with the Context.

stacker/config/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@ class Config(Model):
433433
stacks = ListType(
434434
ModelType(Stack), default=[])
435435

436+
log_formats = DictType(StringType, serialize_when_none=False)
437+
436438
def _remove_excess_keys(self, data):
437439
excess_keys = set(data.keys())
438440
excess_keys -= self._schema.valid_input_keys

stacker/logger/__init__.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,29 @@ def format(self, record):
2121
return msg
2222

2323

24-
def setup_logging(verbosity):
24+
def setup_logging(verbosity, formats=None):
25+
"""
26+
Configure a proper logger based on verbosity and optional log formats.
27+
28+
Args:
29+
verbosity (int): 0, 1, 2
30+
formats (dict): Optional, looks for `info`, `color`, and `debug` keys
31+
which may override the associated default log formats.
32+
"""
33+
if formats is None:
34+
formats = {}
35+
2536
log_level = logging.INFO
26-
log_format = INFO_FORMAT
37+
38+
log_format = formats.get("info", INFO_FORMAT)
39+
2740
if sys.stdout.isatty():
28-
log_format = COLOR_FORMAT
41+
log_format = formats.get("color", COLOR_FORMAT)
2942

3043
if verbosity > 0:
3144
log_level = logging.DEBUG
32-
log_format = DEBUG_FORMAT
45+
log_format = formats.get("debug", DEBUG_FORMAT)
46+
3347
if verbosity < 2:
3448
logging.getLogger("botocore").setLevel(logging.CRITICAL)
3549

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
namespace: test.stacker
2+
environment: test
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
log_formats:
2+
info: "[%(asctime)s] ${environment} custom log format - %(message)s"
3+
4+
stacks:
5+
- name: vpc
6+
class_path: stacker.tests.fixtures.mock_blueprints.VPC
7+
variables:
8+
InstanceType: m3.medium
9+
SshKeyName: default
10+
ImageName: NAT
11+
# Only build 2 AZs, can be overridden with -p on the command line
12+
# Note: If you want more than 4 AZs you should add more subnets below
13+
# Also you need at least 2 AZs in order to use the DB because
14+
# of the fact that the DB blueprint uses MultiAZ
15+
AZCount: 2
16+
# Enough subnets for 4 AZs
17+
PublicSubnets: 10.128.0.0/24,10.128.1.0/24,10.128.2.0/24,10.128.3.0/24
18+
PrivateSubnets: 10.128.8.0/22,10.128.12.0/22,10.128.16.0/22,10.128.20.0/22

stacker/tests/test_stacker.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,27 @@ def test_stacker_build_fail_when_parameters_in_stack_def(self):
9292
with self.assertRaises(InvalidConfig):
9393
stacker.configure(args)
9494

95+
def test_stacker_build_custom_info_log_format(self):
96+
stacker = Stacker()
97+
args = stacker.parse_args(
98+
[
99+
"build", "-r", "us-west-2",
100+
"stacker/tests/fixtures/not-basic.env",
101+
"stacker/tests/fixtures/vpc-custom-log-format-info.yaml"
102+
]
103+
)
104+
stacker.configure(args)
105+
self.assertEqual(
106+
stacker.config.log_formats["info"],
107+
'[%(asctime)s] test custom log format - %(message)s'
108+
)
109+
self.assertIsNone(
110+
stacker.config.log_formats.get("color")
111+
)
112+
self.assertIsNone(
113+
stacker.config.log_formats.get("debug")
114+
)
115+
95116

96117
if __name__ == '__main__':
97118
unittest.main()

0 commit comments

Comments
 (0)