1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#![doc = include_str!("../README.md")]
// required for above example, showing main isn't needless, it shows the context of where this will
// (almost) always be called from
#![allow(clippy::needless_doctest_main)]

mod ingest;
mod layer;
pub mod options;

pub use ingest::{ModalityIngestHandle, TimelineId};
pub use layer::ModalityLayer;
pub use options::Options;

use anyhow::Context as _;
use ingest::ConnectError;
use std::fmt::Debug;
use thiserror::Error;
use tracing_core::Dispatch;

#[derive(Debug, Error)]
pub enum InitError {
    /// No auth was provided, set with [`Options::set_auth`]/[`Options::with_auth`] or set the
    /// `MODALITY_AUTH_TOKEN` environment variable.
    #[error("Authentication required, set init option or env var MODALITY_AUTH_TOKEN")]
    AuthRequired,

    /// Auth was provided, but was not accepted by modality.
    #[error(transparent)]
    AuthFailed(ConnectError),

    /// Errors that it is assumed there is no way to handle without human intervention, meant for
    /// consumers to just print and carry on or panic.
    #[error(transparent)]
    UnexpectedFailure(#[from] anyhow::Error),
}

/// A global tracer instance for [tracing.rs](https://tracing.rs/) that sends traces via a network
/// socket to [Modality](https://auxon.io/).
pub struct TracingModality {
    ingest_handle: ModalityIngestHandle,
}

impl TracingModality {
    /// Initialize with default options and set as the global default tracer.
    pub fn init() -> Result<Self, InitError> {
        Self::init_with_options(Default::default())
    }

    /// Initialize with the provided options and set as the global default tracer.
    pub fn init_with_options(opts: Options) -> Result<Self, InitError> {
        let mut layer =
            ModalityLayer::init_with_options(opts).context("initialize ModalityLayer")?;
        let ingest_handle = layer
            .take_handle()
            .expect("take handle on brand new layer somehow failed");

        let disp = Dispatch::new(layer.into_subscriber());
        tracing::dispatcher::set_global_default(disp).unwrap();

        Ok(Self { ingest_handle })
    }

    /// Stop accepting new trace events, flush all existing events, and stop ingest thread.
    pub fn finish(self) {
        self.ingest_handle.finish();
    }
}

/// Retrieve the current local timeline ID. Useful for for sending alongside data and a custom nonce
/// for recording timeline interactions on remote timelines.
pub fn timeline_id() -> TimelineId {
    ingest::current_timeline()
}