Repository: mitmproxy/mitmproxy

Issue:
calling `ctx.log` in new thread will raise a RuntimeError exception
##### Steps to reproduce the problem:

1. add an addon for MITMDump
2. start a thread in the addon
3. call `ctx.log.info` in the thread
4. an exception is raised

```
Traceback (most recent call last):
  File "/Users/user/.pyenv/versions/3.7.0/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/Users/user/.pyenv/versions/3.7.0/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/user/pro/python/project/project/plugins/crossdomain_xml/__init__.py", line 39, in _check_crossdomain_xml
    ctx.log.debug(str(e))
  File "/Users/user/pro/python/project/venv/lib/python3.7/site-packages/mitmproxy/log.py", line 29, in debug
    self(txt, "debug")
  File "/Users/user/pro/python/project/venv/lib/python3.7/site-packages/mitmproxy/log.py", line 59, in __call__
    asyncio.get_event_loop().call_soon(
  File "/Users/user/.pyenv/versions/3.7.0/lib/python3.7/asyncio/events.py", line 644, in get_event_loop
    % threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-7'.
```

Except for the main thread, there is no event loop in the other threads, so `asyncio.get_event_loop().call_soon(...)` isn't a good way to send the log message.

##### System information

```
Mitmproxy: 4.0.4
Python:    3.7.0
OpenSSL:   OpenSSL 1.1.0i  14 Aug 2018
Platform:  Darwin-18.2.0-x86_64-i386-64bit
```


calling `ctx.log` in new thread will raise a RuntimeError exception
##### Steps to reproduce the problem:

1. add an addon for MITMDump
2. start a thread in the addon
3. call `ctx.log.info` in the thread
4. an exception is raised

```
Traceback (most recent call last):
  File "/Users/user/.pyenv/versions/3.7.0/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/Users/user/.pyenv/versions/3.7.0/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/user/pro/python/project/project/plugins/crossdomain_xml/__init__.py", line 39, in _check_crossdomain_xml
    ctx.log.debug(str(e))
  File "/Users/user/pro/python/project/venv/lib/python3.7/site-packages/mitmproxy/log.py", line 29, in debug
    self(txt, "debug")
  File "/Users/user/pro/python/project/venv/lib/python3.7/site-packages/mitmproxy/log.py", line 59, in __call__
    asyncio.get_event_loop().call_soon(
  File "/Users/user/.pyenv/versions/3.7.0/lib/python3.7/asyncio/events.py", line 644, in get_event_loop
    % threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-7'.
```

Except for the main thread, there is no event loop in the other threads, so `asyncio.get_event_loop().call_soon(...)` isn't a good way to send the log message.

##### System information

```
Mitmproxy: 4.0.4
Python:    3.7.0
OpenSSL:   OpenSSL 1.1.0i  14 Aug 2018
Platform:  Darwin-18.2.0-x86_64-i386-64bit
```




Discussion:
I am having the same issue, is there any plan to fix this? or a workaround?
I am having the same issue, is there any plan to fix this? or a workaround?

Resolving patch:
diff --git a/mitmproxy/log.py b/mitmproxy/log.py
--- a/mitmproxy/log.py
+++ b/mitmproxy/log.py
@@ -1,4 +1,3 @@
-import asyncio
 from dataclasses import dataclass
 
 from mitmproxy import hooks
@@ -61,7 +60,7 @@ def error(self, txt):
         self(txt, "error")
 
     def __call__(self, text, level="info"):
-        asyncio.get_event_loop().call_soon(
+        self.master.event_loop.call_soon_threadsafe(
             self.master.addons.trigger, AddLogHook(LogEntry(text, level)),
         )
 


Test patch:
diff --git a/test/mitmproxy/addons/test_command_history.py b/test/mitmproxy/addons/test_command_history.py
--- a/test/mitmproxy/addons/test_command_history.py
+++ b/test/mitmproxy/addons/test_command_history.py
@@ -40,13 +40,13 @@ async def test_done_writing_failed(self):
 
     def test_add_command(self):
         ch = command_history.CommandHistory()
+        with taddons.context(ch):
+            ch.add_command('cmd1')
+            ch.add_command('cmd2')
+            assert ch.history == ['cmd1', 'cmd2']
 
-        ch.add_command('cmd1')
-        ch.add_command('cmd2')
-        assert ch.history == ['cmd1', 'cmd2']
-
-        ch.add_command('')
-        assert ch.history == ['cmd1', 'cmd2']
+            ch.add_command('')
+            assert ch.history == ['cmd1', 'cmd2']
 
     @pytest.mark.asyncio
     async def test_add_command_failed(self):