We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Don't show infraction total outside staff channels.
Currently, when we deliver an infraction, it will show the infraction total in the bot's response.

![image](https://user-images.githubusercontent.com/2098517/66942037-70113c00-f048-11e9-8867-114b2339a1ac.png)

This is a cool feature, but should not happen in public channels. So let's do something about that.

### Infraction total should be allowed in the following channels:
```
#admins        ID: 365960823622991872
#admin-spam    ID: 563594791770914816
#mod-spam      ID: 620607373828030464
#mods          ID: 305126844661760000
#helpers       ID: 385474242440986624
#organisation  ID: 551789653284356126
#defcon        ID: 464469101889454091
```

If the command is called in any other channel, **do not show the infraction total**. This applies to all moderation commands that currently show the total.

If any of the above channels are not currently registered as constants, please create new constants for them. The above list of channels can be stored as a group constant called `STAFF_CHANNELS`. Make use of [YAML node anchors](https://yaml.org/spec/1.2/spec.html#&%20anchor//) when you do this.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bot/api.py`
Content:
```
1 from urllib.parse import quote as quote_url
2 
3 import aiohttp
4 
5 from .constants import Keys, URLs
6 
7 
8 class ResponseCodeError(ValueError):
9     def __init__(self, response: aiohttp.ClientResponse):
10         self.response = response
11 
12 
13 class APIClient:
14     def __init__(self, **kwargs):
15         auth_headers = {
16             'Authorization': f"Token {Keys.site_api}"
17         }
18 
19         if 'headers' in kwargs:
20             kwargs['headers'].update(auth_headers)
21         else:
22             kwargs['headers'] = auth_headers
23 
24         self.session = aiohttp.ClientSession(**kwargs)
25 
26     @staticmethod
27     def _url_for(endpoint: str):
28         return f"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}"
29 
30     def maybe_raise_for_status(self, response: aiohttp.ClientResponse, should_raise: bool):
31         if should_raise and response.status >= 400:
32             raise ResponseCodeError(response=response)
33 
34     async def get(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):
35         async with self.session.get(self._url_for(endpoint), *args, **kwargs) as resp:
36             self.maybe_raise_for_status(resp, raise_for_status)
37             return await resp.json()
38 
39     async def patch(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):
40         async with self.session.patch(self._url_for(endpoint), *args, **kwargs) as resp:
41             self.maybe_raise_for_status(resp, raise_for_status)
42             return await resp.json()
43 
44     async def post(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):
45         async with self.session.post(self._url_for(endpoint), *args, **kwargs) as resp:
46             self.maybe_raise_for_status(resp, raise_for_status)
47             return await resp.json()
48 
49     async def put(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):
50         async with self.session.put(self._url_for(endpoint), *args, **kwargs) as resp:
51             self.maybe_raise_for_status(resp, raise_for_status)
52             return await resp.json()
53 
54     async def delete(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):
55         async with self.session.delete(self._url_for(endpoint), *args, **kwargs) as resp:
56             if resp.status == 204:
57                 return None
58 
59             self.maybe_raise_for_status(resp, raise_for_status)
60             return await resp.json()
61
```

Path: `bot/__main__.py`
Content:
```
1 import asyncio
2 import logging
3 import socket
4 
5 from aiohttp import AsyncResolver, ClientSession, TCPConnector
6 from discord import Game
7 from discord.ext.commands import Bot, when_mentioned_or
8 
9 from bot.api import APIClient
10 from bot.constants import Bot as BotConfig, DEBUG_MODE
11 
12 
13 log = logging.getLogger(__name__)
14 
15 bot = Bot(
16     command_prefix=when_mentioned_or(BotConfig.prefix),
17     activity=Game(name="Commands: !help"),
18     case_insensitive=True,
19     max_messages=10_000
20 )
21 
22 # Global aiohttp session for all cogs
23 # - Uses asyncio for DNS resolution instead of threads, so we don't spam threads
24 # - Uses AF_INET as its socket family to prevent https related problems both locally and in prod.
25 bot.http_session = ClientSession(
26     connector=TCPConnector(
27         resolver=AsyncResolver(),
28         family=socket.AF_INET,
29     )
30 )
31 bot.api_client = APIClient(loop=asyncio.get_event_loop())
32 
33 # Internal/debug
34 bot.load_extension("bot.cogs.error_handler")
35 bot.load_extension("bot.cogs.filtering")
36 bot.load_extension("bot.cogs.logging")
37 bot.load_extension("bot.cogs.modlog")
38 bot.load_extension("bot.cogs.security")
39 
40 # Commands, etc
41 bot.load_extension("bot.cogs.antispam")
42 bot.load_extension("bot.cogs.bot")
43 bot.load_extension("bot.cogs.clean")
44 bot.load_extension("bot.cogs.cogs")
45 bot.load_extension("bot.cogs.help")
46 
47 # Only load this in production
48 if not DEBUG_MODE:
49     bot.load_extension("bot.cogs.doc")
50     bot.load_extension("bot.cogs.verification")
51 
52 # Feature cogs
53 bot.load_extension("bot.cogs.alias")
54 bot.load_extension("bot.cogs.defcon")
55 bot.load_extension("bot.cogs.deployment")
56 bot.load_extension("bot.cogs.eval")
57 bot.load_extension("bot.cogs.free")
58 bot.load_extension("bot.cogs.fun")
59 bot.load_extension("bot.cogs.information")
60 bot.load_extension("bot.cogs.jams")
61 bot.load_extension("bot.cogs.moderation")
62 bot.load_extension("bot.cogs.off_topic_names")
63 bot.load_extension("bot.cogs.reddit")
64 bot.load_extension("bot.cogs.reminders")
65 bot.load_extension("bot.cogs.site")
66 bot.load_extension("bot.cogs.snekbox")
67 bot.load_extension("bot.cogs.superstarify")
68 bot.load_extension("bot.cogs.sync")
69 bot.load_extension("bot.cogs.tags")
70 bot.load_extension("bot.cogs.token_remover")
71 bot.load_extension("bot.cogs.utils")
72 bot.load_extension("bot.cogs.watchchannels")
73 bot.load_extension("bot.cogs.wolfram")
74 
75 bot.run(BotConfig.token)
76 
77 bot.http_session.close()  # Close the aiohttp session when the bot finishes running
78
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bot/__main__.py b/bot/__main__.py
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -6,11 +6,11 @@
 from discord import Game
 from discord.ext.commands import Bot, when_mentioned_or
 
-from bot.api import APIClient
+from bot.api import APIClient, APILoggingHandler
 from bot.constants import Bot as BotConfig, DEBUG_MODE
 
 
-log = logging.getLogger(__name__)
+log = logging.getLogger('bot')
 
 bot = Bot(
     command_prefix=when_mentioned_or(BotConfig.prefix),
@@ -29,6 +29,7 @@
     )
 )
 bot.api_client = APIClient(loop=asyncio.get_event_loop())
+log.addHandler(APILoggingHandler(bot.api_client))
 
 # Internal/debug
 bot.load_extension("bot.cogs.error_handler")
diff --git a/bot/api.py b/bot/api.py
--- a/bot/api.py
+++ b/bot/api.py
@@ -1,9 +1,13 @@
+import asyncio
+import logging
 from urllib.parse import quote as quote_url
 
 import aiohttp
 
 from .constants import Keys, URLs
 
+log = logging.getLogger(__name__)
+
 
 class ResponseCodeError(ValueError):
     def __init__(self, response: aiohttp.ClientResponse):
@@ -58,3 +62,76 @@
 
             self.maybe_raise_for_status(resp, raise_for_status)
             return await resp.json()
+
+
+def loop_is_running() -> bool:
+    # asyncio does not have a way to say "call this when the event
+    # loop is running", see e.g. `callWhenRunning` from twisted.
+
+    try:
+        asyncio.get_running_loop()
+    except RuntimeError:
+        return False
+    return True
+
+
+class APILoggingHandler(logging.StreamHandler):
+    def __init__(self, client: APIClient):
+        logging.StreamHandler.__init__(self)
+        self.client = client
+
+        # internal batch of shipoff tasks that must not be scheduled
+        # on the event loop yet - scheduled when the event loop is ready.
+        self.queue = []
+
+    async def ship_off(self, payload: dict):
+        try:
+            await self.client.post('logs', json=payload)
+        except ResponseCodeError as err:
+            log.warning(
+                "Cannot send logging record to the site, got code %d.",
+                err.response.status,
+                extra={'via_handler': True}
+            )
+        except Exception as err:
+            log.warning(
+                "Cannot send logging record to the site: %r",
+                err,
+                extra={'via_handler': True}
+            )
+
+    def emit(self, record: logging.LogRecord):
+        # Ignore logging messages which are sent by this logging handler
+        # itself. This is required because if we were to not ignore
+        # messages emitted by this handler, we would infinitely recurse
+        # back down into this logging handler, making the reactor run
+        # like crazy, and eventually OOM something. Let's not do that...
+        if not record.__dict__.get('via_handler'):
+            payload = {
+                'application': 'bot',
+                'logger_name': record.name,
+                'level': record.levelname.lower(),
+                'module': record.module,
+                'line': record.lineno,
+                'message': self.format(record)
+            }
+
+            task = self.ship_off(payload)
+            if not loop_is_running():
+                self.queue.append(task)
+            else:
+                asyncio.create_task(task)
+                self.schedule_queued_tasks()
+
+    def schedule_queued_tasks(self):
+        for task in self.queue:
+            asyncio.create_task(task)
+
+        if self.queue:
+            log.debug(
+                "Scheduled %d pending logging tasks.",
+                len(self.queue),
+                extra={'via_handler': True}
+            )
+
+        self.queue.clear()

{"golden_diff": "diff --git a/bot/__main__.py b/bot/__main__.py\n--- a/bot/__main__.py\n+++ b/bot/__main__.py\n@@ -6,11 +6,11 @@\n from discord import Game\n from discord.ext.commands import Bot, when_mentioned_or\n \n-from bot.api import APIClient\n+from bot.api import APIClient, APILoggingHandler\n from bot.constants import Bot as BotConfig, DEBUG_MODE\n \n \n-log = logging.getLogger(__name__)\n+log = logging.getLogger('bot')\n \n bot = Bot(\n     command_prefix=when_mentioned_or(BotConfig.prefix),\n@@ -29,6 +29,7 @@\n     )\n )\n bot.api_client = APIClient(loop=asyncio.get_event_loop())\n+log.addHandler(APILoggingHandler(bot.api_client))\n \n # Internal/debug\n bot.load_extension(\"bot.cogs.error_handler\")\ndiff --git a/bot/api.py b/bot/api.py\n--- a/bot/api.py\n+++ b/bot/api.py\n@@ -1,9 +1,13 @@\n+import asyncio\n+import logging\n from urllib.parse import quote as quote_url\n \n import aiohttp\n \n from .constants import Keys, URLs\n \n+log = logging.getLogger(__name__)\n+\n \n class ResponseCodeError(ValueError):\n     def __init__(self, response: aiohttp.ClientResponse):\n@@ -58,3 +62,76 @@\n \n             self.maybe_raise_for_status(resp, raise_for_status)\n             return await resp.json()\n+\n+\n+def loop_is_running() -> bool:\n+    # asyncio does not have a way to say \"call this when the event\n+    # loop is running\", see e.g. `callWhenRunning` from twisted.\n+\n+    try:\n+        asyncio.get_running_loop()\n+    except RuntimeError:\n+        return False\n+    return True\n+\n+\n+class APILoggingHandler(logging.StreamHandler):\n+    def __init__(self, client: APIClient):\n+        logging.StreamHandler.__init__(self)\n+        self.client = client\n+\n+        # internal batch of shipoff tasks that must not be scheduled\n+        # on the event loop yet - scheduled when the event loop is ready.\n+        self.queue = []\n+\n+    async def ship_off(self, payload: dict):\n+        try:\n+            await self.client.post('logs', json=payload)\n+        except ResponseCodeError as err:\n+            log.warning(\n+                \"Cannot send logging record to the site, got code %d.\",\n+                err.response.status,\n+                extra={'via_handler': True}\n+            )\n+        except Exception as err:\n+            log.warning(\n+                \"Cannot send logging record to the site: %r\",\n+                err,\n+                extra={'via_handler': True}\n+            )\n+\n+    def emit(self, record: logging.LogRecord):\n+        # Ignore logging messages which are sent by this logging handler\n+        # itself. This is required because if we were to not ignore\n+        # messages emitted by this handler, we would infinitely recurse\n+        # back down into this logging handler, making the reactor run\n+        # like crazy, and eventually OOM something. Let's not do that...\n+        if not record.__dict__.get('via_handler'):\n+            payload = {\n+                'application': 'bot',\n+                'logger_name': record.name,\n+                'level': record.levelname.lower(),\n+                'module': record.module,\n+                'line': record.lineno,\n+                'message': self.format(record)\n+            }\n+\n+            task = self.ship_off(payload)\n+            if not loop_is_running():\n+                self.queue.append(task)\n+            else:\n+                asyncio.create_task(task)\n+                self.schedule_queued_tasks()\n+\n+    def schedule_queued_tasks(self):\n+        for task in self.queue:\n+            asyncio.create_task(task)\n+\n+        if self.queue:\n+            log.debug(\n+                \"Scheduled %d pending logging tasks.\",\n+                len(self.queue),\n+                extra={'via_handler': True}\n+            )\n+\n+        self.queue.clear()\n", "issue": "Don't show infraction total outside staff channels.\nCurrently, when we deliver an infraction, it will show the infraction total in the bot's response.\r\n\r\n![image](https://user-images.githubusercontent.com/2098517/66942037-70113c00-f048-11e9-8867-114b2339a1ac.png)\r\n\r\nThis is a cool feature, but should not happen in public channels. So let's do something about that.\r\n\r\n### Infraction total should be allowed in the following channels:\r\n```\r\n#admins        ID: 365960823622991872\r\n#admin-spam    ID: 563594791770914816\r\n#mod-spam      ID: 620607373828030464\r\n#mods          ID: 305126844661760000\r\n#helpers       ID: 385474242440986624\r\n#organisation  ID: 551789653284356126\r\n#defcon        ID: 464469101889454091\r\n```\r\n\r\nIf the command is called in any other channel, **do not show the infraction total**. This applies to all moderation commands that currently show the total.\r\n\r\nIf any of the above channels are not currently registered as constants, please create new constants for them. The above list of channels can be stored as a group constant called `STAFF_CHANNELS`. Make use of [YAML node anchors](https://yaml.org/spec/1.2/spec.html#&%20anchor//) when you do this.\r\n\n", "before_files": [{"content": "from urllib.parse import quote as quote_url\n\nimport aiohttp\n\nfrom .constants import Keys, URLs\n\n\nclass ResponseCodeError(ValueError):\n    def __init__(self, response: aiohttp.ClientResponse):\n        self.response = response\n\n\nclass APIClient:\n    def __init__(self, **kwargs):\n        auth_headers = {\n            'Authorization': f\"Token {Keys.site_api}\"\n        }\n\n        if 'headers' in kwargs:\n            kwargs['headers'].update(auth_headers)\n        else:\n            kwargs['headers'] = auth_headers\n\n        self.session = aiohttp.ClientSession(**kwargs)\n\n    @staticmethod\n    def _url_for(endpoint: str):\n        return f\"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}\"\n\n    def maybe_raise_for_status(self, response: aiohttp.ClientResponse, should_raise: bool):\n        if should_raise and response.status >= 400:\n            raise ResponseCodeError(response=response)\n\n    async def get(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.get(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def patch(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.patch(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def post(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.post(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def put(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.put(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def delete(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.delete(self._url_for(endpoint), *args, **kwargs) as resp:\n            if resp.status == 204:\n                return None\n\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n", "path": "bot/api.py"}, {"content": "import asyncio\nimport logging\nimport socket\n\nfrom aiohttp import AsyncResolver, ClientSession, TCPConnector\nfrom discord import Game\nfrom discord.ext.commands import Bot, when_mentioned_or\n\nfrom bot.api import APIClient\nfrom bot.constants import Bot as BotConfig, DEBUG_MODE\n\n\nlog = logging.getLogger(__name__)\n\nbot = Bot(\n    command_prefix=when_mentioned_or(BotConfig.prefix),\n    activity=Game(name=\"Commands: !help\"),\n    case_insensitive=True,\n    max_messages=10_000\n)\n\n# Global aiohttp session for all cogs\n# - Uses asyncio for DNS resolution instead of threads, so we don't spam threads\n# - Uses AF_INET as its socket family to prevent https related problems both locally and in prod.\nbot.http_session = ClientSession(\n    connector=TCPConnector(\n        resolver=AsyncResolver(),\n        family=socket.AF_INET,\n    )\n)\nbot.api_client = APIClient(loop=asyncio.get_event_loop())\n\n# Internal/debug\nbot.load_extension(\"bot.cogs.error_handler\")\nbot.load_extension(\"bot.cogs.filtering\")\nbot.load_extension(\"bot.cogs.logging\")\nbot.load_extension(\"bot.cogs.modlog\")\nbot.load_extension(\"bot.cogs.security\")\n\n# Commands, etc\nbot.load_extension(\"bot.cogs.antispam\")\nbot.load_extension(\"bot.cogs.bot\")\nbot.load_extension(\"bot.cogs.clean\")\nbot.load_extension(\"bot.cogs.cogs\")\nbot.load_extension(\"bot.cogs.help\")\n\n# Only load this in production\nif not DEBUG_MODE:\n    bot.load_extension(\"bot.cogs.doc\")\n    bot.load_extension(\"bot.cogs.verification\")\n\n# Feature cogs\nbot.load_extension(\"bot.cogs.alias\")\nbot.load_extension(\"bot.cogs.defcon\")\nbot.load_extension(\"bot.cogs.deployment\")\nbot.load_extension(\"bot.cogs.eval\")\nbot.load_extension(\"bot.cogs.free\")\nbot.load_extension(\"bot.cogs.fun\")\nbot.load_extension(\"bot.cogs.information\")\nbot.load_extension(\"bot.cogs.jams\")\nbot.load_extension(\"bot.cogs.moderation\")\nbot.load_extension(\"bot.cogs.off_topic_names\")\nbot.load_extension(\"bot.cogs.reddit\")\nbot.load_extension(\"bot.cogs.reminders\")\nbot.load_extension(\"bot.cogs.site\")\nbot.load_extension(\"bot.cogs.snekbox\")\nbot.load_extension(\"bot.cogs.superstarify\")\nbot.load_extension(\"bot.cogs.sync\")\nbot.load_extension(\"bot.cogs.tags\")\nbot.load_extension(\"bot.cogs.token_remover\")\nbot.load_extension(\"bot.cogs.utils\")\nbot.load_extension(\"bot.cogs.watchchannels\")\nbot.load_extension(\"bot.cogs.wolfram\")\n\nbot.run(BotConfig.token)\n\nbot.http_session.close()  # Close the aiohttp session when the bot finishes running\n", "path": "bot/__main__.py"}], "after_files": [{"content": "import asyncio\nimport logging\nfrom urllib.parse import quote as quote_url\n\nimport aiohttp\n\nfrom .constants import Keys, URLs\n\nlog = logging.getLogger(__name__)\n\n\nclass ResponseCodeError(ValueError):\n    def __init__(self, response: aiohttp.ClientResponse):\n        self.response = response\n\n\nclass APIClient:\n    def __init__(self, **kwargs):\n        auth_headers = {\n            'Authorization': f\"Token {Keys.site_api}\"\n        }\n\n        if 'headers' in kwargs:\n            kwargs['headers'].update(auth_headers)\n        else:\n            kwargs['headers'] = auth_headers\n\n        self.session = aiohttp.ClientSession(**kwargs)\n\n    @staticmethod\n    def _url_for(endpoint: str):\n        return f\"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}\"\n\n    def maybe_raise_for_status(self, response: aiohttp.ClientResponse, should_raise: bool):\n        if should_raise and response.status >= 400:\n            raise ResponseCodeError(response=response)\n\n    async def get(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.get(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def patch(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.patch(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def post(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.post(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def put(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.put(self._url_for(endpoint), *args, **kwargs) as resp:\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n    async def delete(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs):\n        async with self.session.delete(self._url_for(endpoint), *args, **kwargs) as resp:\n            if resp.status == 204:\n                return None\n\n            self.maybe_raise_for_status(resp, raise_for_status)\n            return await resp.json()\n\n\ndef loop_is_running() -> bool:\n    # asyncio does not have a way to say \"call this when the event\n    # loop is running\", see e.g. `callWhenRunning` from twisted.\n\n    try:\n        asyncio.get_running_loop()\n    except RuntimeError:\n        return False\n    return True\n\n\nclass APILoggingHandler(logging.StreamHandler):\n    def __init__(self, client: APIClient):\n        logging.StreamHandler.__init__(self)\n        self.client = client\n\n        # internal batch of shipoff tasks that must not be scheduled\n        # on the event loop yet - scheduled when the event loop is ready.\n        self.queue = []\n\n    async def ship_off(self, payload: dict):\n        try:\n            await self.client.post('logs', json=payload)\n        except ResponseCodeError as err:\n            log.warning(\n                \"Cannot send logging record to the site, got code %d.\",\n                err.response.status,\n                extra={'via_handler': True}\n            )\n        except Exception as err:\n            log.warning(\n                \"Cannot send logging record to the site: %r\",\n                err,\n                extra={'via_handler': True}\n            )\n\n    def emit(self, record: logging.LogRecord):\n        # Ignore logging messages which are sent by this logging handler\n        # itself. This is required because if we were to not ignore\n        # messages emitted by this handler, we would infinitely recurse\n        # back down into this logging handler, making the reactor run\n        # like crazy, and eventually OOM something. Let's not do that...\n        if not record.__dict__.get('via_handler'):\n            payload = {\n                'application': 'bot',\n                'logger_name': record.name,\n                'level': record.levelname.lower(),\n                'module': record.module,\n                'line': record.lineno,\n                'message': self.format(record)\n            }\n\n            task = self.ship_off(payload)\n            if not loop_is_running():\n                self.queue.append(task)\n            else:\n                asyncio.create_task(task)\n                self.schedule_queued_tasks()\n\n    def schedule_queued_tasks(self):\n        for task in self.queue:\n            asyncio.create_task(task)\n\n        if self.queue:\n            log.debug(\n                \"Scheduled %d pending logging tasks.\",\n                len(self.queue),\n                extra={'via_handler': True}\n            )\n\n        self.queue.clear()\n", "path": "bot/api.py"}, {"content": "import asyncio\nimport logging\nimport socket\n\nfrom aiohttp import AsyncResolver, ClientSession, TCPConnector\nfrom discord import Game\nfrom discord.ext.commands import Bot, when_mentioned_or\n\nfrom bot.api import APIClient, APILoggingHandler\nfrom bot.constants import Bot as BotConfig, DEBUG_MODE\n\n\nlog = logging.getLogger('bot')\n\nbot = Bot(\n    command_prefix=when_mentioned_or(BotConfig.prefix),\n    activity=Game(name=\"Commands: !help\"),\n    case_insensitive=True,\n    max_messages=10_000\n)\n\n# Global aiohttp session for all cogs\n# - Uses asyncio for DNS resolution instead of threads, so we don't spam threads\n# - Uses AF_INET as its socket family to prevent https related problems both locally and in prod.\nbot.http_session = ClientSession(\n    connector=TCPConnector(\n        resolver=AsyncResolver(),\n        family=socket.AF_INET,\n    )\n)\nbot.api_client = APIClient(loop=asyncio.get_event_loop())\nlog.addHandler(APILoggingHandler(bot.api_client))\n\n# Internal/debug\nbot.load_extension(\"bot.cogs.error_handler\")\nbot.load_extension(\"bot.cogs.filtering\")\nbot.load_extension(\"bot.cogs.logging\")\nbot.load_extension(\"bot.cogs.modlog\")\nbot.load_extension(\"bot.cogs.security\")\n\n# Commands, etc\nbot.load_extension(\"bot.cogs.antispam\")\nbot.load_extension(\"bot.cogs.bot\")\nbot.load_extension(\"bot.cogs.clean\")\nbot.load_extension(\"bot.cogs.cogs\")\nbot.load_extension(\"bot.cogs.help\")\n\n# Only load this in production\nif not DEBUG_MODE:\n    bot.load_extension(\"bot.cogs.doc\")\n    bot.load_extension(\"bot.cogs.verification\")\n\n# Feature cogs\nbot.load_extension(\"bot.cogs.alias\")\nbot.load_extension(\"bot.cogs.defcon\")\nbot.load_extension(\"bot.cogs.deployment\")\nbot.load_extension(\"bot.cogs.eval\")\nbot.load_extension(\"bot.cogs.free\")\nbot.load_extension(\"bot.cogs.fun\")\nbot.load_extension(\"bot.cogs.information\")\nbot.load_extension(\"bot.cogs.jams\")\nbot.load_extension(\"bot.cogs.moderation\")\nbot.load_extension(\"bot.cogs.off_topic_names\")\nbot.load_extension(\"bot.cogs.reddit\")\nbot.load_extension(\"bot.cogs.reminders\")\nbot.load_extension(\"bot.cogs.site\")\nbot.load_extension(\"bot.cogs.snekbox\")\nbot.load_extension(\"bot.cogs.superstarify\")\nbot.load_extension(\"bot.cogs.sync\")\nbot.load_extension(\"bot.cogs.tags\")\nbot.load_extension(\"bot.cogs.token_remover\")\nbot.load_extension(\"bot.cogs.utils\")\nbot.load_extension(\"bot.cogs.watchchannels\")\nbot.load_extension(\"bot.cogs.wolfram\")\n\nbot.run(BotConfig.token)\n\nbot.http_session.close()  # Close the aiohttp session when the bot finishes running\n", "path": "bot/__main__.py"}]}