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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Tracing subscriber for emitting logs into Cloudflare's syslog pipeline (queryable via Kibana).
//!
//! Follow [How To: Generate a WShim Token] to generate a logging token and put it in your worker
//! secrets:
//! ```ignore
//! wrangler secret put LOGGING_TOKEN
//! ```
//!
//! Usage in worker using workers-rs:
//! ```rust
//! use tracing::{debug, info, info_span, Instrument};
//! use worker::*;
//! use worker_cf_logging::initialize_tracing;
//!
//! #[event(fetch)]
//! pub async fn main(req: Request, env: Env, ctx: worker::Context) -> Result<Response> {
//!     let logging_token = env.secret("LOGGING_TOKEN")?.to_string();
//!
//!     let log_level = match env.var("LOG_LEVEL") {
//!         Ok(var) => var.to_string(),
//!         Err(_) => "info".to_string(),
//!     };
//!
//!     let logger = initialize_tracing(&log_level, logging_token, true);
//!
//!     let res = handle(req).instrument(info_span!("handle")).await?;
//!
//!     ctx.wait_until(async move {
//!         _ = logger.flush().await;
//!     });
//!
//!     Ok(res)
//! }
//!
//! async fn handle(req: Request) -> Result<Response> {
//!     info!(path = req.path(), req_method = ?req.method(), "incoming request");
//!
//!     let req = worker::Fetch::Url(Url::parse("https://httpbin.org/ip")?);
//!
//!     let ip = req.send().await?.text().await?;
//!
//!     debug!(
//!         ip_response = ?ip,
//!         veg = "potato",
//!         float = 0.12345,
//!         signed = -12345,
//!         unsigned = 12345u64,
//!         flag = false,
//!         "my favourites"
//!     );
//!
//!     Response::ok("bienvenue")
//! }
//!
//! ```
//!
//! You can then query your logs in Kibana, the service being the one you specified when generating
//! the logging token.
//!
//! [How To: Generate a WShim Token]: https://wiki.cfdata.org/display/OBS/How+To%3A+Generate+a+WShim+Token
//! [Tracing subscriber]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/
mod logger;
mod timing;

pub use logger::SyslogLogger;
use std::sync::Once;
use timing::WasmTimingLayer;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};

use tracing_subscriber::registry;

static mut LOGGER: Option<SyslogLogger> = None;
static INITIALIZE_TRACING: Once = Once::new();

/// Initializes tracing subscriber.
///
/// Initialization is only performed once, meaning subsequent calls are ignored.
///
/// Enabling timings will attempt to time and log spans. NB. Time is only updated on IO in Workers.
///
/// Returns the underlying logger which **must** be called to flush to Kibana.
pub fn initialize_tracing(
    filter_level: &str,
    logging_token: String,
    enable_timings: bool,
) -> SyslogLogger {
    unsafe {
        INITIALIZE_TRACING.call_once(|| {
            let logger = SyslogLogger::new(&logging_token);
            LOGGER = Some(logger.clone());

            registry()
                .with(enable_timings.then(WasmTimingLayer::new))
                .with(logger)
                .with(EnvFilter::new(filter_level))
                .init();
        });

        LOGGER.as_ref().cloned().unwrap()
    }
}