|
13 | 13 | # See the License for the specific language governing permissions and |
14 | 14 | # limitations under the License. |
15 | 15 |
|
16 | | -from __future__ import annotations |
| 16 | +""" |
| 17 | +OpenTelemetry Adapter for NeMo Guardrails |
| 18 | +
|
| 19 | +This adapter follows OpenTelemetry best practices for libraries: |
| 20 | +- Uses only the OpenTelemetry API (not SDK) |
| 21 | +- Does not modify global state |
| 22 | +- Relies on the application to configure the SDK |
| 23 | +
|
| 24 | +Usage: |
| 25 | + Applications using NeMo Guardrails with OpenTelemetry should configure |
| 26 | + the OpenTelemetry SDK before using this adapter: |
| 27 | +
|
| 28 | + ```python |
| 29 | + from opentelemetry import trace |
| 30 | + from opentelemetry.sdk.trace import TracerProvider |
| 31 | + from opentelemetry.sdk.trace.export import BatchSpanProcessor |
| 32 | + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter |
| 33 | +
|
| 34 | + # application configures the SDK |
| 35 | + trace.set_tracer_provider(TracerProvider()) |
| 36 | + tracer_provider = trace.get_tracer_provider() |
| 37 | +
|
| 38 | + exporter = OTLPSpanExporter(endpoint="http://localhost:4317") |
| 39 | + span_processor = BatchSpanProcessor(exporter) |
| 40 | + tracer_provider.add_span_processor(span_processor) |
| 41 | +
|
| 42 | + # now NeMo Guardrails can use the configured tracer |
| 43 | + config = RailsConfig.from_content( |
| 44 | + config={ |
| 45 | + "tracing": { |
| 46 | + "enabled": True, |
| 47 | + "adapters": [{"name": "OpenTelemetry"}] |
| 48 | + } |
| 49 | + } |
| 50 | + ) |
| 51 | + ``` |
| 52 | +""" |
17 | 53 |
|
18 | | -from typing import TYPE_CHECKING, Dict, Optional, Type |
| 54 | +from __future__ import annotations |
19 | 55 |
|
20 | | -from opentelemetry.sdk.trace.export import SpanExporter |
| 56 | +import warnings |
| 57 | +from importlib.metadata import version |
| 58 | +from typing import TYPE_CHECKING, Optional, Type |
21 | 59 |
|
22 | 60 | if TYPE_CHECKING: |
23 | 61 | from nemoguardrails.tracing import InteractionLog |
24 | 62 | try: |
25 | 63 | from opentelemetry import trace |
26 | | - from opentelemetry.sdk.resources import Attributes, Resource |
27 | | - from opentelemetry.sdk.trace import SpanProcessor, TracerProvider |
28 | | - from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter |
| 64 | + from opentelemetry.trace import NoOpTracerProvider |
29 | 65 |
|
30 | 66 | except ImportError: |
31 | 67 | raise ImportError( |
32 | | - "opentelemetry is not installed. Please install it using `pip install opentelemetry-api opentelemetry-sdk`." |
| 68 | + "OpenTelemetry API is not installed. Please install NeMo Guardrails with tracing support: " |
| 69 | + "`pip install nemoguardrails[tracing]` or install the API directly: `pip install opentelemetry-api`." |
33 | 70 | ) |
34 | 71 |
|
35 | 72 | from nemoguardrails.tracing.adapters.base import InteractionLogAdapter |
36 | 73 |
|
37 | | -# Global dictionary to store registered exporters |
38 | | -_exporter_name_cls_map: Dict[str, Type[SpanExporter]] = { |
39 | | - "console": ConsoleSpanExporter, |
40 | | -} |
41 | | - |
42 | | - |
43 | | -def register_otel_exporter(name: str, exporter_cls: Type[SpanExporter]): |
44 | | - """Register a new exporter.""" |
| 74 | +# DEPRECATED: global dictionary to store registered exporters |
| 75 | +# will be removed in v0.16.0 |
| 76 | +_exporter_name_cls_map: dict[str, Type] = {} |
| 77 | + |
| 78 | + |
| 79 | +def register_otel_exporter(name: str, exporter_cls: Type): |
| 80 | + """Register a new exporter. |
| 81 | +
|
| 82 | + Args: |
| 83 | + name: The name to register the exporter under. |
| 84 | + exporter_cls: The exporter class to register. |
| 85 | +
|
| 86 | + Deprecated: |
| 87 | + This function is deprecated and will be removed in version 0.16.0. |
| 88 | + Please configure OpenTelemetry exporters directly in your application code. |
| 89 | + See the migration guide at: |
| 90 | + https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/configs/tracing/README.md#migration-guide |
| 91 | + """ |
| 92 | + warnings.warn( |
| 93 | + "register_otel_exporter is deprecated and will be removed in version 0.16.0. " |
| 94 | + "Please configure OpenTelemetry exporters directly in your application code. " |
| 95 | + "See the migration guide at: " |
| 96 | + "https://github.com/NVIDIA/NeMo-Guardrails/blob/develop/examples/configs/tracing/README.md#migration-guide", |
| 97 | + DeprecationWarning, |
| 98 | + stacklevel=2, |
| 99 | + ) |
45 | 100 | _exporter_name_cls_map[name] = exporter_cls |
46 | 101 |
|
47 | 102 |
|
48 | 103 | class OpenTelemetryAdapter(InteractionLogAdapter): |
| 104 | + """ |
| 105 | + OpenTelemetry adapter that follows library best practices. |
| 106 | +
|
| 107 | + This adapter uses only the OpenTelemetry API and relies on the application |
| 108 | + to configure the SDK. It does not modify global state or create its own |
| 109 | + tracer provider. |
| 110 | + """ |
| 111 | + |
49 | 112 | name = "OpenTelemetry" |
50 | 113 |
|
51 | 114 | def __init__( |
52 | 115 | self, |
53 | | - service_name="nemo_guardrails_service", |
54 | | - span_processor: Optional[SpanProcessor] = None, |
55 | | - exporter: Optional[str] = None, |
56 | | - exporter_cls: Optional[SpanExporter] = None, |
57 | | - resource_attributes: Optional[Attributes] = None, |
| 116 | + service_name: str = "nemo_guardrails", |
58 | 117 | **kwargs, |
59 | 118 | ): |
60 | | - resource_attributes = resource_attributes or {} |
61 | | - resource = Resource.create( |
62 | | - {"service.name": service_name, **resource_attributes} |
63 | | - ) |
64 | | - |
65 | | - if exporter_cls and exporter: |
66 | | - raise ValueError( |
67 | | - "Only one of 'exporter' or 'exporter_name' should be provided" |
| 119 | + """ |
| 120 | + Initialize the OpenTelemetry adapter. |
| 121 | +
|
| 122 | + Args: |
| 123 | + service_name: Service name for instrumentation scope (not used for resource) |
| 124 | + **kwargs: Additional arguments (for backward compatibility) |
| 125 | +
|
| 126 | + Note: |
| 127 | + Applications must configure the OpenTelemetry SDK before using this adapter. |
| 128 | + The adapter will use the globally configured tracer provider. |
| 129 | + """ |
| 130 | + # check for deprecated parameters and warn users |
| 131 | + deprecated_params = [ |
| 132 | + "exporter", |
| 133 | + "exporter_cls", |
| 134 | + "resource_attributes", |
| 135 | + "span_processor", |
| 136 | + ] |
| 137 | + used_deprecated = [param for param in deprecated_params if param in kwargs] |
| 138 | + |
| 139 | + if used_deprecated: |
| 140 | + warnings.warn( |
| 141 | + f"OpenTelemetry configuration parameters {used_deprecated} in YAML/config are deprecated " |
| 142 | + "and will be ignored. Please configure OpenTelemetry in your application code. " |
| 143 | + "See the migration guide at: " |
| 144 | + "https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/configs/tracing/README.md#migration-guide", |
| 145 | + DeprecationWarning, |
| 146 | + stacklevel=2, |
68 | 147 | ) |
69 | | - # Set up the tracer provider |
70 | | - provider = TracerProvider(resource=resource) |
71 | | - |
72 | | - # Init the span processor and exporter |
73 | | - exporter_cls = None |
74 | | - if exporter: |
75 | | - exporter_cls = self.get_exporter(exporter, **kwargs) |
76 | 148 |
|
77 | | - if exporter_cls is None: |
78 | | - exporter_cls = ConsoleSpanExporter() |
79 | | - |
80 | | - if span_processor is None: |
81 | | - span_processor = BatchSpanProcessor(exporter_cls) |
82 | | - |
83 | | - provider.add_span_processor(span_processor) |
84 | | - trace.set_tracer_provider(provider) |
| 149 | + # validate that OpenTelemetry is properly configured |
| 150 | + provider = trace.get_tracer_provider() |
| 151 | + if provider is None or isinstance(provider, NoOpTracerProvider): |
| 152 | + warnings.warn( |
| 153 | + "No OpenTelemetry TracerProvider configured. Traces will not be exported. " |
| 154 | + "Please configure OpenTelemetry in your application code before using NeMo Guardrails. " |
| 155 | + "See setup guide at: " |
| 156 | + "https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/configs/tracing/README.md#opentelemetry-setup", |
| 157 | + UserWarning, |
| 158 | + stacklevel=2, |
| 159 | + ) |
85 | 160 |
|
86 | | - self.tracer_provider = provider |
87 | | - self.tracer = trace.get_tracer(__name__) |
| 161 | + self.tracer = trace.get_tracer( |
| 162 | + service_name, |
| 163 | + instrumenting_library_version=version("nemoguardrails"), |
| 164 | + schema_url="https://opentelemetry.io/schemas/1.26.0", |
| 165 | + ) |
88 | 166 |
|
89 | 167 | def transform(self, interaction_log: "InteractionLog"): |
90 | 168 | """Transforms the InteractionLog into OpenTelemetry spans.""" |
@@ -139,20 +217,3 @@ def _create_span( |
139 | 217 | span.set_attribute("duration", span_data.duration) |
140 | 218 |
|
141 | 219 | spans[span_data.span_id] = span |
142 | | - |
143 | | - @staticmethod |
144 | | - def get_exporter(exporter: str, **kwargs) -> SpanExporter: |
145 | | - if exporter == "zipkin": |
146 | | - try: |
147 | | - from opentelemetry.exporter.zipkin.json import ZipkinExporter |
148 | | - |
149 | | - _exporter_name_cls_map["zipkin"] = ZipkinExporter |
150 | | - except ImportError: |
151 | | - raise ImportError( |
152 | | - "The opentelemetry-exporter-zipkin package is not installed. Please install it using 'pip install opentelemetry-exporter-zipkin'." |
153 | | - ) |
154 | | - |
155 | | - exporter_cls = _exporter_name_cls_map.get(exporter) |
156 | | - if not exporter_cls: |
157 | | - raise ValueError(f"Unknown exporter: {exporter}") |
158 | | - return exporter_cls(**kwargs) |
|
0 commit comments