Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use log helper #449

Merged
merged 21 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f7d03a8
crypto: use log_helper
christian-intra2net Apr 24, 2019
a025857
crypto: create enable_logging
christian-intra2net May 27, 2019
68a27d0
record_base: use log_helper
christian-intra2net Apr 24, 2019
5c0db3b
record_base: create enable_logging
christian-intra2net May 27, 2019
f7a3957
record_base: remove unused import, add missing doc string
christian-intra2net May 27, 2019
55d9926
olevba: close() the VBA_Parser
christian-intra2net May 23, 2019
5e59e93
olevba: Add missing meta info
christian-intra2net May 24, 2019
ca0ee46
olevba: use log_helper
christian-intra2net Sep 28, 2021
cdede5b
log_helper: allow integration with other json-printers
christian-intra2net Apr 25, 2019
1e920d6
log_helper: Use stdout for all json logging
christian-intra2net May 23, 2019
87378db
log_helper: provide logging.NOTSET
christian-intra2net May 27, 2019
41b986a
log_helper: Provide logger.setLevel also for older python
christian-intra2net May 27, 2019
58954d6
tests: Update olevba test to slight change in json output
christian-intra2net May 23, 2019
a3a5775
tests: Remove unused variables
christian-intra2net May 27, 2019
79b3c64
tests: make log test modules look more like regular ones
christian-intra2net May 27, 2019
6c1c523
tests: add test for log when imported by 3rd party
christian-intra2net May 27, 2019
de1e1ee
olevba: debug-log caught exception
christian-intra2net May 27, 2019
bcc339d
mraptor: Use log_helper
christian-intra2net Sep 28, 2021
410baa0
log_helper: Add short usage instruction
christian-intra2net Sep 28, 2021
f899797
tests: fix & extend olevba crypto test
christian-intra2net Sep 28, 2021
b8ba847
tests: speed up longest test
christian-intra2net Sep 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions oletools/common/log_helper/_json_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class JsonFormatter(logging.Formatter):
"""
_is_first_line = True

def __init__(self, other_logger_has_first_line=False):
if other_logger_has_first_line:
self._is_first_line = False

def format(self, record):
"""
Since we don't buffer messages, we always prepend messages with a comma to make
Expand Down
5 changes: 5 additions & 0 deletions oletools/common/log_helper/_logger_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ def set_json_enabled_function(self, json_enabled):
self._json_enabled = json_enabled

def level(self):
"""Return current level of logger."""
return self.logger.level

def setLevel(self, new_level):
"""Set level of underlying logger. Required only for python < 3.2."""
return self.logger.setLevel(new_level)
57 changes: 51 additions & 6 deletions oletools/common/log_helper/log_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@

General logging helpers

Use as follows:

# at the start of your file:
# import logging <-- replace this with next line
from oletools.common.log_helper import log_helper

logger = log_helper.get_or_create_silent_logger("module_name")
def enable_logging():
'''Enable logging in this module; for use by importing scripts'''
logger.setLevel(log_helper.NOTSET)
imported_oletool_module.enable_logging()
other_imported_oletool_module.enable_logging()

# ... your code; use logger instead of logging ...

def main():
log_helper.enable_logging(level=...) # instead of logging.basicConfig
# ... your main code ...
log_helper.end_logging()

.. codeauthor:: Intra2net AG <info@intra2net>, Philippe Lagadec
"""

Expand Down Expand Up @@ -45,6 +65,7 @@
# TODO:


from __future__ import print_function
from ._json_formatter import JsonFormatter
from ._logger_adapter import OletoolsLoggerAdapter
from . import _root_logger_wrapper
Expand All @@ -61,15 +82,27 @@
'critical': logging.CRITICAL
}

#: provide this constant to modules, so they do not have to import
#: :py:mod:`logging` for themselves just for this one constant.
NOTSET = logging.NOTSET

DEFAULT_LOGGER_NAME = 'oletools'
DEFAULT_MESSAGE_FORMAT = '%(levelname)-8s %(message)s'


class LogHelper:
"""
Single helper class that creates and remembers loggers.
"""

#: for convenience: here again (see also :py:data:`log_helper.NOTSET`)
NOTSET = logging.NOTSET

def __init__(self):
self._all_names = set() # set so we do not have duplicates
self._use_json = False
self._is_enabled = False
self._target_stream = None

def get_or_create_silent_logger(self, name=DEFAULT_LOGGER_NAME, level=logging.CRITICAL + 1):
"""
Expand All @@ -82,7 +115,8 @@ def get_or_create_silent_logger(self, name=DEFAULT_LOGGER_NAME, level=logging.CR
"""
return self._get_or_create_logger(name, level, logging.NullHandler())

def enable_logging(self, use_json=False, level='warning', log_format=DEFAULT_MESSAGE_FORMAT, stream=None):
def enable_logging(self, use_json=False, level='warning', log_format=DEFAULT_MESSAGE_FORMAT, stream=None,
other_logger_has_first_line=False):
"""
This function initializes the root logger and enables logging.
We set the level of the root logger to the one passed by calling logging.basicConfig.
Expand All @@ -93,15 +127,26 @@ def enable_logging(self, use_json=False, level='warning', log_format=DEFAULT_MES
which in turn will log to the stream set in this function.
Since the root logger is the one doing the work, when using JSON we set its formatter
so that every message logged is JSON-compatible.

If other code also creates json output, all items should be pre-pended
with a comma like the `JsonFormatter` does. Except the first; use param
`other_logger_has_first_line` to clarify whether our logger or the
other code will produce the first json item.
"""
if self._is_enabled:
raise ValueError('re-enabling logging. Not sure whether that is ok...')

if stream in (None, sys.stdout):
if stream is None:
self.target_stream = sys.stdout
else:
self.target_stream = stream

if self.target_stream == sys.stdout:
ensure_stdout_handles_unicode()

log_level = LOG_LEVELS[level]
logging.basicConfig(level=log_level, format=log_format, stream=stream)
logging.basicConfig(level=log_level, format=log_format,
stream=self.target_stream)
self._is_enabled = True

self._use_json = use_json
Expand All @@ -115,8 +160,8 @@ def enable_logging(self, use_json=False, level='warning', log_format=DEFAULT_MES

# add a JSON formatter to the root logger, which will be used by every logger
if self._use_json:
_root_logger_wrapper.set_formatter(JsonFormatter())
print('[')
_root_logger_wrapper.set_formatter(JsonFormatter(other_logger_has_first_line))
print('[', file=self.target_stream)

def end_logging(self):
"""
Expand All @@ -133,7 +178,7 @@ def end_logging(self):

# end json list
if self._use_json:
print(']')
print(']', file=self.target_stream)
self._use_json = False

def _get_except_hook(self, old_hook):
Expand Down
38 changes: 7 additions & 31 deletions oletools/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def script_main_function(input_file, passwords, crypto_nesting=0, args):
# 2019-05-23 PL: - added DEFAULT_PASSWORDS list
# 2021-05-22 v0.60 PL: - added PowerPoint transparent password
# '/01Hannes Ruescher/01' (issue #627)
# 2019-05-24 CH: - use log_helper

__version__ = '0.60'

Expand All @@ -104,7 +105,6 @@ def script_main_function(input_file, passwords, crypto_nesting=0, args):
from os.path import splitext, isfile
from tempfile import mkstemp
import zipfile
import logging

from olefile import OleFileIO

Expand Down Expand Up @@ -134,44 +134,20 @@ def script_main_function(input_file, passwords, crypto_nesting=0, args):

# === LOGGING =================================================================

# TODO: use log_helper instead

def get_logger(name, level=logging.CRITICAL+1):
"""
Create a suitable logger object for this module.
The goal is not to change settings of the root logger, to avoid getting
other modules' logs on the screen.
If a logger exists with same name, reuse it. (Else it would have duplicate
handlers and messages would be doubled.)
The level is set to CRITICAL+1 by default, to avoid any logging.
"""
# First, test if there is already a logger with the same name, else it
# will generate duplicate messages (due to duplicate handlers):
if name in logging.Logger.manager.loggerDict:
# NOTE: another less intrusive but more "hackish" solution would be to
# use getLogger then test if its effective level is not default.
logger = logging.getLogger(name)
# make sure level is OK:
logger.setLevel(level)
return logger
# get a new logger:
logger = logging.getLogger(name)
# only add a NullHandler for this logger, it is up to the application
# to configure its own logging:
logger.addHandler(logging.NullHandler())
logger.setLevel(level)
return logger

# a global logger object used for debugging:
log = get_logger('crypto')
log = log_helper.get_or_create_silent_logger('crypto')


def enable_logging():
"""
Enable logging for this module (disabled by default).

For use by third-party libraries that import `crypto` as module.

This will set the module-specific logger level to NOTSET, which
means the main application controls the actual logging level.
"""
log.setLevel(logging.NOTSET)
log.setLevel(log_helper.NOTSET)


def is_encrypted(some_file):
Expand Down
18 changes: 6 additions & 12 deletions oletools/mraptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@

#--- IMPORTS ------------------------------------------------------------------

import sys, logging, optparse, re, os
import sys, optparse, re, os

# IMPORTANT: it should be possible to run oletools directly as scripts
# in any directory without installing them with pip or setup.py.
Expand All @@ -90,11 +90,12 @@

from oletools import olevba
from oletools.olevba import TYPE2TAG
from oletools.common.log_helper import log_helper

# === LOGGING =================================================================

# a global logger object used for debugging:
log = olevba.get_logger('mraptor')
log = log_helper.get_or_create_silent_logger('mraptor')


#--- CONSTANTS ----------------------------------------------------------------
Expand Down Expand Up @@ -230,15 +231,7 @@ def main():
"""
Main function, called when olevba is run from the command line
"""
global log
DEFAULT_LOG_LEVEL = "warning" # Default log level
LOG_LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}

usage = 'usage: mraptor [options] <filename> [filename2 ...]'
parser = optparse.OptionParser(usage=usage)
Expand Down Expand Up @@ -272,9 +265,9 @@ def main():
print('MacroRaptor %s - http://decalage.info/python/oletools' % __version__)
print('This is work in progress, please report issues at %s' % URL_ISSUES)

logging.basicConfig(level=LOG_LEVELS[options.loglevel], format='%(levelname)-8s %(message)s')
log_helper.enable_logging(level=options.loglevel)
# enable logging in the modules:
log.setLevel(logging.NOTSET)
olevba.enable_logging()

t = tablestream.TableStream(style=tablestream.TableStyleSlim,
header_row=['Result', 'Flags', 'Type', 'File'],
Expand Down Expand Up @@ -346,6 +339,7 @@ def main():
global_result = result
exitcode = result.exit_code

log_helper.end_logging()
print('')
print('Flags: A=AutoExec, W=Write, X=Execute')
print('Exit code: %d - %s' % (exitcode, global_result.name))
Expand Down
Loading