Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typescript: add logs to traces #1248

Merged
merged 11 commits into from
Jun 7, 2024
2 changes: 2 additions & 0 deletions runtimes/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ impl Runtime {
trace::Tracer::noop()
};

log::set_tracer(tracer.clone());

let pubsub = pubsub::Manager::new(tracer.clone(), resources.pubsub_clusters, &md);
let sqldb = sqldb::Manager::new(
resources.sql_clusters,
Expand Down
77 changes: 74 additions & 3 deletions runtimes/core/src/log/logger.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use crate::error::AppError;
use crate::log::fields::{FieldConfig, DEFAULT_FIELDS, GCP_FIELDS};
use crate::log::fields::FieldConfig;
use crate::log::writers::{default_writer, Writer};
use crate::model;
use crate::model::{self, LogField};
use crate::trace::protocol::LogMessageData;
use crate::trace::Tracer;
use anyhow::Context;
use env_logger::filter::Filter;
use log::{Log, Metadata, Record};
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::time::SystemTime;

pub type Fields = BTreeMap<String, serde_json::Value>;
Expand All @@ -19,6 +21,7 @@ pub struct Logger {
field_config: &'static FieldConfig,
writer: Arc<dyn Writer>,
extra_fields: Fields,
tracer: Arc<RwLock<Tracer>>,
}

impl Logger {
Expand All @@ -34,9 +37,16 @@ impl Logger {
field_config,
writer: default_writer(field_config),
extra_fields: Fields::new(),
tracer: Arc::new(RwLock::new(Tracer::noop())),
}
}

/// Sets the loggers tracer
pub fn set_tracer(&self, tracer: Tracer) {
let mut t = self.tracer.write().expect("tracer lock poisoned");
*t = tracer;
}

/// Returns a new logger with the given log level.
pub fn with_level(&self, level: log::LevelFilter) -> Self {
Self {
Expand Down Expand Up @@ -88,6 +98,8 @@ impl Logger {
caller: Option<String>,
fields: Option<Fields>,
) -> anyhow::Result<()> {
self.write_to_trace(request, level, &msg, &fields);

let mut values = Fields::new();

// Copy the extra fields into the values map.
Expand Down Expand Up @@ -236,6 +248,65 @@ impl Logger {
Some(visitor.0),
)
}

/// Writes the log to trace
fn write_to_trace(
&self,
request: Option<&model::Request>,
level: log::Level,
msg: &str,
fields: &Option<Fields>,
) {
let mut trace_fields = Vec::new();

if let Some(ref fields) = fields {
fields.iter().for_each(|(ref key, val)| match val {
serde_json::Value::Number(num) => {
if num.is_i64() {
trace_fields.push(LogField {
key,
value: model::LogFieldValue::I64(num.as_i64().unwrap()),
});
} else if num.is_u64() {
trace_fields.push(LogField {
key,
value: model::LogFieldValue::U64(num.as_u64().unwrap()),
});
} else if num.is_f64() {
trace_fields.push(LogField {
key,
value: model::LogFieldValue::F64(num.as_f64().unwrap()),
});
}
}
serde_json::Value::Bool(b) => trace_fields.push(LogField {
key,
value: model::LogFieldValue::Bool(b.to_owned()),
}),
serde_json::Value::String(ref str) => trace_fields.push(LogField {
key,
value: model::LogFieldValue::String(str),
}),
serde_json::Value::Array(_)
| serde_json::Value::Object(_)
| serde_json::Value::Null => trace_fields.push(LogField {
key,
value: model::LogFieldValue::Json(&val),
}),
});
}

let _ = self
.tracer
.read()
.expect("tracer lock poisoned")
.log_message(LogMessageData {
source: request,
msg,
level,
fields: trace_fields,
fredr marked this conversation as resolved.
Show resolved Hide resolved
});
}
}

/// This trait defines the logging functions that are available on the `Logger` type.
Expand Down
7 changes: 7 additions & 0 deletions runtimes/core/src/log/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ mod writers;
use crate::log::fields::FieldConfig;
pub use logger::{Fields, LogFromExternalRuntime, LogFromRust, Logger};

use crate::trace::Tracer;

/// The global root logger instance that is used by both the `log` crate
/// and all other code in the Encore runtime.
static ROOT: OnceCell<&Logger> = OnceCell::new();
Expand All @@ -20,6 +22,11 @@ pub fn init() {
_ = root();
}

/// Set the tracer on the global logger
pub fn set_tracer(tracer: Tracer) {
root().set_tracer(tracer);
}

/// Returns a reference to the global root logger instance.
pub fn root() -> &'static Logger {
ROOT.get_or_init(|| {
Expand Down
36 changes: 36 additions & 0 deletions runtimes/core/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,39 @@ pub struct AuthSuccessResponse {
/// The user data.
pub user_data: serde_json::Map<String, serde_json::Value>,
}

// matches go runtime
pub enum LogLevel {
fredr marked this conversation as resolved.
Show resolved Hide resolved
Trace = 0,
Debug,
Info,
Warn,
Error,
}

pub enum LogFieldValue<'a> {
String(&'a str),
U64(u64),
I64(i64),
F64(f64),
Bool(bool),
Json(&'a serde_json::Value),
}

pub struct LogField<'a> {
pub key: &'a str,
pub value: LogFieldValue<'a>,
}

impl LogField<'_> {
pub fn type_byte(&self) -> u8 {
match self.value {
LogFieldValue::String(_) => 2,
LogFieldValue::Bool(_) => 3,
LogFieldValue::I64(_) => 8,
LogFieldValue::Json(_) => 7,
LogFieldValue::U64(_) => 9,
LogFieldValue::F64(_) => 11,
}
}
}
7 changes: 7 additions & 0 deletions runtimes/core/src/trace/eventbuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ impl EventBuffer {
self.buf.extend_from_slice(&self.scratch[..i]);
}

/// Writes a float, always as 8 bytes.
#[inline]
pub fn f64(&mut self, f: f64) {
let data: [u8; 8] = f.to_le_bytes();
self.buf.extend_from_slice(&data);
}

/// Writes a signed integer, always as 8 bytes.
#[inline]
pub fn i64(&mut self, i: i64) {
Expand Down
65 changes: 64 additions & 1 deletion runtimes/core/src/trace/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::sync::atomic::{AtomicU64, Ordering};

use crate::api;
use crate::model::{Request, TraceEventId};
use crate::model::{LogField, LogFieldValue, Request, TraceEventId};
use crate::trace::eventbuf::EventBuffer;
use crate::trace::log::TraceEvent;
use crate::{model, EncoreName};
Expand Down Expand Up @@ -57,6 +57,69 @@ impl Tracer {
}
}

pub struct LogMessageData<'a> {
pub source: Option<&'a Request>,
pub msg: &'a str,
pub level: log::Level,
pub fields: Vec<LogField<'a>>,
}

impl LogMessageData<'_> {
fn level_byte(&self) -> u8 {
match self.level {
log::Level::Error => 4,
log::Level::Warn => 3,
log::Level::Info => 2,
log::Level::Debug => 1,
log::Level::Trace => 0,
}
}
}

impl Tracer {
#[inline]
pub fn log_message(&self, data: LogMessageData) {
let Some(source) = data.source else {
return;
};

let mut eb = BasicEventData {
correlation_event_id: None,
extra_space: 4 + 4 + data.msg.len() + 1 + 64 * data.fields.len(),
}
.to_eb();

eb.byte(data.level_byte());
eb.str(&data.msg);
eb.uvarint(data.fields.len() as u64);
for field in data.fields {
eb.byte(field.type_byte());
eb.str(field.key);

match field.value {
LogFieldValue::String(str) => eb.str(str),
LogFieldValue::U64(n) => eb.uvarint(n),
LogFieldValue::I64(n) => eb.ivarint(n),
LogFieldValue::F64(n) => eb.f64(n),
LogFieldValue::Bool(b) => eb.bool(b),
LogFieldValue::Json(json) => match serde_json::to_vec(json) {
Ok(bytes) => {
eb.byte_string(&bytes);
eb.nyi_stack_pcs()
}
Err(err) => {
eb.byte_string(&[]);
eb.err_with_legacy_stack(Some(&err))
}
},
}
}
eb.nyi_stack_pcs();

_ = self.send(EventType::LogMessage, source.span, eb);
}
}

impl Tracer {
#[inline]
pub fn request_span_start(&self, req: &model::Request) {
Expand Down
Loading