genkit-plugin-google-cloud 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # SPDX-License-Identifier: Apache-2.0
16
+
17
+ """Feature telemetry for GCP.
18
+
19
+ This module tracks feature-level metrics (requests, latencies) and logs
20
+ input/output for root spans, matching the JavaScript implementation.
21
+
22
+ Metrics Recorded:
23
+ - genkit/feature/requests: Counter for root span calls
24
+ - genkit/feature/latency: Histogram for root span latency (ms)
25
+
26
+ Cross-Language Parity:
27
+ - JavaScript: js/plugins/google-cloud/src/telemetry/feature.ts
28
+ - Go: go/plugins/googlecloud/feature.go
29
+
30
+ See Also:
31
+ - Cloud Monitoring Custom Metrics: https://cloud.google.com/monitoring/custom-metrics
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import structlog
37
+ from opentelemetry import metrics
38
+ from opentelemetry.sdk.trace import ReadableSpan
39
+
40
+ from genkit.core import GENKIT_VERSION
41
+
42
+ from .utils import (
43
+ create_common_log_attributes,
44
+ extract_error_name,
45
+ to_display_path,
46
+ truncate,
47
+ truncate_path,
48
+ )
49
+
50
+ logger = structlog.get_logger(__name__)
51
+
52
+ # Lazy-initialized metrics
53
+ _feature_counter: metrics.Counter | None = None
54
+ _feature_latency: metrics.Histogram | None = None
55
+
56
+
57
+ def _get_feature_counter() -> metrics.Counter:
58
+ """Get or create the feature requests counter."""
59
+ global _feature_counter
60
+ if _feature_counter is None:
61
+ meter = metrics.get_meter('genkit')
62
+ _feature_counter = meter.create_counter(
63
+ 'genkit/feature/requests',
64
+ description='Counts calls to genkit features.',
65
+ unit='1',
66
+ )
67
+ return _feature_counter
68
+
69
+
70
+ def _get_feature_latency() -> metrics.Histogram:
71
+ """Get or create the feature latency histogram."""
72
+ global _feature_latency
73
+ if _feature_latency is None:
74
+ meter = metrics.get_meter('genkit')
75
+ _feature_latency = meter.create_histogram(
76
+ 'genkit/feature/latency',
77
+ description='Latencies when calling Genkit features.',
78
+ unit='ms',
79
+ )
80
+ return _feature_latency
81
+
82
+
83
+ class FeaturesTelemetry:
84
+ """Telemetry handler for Genkit features (root spans)."""
85
+
86
+ def tick(
87
+ self,
88
+ span: ReadableSpan,
89
+ log_input_and_output: bool,
90
+ project_id: str | None = None,
91
+ ) -> None:
92
+ """Record telemetry for a feature span.
93
+
94
+ Args:
95
+ span: The span to record telemetry for.
96
+ log_input_and_output: Whether to log input/output.
97
+ project_id: Optional GCP project ID.
98
+ """
99
+ attrs = span.attributes or {}
100
+ name = str(attrs.get('genkit:name', '<unknown>'))
101
+ path = str(attrs.get('genkit:path', ''))
102
+ state = str(attrs.get('genkit:state', ''))
103
+
104
+ # Calculate latency
105
+ latency_ms = 0.0
106
+ if span.end_time and span.start_time:
107
+ latency_ms = (span.end_time - span.start_time) / 1_000_000
108
+
109
+ if state == 'success':
110
+ self._write_feature_success(name, latency_ms)
111
+ elif state == 'error':
112
+ error_name = extract_error_name(list(span.events)) or '<unknown>'
113
+ self._write_feature_failure(name, latency_ms, error_name)
114
+ else:
115
+ logger.warning('Unknown state', state=state)
116
+ return
117
+
118
+ if log_input_and_output:
119
+ input_val = truncate(str(attrs.get('genkit:input', '')))
120
+ output_val = truncate(str(attrs.get('genkit:output', '')))
121
+ session_id = str(attrs.get('genkit:sessionId', '')) or None
122
+ thread_name = str(attrs.get('genkit:threadName', '')) or None
123
+
124
+ if input_val:
125
+ self._write_log(span, 'Input', name, path, input_val, project_id, session_id, thread_name)
126
+ if output_val:
127
+ self._write_log(span, 'Output', name, path, output_val, project_id, session_id, thread_name)
128
+
129
+ def _write_feature_success(self, feature_name: str, latency_ms: float) -> None:
130
+ """Record success metrics for a feature."""
131
+ dimensions = {
132
+ 'name': feature_name[:256],
133
+ 'status': 'success',
134
+ 'source': 'py',
135
+ 'sourceVersion': GENKIT_VERSION,
136
+ }
137
+ _get_feature_counter().add(1, dimensions)
138
+ _get_feature_latency().record(latency_ms, dimensions)
139
+
140
+ def _write_feature_failure(
141
+ self,
142
+ feature_name: str,
143
+ latency_ms: float,
144
+ error_name: str,
145
+ ) -> None:
146
+ """Record failure metrics for a feature."""
147
+ dimensions = {
148
+ 'name': feature_name[:256],
149
+ 'status': 'failure',
150
+ 'source': 'py',
151
+ 'sourceVersion': GENKIT_VERSION,
152
+ 'error': error_name[:256],
153
+ }
154
+ _get_feature_counter().add(1, dimensions)
155
+ _get_feature_latency().record(latency_ms, dimensions)
156
+
157
+ def _write_log(
158
+ self,
159
+ span: ReadableSpan,
160
+ tag: str,
161
+ feature_name: str,
162
+ qualified_path: str,
163
+ content: str,
164
+ project_id: str | None,
165
+ session_id: str | None,
166
+ thread_name: str | None,
167
+ ) -> None:
168
+ """Write a structured log entry."""
169
+ path = truncate_path(to_display_path(qualified_path))
170
+ metadata = {
171
+ **create_common_log_attributes(span, project_id),
172
+ 'path': path,
173
+ 'qualifiedPath': qualified_path,
174
+ 'featureName': feature_name,
175
+ 'content': content,
176
+ }
177
+ if session_id:
178
+ metadata['sessionId'] = session_id
179
+ if thread_name:
180
+ metadata['threadName'] = thread_name
181
+
182
+ logger.info(f'{tag}[{path}, {feature_name}]', **metadata)
183
+
184
+
185
+ # Singleton instance
186
+ features_telemetry = FeaturesTelemetry()