Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/logging/config.py: 41%

584 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# Copyright 2001-2019 by Vinay Sajip. All Rights Reserved. 

2# 

3# Permission to use, copy, modify, and distribute this software and its 

4# documentation for any purpose and without fee is hereby granted, 

5# provided that the above copyright notice appear in all copies and that 

6# both that copyright notice and this permission notice appear in 

7# supporting documentation, and that the name of Vinay Sajip 

8# not be used in advertising or publicity pertaining to distribution 

9# of the software without specific, written prior permission. 

10# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 

11# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 

12# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 

13# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 

14# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 

15# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 

16 

17""" 

18Configuration functions for the logging package for Python. The core package 

19is based on PEP 282 and comments thereto in comp.lang.python, and influenced 

20by Apache's log4j system. 

21 

22Copyright (C) 2001-2019 Vinay Sajip. All Rights Reserved. 

23 

24To use, simply 'import logging' and log away! 

25""" 

26 

27import errno 

28import io 

29import logging 

30import logging.handlers 

31import re 

32import struct 

33import sys 

34import threading 

35import traceback 

36 

37from socketserver import ThreadingTCPServer, StreamRequestHandler 

38 

39 

40DEFAULT_LOGGING_CONFIG_PORT = 9030 

41 

42RESET_ERROR = errno.ECONNRESET 

43 

44# 

45# The following code implements a socket listener for on-the-fly 

46# reconfiguration of logging. 

47# 

48# _listener holds the server object doing the listening 

49_listener = None 

50 

51def fileConfig(fname, defaults=None, disable_existing_loggers=True): 

52 """ 

53 Read the logging configuration from a ConfigParser-format file. 

54 

55 This can be called several times from an application, allowing an end user 

56 the ability to select from various pre-canned configurations (if the 

57 developer provides a mechanism to present the choices and load the chosen 

58 configuration). 

59 """ 

60 import configparser 

61 

62 if isinstance(fname, configparser.RawConfigParser): 

63 cp = fname 

64 else: 

65 cp = configparser.ConfigParser(defaults) 

66 if hasattr(fname, 'readline'): 

67 cp.read_file(fname) 

68 else: 

69 cp.read(fname) 

70 

71 formatters = _create_formatters(cp) 

72 

73 # critical section 

74 logging._acquireLock() 

75 try: 

76 _clearExistingHandlers() 

77 

78 # Handlers add themselves to logging._handlers 

79 handlers = _install_handlers(cp, formatters) 

80 _install_loggers(cp, handlers, disable_existing_loggers) 

81 finally: 

82 logging._releaseLock() 

83 

84 

85def _resolve(name): 

86 """Resolve a dotted name to a global object.""" 

87 name = name.split('.') 

88 used = name.pop(0) 

89 found = __import__(used) 

90 for n in name: 

91 used = used + '.' + n 

92 try: 

93 found = getattr(found, n) 

94 except AttributeError: 

95 __import__(used) 

96 found = getattr(found, n) 

97 return found 

98 

99def _strip_spaces(alist): 

100 return map(str.strip, alist) 

101 

102def _create_formatters(cp): 

103 """Create and return formatters""" 

104 flist = cp["formatters"]["keys"] 

105 if not len(flist): 

106 return {} 

107 flist = flist.split(",") 

108 flist = _strip_spaces(flist) 

109 formatters = {} 

110 for form in flist: 

111 sectname = "formatter_%s" % form 

112 fs = cp.get(sectname, "format", raw=True, fallback=None) 

113 dfs = cp.get(sectname, "datefmt", raw=True, fallback=None) 

114 stl = cp.get(sectname, "style", raw=True, fallback='%') 

115 c = logging.Formatter 

116 class_name = cp[sectname].get("class") 

117 if class_name: 

118 c = _resolve(class_name) 

119 f = c(fs, dfs, stl) 

120 formatters[form] = f 

121 return formatters 

122 

123 

124def _install_handlers(cp, formatters): 

125 """Install and return handlers""" 

126 hlist = cp["handlers"]["keys"] 

127 if not len(hlist): 

128 return {} 

129 hlist = hlist.split(",") 

130 hlist = _strip_spaces(hlist) 

131 handlers = {} 

132 fixups = [] #for inter-handler references 

133 for hand in hlist: 

134 section = cp["handler_%s" % hand] 

135 klass = section["class"] 

136 fmt = section.get("formatter", "") 

137 try: 

138 klass = eval(klass, vars(logging)) 

139 except (AttributeError, NameError): 

140 klass = _resolve(klass) 

141 args = section.get("args", '()') 

142 args = eval(args, vars(logging)) 

143 kwargs = section.get("kwargs", '{}') 

144 kwargs = eval(kwargs, vars(logging)) 

145 h = klass(*args, **kwargs) 

146 if "level" in section: 

147 level = section["level"] 

148 h.setLevel(level) 

149 if len(fmt): 

150 h.setFormatter(formatters[fmt]) 

151 if issubclass(klass, logging.handlers.MemoryHandler): 

152 target = section.get("target", "") 

153 if len(target): #the target handler may not be loaded yet, so keep for later... 

154 fixups.append((h, target)) 

155 handlers[hand] = h 

156 #now all handlers are loaded, fixup inter-handler references... 

157 for h, t in fixups: 

158 h.setTarget(handlers[t]) 

159 return handlers 

160 

161def _handle_existing_loggers(existing, child_loggers, disable_existing): 

162 """ 

163 When (re)configuring logging, handle loggers which were in the previous 

164 configuration but are not in the new configuration. There's no point 

165 deleting them as other threads may continue to hold references to them; 

166 and by disabling them, you stop them doing any logging. 

167 

168 However, don't disable children of named loggers, as that's probably not 

169 what was intended by the user. Also, allow existing loggers to NOT be 

170 disabled if disable_existing is false. 

171 """ 

172 root = logging.root 

173 for log in existing: 

174 logger = root.manager.loggerDict[log] 

175 if log in child_loggers: 

176 if not isinstance(logger, logging.PlaceHolder): 

177 logger.setLevel(logging.NOTSET) 

178 logger.handlers = [] 

179 logger.propagate = True 

180 else: 

181 logger.disabled = disable_existing 

182 

183def _install_loggers(cp, handlers, disable_existing): 

184 """Create and install loggers""" 

185 

186 # configure the root first 

187 llist = cp["loggers"]["keys"] 

188 llist = llist.split(",") 

189 llist = list(_strip_spaces(llist)) 

190 llist.remove("root") 

191 section = cp["logger_root"] 

192 root = logging.root 

193 log = root 

194 if "level" in section: 

195 level = section["level"] 

196 log.setLevel(level) 

197 for h in root.handlers[:]: 

198 root.removeHandler(h) 

199 hlist = section["handlers"] 

200 if len(hlist): 

201 hlist = hlist.split(",") 

202 hlist = _strip_spaces(hlist) 

203 for hand in hlist: 

204 log.addHandler(handlers[hand]) 

205 

206 #and now the others... 

207 #we don't want to lose the existing loggers, 

208 #since other threads may have pointers to them. 

209 #existing is set to contain all existing loggers, 

210 #and as we go through the new configuration we 

211 #remove any which are configured. At the end, 

212 #what's left in existing is the set of loggers 

213 #which were in the previous configuration but 

214 #which are not in the new configuration. 

215 existing = list(root.manager.loggerDict.keys()) 

216 #The list needs to be sorted so that we can 

217 #avoid disabling child loggers of explicitly 

218 #named loggers. With a sorted list it is easier 

219 #to find the child loggers. 

220 existing.sort() 

221 #We'll keep the list of existing loggers 

222 #which are children of named loggers here... 

223 child_loggers = [] 

224 #now set up the new ones... 

225 for log in llist: 

226 section = cp["logger_%s" % log] 

227 qn = section["qualname"] 

228 propagate = section.getint("propagate", fallback=1) 

229 logger = logging.getLogger(qn) 

230 if qn in existing: 

231 i = existing.index(qn) + 1 # start with the entry after qn 

232 prefixed = qn + "." 

233 pflen = len(prefixed) 

234 num_existing = len(existing) 

235 while i < num_existing: 

236 if existing[i][:pflen] == prefixed: 

237 child_loggers.append(existing[i]) 

238 i += 1 

239 existing.remove(qn) 

240 if "level" in section: 

241 level = section["level"] 

242 logger.setLevel(level) 

243 for h in logger.handlers[:]: 

244 logger.removeHandler(h) 

245 logger.propagate = propagate 

246 logger.disabled = 0 

247 hlist = section["handlers"] 

248 if len(hlist): 

249 hlist = hlist.split(",") 

250 hlist = _strip_spaces(hlist) 

251 for hand in hlist: 

252 logger.addHandler(handlers[hand]) 

253 

254 #Disable any old loggers. There's no point deleting 

255 #them as other threads may continue to hold references 

256 #and by disabling them, you stop them doing any logging. 

257 #However, don't disable children of named loggers, as that's 

258 #probably not what was intended by the user. 

259 #for log in existing: 

260 # logger = root.manager.loggerDict[log] 

261 # if log in child_loggers: 

262 # logger.level = logging.NOTSET 

263 # logger.handlers = [] 

264 # logger.propagate = 1 

265 # elif disable_existing_loggers: 

266 # logger.disabled = 1 

267 _handle_existing_loggers(existing, child_loggers, disable_existing) 

268 

269 

270def _clearExistingHandlers(): 

271 """Clear and close existing handlers""" 

272 logging._handlers.clear() 

273 logging.shutdown(logging._handlerList[:]) 

274 del logging._handlerList[:] 

275 

276 

277IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) 

278 

279 

280def valid_ident(s): 

281 m = IDENTIFIER.match(s) 

282 if not m: 

283 raise ValueError('Not a valid Python identifier: %r' % s) 

284 return True 

285 

286 

287class ConvertingMixin(object): 

288 """For ConvertingXXX's, this mixin class provides common functions""" 

289 

290 def convert_with_key(self, key, value, replace=True): 

291 result = self.configurator.convert(value) 

292 #If the converted value is different, save for next time 

293 if value is not result: 

294 if replace: 

295 self[key] = result 

296 if type(result) in (ConvertingDict, ConvertingList, 

297 ConvertingTuple): 

298 result.parent = self 

299 result.key = key 

300 return result 

301 

302 def convert(self, value): 

303 result = self.configurator.convert(value) 

304 if value is not result: 

305 if type(result) in (ConvertingDict, ConvertingList, 

306 ConvertingTuple): 

307 result.parent = self 

308 return result 

309 

310 

311# The ConvertingXXX classes are wrappers around standard Python containers, 

312# and they serve to convert any suitable values in the container. The 

313# conversion converts base dicts, lists and tuples to their wrapped 

314# equivalents, whereas strings which match a conversion format are converted 

315# appropriately. 

316# 

317# Each wrapper should have a configurator attribute holding the actual 

318# configurator to use for conversion. 

319 

320class ConvertingDict(dict, ConvertingMixin): 

321 """A converting dictionary wrapper.""" 

322 

323 def __getitem__(self, key): 

324 value = dict.__getitem__(self, key) 

325 return self.convert_with_key(key, value) 

326 

327 def get(self, key, default=None): 

328 value = dict.get(self, key, default) 

329 return self.convert_with_key(key, value) 

330 

331 def pop(self, key, default=None): 

332 value = dict.pop(self, key, default) 

333 return self.convert_with_key(key, value, replace=False) 

334 

335class ConvertingList(list, ConvertingMixin): 

336 """A converting list wrapper.""" 

337 def __getitem__(self, key): 

338 value = list.__getitem__(self, key) 

339 return self.convert_with_key(key, value) 

340 

341 def pop(self, idx=-1): 

342 value = list.pop(self, idx) 

343 return self.convert(value) 

344 

345class ConvertingTuple(tuple, ConvertingMixin): 

346 """A converting tuple wrapper.""" 

347 def __getitem__(self, key): 

348 value = tuple.__getitem__(self, key) 

349 # Can't replace a tuple entry. 

350 return self.convert_with_key(key, value, replace=False) 

351 

352class BaseConfigurator(object): 

353 """ 

354 The configurator base class which defines some useful defaults. 

355 """ 

356 

357 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$') 

358 

359 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') 

360 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') 

361 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') 

362 DIGIT_PATTERN = re.compile(r'^\d+$') 

363 

364 value_converters = { 

365 'ext' : 'ext_convert', 

366 'cfg' : 'cfg_convert', 

367 } 

368 

369 # We might want to use a different one, e.g. importlib 

370 importer = staticmethod(__import__) 

371 

372 def __init__(self, config): 

373 self.config = ConvertingDict(config) 

374 self.config.configurator = self 

375 

376 def resolve(self, s): 

377 """ 

378 Resolve strings to objects using standard import and attribute 

379 syntax. 

380 """ 

381 name = s.split('.') 

382 used = name.pop(0) 

383 try: 

384 found = self.importer(used) 

385 for frag in name: 

386 used += '.' + frag 

387 try: 

388 found = getattr(found, frag) 

389 except AttributeError: 

390 self.importer(used) 

391 found = getattr(found, frag) 

392 return found 

393 except ImportError: 

394 e, tb = sys.exc_info()[1:] 

395 v = ValueError('Cannot resolve %r: %s' % (s, e)) 

396 v.__cause__, v.__traceback__ = e, tb 

397 raise v 

398 

399 def ext_convert(self, value): 

400 """Default converter for the ext:// protocol.""" 

401 return self.resolve(value) 

402 

403 def cfg_convert(self, value): 

404 """Default converter for the cfg:// protocol.""" 

405 rest = value 

406 m = self.WORD_PATTERN.match(rest) 

407 if m is None: 

408 raise ValueError("Unable to convert %r" % value) 

409 else: 

410 rest = rest[m.end():] 

411 d = self.config[m.groups()[0]] 

412 #print d, rest 

413 while rest: 

414 m = self.DOT_PATTERN.match(rest) 

415 if m: 

416 d = d[m.groups()[0]] 

417 else: 

418 m = self.INDEX_PATTERN.match(rest) 

419 if m: 

420 idx = m.groups()[0] 

421 if not self.DIGIT_PATTERN.match(idx): 

422 d = d[idx] 

423 else: 

424 try: 

425 n = int(idx) # try as number first (most likely) 

426 d = d[n] 

427 except TypeError: 

428 d = d[idx] 

429 if m: 

430 rest = rest[m.end():] 

431 else: 

432 raise ValueError('Unable to convert ' 

433 '%r at %r' % (value, rest)) 

434 #rest should be empty 

435 return d 

436 

437 def convert(self, value): 

438 """ 

439 Convert values to an appropriate type. dicts, lists and tuples are 

440 replaced by their converting alternatives. Strings are checked to 

441 see if they have a conversion format and are converted if they do. 

442 """ 

443 if not isinstance(value, ConvertingDict) and isinstance(value, dict): 

444 value = ConvertingDict(value) 

445 value.configurator = self 

446 elif not isinstance(value, ConvertingList) and isinstance(value, list): 

447 value = ConvertingList(value) 

448 value.configurator = self 

449 elif not isinstance(value, ConvertingTuple) and\ 

450 isinstance(value, tuple) and not hasattr(value, '_fields'): 

451 value = ConvertingTuple(value) 

452 value.configurator = self 

453 elif isinstance(value, str): # str for py3k 

454 m = self.CONVERT_PATTERN.match(value) 

455 if m: 

456 d = m.groupdict() 

457 prefix = d['prefix'] 

458 converter = self.value_converters.get(prefix, None) 

459 if converter: 

460 suffix = d['suffix'] 

461 converter = getattr(self, converter) 

462 value = converter(suffix) 

463 return value 

464 

465 def configure_custom(self, config): 

466 """Configure an object with a user-supplied factory.""" 

467 c = config.pop('()') 

468 if not callable(c): 

469 c = self.resolve(c) 

470 props = config.pop('.', None) 

471 # Check for valid identifiers 

472 kwargs = {k: config[k] for k in config if valid_ident(k)} 

473 result = c(**kwargs) 

474 if props: 

475 for name, value in props.items(): 

476 setattr(result, name, value) 

477 return result 

478 

479 def as_tuple(self, value): 

480 """Utility function which converts lists to tuples.""" 

481 if isinstance(value, list): 

482 value = tuple(value) 

483 return value 

484 

485class DictConfigurator(BaseConfigurator): 

486 """ 

487 Configure logging using a dictionary-like object to describe the 

488 configuration. 

489 """ 

490 

491 def configure(self): 

492 """Do the configuration.""" 

493 

494 config = self.config 

495 if 'version' not in config: 

496 raise ValueError("dictionary doesn't specify a version") 

497 if config['version'] != 1: 

498 raise ValueError("Unsupported version: %s" % config['version']) 

499 incremental = config.pop('incremental', False) 

500 EMPTY_DICT = {} 

501 logging._acquireLock() 

502 try: 

503 if incremental: 

504 handlers = config.get('handlers', EMPTY_DICT) 

505 for name in handlers: 

506 if name not in logging._handlers: 

507 raise ValueError('No handler found with ' 

508 'name %r' % name) 

509 else: 

510 try: 

511 handler = logging._handlers[name] 

512 handler_config = handlers[name] 

513 level = handler_config.get('level', None) 

514 if level: 

515 handler.setLevel(logging._checkLevel(level)) 

516 except Exception as e: 

517 raise ValueError('Unable to configure handler ' 

518 '%r' % name) from e 

519 loggers = config.get('loggers', EMPTY_DICT) 

520 for name in loggers: 

521 try: 

522 self.configure_logger(name, loggers[name], True) 

523 except Exception as e: 

524 raise ValueError('Unable to configure logger ' 

525 '%r' % name) from e 

526 root = config.get('root', None) 

527 if root: 

528 try: 

529 self.configure_root(root, True) 

530 except Exception as e: 

531 raise ValueError('Unable to configure root ' 

532 'logger') from e 

533 else: 

534 disable_existing = config.pop('disable_existing_loggers', True) 

535 

536 _clearExistingHandlers() 

537 

538 # Do formatters first - they don't refer to anything else 

539 formatters = config.get('formatters', EMPTY_DICT) 

540 for name in formatters: 

541 try: 

542 formatters[name] = self.configure_formatter( 

543 formatters[name]) 

544 except Exception as e: 

545 raise ValueError('Unable to configure ' 

546 'formatter %r' % name) from e 

547 # Next, do filters - they don't refer to anything else, either 

548 filters = config.get('filters', EMPTY_DICT) 

549 for name in filters: 

550 try: 

551 filters[name] = self.configure_filter(filters[name]) 

552 except Exception as e: 

553 raise ValueError('Unable to configure ' 

554 'filter %r' % name) from e 

555 

556 # Next, do handlers - they refer to formatters and filters 

557 # As handlers can refer to other handlers, sort the keys 

558 # to allow a deterministic order of configuration 

559 handlers = config.get('handlers', EMPTY_DICT) 

560 deferred = [] 

561 for name in sorted(handlers): 

562 try: 

563 handler = self.configure_handler(handlers[name]) 

564 handler.name = name 

565 handlers[name] = handler 

566 except Exception as e: 

567 if 'target not configured yet' in str(e.__cause__): 

568 deferred.append(name) 

569 else: 

570 raise ValueError('Unable to configure handler ' 

571 '%r' % name) from e 

572 

573 # Now do any that were deferred 

574 for name in deferred: 

575 try: 

576 handler = self.configure_handler(handlers[name]) 

577 handler.name = name 

578 handlers[name] = handler 

579 except Exception as e: 

580 raise ValueError('Unable to configure handler ' 

581 '%r' % name) from e 

582 

583 # Next, do loggers - they refer to handlers and filters 

584 

585 #we don't want to lose the existing loggers, 

586 #since other threads may have pointers to them. 

587 #existing is set to contain all existing loggers, 

588 #and as we go through the new configuration we 

589 #remove any which are configured. At the end, 

590 #what's left in existing is the set of loggers 

591 #which were in the previous configuration but 

592 #which are not in the new configuration. 

593 root = logging.root 

594 existing = list(root.manager.loggerDict.keys()) 

595 #The list needs to be sorted so that we can 

596 #avoid disabling child loggers of explicitly 

597 #named loggers. With a sorted list it is easier 

598 #to find the child loggers. 

599 existing.sort() 

600 #We'll keep the list of existing loggers 

601 #which are children of named loggers here... 

602 child_loggers = [] 

603 #now set up the new ones... 

604 loggers = config.get('loggers', EMPTY_DICT) 

605 for name in loggers: 

606 if name in existing: 

607 i = existing.index(name) + 1 # look after name 

608 prefixed = name + "." 

609 pflen = len(prefixed) 

610 num_existing = len(existing) 

611 while i < num_existing: 

612 if existing[i][:pflen] == prefixed: 

613 child_loggers.append(existing[i]) 

614 i += 1 

615 existing.remove(name) 

616 try: 

617 self.configure_logger(name, loggers[name]) 

618 except Exception as e: 

619 raise ValueError('Unable to configure logger ' 

620 '%r' % name) from e 

621 

622 #Disable any old loggers. There's no point deleting 

623 #them as other threads may continue to hold references 

624 #and by disabling them, you stop them doing any logging. 

625 #However, don't disable children of named loggers, as that's 

626 #probably not what was intended by the user. 

627 #for log in existing: 

628 # logger = root.manager.loggerDict[log] 

629 # if log in child_loggers: 

630 # logger.level = logging.NOTSET 

631 # logger.handlers = [] 

632 # logger.propagate = True 

633 # elif disable_existing: 

634 # logger.disabled = True 

635 _handle_existing_loggers(existing, child_loggers, 

636 disable_existing) 

637 

638 # And finally, do the root logger 

639 root = config.get('root', None) 

640 if root: 

641 try: 

642 self.configure_root(root) 

643 except Exception as e: 

644 raise ValueError('Unable to configure root ' 

645 'logger') from e 

646 finally: 

647 logging._releaseLock() 

648 

649 def configure_formatter(self, config): 

650 """Configure a formatter from a dictionary.""" 

651 if '()' in config: 

652 factory = config['()'] # for use in exception handler 

653 try: 

654 result = self.configure_custom(config) 

655 except TypeError as te: 

656 if "'format'" not in str(te): 

657 raise 

658 #Name of parameter changed from fmt to format. 

659 #Retry with old name. 

660 #This is so that code can be used with older Python versions 

661 #(e.g. by Django) 

662 config['fmt'] = config.pop('format') 

663 config['()'] = factory 

664 result = self.configure_custom(config) 

665 else: 

666 fmt = config.get('format', None) 

667 dfmt = config.get('datefmt', None) 

668 style = config.get('style', '%') 

669 cname = config.get('class', None) 

670 

671 if not cname: 

672 c = logging.Formatter 

673 else: 

674 c = _resolve(cname) 

675 

676 # A TypeError would be raised if "validate" key is passed in with a formatter callable 

677 # that does not accept "validate" as a parameter 

678 if 'validate' in config: # if user hasn't mentioned it, the default will be fine 

679 result = c(fmt, dfmt, style, config['validate']) 

680 else: 

681 result = c(fmt, dfmt, style) 

682 

683 return result 

684 

685 def configure_filter(self, config): 

686 """Configure a filter from a dictionary.""" 

687 if '()' in config: 

688 result = self.configure_custom(config) 

689 else: 

690 name = config.get('name', '') 

691 result = logging.Filter(name) 

692 return result 

693 

694 def add_filters(self, filterer, filters): 

695 """Add filters to a filterer from a list of names.""" 

696 for f in filters: 

697 try: 

698 filterer.addFilter(self.config['filters'][f]) 

699 except Exception as e: 

700 raise ValueError('Unable to add filter %r' % f) from e 

701 

702 def configure_handler(self, config): 

703 """Configure a handler from a dictionary.""" 

704 config_copy = dict(config) # for restoring in case of error 

705 formatter = config.pop('formatter', None) 

706 if formatter: 

707 try: 

708 formatter = self.config['formatters'][formatter] 

709 except Exception as e: 

710 raise ValueError('Unable to set formatter ' 

711 '%r' % formatter) from e 

712 level = config.pop('level', None) 

713 filters = config.pop('filters', None) 

714 if '()' in config: 

715 c = config.pop('()') 

716 if not callable(c): 

717 c = self.resolve(c) 

718 factory = c 

719 else: 

720 cname = config.pop('class') 

721 klass = self.resolve(cname) 

722 #Special case for handler which refers to another handler 

723 if issubclass(klass, logging.handlers.MemoryHandler) and\ 

724 'target' in config: 

725 try: 

726 th = self.config['handlers'][config['target']] 

727 if not isinstance(th, logging.Handler): 

728 config.update(config_copy) # restore for deferred cfg 

729 raise TypeError('target not configured yet') 

730 config['target'] = th 

731 except Exception as e: 

732 raise ValueError('Unable to set target handler ' 

733 '%r' % config['target']) from e 

734 elif issubclass(klass, logging.handlers.SMTPHandler) and\ 

735 'mailhost' in config: 

736 config['mailhost'] = self.as_tuple(config['mailhost']) 

737 elif issubclass(klass, logging.handlers.SysLogHandler) and\ 

738 'address' in config: 

739 config['address'] = self.as_tuple(config['address']) 

740 factory = klass 

741 props = config.pop('.', None) 

742 kwargs = {k: config[k] for k in config if valid_ident(k)} 

743 try: 

744 result = factory(**kwargs) 

745 except TypeError as te: 

746 if "'stream'" not in str(te): 

747 raise 

748 #The argument name changed from strm to stream 

749 #Retry with old name. 

750 #This is so that code can be used with older Python versions 

751 #(e.g. by Django) 

752 kwargs['strm'] = kwargs.pop('stream') 

753 result = factory(**kwargs) 

754 if formatter: 

755 result.setFormatter(formatter) 

756 if level is not None: 

757 result.setLevel(logging._checkLevel(level)) 

758 if filters: 

759 self.add_filters(result, filters) 

760 if props: 

761 for name, value in props.items(): 

762 setattr(result, name, value) 

763 return result 

764 

765 def add_handlers(self, logger, handlers): 

766 """Add handlers to a logger from a list of names.""" 

767 for h in handlers: 

768 try: 

769 logger.addHandler(self.config['handlers'][h]) 

770 except Exception as e: 

771 raise ValueError('Unable to add handler %r' % h) from e 

772 

773 def common_logger_config(self, logger, config, incremental=False): 

774 """ 

775 Perform configuration which is common to root and non-root loggers. 

776 """ 

777 level = config.get('level', None) 

778 if level is not None: 

779 logger.setLevel(logging._checkLevel(level)) 

780 if not incremental: 

781 #Remove any existing handlers 

782 for h in logger.handlers[:]: 

783 logger.removeHandler(h) 

784 handlers = config.get('handlers', None) 

785 if handlers: 

786 self.add_handlers(logger, handlers) 

787 filters = config.get('filters', None) 

788 if filters: 

789 self.add_filters(logger, filters) 

790 

791 def configure_logger(self, name, config, incremental=False): 

792 """Configure a non-root logger from a dictionary.""" 

793 logger = logging.getLogger(name) 

794 self.common_logger_config(logger, config, incremental) 

795 propagate = config.get('propagate', None) 

796 if propagate is not None: 

797 logger.propagate = propagate 

798 

799 def configure_root(self, config, incremental=False): 

800 """Configure a root logger from a dictionary.""" 

801 root = logging.getLogger() 

802 self.common_logger_config(root, config, incremental) 

803 

804dictConfigClass = DictConfigurator 

805 

806def dictConfig(config): 

807 """Configure logging using a dictionary.""" 

808 dictConfigClass(config).configure() 

809 

810 

811def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None): 

812 """ 

813 Start up a socket server on the specified port, and listen for new 

814 configurations. 

815 

816 These will be sent as a file suitable for processing by fileConfig(). 

817 Returns a Thread object on which you can call start() to start the server, 

818 and which you can join() when appropriate. To stop the server, call 

819 stopListening(). 

820 

821 Use the ``verify`` argument to verify any bytes received across the wire 

822 from a client. If specified, it should be a callable which receives a 

823 single argument - the bytes of configuration data received across the 

824 network - and it should return either ``None``, to indicate that the 

825 passed in bytes could not be verified and should be discarded, or a 

826 byte string which is then passed to the configuration machinery as 

827 normal. Note that you can return transformed bytes, e.g. by decrypting 

828 the bytes passed in. 

829 """ 

830 

831 class ConfigStreamHandler(StreamRequestHandler): 

832 """ 

833 Handler for a logging configuration request. 

834 

835 It expects a completely new logging configuration and uses fileConfig 

836 to install it. 

837 """ 

838 def handle(self): 

839 """ 

840 Handle a request. 

841 

842 Each request is expected to be a 4-byte length, packed using 

843 struct.pack(">L", n), followed by the config file. 

844 Uses fileConfig() to do the grunt work. 

845 """ 

846 try: 

847 conn = self.connection 

848 chunk = conn.recv(4) 

849 if len(chunk) == 4: 

850 slen = struct.unpack(">L", chunk)[0] 

851 chunk = self.connection.recv(slen) 

852 while len(chunk) < slen: 

853 chunk = chunk + conn.recv(slen - len(chunk)) 

854 if self.server.verify is not None: 

855 chunk = self.server.verify(chunk) 

856 if chunk is not None: # verified, can process 

857 chunk = chunk.decode("utf-8") 

858 try: 

859 import json 

860 d =json.loads(chunk) 

861 assert isinstance(d, dict) 

862 dictConfig(d) 

863 except Exception: 

864 #Apply new configuration. 

865 

866 file = io.StringIO(chunk) 

867 try: 

868 fileConfig(file) 

869 except Exception: 

870 traceback.print_exc() 

871 if self.server.ready: 

872 self.server.ready.set() 

873 except OSError as e: 

874 if e.errno != RESET_ERROR: 

875 raise 

876 

877 class ConfigSocketReceiver(ThreadingTCPServer): 

878 """ 

879 A simple TCP socket-based logging config receiver. 

880 """ 

881 

882 allow_reuse_address = 1 

883 

884 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, 

885 handler=None, ready=None, verify=None): 

886 ThreadingTCPServer.__init__(self, (host, port), handler) 

887 logging._acquireLock() 

888 self.abort = 0 

889 logging._releaseLock() 

890 self.timeout = 1 

891 self.ready = ready 

892 self.verify = verify 

893 

894 def serve_until_stopped(self): 

895 import select 

896 abort = 0 

897 while not abort: 

898 rd, wr, ex = select.select([self.socket.fileno()], 

899 [], [], 

900 self.timeout) 

901 if rd: 

902 self.handle_request() 

903 logging._acquireLock() 

904 abort = self.abort 

905 logging._releaseLock() 

906 self.server_close() 

907 

908 class Server(threading.Thread): 

909 

910 def __init__(self, rcvr, hdlr, port, verify): 

911 super(Server, self).__init__() 

912 self.rcvr = rcvr 

913 self.hdlr = hdlr 

914 self.port = port 

915 self.verify = verify 

916 self.ready = threading.Event() 

917 

918 def run(self): 

919 server = self.rcvr(port=self.port, handler=self.hdlr, 

920 ready=self.ready, 

921 verify=self.verify) 

922 if self.port == 0: 

923 self.port = server.server_address[1] 

924 self.ready.set() 

925 global _listener 

926 logging._acquireLock() 

927 _listener = server 

928 logging._releaseLock() 

929 server.serve_until_stopped() 

930 

931 return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify) 

932 

933def stopListening(): 

934 """ 

935 Stop the listening server which was created with a call to listen(). 

936 """ 

937 global _listener 

938 logging._acquireLock() 

939 try: 

940 if _listener: 

941 _listener.abort = 1 

942 _listener = None 

943 finally: 

944 logging._releaseLock()