We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
No attachment preview for .log text files
### Environment

SaaS (https://sentry.io/)

### Version

_No response_

### Steps to Reproduce

1. upload a crash report with .log attachment
2. go into issue that was created and scroll to the bottom
3. in attachment section see the .log file and grayed out Preview button for this attachment, hover saying "This attachment cannot be previewed"

### Expected Result

As it is a text file, I would expect this to be the most basic file type able to be previewed without downloading it each time to your drive. I do assume .txt files could be previewed in such way. I see when searching that even image files can be previewed. So .log files 100% should also be previewable...

### Actual Result

Cannot preview this file:
![obraz](https://user-images.githubusercontent.com/5182588/187083496-19f8320a-8c62-4349-b883-b4ecef405dc4.png)


--- 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/sentry/runner/settings.py`
Content:
```
1 import os
2 import warnings
3 
4 import click
5 
6 DEFAULT_SETTINGS_MODULE = "sentry.conf.server"
7 DEFAULT_SETTINGS_CONF = "config.yml"
8 DEFAULT_SETTINGS_OVERRIDE = "sentry.conf.py"
9 
10 
11 def generate_secret_key():
12     from django.utils.crypto import get_random_string
13 
14     chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#%^&*(-_=+)"
15     return get_random_string(50, chars)
16 
17 
18 def load_config_template(path, version="default"):
19     from pkg_resources import resource_string
20 
21     return resource_string("sentry", f"data/config/{path}.{version}").decode("utf8")
22 
23 
24 def generate_settings(dev=False):
25     """
26     This command is run when ``default_path`` doesn't exist, or ``init`` is
27     run and returns a string representing the default data to put into their
28     settings file.
29     """
30     context = {
31         "secret_key": generate_secret_key(),
32         "debug_flag": dev,
33         "mail.backend": "console" if dev else "smtp",
34     }
35 
36     py = load_config_template(DEFAULT_SETTINGS_OVERRIDE, "default") % context
37     yaml = load_config_template(DEFAULT_SETTINGS_CONF, "default") % context
38     return py, yaml
39 
40 
41 def get_sentry_conf():
42     """
43     Fetch the SENTRY_CONF value, either from the click context
44     if available, or SENTRY_CONF environment variable.
45     """
46     try:
47         ctx = click.get_current_context()
48         return ctx.obj["config"]
49     except (RuntimeError, KeyError, TypeError):
50         try:
51             return os.environ["SENTRY_CONF"]
52         except KeyError:
53             return "~/.sentry"
54 
55 
56 def discover_configs():
57     """
58     Discover the locations of three configuration components:
59      * Config directory (~/.sentry)
60      * Optional python config file (~/.sentry/sentry.conf.py)
61      * Optional yaml config (~/.sentry/config.yml)
62     """
63     try:
64         config = os.environ["SENTRY_CONF"]
65     except KeyError:
66         config = "~/.sentry"
67 
68     config = os.path.expanduser(config)
69 
70     # This is the old, now deprecated code path where SENTRY_CONF is pointed directly
71     # to a python file
72     if config.endswith((".py", ".conf")) or os.path.isfile(config):
73         return (os.path.dirname(config), config, None)
74 
75     return (
76         config,
77         os.path.join(config, DEFAULT_SETTINGS_OVERRIDE),
78         os.path.join(config, DEFAULT_SETTINGS_CONF),
79     )
80 
81 
82 def configure(ctx, py, yaml, skip_service_validation=False):
83     """
84     Given the two different config files, set up the environment.
85 
86     NOTE: Will only execute once, so it's safe to call multiple times.
87     """
88     global __installed
89     if __installed:
90         return
91 
92     # Make sure that our warnings are always displayed.
93     warnings.filterwarnings("default", "", Warning, r"^sentry")
94 
95     from django.utils.deprecation import RemovedInDjango30Warning
96 
97     warnings.filterwarnings(action="ignore", category=RemovedInDjango30Warning)
98 
99     # Add in additional mimetypes that are useful for our static files
100     # which aren't common in default system registries
101     import mimetypes
102 
103     for type, ext in (
104         ("application/json", "map"),
105         ("application/font-woff", "woff"),
106         ("application/font-woff2", "woff2"),
107         ("application/vnd.ms-fontobject", "eot"),
108         ("application/x-font-ttf", "ttf"),
109         ("application/x-font-ttf", "ttc"),
110         ("font/opentype", "otf"),
111         ("image/svg+xml", "svg"),
112     ):
113         mimetypes.add_type(type, "." + ext)
114 
115     from .importer import install
116 
117     if yaml is None:
118         # `yaml` will be None when SENTRY_CONF is pointed
119         # directly to a file, in which case, this file must exist
120         if not os.path.exists(py):
121             if ctx:
122                 raise click.ClickException(
123                     "Configuration file does not exist. Use 'sentry init' to initialize the file."
124                 )
125             raise ValueError(
126                 "Configuration file does not exist at '%s'" % click.format_filename(py)
127             )
128     elif not os.path.exists(yaml) and not os.path.exists(py):
129         if ctx:
130             raise click.ClickException(
131                 "Configuration file does not exist. Use 'sentry init' to initialize the file."
132             )
133         raise ValueError("Configuration file does not exist at '%s'" % click.format_filename(yaml))
134 
135     # Add autoreload for config.yml file if needed
136     if yaml is not None and os.path.exists(yaml):
137         from sentry.utils.uwsgi import reload_on_change
138 
139         reload_on_change(yaml)
140 
141     os.environ["DJANGO_SETTINGS_MODULE"] = "sentry_config"
142 
143     install("sentry_config", py, DEFAULT_SETTINGS_MODULE)
144 
145     from django.conf import settings
146 
147     # HACK: we need to force access of django.conf.settings to
148     # ensure we don't hit any import-driven recursive behavior
149     hasattr(settings, "INSTALLED_APPS")
150 
151     from .initializer import initialize_app
152 
153     initialize_app(
154         {"config_path": py, "settings": settings, "options": yaml},
155         skip_service_validation=skip_service_validation,
156     )
157 
158     if os.environ.get("OPENAPIGENERATE", False):
159         # see https://drf-spectacular.readthedocs.io/en/latest/customization.html#step-5-extensions
160         from sentry.apidocs import extensions  # NOQA
161 
162     __installed = True
163 
164 
165 __installed = False
166
```
--- 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/sentry/runner/settings.py b/src/sentry/runner/settings.py
--- a/src/sentry/runner/settings.py
+++ b/src/sentry/runner/settings.py
@@ -109,6 +109,7 @@
         ("application/x-font-ttf", "ttc"),
         ("font/opentype", "otf"),
         ("image/svg+xml", "svg"),
+        ("text/plain", "log"),
     ):
         mimetypes.add_type(type, "." + ext)

{"golden_diff": "diff --git a/src/sentry/runner/settings.py b/src/sentry/runner/settings.py\n--- a/src/sentry/runner/settings.py\n+++ b/src/sentry/runner/settings.py\n@@ -109,6 +109,7 @@\n         (\"application/x-font-ttf\", \"ttc\"),\n         (\"font/opentype\", \"otf\"),\n         (\"image/svg+xml\", \"svg\"),\n+        (\"text/plain\", \"log\"),\n     ):\n         mimetypes.add_type(type, \".\" + ext)\n", "issue": "No attachment preview for .log text files\n### Environment\n\nSaaS (https://sentry.io/)\n\n### Version\n\n_No response_\n\n### Steps to Reproduce\n\n1. upload a crash report with .log attachment\r\n2. go into issue that was created and scroll to the bottom\r\n3. in attachment section see the .log file and grayed out Preview button for this attachment, hover saying \"This attachment cannot be previewed\"\n\n### Expected Result\n\nAs it is a text file, I would expect this to be the most basic file type able to be previewed without downloading it each time to your drive. I do assume .txt files could be previewed in such way. I see when searching that even image files can be previewed. So .log files 100% should also be previewable...\n\n### Actual Result\n\nCannot preview this file:\r\n![obraz](https://user-images.githubusercontent.com/5182588/187083496-19f8320a-8c62-4349-b883-b4ecef405dc4.png)\r\n\n", "before_files": [{"content": "import os\nimport warnings\n\nimport click\n\nDEFAULT_SETTINGS_MODULE = \"sentry.conf.server\"\nDEFAULT_SETTINGS_CONF = \"config.yml\"\nDEFAULT_SETTINGS_OVERRIDE = \"sentry.conf.py\"\n\n\ndef generate_secret_key():\n    from django.utils.crypto import get_random_string\n\n    chars = \"abcdefghijklmnopqrstuvwxyz0123456789!@#%^&*(-_=+)\"\n    return get_random_string(50, chars)\n\n\ndef load_config_template(path, version=\"default\"):\n    from pkg_resources import resource_string\n\n    return resource_string(\"sentry\", f\"data/config/{path}.{version}\").decode(\"utf8\")\n\n\ndef generate_settings(dev=False):\n    \"\"\"\n    This command is run when ``default_path`` doesn't exist, or ``init`` is\n    run and returns a string representing the default data to put into their\n    settings file.\n    \"\"\"\n    context = {\n        \"secret_key\": generate_secret_key(),\n        \"debug_flag\": dev,\n        \"mail.backend\": \"console\" if dev else \"smtp\",\n    }\n\n    py = load_config_template(DEFAULT_SETTINGS_OVERRIDE, \"default\") % context\n    yaml = load_config_template(DEFAULT_SETTINGS_CONF, \"default\") % context\n    return py, yaml\n\n\ndef get_sentry_conf():\n    \"\"\"\n    Fetch the SENTRY_CONF value, either from the click context\n    if available, or SENTRY_CONF environment variable.\n    \"\"\"\n    try:\n        ctx = click.get_current_context()\n        return ctx.obj[\"config\"]\n    except (RuntimeError, KeyError, TypeError):\n        try:\n            return os.environ[\"SENTRY_CONF\"]\n        except KeyError:\n            return \"~/.sentry\"\n\n\ndef discover_configs():\n    \"\"\"\n    Discover the locations of three configuration components:\n     * Config directory (~/.sentry)\n     * Optional python config file (~/.sentry/sentry.conf.py)\n     * Optional yaml config (~/.sentry/config.yml)\n    \"\"\"\n    try:\n        config = os.environ[\"SENTRY_CONF\"]\n    except KeyError:\n        config = \"~/.sentry\"\n\n    config = os.path.expanduser(config)\n\n    # This is the old, now deprecated code path where SENTRY_CONF is pointed directly\n    # to a python file\n    if config.endswith((\".py\", \".conf\")) or os.path.isfile(config):\n        return (os.path.dirname(config), config, None)\n\n    return (\n        config,\n        os.path.join(config, DEFAULT_SETTINGS_OVERRIDE),\n        os.path.join(config, DEFAULT_SETTINGS_CONF),\n    )\n\n\ndef configure(ctx, py, yaml, skip_service_validation=False):\n    \"\"\"\n    Given the two different config files, set up the environment.\n\n    NOTE: Will only execute once, so it's safe to call multiple times.\n    \"\"\"\n    global __installed\n    if __installed:\n        return\n\n    # Make sure that our warnings are always displayed.\n    warnings.filterwarnings(\"default\", \"\", Warning, r\"^sentry\")\n\n    from django.utils.deprecation import RemovedInDjango30Warning\n\n    warnings.filterwarnings(action=\"ignore\", category=RemovedInDjango30Warning)\n\n    # Add in additional mimetypes that are useful for our static files\n    # which aren't common in default system registries\n    import mimetypes\n\n    for type, ext in (\n        (\"application/json\", \"map\"),\n        (\"application/font-woff\", \"woff\"),\n        (\"application/font-woff2\", \"woff2\"),\n        (\"application/vnd.ms-fontobject\", \"eot\"),\n        (\"application/x-font-ttf\", \"ttf\"),\n        (\"application/x-font-ttf\", \"ttc\"),\n        (\"font/opentype\", \"otf\"),\n        (\"image/svg+xml\", \"svg\"),\n    ):\n        mimetypes.add_type(type, \".\" + ext)\n\n    from .importer import install\n\n    if yaml is None:\n        # `yaml` will be None when SENTRY_CONF is pointed\n        # directly to a file, in which case, this file must exist\n        if not os.path.exists(py):\n            if ctx:\n                raise click.ClickException(\n                    \"Configuration file does not exist. Use 'sentry init' to initialize the file.\"\n                )\n            raise ValueError(\n                \"Configuration file does not exist at '%s'\" % click.format_filename(py)\n            )\n    elif not os.path.exists(yaml) and not os.path.exists(py):\n        if ctx:\n            raise click.ClickException(\n                \"Configuration file does not exist. Use 'sentry init' to initialize the file.\"\n            )\n        raise ValueError(\"Configuration file does not exist at '%s'\" % click.format_filename(yaml))\n\n    # Add autoreload for config.yml file if needed\n    if yaml is not None and os.path.exists(yaml):\n        from sentry.utils.uwsgi import reload_on_change\n\n        reload_on_change(yaml)\n\n    os.environ[\"DJANGO_SETTINGS_MODULE\"] = \"sentry_config\"\n\n    install(\"sentry_config\", py, DEFAULT_SETTINGS_MODULE)\n\n    from django.conf import settings\n\n    # HACK: we need to force access of django.conf.settings to\n    # ensure we don't hit any import-driven recursive behavior\n    hasattr(settings, \"INSTALLED_APPS\")\n\n    from .initializer import initialize_app\n\n    initialize_app(\n        {\"config_path\": py, \"settings\": settings, \"options\": yaml},\n        skip_service_validation=skip_service_validation,\n    )\n\n    if os.environ.get(\"OPENAPIGENERATE\", False):\n        # see https://drf-spectacular.readthedocs.io/en/latest/customization.html#step-5-extensions\n        from sentry.apidocs import extensions  # NOQA\n\n    __installed = True\n\n\n__installed = False\n", "path": "src/sentry/runner/settings.py"}], "after_files": [{"content": "import os\nimport warnings\n\nimport click\n\nDEFAULT_SETTINGS_MODULE = \"sentry.conf.server\"\nDEFAULT_SETTINGS_CONF = \"config.yml\"\nDEFAULT_SETTINGS_OVERRIDE = \"sentry.conf.py\"\n\n\ndef generate_secret_key():\n    from django.utils.crypto import get_random_string\n\n    chars = \"abcdefghijklmnopqrstuvwxyz0123456789!@#%^&*(-_=+)\"\n    return get_random_string(50, chars)\n\n\ndef load_config_template(path, version=\"default\"):\n    from pkg_resources import resource_string\n\n    return resource_string(\"sentry\", f\"data/config/{path}.{version}\").decode(\"utf8\")\n\n\ndef generate_settings(dev=False):\n    \"\"\"\n    This command is run when ``default_path`` doesn't exist, or ``init`` is\n    run and returns a string representing the default data to put into their\n    settings file.\n    \"\"\"\n    context = {\n        \"secret_key\": generate_secret_key(),\n        \"debug_flag\": dev,\n        \"mail.backend\": \"console\" if dev else \"smtp\",\n    }\n\n    py = load_config_template(DEFAULT_SETTINGS_OVERRIDE, \"default\") % context\n    yaml = load_config_template(DEFAULT_SETTINGS_CONF, \"default\") % context\n    return py, yaml\n\n\ndef get_sentry_conf():\n    \"\"\"\n    Fetch the SENTRY_CONF value, either from the click context\n    if available, or SENTRY_CONF environment variable.\n    \"\"\"\n    try:\n        ctx = click.get_current_context()\n        return ctx.obj[\"config\"]\n    except (RuntimeError, KeyError, TypeError):\n        try:\n            return os.environ[\"SENTRY_CONF\"]\n        except KeyError:\n            return \"~/.sentry\"\n\n\ndef discover_configs():\n    \"\"\"\n    Discover the locations of three configuration components:\n     * Config directory (~/.sentry)\n     * Optional python config file (~/.sentry/sentry.conf.py)\n     * Optional yaml config (~/.sentry/config.yml)\n    \"\"\"\n    try:\n        config = os.environ[\"SENTRY_CONF\"]\n    except KeyError:\n        config = \"~/.sentry\"\n\n    config = os.path.expanduser(config)\n\n    # This is the old, now deprecated code path where SENTRY_CONF is pointed directly\n    # to a python file\n    if config.endswith((\".py\", \".conf\")) or os.path.isfile(config):\n        return (os.path.dirname(config), config, None)\n\n    return (\n        config,\n        os.path.join(config, DEFAULT_SETTINGS_OVERRIDE),\n        os.path.join(config, DEFAULT_SETTINGS_CONF),\n    )\n\n\ndef configure(ctx, py, yaml, skip_service_validation=False):\n    \"\"\"\n    Given the two different config files, set up the environment.\n\n    NOTE: Will only execute once, so it's safe to call multiple times.\n    \"\"\"\n    global __installed\n    if __installed:\n        return\n\n    # Make sure that our warnings are always displayed.\n    warnings.filterwarnings(\"default\", \"\", Warning, r\"^sentry\")\n\n    from django.utils.deprecation import RemovedInDjango30Warning\n\n    warnings.filterwarnings(action=\"ignore\", category=RemovedInDjango30Warning)\n\n    # Add in additional mimetypes that are useful for our static files\n    # which aren't common in default system registries\n    import mimetypes\n\n    for type, ext in (\n        (\"application/json\", \"map\"),\n        (\"application/font-woff\", \"woff\"),\n        (\"application/font-woff2\", \"woff2\"),\n        (\"application/vnd.ms-fontobject\", \"eot\"),\n        (\"application/x-font-ttf\", \"ttf\"),\n        (\"application/x-font-ttf\", \"ttc\"),\n        (\"font/opentype\", \"otf\"),\n        (\"image/svg+xml\", \"svg\"),\n        (\"text/plain\", \"log\"),\n    ):\n        mimetypes.add_type(type, \".\" + ext)\n\n    from .importer import install\n\n    if yaml is None:\n        # `yaml` will be None when SENTRY_CONF is pointed\n        # directly to a file, in which case, this file must exist\n        if not os.path.exists(py):\n            if ctx:\n                raise click.ClickException(\n                    \"Configuration file does not exist. Use 'sentry init' to initialize the file.\"\n                )\n            raise ValueError(\n                \"Configuration file does not exist at '%s'\" % click.format_filename(py)\n            )\n    elif not os.path.exists(yaml) and not os.path.exists(py):\n        if ctx:\n            raise click.ClickException(\n                \"Configuration file does not exist. Use 'sentry init' to initialize the file.\"\n            )\n        raise ValueError(\"Configuration file does not exist at '%s'\" % click.format_filename(yaml))\n\n    # Add autoreload for config.yml file if needed\n    if yaml is not None and os.path.exists(yaml):\n        from sentry.utils.uwsgi import reload_on_change\n\n        reload_on_change(yaml)\n\n    os.environ[\"DJANGO_SETTINGS_MODULE\"] = \"sentry_config\"\n\n    install(\"sentry_config\", py, DEFAULT_SETTINGS_MODULE)\n\n    from django.conf import settings\n\n    # HACK: we need to force access of django.conf.settings to\n    # ensure we don't hit any import-driven recursive behavior\n    hasattr(settings, \"INSTALLED_APPS\")\n\n    from .initializer import initialize_app\n\n    initialize_app(\n        {\"config_path\": py, \"settings\": settings, \"options\": yaml},\n        skip_service_validation=skip_service_validation,\n    )\n\n    if os.environ.get(\"OPENAPIGENERATE\", False):\n        # see https://drf-spectacular.readthedocs.io/en/latest/customization.html#step-5-extensions\n        from sentry.apidocs import extensions  # NOQA\n\n    __installed = True\n\n\n__installed = False\n", "path": "src/sentry/runner/settings.py"}]}