mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 11:10:54 +00:00
feat(backend): improve worker tracing (#9808)
* feat(backend): improve worker log * refactor tracing details * add tracing to gunicorn setup * add sqlite tracing * add system metrics * instument wsgi * make dbengine better accessible * fix instruction * instrument worker * track task scheduling * trace common tasks * patch in support for django q * trace various tasks * add trcing for other dbs * ignore coverage on tracing stuff * more ignorance
This commit is contained in:
@ -40,3 +40,18 @@ max_requests_jitter = 50
|
|||||||
|
|
||||||
# preload app so that the ready functions are only executed once
|
# preload app so that the ready functions are only executed once
|
||||||
preload_app = True
|
preload_app = True
|
||||||
|
|
||||||
|
|
||||||
|
def post_fork(server, worker):
|
||||||
|
"""Post-fork hook to set up logging for each worker."""
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
if not settings.TRACING_ENABLED:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Instrument gunicorm
|
||||||
|
from InvenTree.tracing import setup_instruments, setup_tracing
|
||||||
|
|
||||||
|
# Run tracing/logging instrumentation
|
||||||
|
setup_tracing(**settings.TRACING_DETAILS)
|
||||||
|
setup_instruments()
|
||||||
|
@ -13,6 +13,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
import django.conf.locale
|
import django.conf.locale
|
||||||
@ -33,7 +34,11 @@ from InvenTree.config import (
|
|||||||
)
|
)
|
||||||
from InvenTree.ready import isInMainThread
|
from InvenTree.ready import isInMainThread
|
||||||
from InvenTree.sentry import default_sentry_dsn, init_sentry
|
from InvenTree.sentry import default_sentry_dsn, init_sentry
|
||||||
from InvenTree.version import checkMinPythonVersion, inventreeApiVersion
|
from InvenTree.version import (
|
||||||
|
checkMinPythonVersion,
|
||||||
|
inventreeApiVersion,
|
||||||
|
inventreeCommitHash,
|
||||||
|
)
|
||||||
from users.oauth2_scopes import oauth2_scopes
|
from users.oauth2_scopes import oauth2_scopes
|
||||||
|
|
||||||
from . import config, locales
|
from . import config, locales
|
||||||
@ -636,25 +641,25 @@ It can be specified in config.yaml (or envvar) as either (for example):
|
|||||||
- django.db.backends.postgresql
|
- django.db.backends.postgresql
|
||||||
"""
|
"""
|
||||||
|
|
||||||
db_engine = db_config['ENGINE'].lower()
|
DB_ENGINE = db_config['ENGINE'].lower()
|
||||||
|
|
||||||
# Correct common misspelling
|
# Correct common misspelling
|
||||||
if db_engine == 'sqlite':
|
if DB_ENGINE == 'sqlite':
|
||||||
db_engine = 'sqlite3' # pragma: no cover
|
DB_ENGINE = 'sqlite3' # pragma: no cover
|
||||||
|
|
||||||
if db_engine in ['sqlite3', 'postgresql', 'mysql']:
|
if DB_ENGINE in ['sqlite3', 'postgresql', 'mysql']:
|
||||||
# Prepend the required python module string
|
# Prepend the required python module string
|
||||||
db_engine = f'django.db.backends.{db_engine}'
|
DB_ENGINE = f'django.db.backends.{DB_ENGINE}'
|
||||||
db_config['ENGINE'] = db_engine
|
db_config['ENGINE'] = DB_ENGINE
|
||||||
|
|
||||||
db_name = db_config['NAME']
|
db_name = db_config['NAME']
|
||||||
db_host = db_config.get('HOST', "''")
|
db_host = db_config.get('HOST', "''")
|
||||||
|
|
||||||
if 'sqlite' in db_engine:
|
if 'sqlite' in DB_ENGINE:
|
||||||
db_name = str(Path(db_name).resolve())
|
db_name = str(Path(db_name).resolve())
|
||||||
db_config['NAME'] = db_name
|
db_config['NAME'] = db_name
|
||||||
|
|
||||||
logger.info('DB_ENGINE: %s', db_engine)
|
logger.info('DB_ENGINE: %s', DB_ENGINE)
|
||||||
logger.info('DB_NAME: %s', db_name)
|
logger.info('DB_NAME: %s', db_name)
|
||||||
logger.info('DB_HOST: %s', db_host)
|
logger.info('DB_HOST: %s', db_host)
|
||||||
|
|
||||||
@ -675,7 +680,7 @@ if db_options is None:
|
|||||||
db_options = {}
|
db_options = {}
|
||||||
|
|
||||||
# Specific options for postgres backend
|
# Specific options for postgres backend
|
||||||
if 'postgres' in db_engine: # pragma: no cover
|
if 'postgres' in DB_ENGINE: # pragma: no cover
|
||||||
from django.db.backends.postgresql.psycopg_any import IsolationLevel
|
from django.db.backends.postgresql.psycopg_any import IsolationLevel
|
||||||
|
|
||||||
# Connection timeout
|
# Connection timeout
|
||||||
@ -748,7 +753,7 @@ if 'postgres' in db_engine: # pragma: no cover
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Specific options for MySql / MariaDB backend
|
# Specific options for MySql / MariaDB backend
|
||||||
elif 'mysql' in db_engine: # pragma: no cover
|
elif 'mysql' in DB_ENGINE: # pragma: no cover
|
||||||
# TODO TCP time outs and keepalives
|
# TODO TCP time outs and keepalives
|
||||||
|
|
||||||
# MariaDB's default isolation level is Repeatable Read which is
|
# MariaDB's default isolation level is Repeatable Read which is
|
||||||
@ -766,7 +771,7 @@ elif 'mysql' in db_engine: # pragma: no cover
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Specific options for sqlite backend
|
# Specific options for sqlite backend
|
||||||
elif 'sqlite' in db_engine:
|
elif 'sqlite' in DB_ENGINE:
|
||||||
# TODO: Verify timeouts are not an issue because no network is involved for SQLite
|
# TODO: Verify timeouts are not an issue because no network is involved for SQLite
|
||||||
|
|
||||||
# SQLite's default isolation level is Serializable due to SQLite's
|
# SQLite's default isolation level is Serializable due to SQLite's
|
||||||
@ -782,7 +787,7 @@ db_config['OPTIONS'] = db_options
|
|||||||
db_config['TEST'] = {'CHARSET': 'utf8'}
|
db_config['TEST'] = {'CHARSET': 'utf8'}
|
||||||
|
|
||||||
# Set collation option for mysql test database
|
# Set collation option for mysql test database
|
||||||
if 'mysql' in db_engine:
|
if 'mysql' in DB_ENGINE:
|
||||||
db_config['TEST']['COLLATION'] = 'utf8_general_ci' # pragma: no cover
|
db_config['TEST']['COLLATION'] = 'utf8_general_ci' # pragma: no cover
|
||||||
|
|
||||||
DATABASES = {'default': db_config}
|
DATABASES = {'default': db_config}
|
||||||
@ -801,6 +806,7 @@ inventree_tags = {
|
|||||||
'docker': DOCKER,
|
'docker': DOCKER,
|
||||||
'debug': DEBUG,
|
'debug': DEBUG,
|
||||||
'remote': REMOTE_LOGIN,
|
'remote': REMOTE_LOGIN,
|
||||||
|
'commit': inventreeCommitHash(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# sentry.io integration for error reporting
|
# sentry.io integration for error reporting
|
||||||
@ -821,6 +827,7 @@ if SENTRY_ENABLED and SENTRY_DSN and not TESTING: # pragma: no cover
|
|||||||
TRACING_ENABLED = get_boolean_setting(
|
TRACING_ENABLED = get_boolean_setting(
|
||||||
'INVENTREE_TRACING_ENABLED', 'tracing.enabled', False
|
'INVENTREE_TRACING_ENABLED', 'tracing.enabled', False
|
||||||
)
|
)
|
||||||
|
TRACING_DETAILS: Optional[dict] = None
|
||||||
|
|
||||||
if TRACING_ENABLED: # pragma: no cover
|
if TRACING_ENABLED: # pragma: no cover
|
||||||
from InvenTree.tracing import setup_instruments, setup_tracing
|
from InvenTree.tracing import setup_instruments, setup_tracing
|
||||||
@ -834,34 +841,41 @@ if TRACING_ENABLED: # pragma: no cover
|
|||||||
if _t_endpoint:
|
if _t_endpoint:
|
||||||
logger.info('OpenTelemetry tracing enabled')
|
logger.info('OpenTelemetry tracing enabled')
|
||||||
|
|
||||||
_t_resources = get_setting(
|
TRACING_DETAILS = (
|
||||||
'INVENTREE_TRACING_RESOURCES',
|
TRACING_DETAILS
|
||||||
'tracing.resources',
|
if TRACING_DETAILS
|
||||||
default_value=None,
|
else {
|
||||||
typecast=dict,
|
'endpoint': _t_endpoint,
|
||||||
|
'headers': _t_headers,
|
||||||
|
'resources_input': {
|
||||||
|
**{'inventree.env.' + k: v for k, v in inventree_tags.items()},
|
||||||
|
**get_setting(
|
||||||
|
'INVENTREE_TRACING_RESOURCES',
|
||||||
|
'tracing.resources',
|
||||||
|
default_value=None,
|
||||||
|
typecast=dict,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'console': get_boolean_setting(
|
||||||
|
'INVENTREE_TRACING_CONSOLE', 'tracing.console', False
|
||||||
|
),
|
||||||
|
'auth': get_setting(
|
||||||
|
'INVENTREE_TRACING_AUTH',
|
||||||
|
'tracing.auth',
|
||||||
|
default_value=None,
|
||||||
|
typecast=dict,
|
||||||
|
),
|
||||||
|
'is_http': get_setting(
|
||||||
|
'INVENTREE_TRACING_IS_HTTP', 'tracing.is_http', True
|
||||||
|
),
|
||||||
|
'append_http': get_boolean_setting(
|
||||||
|
'INVENTREE_TRACING_APPEND_HTTP', 'tracing.append_http', True
|
||||||
|
),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
cstm_tags = {'inventree.env.' + k: v for k, v in inventree_tags.items()}
|
|
||||||
tracing_resources = {**cstm_tags, **_t_resources}
|
|
||||||
|
|
||||||
setup_tracing(
|
|
||||||
_t_endpoint,
|
|
||||||
_t_headers,
|
|
||||||
resources_input=tracing_resources,
|
|
||||||
console=get_boolean_setting(
|
|
||||||
'INVENTREE_TRACING_CONSOLE', 'tracing.console', False
|
|
||||||
),
|
|
||||||
auth=get_setting(
|
|
||||||
'INVENTREE_TRACING_AUTH',
|
|
||||||
'tracing.auth',
|
|
||||||
default_value=None,
|
|
||||||
typecast=dict,
|
|
||||||
),
|
|
||||||
is_http=get_setting('INVENTREE_TRACING_IS_HTTP', 'tracing.is_http', True),
|
|
||||||
append_http=get_boolean_setting(
|
|
||||||
'INVENTREE_TRACING_APPEND_HTTP', 'tracing.append_http', True
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# Run tracing/logging instrumentation
|
# Run tracing/logging instrumentation
|
||||||
|
setup_tracing(**TRACING_DETAILS)
|
||||||
setup_instruments()
|
setup_instruments()
|
||||||
else:
|
else:
|
||||||
logger.warning('OpenTelemetry tracing not enabled because endpoint is not set')
|
logger.warning('OpenTelemetry tracing not enabled because endpoint is not set')
|
||||||
|
@ -26,6 +26,7 @@ from maintenance_mode.core import (
|
|||||||
maintenance_mode_on,
|
maintenance_mode_on,
|
||||||
set_maintenance_mode,
|
set_maintenance_mode,
|
||||||
)
|
)
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
from common.settings import get_global_setting, set_global_setting
|
from common.settings import get_global_setting, set_global_setting
|
||||||
from InvenTree.config import get_setting
|
from InvenTree.config import get_setting
|
||||||
@ -34,6 +35,7 @@ from plugin import registry
|
|||||||
from .version import isInvenTreeUpToDate
|
from .version import isInvenTreeUpToDate
|
||||||
|
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
|
|
||||||
|
|
||||||
def schedule_task(taskname, **kwargs):
|
def schedule_task(taskname, **kwargs):
|
||||||
@ -205,7 +207,8 @@ def offload_task(
|
|||||||
# Running as asynchronous task
|
# Running as asynchronous task
|
||||||
try:
|
try:
|
||||||
task = AsyncTask(taskname, *args, group=group, **kwargs)
|
task = AsyncTask(taskname, *args, group=group, **kwargs)
|
||||||
task.run()
|
with tracer.start_as_current_span(f'async worker: {taskname}'):
|
||||||
|
task.run()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise_warning(f"WARNING: '{taskname}' not offloaded - Function not found")
|
raise_warning(f"WARNING: '{taskname}' not offloaded - Function not found")
|
||||||
return False
|
return False
|
||||||
@ -257,7 +260,8 @@ def offload_task(
|
|||||||
|
|
||||||
# Workers are not running: run it as synchronous task
|
# Workers are not running: run it as synchronous task
|
||||||
try:
|
try:
|
||||||
_func(*args, **kwargs)
|
with tracer.start_as_current_span(f'sync worker: {taskname}'):
|
||||||
|
_func(*args, **kwargs)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log_error('InvenTree.offload_task')
|
log_error('InvenTree.offload_task')
|
||||||
raise_warning(f"WARNING: '{taskname}' failed due to {exc!s}")
|
raise_warning(f"WARNING: '{taskname}' failed due to {exc!s}")
|
||||||
@ -345,6 +349,7 @@ def scheduled_task(
|
|||||||
return _task_wrapper
|
return _task_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('heartbeat')
|
||||||
@scheduled_task(ScheduledTask.MINUTES, 5)
|
@scheduled_task(ScheduledTask.MINUTES, 5)
|
||||||
def heartbeat():
|
def heartbeat():
|
||||||
"""Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running.
|
"""Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running.
|
||||||
@ -373,6 +378,7 @@ def heartbeat():
|
|||||||
task.delete()
|
task.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('delete_successful_tasks')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_successful_tasks():
|
def delete_successful_tasks():
|
||||||
"""Delete successful task logs which are older than a specified period."""
|
"""Delete successful task logs which are older than a specified period."""
|
||||||
@ -395,6 +401,7 @@ def delete_successful_tasks():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('delete_failed_tasks')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_failed_tasks():
|
def delete_failed_tasks():
|
||||||
"""Delete failed task logs which are older than a specified period."""
|
"""Delete failed task logs which are older than a specified period."""
|
||||||
@ -415,6 +422,7 @@ def delete_failed_tasks():
|
|||||||
logger.info("Could not perform 'delete_failed_tasks' - App registry not ready")
|
logger.info("Could not perform 'delete_failed_tasks' - App registry not ready")
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('delete_old_error_logs')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_old_error_logs():
|
def delete_old_error_logs():
|
||||||
"""Delete old error logs from the server."""
|
"""Delete old error logs from the server."""
|
||||||
@ -437,6 +445,7 @@ def delete_old_error_logs():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('delete_old_notifications')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_old_notifications():
|
def delete_old_notifications():
|
||||||
"""Delete old notification logs."""
|
"""Delete old notification logs."""
|
||||||
@ -464,6 +473,7 @@ def delete_old_notifications():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('check_for_updates')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_for_updates():
|
def check_for_updates():
|
||||||
"""Check if there is an update for InvenTree."""
|
"""Check if there is an update for InvenTree."""
|
||||||
@ -547,6 +557,7 @@ def check_for_updates():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('update_exchange_rates')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def update_exchange_rates(force: bool = False):
|
def update_exchange_rates(force: bool = False):
|
||||||
"""Update currency exchange rates.
|
"""Update currency exchange rates.
|
||||||
@ -597,6 +608,7 @@ def update_exchange_rates(force: bool = False):
|
|||||||
logger.exception('Error updating exchange rates: %s', str(type(e)))
|
logger.exception('Error updating exchange rates: %s', str(type(e)))
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('run_backup')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def run_backup():
|
def run_backup():
|
||||||
"""Run the backup command."""
|
"""Run the backup command."""
|
||||||
@ -628,6 +640,7 @@ def get_migration_plan():
|
|||||||
return plan
|
return plan
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('check_for_migrations')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_for_migrations(force: bool = False, reload_registry: bool = True) -> bool:
|
def check_for_migrations(force: bool = False, reload_registry: bool = True) -> bool:
|
||||||
"""Checks if migrations are needed.
|
"""Checks if migrations are needed.
|
||||||
@ -720,6 +733,7 @@ def email_user(user_id: int, subject: str, message: str) -> None:
|
|||||||
send_email(subject, message, [email])
|
send_email(subject, message, [email])
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('run_oauth_maintenance')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def run_oauth_maintenance():
|
def run_oauth_maintenance():
|
||||||
"""Run the OAuth maintenance task(s)."""
|
"""Run the OAuth maintenance task(s)."""
|
||||||
|
@ -4,10 +4,14 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from opentelemetry import metrics, trace
|
from opentelemetry import metrics, trace
|
||||||
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
||||||
from opentelemetry.instrumentation.redis import RedisInstrumentor
|
from opentelemetry.instrumentation.redis import RedisInstrumentor
|
||||||
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||||
|
from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor
|
||||||
|
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
|
||||||
from opentelemetry.sdk import _logs as logs
|
from opentelemetry.sdk import _logs as logs
|
||||||
from opentelemetry.sdk import resources
|
from opentelemetry.sdk import resources
|
||||||
from opentelemetry.sdk._logs import export as logs_export
|
from opentelemetry.sdk._logs import export as logs_export
|
||||||
@ -22,6 +26,9 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExport
|
|||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
from InvenTree.version import inventreeVersion
|
from InvenTree.version import inventreeVersion
|
||||||
|
|
||||||
|
TRACE_PROC = None
|
||||||
|
TRACE_PROV = None
|
||||||
|
|
||||||
|
|
||||||
def setup_tracing(
|
def setup_tracing(
|
||||||
endpoint: str,
|
endpoint: str,
|
||||||
@ -31,7 +38,7 @@ def setup_tracing(
|
|||||||
auth: Optional[dict] = None,
|
auth: Optional[dict] = None,
|
||||||
is_http: bool = False,
|
is_http: bool = False,
|
||||||
append_http: bool = True,
|
append_http: bool = True,
|
||||||
):
|
): # pragma: no cover
|
||||||
"""Set up tracing for the application in the current context.
|
"""Set up tracing for the application in the current context.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -68,9 +75,14 @@ def setup_tracing(
|
|||||||
headers = {k: v for k, v in headers.items() if v is not None}
|
headers = {k: v for k, v in headers.items() if v is not None}
|
||||||
|
|
||||||
# Initialize the OTLP Resource
|
# Initialize the OTLP Resource
|
||||||
|
service_name = 'Unkown'
|
||||||
|
if InvenTree.ready.isInServerThread():
|
||||||
|
service_name = 'BACKEND'
|
||||||
|
elif InvenTree.ready.isInWorkerThread():
|
||||||
|
service_name = 'WORKER'
|
||||||
resource = resources.Resource(
|
resource = resources.Resource(
|
||||||
attributes={
|
attributes={
|
||||||
resources.SERVICE_NAME: 'BACKEND',
|
resources.SERVICE_NAME: service_name,
|
||||||
resources.SERVICE_NAMESPACE: 'INVENTREE',
|
resources.SERVICE_NAMESPACE: 'INVENTREE',
|
||||||
resources.SERVICE_VERSION: inventreeVersion(),
|
resources.SERVICE_VERSION: inventreeVersion(),
|
||||||
**resources_input,
|
**resources_input,
|
||||||
@ -141,9 +153,34 @@ def setup_tracing(
|
|||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
global TRACE_PROC, TRACE_PROV
|
||||||
|
TRACE_PROC = trace_processor
|
||||||
|
TRACE_PROV = trace_provider
|
||||||
|
|
||||||
def setup_instruments():
|
|
||||||
|
def setup_instruments(): # pragma: no cover
|
||||||
"""Run auto-insturmentation for OpenTelemetry tracing."""
|
"""Run auto-insturmentation for OpenTelemetry tracing."""
|
||||||
DjangoInstrumentor().instrument()
|
DjangoInstrumentor().instrument()
|
||||||
RedisInstrumentor().instrument()
|
RedisInstrumentor().instrument()
|
||||||
RequestsInstrumentor().instrument()
|
RequestsInstrumentor().instrument()
|
||||||
|
SystemMetricsInstrumentor().instrument()
|
||||||
|
|
||||||
|
# DBs
|
||||||
|
if settings.DB_ENGINE == 'sqlite':
|
||||||
|
SQLite3Instrumentor().instrument()
|
||||||
|
elif settings.DB_ENGINE == 'postgresql':
|
||||||
|
try:
|
||||||
|
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
|
||||||
|
|
||||||
|
PsycopgInstrumentor().instrument(
|
||||||
|
enable_commenter=False, commenter_options={}
|
||||||
|
)
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
elif settings.DB_ENGINE == 'mysql':
|
||||||
|
try:
|
||||||
|
from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor
|
||||||
|
|
||||||
|
PyMySQLInstrumentor().instrument()
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
@ -306,8 +306,7 @@ def inventreePlatform():
|
|||||||
|
|
||||||
def inventreeDatabase():
|
def inventreeDatabase():
|
||||||
"""Return the InvenTree database backend e.g. 'postgresql'."""
|
"""Return the InvenTree database backend e.g. 'postgresql'."""
|
||||||
db = settings.DATABASES['default']
|
return settings.DB_ENGINE
|
||||||
return db.get('ENGINE', None).replace('django.db.backends.', '')
|
|
||||||
|
|
||||||
|
|
||||||
def inventree_identifier(override_announce: bool = False):
|
def inventree_identifier(override_announce: bool = False):
|
||||||
|
@ -10,8 +10,11 @@ import os # pragma: no cover
|
|||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application # pragma: no cover
|
from django.core.wsgi import get_wsgi_application # pragma: no cover
|
||||||
|
|
||||||
|
from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
|
||||||
|
|
||||||
os.environ.setdefault(
|
os.environ.setdefault(
|
||||||
'DJANGO_SETTINGS_MODULE', 'InvenTree.settings'
|
'DJANGO_SETTINGS_MODULE', 'InvenTree.settings'
|
||||||
) # pragma: no cover
|
) # pragma: no cover
|
||||||
|
|
||||||
application = get_wsgi_application() # pragma: no cover
|
application = get_wsgi_application() # pragma: no cover
|
||||||
|
application = OpenTelemetryMiddleware(application)
|
||||||
|
@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from allauth.account.models import EmailAddress
|
from allauth.account.models import EmailAddress
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
import build.models as build_models
|
import build.models as build_models
|
||||||
import common.notifications
|
import common.notifications
|
||||||
@ -22,9 +23,11 @@ from build.status_codes import BuildStatusGroups
|
|||||||
from InvenTree.ready import isImportingData
|
from InvenTree.ready import isImportingData
|
||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('auto_allocate_build')
|
||||||
def auto_allocate_build(build_id: int, **kwargs):
|
def auto_allocate_build(build_id: int, **kwargs):
|
||||||
"""Run auto-allocation for a specified BuildOrder."""
|
"""Run auto-allocation for a specified BuildOrder."""
|
||||||
build_order = build_models.Build.objects.filter(pk=build_id).first()
|
build_order = build_models.Build.objects.filter(pk=build_id).first()
|
||||||
@ -39,6 +42,7 @@ def auto_allocate_build(build_id: int, **kwargs):
|
|||||||
build_order.auto_allocate_stock(**kwargs)
|
build_order.auto_allocate_stock(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('complete_build_allocations')
|
||||||
def complete_build_allocations(build_id: int, user_id: int):
|
def complete_build_allocations(build_id: int, user_id: int):
|
||||||
"""Complete build allocations for a specified BuildOrder."""
|
"""Complete build allocations for a specified BuildOrder."""
|
||||||
build_order = build_models.Build.objects.filter(pk=build_id).first()
|
build_order = build_models.Build.objects.filter(pk=build_id).first()
|
||||||
@ -65,6 +69,7 @@ def complete_build_allocations(build_id: int, user_id: int):
|
|||||||
build_order.complete_allocations(user)
|
build_order.complete_allocations(user)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('update_build_order_lines')
|
||||||
def update_build_order_lines(bom_item_pk: int):
|
def update_build_order_lines(bom_item_pk: int):
|
||||||
"""Update all BuildOrderLineItem objects which reference a particular BomItem.
|
"""Update all BuildOrderLineItem objects which reference a particular BomItem.
|
||||||
|
|
||||||
@ -111,6 +116,7 @@ def update_build_order_lines(bom_item_pk: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('check_build_stock')
|
||||||
def check_build_stock(build: build_models.Build):
|
def check_build_stock(build: build_models.Build):
|
||||||
"""Check the required stock for a newly created build order.
|
"""Check the required stock for a newly created build order.
|
||||||
|
|
||||||
@ -196,6 +202,7 @@ def check_build_stock(build: build_models.Build):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('notify_overdue_build_order')
|
||||||
def notify_overdue_build_order(bo: build_models.Build):
|
def notify_overdue_build_order(bo: build_models.Build):
|
||||||
"""Notify appropriate users that a Build has just become 'overdue'."""
|
"""Notify appropriate users that a Build has just become 'overdue'."""
|
||||||
targets = []
|
targets = []
|
||||||
@ -229,6 +236,7 @@ def notify_overdue_build_order(bo: build_models.Build):
|
|||||||
trigger_event(event_name, build_order=bo.pk)
|
trigger_event(event_name, build_order=bo.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('check_overdue_build_orders')
|
||||||
@InvenTree.tasks.scheduled_task(InvenTree.tasks.ScheduledTask.DAILY)
|
@InvenTree.tasks.scheduled_task(InvenTree.tasks.ScheduledTask.DAILY)
|
||||||
def check_overdue_build_orders():
|
def check_overdue_build_orders():
|
||||||
"""Check if any outstanding BuildOrders have just become overdue.
|
"""Check if any outstanding BuildOrders have just become overdue.
|
||||||
|
@ -28,14 +28,16 @@ from django.core.validators import MinValueValidator
|
|||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||||
from django.dispatch.dispatcher import receiver
|
from django.dispatch import receiver
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from django_q.signals import post_spawn
|
||||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||||
from djmoney.contrib.exchange.models import convert_money
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
|
from opentelemetry import trace
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ from generic.states import ColorEnum
|
|||||||
from generic.states.custom import state_color_mappings
|
from generic.states.custom import state_color_mappings
|
||||||
from InvenTree.cache import get_session_cache, set_session_cache
|
from InvenTree.cache import get_session_cache, set_session_cache
|
||||||
from InvenTree.sanitizer import sanitize_svg
|
from InvenTree.sanitizer import sanitize_svg
|
||||||
|
from InvenTree.tracing import TRACE_PROC, TRACE_PROV
|
||||||
|
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
@ -2414,3 +2417,16 @@ class DataOutput(models.Model):
|
|||||||
output = models.FileField(upload_to='data_output', blank=True, null=True)
|
output = models.FileField(upload_to='data_output', blank=True, null=True)
|
||||||
|
|
||||||
errors = models.JSONField(blank=True, null=True)
|
errors = models.JSONField(blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
# region tracing for django q
|
||||||
|
if TRACE_PROC: # pragma: no cover
|
||||||
|
|
||||||
|
@receiver(post_spawn)
|
||||||
|
def spwan_callback(sender, proc_name, **kwargs):
|
||||||
|
"""Callback to patch in tracing support."""
|
||||||
|
TRACE_PROV.add_span_processor(TRACE_PROC)
|
||||||
|
trace.set_tracer_provider(TRACE_PROV)
|
||||||
|
trace.get_tracer(__name__)
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
@ -11,6 +11,7 @@ from django.utils import timezone
|
|||||||
import feedparser
|
import feedparser
|
||||||
import requests
|
import requests
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
@ -18,9 +19,11 @@ from InvenTree.helpers_model import getModelsWithMixin
|
|||||||
from InvenTree.models import InvenTreeNotesMixin
|
from InvenTree.models import InvenTreeNotesMixin
|
||||||
from InvenTree.tasks import ScheduledTask, scheduled_task
|
from InvenTree.tasks import ScheduledTask, scheduled_task
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('cleanup_old_data_outputs')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def cleanup_old_data_outputs():
|
def cleanup_old_data_outputs():
|
||||||
"""Remove old data outputs from the database."""
|
"""Remove old data outputs from the database."""
|
||||||
@ -32,6 +35,7 @@ def cleanup_old_data_outputs():
|
|||||||
output.delete()
|
output.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('delete_old_notifications')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_old_notifications():
|
def delete_old_notifications():
|
||||||
"""Remove old notifications from the database.
|
"""Remove old notifications from the database.
|
||||||
@ -52,6 +56,7 @@ def delete_old_notifications():
|
|||||||
NotificationEntry.objects.filter(updated__lte=before).delete()
|
NotificationEntry.objects.filter(updated__lte=before).delete()
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('update_news_feed')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def update_news_feed():
|
def update_news_feed():
|
||||||
"""Update the newsfeed."""
|
"""Update the newsfeed."""
|
||||||
@ -105,6 +110,7 @@ def update_news_feed():
|
|||||||
logger.info('update_news_feed: Sync done')
|
logger.info('update_news_feed: Sync done')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('delete_old_notes_images')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def delete_old_notes_images():
|
def delete_old_notes_images():
|
||||||
"""Remove old notes images from the database.
|
"""Remove old notes images from the database.
|
||||||
|
@ -11,3 +11,18 @@ max_requests_jitter = 50
|
|||||||
|
|
||||||
# preload app so that the ready functions are only executed once
|
# preload app so that the ready functions are only executed once
|
||||||
preload_app = True
|
preload_app = True
|
||||||
|
|
||||||
|
|
||||||
|
def post_fork(server, worker):
|
||||||
|
"""Post-fork hook to set up logging for each worker."""
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
if not settings.TRACING_ENABLED:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Instrument gunicorm
|
||||||
|
from InvenTree.tracing import setup_instruments, setup_tracing
|
||||||
|
|
||||||
|
# Run tracing/logging instrumentation
|
||||||
|
setup_tracing(**settings.TRACING_DETAILS)
|
||||||
|
setup_instruments()
|
||||||
|
@ -8,6 +8,7 @@ from django.db.models import F
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
import common.notifications
|
import common.notifications
|
||||||
import InvenTree.helpers_model
|
import InvenTree.helpers_model
|
||||||
@ -22,9 +23,11 @@ from order.status_codes import (
|
|||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
from users.models import Owner
|
from users.models import Owner
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('notify_overdue_purchase_order')
|
||||||
def notify_overdue_purchase_order(po: order.models.PurchaseOrder) -> None:
|
def notify_overdue_purchase_order(po: order.models.PurchaseOrder) -> None:
|
||||||
"""Notify users that a PurchaseOrder has just become 'overdue'.
|
"""Notify users that a PurchaseOrder has just become 'overdue'.
|
||||||
|
|
||||||
@ -62,6 +65,7 @@ def notify_overdue_purchase_order(po: order.models.PurchaseOrder) -> None:
|
|||||||
trigger_event(event_name, purchase_order=po.pk)
|
trigger_event(event_name, purchase_order=po.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('scheduled_task')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_overdue_purchase_orders():
|
def check_overdue_purchase_orders():
|
||||||
"""Check if any outstanding PurchaseOrders have just become overdue.
|
"""Check if any outstanding PurchaseOrders have just become overdue.
|
||||||
@ -97,6 +101,7 @@ def check_overdue_purchase_orders():
|
|||||||
notified_orders.add(line.order.pk)
|
notified_orders.add(line.order.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('notify_overdue_sales_order')
|
||||||
def notify_overdue_sales_order(so: order.models.SalesOrder) -> None:
|
def notify_overdue_sales_order(so: order.models.SalesOrder) -> None:
|
||||||
"""Notify appropriate users that a SalesOrder has just become 'overdue'."""
|
"""Notify appropriate users that a SalesOrder has just become 'overdue'."""
|
||||||
targets: list[User, Group, Owner] = []
|
targets: list[User, Group, Owner] = []
|
||||||
@ -130,6 +135,7 @@ def notify_overdue_sales_order(so: order.models.SalesOrder) -> None:
|
|||||||
trigger_event(event_name, sales_order=so.pk)
|
trigger_event(event_name, sales_order=so.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('scheduled_task')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_overdue_sales_orders():
|
def check_overdue_sales_orders():
|
||||||
"""Check if any outstanding SalesOrders have just become overdue.
|
"""Check if any outstanding SalesOrders have just become overdue.
|
||||||
@ -162,6 +168,7 @@ def check_overdue_sales_orders():
|
|||||||
notified_orders.add(line.order.pk)
|
notified_orders.add(line.order.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('notify_overdue_return_order')
|
||||||
def notify_overdue_return_order(ro: order.models.ReturnOrder) -> None:
|
def notify_overdue_return_order(ro: order.models.ReturnOrder) -> None:
|
||||||
"""Notify appropriate users that a ReturnOrder has just become 'overdue'."""
|
"""Notify appropriate users that a ReturnOrder has just become 'overdue'."""
|
||||||
targets: list[User, Group, Owner] = []
|
targets: list[User, Group, Owner] = []
|
||||||
@ -195,6 +202,7 @@ def notify_overdue_return_order(ro: order.models.ReturnOrder) -> None:
|
|||||||
trigger_event(event_name, return_order=ro.pk)
|
trigger_event(event_name, return_order=ro.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('check_overdue_return_orders')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_overdue_return_orders():
|
def check_overdue_return_orders():
|
||||||
"""Check if any outstanding return orders have just become overdue.
|
"""Check if any outstanding return orders have just become overdue.
|
||||||
@ -227,6 +235,7 @@ def check_overdue_return_orders():
|
|||||||
notified_orders.add(line.order.pk)
|
notified_orders.add(line.order.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('complete_sales_order_shipment')
|
||||||
def complete_sales_order_shipment(shipment_id: int, user_id: int) -> None:
|
def complete_sales_order_shipment(shipment_id: int, user_id: int) -> None:
|
||||||
"""Complete allocations for a pending shipment against a SalesOrder.
|
"""Complete allocations for a pending shipment against a SalesOrder.
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
import common.currency
|
import common.currency
|
||||||
import common.notifications
|
import common.notifications
|
||||||
@ -25,9 +26,11 @@ from InvenTree.tasks import (
|
|||||||
scheduled_task,
|
scheduled_task,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('notify_low_stock')
|
||||||
def notify_low_stock(part: part_models.Part):
|
def notify_low_stock(part: part_models.Part):
|
||||||
"""Notify interested users that a part is 'low stock'.
|
"""Notify interested users that a part is 'low stock'.
|
||||||
|
|
||||||
@ -52,6 +55,7 @@ def notify_low_stock(part: part_models.Part):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('notify_low_stock_if_required')
|
||||||
def notify_low_stock_if_required(part_id: int):
|
def notify_low_stock_if_required(part_id: int):
|
||||||
"""Check if the stock quantity has fallen below the minimum threshold of part.
|
"""Check if the stock quantity has fallen below the minimum threshold of part.
|
||||||
|
|
||||||
@ -73,6 +77,7 @@ def notify_low_stock_if_required(part_id: int):
|
|||||||
InvenTree.tasks.offload_task(notify_low_stock, p, group='notification')
|
InvenTree.tasks.offload_task(notify_low_stock, p, group='notification')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('update_part_pricing')
|
||||||
def update_part_pricing(pricing: part_models.PartPricing, counter: int = 0):
|
def update_part_pricing(pricing: part_models.PartPricing, counter: int = 0):
|
||||||
"""Update cached pricing data for the specified PartPricing instance.
|
"""Update cached pricing data for the specified PartPricing instance.
|
||||||
|
|
||||||
@ -89,6 +94,7 @@ def update_part_pricing(pricing: part_models.PartPricing, counter: int = 0):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('check_missing_pricing')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_missing_pricing(limit=250):
|
def check_missing_pricing(limit=250):
|
||||||
"""Check for parts with missing or outdated pricing information.
|
"""Check for parts with missing or outdated pricing information.
|
||||||
@ -144,6 +150,7 @@ def check_missing_pricing(limit=250):
|
|||||||
pricing.schedule_for_update()
|
pricing.schedule_for_update()
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('scheduled_stocktake_reports')
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def scheduled_stocktake_reports():
|
def scheduled_stocktake_reports():
|
||||||
"""Scheduled tasks for creating automated stocktake reports.
|
"""Scheduled tasks for creating automated stocktake reports.
|
||||||
@ -189,6 +196,7 @@ def scheduled_stocktake_reports():
|
|||||||
record_task_success('STOCKTAKE_RECENT_REPORT')
|
record_task_success('STOCKTAKE_RECENT_REPORT')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('rebuild_parameters')
|
||||||
def rebuild_parameters(template_id):
|
def rebuild_parameters(template_id):
|
||||||
"""Rebuild all parameters for a given template.
|
"""Rebuild all parameters for a given template.
|
||||||
|
|
||||||
@ -218,6 +226,7 @@ def rebuild_parameters(template_id):
|
|||||||
logger.info("Rebuilt %s parameters for template '%s'", n, template.name)
|
logger.info("Rebuilt %s parameters for template '%s'", n, template.name)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('rebuild_supplier_parts')
|
||||||
def rebuild_supplier_parts(part_id):
|
def rebuild_supplier_parts(part_id):
|
||||||
"""Rebuild all SupplierPart objects for a given part.
|
"""Rebuild all SupplierPart objects for a given part.
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from django.db.models.signals import post_delete, post_save
|
|||||||
from django.dispatch.dispatcher import receiver
|
from django.dispatch.dispatcher import receiver
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
import InvenTree.exceptions
|
import InvenTree.exceptions
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
@ -13,9 +14,11 @@ from InvenTree.ready import canAppAccessDatabase, isImportingData
|
|||||||
from InvenTree.tasks import offload_task
|
from InvenTree.tasks import offload_task
|
||||||
from plugin.registry import registry
|
from plugin.registry import registry
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('trigger_event')
|
||||||
def trigger_event(event: str, *args, **kwargs) -> None:
|
def trigger_event(event: str, *args, **kwargs) -> None:
|
||||||
"""Trigger an event with optional arguments.
|
"""Trigger an event with optional arguments.
|
||||||
|
|
||||||
@ -54,6 +57,7 @@ def trigger_event(event: str, *args, **kwargs) -> None:
|
|||||||
offload_task(register_event, event, *args, group='plugin', **kwargs)
|
offload_task(register_event, event, *args, group='plugin', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('register_event')
|
||||||
def register_event(event, *args, **kwargs):
|
def register_event(event, *args, **kwargs):
|
||||||
"""Register the event with any interested plugins.
|
"""Register the event with any interested plugins.
|
||||||
|
|
||||||
@ -93,6 +97,7 @@ def register_event(event, *args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('process_event')
|
||||||
def process_event(plugin_slug, event, *args, **kwargs):
|
def process_event(plugin_slug, event, *args, **kwargs):
|
||||||
"""Respond to a triggered event.
|
"""Respond to a triggered event.
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
"""Background tasks for the report app."""
|
"""Background tasks for the report app."""
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
from InvenTree.exceptions import log_error
|
from InvenTree.exceptions import log_error
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('print_reports')
|
||||||
def print_reports(template_id: int, item_ids: list[int], output_id: int, **kwargs):
|
def print_reports(template_id: int, item_ids: list[int], output_id: int, **kwargs):
|
||||||
"""Print multiple reports against the provided template.
|
"""Print multiple reports against the provided template.
|
||||||
|
|
||||||
@ -35,6 +38,7 @@ def print_reports(template_id: int, item_ids: list[int], output_id: int, **kwarg
|
|||||||
template.print(items, output=output)
|
template.print(items, output=output)
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('print_labels')
|
||||||
def print_labels(
|
def print_labels(
|
||||||
template_id: int, item_ids: list[int], output_id: int, plugin_slug: str, **kwargs
|
template_id: int, item_ids: list[int], output_id: int, plugin_slug: str, **kwargs
|
||||||
):
|
):
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
"""Background tasks for the stock app."""
|
"""Background tasks for the stock app."""
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
|
tracer = trace.get_tracer(__name__)
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
@tracer.start_as_current_span('rebuild_stock_item_tree')
|
||||||
def rebuild_stock_item_tree(tree_id=None):
|
def rebuild_stock_item_tree(tree_id=None):
|
||||||
"""Rebuild the stock tree structure.
|
"""Rebuild the stock tree structure.
|
||||||
|
|
||||||
|
@ -61,6 +61,11 @@ opentelemetry-exporter-otlp
|
|||||||
opentelemetry-instrumentation-django
|
opentelemetry-instrumentation-django
|
||||||
opentelemetry-instrumentation-requests
|
opentelemetry-instrumentation-requests
|
||||||
opentelemetry-instrumentation-redis
|
opentelemetry-instrumentation-redis
|
||||||
|
opentelemetry-instrumentation-sqlite3
|
||||||
|
opentelemetry-instrumentation-system_metrics
|
||||||
|
opentelemetry-instrumentation-wsgi
|
||||||
|
opentelemetry-instrumentation-psycopg
|
||||||
|
opentelemetry-instrumentation-pymysql
|
||||||
|
|
||||||
# Pins
|
# Pins
|
||||||
xmlsec==1.3.14 # 2025-06-02 pinned to avoid issues with builds - see https://github.com/inventree/InvenTree/pull/9713
|
xmlsec==1.3.14 # 2025-06-02 pinned to avoid issues with builds - see https://github.com/inventree/InvenTree/pull/9713
|
||||||
|
@ -988,9 +988,14 @@ opentelemetry-api==1.34.0 \
|
|||||||
# opentelemetry-exporter-otlp-proto-grpc
|
# opentelemetry-exporter-otlp-proto-grpc
|
||||||
# opentelemetry-exporter-otlp-proto-http
|
# opentelemetry-exporter-otlp-proto-http
|
||||||
# opentelemetry-instrumentation
|
# opentelemetry-instrumentation
|
||||||
|
# opentelemetry-instrumentation-dbapi
|
||||||
# opentelemetry-instrumentation-django
|
# opentelemetry-instrumentation-django
|
||||||
|
# opentelemetry-instrumentation-psycopg
|
||||||
|
# opentelemetry-instrumentation-pymysql
|
||||||
# opentelemetry-instrumentation-redis
|
# opentelemetry-instrumentation-redis
|
||||||
# opentelemetry-instrumentation-requests
|
# opentelemetry-instrumentation-requests
|
||||||
|
# opentelemetry-instrumentation-sqlite3
|
||||||
|
# opentelemetry-instrumentation-system-metrics
|
||||||
# opentelemetry-instrumentation-wsgi
|
# opentelemetry-instrumentation-wsgi
|
||||||
# opentelemetry-sdk
|
# opentelemetry-sdk
|
||||||
# opentelemetry-semantic-conventions
|
# opentelemetry-semantic-conventions
|
||||||
@ -1016,14 +1021,34 @@ opentelemetry-instrumentation==0.55b0 \
|
|||||||
--hash=sha256:9669f19a561f7eacd9974823e48949bc12506d34cb2dd277e9d7b70987c7cc66 \
|
--hash=sha256:9669f19a561f7eacd9974823e48949bc12506d34cb2dd277e9d7b70987c7cc66 \
|
||||||
--hash=sha256:c0c64c16d2abae80a0f43906d3c68de10a700a4fc11d22b1c31f32d628e95e31
|
--hash=sha256:c0c64c16d2abae80a0f43906d3c68de10a700a4fc11d22b1c31f32d628e95e31
|
||||||
# via
|
# via
|
||||||
|
# opentelemetry-instrumentation-dbapi
|
||||||
# opentelemetry-instrumentation-django
|
# opentelemetry-instrumentation-django
|
||||||
|
# opentelemetry-instrumentation-psycopg
|
||||||
|
# opentelemetry-instrumentation-pymysql
|
||||||
# opentelemetry-instrumentation-redis
|
# opentelemetry-instrumentation-redis
|
||||||
# opentelemetry-instrumentation-requests
|
# opentelemetry-instrumentation-requests
|
||||||
|
# opentelemetry-instrumentation-sqlite3
|
||||||
|
# opentelemetry-instrumentation-system-metrics
|
||||||
# opentelemetry-instrumentation-wsgi
|
# opentelemetry-instrumentation-wsgi
|
||||||
|
opentelemetry-instrumentation-dbapi==0.55b0 \
|
||||||
|
--hash=sha256:dca1344a5d7303d0c225631262458835f80a2ed00d6e0a4053d9c47bbca41cb5 \
|
||||||
|
--hash=sha256:ebfe8b5506cd77ec37a94e59491537c5d4b38aeb4ad942c9a68aac73bc3d3d31
|
||||||
|
# via
|
||||||
|
# opentelemetry-instrumentation-psycopg
|
||||||
|
# opentelemetry-instrumentation-pymysql
|
||||||
|
# opentelemetry-instrumentation-sqlite3
|
||||||
opentelemetry-instrumentation-django==0.55b0 \
|
opentelemetry-instrumentation-django==0.55b0 \
|
||||||
--hash=sha256:5421e0e6a8d2847e5296714affce239150e3ac27defdbd0d22f9842c4f3b1ca8 \
|
--hash=sha256:5421e0e6a8d2847e5296714affce239150e3ac27defdbd0d22f9842c4f3b1ca8 \
|
||||||
--hash=sha256:9c50ab2f9e359d0f96a1516cc25b3e515045c858a3994cf04e21ef602905158b
|
--hash=sha256:9c50ab2f9e359d0f96a1516cc25b3e515045c858a3994cf04e21ef602905158b
|
||||||
# via -r src/backend/requirements.in
|
# via -r src/backend/requirements.in
|
||||||
|
opentelemetry-instrumentation-psycopg==0.55b0 \
|
||||||
|
--hash=sha256:1edac6fa90a49e81b1f847d3ebf2746a38170885215b75b02cf6faf71e2d402d \
|
||||||
|
--hash=sha256:c44d689eb50666341c8e9077cf1079f81003f4e679a666424b338bbe4ebbcbf3
|
||||||
|
# via -r src/backend/requirements.in
|
||||||
|
opentelemetry-instrumentation-pymysql==0.55b0 \
|
||||||
|
--hash=sha256:7438aeeca9e28590a681f71648b94b5ddf094f4fed77bf28ec81fee5aa329a84 \
|
||||||
|
--hash=sha256:91bb628b79809cb00d4276150e933e25bb5defe02e80ef7f97e49d4bf76e4861
|
||||||
|
# via -r src/backend/requirements.in
|
||||||
opentelemetry-instrumentation-redis==0.55b0 \
|
opentelemetry-instrumentation-redis==0.55b0 \
|
||||||
--hash=sha256:4366a06e16ae42a36c1fc2a30c880a12cdce8c0f9a2796abbf46f43c84788b95 \
|
--hash=sha256:4366a06e16ae42a36c1fc2a30c880a12cdce8c0f9a2796abbf46f43c84788b95 \
|
||||||
--hash=sha256:88ca82ceb950ef1ec71b7b9eb7584b5030cb78200b6c628c34c783d6b888f628
|
--hash=sha256:88ca82ceb950ef1ec71b7b9eb7584b5030cb78200b6c628c34c783d6b888f628
|
||||||
@ -1032,10 +1057,20 @@ opentelemetry-instrumentation-requests==0.55b0 \
|
|||||||
--hash=sha256:018c6e5550f10a116f101b619a3e330d309ae3438e6c7ad1541c77e56d6f3b49 \
|
--hash=sha256:018c6e5550f10a116f101b619a3e330d309ae3438e6c7ad1541c77e56d6f3b49 \
|
||||||
--hash=sha256:9299303c5b23ea0c825f6bda346f585171471e19e6c1313c19a3facddf1e2a42
|
--hash=sha256:9299303c5b23ea0c825f6bda346f585171471e19e6c1313c19a3facddf1e2a42
|
||||||
# via -r src/backend/requirements.in
|
# via -r src/backend/requirements.in
|
||||||
|
opentelemetry-instrumentation-sqlite3==0.55b0 \
|
||||||
|
--hash=sha256:9c1c9bd3409f2f6494f314c72c89785f45f94117562009d9fcae05f9862a4d9a \
|
||||||
|
--hash=sha256:bfb5a8a6b5d545a5187a1d427417e92e7be47d9d28b315998d01dbc73b89402d
|
||||||
|
# via -r src/backend/requirements.in
|
||||||
|
opentelemetry-instrumentation-system-metrics==0.55b0 \
|
||||||
|
--hash=sha256:23050f289e7c2062672781646deb9c39283fc35a1d1ba3f60856b8ecf45efd32 \
|
||||||
|
--hash=sha256:392b558b6d193ce3eb51a29be984badc5dd2c5a44c8716850d7624045d68cbc4
|
||||||
|
# via -r src/backend/requirements.in
|
||||||
opentelemetry-instrumentation-wsgi==0.55b0 \
|
opentelemetry-instrumentation-wsgi==0.55b0 \
|
||||||
--hash=sha256:63d1851bf98dd2a119f41b8f9c5fd469e63e6e6e042be04196609f05df01b32e \
|
--hash=sha256:63d1851bf98dd2a119f41b8f9c5fd469e63e6e6e042be04196609f05df01b32e \
|
||||||
--hash=sha256:b1903bcc609cc1e7ee7d55a4969eb9107cb2773a4b981e3ad73c6aeb03d8da1e
|
--hash=sha256:b1903bcc609cc1e7ee7d55a4969eb9107cb2773a4b981e3ad73c6aeb03d8da1e
|
||||||
# via opentelemetry-instrumentation-django
|
# via
|
||||||
|
# -r src/backend/requirements.in
|
||||||
|
# opentelemetry-instrumentation-django
|
||||||
opentelemetry-proto==1.34.0 \
|
opentelemetry-proto==1.34.0 \
|
||||||
--hash=sha256:73e40509b692630a47192888424f7e0b8fb19d9ecf2f04e6f708170cd3346dfe \
|
--hash=sha256:73e40509b692630a47192888424f7e0b8fb19d9ecf2f04e6f708170cd3346dfe \
|
||||||
--hash=sha256:ffb1f1b27552fda5a1cd581e34243cc0b6f134fb14c1c2a33cc3b4b208c9bf97
|
--hash=sha256:ffb1f1b27552fda5a1cd581e34243cc0b6f134fb14c1c2a33cc3b4b208c9bf97
|
||||||
@ -1055,6 +1090,7 @@ opentelemetry-semantic-conventions==0.55b0 \
|
|||||||
--hash=sha256:933d2e20c2dbc0f9b2f4f52138282875b4b14c66c491f5273bcdef1781368e9c
|
--hash=sha256:933d2e20c2dbc0f9b2f4f52138282875b4b14c66c491f5273bcdef1781368e9c
|
||||||
# via
|
# via
|
||||||
# opentelemetry-instrumentation
|
# opentelemetry-instrumentation
|
||||||
|
# opentelemetry-instrumentation-dbapi
|
||||||
# opentelemetry-instrumentation-django
|
# opentelemetry-instrumentation-django
|
||||||
# opentelemetry-instrumentation-redis
|
# opentelemetry-instrumentation-redis
|
||||||
# opentelemetry-instrumentation-requests
|
# opentelemetry-instrumentation-requests
|
||||||
@ -1201,6 +1237,18 @@ protobuf==5.29.5 \
|
|||||||
# via
|
# via
|
||||||
# googleapis-common-protos
|
# googleapis-common-protos
|
||||||
# opentelemetry-proto
|
# opentelemetry-proto
|
||||||
|
psutil==7.0.0 \
|
||||||
|
--hash=sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25 \
|
||||||
|
--hash=sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e \
|
||||||
|
--hash=sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91 \
|
||||||
|
--hash=sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da \
|
||||||
|
--hash=sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34 \
|
||||||
|
--hash=sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553 \
|
||||||
|
--hash=sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456 \
|
||||||
|
--hash=sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17 \
|
||||||
|
--hash=sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993 \
|
||||||
|
--hash=sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99
|
||||||
|
# via opentelemetry-instrumentation-system-metrics
|
||||||
py-moneyed==3.0 \
|
py-moneyed==3.0 \
|
||||||
--hash=sha256:4906f0f02cf2b91edba2e156f2d4e9a78f224059ab8c8fa2ff26230c75d894e8 \
|
--hash=sha256:4906f0f02cf2b91edba2e156f2d4e9a78f224059ab8c8fa2ff26230c75d894e8 \
|
||||||
--hash=sha256:9583a14f99c05b46196193d8185206e9b73c8439fc8a5eee9cfc7e733676d9bb
|
--hash=sha256:9583a14f99c05b46196193d8185206e9b73c8439fc8a5eee9cfc7e733676d9bb
|
||||||
@ -1784,6 +1832,7 @@ wrapt==1.17.2 \
|
|||||||
--hash=sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58
|
--hash=sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58
|
||||||
# via
|
# via
|
||||||
# opentelemetry-instrumentation
|
# opentelemetry-instrumentation
|
||||||
|
# opentelemetry-instrumentation-dbapi
|
||||||
# opentelemetry-instrumentation-redis
|
# opentelemetry-instrumentation-redis
|
||||||
xlrd==2.0.1 \
|
xlrd==2.0.1 \
|
||||||
--hash=sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd \
|
--hash=sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd \
|
||||||
|
Reference in New Issue
Block a user