#! /usr/bin/env python3.3 """Logging p4 command requests and responses. Broken out from p4gf_util so that p4gf_p4key can use them without importing p4gf_util. Avoids circular import. """ import logging import pprint import P4 import p4gf_p4msg import p4gf_p4msgid # Common Perforce warnings that occur normally as part of how we interact # with Perforce. Don't clutter the log with WARNING entries unworthy of # human attention. _SQUELCHED_P4_RESULTS = [ p4gf_p4msgid.MsgDm_ExHAVE # "[%argc% - file(s)|File(s)] not on client." } ; , p4gf_p4msgid.MsgDm_ExFILE # "[%argc% - no|No] such file(s)." } ; , p4gf_p4msgid.MsgDm_ExINTEGPERM # "[%argc% - all|All] revision(s) already integrated." } ; ] def run_wrapper(run_func): """Wrap P4.P4.run() to add logging. Then just use P4.run() normally and command and result will be logged. Args may be passed separately or as a list or as some combination: p4.run('user', '-o', 'myron') p4.run(['user', '-o', 'myron']) p4.run('user', ['-o', 'myron']) In some cases, errors and warnings will be logged; see _log_p4_results(). """ def new_run(*args, **kwargs): """Wrapper function for P4.P4.run(). Optional keyword args log_warnings and log_errors specify what logging level to use when recording Perforce warnings and errors. We run some Perforce commands where we expect warnings (especially "no such file(s)" and we really don't need to pollute the log with those expected warnings.) """ nargs = list(args) p4 = nargs[0] cmd = nargs[1:] log_warnings = kwargs.get('log_warnings', logging.WARNING) log_errors = kwargs.get('log_errors', logging.ERROR) _log_p4_request(*cmd) results = run_func(*args) fatal_msg = p4gf_p4msg.first_fatal_error(p4) if fatal_msg: raise RuntimeError("Fatal error encountered: {}".format( p4gf_p4msg.msg_repr(fatal_msg))) _log_p4_results(p4, results, log_warnings=log_warnings, log_errors=log_errors) return results return new_run # install the wrapper on P4.P4: P4.P4.run = run_wrapper(P4.P4.run) def _log_p4_request(*args): """Write p4 cmd request to log, depending on log level.""" # flatten args cmd = [] for a in args: if isinstance(a, list): cmd.extend(a) else: cmd.append(a) logging.getLogger('p4.cmd').debug(' '.join([str(c) for c in cmd])) def _log_p4_results( p4, results , log_warnings = logging.WARNING , log_errors = logging.ERROR ): """Write p4 results to log, depending on log level.""" log_out = logging.getLogger('p4.out') log_out.debug('result ct={}'.format(len(results))) if log_out.isEnabledFor(logging.DEBUG3): pp = pprint.PrettyPrinter() log_out.debug3(pp.pformat(results)) log_err = logging.getLogger('p4.err') log_warn = logging.getLogger('p4.warn') log_msgid = logging.getLogger('p4.msgid') # If we're inside a "with p4.at_exception_level(p4.RAISE_NONE), then # don't pollute the log with expected errors. Log expected errors # at level "debug". _log_errors = log_errors _log_warnings = log_warnings if p4.exception_level == p4.RAISE_NONE: _log_errors = logging.DEBUG _log_warnings = logging.DEBUG # Dump in two groups: first the textual stuff, then all the numeric message # ID stuff. Too hard to read text + numeric when commingled. if ( (p4.errors and log_err .isEnabledFor(_log_errors )) or not (p4.warnings and log_warn .isEnabledFor(_log_warnings))): for m in p4.messages: if m.msgid in _SQUELCHED_P4_RESULTS: continue if p4gf_p4msgid.E_FAILED <= m.severity: log_err.log(_log_errors, str(m)) elif p4gf_p4msgid.E_WARN == m.severity: log_warn.log(_log_warnings, str(m)) if log_msgid.isEnabledFor(logging.DEBUG2): for m in p4.messages: log_msgid.debug2(p4gf_p4msg.msg_repr(m))