Merge ~jfguedez/juju-lint:refactor-header-logging into juju-lint:master

Proposed by Jose Guedez
Status: Merged
Approved by: James Troup
Approved revision: d219e5ebd839c1ff6b635a07b1b689a4cdbc378a
Merged at revision: bd261d5c562a1e3d32b68bd59b0b3739807ab693
Proposed branch: ~jfguedez/juju-lint:refactor-header-logging
Merge into: juju-lint:master
Diff against target: 582 lines (+95/-247)
2 files modified
jujulint/lint.py (+91/-247)
jujulint/logging.py (+4/-0)
Reviewer Review Type Date Requested Status
Juju Lint maintainers Pending
Review via email: mp+408783@code.launchpad.net

Commit message

Refactor logging to make code more readable

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
James Troup (elmo) wrote :

Nice cleanup, thanks.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision bd261d5c562a1e3d32b68bd59b0b3739807ab693

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/jujulint/lint.py b/jujulint/lint.py
index 38dfc00..68fef78 100755
--- a/jujulint/lint.py
+++ b/jujulint/lint.py
@@ -21,6 +21,7 @@
21import collections21import collections
22from datetime import datetime, timezone22from datetime import datetime, timezone
23import json23import json
24import logging
24import os.path25import os.path
25import pprint26import pprint
26import re27import re
@@ -126,27 +127,16 @@ class Linter:
126 if self.overrides:127 if self.overrides:
127 for override in self.overrides.split("#"):128 for override in self.overrides.split("#"):
128 (name, where) = override.split(":")129 (name, where) = override.split(":")
129 self.logger.info(130 self._log_with_header(
130 "[{}] [{}/{}] Overriding {} with {}".format(131 "Overriding {} with {}".format(name, where), level=logging.INFO
131 self.cloud_name,
132 self.controller_name,
133 self.model_name,
134 name,
135 where,
136 )
137 )132 )
138 self.lint_rules["subordinates"][name] = dict(where=where)133 self.lint_rules["subordinates"][name] = dict(where=where)
139134
140 # Flatten all entries (to account for nesting due to YAML anchors (templating)135 # Flatten all entries (to account for nesting due to YAML anchors (templating)
141 self.lint_rules = {k: flatten_list(v) for k, v in self.lint_rules.items()}136 self.lint_rules = {k: flatten_list(v) for k, v in self.lint_rules.items()}
142137
143 self.logger.debug(138 self._log_with_header(
144 "[{}] [{}/{}] Lint Rules: {}".format(139 "Lint Rules: {}".format(pprint.pformat(self.lint_rules))
145 self.cloud_name,
146 self.controller_name,
147 self.model_name,
148 pprint.pformat(self.lint_rules),
149 )
150 )140 )
151 return True141 return True
152 self.logger.error("Rules file {} does not exist.".format(self.filename))142 self.logger.error("Rules file {} does not exist.".format(self.filename))
@@ -165,15 +155,7 @@ class Linter:
165 subordinates = [i.split("/")[0] for i in subordinates]155 subordinates = [i.split("/")[0] for i in subordinates]
166 else:156 else:
167 subordinates = []157 subordinates = []
168 self.logger.debug(158 self._log_with_header("{}: {}".format(unit, subordinates))
169 "[{}] [{}/{}] {}: {}".format(
170 self.cloud_name,
171 self.controller_name,
172 self.model_name,
173 unit,
174 subordinates,
175 )
176 )
177 machine = app_d["units"][unit]["machine"]159 machine = app_d["units"][unit]["machine"]
178 self.model.subs_on_machines.setdefault(machine, set())160 self.model.subs_on_machines.setdefault(machine, set())
179 for sub in subordinates:161 for sub in subordinates:
@@ -214,11 +196,8 @@ class Linter:
214 """Check if value is set per rule constraints."""196 """Check if value is set per rule constraints."""
215 if rule in config:197 if rule in config:
216 if check_value is True:198 if check_value is True:
217 self.logger.debug(199 self._log_with_header(
218 "[{}] [{}/{}] (PASS) Application {} correctly has config for '{}': {}.".format(200 "(PASS) Application {} correctly has config for '{}': {}.".format(
219 self.cloud_name,
220 self.controller_name,
221 self.model_name,
222 name,201 name,
223 rule,202 rule,
224 config[rule],203 config[rule],
@@ -241,11 +220,8 @@ class Linter:
241 )220 )
242 return False221 return False
243 elif check_value is False:222 elif check_value is False:
244 self.logger.debug(223 self._log_with_header(
245 "[{}] [{}/{}] (PASS) Application {} correctly had no config for '{}'.".format(224 "(PASS) Application {} correctly had no config for '{}'.".format(
246 self.cloud_name,
247 self.controller_name,
248 self.model_name,
249 name,225 name,
250 rule,226 rule,
251 )227 )
@@ -313,22 +289,16 @@ class Linter:
313 self, operator, app_name, check_value, config_key, app_config289 self, operator, app_name, check_value, config_key, app_config
314 ):290 ):
315 """Apply the provided config operator to the configuration."""291 """Apply the provided config operator to the configuration."""
316 model_header = "[{}] [{}/{}]".format(
317 self.cloud_name,
318 self.controller_name,
319 self.model_name,
320 )
321
322 # First check if the config key is present292 # First check if the config key is present
323 if config_key not in app_config:293 if config_key not in app_config:
324 self.logger.warn(294 self._log_with_header(
325 "{} Application {} has no config for '{}', cannot determine if {} {}.".format(295 "Application {} has no config for '{}', cannot determine if {} {}.".format(
326 model_header,
327 app_name,296 app_name,
328 config_key,297 config_key,
329 operator.repr,298 operator.repr,
330 repr(check_value),299 repr(check_value),
331 )300 ),
301 level=logging.WARN,
332 )302 )
333 return False303 return False
334304
@@ -336,9 +306,8 @@ class Linter:
336306
337 # Apply the check callable and handle the possible cases307 # Apply the check callable and handle the possible cases
338 if operator.check(check_value, actual_value):308 if operator.check(check_value, actual_value):
339 self.logger.debug(309 self._log_with_header(
340 "{} Application {} has a valid config for '{}': {} ({} {})".format(310 "Application {} has a valid config for '{}': {} ({} {})".format(
341 model_header,
342 app_name,311 app_name,
343 config_key,312 config_key,
344 repr(check_value),313 repr(check_value),
@@ -373,26 +342,20 @@ class Linter:
373 """Check application against provided rules."""342 """Check application against provided rules."""
374 rules = dict(rules)343 rules = dict(rules)
375 for rule in rules:344 for rule in rules:
376 self.logger.debug(345 self._log_with_header("Checking {} for configuration {}".format(name, rule))
377 "[{}] [{}/{}] Checking {} for configuration {}".format(
378 self.cloud_name, self.controller_name, self.model_name, name, rule
379 )
380 )
381 for check_op, check_value in rules[rule].items():346 for check_op, check_value in rules[rule].items():
382 # check_op should be the operator name, e.g. (eq, neq, gte, isset)347 # check_op should be the operator name, e.g. (eq, neq, gte, isset)
383 if check_op in VALID_CONFIG_CHECKS:348 if check_op in VALID_CONFIG_CHECKS:
384 check_method = getattr(self, check_op)349 check_method = getattr(self, check_op)
385 check_method(name, check_value, rule, config)350 check_method(name, check_value, rule, config)
386 else:351 else:
387 self.logger.warn(352 self._log_with_header(
388 "[{}] [{}/{}] Application {} has unknown check operation for {}: {}.".format(353 "Application {} has unknown check operation for {}: {}.".format(
389 self.cloud_name,
390 self.controller_name,
391 self.model_name,
392 name,354 name,
393 rule,355 rule,
394 check_op,356 check_op,
395 )357 ),
358 level=logging.WARN,
396 )359 )
397360
398 def check_configuration(self, applications):361 def check_configuration(self, applications):
@@ -401,13 +364,11 @@ class Linter:
401 # look for config rules for this application364 # look for config rules for this application
402 lint_rules = []365 lint_rules = []
403 if "charm" not in applications[application]:366 if "charm" not in applications[application]:
404 self.logger.warn(367 self._log_with_header(
405 "[{}] [{}/{}] Application {} has no charm.".format(368 "Application {} has no charm.".format(
406 self.cloud_name,
407 self.controller_name,
408 self.model_name,
409 application,369 application,
410 )370 ),
371 level=logging.WARN,
411 )372 )
412 continue373 continue
413374
@@ -442,103 +403,51 @@ class Linter:
442 for required_sub in self.lint_rules["subordinates"]:403 for required_sub in self.lint_rules["subordinates"]:
443 self.model.missing_subs.setdefault(required_sub, set())404 self.model.missing_subs.setdefault(required_sub, set())
444 self.model.extraneous_subs.setdefault(required_sub, set())405 self.model.extraneous_subs.setdefault(required_sub, set())
445 self.logger.debug(406 self._log_with_header("Checking for sub {}".format(required_sub))
446 "[{}] [{}/{}] Checking for sub {}".format(
447 self.cloud_name, self.controller_name, self.model_name, required_sub
448 )
449 )
450 where = self.lint_rules["subordinates"][required_sub]["where"]407 where = self.lint_rules["subordinates"][required_sub]["where"]
451 for machine in self.model.subs_on_machines:408 for machine in self.model.subs_on_machines:
452 self.logger.debug(409 self._log_with_header("Checking on {}".format(machine))
453 "[{}] [{}/{}] Checking on {}".format(
454 self.cloud_name, self.controller_name, self.model_name, machine
455 )
456 )
457 present_subs = self.model.subs_on_machines[machine]410 present_subs = self.model.subs_on_machines[machine]
458 apps = self.model.apps_on_machines[machine]411 apps = self.model.apps_on_machines[machine]
459 if where.startswith("on "): # only on specific apps412 if where.startswith("on "): # only on specific apps
460 required_on = where[3:]413 required_on = where[3:]
461 self.logger.debug(414 self._log_with_header(
462 "[{}] [{}/{}] Requirement {} is = from...".format(415 "Requirement {} is = from...".format(required_on)
463 self.cloud_name,
464 self.controller_name,
465 self.model_name,
466 required_on,
467 )
468 )416 )
469 if required_on not in apps:417 if required_on not in apps:
470 self.logger.debug(418 self._log_with_header("... NOT matched")
471 "[{}] [{}/{}] ... NOT matched".format(
472 self.cloud_name, self.controller_name, self.model_name
473 )
474 )
475 continue419 continue
476 self.logger.debug("[{}] [{}/{}] ... matched")420 self._log_with_header("... matched")
477 # TODO this needs to be not just one app, but a list421 # TODO this needs to be not just one app, but a list
478 elif where.startswith("all except "): # not next to this app422 elif where.startswith("all except "): # not next to this app
479 self.logger.debug(423 self._log_with_header("requirement is != form...")
480 "[{}] [{}/{}] requirement is != form...".format(
481 self.cloud_name, self.controller_name, self.model_name
482 )
483 )
484 not_on = where[11:]424 not_on = where[11:]
485 if not_on in apps:425 if not_on in apps:
486 self.logger.debug(426 self._log_with_header("... matched, not wanted on this host")
487 "[{}] [{}/{}] ... matched, not wanted on this host".format(
488 self.cloud_name, self.controller_name, self.model_name
489 )
490 )
491 continue427 continue
492 elif where == "host only":428 elif where == "host only":
493 self.logger.debug(429 self._log_with_header(
494 "[{}] [{}/{}] requirement is 'host only' form....".format(430 "requirement is 'host only' form...."
495 self.cloud_name, self.controller_name, self.model_name
496 )
497 )431 )
498 if is_container(machine):432 if is_container(machine):
499 self.logger.debug(433 self._log_with_header("... and we are a container, checking")
500 "[{}] [{}/{}] ... and we are a container, checking".format(
501 self.cloud_name, self.controller_name, self.model_name
502 )
503 )
504 # XXX check alternate names?434 # XXX check alternate names?
505 if required_sub in present_subs:435 if required_sub in present_subs:
506 self.logger.debug(436 self._log_with_header("... found extraneous sub")
507 "[{}] [{}/{}] ... found extraneous sub".format(
508 self.cloud_name,
509 self.controller_name,
510 self.model_name,
511 )
512 )
513 for app in self.model.apps_on_machines[machine]:437 for app in self.model.apps_on_machines[machine]:
514 self.model.extraneous_subs[required_sub].add(app)438 self.model.extraneous_subs[required_sub].add(app)
515 continue439 continue
516 self.logger.debug(440 self._log_with_header("... and we are a host, will fallthrough")
517 "[{}] [{}/{}] ... and we are a host, will fallthrough".format(441
518 self.cloud_name,
519 self.controller_name,
520 self.model_name,
521 )
522 )
523 elif where == "all or nothing" and required_sub not in all_or_nothing:442 elif where == "all or nothing" and required_sub not in all_or_nothing:
524 self.logger.debug(443 self._log_with_header(
525 "[{}] [{}/{}] requirement is 'all or nothing' and was 'nothing'.".format(444 "requirement is 'all or nothing' and was 'nothing'."
526 self.cloud_name,
527 self.controller_name,
528 self.model_name,
529 )
530 )445 )
531 continue446 continue
532 # At this point we know we require the subordinate - we might just447 # At this point we know we require the subordinate - we might just
533 # need to change the name we expect to see it as448 # need to change the name we expect to see it as
534 elif where == "container aware":449 elif where == "container aware":
535 self.logger.debug(450 self._log_with_header("requirement is 'container aware'.")
536 "[{}] [{}/{}] requirement is 'container aware'.".format(
537 self.cloud_name,
538 self.controller_name,
539 self.model_name,
540 )
541 )
542 if is_container(machine):451 if is_container(machine):
543 suffixes = self.lint_rules["subordinates"][required_sub][452 suffixes = self.lint_rules["subordinates"][required_sub][
544 "container-suffixes"453 "container-suffixes"
@@ -547,38 +456,17 @@ class Linter:
547 suffixes = self.lint_rules["subordinates"][required_sub][456 suffixes = self.lint_rules["subordinates"][required_sub][
548 "host-suffixes"457 "host-suffixes"
549 ]458 ]
550 self.logger.debug(459 self._log_with_header("-> suffixes == {}".format(suffixes))
551 "[{}] [{}/{}] -> suffixes == {}".format(
552 self.cloud_name,
553 self.controller_name,
554 self.model_name,
555 suffixes,
556 )
557 )
558 exceptions = []460 exceptions = []
559 if "exceptions" in self.lint_rules["subordinates"][required_sub]:461 if "exceptions" in self.lint_rules["subordinates"][required_sub]:
560 exceptions = self.lint_rules["subordinates"][required_sub][462 exceptions = self.lint_rules["subordinates"][required_sub][
561 "exceptions"463 "exceptions"
562 ]464 ]
563 self.logger.debug(465 self._log_with_header("-> exceptions == {}".format(exceptions))
564 "[{}] [{}/{}] -> exceptions == {}".format(
565 self.cloud_name,
566 self.controller_name,
567 self.model_name,
568 exceptions,
569 )
570 )
571 found = False466 found = False
572 for suffix in suffixes:467 for suffix in suffixes:
573 looking_for = "{}-{}".format(required_sub, suffix)468 looking_for = "{}-{}".format(required_sub, suffix)
574 self.logger.debug(469 self._log_with_header("-> Looking for {}".format(looking_for))
575 "[{}] [{}/{}] -> Looking for {}".format(
576 self.cloud_name,
577 self.controller_name,
578 self.model_name,
579 looking_for,
580 )
581 )
582 if looking_for in present_subs:470 if looking_for in present_subs:
583 self.logger.debug("-> FOUND!!!")471 self.logger.debug("-> FOUND!!!")
584 self.cloud_name,472 self.cloud_name,
@@ -588,44 +476,24 @@ class Linter:
588 if not found:476 if not found:
589 for sub in present_subs:477 for sub in present_subs:
590 if self.model.app_to_charm[sub] == required_sub:478 if self.model.app_to_charm[sub] == required_sub:
591 self.logger.debug(479 self._log_with_header(
592 "[{}] [{}/{}] Winner winner, chicken dinner! 🍗 {}".format(480 "Winner winner, chicken dinner! 🍗 {}".format(sub)
593 self.cloud_name,
594 self.controller_name,
595 self.model_name,
596 sub,
597 )
598 )481 )
599 found = True482 found = True
600 if not found:483 if not found:
601 for exception in exceptions:484 for exception in exceptions:
602 if exception in apps:485 if exception in apps:
603 self.logger.debug(486 self._log_with_header(
604 "[{}] [{}/{}]-> continuing as found exception: {}".format(487 "continuing as found exception: {}".format(
605 self.cloud_name,488 exception
606 self.controller_name,
607 self.model_name,
608 exception,
609 )489 )
610 )490 )
611 found = True491 found = True
612 if not found:492 if not found:
613 self.logger.debug(493 self._log_with_header("-> NOT FOUND")
614 "[{}] [{}/{}] -> NOT FOUND".format(
615 self.cloud_name,
616 self.controller_name,
617 self.model_name,
618 )
619 )
620 for app in self.model.apps_on_machines[machine]:494 for app in self.model.apps_on_machines[machine]:
621 self.model.missing_subs[required_sub].add(app)495 self.model.missing_subs[required_sub].add(app)
622 self.logger.debug(496 self._log_with_header("-> continue-ing back out...")
623 "[{}] [{}/{}] -> continue-ing back out...".format(
624 self.cloud_name,
625 self.controller_name,
626 self.model_name,
627 )
628 )
629 continue497 continue
630 elif where not in ["all", "all or nothing"]:498 elif where not in ["all", "all or nothing"]:
631 self.logger.fubar(499 self.logger.fubar(
@@ -637,30 +505,15 @@ class Linter:
637 required_sub,505 required_sub,
638 )506 )
639 )507 )
640 self.logger.debug(508 self._log_with_header("requirement is 'all' OR we fell through.")
641 "[{}] [{}/{}] requirement is 'all' OR we fell through.".format(
642 self.cloud_name,
643 self.controller_name,
644 self.model_name,
645 )
646 )
647 if required_sub not in present_subs:509 if required_sub not in present_subs:
648 for sub in present_subs:510 for sub in present_subs:
649 if self.model.app_to_charm[sub] == required_sub:511 if self.model.app_to_charm[sub] == required_sub:
650 self.logger.debug(512 self._log_with_header(
651 "Winner winner, chicken dinner! 🍗 {}".format(sub)513 "Winner winner, chicken dinner! 🍗 {}".format(sub)
652 )514 )
653 self.cloud_name,
654 self.controller_name,
655 self.model_name,
656 continue515 continue
657 self.logger.debug(516 self._log_with_header("not found.")
658 "[{}] [{}/{}] not found.".format(
659 self.cloud_name,
660 self.controller_name,
661 self.model_name,
662 )
663 )
664 for app in self.model.apps_on_machines[machine]:517 for app in self.model.apps_on_machines[machine]:
665 self.model.missing_subs[required_sub].add(app)518 self.model.missing_subs[required_sub].add(app)
666519
@@ -904,10 +757,9 @@ class Linter:
904 """Map machines in the model to their availability zone."""757 """Map machines in the model to their availability zone."""
905 for machine in machines:758 for machine in machines:
906 if "hardware" not in machines[machine]:759 if "hardware" not in machines[machine]:
907 self.logger.warn(760 self._log_with_header(
908 "[{}] [{}/{}] Machine {} has no hardware info; skipping.".format(761 "Machine {} has no hardware info; skipping.".format(machine),
909 self.cloud_name, self.controller_name, self.model_name, machine762 level=logging.WARN,
910 )
911 )763 )
912 continue764 continue
913765
@@ -920,10 +772,11 @@ class Linter:
920 self.model.machines_to_az[machine] = az772 self.model.machines_to_az[machine] = az
921 break773 break
922 if not found_az:774 if not found_az:
923 self.logger.warn(775 self._log_with_header(
924 "[{}] [{}/{}] Machine {} has no availability-zone info in hardware field; skipping.".format(776 "Machine {} has no availability-zone info in hardware field; skipping.".format(
925 self.cloud_name, self.controller_name, self.model_name, machine777 machine
926 )778 ),
779 level=logging.WARN,
927 )780 )
928781
929 def check_status(self, what, status, expected):782 def check_status(self, what, status, expected):
@@ -987,19 +840,16 @@ class Linter:
987 expected=juju_expected,840 expected=juju_expected,
988 )841 )
989 else:842 else:
990 self.logger.warn(843 self._log_with_header(
991 "[{}] [{}/{}] Could not determine Juju status for {}.".format(844 "Could not determine Juju status for {}.".format(name),
992 self.cloud_name, self.controller_name, self.model_name, name845 level=logging.WARN,
993 )
994 )846 )
995 else:847 else:
996 self.logger.warn(848 self._log_with_header(
997 "[{}] [{}/{}] Could not determine appropriate status key for {}.".format(849 "Could not determine appropriate status key for {}.".format(
998 self.cloud_name,
999 self.controller_name,
1000 self.model_name,
1001 name,850 name,
1002 )851 ),
852 level=logging.WARN,
1003 )853 )
1004854
1005 def check_statuses(self, juju_status, applications):855 def check_statuses(self, juju_status, applications):
@@ -1073,14 +923,12 @@ class Linter:
1073 machine = applications[app_name]["units"][unit]["machine"]923 machine = applications[app_name]["units"][unit]["machine"]
1074 machine = machine.split("/")[0]924 machine = machine.split("/")[0]
1075 if machine not in self.model.machines_to_az:925 if machine not in self.model.machines_to_az:
1076 self.logger.error(926 self._log_with_header(
1077 "[{}] [{}/{}] {}: Can't find machine {} in machine to AZ mapping data".format(927 "{}: Can't find machine {} in machine to AZ mapping data".format(
1078 self.cloud_name,
1079 self.controller_name,
1080 self.model_name,
1081 app_name,928 app_name,
1082 machine,929 machine,
1083 )930 ),
931 level=logging.ERROR,
1084 )932 )
1085 continue933 continue
1086 az_counter[self.model.machines_to_az[machine]] += 1934 az_counter[self.model.machines_to_az[machine]] += 1
@@ -1133,25 +981,14 @@ class Linter:
1133 self.check_azs(parsed_yaml[applications])981 self.check_azs(parsed_yaml[applications])
1134 self.check_statuses(parsed_yaml, applications)982 self.check_statuses(parsed_yaml, applications)
1135 else:983 else:
1136 self.logger.warn(984 self._log_with_header(
1137 (985 "Relations data found; assuming a bundle and skipping AZ and status checks."
1138 "[{}] [{}/{}] Relations data found; assuming a bundle and "
1139 "skipping AZ and status checks."
1140 ).format(
1141 self.cloud_name,
1142 self.model_name,
1143 self.controller_name,
1144 )
1145 )986 )
1146987
1147 self.results()988 self.results()
1148 else:989 else:
1149 self.logger.warn(990 self._log_with_header(
1150 "[{}] [{}/{}] Model contains no applications, skipping.".format(991 "Model contains no applications, skipping.", level=logging.WARN
1151 self.cloud_name,
1152 self.controller_name,
1153 self.model_name,
1154 )
1155 )992 )
1156993
1157 def collect(self, error):994 def collect(self, error):
@@ -1160,11 +997,7 @@ class Linter:
1160997
1161 def handle_error(self, error):998 def handle_error(self, error):
1162 """Collect an error and add it to the collector."""999 """Collect an error and add it to the collector."""
1163 self.logger.error(1000 self._log_with_header(error["message"], level=logging.ERROR)
1164 "[{}] [{}/{}] {}.".format(
1165 self.cloud_name, self.controller_name, self.model_name, error["message"]
1166 )
1167 )
1168 if self.collect_errors:1001 if self.collect_errors:
1169 self.collect(error)1002 self.collect(error)
11701003
@@ -1184,7 +1017,9 @@ class Linter:
1184 try:1017 try:
1185 _, rel_path = line.split()1018 _, rel_path = line.split()
1186 except ValueError:1019 except ValueError:
1187 self.logger.warn("invalid include in rules, ignored: '{}'".format(line))1020 self.logger.warn(
1021 "invalid include in rules, ignored: '{}'".format(line)
1022 )
1188 continue1023 continue
11891024
1190 include_path = os.path.join(os.path.dirname(self.filename), rel_path)1025 include_path = os.path.join(os.path.dirname(self.filename), rel_path)
@@ -1196,3 +1031,12 @@ class Linter:
1196 collector.append(line)1031 collector.append(line)
11971032
1198 return yaml.safe_load("\n".join(collector))1033 return yaml.safe_load("\n".join(collector))
1034
1035 def _log_with_header(self, msg, level=logging.DEBUG):
1036 """Log a message with the cloud/controller/model header."""
1037 self.logger.log(
1038 "[{}] [{}/{}] {}".format(
1039 self.cloud_name, self.controller_name, self.model_name, msg
1040 ),
1041 level=level,
1042 )
diff --git a/jujulint/logging.py b/jujulint/logging.py
index 47eaa88..e3c42f8 100644
--- a/jujulint/logging.py
+++ b/jujulint/logging.py
@@ -105,3 +105,7 @@ class Logger:
105 def error(self, message):105 def error(self, message):
106 """Log a message with warn loglevel."""106 """Log a message with warn loglevel."""
107 self.logger.error(message)107 self.logger.error(message)
108
109 def log(self, message, level=logging.DEBUG):
110 """Log a message with arbitrary loglevel."""
111 self.logger.log(level, message)

Subscribers

People subscribed via source and target branches