From 11c74d741a6b2a17f9d0419fafbb3c2ebbc88bf6 Mon Sep 17 00:00:00 2001 From: GareArc Date: Fri, 6 Feb 2026 02:38:19 -0800 Subject: [PATCH] feat: add dedicated app event counters and convert event names to StrEnum - Add APP_CREATED, APP_UPDATED, APP_DELETED counters to EnterpriseTelemetryCounter - Create EnterpriseTelemetryEvent StrEnum for type-safe event names - Update metric_handler to use new app-specific counters with labels (tenant_id, app_id, mode) - Convert all event_name strings to EnterpriseTelemetryEvent enum values - Update exporter to create OTEL meters for new app counters (dify.app.created.total, etc.) - Update tests to verify new counter behavior and enum usage --- api/enterprise/telemetry/enterprise_trace.py | 17 ++++----- api/enterprise/telemetry/entities/__init__.py | 22 ++++++++++++ api/enterprise/telemetry/exporter.py | 3 ++ api/enterprise/telemetry/metric_handler.py | 32 +++++++++-------- api/enterprise/telemetry/telemetry_log.py | 9 +++-- .../telemetry/test_metric_handler.py | 36 +++++++++++++++---- 6 files changed, 86 insertions(+), 33 deletions(-) diff --git a/api/enterprise/telemetry/enterprise_trace.py b/api/enterprise/telemetry/enterprise_trace.py index 5db6215552..8eb97fd52c 100644 --- a/api/enterprise/telemetry/enterprise_trace.py +++ b/api/enterprise/telemetry/enterprise_trace.py @@ -32,6 +32,7 @@ from core.ops.entities.trace_entity import ( ) from enterprise.telemetry.entities import ( EnterpriseTelemetryCounter, + EnterpriseTelemetryEvent, EnterpriseTelemetryHistogram, EnterpriseTelemetrySpan, ) @@ -203,7 +204,7 @@ class EnterpriseOtelTrace: log_attrs["dify.workflow.query"] = self._content_or_ref(info.query, ref) emit_telemetry_log( - event_name="dify.workflow.run", + event_name=EnterpriseTelemetryEvent.WORKFLOW_RUN, attributes=log_attrs, signal="span_detail", trace_id_source=info.workflow_run_id, @@ -448,7 +449,7 @@ class EnterpriseOtelTrace: attrs["dify.message.outputs"] = self._content_or_ref(outputs, ref) emit_metric_only_event( - event_name="dify.message.run", + event_name=EnterpriseTelemetryEvent.MESSAGE_RUN, attributes=attrs, trace_id_source=metadata.get("workflow_run_id") or str(info.message_id) if info.message_id else None, span_id_source=node_execution_id, @@ -525,7 +526,7 @@ class EnterpriseOtelTrace: attrs["dify.tool.config"] = self._content_or_ref(info.tool_config, ref) emit_metric_only_event( - event_name="dify.tool.execution", + event_name=EnterpriseTelemetryEvent.TOOL_EXECUTION, attributes=attrs, span_id_source=node_execution_id, tenant_id=tenant_id, @@ -579,7 +580,7 @@ class EnterpriseOtelTrace: ) emit_metric_only_event( - event_name="dify.moderation.check", + event_name=EnterpriseTelemetryEvent.MODERATION_CHECK, attributes=attrs, span_id_source=node_execution_id, tenant_id=tenant_id, @@ -624,7 +625,7 @@ class EnterpriseOtelTrace: ) emit_metric_only_event( - event_name="dify.suggested_question.generation", + event_name=EnterpriseTelemetryEvent.SUGGESTED_QUESTION_GENERATION, attributes=attrs, span_id_source=node_execution_id, tenant_id=tenant_id, @@ -711,7 +712,7 @@ class EnterpriseOtelTrace: attrs["dify.dataset.documents"] = self._content_or_ref(structured_docs, ref) emit_metric_only_event( - event_name="dify.dataset.retrieval", + event_name=EnterpriseTelemetryEvent.DATASET_RETRIEVAL, attributes=attrs, trace_id_source=metadata.get("workflow_run_id") or str(info.message_id) if info.message_id else None, span_id_source=node_execution_id or (str(info.message_id) if info.message_id else None), @@ -758,7 +759,7 @@ class EnterpriseOtelTrace: attrs["dify.generate_name.outputs"] = self._content_or_ref(outputs, ref) emit_metric_only_event( - event_name="dify.generate_name.execution", + event_name=EnterpriseTelemetryEvent.GENERATE_NAME_EXECUTION, attributes=attrs, span_id_source=node_execution_id, tenant_id=tenant_id, @@ -811,7 +812,7 @@ class EnterpriseOtelTrace: attrs["dify.prompt_generation.output"] = self._content_or_ref(outputs, ref) emit_metric_only_event( - event_name="dify.prompt_generation.execution", + event_name=EnterpriseTelemetryEvent.PROMPT_GENERATION_EXECUTION, attributes=attrs, span_id_source=node_execution_id, tenant_id=tenant_id, diff --git a/api/enterprise/telemetry/entities/__init__.py b/api/enterprise/telemetry/entities/__init__.py index c0a5499c6c..388bdc8fa2 100644 --- a/api/enterprise/telemetry/entities/__init__.py +++ b/api/enterprise/telemetry/entities/__init__.py @@ -7,6 +7,24 @@ class EnterpriseTelemetrySpan(StrEnum): DRAFT_NODE_EXECUTION = "dify.node.execution.draft" +class EnterpriseTelemetryEvent(StrEnum): + """Event names for enterprise telemetry logs.""" + + APP_CREATED = "dify.app.created" + APP_UPDATED = "dify.app.updated" + APP_DELETED = "dify.app.deleted" + FEEDBACK_CREATED = "dify.feedback.created" + WORKFLOW_RUN = "dify.workflow.run" + MESSAGE_RUN = "dify.message.run" + TOOL_EXECUTION = "dify.tool.execution" + MODERATION_CHECK = "dify.moderation.check" + SUGGESTED_QUESTION_GENERATION = "dify.suggested_question.generation" + DATASET_RETRIEVAL = "dify.dataset.retrieval" + GENERATE_NAME_EXECUTION = "dify.generate_name.execution" + PROMPT_GENERATION_EXECUTION = "dify.prompt_generation.execution" + REHYDRATION_FAILED = "dify.telemetry.rehydration_failed" + + class EnterpriseTelemetryCounter(StrEnum): TOKENS = "tokens" INPUT_TOKENS = "input_tokens" @@ -15,6 +33,9 @@ class EnterpriseTelemetryCounter(StrEnum): ERRORS = "errors" FEEDBACK = "feedback" DATASET_RETRIEVALS = "dataset_retrievals" + APP_CREATED = "app_created" + APP_UPDATED = "app_updated" + APP_DELETED = "app_deleted" class EnterpriseTelemetryHistogram(StrEnum): @@ -28,6 +49,7 @@ class EnterpriseTelemetryHistogram(StrEnum): __all__ = [ "EnterpriseTelemetryCounter", + "EnterpriseTelemetryEvent", "EnterpriseTelemetryHistogram", "EnterpriseTelemetrySpan", ] diff --git a/api/enterprise/telemetry/exporter.py b/api/enterprise/telemetry/exporter.py index 529c38741a..8b66157c61 100644 --- a/api/enterprise/telemetry/exporter.py +++ b/api/enterprise/telemetry/exporter.py @@ -141,6 +141,9 @@ class EnterpriseExporter: EnterpriseTelemetryCounter.DATASET_RETRIEVALS: meter.create_counter( "dify.dataset.retrievals.total", unit="{retrieval}" ), + EnterpriseTelemetryCounter.APP_CREATED: meter.create_counter("dify.app.created.total", unit="{app}"), + EnterpriseTelemetryCounter.APP_UPDATED: meter.create_counter("dify.app.updated.total", unit="{app}"), + EnterpriseTelemetryCounter.APP_DELETED: meter.create_counter("dify.app.deleted.total", unit="{app}"), } self._histograms = { EnterpriseTelemetryHistogram.WORKFLOW_DURATION: meter.create_histogram("dify.workflow.duration", unit="s"), diff --git a/api/enterprise/telemetry/metric_handler.py b/api/enterprise/telemetry/metric_handler.py index cfe1768a10..e0a6c4998d 100644 --- a/api/enterprise/telemetry/metric_handler.py +++ b/api/enterprise/telemetry/metric_handler.py @@ -174,10 +174,11 @@ class EnterpriseMetricHandler: envelope.case, ) # Emit degraded event marker + from enterprise.telemetry.entities import EnterpriseTelemetryEvent from enterprise.telemetry.telemetry_log import emit_metric_only_event emit_metric_only_event( - event_name="dify.telemetry.rehydration_failed", + event_name=EnterpriseTelemetryEvent.REHYDRATION_FAILED, attributes={ "dify.tenant_id": envelope.tenant_id, "dify.event_id": envelope.event_id, @@ -196,7 +197,7 @@ class EnterpriseMetricHandler: def _on_app_created(self, envelope: TelemetryEnvelope) -> None: """Handle app created event.""" - from enterprise.telemetry.entities import EnterpriseTelemetryCounter + from enterprise.telemetry.entities import EnterpriseTelemetryCounter, EnterpriseTelemetryEvent from enterprise.telemetry.telemetry_log import emit_metric_only_event from extensions.ext_enterprise_telemetry import get_enterprise_exporter @@ -216,22 +217,23 @@ class EnterpriseMetricHandler: } emit_metric_only_event( - event_name="dify.app.created", + event_name=EnterpriseTelemetryEvent.APP_CREATED, attributes=attrs, tenant_id=envelope.tenant_id, ) exporter.increment_counter( - EnterpriseTelemetryCounter.REQUESTS, + EnterpriseTelemetryCounter.APP_CREATED, 1, { - "type": "app.created", "tenant_id": envelope.tenant_id, + "app_id": str(payload.get("app_id", "")), + "mode": str(payload.get("mode", "")), }, ) def _on_app_updated(self, envelope: TelemetryEnvelope) -> None: """Handle app updated event.""" - from enterprise.telemetry.entities import EnterpriseTelemetryCounter + from enterprise.telemetry.entities import EnterpriseTelemetryCounter, EnterpriseTelemetryEvent from enterprise.telemetry.telemetry_log import emit_metric_only_event from extensions.ext_enterprise_telemetry import get_enterprise_exporter @@ -250,22 +252,22 @@ class EnterpriseMetricHandler: } emit_metric_only_event( - event_name="dify.app.updated", + event_name=EnterpriseTelemetryEvent.APP_UPDATED, attributes=attrs, tenant_id=envelope.tenant_id, ) exporter.increment_counter( - EnterpriseTelemetryCounter.REQUESTS, + EnterpriseTelemetryCounter.APP_UPDATED, 1, { - "type": "app.updated", "tenant_id": envelope.tenant_id, + "app_id": str(payload.get("app_id", "")), }, ) def _on_app_deleted(self, envelope: TelemetryEnvelope) -> None: """Handle app deleted event.""" - from enterprise.telemetry.entities import EnterpriseTelemetryCounter + from enterprise.telemetry.entities import EnterpriseTelemetryCounter, EnterpriseTelemetryEvent from enterprise.telemetry.telemetry_log import emit_metric_only_event from extensions.ext_enterprise_telemetry import get_enterprise_exporter @@ -284,22 +286,22 @@ class EnterpriseMetricHandler: } emit_metric_only_event( - event_name="dify.app.deleted", + event_name=EnterpriseTelemetryEvent.APP_DELETED, attributes=attrs, tenant_id=envelope.tenant_id, ) exporter.increment_counter( - EnterpriseTelemetryCounter.REQUESTS, + EnterpriseTelemetryCounter.APP_DELETED, 1, { - "type": "app.deleted", "tenant_id": envelope.tenant_id, + "app_id": str(payload.get("app_id", "")), }, ) def _on_feedback_created(self, envelope: TelemetryEnvelope) -> None: """Handle feedback created event.""" - from enterprise.telemetry.entities import EnterpriseTelemetryCounter + from enterprise.telemetry.entities import EnterpriseTelemetryCounter, EnterpriseTelemetryEvent from enterprise.telemetry.telemetry_log import emit_metric_only_event from extensions.ext_enterprise_telemetry import get_enterprise_exporter @@ -327,7 +329,7 @@ class EnterpriseMetricHandler: user_id = payload.get("from_end_user_id") or payload.get("from_account_id") emit_metric_only_event( - event_name="dify.feedback.created", + event_name=EnterpriseTelemetryEvent.FEEDBACK_CREATED, attributes=attrs, tenant_id=envelope.tenant_id, user_id=str(user_id or ""), diff --git a/api/enterprise/telemetry/telemetry_log.py b/api/enterprise/telemetry/telemetry_log.py index 63d79e8dc4..8cce4a9fcd 100644 --- a/api/enterprise/telemetry/telemetry_log.py +++ b/api/enterprise/telemetry/telemetry_log.py @@ -9,7 +9,10 @@ from __future__ import annotations import logging import uuid from functools import lru_cache -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from enterprise.telemetry.entities import EnterpriseTelemetryEvent logger = logging.getLogger("dify.telemetry") @@ -48,7 +51,7 @@ def compute_span_id_hex(uuid_str: str | None) -> str: def emit_telemetry_log( *, - event_name: str, + event_name: str | EnterpriseTelemetryEvent, attributes: dict[str, Any], signal: str = "metric_only", trace_id_source: str | None = None, @@ -101,7 +104,7 @@ def emit_telemetry_log( def emit_metric_only_event( *, - event_name: str, + event_name: str | EnterpriseTelemetryEvent, attributes: dict[str, Any], trace_id_source: str | None = None, span_id_source: str | None = None, diff --git a/api/tests/unit_tests/enterprise/telemetry/test_metric_handler.py b/api/tests/unit_tests/enterprise/telemetry/test_metric_handler.py index 581e1631d5..a858c8e95a 100644 --- a/api/tests/unit_tests/enterprise/telemetry/test_metric_handler.py +++ b/api/tests/unit_tests/enterprise/telemetry/test_metric_handler.py @@ -269,10 +269,12 @@ def test_rehydration_emits_degraded_event_on_failure(): with patch("enterprise.telemetry.telemetry_log.emit_metric_only_event") as mock_emit: payload = handler._rehydrate(envelope) + from enterprise.telemetry.entities import EnterpriseTelemetryEvent + assert payload == {} mock_emit.assert_called_once() call_args = mock_emit.call_args - assert call_args[1]["event_name"] == "dify.telemetry.rehydration_failed" + assert call_args[1]["event_name"] == EnterpriseTelemetryEvent.REHYDRATION_FAILED assert call_args[1]["attributes"]["rehydration_failed"] is True @@ -295,8 +297,10 @@ def test_on_app_created_emits_correct_event(mock_redis): handler._on_app_created(envelope) + from enterprise.telemetry.entities import EnterpriseTelemetryEvent + mock_emit.assert_called_once_with( - event_name="dify.app.created", + event_name=EnterpriseTelemetryEvent.APP_CREATED, attributes={ "dify.app.id": "app-789", "dify.tenant_id": "tenant-123", @@ -304,11 +308,15 @@ def test_on_app_created_emits_correct_event(mock_redis): }, tenant_id="tenant-123", ) + from enterprise.telemetry.entities import EnterpriseTelemetryCounter + mock_exporter.increment_counter.assert_called_once() call_args = mock_exporter.increment_counter.call_args + assert call_args[0][0] == EnterpriseTelemetryCounter.APP_CREATED assert call_args[0][1] == 1 - assert call_args[0][2]["type"] == "app.created" assert call_args[0][2]["tenant_id"] == "tenant-123" + assert call_args[0][2]["app_id"] == "app-789" + assert call_args[0][2]["mode"] == "chat" def test_on_app_updated_emits_correct_event(mock_redis): @@ -330,17 +338,24 @@ def test_on_app_updated_emits_correct_event(mock_redis): handler._on_app_updated(envelope) + from enterprise.telemetry.entities import EnterpriseTelemetryEvent + mock_emit.assert_called_once_with( - event_name="dify.app.updated", + event_name=EnterpriseTelemetryEvent.APP_UPDATED, attributes={ "dify.app.id": "app-789", "dify.tenant_id": "tenant-123", }, tenant_id="tenant-123", ) + from enterprise.telemetry.entities import EnterpriseTelemetryCounter + mock_exporter.increment_counter.assert_called_once() call_args = mock_exporter.increment_counter.call_args - assert call_args[0][2]["type"] == "app.updated" + assert call_args[0][0] == EnterpriseTelemetryCounter.APP_UPDATED + assert call_args[0][1] == 1 + assert call_args[0][2]["tenant_id"] == "tenant-123" + assert call_args[0][2]["app_id"] == "app-789" def test_on_app_deleted_emits_correct_event(mock_redis): @@ -362,17 +377,24 @@ def test_on_app_deleted_emits_correct_event(mock_redis): handler._on_app_deleted(envelope) + from enterprise.telemetry.entities import EnterpriseTelemetryEvent + mock_emit.assert_called_once_with( - event_name="dify.app.deleted", + event_name=EnterpriseTelemetryEvent.APP_DELETED, attributes={ "dify.app.id": "app-789", "dify.tenant_id": "tenant-123", }, tenant_id="tenant-123", ) + from enterprise.telemetry.entities import EnterpriseTelemetryCounter + mock_exporter.increment_counter.assert_called_once() call_args = mock_exporter.increment_counter.call_args - assert call_args[0][2]["type"] == "app.deleted" + assert call_args[0][0] == EnterpriseTelemetryCounter.APP_DELETED + assert call_args[0][1] == 1 + assert call_args[0][2]["tenant_id"] == "tenant-123" + assert call_args[0][2]["app_id"] == "app-789" def test_on_feedback_created_emits_correct_event(mock_redis):