• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 781b1967170151f34ef8334c55b13a3fe6c70e11

pending completion
781b1967170151f34ef8334c55b13a3fe6c70e11

push

github-actions

Arusekk
Use global env

3903 of 6420 branches covered (60.79%)

12247 of 16698 relevant lines covered (73.34%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.99
/pwnlib/log.py
1
"""
2
Logging module for printing status during an exploit, and internally
3
within ``pwntools``.
4

5
Exploit Developers
6
------------------
7
By using the standard ``from pwn import *``, an object named ``log`` will
8
be inserted into the global namespace.  You can use this to print out
9
status messages during exploitation.
10

11
For example,::
12

13
    log.info('Hello, world!')
14

15
prints::
16

17
    [*] Hello, world!
18

19
Additionally, there are some nifty mechanisms for performing status updates
20
on a running job (e.g. when brute-forcing).::
21

22
    p = log.progress('Working')
23
    p.status('Reticulating splines')
24
    time.sleep(1)
25
    p.success('Got a shell!')
26

27

28
The verbosity of logging can be most easily controlled by setting
29
``log_level`` on the global ``context`` object.::
30

31
    log.info("No you see me")
32
    context.log_level = 'error'
33
    log.info("Now you don't")
34

35
The purpose of this attribute is to control what gets printed to the screen,
36
not what gets emitted. This means that you can put all logging events into
37
a log file, while only wanting to see a small subset of them on your screen.
38

39
Pwnlib Developers
40
-----------------
41
A module-specific logger can be imported into the module via::
42

43
    from pwnlib.log import getLogger
44
    log = getLogger(__name__)
45

46
This provides an easy way to filter logging programmatically
47
or via a configuration file for debugging.
48

49
When using ``progress``, you should use the ``with``
50
keyword to manage scoping, to ensure the spinner stops if an
51
exception is thrown.
52

53
Technical details
54
-----------------
55
Familiarity with the :mod:`logging` module is assumed.
56

57
A pwnlib root logger named 'pwnlib' is created and a custom handler and
58
formatter is installed for it.  The handler determines its logging level from
59
:data:`context.log_level`.
60

61
Ideally :data:`context.log_level` should only affect which records will be
62
emitted by the handler such that e.g. logging to a file will not be changed by
63
it. But for performance reasons it is not feasible log everything in the normal
64
case. In particular there are tight loops inside :mod:`pwnlib.tubes.tube`, which
65
we would like to be able to debug, but if we are not debugging them, they should
66
not spit out messages (even to a log file). For this reason there are a few places
67
inside pwnlib, that will not even emit a record without :data:`context.log_level`
68
being set to `logging.DEBUG` or below.
69

70
Log records created by ``Progress`` and ``Logger`` objects will set
71
``'pwnlib_msgtype'`` on the ``extra`` field to signal which kind of message was
72
generated.  This information is used by the formatter to prepend a symbol to the
73
message, e.g. ``'[+] '`` in ``'[+] got a shell!'``
74

75
This field is ignored when using the ``logging`` module's standard formatters.
76

77
All status updates (which are not dropped due to throttling) on progress loggers
78
result in a log record being created.  The ``extra`` field then carries a
79
reference to the ``Progress`` logger as ``'pwnlib_progress'``.
80

81
If the custom handler determines that :data:`term.term_mode` is enabled, log
82
records that have a ``'pwnlib_progess'`` in their ``extra`` field will not
83
result in a message being emitted but rather an animated progress line (with a
84
spinner!) being created.  Note that other handlers will still see a meaningful
85
log record.
86

87
The custom handler will only handle log records with a level of at least
88
:data:`context.log_level`.  Thus if e.g. the level for the
89
``'pwnlib.tubes.ssh'`` is set to ``'DEBUG'`` no additional output will show up
90
unless :data:`context.log_level` is also set to ``'DEBUG'``.  Other handlers
91
will however see the extra log records generated by the ``'pwnlib.tubes.ssh'``
92
logger.
93
"""
94
from __future__ import absolute_import
1✔
95
from __future__ import division
1✔
96

97
import logging
1✔
98
import os
1✔
99
import random
1✔
100
import re
1✔
101
import six
1✔
102
import string
1✔
103
import sys
1✔
104
import threading
1✔
105
import time
1✔
106

107
from pwnlib import term
1✔
108
from pwnlib.config import register_config
1✔
109
from pwnlib.context import Thread
1✔
110
from pwnlib.context import context
1✔
111
from pwnlib.exception import PwnlibException
1✔
112
from pwnlib.term import spinners
1✔
113
from pwnlib.term import text
1✔
114

115
__all__ = [
1✔
116
    'getLogger', 'install_default_handler', 'rootlogger'
117
]
118

119

120

121
# list of prefixes to use for the different message types.  note that the `text`
122
# module won't add any escape codes if `pwnlib.context.log_console.isatty()` is `False`
123
_msgtype_prefixes = {
1✔
124
    'status'       : [text.magenta, 'x'],
125
    'success'      : [text.bold_green, '+'],
126
    'failure'      : [text.bold_red, '-'],
127
    'debug'        : [text.bold_red, 'DEBUG'],
128
    'info'         : [text.bold_blue, '*'],
129
    'warning'      : [text.bold_yellow, '!'],
130
    'error'        : [text.on_red, 'ERROR'],
131
    'exception'    : [text.on_red, 'ERROR'],
132
    'critical'     : [text.on_red, 'CRITICAL'],
133
    'info_once'    : [text.bold_blue, '*'],
134
    'warning_once' : [text.bold_yellow, '!'],
135
    }
136

137

138
def read_log_config(settings):
1✔
139
    log = getLogger(__name__)
×
140
    for key, value in settings.items():
×
141
        if '.' not in key:
×
142
            log.warn("Invalid configuration option %r in section %r" % (key, 'log'))
×
143
            continue
×
144

145
        msgtype, key = key.split('.', 1)
×
146

147
        if key == 'color':
×
148
            current = _msgtype_prefixes[msgtype][0]
×
149
            _msgtype_prefixes[msgtype][0] = getattr(text, value, current)
×
150

151
        elif key == 'symbol':
×
152
            _msgtype_prefixes[msgtype][1] = value
×
153

154
        else:
155
            log.warn("Unknown configuration option %r in section %r" % (key, 'log'))
×
156

157
register_config('log', read_log_config)
1✔
158

159
# the text decoration to use for spinners.  the spinners themselves can be found
160
# in the `pwnlib.term.spinners` module
161
_spinner_style = text.bold_blue
1✔
162

163
class Progress(object):
1✔
164
    """
165
    Progress logger used to generate log records associated with some running
166
    job.  Instances can be used as context managers which will automatically
167
    declare the running job a success upon exit or a failure upon a thrown
168
    exception.  After :meth:`success` or :meth:`failure` is called the status
169
    can no longer be updated.
170

171
    This class is intended for internal use.  Progress loggers should be created
172
    using :meth:`Logger.progress`.
173
    """
174
    def __init__(self, logger, msg, status, level, args, kwargs):
1✔
175
        self._logger = logger
1✔
176
        self._msg = msg
1✔
177
        self._status = status
1✔
178
        self._level = level
1✔
179
        self._stopped = False
1✔
180
        self.last_status = 0
1✔
181
        self.rate = kwargs.pop('rate', 0)
1✔
182
        self._log(status, args, kwargs, 'status')
1✔
183
        # it is a common use case to create a logger and then immediately update
184
        # its status line, so we reset `last_status` to accommodate this pattern
185
        self.last_status = 0
1✔
186

187
    def _log(self, status, args, kwargs, msgtype):
1✔
188
        # Logs are strings, not bytes.  Handle Python3 bytes() objects.
189
        status = _need_text(status)
1✔
190

191
        # this progress logger is stopped, so don't generate any more records
192
        if self._stopped:
1✔
193
            return
1✔
194
        msg = self._msg
1✔
195
        if msg and status:
1✔
196
            msg += ': '
1✔
197
        msg += status
1✔
198
        self._logger._log(self._level, msg, args, kwargs, msgtype, self)
1✔
199

200
    def status(self, status, *args, **kwargs):
1✔
201
        """status(status, *args, **kwargs)
202

203
        Logs a status update for the running job.
204

205
        If the progress logger is animated the status line will be updated in
206
        place.
207

208
        Status updates are throttled at one update per 100ms.
209
        """
210
        now = time.time()
1✔
211
        if (now - self.last_status) > self.rate:
1✔
212
            self.last_status = now
1✔
213
            self._log(status, args, kwargs, 'status')
1✔
214

215
    def success(self, status = 'Done', *args, **kwargs):
1✔
216
        """success(status = 'Done', *args, **kwargs)
217

218
        Logs that the running job succeeded.  No further status updates are
219
        allowed.
220

221
        If the Logger is animated, the animation is stopped.
222
        """
223
        self._log(status, args, kwargs, 'success')
1✔
224
        self._stopped = True
1✔
225

226
    def failure(self, status = 'Failed', *args, **kwargs):
1✔
227
        """failure(message)
228

229
        Logs that the running job failed.  No further status updates are
230
        allowed.
231

232
        If the Logger is animated, the animation is stopped.
233
        """
234
        self._log(status, args, kwargs, 'failure')
1✔
235
        self._stopped = True
1✔
236

237
    def __enter__(self):
1✔
238
        return self
1✔
239

240
    def __exit__(self, exc_typ, exc_val, exc_tb):
1✔
241
        # if the progress logger is already stopped these are no-ops
242
        if exc_typ is None:
1✔
243
            self.success()
1✔
244
        else:
245
            self.failure()
1✔
246

247
class Logger(object):
1✔
248
    """
249
    A class akin to the :class:`logging.LoggerAdapter` class.  All public
250
    methods defined on :class:`logging.Logger` instances are defined on this
251
    class.
252

253
    Also adds some ``pwnlib`` flavor:
254

255
    * :meth:`progress` (alias :meth:`waitfor`)
256
    * :meth:`success`
257
    * :meth:`failure`
258
    * :meth:`indented`
259
    * :meth:`info_once`
260
    * :meth:`warning_once` (alias :meth:`warn_once`)
261

262
    Adds ``pwnlib``-specific information for coloring, indentation and progress
263
    logging via log records ``extra`` field.
264

265
    Loggers instantiated with :func:`getLogger` will be of this class.
266
    """
267
    _one_time_infos    = set()
1✔
268
    _one_time_warnings = set()
1✔
269

270
    def __init__(self, logger=None):
1✔
271
        if logger is None:
1✔
272
            # This is a minor hack to permit user-defined classes which inherit
273
            # from a tube (which do not actually reside in the pwnlib library)
274
            # to receive logging abilities that behave as they would expect from
275
            # the rest of the library
276
            module = self.__module__
1✔
277
            if not module.startswith('pwnlib'):
1!
278
                module = 'pwnlib.' + module
×
279
            # - end hack -
280

281
            logger_name = '%s.%s.%s' % (module, self.__class__.__name__, id(self))
1✔
282
            logger = logging.getLogger(logger_name)
1✔
283

284
        self._logger = logger
1✔
285

286
    def _getlevel(self, levelString):
1✔
287
        if isinstance(levelString, six.integer_types):
1!
288
            return levelString
1✔
289
        return logging._levelNames[levelString.upper()]
×
290

291
    def _log(self, level, msg, args, kwargs, msgtype, progress = None):
1✔
292
        # Logs are strings, not bytes.  Handle Python3 bytes() objects.
293
        msg = _need_text(msg)
1✔
294

295
        extra = kwargs.get('extra', {})
1✔
296
        extra.setdefault('pwnlib_msgtype', msgtype)
1✔
297
        extra.setdefault('pwnlib_progress', progress)
1✔
298
        kwargs['extra'] = extra
1✔
299
        self._logger.log(level, msg, *args, **kwargs)
1✔
300

301
    def progress(self, message, status = '', *args, **kwargs):
1✔
302
        """progress(message, status = '', *args, level = logging.INFO, **kwargs) -> Progress
303

304
        Creates a new progress logger which creates log records with log level
305
        `level`.
306

307
        Progress status can be updated using :meth:`Progress.status` and stopped
308
        using :meth:`Progress.success` or :meth:`Progress.failure`.
309

310
        If `term.term_mode` is enabled the progress logger will be animated.
311

312
        The progress manager also functions as a context manager.  Using context
313
        managers ensures that animations stop even if an exception is raised.
314

315
        .. code-block:: python
316

317
           with log.progress('Trying something...') as p:
318
               for i in range(10):
319
                   p.status("At %i" % i)
320
                   time.sleep(0.5)
321
               x = 1/0
322
        """
323
        level = self._getlevel(kwargs.pop('level', logging.INFO))
1✔
324
        return Progress(self, message, status, level, args, kwargs)
1✔
325

326
    def waitfor(self, *args, **kwargs):
1✔
327
        """Alias for :meth:`progress`."""
328
        return self.progress(*args, **kwargs)
1✔
329

330
    def indented(self, message, *args, **kwargs):
1✔
331
        """indented(message, *args, level = logging.INFO, **kwargs)
332

333
        Log a message but don't put a line prefix on it.
334

335
        Arguments:
336
            level(int): Alternate log level at which to set the indented
337
                        message.  Defaults to :const:`logging.INFO`.
338
        """
339
        level = self._getlevel(kwargs.pop('level', logging.INFO))
1✔
340
        self._log(level, message, args, kwargs, 'indented')
1✔
341

342
    def success(self, message, *args, **kwargs):
1✔
343
        """success(message, *args, **kwargs)
344

345
        Logs a success message.
346
        """
347
        self._log(logging.INFO, message, args, kwargs, 'success')
1✔
348

349
    def failure(self, message, *args, **kwargs):
1✔
350
        """failure(message, *args, **kwargs)
351

352
        Logs a failure message.
353
        """
354
        self._log(logging.INFO, message, args, kwargs, 'failure')
1✔
355

356
    def info_once(self, message, *args, **kwargs):
1✔
357
        """info_once(message, *args, **kwargs)
358

359
        Logs an info message.  The same message is never printed again.
360
        """
361
        m = message % args
1✔
362
        if m not in self._one_time_infos:
1✔
363
            if self.isEnabledFor(logging.INFO):
1✔
364
                self._one_time_infos.add(m)
1✔
365
            self._log(logging.INFO, message, args, kwargs, 'info_once')
1✔
366

367
    def warning_once(self, message, *args, **kwargs):
1✔
368
        """warning_once(message, *args, **kwargs)
369

370
        Logs a warning message.  The same message is never printed again.
371
        """
372
        m = message % args
1✔
373
        if m not in self._one_time_warnings:
1✔
374
            if self.isEnabledFor(logging.WARNING):
1✔
375
                self._one_time_warnings.add(m)
1✔
376
            self._log(logging.WARNING, message, args, kwargs, 'warning_once')
1✔
377

378
    def warn_once(self, *args, **kwargs):
1✔
379
        """Alias for :meth:`warning_once`."""
380
        return self.warning_once(*args, **kwargs)
1✔
381

382
    # logging functions also exposed by `logging.Logger`
383

384
    def debug(self, message, *args, **kwargs):
1✔
385
        """debug(message, *args, **kwargs)
386

387
        Logs a debug message.
388
        """
389
        self._log(logging.DEBUG, message, args, kwargs, 'debug')
1✔
390

391
    def info(self, message, *args, **kwargs):
1✔
392
        """info(message, *args, **kwargs)
393

394
        Logs an info message.
395
        """
396
        self._log(logging.INFO, message, args, kwargs, 'info')
1✔
397

398
    def hexdump(self, message, *args, **kwargs):
1✔
399
        # cyclic dependencies FTW!
400
        # TODO: Move pwnlib.util.fiddling.hexdump into a new module.
401
        import pwnlib.util.fiddling
×
402

403
        self.info(pwnlib.util.fiddling.hexdump(message, *args, **kwargs))
×
404

405
    def maybe_hexdump(self, message, *args, **kwargs):
1✔
406
        """maybe_hexdump(self, message, *args, **kwargs)
407

408
        Logs a message using indented. Repeated single byte is compressed, and
409
        unprintable message is hexdumped.
410
        """
411
        if len(set(message)) == 1 and len(message) > 1:
1!
412
            self.indented('%r * %#x' % (message[:1], len(message)), *args, **kwargs)
×
413
        elif len(message) == 1 or all(c in string.printable.encode() for c in message):
1!
414
            for line in message.splitlines(True):
1✔
415
                self.indented(repr(line), *args, **kwargs)
1✔
416
        else:
417
            import pwnlib.util.fiddling
×
418
            self.indented(pwnlib.util.fiddling.hexdump(message), *args, **kwargs)
×
419

420
    def warning(self, message, *args, **kwargs):
1✔
421
        """warning(message, *args, **kwargs)
422

423
        Logs a warning message.
424
        """
425
        self._log(logging.WARNING, message, args, kwargs, 'warning')
1✔
426

427
    def warn(self, *args, **kwargs):
1✔
428
        """Alias for :meth:`warning`."""
429
        return self.warning(*args, **kwargs)
1✔
430

431
    def error(self, message, *args, **kwargs):
1✔
432
        """error(message, *args, **kwargs)
433

434
        To be called outside an exception handler.
435

436
        Logs an error message, then raises a ``PwnlibException``.
437
        """
438
        self._log(logging.ERROR, message, args, kwargs, 'error')
1✔
439
        raise PwnlibException(message % args)
1✔
440

441
    def exception(self, message, *args, **kwargs):
1✔
442
        """exception(message, *args, **kwargs)
443

444
        To be called from an exception handler.
445

446
        Logs a error message, then re-raises the current exception.
447
        """
448
        kwargs["exc_info"] = 1
×
449
        self._log(logging.ERROR, message, args, kwargs, 'exception')
×
450
        raise
×
451

452
    def critical(self, message, *args, **kwargs):
1✔
453
        """critical(message, *args, **kwargs)
454

455
        Logs a critical message.
456
        """
457
        self._log(logging.CRITICAL, message, args, kwargs, 'critical')
×
458

459
    def log(self, level, message, *args, **kwargs):
1✔
460
        """log(level, message, *args, **kwargs)
461

462
        Logs a message with log level `level`.  The ``pwnlib`` formatter will
463
        use the default :mod:`logging` formater to format this message.
464
        """
465
        self._log(level, message, args, kwargs, None)
×
466

467
    def isEnabledFor(self, level):
1✔
468
        """isEnabledFor(level) -> bool
469

470
        See if the underlying logger is enabled for the specified level.
471
        """
472
        effectiveLevel = self._logger.getEffectiveLevel()
1✔
473

474
        if effectiveLevel == 1:
1✔
475
            effectiveLevel = context.log_level
1✔
476
        return effectiveLevel <= level
1✔
477

478
    def setLevel(self, level):
1✔
479
        """setLevel(level)
480

481
        Set the logging level for the underlying logger.
482
        """
483
        with context.local(log_level=level):
1✔
484
            self._logger.setLevel(context.log_level)
1✔
485

486
    def addHandler(self, handler):
1✔
487
        """addHandler(handler)
488

489
        Add the specified handler to the underlying logger.
490
        """
491
        self._logger.addHandler(handler)
×
492

493
    def removeHandler(self, handler):
1✔
494
        """removeHandler(handler)
495

496
        Remove the specified handler from the underlying logger.
497
        """
498
        self._logger.removeHandler(handler)
×
499

500
    @property
1✔
501
    def level(self):
502
        return self._logger.level
1✔
503
    @level.setter
1✔
504
    def level(self, value):
505
        with context.local(log_level=value):
1✔
506
            self._logger.level = context.log_level
1✔
507

508

509
class Handler(logging.StreamHandler):
1✔
510
    """
511
    A custom handler class.  This class will report whatever
512
    :data:`context.log_level` is currently set to as its log level.
513

514
    If :data:`term.term_mode` is enabled log records originating from a progress
515
    logger will not be emitted but rather an animated progress line will be
516
    created.
517

518
    An instance of this handler is added to the ``'pwnlib'`` logger.
519
    """
520
    @property
1✔
521
    def stream(self):
522
        return context.log_console
1✔
523
    @stream.setter
1✔
524
    def stream(self, value):
525
        pass
1✔
526
    def emit(self, record):
1✔
527
        """
528
        Emit a log record or create/update an animated progress logger
529
        depending on whether :data:`term.term_mode` is enabled.
530
        """
531
        # We have set the root 'pwnlib' logger to have a logLevel of 1,
532
        # when logging has been enabled via install_default_handler.
533
        #
534
        # If the level is 1, we should only process the record if
535
        # context.log_level is less than the record's log level.
536
        #
537
        # If the level is not 1, somebody else expressly set the log
538
        # level somewhere on the tree, and we should use that value.
539
        level = logging.getLogger(record.name).getEffectiveLevel()
1✔
540
        if level == 1:
1!
541
            level = context.log_level
1✔
542
        if level > record.levelno:
1✔
543
            return
1✔
544

545
        progress = getattr(record, 'pwnlib_progress', None)
1✔
546

547
        # if the record originates from a `Progress` object and term handling
548
        # is enabled we can have animated spinners! so check that
549
        if progress is None or not term.term_mode:
1!
550
            super(Handler, self).emit(record)
1✔
551
            return
1✔
552

553
        # yay, spinners!
554

555
        # since we want to be able to update the spinner we overwrite the
556
        # message type so that the formatter doesn't output a prefix symbol
557
        msgtype = record.pwnlib_msgtype
×
558
        record.pwnlib_msgtype = 'animated'
×
559
        msg = "%s\n" % self.format(record)
×
560

561
        # we enrich the `Progress` object to keep track of the spinner
562
        if not hasattr(progress, '_spinner_handle'):
×
563
            spinner_handle = term.output('')
×
564
            msg_handle = term.output(msg)
×
565
            stop = threading.Event()
×
566
            def spin():
×
567
                '''Wheeeee!'''
568
                state = 0
×
569
                states = random.choice(spinners.spinners)
×
570
                while True:
×
571
                    prefix = '[%s] ' % _spinner_style(states[state])
×
572
                    spinner_handle.update(prefix)
×
573
                    state = (state + 1) % len(states)
×
574
                    if stop.wait(0.1):
×
575
                        break
×
576
            t = Thread(target = spin)
×
577
            t.daemon = True
×
578
            t.start()
×
579
            progress._spinner_handle = spinner_handle
×
580
            progress._msg_handle = msg_handle
×
581
            progress._stop_event = stop
×
582
            progress._spinner_thread = t
×
583
        else:
584
            progress._msg_handle.update(msg)
×
585

586
        # if the message type was not a status message update, then we should
587
        # stop the spinner
588
        if msgtype != 'status':
×
589
            progress._stop_event.set()
×
590
            progress._spinner_thread.join()
×
591
            style, symb = _msgtype_prefixes[msgtype]
×
592
            prefix = '[%s] ' % style(symb)
×
593
            progress._spinner_handle.update(prefix)
×
594

595
class Formatter(logging.Formatter):
1✔
596
    """
597
    Logging formatter which performs custom formatting for log records
598
    containing the ``'pwnlib_msgtype'`` attribute.  Other records are formatted
599
    using the `logging` modules default formatter.
600

601
    If ``'pwnlib_msgtype'`` is set, it performs the following actions:
602

603
    * A prefix looked up in `_msgtype_prefixes` is prepended to the message.
604
    * The message is prefixed such that it starts on column four.
605
    * If the message spans multiple lines they are split, and all subsequent
606
      lines are indented.
607

608
    This formatter is used by the handler installed on the ``'pwnlib'`` logger.
609
    """
610

611
    # Indentation from the left side of the terminal.
612
    # All log messages will be indented at list this far.
613
    indent    = '    '
1✔
614

615
    # Newline, followed by an indent.  Used to wrap multiple lines.
616
    nlindent  = '\n' + indent
1✔
617

618
    def format(self, record):
1✔
619
        # use the default formatter to actually format the record
620
        msg = super(Formatter, self).format(record)
1✔
621

622
        # then put on a prefix symbol according to the message type
623

624
        msgtype = getattr(record, 'pwnlib_msgtype', None)
1✔
625

626
        # if 'pwnlib_msgtype' is not set (or set to `None`) we just return the
627
        # message as it is
628
        if msgtype is None:
1!
629
            return msg
×
630

631
        if msgtype in _msgtype_prefixes:
1✔
632
            style, symb = _msgtype_prefixes[msgtype]
1✔
633
            prefix = '[%s] ' % style(symb)
1✔
634
        elif msgtype == 'indented':
1!
635
            prefix = self.indent
1✔
636
        elif msgtype == 'animated':
×
637
            # the handler will take care of updating the spinner, so we will
638
            # not include it here
639
            prefix = ''
×
640
        else:
641
            # this should never happen
642
            prefix = '[?] '
×
643

644
        msg = prefix + msg
1✔
645
        msg = self.nlindent.join(msg.splitlines())
1✔
646
        return msg
1✔
647

648
def _need_text(s):
1✔
649
    # circular import wrapper
650
    global _need_text
651
    from pwnlib.util.packing import _need_text
1✔
652
    return _need_text(s, 2)
1✔
653

654
# we keep a dictionary of loggers such that multiple calls to `getLogger` with
655
# the same name will return the same logger
656
def getLogger(name):
1✔
657
    return Logger(logging.getLogger(name))
1✔
658

659
class LogfileHandler(logging.FileHandler):
1✔
660
    def __init__(self):
1✔
661
        super(LogfileHandler, self).__init__('', delay=1)
1✔
662
    @property
1✔
663
    def stream(self):
664
        return context.log_file
1✔
665
    @stream.setter
1✔
666
    def stream(self, value):
667
        pass
1✔
668
    def handle(self, *a, **kw):
1✔
669
        if self.stream.name is not None:
1✔
670
            super(LogfileHandler, self).handle(*a, **kw)
1✔
671

672
iso_8601 = '%Y-%m-%dT%H:%M:%S'
1✔
673
fmt      = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
1✔
674
log_file = LogfileHandler()
1✔
675
log_file.setFormatter(logging.Formatter(fmt, iso_8601))
1✔
676

677
#
678
# The root 'pwnlib' logger is declared here.  To change the target of all
679
# 'pwntools'-specific logging, only this logger needs to be changed.
680
#
681
# Logging cascades upward through the hierarchy,
682
# so the only point that should ever need to be
683
# modified is the root 'pwnlib' logger.
684
#
685
# For example:
686
#     map(rootlogger.removeHandler, rootlogger.handlers)
687
#     logger.addHandler(myCoolPitchingHandler)
688
#
689
rootlogger = getLogger('pwnlib')
1✔
690
console   = Handler()
1✔
691
formatter = Formatter()
1✔
692
console.setFormatter(formatter)
1✔
693

694
def install_default_handler():
1✔
695
    '''install_default_handler()
696

697
    Instantiates a :class:`Handler` and :class:`Formatter` and installs them for
698
    the ``pwnlib`` root logger.  This function is automatically called from when
699
    importing :mod:`pwn`.
700
    '''
701
    logger         = logging.getLogger('pwnlib')
1✔
702

703
    if console not in logger.handlers:
1✔
704
        logger.addHandler(console)
1✔
705
        logger.addHandler(log_file)
1✔
706

707
    logger.setLevel(1)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc