1mod file_retention;
17
18use std::collections::HashMap;
19use std::env;
20use std::io::IsTerminal;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex, Once};
23use std::time::Duration;
24
25use common_base::readable_size::ReadableSize;
26use common_base::serde::empty_string_as_default;
27use file_retention::{DirectoryRetention, LogFileKind, build_file_appender};
28use once_cell::sync::{Lazy, OnceCell};
29use opentelemetry::trace::TracerProvider;
30use opentelemetry::{KeyValue, global};
31use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig, WithHttpConfig};
32use opentelemetry_sdk::propagation::TraceContextPropagator;
33use opentelemetry_sdk::trace::{Sampler, Tracer};
34use opentelemetry_semantic_conventions::resource;
35use serde::{Deserialize, Serialize};
36use tracing::callsite;
37use tracing::metadata::LevelFilter;
38use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
39use tracing_log::LogTracer;
40use tracing_subscriber::filter::{FilterFn, Targets};
41use tracing_subscriber::fmt::Layer;
42use tracing_subscriber::layer::{Layered, SubscriberExt};
43use tracing_subscriber::prelude::*;
44use tracing_subscriber::{EnvFilter, Registry, filter};
45
46use crate::tracing_sampler::{TracingSampleOptions, create_sampler};
47
48pub const DEFAULT_OTLP_GRPC_ENDPOINT: &str = "http://localhost:4317";
50
51pub const DEFAULT_OTLP_HTTP_ENDPOINT: &str = "http://localhost:4318/v1/traces";
53
54pub const DEFAULT_LOGGING_DIR: &str = "logs";
56
57pub static LOG_RELOAD_HANDLE: OnceCell<tracing_subscriber::reload::Handle<Targets, Registry>> =
59 OnceCell::new();
60
61#[doc(hidden)]
63pub static SLOW_QUERY_LOGGER: OnceCell<tracing::Dispatch> = OnceCell::new();
64
65type DynSubscriber = Layered<tracing_subscriber::reload::Layer<Targets, Registry>, Registry>;
66type OtelTraceLayer = tracing_opentelemetry::OpenTelemetryLayer<DynSubscriber, Tracer>;
67
68struct TraceLayerState {
69 enabled: AtomicBool,
70 layer: OnceCell<OtelTraceLayer>,
71}
72
73#[derive(Clone)]
74pub struct TraceReloadHandle {
75 inner: Arc<TraceLayerState>,
76}
77
78impl TraceReloadHandle {
79 fn new(inner: Arc<TraceLayerState>) -> Self {
80 Self { inner }
81 }
82
83 pub fn set_enabled(&self, enabled: bool) -> Result<(), &'static str> {
87 self.set_enabled_with(enabled, || {
88 get_or_init_tracer().map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer))
89 })
90 }
91
92 fn set_enabled_with(
93 &self,
94 enabled: bool,
95 init: impl FnOnce() -> Result<OtelTraceLayer, &'static str>,
96 ) -> Result<(), &'static str> {
97 if enabled {
98 self.inner.layer.get_or_try_init(init)?;
99 }
100 self.inner.enabled.store(enabled, Ordering::Release);
101 callsite::rebuild_interest_cache();
102 Ok(())
103 }
104}
105
106struct TraceLayer {
108 inner: Arc<TraceLayerState>,
109}
110
111impl TraceLayer {
112 fn new(initial: Option<OtelTraceLayer>) -> (Self, TraceReloadHandle) {
113 let inner = Arc::new(TraceLayerState {
114 enabled: AtomicBool::new(initial.is_some()),
115 layer: initial.map(OnceCell::with_value).unwrap_or_default(),
116 });
117 (
118 Self {
119 inner: inner.clone(),
120 },
121 TraceReloadHandle::new(inner),
122 )
123 }
124
125 fn with_layer<R>(&self, f: impl FnOnce(&OtelTraceLayer) -> R) -> Option<R> {
126 self.inner.layer.get().map(f)
127 }
128
129 fn is_enabled(&self) -> bool {
130 self.inner.enabled.load(Ordering::Acquire)
131 }
132}
133
134impl tracing_subscriber::Layer<DynSubscriber> for TraceLayer {
135 fn on_register_dispatch(&self, subscriber: &tracing::Dispatch) {
136 let _ = self.with_layer(|layer| layer.on_register_dispatch(subscriber));
137 }
138
139 fn register_callsite(
140 &self,
141 metadata: &'static tracing::Metadata<'static>,
142 ) -> tracing::subscriber::Interest {
143 self.with_layer(|layer| layer.register_callsite(metadata))
144 .unwrap_or_else(tracing::subscriber::Interest::always)
145 }
146
147 fn enabled(
148 &self,
149 metadata: &tracing::Metadata<'_>,
150 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
151 ) -> bool {
152 self.with_layer(|layer| layer.enabled(metadata, ctx))
153 .unwrap_or(true)
154 }
155
156 fn on_new_span(
157 &self,
158 attrs: &tracing::span::Attributes<'_>,
159 id: &tracing::span::Id,
160 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
161 ) {
162 if self.is_enabled() {
163 let _ = self.with_layer(|layer| layer.on_new_span(attrs, id, ctx));
164 }
165 }
166
167 fn max_level_hint(&self) -> Option<LevelFilter> {
168 self.with_layer(|layer| layer.max_level_hint()).flatten()
169 }
170
171 fn on_record(
172 &self,
173 span: &tracing::span::Id,
174 values: &tracing::span::Record<'_>,
175 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
176 ) {
177 if self.is_enabled() {
178 let _ = self.with_layer(|layer| layer.on_record(span, values, ctx));
179 }
180 }
181
182 fn on_follows_from(
183 &self,
184 span: &tracing::span::Id,
185 follows: &tracing::span::Id,
186 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
187 ) {
188 if self.is_enabled() {
189 let _ = self.with_layer(|layer| layer.on_follows_from(span, follows, ctx));
190 }
191 }
192
193 fn event_enabled(
194 &self,
195 event: &tracing::Event<'_>,
196 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
197 ) -> bool {
198 self.with_layer(|layer| layer.event_enabled(event, ctx))
199 .unwrap_or(true)
200 }
201
202 fn on_event(
203 &self,
204 event: &tracing::Event<'_>,
205 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
206 ) {
207 if self.is_enabled() {
208 let _ = self.with_layer(|layer| layer.on_event(event, ctx));
209 }
210 }
211
212 fn on_enter(
213 &self,
214 id: &tracing::span::Id,
215 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
216 ) {
217 let _ = self.with_layer(|layer| layer.on_enter(id, ctx));
218 }
219
220 fn on_exit(
221 &self,
222 id: &tracing::span::Id,
223 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
224 ) {
225 let _ = self.with_layer(|layer| layer.on_exit(id, ctx));
226 }
227
228 fn on_close(
229 &self,
230 id: tracing::span::Id,
231 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
232 ) {
233 let _ = self.with_layer(|layer| layer.on_close(id, ctx));
234 }
235
236 fn on_id_change(
237 &self,
238 old: &tracing::span::Id,
239 new: &tracing::span::Id,
240 ctx: tracing_subscriber::layer::Context<'_, DynSubscriber>,
241 ) {
242 let _ = self.with_layer(|layer| layer.on_id_change(old, new, ctx));
243 }
244
245 unsafe fn downcast_raw(&self, id: std::any::TypeId) -> Option<*const ()> {
246 self.inner
250 .layer
251 .get()
252 .and_then(|layer| unsafe { layer.downcast_raw(id) })
253 }
254}
255
256pub static TRACE_RELOAD_HANDLE: OnceCell<TraceReloadHandle> = OnceCell::new();
258
259static TRACER: OnceCell<Mutex<TraceState>> = OnceCell::new();
260
261#[derive(Debug)]
262enum TraceState {
263 Ready(Tracer),
264 Deferred(TraceContext),
265}
266
267#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
269#[serde(default)]
270pub struct LoggingOptions {
271 pub dir: String,
273
274 pub level: Option<String>,
276
277 #[serde(default, deserialize_with = "empty_string_as_default")]
279 pub log_format: LogFormat,
280
281 pub max_log_files: usize,
283
284 pub max_log_dir_size: ReadableSize,
286
287 pub append_stdout: bool,
289
290 pub enable_file_logging: bool,
292
293 pub enable_otlp_tracing: bool,
295
296 pub otlp_endpoint: Option<String>,
298
299 pub tracing_sample_ratio: Option<TracingSampleOptions>,
301
302 pub otlp_export_protocol: Option<OtlpExportProtocol>,
304
305 #[serde(skip_serializing_if = "HashMap::is_empty")]
307 pub otlp_headers: HashMap<String, String>,
308
309 pub enable_per_region_metrics: bool,
311}
312
313#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
315#[serde(rename_all = "snake_case")]
316pub enum OtlpExportProtocol {
317 Grpc,
319
320 Http,
322}
323
324#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
326#[serde(default)]
327pub struct SlowQueryOptions {
328 pub enable: bool,
330
331 #[serde(deserialize_with = "empty_string_as_default")]
334 pub record_type: SlowQueriesRecordType,
335
336 #[serde(with = "humantime_serde")]
338 pub threshold: Duration,
339
340 pub sample_ratio: f64,
342
343 #[serde(with = "humantime_serde")]
346 pub ttl: Duration,
347}
348
349impl Default for SlowQueryOptions {
350 fn default() -> Self {
351 Self {
352 enable: true,
353 record_type: SlowQueriesRecordType::SystemTable,
354 threshold: Duration::from_secs(30),
355 sample_ratio: 1.0,
356 ttl: Duration::from_secs(90 * 86400),
357 }
358 }
359}
360
361#[derive(Clone, Debug, Serialize, Deserialize, Copy, PartialEq, Default)]
362#[serde(rename_all = "snake_case")]
363pub enum SlowQueriesRecordType {
364 #[default]
366 SystemTable,
367 Log,
369}
370
371#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
372#[serde(rename_all = "snake_case")]
373pub enum LogFormat {
374 Json,
375 #[default]
376 Text,
377}
378
379#[derive(Clone, Debug)]
380struct TraceContext {
381 app_name: String,
382 node_id: String,
383 logging_opts: LoggingOptions,
384}
385
386impl Default for LoggingOptions {
387 fn default() -> Self {
388 Self {
389 dir: "".to_string(),
391 level: None,
392 log_format: LogFormat::Text,
393 enable_otlp_tracing: false,
394 otlp_endpoint: None,
395 tracing_sample_ratio: None,
396 append_stdout: true,
397 enable_file_logging: true,
398 max_log_files: 720,
400 max_log_dir_size: ReadableSize::default(),
401 otlp_export_protocol: None,
402 otlp_headers: HashMap::new(),
403 enable_per_region_metrics: false,
404 }
405 }
406}
407
408#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
409pub struct TracingOptions {
410 #[cfg(feature = "tokio-console")]
411 pub tokio_console_addr: Option<String>,
412}
413
414pub fn init_default_ut_logging() {
417 static START: Once = Once::new();
418
419 START.call_once(|| {
420 let mut g = GLOBAL_UT_LOG_GUARD.as_ref().lock().unwrap();
421
422 let dir =
427 env::var("UNITTEST_LOG_DIR").unwrap_or_else(|_| "/tmp/__unittest_logs".to_string());
428
429 let level = env::var("UNITTEST_LOG_LEVEL").unwrap_or_else(|_|
430 "debug,hyper=warn,tower=warn,datafusion=warn,reqwest=warn,sqlparser=warn,h2=info,opendal=info,rskafka=info".to_string()
431 );
432 let opts = LoggingOptions {
433 dir: dir.clone(),
434 level: Some(level),
435 ..Default::default()
436 };
437 *g = Some(init_global_logging(
438 "unittest",
439 &opts,
440 &TracingOptions::default(),
441 None,
442 None,
443 ));
444
445 crate::info!("logs dir = {}", dir);
446 });
447}
448
449static GLOBAL_UT_LOG_GUARD: Lazy<Arc<Mutex<Option<Vec<WorkerGuard>>>>> =
450 Lazy::new(|| Arc::new(Mutex::new(None)));
451
452const DEFAULT_LOG_TARGETS: &str = "info";
453
454#[allow(clippy::print_stdout)]
455pub fn init_global_logging(
456 app_name: &str,
457 opts: &LoggingOptions,
458 tracing_opts: &TracingOptions,
459 node_id: Option<String>,
460 slow_query_opts: Option<&SlowQueryOptions>,
461) -> Vec<WorkerGuard> {
462 static START: Once = Once::new();
463 let mut guards = vec![];
464 let node_id = node_id.unwrap_or_else(|| "none".to_string());
465
466 START.call_once(|| {
467 LogTracer::init().expect("log tracer must be valid");
469
470 let stdout_writer = if opts.append_stdout {
471 let (writer, guard) = tracing_appender::non_blocking(std::io::stdout());
472 guards.push(guard);
473 Some(writer)
474 } else {
475 None
476 };
477 let stdout_logging_layer = stdout_writer.clone().map(|writer| {
478 build_logging_layer(writer, opts.log_format, std::io::stdout().is_terminal())
479 });
480
481 let file_logging_enabled = opts.enable_file_logging && !opts.dir.is_empty();
482 let slow_query_logging_enabled = slow_query_opts
483 .is_some_and(|opts| opts.enable && opts.record_type == SlowQueriesRecordType::Log);
484
485 let retention = file_logging_enabled
486 .then(|| {
487 DirectoryRetention::new(opts.dir.clone(), opts.max_log_dir_size, opts.max_log_files)
488 })
489 .flatten();
490
491 let file_logging_layer = if file_logging_enabled {
493 let rolling_appender =
494 build_file_appender(opts, LogFileKind::Default, retention.as_ref());
495 let (writer, guard) = tracing_appender::non_blocking(rolling_appender);
496 guards.push(guard);
497
498 Some(build_logging_layer(writer, opts.log_format, false))
499 } else {
500 None
501 };
502
503 let err_file_logging_layer = if file_logging_enabled {
505 let rolling_appender =
506 build_file_appender(opts, LogFileKind::Error, retention.as_ref());
507 let (writer, guard) = tracing_appender::non_blocking(rolling_appender);
508 guards.push(guard);
509
510 Some(
511 build_logging_layer(writer, opts.log_format, false)
512 .with_filter(filter::LevelFilter::ERROR),
513 )
514 } else {
515 None
516 };
517
518 if slow_query_logging_enabled {
519 SLOW_QUERY_LOGGER
520 .set(build_slow_query_logger(
521 opts,
522 stdout_writer,
523 retention.as_ref(),
524 &mut guards,
525 ))
526 .expect("slow query logger already initialized");
527 }
528
529 if let Some(retention) = &retention {
530 retention.initialize();
531 }
532
533 let filter = opts
538 .level
539 .as_deref()
540 .or(env::var(EnvFilter::DEFAULT_ENV).ok().as_deref())
541 .unwrap_or(DEFAULT_LOG_TARGETS)
542 .parse::<filter::Targets>()
543 .expect("error parsing log level string");
544
545 let (dyn_filter, reload_handle) = tracing_subscriber::reload::Layer::new(filter.clone());
546
547 LOG_RELOAD_HANDLE
548 .set(reload_handle)
549 .expect("reload handle already set, maybe init_global_logging get called twice?");
550
551 let mut initial_tracer = None;
552 let trace_state = if opts.enable_otlp_tracing {
553 let tracer = create_tracer(app_name, &node_id, opts);
554 initial_tracer = Some(tracer.clone());
555 TraceState::Ready(tracer)
556 } else {
557 TraceState::Deferred(TraceContext {
558 app_name: app_name.to_string(),
559 node_id: node_id.clone(),
560 logging_opts: opts.clone(),
561 })
562 };
563
564 TRACER
565 .set(Mutex::new(trace_state))
566 .expect("trace state already initialized");
567
568 let initial_trace_layer = initial_tracer
569 .as_ref()
570 .map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer.clone()));
571
572 let (dyn_trace_layer, trace_reload_handle) = TraceLayer::new(initial_trace_layer);
573
574 TRACE_RELOAD_HANDLE
575 .set(trace_reload_handle)
576 .unwrap_or_else(|_| panic!("failed to set trace reload handle"));
577
578 #[cfg(feature = "tokio-console")]
581 let subscriber = {
582 let tokio_console_layer =
583 if let Some(tokio_console_addr) = &tracing_opts.tokio_console_addr {
584 let addr: std::net::SocketAddr = tokio_console_addr.parse().unwrap_or_else(|e| {
585 panic!("Invalid binding address '{tokio_console_addr}' for tokio-console: {e}");
586 });
587 println!("tokio-console listening on {addr}");
588
589 Some(
590 console_subscriber::ConsoleLayer::builder()
591 .server_addr(addr)
592 .spawn(),
593 )
594 } else {
595 None
596 };
597
598 Registry::default()
599 .with(dyn_filter)
600 .with(dyn_trace_layer)
601 .with(tokio_console_layer)
602 .with(stdout_logging_layer)
603 .with(file_logging_layer)
604 .with(err_file_logging_layer)
605 };
606
607 let _ = tracing_opts;
609
610 #[cfg(not(feature = "tokio-console"))]
611 let subscriber = Registry::default()
612 .with(dyn_filter)
613 .with(dyn_trace_layer)
614 .with(stdout_logging_layer)
615 .with(file_logging_layer)
616 .with(err_file_logging_layer);
617
618 global::set_text_map_propagator(TraceContextPropagator::new());
619
620 tracing::subscriber::set_global_default(subscriber)
621 .expect("error setting global tracing subscriber");
622 });
623
624 guards
625}
626
627fn create_tracer(app_name: &str, node_id: &str, opts: &LoggingOptions) -> Tracer {
628 let sampler = opts
629 .tracing_sample_ratio
630 .as_ref()
631 .map(create_sampler)
632 .map(Sampler::ParentBased)
633 .unwrap_or(Sampler::ParentBased(Box::new(Sampler::AlwaysOn)));
634
635 let resource = opentelemetry_sdk::Resource::builder_empty()
636 .with_attributes([
637 KeyValue::new(resource::SERVICE_NAME, app_name.to_string()),
638 KeyValue::new(resource::SERVICE_INSTANCE_ID, node_id.to_string()),
639 KeyValue::new(resource::SERVICE_VERSION, common_version::version()),
640 KeyValue::new(resource::PROCESS_PID, std::process::id().to_string()),
641 ])
642 .build();
643
644 opentelemetry_sdk::trace::SdkTracerProvider::builder()
645 .with_batch_exporter(build_otlp_exporter(opts))
646 .with_sampler(sampler)
647 .with_resource(resource)
648 .build()
649 .tracer("greptimedb")
650}
651
652pub fn get_or_init_tracer() -> Result<Tracer, &'static str> {
654 let state = TRACER.get().ok_or("trace state is not initialized")?;
655 let mut guard = state.lock().expect("trace state lock poisoned");
656
657 match &mut *guard {
658 TraceState::Ready(tracer) => Ok(tracer.clone()),
659 TraceState::Deferred(context) => {
660 let tracer = create_tracer(&context.app_name, &context.node_id, &context.logging_opts);
661 *guard = TraceState::Ready(tracer.clone());
662 Ok(tracer)
663 }
664 }
665}
666
667fn build_otlp_exporter(opts: &LoggingOptions) -> SpanExporter {
668 let protocol = opts
669 .otlp_export_protocol
670 .clone()
671 .unwrap_or(OtlpExportProtocol::Http);
672
673 let endpoint = opts
674 .otlp_endpoint
675 .as_ref()
676 .map(|e| {
677 if e.starts_with("http") {
678 e.clone()
679 } else {
680 format!("http://{}", e)
681 }
682 })
683 .unwrap_or_else(|| match protocol {
684 OtlpExportProtocol::Grpc => DEFAULT_OTLP_GRPC_ENDPOINT.to_string(),
685 OtlpExportProtocol::Http => DEFAULT_OTLP_HTTP_ENDPOINT.to_string(),
686 });
687
688 match protocol {
689 OtlpExportProtocol::Grpc => SpanExporter::builder()
690 .with_tonic()
691 .with_endpoint(endpoint)
692 .build()
693 .expect("Failed to create OTLP gRPC exporter "),
694
695 OtlpExportProtocol::Http => SpanExporter::builder()
696 .with_http()
697 .with_endpoint(endpoint)
698 .with_protocol(Protocol::HttpBinary)
699 .with_headers(opts.otlp_headers.clone())
700 .build()
701 .expect("Failed to create OTLP HTTP exporter "),
702 }
703}
704
705fn build_slow_query_logger(
706 opts: &LoggingOptions,
707 stdout_writer: Option<NonBlocking>,
708 retention: Option<&DirectoryRetention>,
709 guards: &mut Vec<WorkerGuard>,
710) -> tracing::Dispatch {
711 let stdout_layer = stdout_writer.map(|writer| {
712 build_logging_layer(writer, opts.log_format, std::io::stdout().is_terminal())
713 });
714 let file_layer = if opts.enable_file_logging && !opts.dir.is_empty() {
715 let rolling_appender = build_file_appender(opts, LogFileKind::SlowQuery, retention);
716 let (writer, guard) = tracing_appender::non_blocking(rolling_appender);
717 guards.push(guard);
718 Some(build_logging_layer(writer, opts.log_format, false))
719 } else {
720 None
721 };
722 let filter = FilterFn::new(|metadata| {
724 *metadata.level() == tracing::Level::INFO && metadata.fields().field("slow").is_some()
725 })
726 .with_max_level_hint(LevelFilter::INFO);
727 tracing::Dispatch::new(
728 Registry::default()
729 .with(filter)
730 .with(stdout_layer)
731 .with(file_layer),
732 )
733}
734
735fn build_logging_layer<S>(
736 writer: NonBlocking,
737 log_format: LogFormat,
738 ansi: bool,
739) -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync>
740where
741 S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
742{
743 if log_format == LogFormat::Json {
744 Layer::new()
745 .json()
746 .with_writer(writer)
747 .with_ansi(ansi)
748 .boxed()
749 } else {
750 Layer::new().with_writer(writer).with_ansi(ansi).boxed()
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use std::sync::Barrier;
757 use std::sync::atomic::AtomicUsize;
758
759 use opentelemetry::trace::TraceContextExt;
760 use opentelemetry_sdk::trace::SdkTracerProvider;
761 use tracing_opentelemetry::OpenTelemetrySpanExt;
762
763 use super::*;
764
765 #[test]
766 fn test_slow_query_logging() {
767 const CASE_ENV: &str = "GREPTIME_TEST_SLOW_QUERY_LOGGING";
768 let Ok(case) = env::var(CASE_ENV) else {
769 for case in [
771 "text",
772 "json",
773 "disabled",
774 "system_table",
775 "file_disabled",
776 "stdout_only_json",
777 "stdout_disabled",
778 "no_directory",
779 "no_options",
780 ] {
781 let output = std::process::Command::new(env::current_exe().unwrap())
782 .args([
783 "--exact",
784 "logging::tests::test_slow_query_logging",
785 "--nocapture",
786 ])
787 .env(CASE_ENV, case)
788 .output()
789 .unwrap();
790 assert!(
791 output.status.success(),
792 "case {case} failed:\n{}\n{}",
793 String::from_utf8_lossy(&output.stdout),
794 String::from_utf8_lossy(&output.stderr),
795 );
796 let stdout = String::from_utf8(output.stdout).unwrap();
797 let expected_slow_queries = if matches!(
798 case,
799 "text" | "json" | "file_disabled" | "stdout_only_json" | "no_directory"
800 ) {
801 4
802 } else {
803 0
804 };
805 assert_eq!(
806 stdout.matches("SELECT 1").count(),
807 expected_slow_queries,
808 "{case}: {stdout}"
809 );
810 assert_eq!(
811 stdout.matches("SELECT 2").count(),
812 expected_slow_queries,
813 "{case}: {stdout}"
814 );
815 assert_eq!(
816 stdout.matches("custom_slow_target").count(),
817 expected_slow_queries,
818 "{case}: {stdout}"
819 );
820 if case != "stdout_disabled" {
821 assert_eq!(
822 stdout.matches("ordinary-info").count(),
823 1,
824 "{case}: {stdout}"
825 );
826 assert_eq!(
827 stdout.matches("ordinary-warning").count(),
828 2,
829 "{case}: {stdout}"
830 );
831 } else {
832 assert!(!stdout.contains("ordinary-"), "{stdout}");
833 }
834 if matches!(case, "json" | "stdout_only_json") {
835 for line in stdout.lines().filter(|line| line.contains("SELECT")) {
836 let event: serde_json::Value = serde_json::from_str(line).unwrap();
837 assert_eq!(event["fields"]["slow"], true);
838 assert_eq!(event["level"], "INFO");
839 }
840 }
841 }
842 return;
843 };
844
845 let dir = tempfile::tempdir().unwrap();
846 let opts = LoggingOptions {
847 dir: if case == "no_directory" {
848 String::new()
849 } else {
850 dir.path().to_str().unwrap().to_string()
851 },
852 level: Some("warn".to_string()),
853 log_format: if matches!(case.as_str(), "json" | "stdout_only_json") {
854 LogFormat::Json
855 } else {
856 LogFormat::Text
857 },
858 append_stdout: case != "stdout_disabled",
859 enable_file_logging: !matches!(case.as_str(), "file_disabled" | "stdout_only_json"),
860 max_log_dir_size: ReadableSize::mb(1),
861 ..Default::default()
862 };
863 let slow_opts = SlowQueryOptions {
864 enable: case != "disabled",
865 record_type: if case == "system_table" {
866 SlowQueriesRecordType::SystemTable
867 } else {
868 SlowQueriesRecordType::Log
869 },
870 ..Default::default()
871 };
872 let guards = init_global_logging(
873 "slow-query-test",
874 &opts,
875 &TracingOptions::default(),
876 None,
877 (case != "no_options").then_some(&slow_opts),
878 );
879
880 for level in ["warn", "error", "off", "info"] {
881 LOG_RELOAD_HANDLE
882 .get()
883 .unwrap()
884 .reload(level.parse::<Targets>().unwrap())
885 .unwrap();
886 crate::info!(slow_operation = true, "ordinary-info");
887 crate::warn!("ordinary-warning");
888 crate::slow!(query = "SELECT 1", cost = 31, threshold = 30);
889 crate::slow!(target: "custom_slow_target", query = "SELECT 2");
890 }
891 drop(guards);
893
894 let mut slow_logs = String::new();
895 let mut regular_logs = String::new();
896 for entry in std::fs::read_dir(dir.path()).unwrap() {
897 let entry = entry.unwrap();
898 let name = entry.file_name();
899 let name = name.to_str().unwrap();
900 if name.starts_with("greptimedb-slow-queries.") {
901 slow_logs.push_str(&std::fs::read_to_string(entry.path()).unwrap());
902 } else if name.starts_with("greptimedb.") {
903 regular_logs.push_str(&std::fs::read_to_string(entry.path()).unwrap());
904 }
905 }
906
907 if matches!(case.as_str(), "text" | "json" | "stdout_disabled") {
908 assert_eq!(slow_logs.lines().count(), 8, "{slow_logs}");
909 assert_eq!(slow_logs.matches("SELECT 1").count(), 4);
910 assert_eq!(slow_logs.matches("SELECT 2").count(), 4);
911 assert_eq!(slow_logs.matches("custom_slow_target").count(), 4);
912 assert!(!slow_logs.contains("ordinary-"));
913 if case == "json" {
914 for line in slow_logs.lines() {
915 let event: serde_json::Value = serde_json::from_str(line).unwrap();
916 assert_eq!(event["fields"]["slow"], true);
917 assert_eq!(event["level"], "INFO");
918 }
919 }
920 } else {
921 assert!(slow_logs.is_empty(), "{slow_logs}");
922 }
923 if matches!(
924 case.as_str(),
925 "file_disabled" | "stdout_only_json" | "no_directory"
926 ) {
927 assert!(regular_logs.is_empty(), "{regular_logs}");
928 } else {
929 assert_eq!(regular_logs.matches("ordinary-info").count(), 1);
930 assert_eq!(regular_logs.matches("ordinary-warning").count(), 2);
931 assert!(!regular_logs.contains("SELECT"), "{regular_logs}");
932 }
933 }
934
935 #[test]
936 fn test_trace_switch_preserves_active_spans() {
937 let provider = SdkTracerProvider::builder()
938 .with_sampler(Sampler::AlwaysOn)
939 .build();
940 for initially_enabled in [false, true] {
941 let new_layer =
942 || tracing_opentelemetry::layer().with_tracer(provider.tracer("switch"));
943 let (filter, _) = tracing_subscriber::reload::Layer::new(
944 Targets::new().with_default(tracing::Level::INFO),
945 );
946 let (layer, handle) = TraceLayer::new(initially_enabled.then(new_layer));
947 let dispatch = tracing::Dispatch::new(Registry::default().with(filter).with(layer));
948 tracing::dispatcher::with_default(&dispatch, || {
949 let initial = tracing::info_span!("initial");
950 assert!(!initial.is_disabled());
951 assert_eq!(
952 initial.context().span().span_context().is_valid(),
953 initially_enabled
954 );
955 handle.set_enabled_with(true, || Ok(new_layer())).unwrap();
956 let parent = tracing::info_span!("parent");
957 let parent_context = parent.context();
958 assert!(parent_context.span().span_context().is_valid());
959 let previous_context = opentelemetry::Context::current();
960 let entered = parent.enter();
961 handle.set_enabled(false).unwrap();
962 let disabled = tracing::info_span!("disabled");
963 assert!(!disabled.is_disabled());
964 assert!(!disabled.context().span().span_context().is_valid());
965 assert_eq!(
966 parent.context().span().span_context(),
967 parent_context.span().span_context()
968 );
969 assert!(dispatch.downcast_ref::<OtelTraceLayer>().is_some());
970 drop(entered);
971 assert_eq!(
972 opentelemetry::Context::current().span().span_context(),
973 previous_context.span().span_context()
974 );
975 let disabled_entered = disabled.enter();
976 handle
977 .set_enabled_with(true, || panic!("layer must not be replaced"))
978 .unwrap();
979 drop(disabled_entered);
980 let child = tracing::info_span!(parent: &parent, "child");
981 assert_eq!(
982 child.context().span().span_context().trace_id(),
983 parent_context.span().span_context().trace_id()
984 );
985 assert!(!disabled.context().span().span_context().is_valid());
986 });
987 }
988 }
989
990 #[test]
991 fn test_trace_switch_retries_initialization() {
992 let (filter, _) = tracing_subscriber::reload::Layer::new(
993 Targets::new().with_default(tracing::Level::INFO),
994 );
995 let (layer, handle) = TraceLayer::new(None);
996 let subscriber = Registry::default().with(filter).with(layer);
997 tracing::subscriber::with_default(subscriber, || {
998 handle
999 .set_enabled_with(false, || panic!("disabling must not initialize OTLP"))
1000 .unwrap();
1001 assert_eq!(
1002 handle.set_enabled_with(true, || Err("initialization failed")),
1003 Err("initialization failed")
1004 );
1005 let failed = tracing::info_span!("after_failed_enable");
1006 assert!(!failed.context().span().span_context().is_valid());
1007 let provider = SdkTracerProvider::builder()
1008 .with_sampler(Sampler::AlwaysOn)
1009 .build();
1010 handle
1011 .set_enabled_with(true, || {
1012 Ok(tracing_opentelemetry::layer().with_tracer(provider.tracer("retry")))
1013 })
1014 .unwrap();
1015 let enabled = tracing::info_span!("after_successful_enable");
1016 assert!(enabled.context().span().span_context().is_valid());
1017 });
1018 }
1019
1020 #[test]
1021 fn test_trace_switch_stops_collecting_fields_when_disabled() {
1022 struct CountFormatting(AtomicUsize);
1023
1024 impl std::fmt::Debug for CountFormatting {
1025 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1026 self.0.fetch_add(1, Ordering::Relaxed);
1027 f.write_str("value")
1028 }
1029 }
1030
1031 let value = CountFormatting(AtomicUsize::new(0));
1032 let provider = SdkTracerProvider::builder()
1033 .with_sampler(Sampler::AlwaysOn)
1034 .build();
1035 let (filter, _) = tracing_subscriber::reload::Layer::new(
1036 Targets::new().with_default(tracing::Level::INFO),
1037 );
1038 let (layer, handle) = TraceLayer::new(Some(
1039 tracing_opentelemetry::layer().with_tracer(provider.tracer("fields")),
1040 ));
1041 tracing::subscriber::with_default(Registry::default().with(filter).with(layer), || {
1042 let admitted = tracing::info_span!("admitted", field = tracing::field::Empty);
1043 admitted.record("field", tracing::field::debug(&value));
1044 admitted.in_scope(|| tracing::info!(value = ?value));
1045 assert_eq!(value.0.load(Ordering::Relaxed), 2);
1046
1047 handle.set_enabled(false).unwrap();
1048 let unadmitted = tracing::info_span!("unadmitted", field = tracing::field::Empty);
1049 for span in [&admitted, &unadmitted] {
1050 span.record("field", tracing::field::debug(&value));
1051 span.in_scope(|| tracing::info!(value = ?value));
1052 }
1053 assert_eq!(value.0.load(Ordering::Relaxed), 2);
1054
1055 handle
1056 .set_enabled_with(true, || panic!("layer must not be replaced"))
1057 .unwrap();
1058 admitted.record("field", tracing::field::debug(&value));
1059 admitted.in_scope(|| tracing::info!(value = ?value));
1060 assert_eq!(value.0.load(Ordering::Relaxed), 4);
1061 });
1062 }
1063
1064 #[test]
1065 fn test_trace_switch_finishes_admitted_spans() {
1066 #[derive(Debug)]
1067 struct Exporter(std::sync::mpsc::Sender<String>);
1068
1069 impl opentelemetry_sdk::trace::SpanExporter for Exporter {
1070 async fn export(
1071 &self,
1072 batch: Vec<opentelemetry_sdk::trace::SpanData>,
1073 ) -> opentelemetry_sdk::error::OTelSdkResult {
1074 for span in batch {
1075 self.0.send(span.name.into_owned()).unwrap();
1076 }
1077 Ok(())
1078 }
1079 }
1080
1081 let (sender, receiver) = std::sync::mpsc::channel();
1082 let provider = SdkTracerProvider::builder()
1083 .with_sampler(Sampler::AlwaysOn)
1084 .with_simple_exporter(Exporter(sender))
1085 .build();
1086 let (filter, _) = tracing_subscriber::reload::Layer::new(
1087 Targets::new().with_default(tracing::Level::INFO),
1088 );
1089 let (layer, handle) = TraceLayer::new(Some(
1090 tracing_opentelemetry::layer().with_tracer(provider.tracer("export")),
1091 ));
1092 tracing::subscriber::with_default(Registry::default().with(filter).with(layer), || {
1093 let active = tracing::info_span!("admitted");
1094 handle.set_enabled(false).unwrap();
1095 let disabled = tracing::info_span!("not_admitted");
1096 drop(active);
1097 drop(disabled);
1098 });
1099 provider.force_flush().unwrap();
1100 assert_eq!(receiver.try_iter().collect::<Vec<_>>(), ["admitted"]);
1101 }
1102
1103 #[test]
1104 fn test_trace_switch_concurrent_context_access() {
1105 let provider = SdkTracerProvider::builder()
1106 .with_sampler(Sampler::AlwaysOn)
1107 .build();
1108 let (filter, _) = tracing_subscriber::reload::Layer::new(
1109 Targets::new().with_default(tracing::Level::INFO),
1110 );
1111 let (layer, handle) = TraceLayer::new(Some(
1112 tracing_opentelemetry::layer().with_tracer(provider.tracer("concurrent")),
1113 ));
1114 let dispatch = tracing::Dispatch::new(Registry::default().with(filter).with(layer));
1115 let parent = tracing::dispatcher::with_default(&dispatch, || {
1116 tracing::info_span!("concurrent_parent")
1117 });
1118 let context = parent.context();
1119 let barrier = Barrier::new(4);
1120 std::thread::scope(|scope| {
1121 for _ in 0..3 {
1122 scope.spawn(|| {
1123 tracing::dispatcher::with_default(&dispatch, || {
1124 let _entered = parent.enter();
1125 barrier.wait();
1126 for _ in 0..500 {
1127 assert_eq!(
1128 parent.context().span().span_context(),
1129 context.span().span_context()
1130 );
1131 {
1132 let child = tracing::info_span!("concurrent_child");
1133 let _entered = child.enter();
1134 let _ = child.context();
1135 tracing::info!("concurrent event");
1136 }
1137 assert_eq!(
1138 opentelemetry::Context::current().span().span_context(),
1139 context.span().span_context()
1140 );
1141 }
1142 });
1143 });
1144 }
1145 barrier.wait();
1146 for i in 0..1000 {
1147 handle
1148 .set_enabled_with(i % 2 == 0, || panic!("layer must not be replaced"))
1149 .unwrap();
1150 }
1151 });
1152 }
1153
1154 #[test]
1155 fn test_logging_options_deserialization_default() {
1156 let json = r#"{}"#;
1157 let opts: LoggingOptions = serde_json::from_str(json).unwrap();
1158
1159 assert_eq!(opts.log_format, LogFormat::Text);
1160 assert_eq!(opts.dir, "");
1161 assert_eq!(opts.level, None);
1162 assert!(opts.append_stdout);
1163 assert!(opts.enable_file_logging);
1164 }
1165
1166 #[test]
1167 fn test_logging_options_deserialization_enable_file_logging() {
1168 let json = r#"{"enable_file_logging": false}"#;
1169 let opts: LoggingOptions = serde_json::from_str(json).unwrap();
1170
1171 assert!(!opts.enable_file_logging);
1172 }
1173
1174 #[test]
1175 fn test_logging_options_deserialization_max_log_dir_size() {
1176 let json = r#"{"max_log_dir_size": "1MiB"}"#;
1177 let opts: LoggingOptions = serde_json::from_str(json).unwrap();
1178
1179 assert_eq!(opts.max_log_dir_size, ReadableSize::mb(1));
1180 }
1181
1182 #[test]
1183 fn test_logging_options_deserialization_empty_log_format() {
1184 let json = r#"{"log_format": ""}"#;
1185 let opts: LoggingOptions = serde_json::from_str(json).unwrap();
1186
1187 assert_eq!(opts.log_format, LogFormat::Text);
1189 }
1190
1191 #[test]
1192 fn test_logging_options_deserialization_valid_log_format() {
1193 let json_format = r#"{"log_format": "json"}"#;
1194 let opts: LoggingOptions = serde_json::from_str(json_format).unwrap();
1195 assert_eq!(opts.log_format, LogFormat::Json);
1196
1197 let text_format = r#"{"log_format": "text"}"#;
1198 let opts: LoggingOptions = serde_json::from_str(text_format).unwrap();
1199 assert_eq!(opts.log_format, LogFormat::Text);
1200 }
1201
1202 #[test]
1203 fn test_logging_options_deserialization_missing_log_format() {
1204 let json = r#"{"dir": "/tmp/logs"}"#;
1205 let opts: LoggingOptions = serde_json::from_str(json).unwrap();
1206
1207 assert_eq!(opts.log_format, LogFormat::Text);
1209 assert_eq!(opts.dir, "/tmp/logs");
1210 }
1211
1212 #[test]
1213 fn test_slow_query_options_deserialization_default() {
1214 let json = r#"{"enable": true, "threshold": "30s"}"#;
1215 let opts: SlowQueryOptions = serde_json::from_str(json).unwrap();
1216
1217 assert_eq!(opts.record_type, SlowQueriesRecordType::SystemTable);
1218 assert!(opts.enable);
1219 }
1220
1221 #[test]
1222 fn test_slow_query_options_deserialization_empty_record_type() {
1223 let json = r#"{"enable": true, "record_type": "", "threshold": "30s"}"#;
1224 let opts: SlowQueryOptions = serde_json::from_str(json).unwrap();
1225
1226 assert_eq!(opts.record_type, SlowQueriesRecordType::SystemTable);
1228 assert!(opts.enable);
1229 }
1230
1231 #[test]
1232 fn test_slow_query_options_deserialization_valid_record_type() {
1233 let system_table_json =
1234 r#"{"enable": true, "record_type": "system_table", "threshold": "30s"}"#;
1235 let opts: SlowQueryOptions = serde_json::from_str(system_table_json).unwrap();
1236 assert_eq!(opts.record_type, SlowQueriesRecordType::SystemTable);
1237
1238 let log_json = r#"{"enable": true, "record_type": "log", "threshold": "30s"}"#;
1239 let opts: SlowQueryOptions = serde_json::from_str(log_json).unwrap();
1240 assert_eq!(opts.record_type, SlowQueriesRecordType::Log);
1241 }
1242
1243 #[test]
1244 fn test_slow_query_options_deserialization_missing_record_type() {
1245 let json = r#"{"enable": false, "threshold": "30s"}"#;
1246 let opts: SlowQueryOptions = serde_json::from_str(json).unwrap();
1247
1248 assert_eq!(opts.record_type, SlowQueriesRecordType::SystemTable);
1250 assert!(!opts.enable);
1251 }
1252
1253 #[test]
1254 fn test_otlp_export_protocol_deserialization_valid_values() {
1255 let grpc_json = r#""grpc""#;
1256 let protocol: OtlpExportProtocol = serde_json::from_str(grpc_json).unwrap();
1257 assert_eq!(protocol, OtlpExportProtocol::Grpc);
1258
1259 let http_json = r#""http""#;
1260 let protocol: OtlpExportProtocol = serde_json::from_str(http_json).unwrap();
1261 assert_eq!(protocol, OtlpExportProtocol::Http);
1262 }
1263
1264 #[test]
1265 fn test_logging_options_partial_eq_all_fields() {
1266 let base = LoggingOptions::default();
1267
1268 let mut log_format = base.clone();
1269 log_format.log_format = LogFormat::Json;
1270 assert_ne!(base, log_format);
1271
1272 let mut max_log_files = base.clone();
1273 max_log_files.max_log_files += 1;
1274 assert_ne!(base, max_log_files);
1275
1276 let mut max_log_dir_size = base.clone();
1277 max_log_dir_size.max_log_dir_size = ReadableSize::mb(1);
1278 assert_ne!(base, max_log_dir_size);
1279
1280 let mut otlp_export_protocol = base.clone();
1281 otlp_export_protocol.otlp_export_protocol = Some(OtlpExportProtocol::Http);
1282 assert_ne!(base, otlp_export_protocol);
1283
1284 let mut otlp_headers = base.clone();
1285 otlp_headers
1286 .otlp_headers
1287 .insert("key".to_string(), "value".to_string());
1288 assert_ne!(base, otlp_headers);
1289 }
1290}