From d1f35b2be4414ee69373649661335a3d8cce6fd2 Mon Sep 17 00:00:00 2001 From: "Moises Lopez - https://www.vauxoo.com/" Date: Sat, 12 Sep 2026 16:17:32 -0600 Subject: [PATCH] [IMP] sentry: configure through environment variables Every option can now come from ODOO_SENTRY_*, named after the option it carries: sentry_dsn is ODOO_SENTRY_DSN. That is the shape queue_job has been using for ODOO_QUEUE_JOB_* for years. Both sources stay supported and are merged per option, the environment winning where the two disagree, so an existing [sentry] section keeps working untouched. A container has two reasons to prefer the environment. Odoo reads only [options] from its configuration file and logs "unknown option ... in the config file" for anything else it finds there, which is why these options were moved out to a [sentry] section to begin with. And odoo-bin -s rewrites that file out of Odoo's own options, dropping every other section with it. The ODOO_ prefix keeps these apart from the SENTRY_* variables sentry-sdk reads on its own. SENTRY_DSN addresses whichever process the library runs in, and it cannot serve here anyway: the dsn is always passed explicitly, and sentry-sdk only falls back to the environment for an option it receives as None. Options that answer yes or no now go through to_bool. A configuration file and an environment variable can only deliver a string, and bool("False") is True, so sentry_enabled = False used to turn sentry on. --- sentry/README.rst | 48 +++++++++++++++ sentry/const.py | 52 +++++++++++++++++ sentry/hooks.py | 15 ++++- sentry/readme/CONFIGURE.md | 41 +++++++++++++ sentry/static/description/index.html | 81 ++++++++++++++++++++------ sentry/tests/test_client.py | 87 +++++++++++++++++++++++++++- 6 files changed, 302 insertions(+), 22 deletions(-) diff --git a/sentry/README.rst b/sentry/README.rst index e77d84932bf..c751eb570c9 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -72,6 +72,36 @@ your Odoo config file. Currently supported additional client arguments are: ``with_locals, max_breadcrumbs, release, environment, server_name, shutdown_timeout, in_app_include, in_app_exclude, default_integrations, dist, sample_rate, send_default_pii, http_proxy, https_proxy, request_bodies, debug, attach_stacktrace, ca_certs, propagate_traces, traces_sample_rate, auto_enabling_integrations``. +Environment variables +--------------------- + +Every option above can also be set through an environment variable, +named after the option it carries with an ``ODOO_`` prefix: +``sentry_dsn`` becomes ``ODOO_SENTRY_DSN``, +``sentry_traces_sample_rate`` becomes +``ODOO_SENTRY_TRACES_SAMPLE_RATE``. This is the same shape *queue_job* +uses for ``ODOO_QUEUE_JOB_*``. + +Both sources stay supported and are merged per option, the environment +winning where the two disagree. An existing ``[sentry]`` section keeps +working as it is, and a deployment that would rather not ship a +configuration file at all can set everything through the environment +instead. + +The ``ODOO_`` prefix is what keeps these apart from the ``SENTRY_*`` +variables *sentry-sdk* reads on its own. ``SENTRY_DSN`` addresses +whichever process the library runs in, so it is left alone rather than +reused here. + +Two reasons a container may prefer the environment: + +- Odoo reads only ``[options]`` from its configuration file, and logs + ``unknown option ... in the config file`` for anything there it does + not know. The ``[sentry]`` section is quiet, but it exists only + because these options had to leave ``[options]`` for that reason. +- ``odoo-bin -s`` rewrites the configuration file out of its own + options, which drops every other section, ``[sentry]`` included. + Example Odoo configuration -------------------------- @@ -100,6 +130,24 @@ options: sentry_odoo_dir = /home/odoo/odoo/ sentry_startup_message = true +Example environment +------------------- + +The same setup, with ``server_wide_modules`` left in the configuration +file because that one is Odoo's own option: + +:: + + ODOO_SENTRY_DSN=https://:@sentry.example.com/ + ODOO_SENTRY_ENABLED=true + ODOO_SENTRY_LOGGING_LEVEL=warn + ODOO_SENTRY_EXCLUDE_LOGGERS=werkzeug + ODOO_SENTRY_INCLUDE_CONTEXT=true + ODOO_SENTRY_ENVIRONMENT=production + ODOO_SENTRY_RELEASE=1.3.2 + ODOO_SENTRY_ODOO_DIR=/home/odoo/odoo/ + ODOO_SENTRY_STARTUP_MESSAGE=true + Usage ===== diff --git a/sentry/const.py b/sentry/const.py index f0205a6f0bc..18be6df9009 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import collections import logging +import os from sentry_sdk import HttpTransport from sentry_sdk.consts import DEFAULT_OPTIONS @@ -29,6 +30,26 @@ def to_float_if_defined(value): return float(value) +TRUTHY_VALUES = ("1", "on", "true", "yes") + + +def to_bool(value, default=False): + """Read a boolean out of a value that a configuration file or an environment + variable can only deliver as a string. + + ``bool("False")`` is ``True``, so a plain truth test on a string turns an option + the user switched off back on. + """ + if value is None: + return default + if isinstance(value, str): + value = value.strip().lower() + # An option present but left empty says nothing, so it is not an answer of + # "off": keep the default the caller asked for. + return default if not value else value in TRUTHY_VALUES + return bool(value) + + SentryOption = collections.namedtuple("SentryOption", ["key", "default", "converter"]) # Mapping of Odoo logging level -> Python stdlib logging library log level. @@ -132,3 +153,34 @@ def get_sentry_options(): ) return res + + +# ENV_OPTION_PREFIX is what the options are already called in the configuration +# file, so ODOO_SENTRY_DSN and sentry_dsn name the same option and neither source +# needs a translation table. +ENV_PREFIX = "ODOO_" +ENV_OPTION_PREFIX = "SENTRY_" + + +def get_options_from_env(environ=None): + """Return the options set through environment variables. + + Keys come back in the shape the configuration file uses, so the caller merges + both sources without caring where each value came from: ``ODOO_SENTRY_DSN`` + becomes ``sentry_dsn``. Every option is covered, including the ones this module + does not name itself, because the prefix is what selects them rather than a + list that would have to be kept up to date. + + The ``ODOO_`` prefix follows ``queue_job``, which reads ``ODOO_QUEUE_JOB_*`` + this way, and keeps these apart from the ``SENTRY_*`` variables ``sentry-sdk`` + reads on its own. ``SENTRY_DSN`` addresses whichever process the library runs + in, so reusing it here would point Odoo at a DSN meant for something else. + """ + if environ is None: + environ = os.environ + prefix = f"{ENV_PREFIX}{ENV_OPTION_PREFIX}" + return { + name[len(ENV_PREFIX) :].lower(): value + for name, value in environ.items() + if name.startswith(prefix) + } diff --git a/sentry/hooks.py b/sentry/hooks.py index 716592c6a3e..fe5adb9bfb8 100644 --- a/sentry/hooks.py +++ b/sentry/hooks.py @@ -101,7 +101,13 @@ def initialize_sentry(config): :param config: Sentry configuration :param client: class used to instantiate the sentry_sdk client. """ - enabled = config.get("sentry_enabled", False) + # An environment variable wins over the file, the precedence queue_job's + # jobrunner has been applying to ODOO_QUEUE_JOB_* for years. Both sources stay + # supported, so an existing [sentry] section keeps working untouched, and a + # deployment that would rather not carry a configuration file at all can set + # every option through the environment instead. + config = {**config, **const.get_options_from_env()} + enabled = const.to_bool(config.get("sentry_enabled", False)) if not (HAS_SENTRY_SDK and enabled): return _logger.info("Initializing sentry...") @@ -149,7 +155,10 @@ def initialize_sentry(config): client = sentry_sdk.init(**options) - sentry_sdk.set_tag("include_context", config.get("sentry_include_context", True)) + sentry_sdk.set_tag( + "include_context", + const.to_bool(config.get("sentry_include_context", True), default=True), + ) if exclude_loggers: for item in exclude_loggers: @@ -179,7 +188,7 @@ def sentry_application_call(self, environ, start_response): odoo.http.Application.__call__ = sentry_application_call - if config.get("sentry_startup_message", True) not in (False, "False", "false"): + if const.to_bool(config.get("sentry_startup_message", True), default=True): with sentry_sdk.new_scope() as scope: scope.set_extra("debug", False) sentry_sdk.capture_message("Starting Odoo Server", "info") diff --git a/sentry/readme/CONFIGURE.md b/sentry/readme/CONFIGURE.md index f5ef4cfb605..5538b1ec31c 100644 --- a/sentry/readme/CONFIGURE.md +++ b/sentry/readme/CONFIGURE.md @@ -9,6 +9,32 @@ be configured by prepending the argument name with *sentry\_* in your Odoo config file. Currently supported additional client arguments are: `with_locals, max_breadcrumbs, release, environment, server_name, shutdown_timeout, in_app_include, in_app_exclude, default_integrations, dist, sample_rate, send_default_pii, http_proxy, https_proxy, request_bodies, debug, attach_stacktrace, ca_certs, propagate_traces, traces_sample_rate, auto_enabling_integrations`. +## Environment variables + +Every option above can also be set through an environment variable, named after +the option it carries with an `ODOO_` prefix: `sentry_dsn` becomes +`ODOO_SENTRY_DSN`, `sentry_traces_sample_rate` becomes +`ODOO_SENTRY_TRACES_SAMPLE_RATE`. This is the same shape *queue_job* uses for +`ODOO_QUEUE_JOB_*`. + +Both sources stay supported and are merged per option, the environment winning +where the two disagree. An existing `[sentry]` section keeps working as it is, +and a deployment that would rather not ship a configuration file at all can set +everything through the environment instead. + +The `ODOO_` prefix is what keeps these apart from the `SENTRY_*` variables +*sentry-sdk* reads on its own. `SENTRY_DSN` addresses whichever process the +library runs in, so it is left alone rather than reused here. + +Two reasons a container may prefer the environment: + +- Odoo reads only `[options]` from its configuration file, and logs + `unknown option ... in the config file` for anything there it does not know. + The `[sentry]` section is quiet, but it exists only because these options had + to leave `[options]` for that reason. +- `odoo-bin -s` rewrites the configuration file out of its own options, which + drops every other section, `[sentry]` included. + ## Example Odoo configuration Below is an example of Odoo configuration file with *Odoo Sentry* @@ -33,3 +59,18 @@ options: sentry_release = 1.3.2 sentry_odoo_dir = /home/odoo/odoo/ sentry_startup_message = true + +## Example environment + +The same setup, with `server_wide_modules` left in the configuration file +because that one is Odoo's own option: + + ODOO_SENTRY_DSN=https://:@sentry.example.com/ + ODOO_SENTRY_ENABLED=true + ODOO_SENTRY_LOGGING_LEVEL=warn + ODOO_SENTRY_EXCLUDE_LOGGERS=werkzeug + ODOO_SENTRY_INCLUDE_CONTEXT=true + ODOO_SENTRY_ENVIRONMENT=production + ODOO_SENTRY_RELEASE=1.3.2 + ODOO_SENTRY_ODOO_DIR=/home/odoo/odoo/ + ODOO_SENTRY_STARTUP_MESSAGE=true diff --git a/sentry/static/description/index.html b/sentry/static/description/index.html index 5e3c5011d95..65ad2b9e336 100644 --- a/sentry/static/description/index.html +++ b/sentry/static/description/index.html @@ -382,17 +382,19 @@

Sentry