We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Django log levels are ignored by core agent
No matter what I did to the Django log levels, I kept getting this message when running Django management tasks:

```
[2020-04-02T09:36:06][core_agent][INFO] Initializing logger with log level: Info
```

This is super annoying, because this causes programs in my crontab to start sending out lots of e-mails because previously quiet tasks would now start producing output.

After a lot of trial and error, I figured out that you can set `SCOUT_LOG_LEVEL` in Django settings to suppress this message.

This really should be fixed so the agent also honors the Django logging settings, or at least documented under the logging section in the Python documentation. It's really non-obvious that this the agent is a separate thing which ignores the Django logging settings.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/core_agent_manager.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import hashlib
5 import json
6 import logging
7 import os
8 import subprocess
9 import tarfile
10 import time
11 
12 from urllib3.exceptions import HTTPError
13 
14 from scout_apm.compat import urllib3_cert_pool_manager
15 from scout_apm.core.config import scout_config
16 
17 logger = logging.getLogger(__name__)
18 
19 
20 class CoreAgentManager(object):
21     def __init__(self):
22         self.core_agent_bin_path = None
23         self.core_agent_bin_version = None
24         self.core_agent_dir = "{}/{}".format(
25             scout_config.value("core_agent_dir"),
26             scout_config.value("core_agent_full_name"),
27         )
28         self.downloader = CoreAgentDownloader(
29             self.core_agent_dir, scout_config.value("core_agent_full_name")
30         )
31 
32     def launch(self):
33         if not scout_config.value("core_agent_launch"):
34             logger.debug(
35                 "Not attempting to launch Core Agent "
36                 "due to 'core_agent_launch' setting."
37             )
38             return False
39 
40         if not self.verify():
41             if not scout_config.value("core_agent_download"):
42                 logger.debug(
43                     "Not attempting to download Core Agent due "
44                     "to 'core_agent_download' setting."
45                 )
46                 return False
47 
48             self.download()
49 
50             if not self.verify():
51                 logger.debug("Failed to verify Core Agent. Not launching Core Agent.")
52                 return False
53 
54         return self.run()
55 
56     def download(self):
57         self.downloader.download()
58 
59     def run(self):
60         try:
61             subprocess.check_call(
62                 (
63                     self.agent_binary()
64                     + self.daemonize_flag()
65                     + self.log_level()
66                     + self.log_file()
67                     + self.config_file()
68                     + self.socket_path()
69                 ),
70                 close_fds=True,
71             )
72         except Exception:
73             # TODO detect failure of launch properly
74             logger.exception("Error running Core Agent")
75             return False
76         return True
77 
78     def agent_binary(self):
79         return [self.core_agent_bin_path, "start"]
80 
81     def daemonize_flag(self):
82         return ["--daemonize", "true"]
83 
84     def socket_path(self):
85         # Old deprecated name "socket_path"
86         socket_path = scout_config.value("socket_path")
87         if socket_path is None:
88             socket_path = scout_config.value("core_agent_socket_path")
89         return ["--socket", socket_path]
90 
91     def log_level(self):
92         # Old deprecated name "log_level"
93         log_level = scout_config.value("log_level")
94         if log_level is None:
95             log_level = scout_config.value("core_agent_log_level")
96         return ["--log-level", log_level]
97 
98     def log_file(self):
99         # Old deprecated name "log_file"
100         path = scout_config.value("log_file")
101         if path is None:
102             path = scout_config.value("core_agent_log_file")
103 
104         if path is not None:
105             return ["--log-file", path]
106         else:
107             return []
108 
109     def config_file(self):
110         # Old deprecated name "config_file"
111         path = scout_config.value("config_file")
112         if path is None:
113             path = scout_config.value("core_agent_config_file")
114 
115         if path is not None:
116             return ["--config-file", path]
117         else:
118             return []
119 
120     def verify(self):
121         manifest = CoreAgentManifest(self.core_agent_dir + "/manifest.json")
122         if not manifest.is_valid():
123             logger.debug(
124                 "Core Agent verification failed: CoreAgentManifest is not valid."
125             )
126             self.core_agent_bin_path = None
127             self.core_agent_bin_version = None
128             return False
129 
130         bin_path = self.core_agent_dir + "/" + manifest.bin_name
131         if sha256_digest(bin_path) == manifest.sha256:
132             self.core_agent_bin_path = bin_path
133             self.core_agent_bin_version = manifest.bin_version
134             return True
135         else:
136             logger.debug("Core Agent verification failed: SHA mismatch.")
137             self.core_agent_bin_path = None
138             self.core_agent_bin_version = None
139             return False
140 
141 
142 class CoreAgentDownloader(object):
143     def __init__(self, download_destination, core_agent_full_name):
144         self.stale_download_secs = 120
145         self.destination = download_destination
146         self.core_agent_full_name = core_agent_full_name
147         self.package_location = self.destination + "/{}.tgz".format(
148             self.core_agent_full_name
149         )
150         self.download_lock_path = self.destination + "/download.lock"
151         self.download_lock_fd = None
152 
153     def download(self):
154         self.create_core_agent_dir()
155         self.obtain_download_lock()
156         if self.download_lock_fd is not None:
157             try:
158                 downloaded = self.download_package()
159                 if downloaded:
160                     self.untar()
161             except (OSError, HTTPError):
162                 logger.exception("Exception raised while downloading Core Agent")
163             finally:
164                 self.release_download_lock()
165 
166     def create_core_agent_dir(self):
167         try:
168             os.makedirs(self.destination, scout_config.core_agent_permissions())
169         except OSError:
170             pass
171 
172     def obtain_download_lock(self):
173         self.clean_stale_download_lock()
174         try:
175             self.download_lock_fd = os.open(
176                 self.download_lock_path,
177                 os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,
178             )
179         except OSError as exc:
180             logger.debug(
181                 "Could not obtain download lock on %s",
182                 self.download_lock_path,
183                 exc_info=exc,
184             )
185             self.download_lock_fd = None
186 
187     def clean_stale_download_lock(self):
188         try:
189             delta = time.time() - os.stat(self.download_lock_path).st_ctime
190             if delta > self.stale_download_secs:
191                 logger.debug("Clearing stale download lock file.")
192                 os.unlink(self.download_lock_path)
193         except OSError:
194             pass
195 
196     def release_download_lock(self):
197         if self.download_lock_fd is not None:
198             os.unlink(self.download_lock_path)
199             os.close(self.download_lock_fd)
200 
201     def download_package(self):
202         full_url = self.full_url()
203         logger.debug("Downloading: %s to %s", full_url, self.package_location)
204         http = urllib3_cert_pool_manager()
205         response = http.request(
206             "GET", full_url, preload_content=False, timeout=10.0, retries=3
207         )
208         try:
209             if response.status != 200:
210                 return False
211             with open(self.package_location, "wb") as fp:
212                 for chunk in response.stream():
213                     fp.write(chunk)
214         finally:
215             response.release_conn()
216         return True
217 
218     def untar(self):
219         t = tarfile.open(self.package_location, "r")
220         t.extractall(self.destination)
221 
222     def full_url(self):
223         return "{root_url}/{core_agent_full_name}.tgz".format(
224             root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name
225         )
226 
227     def root_url(self):
228         return scout_config.value("download_url")
229 
230 
231 class CoreAgentManifest(object):
232     def __init__(self, path):
233         self.manifest_path = path
234         self.bin_name = None
235         self.bin_version = None
236         self.sha256 = None
237         self.valid = False
238         try:
239             self.parse()
240         # noqa for this issue: https://github.com/PyCQA/flake8-bugbear/issues/110
241         except (ValueError, TypeError, OSError, IOError) as exc:  # noqa: B014
242             logger.debug("Error parsing Core Agent Manifest", exc_info=exc)
243 
244     def parse(self):
245         logger.debug("Parsing Core Agent manifest path: %s", self.manifest_path)
246         with open(self.manifest_path) as manifest_file:
247             self.raw = manifest_file.read()
248             self.json = json.loads(self.raw)
249             self.version = self.json["version"]
250             self.bin_version = self.json["core_agent_version"]
251             self.bin_name = self.json["core_agent_binary"]
252             self.sha256 = self.json["core_agent_binary_sha256"]
253             self.valid = True
254             logger.debug("Core Agent manifest json: %s", self.json)
255 
256     def is_valid(self):
257         return self.valid
258 
259 
260 def sha256_digest(filename, block_size=65536):
261     try:
262         sha256 = hashlib.sha256()
263         with open(filename, "rb") as f:
264             for block in iter(lambda: f.read(block_size), b""):
265                 sha256.update(block)
266         return sha256.hexdigest()
267     except OSError as exc:
268         logger.debug("Error on digest", exc_info=exc)
269         return None
270
```
--- 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/src/scout_apm/core/core_agent_manager.py b/src/scout_apm/core/core_agent_manager.py
--- a/src/scout_apm/core/core_agent_manager.py
+++ b/src/scout_apm/core/core_agent_manager.py
@@ -58,17 +58,19 @@
 
     def run(self):
         try:
-            subprocess.check_call(
-                (
-                    self.agent_binary()
-                    + self.daemonize_flag()
-                    + self.log_level()
-                    + self.log_file()
-                    + self.config_file()
-                    + self.socket_path()
-                ),
-                close_fds=True,
-            )
+            with open(os.devnull) as devnull:
+                subprocess.check_call(
+                    (
+                        self.agent_binary()
+                        + self.daemonize_flag()
+                        + self.log_level()
+                        + self.log_file()
+                        + self.config_file()
+                        + self.socket_path()
+                    ),
+                    close_fds=True,
+                    stdout=devnull,
+                )
         except Exception:
             # TODO detect failure of launch properly
             logger.exception("Error running Core Agent")

{"golden_diff": "diff --git a/src/scout_apm/core/core_agent_manager.py b/src/scout_apm/core/core_agent_manager.py\n--- a/src/scout_apm/core/core_agent_manager.py\n+++ b/src/scout_apm/core/core_agent_manager.py\n@@ -58,17 +58,19 @@\n \n     def run(self):\n         try:\n-            subprocess.check_call(\n-                (\n-                    self.agent_binary()\n-                    + self.daemonize_flag()\n-                    + self.log_level()\n-                    + self.log_file()\n-                    + self.config_file()\n-                    + self.socket_path()\n-                ),\n-                close_fds=True,\n-            )\n+            with open(os.devnull) as devnull:\n+                subprocess.check_call(\n+                    (\n+                        self.agent_binary()\n+                        + self.daemonize_flag()\n+                        + self.log_level()\n+                        + self.log_file()\n+                        + self.config_file()\n+                        + self.socket_path()\n+                    ),\n+                    close_fds=True,\n+                    stdout=devnull,\n+                )\n         except Exception:\n             # TODO detect failure of launch properly\n             logger.exception(\"Error running Core Agent\")\n", "issue": "Django log levels are ignored by core agent\nNo matter what I did to the Django log levels, I kept getting this message when running Django management tasks:\r\n\r\n```\r\n[2020-04-02T09:36:06][core_agent][INFO] Initializing logger with log level: Info\r\n```\r\n\r\nThis is super annoying, because this causes programs in my crontab to start sending out lots of e-mails because previously quiet tasks would now start producing output.\r\n\r\nAfter a lot of trial and error, I figured out that you can set `SCOUT_LOG_LEVEL` in Django settings to suppress this message.\r\n\r\nThis really should be fixed so the agent also honors the Django logging settings, or at least documented under the logging section in the Python documentation. It's really non-obvious that this the agent is a separate thing which ignores the Django logging settings.\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport hashlib\nimport json\nimport logging\nimport os\nimport subprocess\nimport tarfile\nimport time\n\nfrom urllib3.exceptions import HTTPError\n\nfrom scout_apm.compat import urllib3_cert_pool_manager\nfrom scout_apm.core.config import scout_config\n\nlogger = logging.getLogger(__name__)\n\n\nclass CoreAgentManager(object):\n    def __init__(self):\n        self.core_agent_bin_path = None\n        self.core_agent_bin_version = None\n        self.core_agent_dir = \"{}/{}\".format(\n            scout_config.value(\"core_agent_dir\"),\n            scout_config.value(\"core_agent_full_name\"),\n        )\n        self.downloader = CoreAgentDownloader(\n            self.core_agent_dir, scout_config.value(\"core_agent_full_name\")\n        )\n\n    def launch(self):\n        if not scout_config.value(\"core_agent_launch\"):\n            logger.debug(\n                \"Not attempting to launch Core Agent \"\n                \"due to 'core_agent_launch' setting.\"\n            )\n            return False\n\n        if not self.verify():\n            if not scout_config.value(\"core_agent_download\"):\n                logger.debug(\n                    \"Not attempting to download Core Agent due \"\n                    \"to 'core_agent_download' setting.\"\n                )\n                return False\n\n            self.download()\n\n            if not self.verify():\n                logger.debug(\"Failed to verify Core Agent. Not launching Core Agent.\")\n                return False\n\n        return self.run()\n\n    def download(self):\n        self.downloader.download()\n\n    def run(self):\n        try:\n            subprocess.check_call(\n                (\n                    self.agent_binary()\n                    + self.daemonize_flag()\n                    + self.log_level()\n                    + self.log_file()\n                    + self.config_file()\n                    + self.socket_path()\n                ),\n                close_fds=True,\n            )\n        except Exception:\n            # TODO detect failure of launch properly\n            logger.exception(\"Error running Core Agent\")\n            return False\n        return True\n\n    def agent_binary(self):\n        return [self.core_agent_bin_path, \"start\"]\n\n    def daemonize_flag(self):\n        return [\"--daemonize\", \"true\"]\n\n    def socket_path(self):\n        # Old deprecated name \"socket_path\"\n        socket_path = scout_config.value(\"socket_path\")\n        if socket_path is None:\n            socket_path = scout_config.value(\"core_agent_socket_path\")\n        return [\"--socket\", socket_path]\n\n    def log_level(self):\n        # Old deprecated name \"log_level\"\n        log_level = scout_config.value(\"log_level\")\n        if log_level is None:\n            log_level = scout_config.value(\"core_agent_log_level\")\n        return [\"--log-level\", log_level]\n\n    def log_file(self):\n        # Old deprecated name \"log_file\"\n        path = scout_config.value(\"log_file\")\n        if path is None:\n            path = scout_config.value(\"core_agent_log_file\")\n\n        if path is not None:\n            return [\"--log-file\", path]\n        else:\n            return []\n\n    def config_file(self):\n        # Old deprecated name \"config_file\"\n        path = scout_config.value(\"config_file\")\n        if path is None:\n            path = scout_config.value(\"core_agent_config_file\")\n\n        if path is not None:\n            return [\"--config-file\", path]\n        else:\n            return []\n\n    def verify(self):\n        manifest = CoreAgentManifest(self.core_agent_dir + \"/manifest.json\")\n        if not manifest.is_valid():\n            logger.debug(\n                \"Core Agent verification failed: CoreAgentManifest is not valid.\"\n            )\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n        bin_path = self.core_agent_dir + \"/\" + manifest.bin_name\n        if sha256_digest(bin_path) == manifest.sha256:\n            self.core_agent_bin_path = bin_path\n            self.core_agent_bin_version = manifest.bin_version\n            return True\n        else:\n            logger.debug(\"Core Agent verification failed: SHA mismatch.\")\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n\nclass CoreAgentDownloader(object):\n    def __init__(self, download_destination, core_agent_full_name):\n        self.stale_download_secs = 120\n        self.destination = download_destination\n        self.core_agent_full_name = core_agent_full_name\n        self.package_location = self.destination + \"/{}.tgz\".format(\n            self.core_agent_full_name\n        )\n        self.download_lock_path = self.destination + \"/download.lock\"\n        self.download_lock_fd = None\n\n    def download(self):\n        self.create_core_agent_dir()\n        self.obtain_download_lock()\n        if self.download_lock_fd is not None:\n            try:\n                downloaded = self.download_package()\n                if downloaded:\n                    self.untar()\n            except (OSError, HTTPError):\n                logger.exception(\"Exception raised while downloading Core Agent\")\n            finally:\n                self.release_download_lock()\n\n    def create_core_agent_dir(self):\n        try:\n            os.makedirs(self.destination, scout_config.core_agent_permissions())\n        except OSError:\n            pass\n\n    def obtain_download_lock(self):\n        self.clean_stale_download_lock()\n        try:\n            self.download_lock_fd = os.open(\n                self.download_lock_path,\n                os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,\n            )\n        except OSError as exc:\n            logger.debug(\n                \"Could not obtain download lock on %s\",\n                self.download_lock_path,\n                exc_info=exc,\n            )\n            self.download_lock_fd = None\n\n    def clean_stale_download_lock(self):\n        try:\n            delta = time.time() - os.stat(self.download_lock_path).st_ctime\n            if delta > self.stale_download_secs:\n                logger.debug(\"Clearing stale download lock file.\")\n                os.unlink(self.download_lock_path)\n        except OSError:\n            pass\n\n    def release_download_lock(self):\n        if self.download_lock_fd is not None:\n            os.unlink(self.download_lock_path)\n            os.close(self.download_lock_fd)\n\n    def download_package(self):\n        full_url = self.full_url()\n        logger.debug(\"Downloading: %s to %s\", full_url, self.package_location)\n        http = urllib3_cert_pool_manager()\n        response = http.request(\n            \"GET\", full_url, preload_content=False, timeout=10.0, retries=3\n        )\n        try:\n            if response.status != 200:\n                return False\n            with open(self.package_location, \"wb\") as fp:\n                for chunk in response.stream():\n                    fp.write(chunk)\n        finally:\n            response.release_conn()\n        return True\n\n    def untar(self):\n        t = tarfile.open(self.package_location, \"r\")\n        t.extractall(self.destination)\n\n    def full_url(self):\n        return \"{root_url}/{core_agent_full_name}.tgz\".format(\n            root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name\n        )\n\n    def root_url(self):\n        return scout_config.value(\"download_url\")\n\n\nclass CoreAgentManifest(object):\n    def __init__(self, path):\n        self.manifest_path = path\n        self.bin_name = None\n        self.bin_version = None\n        self.sha256 = None\n        self.valid = False\n        try:\n            self.parse()\n        # noqa for this issue: https://github.com/PyCQA/flake8-bugbear/issues/110\n        except (ValueError, TypeError, OSError, IOError) as exc:  # noqa: B014\n            logger.debug(\"Error parsing Core Agent Manifest\", exc_info=exc)\n\n    def parse(self):\n        logger.debug(\"Parsing Core Agent manifest path: %s\", self.manifest_path)\n        with open(self.manifest_path) as manifest_file:\n            self.raw = manifest_file.read()\n            self.json = json.loads(self.raw)\n            self.version = self.json[\"version\"]\n            self.bin_version = self.json[\"core_agent_version\"]\n            self.bin_name = self.json[\"core_agent_binary\"]\n            self.sha256 = self.json[\"core_agent_binary_sha256\"]\n            self.valid = True\n            logger.debug(\"Core Agent manifest json: %s\", self.json)\n\n    def is_valid(self):\n        return self.valid\n\n\ndef sha256_digest(filename, block_size=65536):\n    try:\n        sha256 = hashlib.sha256()\n        with open(filename, \"rb\") as f:\n            for block in iter(lambda: f.read(block_size), b\"\"):\n                sha256.update(block)\n        return sha256.hexdigest()\n    except OSError as exc:\n        logger.debug(\"Error on digest\", exc_info=exc)\n        return None\n", "path": "src/scout_apm/core/core_agent_manager.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport hashlib\nimport json\nimport logging\nimport os\nimport subprocess\nimport tarfile\nimport time\n\nfrom urllib3.exceptions import HTTPError\n\nfrom scout_apm.compat import urllib3_cert_pool_manager\nfrom scout_apm.core.config import scout_config\n\nlogger = logging.getLogger(__name__)\n\n\nclass CoreAgentManager(object):\n    def __init__(self):\n        self.core_agent_bin_path = None\n        self.core_agent_bin_version = None\n        self.core_agent_dir = \"{}/{}\".format(\n            scout_config.value(\"core_agent_dir\"),\n            scout_config.value(\"core_agent_full_name\"),\n        )\n        self.downloader = CoreAgentDownloader(\n            self.core_agent_dir, scout_config.value(\"core_agent_full_name\")\n        )\n\n    def launch(self):\n        if not scout_config.value(\"core_agent_launch\"):\n            logger.debug(\n                \"Not attempting to launch Core Agent \"\n                \"due to 'core_agent_launch' setting.\"\n            )\n            return False\n\n        if not self.verify():\n            if not scout_config.value(\"core_agent_download\"):\n                logger.debug(\n                    \"Not attempting to download Core Agent due \"\n                    \"to 'core_agent_download' setting.\"\n                )\n                return False\n\n            self.download()\n\n            if not self.verify():\n                logger.debug(\"Failed to verify Core Agent. Not launching Core Agent.\")\n                return False\n\n        return self.run()\n\n    def download(self):\n        self.downloader.download()\n\n    def run(self):\n        try:\n            with open(os.devnull) as devnull:\n                subprocess.check_call(\n                    (\n                        self.agent_binary()\n                        + self.daemonize_flag()\n                        + self.log_level()\n                        + self.log_file()\n                        + self.config_file()\n                        + self.socket_path()\n                    ),\n                    close_fds=True,\n                    stdout=devnull,\n                )\n        except Exception:\n            # TODO detect failure of launch properly\n            logger.exception(\"Error running Core Agent\")\n            return False\n        return True\n\n    def agent_binary(self):\n        return [self.core_agent_bin_path, \"start\"]\n\n    def daemonize_flag(self):\n        return [\"--daemonize\", \"true\"]\n\n    def socket_path(self):\n        # Old deprecated name \"socket_path\"\n        socket_path = scout_config.value(\"socket_path\")\n        if socket_path is None:\n            socket_path = scout_config.value(\"core_agent_socket_path\")\n        return [\"--socket\", socket_path]\n\n    def log_level(self):\n        # Old deprecated name \"log_level\"\n        log_level = scout_config.value(\"log_level\")\n        if log_level is None:\n            log_level = scout_config.value(\"core_agent_log_level\")\n        return [\"--log-level\", log_level]\n\n    def log_file(self):\n        # Old deprecated name \"log_file\"\n        path = scout_config.value(\"log_file\")\n        if path is None:\n            path = scout_config.value(\"core_agent_log_file\")\n\n        if path is not None:\n            return [\"--log-file\", path]\n        else:\n            return []\n\n    def config_file(self):\n        # Old deprecated name \"config_file\"\n        path = scout_config.value(\"config_file\")\n        if path is None:\n            path = scout_config.value(\"core_agent_config_file\")\n\n        if path is not None:\n            return [\"--config-file\", path]\n        else:\n            return []\n\n    def verify(self):\n        manifest = CoreAgentManifest(self.core_agent_dir + \"/manifest.json\")\n        if not manifest.is_valid():\n            logger.debug(\n                \"Core Agent verification failed: CoreAgentManifest is not valid.\"\n            )\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n        bin_path = self.core_agent_dir + \"/\" + manifest.bin_name\n        if sha256_digest(bin_path) == manifest.sha256:\n            self.core_agent_bin_path = bin_path\n            self.core_agent_bin_version = manifest.bin_version\n            return True\n        else:\n            logger.debug(\"Core Agent verification failed: SHA mismatch.\")\n            self.core_agent_bin_path = None\n            self.core_agent_bin_version = None\n            return False\n\n\nclass CoreAgentDownloader(object):\n    def __init__(self, download_destination, core_agent_full_name):\n        self.stale_download_secs = 120\n        self.destination = download_destination\n        self.core_agent_full_name = core_agent_full_name\n        self.package_location = self.destination + \"/{}.tgz\".format(\n            self.core_agent_full_name\n        )\n        self.download_lock_path = self.destination + \"/download.lock\"\n        self.download_lock_fd = None\n\n    def download(self):\n        self.create_core_agent_dir()\n        self.obtain_download_lock()\n        if self.download_lock_fd is not None:\n            try:\n                downloaded = self.download_package()\n                if downloaded:\n                    self.untar()\n            except (OSError, HTTPError):\n                logger.exception(\"Exception raised while downloading Core Agent\")\n            finally:\n                self.release_download_lock()\n\n    def create_core_agent_dir(self):\n        try:\n            os.makedirs(self.destination, scout_config.core_agent_permissions())\n        except OSError:\n            pass\n\n    def obtain_download_lock(self):\n        self.clean_stale_download_lock()\n        try:\n            self.download_lock_fd = os.open(\n                self.download_lock_path,\n                os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,\n            )\n        except OSError as exc:\n            logger.debug(\n                \"Could not obtain download lock on %s\",\n                self.download_lock_path,\n                exc_info=exc,\n            )\n            self.download_lock_fd = None\n\n    def clean_stale_download_lock(self):\n        try:\n            delta = time.time() - os.stat(self.download_lock_path).st_ctime\n            if delta > self.stale_download_secs:\n                logger.debug(\"Clearing stale download lock file.\")\n                os.unlink(self.download_lock_path)\n        except OSError:\n            pass\n\n    def release_download_lock(self):\n        if self.download_lock_fd is not None:\n            os.unlink(self.download_lock_path)\n            os.close(self.download_lock_fd)\n\n    def download_package(self):\n        full_url = self.full_url()\n        logger.debug(\"Downloading: %s to %s\", full_url, self.package_location)\n        http = urllib3_cert_pool_manager()\n        response = http.request(\n            \"GET\", full_url, preload_content=False, timeout=10.0, retries=3\n        )\n        try:\n            if response.status != 200:\n                return False\n            with open(self.package_location, \"wb\") as fp:\n                for chunk in response.stream():\n                    fp.write(chunk)\n        finally:\n            response.release_conn()\n        return True\n\n    def untar(self):\n        t = tarfile.open(self.package_location, \"r\")\n        t.extractall(self.destination)\n\n    def full_url(self):\n        return \"{root_url}/{core_agent_full_name}.tgz\".format(\n            root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name\n        )\n\n    def root_url(self):\n        return scout_config.value(\"download_url\")\n\n\nclass CoreAgentManifest(object):\n    def __init__(self, path):\n        self.manifest_path = path\n        self.bin_name = None\n        self.bin_version = None\n        self.sha256 = None\n        self.valid = False\n        try:\n            self.parse()\n        # noqa for this issue: https://github.com/PyCQA/flake8-bugbear/issues/110\n        except (ValueError, TypeError, OSError, IOError) as exc:  # noqa: B014\n            logger.debug(\"Error parsing Core Agent Manifest\", exc_info=exc)\n\n    def parse(self):\n        logger.debug(\"Parsing Core Agent manifest path: %s\", self.manifest_path)\n        with open(self.manifest_path) as manifest_file:\n            self.raw = manifest_file.read()\n            self.json = json.loads(self.raw)\n            self.version = self.json[\"version\"]\n            self.bin_version = self.json[\"core_agent_version\"]\n            self.bin_name = self.json[\"core_agent_binary\"]\n            self.sha256 = self.json[\"core_agent_binary_sha256\"]\n            self.valid = True\n            logger.debug(\"Core Agent manifest json: %s\", self.json)\n\n    def is_valid(self):\n        return self.valid\n\n\ndef sha256_digest(filename, block_size=65536):\n    try:\n        sha256 = hashlib.sha256()\n        with open(filename, \"rb\") as f:\n            for block in iter(lambda: f.read(block_size), b\"\"):\n                sha256.update(block)\n        return sha256.hexdigest()\n    except OSError as exc:\n        logger.debug(\"Error on digest\", exc_info=exc)\n        return None\n", "path": "src/scout_apm/core/core_agent_manager.py"}]}