We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
serve_logs.py should respect the logging config's task handler's base_log_folder value
### Apache Airflow version

2.2.5

### What happened

in a worker container, a flask app is spun up to serve task log files that is read by the webserver and rendered to the user in the UI.  The log files cannot be read if you overwrite the task handler's base_log_folder value. (404 error)
 ie. in the airflow.cfg, the base_log_folder = `foo/bar/logs`, and the task handler uses `{base_log_folder}/dags`



### What you think should happen instead

in https://github.com/apache/airflow/blob/main/airflow/utils/serve_logs.py#L33, it should read the logging config's task handler log location.

### How to reproduce

use a custom logging config, override the task's base log folder. 
Run a dag and try to view logs in the ui, you will get a 404

```
LOGGING_CONFIG["handlers"].update(
    {
        "task": {
            "class":  "airflow.utils.log.file_task_handler.FileTaskHandler",
            "formatter": "airflow",
            "base_log_folder": f"{BASE_LOG_FOLDER}/dags",
            "filename_template": FILENAME_TEMPLATE,
            "filters": ["mask_secrets"],
        },
  }
```

### Operating System

ubuntu

### Versions of Apache Airflow Providers

_No response_

### Deployment

Virtualenv installation

### Deployment details

_No response_

### Anything else

_No response_

### Are you willing to submit PR?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `airflow/utils/serve_logs.py`
Content:
```
1 # Licensed to the Apache Software Foundation (ASF) under one
2 # or more contributor license agreements.  See the NOTICE file
3 # distributed with this work for additional information
4 # regarding copyright ownership.  The ASF licenses this file
5 # to you under the Apache License, Version 2.0 (the
6 # "License"); you may not use this file except in compliance
7 # with the License.  You may obtain a copy of the License at
8 #
9 #   http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing,
12 # software distributed under the License is distributed on an
13 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 # KIND, either express or implied.  See the License for the
15 # specific language governing permissions and limitations
16 # under the License.
17 """Serve logs process."""
18 from __future__ import annotations
19 
20 import collections
21 import logging
22 import os
23 import socket
24 
25 import gunicorn.app.base
26 from flask import Flask, abort, request, send_from_directory
27 from jwt.exceptions import (
28     ExpiredSignatureError,
29     ImmatureSignatureError,
30     InvalidAudienceError,
31     InvalidIssuedAtError,
32     InvalidSignatureError,
33 )
34 from setproctitle import setproctitle
35 
36 from airflow.configuration import conf
37 from airflow.utils.docs import get_docs_url
38 from airflow.utils.jwt_signer import JWTSigner
39 
40 logger = logging.getLogger(__name__)
41 
42 
43 def create_app():
44     flask_app = Flask(__name__, static_folder=None)
45     expiration_time_in_seconds = conf.getint("webserver", "log_request_clock_grace", fallback=30)
46     log_directory = os.path.expanduser(conf.get("logging", "BASE_LOG_FOLDER"))
47 
48     signer = JWTSigner(
49         secret_key=conf.get("webserver", "secret_key"),
50         expiration_time_in_seconds=expiration_time_in_seconds,
51         audience="task-instance-logs",
52     )
53 
54     # Prevent direct access to the logs port
55     @flask_app.before_request
56     def validate_pre_signed_url():
57         try:
58             auth = request.headers.get("Authorization")
59             if auth is None:
60                 logger.warning("The Authorization header is missing: %s.", request.headers)
61                 abort(403)
62             payload = signer.verify_token(auth)
63             token_filename = payload.get("filename")
64             request_filename = request.view_args["filename"]
65             if token_filename is None:
66                 logger.warning("The payload does not contain 'filename' key: %s.", payload)
67                 abort(403)
68             if token_filename != request_filename:
69                 logger.warning(
70                     "The payload log_relative_path key is different than the one in token:"
71                     "Request path: %s. Token path: %s.",
72                     request_filename,
73                     token_filename,
74                 )
75                 abort(403)
76         except InvalidAudienceError:
77             logger.warning("Invalid audience for the request", exc_info=True)
78             abort(403)
79         except InvalidSignatureError:
80             logger.warning("The signature of the request was wrong", exc_info=True)
81             abort(403)
82         except ImmatureSignatureError:
83             logger.warning("The signature of the request was sent from the future", exc_info=True)
84             abort(403)
85         except ExpiredSignatureError:
86             logger.warning(
87                 "The signature of the request has expired. Make sure that all components "
88                 "in your system have synchronized clocks. "
89                 "See more at %s",
90                 get_docs_url("configurations-ref.html#secret-key"),
91                 exc_info=True,
92             )
93             abort(403)
94         except InvalidIssuedAtError:
95             logger.warning(
96                 "The request was issues in the future. Make sure that all components "
97                 "in your system have synchronized clocks. "
98                 "See more at %s",
99                 get_docs_url("configurations-ref.html#secret-key"),
100                 exc_info=True,
101             )
102             abort(403)
103         except Exception:
104             logger.warning("Unknown error", exc_info=True)
105             abort(403)
106 
107     @flask_app.route("/log/<path:filename>")
108     def serve_logs_view(filename):
109         return send_from_directory(log_directory, filename, mimetype="application/json", as_attachment=False)
110 
111     return flask_app
112 
113 
114 GunicornOption = collections.namedtuple("GunicornOption", ["key", "value"])
115 
116 
117 class StandaloneGunicornApplication(gunicorn.app.base.BaseApplication):
118     """
119     Standalone Gunicorn application/serve for usage with any WSGI-application.
120 
121     Code inspired by an example from the Gunicorn documentation.
122     https://github.com/benoitc/gunicorn/blob/cf55d2cec277f220ebd605989ce78ad1bb553c46/examples/standalone_app.py
123 
124     For details, about standalone gunicorn application, see:
125     https://docs.gunicorn.org/en/stable/custom.html
126     """
127 
128     def __init__(self, app, options=None):
129         self.options = options or []
130         self.application = app
131         super().__init__()
132 
133     def load_config(self):
134         for option in self.options:
135             self.cfg.set(option.key.lower(), option.value)
136 
137     def load(self):
138         return self.application
139 
140 
141 def serve_logs(port=None):
142     """Serves logs generated by Worker."""
143     setproctitle("airflow serve-logs")
144     wsgi_app = create_app()
145 
146     port = port or conf.getint("logging", "WORKER_LOG_SERVER_PORT")
147 
148     # If dual stack is available and IPV6_V6ONLY is not enabled on the socket
149     # then when IPV6 is bound to it will also bind to IPV4 automatically
150     if getattr(socket, "has_dualstack_ipv6", lambda: False)():
151         bind_option = GunicornOption("bind", f"[::]:{port}")
152     else:
153         bind_option = GunicornOption("bind", f"0.0.0.0:{port}")
154 
155     options = [bind_option, GunicornOption("workers", 2)]
156     StandaloneGunicornApplication(wsgi_app, options).run()
157 
158 
159 if __name__ == "__main__":
160     serve_logs()
161
```
--- 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/airflow/utils/serve_logs.py b/airflow/utils/serve_logs.py
--- a/airflow/utils/serve_logs.py
+++ b/airflow/utils/serve_logs.py
@@ -36,6 +36,7 @@
 from airflow.configuration import conf
 from airflow.utils.docs import get_docs_url
 from airflow.utils.jwt_signer import JWTSigner
+from airflow.utils.module_loading import import_string
 
 logger = logging.getLogger(__name__)
 
@@ -44,7 +45,29 @@
     flask_app = Flask(__name__, static_folder=None)
     expiration_time_in_seconds = conf.getint("webserver", "log_request_clock_grace", fallback=30)
     log_directory = os.path.expanduser(conf.get("logging", "BASE_LOG_FOLDER"))
-
+    log_config_class = conf.get("logging", "logging_config_class")
+    if log_config_class:
+        logger.info("Detected user-defined logging config. Attempting to load %s", log_config_class)
+        try:
+            logging_config = import_string(log_config_class)
+            try:
+                base_log_folder = logging_config["handlers"]["task"]["base_log_folder"]
+            except KeyError:
+                base_log_folder = None
+            if base_log_folder is not None:
+                log_directory = base_log_folder
+                logger.info(
+                    "Successfully imported user-defined logging config. Flask App will serve log from %s",
+                    log_directory,
+                )
+            else:
+                logger.warning(
+                    "User-defined logging config does not specify 'base_log_folder'. "
+                    "Flask App will use default log directory %s",
+                    base_log_folder,
+                )
+        except Exception as e:
+            raise ImportError(f"Unable to load {log_config_class} due to error: {e}")
     signer = JWTSigner(
         secret_key=conf.get("webserver", "secret_key"),
         expiration_time_in_seconds=expiration_time_in_seconds,

{"golden_diff": "diff --git a/airflow/utils/serve_logs.py b/airflow/utils/serve_logs.py\n--- a/airflow/utils/serve_logs.py\n+++ b/airflow/utils/serve_logs.py\n@@ -36,6 +36,7 @@\n from airflow.configuration import conf\n from airflow.utils.docs import get_docs_url\n from airflow.utils.jwt_signer import JWTSigner\n+from airflow.utils.module_loading import import_string\n \n logger = logging.getLogger(__name__)\n \n@@ -44,7 +45,29 @@\n     flask_app = Flask(__name__, static_folder=None)\n     expiration_time_in_seconds = conf.getint(\"webserver\", \"log_request_clock_grace\", fallback=30)\n     log_directory = os.path.expanduser(conf.get(\"logging\", \"BASE_LOG_FOLDER\"))\n-\n+    log_config_class = conf.get(\"logging\", \"logging_config_class\")\n+    if log_config_class:\n+        logger.info(\"Detected user-defined logging config. Attempting to load %s\", log_config_class)\n+        try:\n+            logging_config = import_string(log_config_class)\n+            try:\n+                base_log_folder = logging_config[\"handlers\"][\"task\"][\"base_log_folder\"]\n+            except KeyError:\n+                base_log_folder = None\n+            if base_log_folder is not None:\n+                log_directory = base_log_folder\n+                logger.info(\n+                    \"Successfully imported user-defined logging config. Flask App will serve log from %s\",\n+                    log_directory,\n+                )\n+            else:\n+                logger.warning(\n+                    \"User-defined logging config does not specify 'base_log_folder'. \"\n+                    \"Flask App will use default log directory %s\",\n+                    base_log_folder,\n+                )\n+        except Exception as e:\n+            raise ImportError(f\"Unable to load {log_config_class} due to error: {e}\")\n     signer = JWTSigner(\n         secret_key=conf.get(\"webserver\", \"secret_key\"),\n         expiration_time_in_seconds=expiration_time_in_seconds,\n", "issue": "serve_logs.py should respect the logging config's task handler's base_log_folder value\n### Apache Airflow version\n\n2.2.5\n\n### What happened\n\nin a worker container, a flask app is spun up to serve task log files that is read by the webserver and rendered to the user in the UI.  The log files cannot be read if you overwrite the task handler's base_log_folder value. (404 error)\r\n ie. in the airflow.cfg, the base_log_folder = `foo/bar/logs`, and the task handler uses `{base_log_folder}/dags`\r\n\r\n\n\n### What you think should happen instead\n\nin https://github.com/apache/airflow/blob/main/airflow/utils/serve_logs.py#L33, it should read the logging config's task handler log location.\n\n### How to reproduce\n\nuse a custom logging config, override the task's base log folder. \r\nRun a dag and try to view logs in the ui, you will get a 404\r\n\r\n```\r\nLOGGING_CONFIG[\"handlers\"].update(\r\n    {\r\n        \"task\": {\r\n            \"class\":  \"airflow.utils.log.file_task_handler.FileTaskHandler\",\r\n            \"formatter\": \"airflow\",\r\n            \"base_log_folder\": f\"{BASE_LOG_FOLDER}/dags\",\r\n            \"filename_template\": FILENAME_TEMPLATE,\r\n            \"filters\": [\"mask_secrets\"],\r\n        },\r\n  }\r\n```\n\n### Operating System\n\nubuntu\n\n### Versions of Apache Airflow Providers\n\n_No response_\n\n### Deployment\n\nVirtualenv installation\n\n### Deployment details\n\n_No response_\n\n### Anything else\n\n_No response_\n\n### Are you willing to submit PR?\n\n- [X] Yes I am willing to submit a PR!\n\n### Code of Conduct\n\n- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)\n\n", "before_files": [{"content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n\"\"\"Serve logs process.\"\"\"\nfrom __future__ import annotations\n\nimport collections\nimport logging\nimport os\nimport socket\n\nimport gunicorn.app.base\nfrom flask import Flask, abort, request, send_from_directory\nfrom jwt.exceptions import (\n    ExpiredSignatureError,\n    ImmatureSignatureError,\n    InvalidAudienceError,\n    InvalidIssuedAtError,\n    InvalidSignatureError,\n)\nfrom setproctitle import setproctitle\n\nfrom airflow.configuration import conf\nfrom airflow.utils.docs import get_docs_url\nfrom airflow.utils.jwt_signer import JWTSigner\n\nlogger = logging.getLogger(__name__)\n\n\ndef create_app():\n    flask_app = Flask(__name__, static_folder=None)\n    expiration_time_in_seconds = conf.getint(\"webserver\", \"log_request_clock_grace\", fallback=30)\n    log_directory = os.path.expanduser(conf.get(\"logging\", \"BASE_LOG_FOLDER\"))\n\n    signer = JWTSigner(\n        secret_key=conf.get(\"webserver\", \"secret_key\"),\n        expiration_time_in_seconds=expiration_time_in_seconds,\n        audience=\"task-instance-logs\",\n    )\n\n    # Prevent direct access to the logs port\n    @flask_app.before_request\n    def validate_pre_signed_url():\n        try:\n            auth = request.headers.get(\"Authorization\")\n            if auth is None:\n                logger.warning(\"The Authorization header is missing: %s.\", request.headers)\n                abort(403)\n            payload = signer.verify_token(auth)\n            token_filename = payload.get(\"filename\")\n            request_filename = request.view_args[\"filename\"]\n            if token_filename is None:\n                logger.warning(\"The payload does not contain 'filename' key: %s.\", payload)\n                abort(403)\n            if token_filename != request_filename:\n                logger.warning(\n                    \"The payload log_relative_path key is different than the one in token:\"\n                    \"Request path: %s. Token path: %s.\",\n                    request_filename,\n                    token_filename,\n                )\n                abort(403)\n        except InvalidAudienceError:\n            logger.warning(\"Invalid audience for the request\", exc_info=True)\n            abort(403)\n        except InvalidSignatureError:\n            logger.warning(\"The signature of the request was wrong\", exc_info=True)\n            abort(403)\n        except ImmatureSignatureError:\n            logger.warning(\"The signature of the request was sent from the future\", exc_info=True)\n            abort(403)\n        except ExpiredSignatureError:\n            logger.warning(\n                \"The signature of the request has expired. Make sure that all components \"\n                \"in your system have synchronized clocks. \"\n                \"See more at %s\",\n                get_docs_url(\"configurations-ref.html#secret-key\"),\n                exc_info=True,\n            )\n            abort(403)\n        except InvalidIssuedAtError:\n            logger.warning(\n                \"The request was issues in the future. Make sure that all components \"\n                \"in your system have synchronized clocks. \"\n                \"See more at %s\",\n                get_docs_url(\"configurations-ref.html#secret-key\"),\n                exc_info=True,\n            )\n            abort(403)\n        except Exception:\n            logger.warning(\"Unknown error\", exc_info=True)\n            abort(403)\n\n    @flask_app.route(\"/log/<path:filename>\")\n    def serve_logs_view(filename):\n        return send_from_directory(log_directory, filename, mimetype=\"application/json\", as_attachment=False)\n\n    return flask_app\n\n\nGunicornOption = collections.namedtuple(\"GunicornOption\", [\"key\", \"value\"])\n\n\nclass StandaloneGunicornApplication(gunicorn.app.base.BaseApplication):\n    \"\"\"\n    Standalone Gunicorn application/serve for usage with any WSGI-application.\n\n    Code inspired by an example from the Gunicorn documentation.\n    https://github.com/benoitc/gunicorn/blob/cf55d2cec277f220ebd605989ce78ad1bb553c46/examples/standalone_app.py\n\n    For details, about standalone gunicorn application, see:\n    https://docs.gunicorn.org/en/stable/custom.html\n    \"\"\"\n\n    def __init__(self, app, options=None):\n        self.options = options or []\n        self.application = app\n        super().__init__()\n\n    def load_config(self):\n        for option in self.options:\n            self.cfg.set(option.key.lower(), option.value)\n\n    def load(self):\n        return self.application\n\n\ndef serve_logs(port=None):\n    \"\"\"Serves logs generated by Worker.\"\"\"\n    setproctitle(\"airflow serve-logs\")\n    wsgi_app = create_app()\n\n    port = port or conf.getint(\"logging\", \"WORKER_LOG_SERVER_PORT\")\n\n    # If dual stack is available and IPV6_V6ONLY is not enabled on the socket\n    # then when IPV6 is bound to it will also bind to IPV4 automatically\n    if getattr(socket, \"has_dualstack_ipv6\", lambda: False)():\n        bind_option = GunicornOption(\"bind\", f\"[::]:{port}\")\n    else:\n        bind_option = GunicornOption(\"bind\", f\"0.0.0.0:{port}\")\n\n    options = [bind_option, GunicornOption(\"workers\", 2)]\n    StandaloneGunicornApplication(wsgi_app, options).run()\n\n\nif __name__ == \"__main__\":\n    serve_logs()\n", "path": "airflow/utils/serve_logs.py"}], "after_files": [{"content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n\"\"\"Serve logs process.\"\"\"\nfrom __future__ import annotations\n\nimport collections\nimport logging\nimport os\nimport socket\n\nimport gunicorn.app.base\nfrom flask import Flask, abort, request, send_from_directory\nfrom jwt.exceptions import (\n    ExpiredSignatureError,\n    ImmatureSignatureError,\n    InvalidAudienceError,\n    InvalidIssuedAtError,\n    InvalidSignatureError,\n)\nfrom setproctitle import setproctitle\n\nfrom airflow.configuration import conf\nfrom airflow.utils.docs import get_docs_url\nfrom airflow.utils.jwt_signer import JWTSigner\nfrom airflow.utils.module_loading import import_string\n\nlogger = logging.getLogger(__name__)\n\n\ndef create_app():\n    flask_app = Flask(__name__, static_folder=None)\n    expiration_time_in_seconds = conf.getint(\"webserver\", \"log_request_clock_grace\", fallback=30)\n    log_directory = os.path.expanduser(conf.get(\"logging\", \"BASE_LOG_FOLDER\"))\n    log_config_class = conf.get(\"logging\", \"logging_config_class\")\n    if log_config_class:\n        logger.info(\"Detected user-defined logging config. Attempting to load %s\", log_config_class)\n        try:\n            logging_config = import_string(log_config_class)\n            try:\n                base_log_folder = logging_config[\"handlers\"][\"task\"][\"base_log_folder\"]\n            except KeyError:\n                base_log_folder = None\n            if base_log_folder is not None:\n                log_directory = base_log_folder\n                logger.info(\n                    \"Successfully imported user-defined logging config. Flask App will serve log from %s\",\n                    log_directory,\n                )\n            else:\n                logger.warning(\n                    \"User-defined logging config does not specify 'base_log_folder'. \"\n                    \"Flask App will use default log directory %s\",\n                    base_log_folder,\n                )\n        except Exception as e:\n            raise ImportError(f\"Unable to load {log_config_class} due to error: {e}\")\n    signer = JWTSigner(\n        secret_key=conf.get(\"webserver\", \"secret_key\"),\n        expiration_time_in_seconds=expiration_time_in_seconds,\n        audience=\"task-instance-logs\",\n    )\n\n    # Prevent direct access to the logs port\n    @flask_app.before_request\n    def validate_pre_signed_url():\n        try:\n            auth = request.headers.get(\"Authorization\")\n            if auth is None:\n                logger.warning(\"The Authorization header is missing: %s.\", request.headers)\n                abort(403)\n            payload = signer.verify_token(auth)\n            token_filename = payload.get(\"filename\")\n            request_filename = request.view_args[\"filename\"]\n            if token_filename is None:\n                logger.warning(\"The payload does not contain 'filename' key: %s.\", payload)\n                abort(403)\n            if token_filename != request_filename:\n                logger.warning(\n                    \"The payload log_relative_path key is different than the one in token:\"\n                    \"Request path: %s. Token path: %s.\",\n                    request_filename,\n                    token_filename,\n                )\n                abort(403)\n        except InvalidAudienceError:\n            logger.warning(\"Invalid audience for the request\", exc_info=True)\n            abort(403)\n        except InvalidSignatureError:\n            logger.warning(\"The signature of the request was wrong\", exc_info=True)\n            abort(403)\n        except ImmatureSignatureError:\n            logger.warning(\"The signature of the request was sent from the future\", exc_info=True)\n            abort(403)\n        except ExpiredSignatureError:\n            logger.warning(\n                \"The signature of the request has expired. Make sure that all components \"\n                \"in your system have synchronized clocks. \"\n                \"See more at %s\",\n                get_docs_url(\"configurations-ref.html#secret-key\"),\n                exc_info=True,\n            )\n            abort(403)\n        except InvalidIssuedAtError:\n            logger.warning(\n                \"The request was issues in the future. Make sure that all components \"\n                \"in your system have synchronized clocks. \"\n                \"See more at %s\",\n                get_docs_url(\"configurations-ref.html#secret-key\"),\n                exc_info=True,\n            )\n            abort(403)\n        except Exception:\n            logger.warning(\"Unknown error\", exc_info=True)\n            abort(403)\n\n    @flask_app.route(\"/log/<path:filename>\")\n    def serve_logs_view(filename):\n        return send_from_directory(log_directory, filename, mimetype=\"application/json\", as_attachment=False)\n\n    return flask_app\n\n\nGunicornOption = collections.namedtuple(\"GunicornOption\", [\"key\", \"value\"])\n\n\nclass StandaloneGunicornApplication(gunicorn.app.base.BaseApplication):\n    \"\"\"\n    Standalone Gunicorn application/serve for usage with any WSGI-application.\n\n    Code inspired by an example from the Gunicorn documentation.\n    https://github.com/benoitc/gunicorn/blob/cf55d2cec277f220ebd605989ce78ad1bb553c46/examples/standalone_app.py\n\n    For details, about standalone gunicorn application, see:\n    https://docs.gunicorn.org/en/stable/custom.html\n    \"\"\"\n\n    def __init__(self, app, options=None):\n        self.options = options or []\n        self.application = app\n        super().__init__()\n\n    def load_config(self):\n        for option in self.options:\n            self.cfg.set(option.key.lower(), option.value)\n\n    def load(self):\n        return self.application\n\n\ndef serve_logs(port=None):\n    \"\"\"Serves logs generated by Worker.\"\"\"\n    setproctitle(\"airflow serve-logs\")\n    wsgi_app = create_app()\n\n    port = port or conf.getint(\"logging\", \"WORKER_LOG_SERVER_PORT\")\n\n    # If dual stack is available and IPV6_V6ONLY is not enabled on the socket\n    # then when IPV6 is bound to it will also bind to IPV4 automatically\n    if getattr(socket, \"has_dualstack_ipv6\", lambda: False)():\n        bind_option = GunicornOption(\"bind\", f\"[::]:{port}\")\n    else:\n        bind_option = GunicornOption(\"bind\", f\"0.0.0.0:{port}\")\n\n    options = [bind_option, GunicornOption(\"workers\", 2)]\n    StandaloneGunicornApplication(wsgi_app, options).run()\n\n\nif __name__ == \"__main__\":\n    serve_logs()\n", "path": "airflow/utils/serve_logs.py"}]}