@@ -7,6 +7,18 @@ use tower_http::cors::CorsLayer;
77use tracing:: info;
88use tracing_subscriber:: { fmt, layer:: SubscriberExt , util:: SubscriberInitExt , EnvFilter } ;
99
10+ use opentelemetry:: { global, trace:: TracerProvider as _, KeyValue } ;
11+ use opentelemetry_sdk:: {
12+ metrics:: { MeterProviderBuilder , PeriodicReader , SdkMeterProvider } ,
13+ trace:: { RandomIdGenerator , Sampler , SdkTracerProvider } ,
14+ Resource ,
15+ } ;
16+ use opentelemetry_semantic_conventions:: {
17+ attribute:: { DEPLOYMENT_ENVIRONMENT_NAME , SERVICE_NAME , SERVICE_VERSION } ,
18+ SCHEMA_URL ,
19+ } ;
20+ use tracing_opentelemetry:: { MetricsLayer , OpenTelemetryLayer } ;
21+
1022use daily_task:: run_daily_task_at_midnight;
1123use graphql:: { Mutation , Query } ;
1224use routes:: setup_router;
@@ -37,10 +49,27 @@ impl Config {
3749 }
3850}
3951
52+ struct OtelGuard {
53+ tracer_provider : SdkTracerProvider ,
54+ meter_provider : SdkMeterProvider ,
55+ }
56+
57+ impl Drop for OtelGuard {
58+ fn drop ( & mut self ) {
59+ if let Err ( err) = self . tracer_provider . shutdown ( ) {
60+ eprintln ! ( "{err:?}" ) ;
61+ }
62+ if let Err ( err) = self . meter_provider . shutdown ( ) {
63+ eprintln ! ( "{err:?}" ) ;
64+ }
65+ }
66+ }
67+
4068#[ tokio:: main]
69+ #[ tracing:: instrument]
4170async fn main ( ) {
4271 let config = Config :: from_env ( ) ;
43- setup_tracing ( & config. env ) ;
72+ let guard = setup_tracing ( & config. env ) ;
4473
4574 let pool = setup_database ( & config. database_url ) . await ;
4675 let schema = build_graphql_schema ( pool. clone ( ) , config. secret_key ) ;
@@ -56,10 +85,85 @@ async fn main() {
5685 let listener = tokio:: net:: TcpListener :: bind ( format ! ( "0.0.0.0:{}" , config. port) )
5786 . await
5887 . unwrap ( ) ;
59- axum:: serve ( listener, router) . await . unwrap ( ) ;
88+
89+ axum:: serve ( listener, router)
90+ . with_graceful_shutdown ( shutdown_signal ( ) )
91+ . await
92+ . unwrap ( ) ;
93+
94+ drop ( guard) ;
95+ }
96+
97+ #[ tracing:: instrument]
98+ async fn shutdown_signal ( ) {
99+ // Wait for Ctrl-C
100+ tokio:: signal:: ctrl_c ( )
101+ . await
102+ . expect ( "failed to install Ctrl+C handler" ) ;
103+
104+ tracing:: info!( "Shutdown signal received. Flushing telemetry..." ) ;
105+
106+ // Flush traces and metrics
107+ // guard.tracer_provider.shutdown().unwrap();
108+ }
109+
110+ fn resource ( ) -> Resource {
111+ Resource :: builder ( )
112+ . with_attributes ( vec ! [
113+ KeyValue :: new( SERVICE_NAME , env!( "CARGO_PKG_NAME" ) ) ,
114+ KeyValue :: new( SERVICE_VERSION , env!( "CARGO_PKG_VERSION" ) ) ,
115+ KeyValue :: new( DEPLOYMENT_ENVIRONMENT_NAME , "develop" ) ,
116+ ] )
117+ . with_schema_url ( Vec :: new ( ) , SCHEMA_URL )
118+ . build ( )
119+ }
120+
121+ fn init_meter_provider ( ) -> SdkMeterProvider {
122+ let exporter = opentelemetry_otlp:: MetricExporter :: builder ( )
123+ . with_tonic ( )
124+ . with_temporality ( opentelemetry_sdk:: metrics:: Temporality :: default ( ) )
125+ . build ( )
126+ . unwrap ( ) ;
127+
128+ let reader = PeriodicReader :: builder ( exporter)
129+ . with_interval ( std:: time:: Duration :: from_secs ( 30 ) )
130+ . build ( ) ;
131+
132+ let stdout_reader =
133+ PeriodicReader :: builder ( opentelemetry_stdout:: MetricExporter :: default ( ) ) . build ( ) ;
134+
135+ let meter_provider = MeterProviderBuilder :: default ( )
136+ . with_resource ( resource ( ) )
137+ . with_reader ( reader)
138+ . with_reader ( stdout_reader)
139+ . build ( ) ;
140+
141+ global:: set_meter_provider ( meter_provider. clone ( ) ) ;
142+
143+ meter_provider
144+ }
145+
146+ fn init_tracer_provider ( ) -> SdkTracerProvider {
147+ let exporter = opentelemetry_otlp:: SpanExporter :: builder ( )
148+ . with_tonic ( )
149+ . build ( )
150+ . unwrap ( ) ;
151+
152+ SdkTracerProvider :: builder ( )
153+ . with_sampler ( Sampler :: ParentBased ( Box :: new ( Sampler :: TraceIdRatioBased (
154+ 1.0 ,
155+ ) ) ) )
156+ . with_id_generator ( RandomIdGenerator :: default ( ) )
157+ . with_resource ( resource ( ) )
158+ . with_batch_exporter ( exporter)
159+ . build ( )
60160}
61161
62- fn setup_tracing ( env : & str ) {
162+ fn setup_tracing ( env : & str ) -> OtelGuard {
163+ let tracer_provider = init_tracer_provider ( ) ;
164+ let meter_provider = init_meter_provider ( ) ;
165+ let tracer = tracer_provider. tracer ( "tracing-otel-subscriber" ) ;
166+
63167 let kolkata_offset = UtcOffset :: from_hms ( 5 , 30 , 0 ) . expect ( "Hardcoded offset must be correct" ) ;
64168 let timer = fmt:: time:: OffsetTime :: new (
65169 kolkata_offset,
@@ -75,6 +179,8 @@ fn setup_tracing(env: &str) {
75179 . with_ansi ( false ) // ANSI encodings are unreadable in the raw file.
76180 . with_writer ( std:: fs:: File :: create ( "root.log" ) . unwrap ( ) ) ,
77181 )
182+ . with ( MetricsLayer :: new ( meter_provider. clone ( ) ) )
183+ . with ( OpenTelemetryLayer :: new ( tracer) )
78184 . with ( EnvFilter :: new ( "info" ) )
79185 . init ( ) ;
80186 info ! ( "Running in production mode." )
@@ -93,10 +199,17 @@ fn setup_tracing(env: &str) {
93199 . with_ansi ( false )
94200 . with_writer ( std:: fs:: File :: create ( "root.log" ) . unwrap ( ) ) ,
95201 )
202+ . with ( MetricsLayer :: new ( meter_provider. clone ( ) ) )
203+ . with ( OpenTelemetryLayer :: new ( tracer) )
96204 . with ( EnvFilter :: new ( "trace" ) )
97205 . init ( ) ;
98206 info ! ( "Running in development mode." ) ;
99207 }
208+
209+ OtelGuard {
210+ tracer_provider,
211+ meter_provider,
212+ }
100213}
101214
102215async fn setup_database ( database_url : & str ) -> Arc < PgPool > {
0 commit comments