Coverage for /pythoncovmergedfiles/medio/medio/usr/lib/python3.9/logging/config.py: 11%

585 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:05 +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 h.name = hand 

147 if "level" in section: 

148 level = section["level"] 

149 h.setLevel(level) 

150 if len(fmt): 

151 h.setFormatter(formatters[fmt]) 

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

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

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

155 fixups.append((h, target)) 

156 handlers[hand] = h 

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

158 for h, t in fixups: 

159 h.setTarget(handlers[t]) 

160 return handlers 

161 

162def _handle_existing_loggers(existing, child_loggers, disable_existing): 

163 """ 

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

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

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

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

168 

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

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

171 disabled if disable_existing is false. 

172 """ 

173 root = logging.root 

174 for log in existing: 

175 logger = root.manager.loggerDict[log] 

176 if log in child_loggers: 

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

178 logger.setLevel(logging.NOTSET) 

179 logger.handlers = [] 

180 logger.propagate = True 

181 else: 

182 logger.disabled = disable_existing 

183 

184def _install_loggers(cp, handlers, disable_existing): 

185 """Create and install loggers""" 

186 

187 # configure the root first 

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

189 llist = llist.split(",") 

190 llist = list(_strip_spaces(llist)) 

191 llist.remove("root") 

192 section = cp["logger_root"] 

193 root = logging.root 

194 log = root 

195 if "level" in section: 

196 level = section["level"] 

197 log.setLevel(level) 

198 for h in root.handlers[:]: 

199 root.removeHandler(h) 

200 hlist = section["handlers"] 

201 if len(hlist): 

202 hlist = hlist.split(",") 

203 hlist = _strip_spaces(hlist) 

204 for hand in hlist: 

205 log.addHandler(handlers[hand]) 

206 

207 #and now the others... 

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

209 #since other threads may have pointers to them. 

210 #existing is set to contain all existing loggers, 

211 #and as we go through the new configuration we 

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

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

214 #which were in the previous configuration but 

215 #which are not in the new configuration. 

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

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

218 #avoid disabling child loggers of explicitly 

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

220 #to find the child loggers. 

221 existing.sort() 

222 #We'll keep the list of existing loggers 

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

224 child_loggers = [] 

225 #now set up the new ones... 

226 for log in llist: 

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

228 qn = section["qualname"] 

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

230 logger = logging.getLogger(qn) 

231 if qn in existing: 

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

233 prefixed = qn + "." 

234 pflen = len(prefixed) 

235 num_existing = len(existing) 

236 while i < num_existing: 

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

238 child_loggers.append(existing[i]) 

239 i += 1 

240 existing.remove(qn) 

241 if "level" in section: 

242 level = section["level"] 

243 logger.setLevel(level) 

244 for h in logger.handlers[:]: 

245 logger.removeHandler(h) 

246 logger.propagate = propagate 

247 logger.disabled = 0 

248 hlist = section["handlers"] 

249 if len(hlist): 

250 hlist = hlist.split(",") 

251 hlist = _strip_spaces(hlist) 

252 for hand in hlist: 

253 logger.addHandler(handlers[hand]) 

254 

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

256 #them as other threads may continue to hold references 

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

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

259 #probably not what was intended by the user. 

260 #for log in existing: 

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

262 # if log in child_loggers: 

263 # logger.level = logging.NOTSET 

264 # logger.handlers = [] 

265 # logger.propagate = 1 

266 # elif disable_existing_loggers: 

267 # logger.disabled = 1 

268 _handle_existing_loggers(existing, child_loggers, disable_existing) 

269 

270 

271def _clearExistingHandlers(): 

272 """Clear and close existing handlers""" 

273 logging._handlers.clear() 

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

275 del logging._handlerList[:] 

276 

277 

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

279 

280 

281def valid_ident(s): 

282 m = IDENTIFIER.match(s) 

283 if not m: 

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

285 return True 

286 

287 

288class ConvertingMixin(object): 

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

290 

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

292 result = self.configurator.convert(value) 

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

294 if value is not result: 

295 if replace: 

296 self[key] = result 

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

298 ConvertingTuple): 

299 result.parent = self 

300 result.key = key 

301 return result 

302 

303 def convert(self, value): 

304 result = self.configurator.convert(value) 

305 if value is not result: 

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

307 ConvertingTuple): 

308 result.parent = self 

309 return result 

310 

311 

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

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

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

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

316# appropriately. 

317# 

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

319# configurator to use for conversion. 

320 

321class ConvertingDict(dict, ConvertingMixin): 

322 """A converting dictionary wrapper.""" 

323 

324 def __getitem__(self, key): 

325 value = dict.__getitem__(self, key) 

326 return self.convert_with_key(key, value) 

327 

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

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

330 return self.convert_with_key(key, value) 

331 

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

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

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

335 

336class ConvertingList(list, ConvertingMixin): 

337 """A converting list wrapper.""" 

338 def __getitem__(self, key): 

339 value = list.__getitem__(self, key) 

340 return self.convert_with_key(key, value) 

341 

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

343 value = list.pop(self, idx) 

344 return self.convert(value) 

345 

346class ConvertingTuple(tuple, ConvertingMixin): 

347 """A converting tuple wrapper.""" 

348 def __getitem__(self, key): 

349 value = tuple.__getitem__(self, key) 

350 # Can't replace a tuple entry. 

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

352 

353class BaseConfigurator(object): 

354 """ 

355 The configurator base class which defines some useful defaults. 

356 """ 

357 

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

359 

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

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

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

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

364 

365 value_converters = { 

366 'ext' : 'ext_convert', 

367 'cfg' : 'cfg_convert', 

368 } 

369 

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

371 importer = staticmethod(__import__) 

372 

373 def __init__(self, config): 

374 self.config = ConvertingDict(config) 

375 self.config.configurator = self 

376 

377 def resolve(self, s): 

378 """ 

379 Resolve strings to objects using standard import and attribute 

380 syntax. 

381 """ 

382 name = s.split('.') 

383 used = name.pop(0) 

384 try: 

385 found = self.importer(used) 

386 for frag in name: 

387 used += '.' + frag 

388 try: 

389 found = getattr(found, frag) 

390 except AttributeError: 

391 self.importer(used) 

392 found = getattr(found, frag) 

393 return found 

394 except ImportError: 

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

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

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

398 raise v 

399 

400 def ext_convert(self, value): 

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

402 return self.resolve(value) 

403 

404 def cfg_convert(self, value): 

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

406 rest = value 

407 m = self.WORD_PATTERN.match(rest) 

408 if m is None: 

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

410 else: 

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

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

413 #print d, rest 

414 while rest: 

415 m = self.DOT_PATTERN.match(rest) 

416 if m: 

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

418 else: 

419 m = self.INDEX_PATTERN.match(rest) 

420 if m: 

421 idx = m.groups()[0] 

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

423 d = d[idx] 

424 else: 

425 try: 

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

427 d = d[n] 

428 except TypeError: 

429 d = d[idx] 

430 if m: 

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

432 else: 

433 raise ValueError('Unable to convert ' 

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

435 #rest should be empty 

436 return d 

437 

438 def convert(self, value): 

439 """ 

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

441 replaced by their converting alternatives. Strings are checked to 

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

443 """ 

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

445 value = ConvertingDict(value) 

446 value.configurator = self 

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

448 value = ConvertingList(value) 

449 value.configurator = self 

450 elif not isinstance(value, ConvertingTuple) and\ 

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

452 value = ConvertingTuple(value) 

453 value.configurator = self 

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

455 m = self.CONVERT_PATTERN.match(value) 

456 if m: 

457 d = m.groupdict() 

458 prefix = d['prefix'] 

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

460 if converter: 

461 suffix = d['suffix'] 

462 converter = getattr(self, converter) 

463 value = converter(suffix) 

464 return value 

465 

466 def configure_custom(self, config): 

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

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

469 if not callable(c): 

470 c = self.resolve(c) 

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

472 # Check for valid identifiers 

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

474 result = c(**kwargs) 

475 if props: 

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

477 setattr(result, name, value) 

478 return result 

479 

480 def as_tuple(self, value): 

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

482 if isinstance(value, list): 

483 value = tuple(value) 

484 return value 

485 

486class DictConfigurator(BaseConfigurator): 

487 """ 

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

489 configuration. 

490 """ 

491 

492 def configure(self): 

493 """Do the configuration.""" 

494 

495 config = self.config 

496 if 'version' not in config: 

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

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

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

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

501 EMPTY_DICT = {} 

502 logging._acquireLock() 

503 try: 

504 if incremental: 

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

506 for name in handlers: 

507 if name not in logging._handlers: 

508 raise ValueError('No handler found with ' 

509 'name %r' % name) 

510 else: 

511 try: 

512 handler = logging._handlers[name] 

513 handler_config = handlers[name] 

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

515 if level: 

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

517 except Exception as e: 

518 raise ValueError('Unable to configure handler ' 

519 '%r' % name) from e 

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

521 for name in loggers: 

522 try: 

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

524 except Exception as e: 

525 raise ValueError('Unable to configure logger ' 

526 '%r' % name) from e 

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

528 if root: 

529 try: 

530 self.configure_root(root, True) 

531 except Exception as e: 

532 raise ValueError('Unable to configure root ' 

533 'logger') from e 

534 else: 

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

536 

537 _clearExistingHandlers() 

538 

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

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

541 for name in formatters: 

542 try: 

543 formatters[name] = self.configure_formatter( 

544 formatters[name]) 

545 except Exception as e: 

546 raise ValueError('Unable to configure ' 

547 'formatter %r' % name) from e 

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

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

550 for name in filters: 

551 try: 

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

553 except Exception as e: 

554 raise ValueError('Unable to configure ' 

555 'filter %r' % name) from e 

556 

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

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

559 # to allow a deterministic order of configuration 

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

561 deferred = [] 

562 for name in sorted(handlers): 

563 try: 

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

565 handler.name = name 

566 handlers[name] = handler 

567 except Exception as e: 

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

569 deferred.append(name) 

570 else: 

571 raise ValueError('Unable to configure handler ' 

572 '%r' % name) from e 

573 

574 # Now do any that were deferred 

575 for name in deferred: 

576 try: 

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

578 handler.name = name 

579 handlers[name] = handler 

580 except Exception as e: 

581 raise ValueError('Unable to configure handler ' 

582 '%r' % name) from e 

583 

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

585 

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

587 #since other threads may have pointers to them. 

588 #existing is set to contain all existing loggers, 

589 #and as we go through the new configuration we 

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

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

592 #which were in the previous configuration but 

593 #which are not in the new configuration. 

594 root = logging.root 

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

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

597 #avoid disabling child loggers of explicitly 

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

599 #to find the child loggers. 

600 existing.sort() 

601 #We'll keep the list of existing loggers 

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

603 child_loggers = [] 

604 #now set up the new ones... 

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

606 for name in loggers: 

607 if name in existing: 

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

609 prefixed = name + "." 

610 pflen = len(prefixed) 

611 num_existing = len(existing) 

612 while i < num_existing: 

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

614 child_loggers.append(existing[i]) 

615 i += 1 

616 existing.remove(name) 

617 try: 

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

619 except Exception as e: 

620 raise ValueError('Unable to configure logger ' 

621 '%r' % name) from e 

622 

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

624 #them as other threads may continue to hold references 

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

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

627 #probably not what was intended by the user. 

628 #for log in existing: 

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

630 # if log in child_loggers: 

631 # logger.level = logging.NOTSET 

632 # logger.handlers = [] 

633 # logger.propagate = True 

634 # elif disable_existing: 

635 # logger.disabled = True 

636 _handle_existing_loggers(existing, child_loggers, 

637 disable_existing) 

638 

639 # And finally, do the root logger 

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

641 if root: 

642 try: 

643 self.configure_root(root) 

644 except Exception as e: 

645 raise ValueError('Unable to configure root ' 

646 'logger') from e 

647 finally: 

648 logging._releaseLock() 

649 

650 def configure_formatter(self, config): 

651 """Configure a formatter from a dictionary.""" 

652 if '()' in config: 

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

654 try: 

655 result = self.configure_custom(config) 

656 except TypeError as te: 

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

658 raise 

659 #Name of parameter changed from fmt to format. 

660 #Retry with old name. 

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

662 #(e.g. by Django) 

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

664 config['()'] = factory 

665 result = self.configure_custom(config) 

666 else: 

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

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

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

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

671 

672 if not cname: 

673 c = logging.Formatter 

674 else: 

675 c = _resolve(cname) 

676 

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

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

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

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

681 else: 

682 result = c(fmt, dfmt, style) 

683 

684 return result 

685 

686 def configure_filter(self, config): 

687 """Configure a filter from a dictionary.""" 

688 if '()' in config: 

689 result = self.configure_custom(config) 

690 else: 

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

692 result = logging.Filter(name) 

693 return result 

694 

695 def add_filters(self, filterer, filters): 

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

697 for f in filters: 

698 try: 

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

700 except Exception as e: 

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

702 

703 def configure_handler(self, config): 

704 """Configure a handler from a dictionary.""" 

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

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

707 if formatter: 

708 try: 

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

710 except Exception as e: 

711 raise ValueError('Unable to set formatter ' 

712 '%r' % formatter) from e 

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

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

715 if '()' in config: 

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

717 if not callable(c): 

718 c = self.resolve(c) 

719 factory = c 

720 else: 

721 cname = config.pop('class') 

722 klass = self.resolve(cname) 

723 #Special case for handler which refers to another handler 

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

725 'target' in config: 

726 try: 

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

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

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

730 raise TypeError('target not configured yet') 

731 config['target'] = th 

732 except Exception as e: 

733 raise ValueError('Unable to set target handler ' 

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

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

736 'mailhost' in config: 

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

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

739 'address' in config: 

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

741 factory = klass 

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

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

744 try: 

745 result = factory(**kwargs) 

746 except TypeError as te: 

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

748 raise 

749 #The argument name changed from strm to stream 

750 #Retry with old name. 

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

752 #(e.g. by Django) 

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

754 result = factory(**kwargs) 

755 if formatter: 

756 result.setFormatter(formatter) 

757 if level is not None: 

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

759 if filters: 

760 self.add_filters(result, filters) 

761 if props: 

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

763 setattr(result, name, value) 

764 return result 

765 

766 def add_handlers(self, logger, handlers): 

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

768 for h in handlers: 

769 try: 

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

771 except Exception as e: 

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

773 

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

775 """ 

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

777 """ 

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

779 if level is not None: 

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

781 if not incremental: 

782 #Remove any existing handlers 

783 for h in logger.handlers[:]: 

784 logger.removeHandler(h) 

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

786 if handlers: 

787 self.add_handlers(logger, handlers) 

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

789 if filters: 

790 self.add_filters(logger, filters) 

791 

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

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

794 logger = logging.getLogger(name) 

795 self.common_logger_config(logger, config, incremental) 

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

797 if propagate is not None: 

798 logger.propagate = propagate 

799 

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

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

802 root = logging.getLogger() 

803 self.common_logger_config(root, config, incremental) 

804 

805dictConfigClass = DictConfigurator 

806 

807def dictConfig(config): 

808 """Configure logging using a dictionary.""" 

809 dictConfigClass(config).configure() 

810 

811 

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

813 """ 

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

815 configurations. 

816 

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

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

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

820 stopListening(). 

821 

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

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

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

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

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

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

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

829 the bytes passed in. 

830 """ 

831 

832 class ConfigStreamHandler(StreamRequestHandler): 

833 """ 

834 Handler for a logging configuration request. 

835 

836 It expects a completely new logging configuration and uses fileConfig 

837 to install it. 

838 """ 

839 def handle(self): 

840 """ 

841 Handle a request. 

842 

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

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

845 Uses fileConfig() to do the grunt work. 

846 """ 

847 try: 

848 conn = self.connection 

849 chunk = conn.recv(4) 

850 if len(chunk) == 4: 

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

852 chunk = self.connection.recv(slen) 

853 while len(chunk) < slen: 

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

855 if self.server.verify is not None: 

856 chunk = self.server.verify(chunk) 

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

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

859 try: 

860 import json 

861 d =json.loads(chunk) 

862 assert isinstance(d, dict) 

863 dictConfig(d) 

864 except Exception: 

865 #Apply new configuration. 

866 

867 file = io.StringIO(chunk) 

868 try: 

869 fileConfig(file) 

870 except Exception: 

871 traceback.print_exc() 

872 if self.server.ready: 

873 self.server.ready.set() 

874 except OSError as e: 

875 if e.errno != RESET_ERROR: 

876 raise 

877 

878 class ConfigSocketReceiver(ThreadingTCPServer): 

879 """ 

880 A simple TCP socket-based logging config receiver. 

881 """ 

882 

883 allow_reuse_address = 1 

884 

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

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

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

888 logging._acquireLock() 

889 self.abort = 0 

890 logging._releaseLock() 

891 self.timeout = 1 

892 self.ready = ready 

893 self.verify = verify 

894 

895 def serve_until_stopped(self): 

896 import select 

897 abort = 0 

898 while not abort: 

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

900 [], [], 

901 self.timeout) 

902 if rd: 

903 self.handle_request() 

904 logging._acquireLock() 

905 abort = self.abort 

906 logging._releaseLock() 

907 self.server_close() 

908 

909 class Server(threading.Thread): 

910 

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

912 super(Server, self).__init__() 

913 self.rcvr = rcvr 

914 self.hdlr = hdlr 

915 self.port = port 

916 self.verify = verify 

917 self.ready = threading.Event() 

918 

919 def run(self): 

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

921 ready=self.ready, 

922 verify=self.verify) 

923 if self.port == 0: 

924 self.port = server.server_address[1] 

925 self.ready.set() 

926 global _listener 

927 logging._acquireLock() 

928 _listener = server 

929 logging._releaseLock() 

930 server.serve_until_stopped() 

931 

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

933 

934def stopListening(): 

935 """ 

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

937 """ 

938 global _listener 

939 logging._acquireLock() 

940 try: 

941 if _listener: 

942 _listener.abort = 1 

943 _listener = None 

944 finally: 

945 logging._releaseLock()