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

Gallopsled / pwntools / 13600950642

01 Mar 2025 04:10AM UTC coverage: 74.211% (+3.2%) from 71.055%
13600950642

Pull #2546

github

web-flow
Merge 77df40314 into 60cff2437
Pull Request #2546: ssh: Allow passing `disabled_algorithms` keyword argument from `ssh` to paramiko

3812 of 6380 branches covered (59.75%)

0 of 1 new or added line in 1 file covered. (0.0%)

1243 existing lines in 37 files now uncovered.

13352 of 17992 relevant lines covered (74.21%)

0.74 hits per line

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

76.98
/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 string
1✔
102
import sys
1✔
103
import threading
1✔
104
import time
1✔
105

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

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

118

119

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

136

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

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

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

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

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

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

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

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

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

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

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

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

202
        Logs a status update for the running job.
203

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

283
        self._logger = logger
1✔
284

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

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

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

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

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

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

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

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

314
        .. code-block:: python
315

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

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

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

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

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

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

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

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

351
        Logs a failure message.
352
        """
UNCOV
353
        self._log(logging.INFO, message, args, kwargs, 'failure')
×
354

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

433
        To be called outside an exception handler.
434

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

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

443
        To be called from an exception handler.
444

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

507

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

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

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

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

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

552
        # yay, spinners!
553

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

706
    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

© 2026 Coveralls, Inc