def test_is_tracing(self): logger = log.get_logger() original_handlers = logger.handlers logger.handlers = [log._NullHandler()] try: self.assertFalse(log.is_tracing()) logger.addHandler(log.LogBuffer(log.DEBUG)) self.assertFalse(log.is_tracing()) logger.addHandler(log.LogBuffer(log.TRACE)) self.assertTrue(log.is_tracing()) finally: logger.handlers = original_handlers
def send_message(control_file, message, raw=False): """ Sends a message to the control socket, adding the expected formatting for single verses multi-line messages. Neither message type should contain an ending newline (if so it'll be treated as a multi-line message with a blank line at the end). If the message doesn't contain a newline then it's sent as... :: <message>\\r\\n and if it does contain newlines then it's split on ``\\n`` and sent as... :: +<line 1>\\r\\n <line 2>\\r\\n <line 3>\\r\\n .\\r\\n :param file control_file: file derived from the control socket (see the socket's makefile() method for more information) :param str message: message to be sent on the control socket :param bool raw: leaves the message formatting untouched, passing it to the socket as-is :raises: * :class:`stem.SocketError` if a problem arises in using the socket * :class:`stem.SocketClosed` if the socket is known to be shut down """ if not raw: message = send_formatting(message) try: control_file.write(stem.util.str_tools._to_bytes(message)) control_file.flush() if log.is_tracing(): log_message = message.replace('\r\n', '\n').rstrip() msg_div = '\n' if '\n' in log_message else ' ' log.trace('Sent to tor:%s%s' % (msg_div, log_message)) except socket.error as exc: log.info('Failed to send message: %s' % exc) # When sending there doesn't seem to be a reliable method for # distinguishing between failures from a disconnect verses other things. # Just accounting for known disconnection responses. if str(exc) == '[Errno 32] Broken pipe': raise stem.SocketClosed(exc) else: raise stem.SocketError(exc) except AttributeError: # if the control_file has been closed then flush will receive: # AttributeError: 'NoneType' object has no attribute 'sendall' log.info('Failed to send message: file has been closed') raise stem.SocketClosed('file has been closed')
def _log_trace(response): if not log.is_tracing(): return log_message = stem.util.str_tools._to_unicode(response.replace(b'\r\n', b'\n').rstrip()) log_message_lines = log_message.split('\n') if TRUNCATE_LOGS and len(log_message_lines) > TRUNCATE_LOGS: log_message = '\n'.join(log_message_lines[:TRUNCATE_LOGS] + ['... %i more lines...' % (len(log_message_lines) - TRUNCATE_LOGS)]) if len(log_message_lines) > 2: log.trace('Received from tor:\n%s' % log_message) else: log.trace('Received from tor: %s' % log_message.replace('\n', '\\n'))
async def send_message(writer: asyncio.StreamWriter, message: Union[bytes, str], raw: bool = False) -> None: """ Sends a message to the control socket, adding the expected formatting for single verses multi-line messages. Neither message type should contain an ending newline (if so it'll be treated as a multi-line message with a blank line at the end). If the message doesn't contain a newline then it's sent as... :: <message>\\r\\n and if it does contain newlines then it's split on ``\\n`` and sent as... :: +<line 1>\\r\\n <line 2>\\r\\n <line 3>\\r\\n .\\r\\n :param writer: writer object :param message: message to be sent on the control socket :param raw: leaves the message formatting untouched, passing it to the socket as-is :raises: * :class:`stem.SocketError` if a problem arises in using the socket * :class:`stem.SocketClosed` if the socket is known to be shut down """ message = stem.util.str_tools._to_unicode(message) if not raw: message = send_formatting(message) await _write_to_socket(writer, message) if log.is_tracing(): log_message = message.replace('\r\n', '\n').rstrip() msg_div = '\n' if '\n' in log_message else ' ' log.trace('Sent to tor:%s%s' % (msg_div, log_message))
def send_message(control_file, message, raw = False): """ Sends a message to the control socket, adding the expected formatting for single verses multi-line messages. Neither message type should contain an ending newline (if so it'll be treated as a multi-line message with a blank line at the end). If the message doesn't contain a newline then it's sent as... :: <message>\\r\\n and if it does contain newlines then it's split on ``\\n`` and sent as... :: +<line 1>\\r\\n <line 2>\\r\\n <line 3>\\r\\n .\\r\\n :param file control_file: file derived from the control socket (see the socket's makefile() method for more information) :param str message: message to be sent on the control socket :param bool raw: leaves the message formatting untouched, passing it to the socket as-is :raises: * :class:`stem.SocketError` if a problem arises in using the socket * :class:`stem.SocketClosed` if the socket is known to be shut down """ if not raw: message = send_formatting(message) _write_to_socket(control_file, message) if log.is_tracing(): log_message = message.replace('\r\n', '\n').rstrip() msg_div = '\n' if '\n' in log_message else ' ' log.trace('Sent to tor:%s%s' % (msg_div, log_message))
def call(command, default = UNDEFINED, ignore_exit_status = False, timeout = None, cwd = None, env = None): """ call(command, default = UNDEFINED, ignore_exit_status = False) Issues a command in a subprocess, blocking until completion and returning the results. This is not actually ran in a shell so pipes and other shell syntax are not permitted. .. versionchanged:: 1.5.0 Providing additional information upon failure by raising a CallError. This is a subclass of OSError, providing backward compatibility. .. versionchanged:: 1.5.0 Added env argument. .. versionchanged:: 1.6.0 Added timeout and cwd arguments. :param str,list command: command to be issued :param object default: response if the query fails :param bool ignore_exit_status: reports failure if our command's exit status was non-zero :param float timeout: maximum seconds to wait, blocks indefinitely if **None** :param dict env: environment variables :returns: **list** with the lines of output from the command :raises: * **CallError** if this fails and no default was provided * **CallTimeoutError** if the timeout is reached without a default """ # TODO: in stem 2.x return a struct with stdout, stderr, and runtime instead global SYSTEM_CALL_TIME if isinstance(command, str): command_list = command.split(' ') else: command_list = list(map(str, command)) exit_status, runtime, stdout, stderr = None, None, None, None start_time = time.time() try: is_shell_command = command_list[0] in SHELL_COMMANDS process = subprocess.Popen(command_list, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = is_shell_command, cwd = cwd, env = env) if timeout: while process.poll() is None: if time.time() - start_time > timeout: raise CallTimeoutError("Process didn't finish after %0.1f seconds" % timeout, ' '.join(command_list), None, timeout, '', '', timeout) time.sleep(0.001) stdout, stderr = process.communicate() stdout, stderr = stdout.strip(), stderr.strip() runtime = time.time() - start_time log.debug('System call: %s (runtime: %0.2f)' % (command, runtime)) if log.is_tracing(): trace_prefix = 'Received from system (%s)' % command if stdout and stderr: log.trace(trace_prefix + ', stdout:\n%s\nstderr:\n%s' % (stdout, stderr)) elif stdout: log.trace(trace_prefix + ', stdout:\n%s' % stdout) elif stderr: log.trace(trace_prefix + ', stderr:\n%s' % stderr) exit_status = process.poll() if not ignore_exit_status and exit_status != 0: raise OSError('%s returned exit status %i' % (command, exit_status)) if stdout: return stdout.decode('utf-8', 'replace').splitlines() else: return [] except CallTimeoutError: log.debug('System call (timeout): %s (after %0.4fs)' % (command, timeout)) if default != UNDEFINED: return default else: raise except OSError as exc: log.debug('System call (failed): %s (error: %s)' % (command, exc)) if default != UNDEFINED: return default else: raise CallError(str(exc), ' '.join(command_list), exit_status, runtime, stdout, stderr) finally: with SYSTEM_CALL_TIME_LOCK: SYSTEM_CALL_TIME += time.time() - start_time