Repository: mitmproxy/mitmproxy

Issue:
Handle print() statements for inline scripts
print() statements in inline scripts should be suppressed, and produce into ctx.log.warn() calls instead. 



Discussion:
Doing a simple search at github of `print(` a lot of candidates appear. To understand what you propose to do, tell me, Is [this](https://github.com/mitmproxy/mitmproxy/blob/6792cc1de90b02b89da9ae80704ea237c5486e66/mitmproxy/tools/main.py#L6) a good candidate? It is a `print` to `sys.stderr`. And what about [this](https://github.com/mitmproxy/mitmproxy/blob/711078ba3f63257df745bb3edd80a85717e94b20/mitmproxy/utils/version_check.py#L19)? It seems a legit use of print, although it could be write as a file write, given the file object is an argument.

I think a better question I could ask is: Which are those inline scripts?
If I may, I could add another suggestion/question here. Why not use python's built-in logger instead of print? That way the log level can be changed as well
How about something like http://stackoverflow.com/questions/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-py :question: 
I think this issue is about redirecting anything that gets sent to `sys.stdout` to `ctx.log.warn`. This would probably look roughly like to this:

```python
class StreamLog:
    def __init__(self, log: Callable):
        self.log = log
    def write(self, buf):
        self.log(buf)

stdout_replacement = StreamLog(ctx.log.warn)
with contextlib.redirect_stdout(stdout_replacement):
    # invoke addon
```

A possible integration between Python's built-in logger and `ctx.log` is out of scope for this issue.

Resolving patch:
diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py
--- a/mitmproxy/addons/script.py
+++ b/mitmproxy/addons/script.py
@@ -65,14 +65,28 @@ def cut_traceback(tb, func_name):
         return tb
 
 
+class StreamLog:
+    """
+        A class for redirecting output using contextlib.
+    """
+    def __init__(self, log):
+        self.log = log
+
+    def write(self, buf):
+        if buf.strip():
+            self.log(buf)
+
+
 @contextlib.contextmanager
 def scriptenv(path, args):
     oldargs = sys.argv
     sys.argv = [path] + args
     script_dir = os.path.dirname(os.path.abspath(path))
     sys.path.append(script_dir)
+    stdout_replacement = StreamLog(ctx.log.warn)
     try:
-        yield
+        with contextlib.redirect_stdout(stdout_replacement):
+            yield
     except SystemExit as v:
         ctx.log.error("Script exited with code %s" % v.code)
     except Exception:


Test patch:
diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py
--- a/test/mitmproxy/addons/test_script.py
+++ b/test/mitmproxy/addons/test_script.py
@@ -5,6 +5,7 @@
 import watchdog.events
 import pytest
 
+from unittest import mock
 from mitmproxy.test import tflow
 from mitmproxy.test import tutils
 from mitmproxy.test import taddons
@@ -97,12 +98,26 @@ def test_parse_windows(self):
 
 
 def test_load_script():
-    ns = script.load_script(
-        tutils.test_data.path(
-            "mitmproxy/data/addonscripts/recorder.py"
-        ), []
-    )
-    assert ns.start
+    with taddons.context():
+        ns = script.load_script(
+            tutils.test_data.path(
+                "mitmproxy/data/addonscripts/recorder.py"
+            ), []
+        )
+        assert ns.start
+
+
+def test_script_print_stdout():
+    with taddons.context() as tctx:
+        with mock.patch('mitmproxy.ctx.log.warn') as mock_warn:
+            with script.scriptenv("path", []):
+                ns = script.load_script(
+                    tutils.test_data.path(
+                        "mitmproxy/data/addonscripts/print.py"
+                    ), []
+                )
+                ns.start(tctx.options)
+        mock_warn.assert_called_once_with("stdoutprint")
 
 
 class TestScript:
diff --git a/test/mitmproxy/data/addonscripts/print.py b/test/mitmproxy/data/addonscripts/print.py
new file mode 100644
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/print.py
@@ -0,0 +1,2 @@
+def start(opts):
+    print("stdoutprint")