|
36 | 36 | level_to_level_number(*attrs.metadata().level()),
|
37 | 37 | ));
|
38 | 38 | attributes.push(KeyValue::new("logfire.span_type", "span"));
|
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + /// Emit a pending span when this span is first entered, if this span will be sampled. |
| 43 | + /// |
| 44 | + /// We do this on first enter, not on creation, because some SDKs set the parent span after |
| 45 | + /// creation. |
| 46 | + /// |
| 47 | + /// e.g. <https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/blob/5830c9113b0d42b72167567bf8e5f4c6b20933c8/axum-tracing-opentelemetry/src/middleware/trace_extractor.rs#L132> |
| 48 | + fn on_enter(&self, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { |
| 49 | + let span = ctx.span(id).expect("span not found"); |
| 50 | + let mut extensions = span.extensions_mut(); |
| 51 | + |
| 52 | + if extensions.get_mut::<LogfirePendingSpanSent>().is_some() { |
| 53 | + return; |
| 54 | + } |
39 | 55 |
|
| 56 | + extensions.insert(LogfirePendingSpanSent); |
| 57 | + |
| 58 | + // Guaranteed to be on first entering of the span |
| 59 | + if let Some(otel_data) = extensions.get_mut::<OtelData>() { |
40 | 60 | // Emit a pending span, if this span will be sampled.
|
41 | 61 | let context = self.0.sampled_context(otel_data);
|
42 | 62 | let sampling_result = otel_data
|
@@ -121,6 +141,39 @@ where
|
121 | 141 | }
|
122 | 142 | }
|
123 | 143 |
|
| 144 | +/// Helper to print spans when dropped; if it was never entered then the pending span |
| 145 | +/// is never sent (the console writer uses pending spans). |
| 146 | +/// |
| 147 | +/// This needs to be a separate layer so that it can access the `OtelData` before the |
| 148 | +/// `tracing_opentelemetry` layer removes it. |
| 149 | +pub struct LogfireTracingPendingSpanNotSentLayer; |
| 150 | + |
| 151 | +impl<S> Layer<S> for LogfireTracingPendingSpanNotSentLayer |
| 152 | +where |
| 153 | + S: Subscriber + for<'span> LookupSpan<'span>, |
| 154 | +{ |
| 155 | + fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { |
| 156 | + let span = ctx.span(&id).expect("span not found"); |
| 157 | + let mut extensions = span.extensions_mut(); |
| 158 | + |
| 159 | + if extensions.get_mut::<LogfirePendingSpanSent>().is_some() { |
| 160 | + return; |
| 161 | + } |
| 162 | + |
| 163 | + // Guaranteed to be on first entering of the span |
| 164 | + if let Some(otel_data) = extensions.get_mut::<OtelData>() { |
| 165 | + try_with_logfire_tracer(|tracer| { |
| 166 | + if let Some(writer) = &tracer.console_writer { |
| 167 | + writer.write_tracing_opentelemetry_data(otel_data); |
| 168 | + } |
| 169 | + }); |
| 170 | + } |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +/// Dummy struct to mark that we've already entered this span. |
| 175 | +struct LogfirePendingSpanSent; |
| 176 | + |
124 | 177 | pub(crate) fn level_to_level_number(level: tracing::Level) -> i64 {
|
125 | 178 | // These numbers were chosen to match the values emitted by the Python logfire SDK.
|
126 | 179 | match level {
|
@@ -223,18 +276,18 @@ impl Visit for FieldsVisitor {
|
223 | 276 |
|
224 | 277 | #[cfg(test)]
|
225 | 278 | mod tests {
|
226 |
| - use core::time; |
227 | 279 | use std::sync::{Arc, Mutex};
|
228 | 280 |
|
229 | 281 | use insta::{assert_debug_snapshot, assert_snapshot};
|
230 | 282 | use opentelemetry_sdk::trace::{InMemorySpanExporterBuilder, SimpleSpanProcessor};
|
231 |
| - use regex::{Captures, Regex}; |
232 | 283 | use tracing::{Level, level_filters::LevelFilter};
|
233 | 284 |
|
234 | 285 | use crate::{
|
235 | 286 | config::{AdvancedOptions, ConsoleOptions, Target},
|
236 | 287 | set_local_logfire,
|
237 |
| - test_utils::{DeterministicExporter, DeterministicIdGenerator}, |
| 288 | + test_utils::{ |
| 289 | + DeterministicExporter, DeterministicIdGenerator, remap_timestamps_in_console_output, |
| 290 | + }, |
238 | 291 | };
|
239 | 292 |
|
240 | 293 | #[test]
|
@@ -287,7 +340,7 @@ mod tests {
|
287 | 340 | },
|
288 | 341 | parent_span_id: 0000000000000000,
|
289 | 342 | span_kind: Internal,
|
290 |
| - name: "event src/bridges/tracing.rs:261", |
| 343 | + name: "event src/bridges/tracing.rs:314", |
291 | 344 | start_time: SystemTime {
|
292 | 345 | tv_sec: 0,
|
293 | 346 | tv_nsec: 0,
|
@@ -823,122 +876,6 @@ mod tests {
|
823 | 876 | attributes: [],
|
824 | 877 | },
|
825 | 878 | },
|
826 |
| - SpanData { |
827 |
| - span_context: SpanContext { |
828 |
| - trace_id: 000000000000000000000000000000f2, |
829 |
| - span_id: 00000000000000f7, |
830 |
| - trace_flags: TraceFlags( |
831 |
| - 1, |
832 |
| - ), |
833 |
| - is_remote: false, |
834 |
| - trace_state: TraceState( |
835 |
| - None, |
836 |
| - ), |
837 |
| - }, |
838 |
| - parent_span_id: 00000000000000f6, |
839 |
| - span_kind: Internal, |
840 |
| - name: "debug span", |
841 |
| - start_time: SystemTime { |
842 |
| - tv_sec: 5, |
843 |
| - tv_nsec: 0, |
844 |
| - }, |
845 |
| - end_time: SystemTime { |
846 |
| - tv_sec: 5, |
847 |
| - tv_nsec: 0, |
848 |
| - }, |
849 |
| - attributes: [ |
850 |
| - KeyValue { |
851 |
| - key: Static( |
852 |
| - "code.filepath", |
853 |
| - ), |
854 |
| - value: String( |
855 |
| - Static( |
856 |
| - "src/bridges/tracing.rs", |
857 |
| - ), |
858 |
| - ), |
859 |
| - }, |
860 |
| - KeyValue { |
861 |
| - key: Static( |
862 |
| - "code.namespace", |
863 |
| - ), |
864 |
| - value: String( |
865 |
| - Static( |
866 |
| - "logfire::bridges::tracing::tests", |
867 |
| - ), |
868 |
| - ), |
869 |
| - }, |
870 |
| - KeyValue { |
871 |
| - key: Static( |
872 |
| - "code.lineno", |
873 |
| - ), |
874 |
| - value: I64( |
875 |
| - 18, |
876 |
| - ), |
877 |
| - }, |
878 |
| - KeyValue { |
879 |
| - key: Static( |
880 |
| - "thread.id", |
881 |
| - ), |
882 |
| - value: I64( |
883 |
| - 0, |
884 |
| - ), |
885 |
| - }, |
886 |
| - KeyValue { |
887 |
| - key: Static( |
888 |
| - "thread.name", |
889 |
| - ), |
890 |
| - value: String( |
891 |
| - Owned( |
892 |
| - "bridges::tracing::tests::test_tracing_bridge", |
893 |
| - ), |
894 |
| - ), |
895 |
| - }, |
896 |
| - KeyValue { |
897 |
| - key: Static( |
898 |
| - "logfire.level_num", |
899 |
| - ), |
900 |
| - value: I64( |
901 |
| - 5, |
902 |
| - ), |
903 |
| - }, |
904 |
| - KeyValue { |
905 |
| - key: Static( |
906 |
| - "logfire.span_type", |
907 |
| - ), |
908 |
| - value: String( |
909 |
| - Static( |
910 |
| - "pending_span", |
911 |
| - ), |
912 |
| - ), |
913 |
| - }, |
914 |
| - KeyValue { |
915 |
| - key: Static( |
916 |
| - "logfire.pending_parent_id", |
917 |
| - ), |
918 |
| - value: String( |
919 |
| - Owned( |
920 |
| - "00000000000000f2", |
921 |
| - ), |
922 |
| - ), |
923 |
| - }, |
924 |
| - ], |
925 |
| - dropped_attributes_count: 0, |
926 |
| - events: SpanEvents { |
927 |
| - events: [], |
928 |
| - dropped_count: 0, |
929 |
| - }, |
930 |
| - links: SpanLinks { |
931 |
| - links: [], |
932 |
| - dropped_count: 0, |
933 |
| - }, |
934 |
| - status: Unset, |
935 |
| - instrumentation_scope: InstrumentationScope { |
936 |
| - name: "logfire", |
937 |
| - version: None, |
938 |
| - schema_url: None, |
939 |
| - attributes: [], |
940 |
| - }, |
941 |
| - }, |
942 | 879 | SpanData {
|
943 | 880 | span_context: SpanContext {
|
944 | 881 | trace_id: 000000000000000000000000000000f2,
|
@@ -1064,123 +1001,7 @@ mod tests {
|
1064 | 1001 | SpanData {
|
1065 | 1002 | span_context: SpanContext {
|
1066 | 1003 | trace_id: 000000000000000000000000000000f2,
|
1067 |
| - span_id: 00000000000000f9, |
1068 |
| - trace_flags: TraceFlags( |
1069 |
| - 1, |
1070 |
| - ), |
1071 |
| - is_remote: false, |
1072 |
| - trace_state: TraceState( |
1073 |
| - None, |
1074 |
| - ), |
1075 |
| - }, |
1076 |
| - parent_span_id: 00000000000000f8, |
1077 |
| - span_kind: Internal, |
1078 |
| - name: "debug span with explicit parent", |
1079 |
| - start_time: SystemTime { |
1080 |
| - tv_sec: 7, |
1081 |
| - tv_nsec: 0, |
1082 |
| - }, |
1083 |
| - end_time: SystemTime { |
1084 |
| - tv_sec: 7, |
1085 |
| - tv_nsec: 0, |
1086 |
| - }, |
1087 |
| - attributes: [ |
1088 |
| - KeyValue { |
1089 |
| - key: Static( |
1090 |
| - "code.filepath", |
1091 |
| - ), |
1092 |
| - value: String( |
1093 |
| - Static( |
1094 |
| - "src/bridges/tracing.rs", |
1095 |
| - ), |
1096 |
| - ), |
1097 |
| - }, |
1098 |
| - KeyValue { |
1099 |
| - key: Static( |
1100 |
| - "code.namespace", |
1101 |
| - ), |
1102 |
| - value: String( |
1103 |
| - Static( |
1104 |
| - "logfire::bridges::tracing::tests", |
1105 |
| - ), |
1106 |
| - ), |
1107 |
| - }, |
1108 |
| - KeyValue { |
1109 |
| - key: Static( |
1110 |
| - "code.lineno", |
1111 |
| - ), |
1112 |
| - value: I64( |
1113 |
| - 19, |
1114 |
| - ), |
1115 |
| - }, |
1116 |
| - KeyValue { |
1117 |
| - key: Static( |
1118 |
| - "thread.id", |
1119 |
| - ), |
1120 |
| - value: I64( |
1121 |
| - 0, |
1122 |
| - ), |
1123 |
| - }, |
1124 |
| - KeyValue { |
1125 |
| - key: Static( |
1126 |
| - "thread.name", |
1127 |
| - ), |
1128 |
| - value: String( |
1129 |
| - Owned( |
1130 |
| - "bridges::tracing::tests::test_tracing_bridge", |
1131 |
| - ), |
1132 |
| - ), |
1133 |
| - }, |
1134 |
| - KeyValue { |
1135 |
| - key: Static( |
1136 |
| - "logfire.level_num", |
1137 |
| - ), |
1138 |
| - value: I64( |
1139 |
| - 5, |
1140 |
| - ), |
1141 |
| - }, |
1142 |
| - KeyValue { |
1143 |
| - key: Static( |
1144 |
| - "logfire.span_type", |
1145 |
| - ), |
1146 |
| - value: String( |
1147 |
| - Static( |
1148 |
| - "pending_span", |
1149 |
| - ), |
1150 |
| - ), |
1151 |
| - }, |
1152 |
| - KeyValue { |
1153 |
| - key: Static( |
1154 |
| - "logfire.pending_parent_id", |
1155 |
| - ), |
1156 |
| - value: String( |
1157 |
| - Owned( |
1158 |
| - "00000000000000f2", |
1159 |
| - ), |
1160 |
| - ), |
1161 |
| - }, |
1162 |
| - ], |
1163 |
| - dropped_attributes_count: 0, |
1164 |
| - events: SpanEvents { |
1165 |
| - events: [], |
1166 |
| - dropped_count: 0, |
1167 |
| - }, |
1168 |
| - links: SpanLinks { |
1169 |
| - links: [], |
1170 |
| - dropped_count: 0, |
1171 |
| - }, |
1172 |
| - status: Unset, |
1173 |
| - instrumentation_scope: InstrumentationScope { |
1174 |
| - name: "logfire", |
1175 |
| - version: None, |
1176 |
| - schema_url: None, |
1177 |
| - attributes: [], |
1178 |
| - }, |
1179 |
| - }, |
1180 |
| - SpanData { |
1181 |
| - span_context: SpanContext { |
1182 |
| - trace_id: 000000000000000000000000000000f2, |
1183 |
| - span_id: 00000000000000f8, |
| 1004 | + span_id: 00000000000000f7, |
1184 | 1005 | trace_flags: TraceFlags(
|
1185 | 1006 | 1,
|
1186 | 1007 | ),
|
@@ -1459,7 +1280,7 @@ mod tests {
|
1459 | 1280 | "code.lineno",
|
1460 | 1281 | ),
|
1461 | 1282 | value: I64(
|
1462 |
| - 269, |
| 1283 | + 322, |
1463 | 1284 | ),
|
1464 | 1285 | },
|
1465 | 1286 | ],
|
@@ -1525,7 +1346,7 @@ mod tests {
|
1525 | 1346 | "code.lineno",
|
1526 | 1347 | ),
|
1527 | 1348 | value: I64(
|
1528 |
| - 270, |
| 1349 | + 323, |
1529 | 1350 | ),
|
1530 | 1351 | },
|
1531 | 1352 | ],
|
@@ -1587,15 +1408,7 @@ mod tests {
|
1587 | 1408 |
|
1588 | 1409 | let output = output.lock().unwrap();
|
1589 | 1410 | let output = std::str::from_utf8(&output).unwrap();
|
1590 |
| - |
1591 |
| - // Replace all timestamps in output to make them deterministic |
1592 |
| - let mut timestamp = chrono::DateTime::UNIX_EPOCH; |
1593 |
| - let re = Regex::new(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z").unwrap(); |
1594 |
| - let output = re.replace_all(output, |_: &Captures<'_>| { |
1595 |
| - let replaced = timestamp.to_rfc3339_opts(chrono::SecondsFormat::Micros, true); |
1596 |
| - timestamp += time::Duration::from_micros(1); |
1597 |
| - replaced |
1598 |
| - }); |
| 1411 | + let output = remap_timestamps_in_console_output(output); |
1599 | 1412 |
|
1600 | 1413 | assert_snapshot!(output, @r#"
|
1601 | 1414 | [2m1970-01-01T00:00:00.000000Z[0m[32m INFO[0m [2;3mlogfire::bridges::tracing::tests[0m [1mroot event[0m
|
|
0 commit comments