1use ahash::HashMap;
33use rama_core::telemetry::tracing::{
34 Event, Level, Metadata, Subscriber,
35 field::{Field, Visit},
36 span::{Attributes, Id, Record},
37};
38use rama_utils::octets::kib;
39use std::{
40 ffi::{CString, NulError, c_char, c_void},
41 fmt::{self, Write as _},
42 ptr::NonNull,
43 sync::{
44 Arc,
45 atomic::{AtomicU64, Ordering},
46 },
47};
48use tracing_subscriber::{
49 layer::{Context, Layer},
50 registry::LookupSpan,
51};
52
53const DEFAULT_MAX_MESSAGE_BYTES: usize = kib(1);
54const MIN_MESSAGE_BYTES: usize = 3;
55const OS_SIGNPOST_ID_NULL: u64 = 0;
56const OS_SIGNPOST_ID_INVALID: u64 = u64::MAX;
57
58static NEXT_LAYER_ID: AtomicU64 = AtomicU64::new(1);
59
60#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
66#[repr(u8)]
67pub enum Privacy {
68 #[default]
70 Private = 0,
71 Public = 1,
73 PublicMessagePrivateFields = 2,
79}
80
81#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
83pub enum SpanMode {
84 Disabled,
89 #[default]
98 Signposts,
99}
100
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103#[repr(u8)]
104pub enum OsLogType {
105 Default = 0x00,
107 Info = 0x01,
109 Debug = 0x02,
111 Error = 0x10,
113 Fault = 0x11,
115}
116
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub struct LevelMap {
120 trace: OsLogType,
121 debug: OsLogType,
122 info: OsLogType,
123 warn: OsLogType,
124 error: OsLogType,
125}
126
127impl LevelMap {
128 pub const fn new(
130 trace: OsLogType,
131 debug: OsLogType,
132 info: OsLogType,
133 warn: OsLogType,
134 error: OsLogType,
135 ) -> Self {
136 Self {
137 trace,
138 debug,
139 info,
140 warn,
141 error,
142 }
143 }
144
145 pub const fn apple() -> Self {
150 Self::new(
151 OsLogType::Debug,
152 OsLogType::Debug,
153 OsLogType::Info,
154 OsLogType::Error,
155 OsLogType::Error,
156 )
157 }
158
159 pub const fn persistent_info() -> Self {
164 Self::new(
165 OsLogType::Debug,
166 OsLogType::Info,
167 OsLogType::Default,
168 OsLogType::Error,
169 OsLogType::Error,
170 )
171 }
172
173 pub const fn tracing_oslog_compatible() -> Self {
178 Self::new(
179 OsLogType::Debug,
180 OsLogType::Info,
181 OsLogType::Default,
182 OsLogType::Error,
183 OsLogType::Fault,
184 )
185 }
186
187 const fn get(self, level: Level) -> OsLogType {
188 match level {
189 Level::TRACE => self.trace,
190 Level::DEBUG => self.debug,
191 Level::INFO => self.info,
192 Level::WARN => self.warn,
193 Level::ERROR => self.error,
194 }
195 }
196}
197
198impl Default for LevelMap {
199 fn default() -> Self {
200 Self::apple()
201 }
202}
203
204#[derive(Debug)]
206pub enum OsLogError {
207 InvalidSubsystem(NulError),
209 InvalidCategory(NulError),
211 CreateFailed,
213}
214
215impl fmt::Display for OsLogError {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 match self {
218 Self::InvalidSubsystem(_) => f.write_str("os_log subsystem contains a NUL byte"),
219 Self::InvalidCategory(_) => f.write_str("os_log category contains a NUL byte"),
220 Self::CreateFailed => f.write_str("Apple os_log_create returned a null handle"),
221 }
222 }
223}
224
225impl std::error::Error for OsLogError {
226 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
227 match self {
228 Self::InvalidSubsystem(err) | Self::InvalidCategory(err) => Some(err),
229 Self::CreateFailed => None,
230 }
231 }
232}
233
234pub struct OsLogLayer {
237 log: Arc<LogHandle>,
238 layer_id: u64,
239 privacy: Privacy,
240 span_mode: SpanMode,
241 level_map: LevelMap,
242 include_target: bool,
243 include_span_context: bool,
244 max_message_bytes: usize,
245}
246
247impl OsLogLayer {
248 pub fn new(subsystem: impl AsRef<str>, category: impl AsRef<str>) -> Result<Self, OsLogError> {
254 let subsystem = CString::new(subsystem.as_ref()).map_err(OsLogError::InvalidSubsystem)?;
255 let category = CString::new(category.as_ref()).map_err(OsLogError::InvalidCategory)?;
256
257 let raw = unsafe { ffi::rama_apple_oslog_create(subsystem.as_ptr(), category.as_ptr()) };
261 let raw = NonNull::new(raw).ok_or(OsLogError::CreateFailed)?;
262
263 Ok(Self {
264 log: Arc::new(LogHandle(raw)),
265 layer_id: next_layer_id(),
266 privacy: Privacy::default(),
267 span_mode: SpanMode::default(),
268 level_map: LevelMap::default(),
269 include_target: true,
270 include_span_context: false,
271 max_message_bytes: DEFAULT_MAX_MESSAGE_BYTES,
272 })
273 }
274
275 rama_utils::macros::generate_set_and_with! {
276 pub fn privacy(mut self, privacy: Privacy) -> Self {
278 self.privacy = privacy;
279 self
280 }
281 }
282
283 rama_utils::macros::generate_set_and_with! {
284 pub fn span_mode(mut self, span_mode: SpanMode) -> Self {
288 self.span_mode = span_mode;
289 self
290 }
291 }
292
293 rama_utils::macros::generate_set_and_with! {
294 pub fn level_map(mut self, level_map: LevelMap) -> Self {
296 self.level_map = level_map;
297 self
298 }
299 }
300
301 rama_utils::macros::generate_set_and_with! {
302 pub fn target(mut self, include_target: bool) -> Self {
304 self.include_target = include_target;
305 self
306 }
307 }
308
309 rama_utils::macros::generate_set_and_with! {
310 pub fn span_context(mut self, include_span_context: bool) -> Self {
312 self.include_span_context = include_span_context;
313 self
314 }
315 }
316
317 rama_utils::macros::generate_set_and_with! {
318 pub fn max_message_bytes(mut self, max_message_bytes: usize) -> Self {
324 self.max_message_bytes = if max_message_bytes < MIN_MESSAGE_BYTES {
325 MIN_MESSAGE_BYTES
326 } else {
327 max_message_bytes
328 };
329 self
330 }
331 }
332
333 fn format_span(&self, state: &SpanState) -> Vec<u8> {
334 let mut output = BoundedString::new(self.max_message_bytes);
335 if self.include_target {
336 _ = write!(output, "[{}] ", state.metadata.target());
337 }
338 output.push_str(state.metadata.name());
339 if !state.fields.is_empty() {
340 output.push_str(" ");
341 output.push_bounded(&state.fields);
342 }
343 output.into_c_message()
344 }
345
346 fn format_span_split(&self, state: &SpanState) -> (Vec<u8>, Vec<u8>) {
347 let mut public = BoundedString::new(self.max_message_bytes);
348 if self.include_target {
349 _ = write!(public, "[{}] ", state.metadata.target());
350 }
351 public.push_str(state.metadata.name());
352
353 let mut private = BoundedString::new(self.max_message_bytes);
354 private.push_bounded(&state.fields);
355 (public.into_c_message(), private.into_c_message())
356 }
357
358 fn format_event<S>(&self, event: &Event<'_>, ctx: &Context<'_, S>) -> Vec<u8>
359 where
360 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
361 {
362 let mut message = BoundedString::new(self.max_message_bytes);
363 let mut fields = BoundedString::new(self.max_message_bytes);
364 event.record(&mut FieldVisitor::event(&mut message, &mut fields));
365
366 let mut output = BoundedString::new(self.max_message_bytes);
367 if self.include_target {
368 _ = write!(output, "[{}] ", event.metadata().target());
369 }
370
371 if !message.is_empty() {
372 output.push_bounded(&message);
373 }
374 if !fields.is_empty() {
375 if !message.is_empty() {
376 output.push_str(" ");
377 }
378 output.push_bounded(&fields);
379 }
380
381 if self.include_span_context {
382 let mut wrote_span = false;
383 if let Some(scope) = ctx.event_scope(event) {
384 for span in scope.from_root() {
385 let extensions = span.extensions();
386 let Some(states) = extensions.get::<SpanStates>() else {
387 continue;
388 };
389 let Some(state) = states.0.get(&self.layer_id) else {
390 continue;
391 };
392
393 if !wrote_span {
394 if !output.is_empty() {
395 output.push_str(" ");
396 }
397 output.push_str("spans=[");
398 wrote_span = true;
399 } else {
400 output.push_str(" > ");
401 }
402
403 output.push_str(state.metadata.name());
404 if !state.fields.is_empty() {
405 output.push_str("{");
406 output.push_bounded(&state.fields);
407 output.push_str("}");
408 }
409 }
410 }
411 if wrote_span {
412 output.push_str("]");
413 }
414 }
415
416 output.into_c_message()
417 }
418
419 fn format_event_split<S>(&self, event: &Event<'_>, ctx: &Context<'_, S>) -> (Vec<u8>, Vec<u8>)
420 where
421 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
422 {
423 let mut message = BoundedString::new(self.max_message_bytes);
424 let mut fields = BoundedString::new(self.max_message_bytes);
425 event.record(&mut FieldVisitor::event(&mut message, &mut fields));
426
427 let mut public = BoundedString::new(self.max_message_bytes);
428 if self.include_target {
429 _ = write!(public, "[{}] ", event.metadata().target());
430 }
431 public.push_bounded(&message);
432
433 let mut private = BoundedString::new(self.max_message_bytes);
434 private.push_bounded(&fields);
435 if self.include_span_context {
436 self.append_span_context(event, ctx, &mut private);
437 }
438
439 (public.into_c_message(), private.into_c_message())
440 }
441
442 fn append_span_context<S>(
443 &self,
444 event: &Event<'_>,
445 ctx: &Context<'_, S>,
446 output: &mut BoundedString,
447 ) where
448 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
449 {
450 let mut wrote_span = false;
451 if let Some(scope) = ctx.event_scope(event) {
452 for span in scope.from_root() {
453 let extensions = span.extensions();
454 let Some(states) = extensions.get::<SpanStates>() else {
455 continue;
456 };
457 let Some(state) = states.0.get(&self.layer_id) else {
458 continue;
459 };
460
461 if !wrote_span {
462 if !output.is_empty() {
463 output.push_str(" ");
464 }
465 output.push_str("spans=[");
466 wrote_span = true;
467 } else {
468 output.push_str(" > ");
469 }
470
471 output.push_str(state.metadata.name());
472 if !state.fields.is_empty() {
473 output.push_str("{");
474 output.push_bounded(&state.fields);
475 output.push_str("}");
476 }
477 }
478 }
479 if wrote_span {
480 output.push_str("]");
481 }
482 }
483}
484
485impl Clone for OsLogLayer {
486 fn clone(&self) -> Self {
487 Self {
488 log: Arc::clone(&self.log),
489 layer_id: next_layer_id(),
490 privacy: self.privacy,
491 span_mode: self.span_mode,
492 level_map: self.level_map,
493 include_target: self.include_target,
494 include_span_context: self.include_span_context,
495 max_message_bytes: self.max_message_bytes,
496 }
497 }
498}
499
500impl fmt::Debug for OsLogLayer {
501 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 f.debug_struct("OsLogLayer")
503 .field("layer_id", &self.layer_id)
504 .field("privacy", &self.privacy)
505 .field("span_mode", &self.span_mode)
506 .field("level_map", &self.level_map)
507 .field("include_target", &self.include_target)
508 .field("include_span_context", &self.include_span_context)
509 .field("max_message_bytes", &self.max_message_bytes)
510 .finish_non_exhaustive()
511 }
512}
513
514impl<S> Layer<S> for OsLogLayer
515where
516 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
517{
518 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
519 let signpost_enabled = self.span_mode == SpanMode::Signposts && self.log.signpost_enabled();
520 if !self.include_span_context && !signpost_enabled {
521 return;
522 }
523
524 let Some(span) = ctx.span(id) else {
525 return;
526 };
527
528 let mut fields = BoundedString::new(self.max_message_bytes);
529 attrs.record(&mut FieldVisitor::fields(&mut fields));
530
531 let signpost_id = if signpost_enabled {
532 let signpost_id = self.log.signpost_id_generate();
533 if is_valid_signpost_id(signpost_id) {
534 Some(signpost_id)
535 } else {
536 None
537 }
538 } else {
539 None
540 };
541
542 let state = SpanState {
543 metadata: attrs.metadata(),
544 fields,
545 signpost_id,
546 };
547
548 if let Some(signpost_id) = signpost_id {
549 if self.privacy == Privacy::PublicMessagePrivateFields {
550 let (public, private) = self.format_span_split(&state);
551 self.log
552 .signpost_begin_split(signpost_id, &public, &private);
553 } else {
554 let message = self.format_span(&state);
555 self.log.signpost_begin(signpost_id, &message, self.privacy);
556 }
557 }
558
559 let mut extensions = span.extensions_mut();
560 if let Some(states) = extensions.get_mut::<SpanStates>() {
561 states.0.insert(self.layer_id, state);
562 } else {
563 let mut states = HashMap::default();
564 states.insert(self.layer_id, state);
565 extensions.insert(SpanStates(states));
566 }
567 }
568
569 fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
570 let Some(span) = ctx.span(id) else {
571 return;
572 };
573 let mut extensions = span.extensions_mut();
574 let Some(states) = extensions.get_mut::<SpanStates>() else {
575 return;
576 };
577 let Some(state) = states.0.get_mut(&self.layer_id) else {
578 return;
579 };
580
581 values.record(&mut FieldVisitor::fields(&mut state.fields));
582 }
583
584 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
585 let os_type = self.level_map.get(*event.metadata().level());
586 if !self.log.enabled(os_type) {
587 return;
588 }
589
590 if self.privacy == Privacy::PublicMessagePrivateFields {
591 let (public, private) = self.format_event_split(event, &ctx);
592 self.log.emit_split(os_type, &public, &private);
593 } else {
594 let message = self.format_event(event, &ctx);
595 self.log.emit(os_type, &message, self.privacy);
596 }
597 }
598
599 fn on_close(&self, id: Id, ctx: Context<'_, S>) {
600 let Some(span) = ctx.span(&id) else {
601 return;
602 };
603 let mut extensions = span.extensions_mut();
604 let Some(states) = extensions.get_mut::<SpanStates>() else {
605 return;
606 };
607 let Some(state) = states.0.remove(&self.layer_id) else {
608 return;
609 };
610
611 if let Some(signpost_id) = state.signpost_id {
612 if self.privacy == Privacy::PublicMessagePrivateFields {
613 let (public, private) = self.format_span_split(&state);
614 self.log.signpost_end_split(signpost_id, &public, &private);
615 } else {
616 let message = self.format_span(&state);
617 self.log.signpost_end(signpost_id, &message, self.privacy);
618 }
619 }
620 }
621}
622
623struct LogHandle(NonNull<c_void>);
624
625unsafe impl Send for LogHandle {}
628unsafe impl Sync for LogHandle {}
631
632impl LogHandle {
633 fn enabled(&self, os_type: OsLogType) -> bool {
634 unsafe { ffi::rama_apple_oslog_enabled(self.0.as_ptr(), os_type as u8) != 0 }
637 }
638
639 fn emit(&self, os_type: OsLogType, message: &[u8], privacy: Privacy) {
640 debug_assert_eq!(message.last(), Some(&0));
641 unsafe {
644 ffi::rama_apple_oslog_emit(
645 self.0.as_ptr(),
646 os_type as u8,
647 message.as_ptr().cast::<c_char>(),
648 privacy as u8,
649 );
650 }
651 }
652
653 fn emit_split(&self, os_type: OsLogType, public: &[u8], private: &[u8]) {
654 debug_assert_eq!(public.last(), Some(&0));
655 debug_assert_eq!(private.last(), Some(&0));
656 if private == [0] {
657 self.emit(os_type, public, Privacy::Public);
658 return;
659 }
660 unsafe {
661 ffi::rama_apple_oslog_emit_split(
662 self.0.as_ptr(),
663 os_type as u8,
664 public.as_ptr().cast::<c_char>(),
665 private.as_ptr().cast::<c_char>(),
666 );
667 }
668 }
669
670 fn signpost_enabled(&self) -> bool {
671 unsafe { ffi::rama_apple_oslog_signpost_enabled(self.0.as_ptr()) != 0 }
674 }
675
676 fn signpost_id_generate(&self) -> u64 {
677 unsafe { ffi::rama_apple_oslog_signpost_id_generate(self.0.as_ptr()) }
680 }
681
682 fn signpost_begin(&self, signpost_id: u64, message: &[u8], privacy: Privacy) {
683 debug_assert!(is_valid_signpost_id(signpost_id));
684 debug_assert_eq!(message.last(), Some(&0));
685 unsafe {
688 ffi::rama_apple_oslog_signpost_begin(
689 self.0.as_ptr(),
690 signpost_id,
691 message.as_ptr().cast::<c_char>(),
692 privacy as u8,
693 );
694 }
695 }
696
697 fn signpost_end(&self, signpost_id: u64, message: &[u8], privacy: Privacy) {
698 debug_assert!(is_valid_signpost_id(signpost_id));
699 debug_assert_eq!(message.last(), Some(&0));
700 unsafe {
703 ffi::rama_apple_oslog_signpost_end(
704 self.0.as_ptr(),
705 signpost_id,
706 message.as_ptr().cast::<c_char>(),
707 privacy as u8,
708 );
709 }
710 }
711
712 fn signpost_begin_split(&self, signpost_id: u64, public: &[u8], private: &[u8]) {
713 debug_assert!(is_valid_signpost_id(signpost_id));
714 debug_assert_eq!(public.last(), Some(&0));
715 debug_assert_eq!(private.last(), Some(&0));
716 if private == [0] {
717 self.signpost_begin(signpost_id, public, Privacy::Public);
718 return;
719 }
720 unsafe {
721 ffi::rama_apple_oslog_signpost_begin_split(
722 self.0.as_ptr(),
723 signpost_id,
724 public.as_ptr().cast::<c_char>(),
725 private.as_ptr().cast::<c_char>(),
726 );
727 }
728 }
729
730 fn signpost_end_split(&self, signpost_id: u64, public: &[u8], private: &[u8]) {
731 debug_assert!(is_valid_signpost_id(signpost_id));
732 debug_assert_eq!(public.last(), Some(&0));
733 debug_assert_eq!(private.last(), Some(&0));
734 if private == [0] {
735 self.signpost_end(signpost_id, public, Privacy::Public);
736 return;
737 }
738 unsafe {
739 ffi::rama_apple_oslog_signpost_end_split(
740 self.0.as_ptr(),
741 signpost_id,
742 public.as_ptr().cast::<c_char>(),
743 private.as_ptr().cast::<c_char>(),
744 );
745 }
746 }
747}
748
749impl Drop for LogHandle {
750 fn drop(&mut self) {
751 unsafe { ffi::rama_apple_oslog_release(self.0.as_ptr()) };
754 }
755}
756
757struct SpanStates(HashMap<u64, SpanState>);
758
759struct SpanState {
760 metadata: &'static Metadata<'static>,
761 fields: BoundedString,
762 signpost_id: Option<u64>,
763}
764
765struct FieldVisitor<'a> {
766 message: Option<&'a mut BoundedString>,
767 fields: &'a mut BoundedString,
768}
769
770impl<'a> FieldVisitor<'a> {
771 fn event(message: &'a mut BoundedString, fields: &'a mut BoundedString) -> Self {
772 Self {
773 message: Some(message),
774 fields,
775 }
776 }
777
778 fn fields(fields: &'a mut BoundedString) -> Self {
779 Self {
780 message: None,
781 fields,
782 }
783 }
784
785 fn record_value(&mut self, field: &Field, write_value: impl FnOnce(&mut BoundedString, bool)) {
786 if field.name() == "message"
787 && let Some(message) = self.message.as_deref_mut()
788 {
789 write_value(message, true);
790 return;
791 }
792
793 if !self.fields.is_empty() {
794 self.fields.push_str(" ");
795 }
796 self.fields.push_str(field.name());
797 self.fields.push_str("=");
798 write_value(self.fields, false);
799 }
800}
801
802impl Visit for FieldVisitor<'_> {
803 fn record_f64(&mut self, field: &Field, value: f64) {
804 self.record_value(field, |output, _| _ = write!(output, "{value}"));
805 }
806
807 fn record_i64(&mut self, field: &Field, value: i64) {
808 self.record_value(field, |output, _| _ = write!(output, "{value}"));
809 }
810
811 fn record_u64(&mut self, field: &Field, value: u64) {
812 self.record_value(field, |output, _| _ = write!(output, "{value}"));
813 }
814
815 fn record_i128(&mut self, field: &Field, value: i128) {
816 self.record_value(field, |output, _| _ = write!(output, "{value}"));
817 }
818
819 fn record_u128(&mut self, field: &Field, value: u128) {
820 self.record_value(field, |output, _| _ = write!(output, "{value}"));
821 }
822
823 fn record_bool(&mut self, field: &Field, value: bool) {
824 self.record_value(field, |output, _| _ = write!(output, "{value}"));
825 }
826
827 fn record_str(&mut self, field: &Field, value: &str) {
828 self.record_value(field, |output, is_message| {
829 if is_message {
830 output.push_str(value);
831 } else {
832 _ = write!(output, "{value:?}");
833 }
834 });
835 }
836
837 fn record_bytes(&mut self, field: &Field, value: &[u8]) {
838 self.record_value(field, |output, _| _ = write!(output, "{value:?}"));
839 }
840
841 fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
842 self.record_value(field, |output, _| _ = write!(output, "{value}"));
843 }
844
845 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
846 self.record_value(field, |output, _| _ = write!(output, "{value:?}"));
847 }
848}
849
850#[derive(Debug)]
851struct BoundedString {
852 value: String,
853 max_bytes: usize,
854 truncated: bool,
855}
856
857impl BoundedString {
858 fn new(max_bytes: usize) -> Self {
859 Self {
860 value: String::with_capacity(max_bytes.min(256)),
861 max_bytes,
862 truncated: false,
863 }
864 }
865
866 fn is_empty(&self) -> bool {
867 self.value.is_empty()
868 }
869
870 fn as_str(&self) -> &str {
871 &self.value
872 }
873
874 fn push_str(&mut self, value: &str) {
875 _ = self.write_str(value);
876 }
877
878 fn push_bounded(&mut self, value: &Self) {
879 self.push_str(value.as_str());
880 self.truncated |= value.truncated;
881 }
882
883 fn into_c_message(mut self) -> Vec<u8> {
884 if self.truncated {
885 let keep = self.max_bytes.saturating_sub(MIN_MESSAGE_BYTES);
886 truncate_utf8(&mut self.value, keep);
887 self.value.push_str("...");
888 }
889
890 if self.value.contains('\0') {
891 let mut escaped = Self::new(self.max_bytes);
892 for part in self.value.split_inclusive('\0') {
893 if let Some(without_nul) = part.strip_suffix('\0') {
894 escaped.push_str(without_nul);
895 escaped.push_str("\\0");
896 } else {
897 escaped.push_str(part);
898 }
899 }
900 return escaped.into_c_message();
901 }
902
903 let mut bytes = self.value.into_bytes();
904 bytes.push(0);
905 bytes
906 }
907}
908
909impl fmt::Write for BoundedString {
910 fn write_str(&mut self, value: &str) -> fmt::Result {
911 let remaining = self.max_bytes.saturating_sub(self.value.len());
912 if value.len() <= remaining {
913 self.value.push_str(value);
914 return Ok(());
915 }
916
917 let mut end = remaining;
918 while end > 0 && !value.is_char_boundary(end) {
919 end -= 1;
920 }
921 self.value.push_str(&value[..end]);
922 self.truncated = true;
923 Ok(())
924 }
925}
926
927fn truncate_utf8(value: &mut String, max_bytes: usize) {
928 if value.len() <= max_bytes {
929 return;
930 }
931
932 let mut end = max_bytes;
933 while end > 0 && !value.is_char_boundary(end) {
934 end -= 1;
935 }
936 value.truncate(end);
937}
938
939fn next_layer_id() -> u64 {
940 NEXT_LAYER_ID.fetch_add(1, Ordering::Relaxed)
941}
942
943const fn is_valid_signpost_id(signpost_id: u64) -> bool {
944 signpost_id != OS_SIGNPOST_ID_NULL && signpost_id != OS_SIGNPOST_ID_INVALID
945}
946
947mod ffi {
948 use std::ffi::{c_char, c_void};
949
950 unsafe extern "C" {
951 pub(super) fn rama_apple_oslog_create(
952 subsystem: *const c_char,
953 category: *const c_char,
954 ) -> *mut c_void;
955 pub(super) fn rama_apple_oslog_release(log: *mut c_void);
956 pub(super) fn rama_apple_oslog_enabled(log: *mut c_void, os_type: u8) -> u8;
957 pub(super) fn rama_apple_oslog_emit(
958 log: *mut c_void,
959 os_type: u8,
960 message: *const c_char,
961 is_public: u8,
962 );
963 pub(super) fn rama_apple_oslog_emit_split(
964 log: *mut c_void,
965 os_type: u8,
966 public_message: *const c_char,
967 private_fields: *const c_char,
968 );
969
970 pub(super) fn rama_apple_oslog_signpost_enabled(log: *mut c_void) -> u8;
971 pub(super) fn rama_apple_oslog_signpost_id_generate(log: *mut c_void) -> u64;
972 pub(super) fn rama_apple_oslog_signpost_begin(
973 log: *mut c_void,
974 signpost_id: u64,
975 message: *const c_char,
976 is_public: u8,
977 );
978 pub(super) fn rama_apple_oslog_signpost_end(
979 log: *mut c_void,
980 signpost_id: u64,
981 message: *const c_char,
982 is_public: u8,
983 );
984 pub(super) fn rama_apple_oslog_signpost_begin_split(
985 log: *mut c_void,
986 signpost_id: u64,
987 public_message: *const c_char,
988 private_fields: *const c_char,
989 );
990 pub(super) fn rama_apple_oslog_signpost_end_split(
991 log: *mut c_void,
992 signpost_id: u64,
993 public_message: *const c_char,
994 private_fields: *const c_char,
995 );
996 }
997}
998
999#[cfg(test)]
1000mod tests {
1001 use super::*;
1002 use crate::telemetry::tracing::{self, subscriber::layer::SubscriberExt as _};
1003 use std::sync::RwLock;
1004
1005 struct FormattingCapture {
1006 formatter: OsLogLayer,
1007 events: Arc<RwLock<Vec<String>>>,
1008 }
1009
1010 impl<S> Layer<S> for FormattingCapture
1011 where
1012 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
1013 {
1014 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
1015 <OsLogLayer as Layer<S>>::on_new_span(&self.formatter, attrs, id, ctx);
1016 }
1017
1018 fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
1019 <OsLogLayer as Layer<S>>::on_record(&self.formatter, id, values, ctx);
1020 }
1021
1022 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
1023 let message = if self.formatter.privacy == Privacy::PublicMessagePrivateFields {
1024 let (public, private) = self.formatter.format_event_split(event, &ctx);
1025 format!(
1026 "{} |private| {}",
1027 String::from_utf8(public[..public.len() - 1].to_vec()).unwrap(),
1028 String::from_utf8(private[..private.len() - 1].to_vec()).unwrap()
1029 )
1030 } else {
1031 let message = self.formatter.format_event(event, &ctx);
1032 String::from_utf8(message[..message.len() - 1].to_vec()).unwrap()
1033 };
1034 self.events.write().unwrap().push(message);
1035 }
1036
1037 fn on_close(&self, id: Id, ctx: Context<'_, S>) {
1038 <OsLogLayer as Layer<S>>::on_close(&self.formatter, id, ctx);
1039 }
1040 }
1041
1042 #[test]
1043 fn signposts_are_enabled_by_default() {
1044 assert_eq!(SpanMode::default(), SpanMode::Signposts);
1045 }
1046
1047 #[test]
1048 fn level_maps_are_explicit_about_faults() {
1049 assert_eq!(LevelMap::apple().get(Level::ERROR), OsLogType::Error);
1050 assert_eq!(
1051 LevelMap::persistent_info().get(Level::INFO),
1052 OsLogType::Default
1053 );
1054 assert_eq!(
1055 LevelMap::tracing_oslog_compatible().get(Level::ERROR),
1056 OsLogType::Fault
1057 );
1058 }
1059
1060 #[test]
1061 fn bounded_message_is_utf8_safe_and_nul_free() {
1062 let mut message = BoundedString::new(8);
1063 message.push_str("ééééé");
1064 let message = message.into_c_message();
1065
1066 assert_eq!(message.last(), Some(&0));
1067 std::str::from_utf8(&message[..message.len() - 1]).unwrap();
1068 assert!(message.len() <= 9);
1069
1070 let mut message = BoundedString::new(16);
1071 message.push_str("left\0right");
1072 let message = message.into_c_message();
1073 assert_eq!(&message[..message.len() - 1], b"left\\0right");
1074 }
1075
1076 #[test]
1077 fn invalid_subsystem_and_category_are_errors() {
1078 assert!(matches!(
1079 OsLogLayer::new("bad\0subsystem", "category"),
1080 Err(OsLogError::InvalidSubsystem(_))
1081 ));
1082 assert!(matches!(
1083 OsLogLayer::new("com.example", "bad\0category"),
1084 Err(OsLogError::InvalidCategory(_))
1085 ));
1086 }
1087
1088 #[test]
1089 fn explicit_parents_records_and_multiple_layers_do_not_panic() {
1090 let first = OsLogLayer::new("org.plabayo.rama.test", "first")
1091 .unwrap()
1092 .with_privacy(Privacy::Public)
1093 .with_level_map(LevelMap::persistent_info())
1094 .with_span_mode(SpanMode::Signposts)
1095 .with_span_context(true);
1096 let second = first.clone().with_target(false);
1097 let subscriber = tracing::subscriber::registry().with(first).with(second);
1098 let dispatch = tracing::Dispatch::new(subscriber);
1099
1100 tracing::dispatcher::with_default(&dispatch, || {
1101 let span = tracing::info_span!("request", request.id = tracing::field::Empty);
1102 span.record("request.id", 42_u64);
1103 tracing::info!(parent: &span, answer = 42, "explicit parent");
1104 tracing::info!(parent: None, "explicit root");
1105 });
1106 }
1107
1108 #[test]
1109 fn event_formatting_uses_explicit_scope_and_late_records() {
1110 let events = Arc::new(RwLock::new(Vec::new()));
1111 let formatter = OsLogLayer::new("org.plabayo.rama.test", "format")
1112 .unwrap()
1113 .with_target(false)
1114 .with_span_context(true);
1115 let capture = FormattingCapture {
1116 formatter,
1117 events: Arc::clone(&events),
1118 };
1119 let dispatch = tracing::Dispatch::new(tracing::subscriber::registry().with(capture));
1120
1121 tracing::dispatcher::with_default(&dispatch, || {
1122 let span = tracing::info_span!("request", request.id = tracing::field::Empty);
1123 span.record("request.id", 42_u64);
1124 tracing::info!(parent: &span, answer = 42, "explicit parent");
1125 tracing::info!(parent: None, "explicit root");
1126 });
1127
1128 let events = events.read().unwrap();
1129 assert_eq!(events.len(), 2);
1130 assert!(events[0].contains("explicit parent answer=42"));
1131 assert!(events[0].contains("spans=[request{request.id=42}]"));
1132 assert_eq!(events[1], "explicit root");
1133 }
1134
1135 #[test]
1136 fn split_privacy_keeps_fields_out_of_public_message() {
1137 let events = Arc::new(RwLock::new(Vec::new()));
1138 let formatter = OsLogLayer::new("org.plabayo.rama.test", "format")
1139 .unwrap()
1140 .with_privacy(Privacy::PublicMessagePrivateFields)
1141 .with_target(false)
1142 .with_span_context(true);
1143 let capture = FormattingCapture {
1144 formatter,
1145 events: Arc::clone(&events),
1146 };
1147 let dispatch = tracing::Dispatch::new(tracing::subscriber::registry().with(capture));
1148
1149 tracing::dispatcher::with_default(&dispatch, || {
1150 let span = tracing::info_span!("request", user = "private-user");
1151 tracing::info!(parent: &span, endpoint = "/private", "request finished");
1152 });
1153
1154 let events = events.read().unwrap();
1155 assert!(events[0].starts_with("request finished |private| "));
1156 assert!(
1157 !events[0]
1158 .split(" |private| ")
1159 .next()
1160 .unwrap()
1161 .contains("private")
1162 );
1163 assert!(events[0].contains("endpoint=\"/private\""));
1164 assert!(events[0].contains("user=\"private-user\""));
1165 }
1166}