@@ -8,10 +8,12 @@ use std::pin::Pin;
88use std:: str:: FromStr ;
99use std:: sync:: Arc ;
1010use std:: time:: Duration ;
11+ use temporal_sdk_core:: telemetry:: { build_otlp_metric_exporter, start_prometheus_metric_exporter} ;
1112use temporal_sdk_core:: CoreRuntime ;
13+ use temporal_sdk_core_api:: telemetry:: metrics:: CoreMeter ;
1214use temporal_sdk_core_api:: telemetry:: {
13- Logger , MetricsExporter , OtelCollectorOptions , TelemetryOptions , TelemetryOptionsBuilder ,
14- TraceExportConfig , TraceExporter ,
15+ Logger , MetricTemporality , OtelCollectorOptionsBuilder , PrometheusExporterOptionsBuilder ,
16+ TelemetryOptions , TelemetryOptionsBuilder ,
1517} ;
1618use url:: Url ;
1719
@@ -27,16 +29,8 @@ pub(crate) struct Runtime {
2729
2830#[ derive( FromPyObject ) ]
2931pub struct TelemetryConfig {
30- tracing : Option < TracingConfig > ,
3132 logging : Option < LoggingConfig > ,
3233 metrics : Option < MetricsConfig > ,
33- global_tags : Option < HashMap < String , String > >
34- }
35-
36- #[ derive( FromPyObject ) ]
37- pub struct TracingConfig {
38- filter : String ,
39- opentelemetry : OpenTelemetryConfig ,
4034}
4135
4236#[ derive( FromPyObject ) ]
@@ -49,32 +43,44 @@ pub struct LoggingConfig {
4943pub struct MetricsConfig {
5044 opentelemetry : Option < OpenTelemetryConfig > ,
5145 prometheus : Option < PrometheusConfig > ,
46+ attach_service_name : bool ,
47+ global_tags : Option < HashMap < String , String > > ,
48+ metric_prefix : Option < String > ,
5249}
5350
5451#[ derive( FromPyObject ) ]
5552pub struct OpenTelemetryConfig {
5653 url : String ,
5754 headers : HashMap < String , String > ,
5855 metric_periodicity_millis : Option < u64 > ,
56+ metric_temporality_delta : bool ,
5957}
6058
6159#[ derive( FromPyObject ) ]
6260pub struct PrometheusConfig {
6361 bind_address : String ,
62+ counters_total_suffix : bool ,
63+ unit_suffix : bool ,
6464}
6565
6666pub fn init_runtime ( telemetry_config : TelemetryConfig ) -> PyResult < RuntimeRef > {
67+ let mut core = CoreRuntime :: new (
68+ // We don't move telemetry config here because we need it for
69+ // late-binding metrics
70+ ( & telemetry_config) . try_into ( ) ?,
71+ tokio:: runtime:: Builder :: new_multi_thread ( ) ,
72+ )
73+ . map_err ( |err| PyRuntimeError :: new_err ( format ! ( "Failed initializing telemetry: {}" , err) ) ) ?;
74+ // We late-bind the metrics after core runtime is created since it needs
75+ // the Tokio handle
76+ if let Some ( metrics_conf) = telemetry_config. metrics {
77+ let _guard = core. tokio_handle ( ) . enter ( ) ;
78+ core. telemetry_mut ( )
79+ . attach_late_init_metrics ( metrics_conf. try_into ( ) ?) ;
80+ }
6781 Ok ( RuntimeRef {
6882 runtime : Runtime {
69- core : Arc :: new (
70- CoreRuntime :: new (
71- telemetry_config. try_into ( ) ?,
72- tokio:: runtime:: Builder :: new_multi_thread ( ) ,
73- )
74- . map_err ( |err| {
75- PyRuntimeError :: new_err ( format ! ( "Failed initializing telemetry: {}" , err) )
76- } ) ?,
77- ) ,
83+ core : Arc :: new ( core) ,
7884 } ,
7985 } )
8086}
@@ -94,61 +100,97 @@ impl Runtime {
94100 }
95101}
96102
97- impl TryFrom < TelemetryConfig > for TelemetryOptions {
103+ impl TryFrom < & TelemetryConfig > for TelemetryOptions {
98104 type Error = PyErr ;
99105
100- fn try_from ( conf : TelemetryConfig ) -> PyResult < Self > {
106+ fn try_from ( conf : & TelemetryConfig ) -> PyResult < Self > {
101107 let mut build = TelemetryOptionsBuilder :: default ( ) ;
102- if let Some ( v) = conf. tracing {
103- build. tracing ( TraceExportConfig {
104- filter : v. filter ,
105- exporter : TraceExporter :: Otel ( v. opentelemetry . try_into ( ) ?) ,
106- } ) ;
107- }
108- if let Some ( v) = conf. logging {
109- build. logging ( if v. forward {
110- Logger :: Forward { filter : v. filter }
111- } else {
112- Logger :: Console { filter : v. filter }
113- } ) ;
114- }
115- if let Some ( v) = conf. metrics {
116- build. metrics ( if let Some ( t) = v. opentelemetry {
117- if v. prometheus . is_some ( ) {
118- return Err ( PyValueError :: new_err (
119- "Cannot have OpenTelemetry and Prometheus metrics" ,
120- ) ) ;
108+ if let Some ( logging_conf) = & conf. logging {
109+ build. logging ( if logging_conf. forward {
110+ Logger :: Forward {
111+ filter : logging_conf. filter . to_string ( ) ,
121112 }
122- MetricsExporter :: Otel ( t. try_into ( ) ?)
123- } else if let Some ( t) = v. prometheus {
124- MetricsExporter :: Prometheus ( SocketAddr :: from_str ( & t. bind_address ) . map_err (
125- |err| PyValueError :: new_err ( format ! ( "Invalid Prometheus address: {}" , err) ) ,
126- ) ?)
127113 } else {
128- return Err ( PyValueError :: new_err (
129- "Either OpenTelemetry or Prometheus config must be provided" ,
130- ) ) ;
114+ Logger :: Console {
115+ filter : logging_conf . filter . to_string ( ) ,
116+ }
131117 } ) ;
132118 }
133- if let Some ( v) = conf. global_tags {
134- build. global_tags ( v) ;
119+ if let Some ( metrics_conf) = & conf. metrics {
120+ // Note, actual metrics instance is late-bound in init_runtime
121+ build. attach_service_name ( metrics_conf. attach_service_name ) ;
122+ if let Some ( prefix) = & metrics_conf. metric_prefix {
123+ build. metric_prefix ( prefix. to_string ( ) ) ;
124+ }
135125 }
136126 build
137127 . build ( )
138128 . map_err ( |err| PyValueError :: new_err ( format ! ( "Invalid telemetry config: {}" , err) ) )
139129 }
140130}
141131
142- impl TryFrom < OpenTelemetryConfig > for OtelCollectorOptions {
132+ impl TryFrom < MetricsConfig > for Arc < dyn CoreMeter > {
143133 type Error = PyErr ;
144134
145- fn try_from ( conf : OpenTelemetryConfig ) -> PyResult < Self > {
146- Ok ( OtelCollectorOptions {
147- url : Url :: parse ( & conf. url )
148- . map_err ( |err| PyValueError :: new_err ( format ! ( "Invalid OTel URL: {}" , err) ) ) ?,
149- headers : conf. headers ,
150- metric_periodicity : conf. metric_periodicity_millis . map ( Duration :: from_millis) ,
151- } )
135+ fn try_from ( conf : MetricsConfig ) -> PyResult < Self > {
136+ if let Some ( otel_conf) = conf. opentelemetry {
137+ if !conf. prometheus . is_none ( ) {
138+ return Err ( PyValueError :: new_err (
139+ "Cannot have OpenTelemetry and Prometheus metrics" ,
140+ ) ) ;
141+ }
142+
143+ // Build OTel exporter
144+ let mut build = OtelCollectorOptionsBuilder :: default ( ) ;
145+ build
146+ . url (
147+ Url :: parse ( & otel_conf. url ) . map_err ( |err| {
148+ PyValueError :: new_err ( format ! ( "Invalid OTel URL: {}" , err) )
149+ } ) ?,
150+ )
151+ . headers ( otel_conf. headers ) ;
152+ if let Some ( period) = otel_conf. metric_periodicity_millis {
153+ build. metric_periodicity ( Duration :: from_millis ( period) ) ;
154+ }
155+ if otel_conf. metric_temporality_delta {
156+ build. metric_temporality ( MetricTemporality :: Delta ) ;
157+ }
158+ if let Some ( global_tags) = conf. global_tags {
159+ build. global_tags ( global_tags) ;
160+ }
161+ let otel_options = build
162+ . build ( )
163+ . map_err ( |err| PyValueError :: new_err ( format ! ( "Invalid OTel config: {}" , err) ) ) ?;
164+ Ok ( Arc :: new ( build_otlp_metric_exporter ( otel_options) . map_err (
165+ |err| PyValueError :: new_err ( format ! ( "Failed building OTel exporter: {}" , err) ) ,
166+ ) ?) )
167+ } else if let Some ( prom_conf) = conf. prometheus {
168+ // Start prom exporter
169+ let mut build = PrometheusExporterOptionsBuilder :: default ( ) ;
170+ build
171+ . socket_addr (
172+ SocketAddr :: from_str ( & prom_conf. bind_address ) . map_err ( |err| {
173+ PyValueError :: new_err ( format ! ( "Invalid Prometheus address: {}" , err) )
174+ } ) ?,
175+ )
176+ . counters_total_suffix ( prom_conf. counters_total_suffix )
177+ . unit_suffix ( prom_conf. unit_suffix ) ;
178+ if let Some ( global_tags) = conf. global_tags {
179+ build. global_tags ( global_tags) ;
180+ }
181+ let prom_options = build. build ( ) . map_err ( |err| {
182+ PyValueError :: new_err ( format ! ( "Invalid Prometheus config: {}" , err) )
183+ } ) ?;
184+ Ok ( start_prometheus_metric_exporter ( prom_options)
185+ . map_err ( |err| {
186+ PyValueError :: new_err ( format ! ( "Failed starting Prometheus exporter: {}" , err) )
187+ } ) ?
188+ . meter )
189+ } else {
190+ Err ( PyValueError :: new_err (
191+ "Either OpenTelemetry or Prometheus config must be provided" ,
192+ ) )
193+ }
152194 }
153195}
154196
0 commit comments