Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions otel/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
=============
OpenTelemetry
=============

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e51d09d42e4c3a9c8d2991835625afd971653978ff13aeac6aa7a4ac683499c6
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
:target: https://github.com/OCA/server-tools/tree/18.0/otel
:alt: OCA/server-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-otel
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module instruments Odoo with OpenTelemetry support, including
``traceparent`` support for distributed tracing and correlation. It also
includes optional support for instrumenting PostgreSQL (via psycopg2).

**Table of contents**

.. contents::
:local:

Installation
============

This module should be added to your addons directory just like any other
module. You should then add the module to your ``server_wide_modules``,
e.g.:

.. code:: ini

# odoo-server.conf

[options]
# ...
server_wide_modules = base,web,otel

You will also need to install the Python dependencies via pip:

.. code:: shell

pip3 install -r /path/to/otel/requirements.txt

Configuration
=============

The module aims to accomodate different use-cases, and as such, there
are quite a few configuration options. Generally, only a few basic
options are needed for most deployments.

Minimum Config
==============

For most basic use-cases, the following config will be sufficient.

*Note*: This assumes a collector is running locally, and accepts
``http/protobuf`` requests.

.. code:: ini

# odoo-server.conf
[options]
# ...

[otel]
# enable the module
enable = True

# these vars will be tacked onto *all* traces; you should set `service.name` at least
resource_attributes = service.name=odoo,odoo.version=18.0,deployment.environment=dev

# your OTLP endpoint
exporter_otlp_endpoint = http://localhost:4318/v1/traces

# you may also need to set this, if your collector wants gRPC
# exporter_otlp_protocol = grpc

Configuration options can be passed either by environment variables, or
set in the Odoo conf file. Environment variables take precedence. Option
names are consistent between these two options. To convert from env-var
toconf, simply strip the leading ``OTEL_``, and make lowercase. For
example:

- ``OTEL_ENABLE`` -> ``enable``
- ``OTEL_EXPORTER_OTLP_ENDPOINT`` -> ``exporter_otlp_endpoint``
- etc

Config Reference
================

Core Options
------------

- ``OTEL_ENABLE`` will enable (or disable) the module. Possible values:
``true`` or ``false``
- ``OTEL_RESOURCE_ATTRIBUTES`` should be a set of
``key1=value,key2=value`` pairs. You should set ``service.name`` at
least, and consider setting ``deployment.environment``
- ``OTEL_RESOURCE_ATTRIBUTES_SERVICE_NAME`` will override the
``service.name`` attribute in the Resource Attributes
- ``OTEL_RESOURCE_ATTRIBUTES_SERVICE_VERSION`` as above, but for the
``service.version`` attribute; useful if you want to set the version
dynamically (e.g., to a docker build hash, or git revision)
- ``OTEL_RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT`` as above, but for
the ``deployment.environment`` attribute (again, useful to be
configureable dynamically)

Collector Options
-----------------

- ``OTEL_EXPORTER_OTLP_PROTOCOL`` is the protocol the OTel SDK will use
to communicate with your collector. Possible values: ``grpc`` or
``http`` (which is an alias for ``http/protobuf``)
- ``OTEL_EXPORTER_OTLP_ENDPOINT`` is the collector endpoint. Examples
are:

- ``http://localhost:4317`` (gRPC)
- ``http://localhost:4318`` (http/protobuf)

- ``OTEL_EXPORTER_OTLP_HEADERS`` accepts a set of
``header=value,header2=value`` pairs, which will be sent along with
requests to the collector. This is useful for passing auth headers.
Examples are:

- ``exporter_otlp_headers = Authorization=Bearer 12345``

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20otel%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Ryan Cole

Contributors
------------

- Ryan Cole <admin@ryanc.me>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-ryanc-me| image:: https://github.com/ryanc-me.png?size=40px
:target: https://github.com/ryanc-me
:alt: ryanc-me

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-ryanc-me|

This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/18.0/otel>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
9 changes: 9 additions & 0 deletions otel/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- Cron tracing
- Add span/trace IDs to logs (https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/logging/logging.html)
- Allow enable/disable tracing per level (RPC, ORM, Cron, DB)
- For each type, a few options:
- Allowlist for methods (empty for all)
- Allow excluding particular models (ir.model.access, etc)
- Allow excluding model:method pairs?
- Only trace when parent is tracing (possible for DB?)
- PII handling
2 changes: 2 additions & 0 deletions otel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .post_load import post_load
from . import controllers
17 changes: 17 additions & 0 deletions otel/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2026 Ryan Cole (https://www.ryanc.me)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "OpenTelemetry",
"summary": "Fully featured OpenTelemetry integration for Odoo",
"author": "Ryan Cole, Odoo Community Association (OCA)",
"maintainers": ["ryanc-me"],
"category": "Technical",
"website": "https://github.com/OCA/server-tools",
"version": "18.0.1.0.0",
"license": "LGPL-3",
"installable": True,
"depends": ["web"],
"post_load": "post_load",
"sequence": 999,
}
100 changes: 100 additions & 0 deletions otel/bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import logging

from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor

from .config import OTelConfig

_logger = logging.getLogger(__name__)


def _build_resource(resource_attributes: dict) -> Resource:
return Resource(attributes=resource_attributes)


def _init_tracing(config: OTelConfig):
if not config.traces_exporter:
# _logger.info("OpenTelemetry tracing is not configured, skipping")
return

resource = _build_resource(config.resource_attributes)
provider = TracerProvider(resource=resource)

if config.traces_exporter.protocol == "grpc":
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)

exporter = OTLPSpanExporter(
endpoint=config.traces_exporter.endpoint,
headers=config.traces_exporter.headers,
insecure=config.traces_exporter.grpc_insecure,
)
provider.add_span_processor(BatchSpanProcessor(exporter))
elif config.traces_exporter.protocol == "http/protobuf":
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
)

exporter = OTLPSpanExporter(
endpoint=config.traces_exporter.endpoint,
headers=config.traces_exporter.headers,
)
provider.add_span_processor(SimpleSpanProcessor(exporter))
else:
_logger.error(
f"Invalid traces exporter protocol: {config.traces_exporter.protocol}"
)
return

trace.set_tracer_provider(provider)
_logger.info("OpenTelemetry tracing initialized")


def _init_metrics(config: OTelConfig):
if not config.metrics_exporter:
# _logger.info("OpenTelemetry metrics export is not configured, skipping")
return

_logger.warning(
"OpenTelemetry metrics export is configured but not implemented yet"
)


def _init_logs(config: OTelConfig):
if not config.logs_exporter:
# _logger.info("OpenTelemetry logs export is not configured, skipping")
return

_logger.warning("OpenTelemetry logs export is configured but not implemented yet")


def _init_psycopg2():
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor

Psycopg2Instrumentor().instrument()


_OTEL_INITIALIZED = False


def init_otel():
global _OTEL_INITIALIZED
if _OTEL_INITIALIZED:
return

config = OTelConfig.load()
if not config.enable:
_logger.info("OpenTelemetry is disabled by configuration")
return

_init_tracing(config)
_init_metrics(config)
_init_logs(config)
_init_psycopg2()

_OTEL_INITIALIZED = True

_logger.info("OpenTelemetry initialized")
Loading
Loading