use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use serde::Serialize;
use serde_json::{json, Value};
static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn now_millis() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0)
}
fn now_iso() -> String {
let now = time::OffsetDateTime::now_utc();
let format = time::format_description::well_known::Rfc3339;
now.format(&format).unwrap_or_else(|_| String::from("unknown"))
}
pub fn generate_request_id() -> String {
let seq = REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{}-{}", now_millis(), seq)
}
#[derive(Clone)]
pub struct Ctx {
pub request_id: String,
pub project: String,
pub bundle: String,
pub function: String,
}
impl Ctx {
fn emit(&self, level: &str, message: &str, fields: Option<Value>) {
let mut record = json!({
"ts": now_iso(),
"level": level,
"project": self.project,
"bundle": self.bundle,
"function": self.function,
"requestId": self.request_id,
"msg": message,
});
if let (Some(Value::Object(extra)), Value::Object(base)) = (fields, &mut record) {
for (key, value) in extra {
base.insert(key, value);
}
}
println!("{}", record);
}
pub fn debug(&self, message: &str) {
self.emit("debug", message, None);
}
pub fn info(&self, message: &str) {
self.emit("info", message, None);
}
pub fn warn(&self, message: &str) {
self.emit("warn", message, None);
}
pub fn error(&self, message: &str) {
self.emit("error", message, None);
}
pub fn info_with(&self, message: &str, fields: Value) {
self.emit("info", message, Some(fields));
}
}
#[derive(Debug)]
pub enum FunctionError {
Validation(String),
Business { code: String, data: Value },
Crash(String),
}
impl FunctionError {
pub fn business<T: Serialize>(code: &str, data: T) -> Self {
FunctionError::Business {
code: code.to_string(),
data: serde_json::to_value(data).unwrap_or(Value::Null),
}
}
pub fn crash(message: impl Into<String>) -> Self {
FunctionError::Crash(message.into())
}
}