diff --git a/Cargo.lock b/Cargo.lock index edbb0078..797f4b22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1078,6 +1078,7 @@ dependencies = [ "tokio", "tracing", "tracing-core", + "tracing-ffi-schema", "tracing-subscriber", ] @@ -1096,6 +1097,7 @@ name = "rodbus-schema" version = "1.0.0" dependencies = [ "oo-bindgen", + "tracing-ffi-schema", ] [[package]] @@ -1514,6 +1516,14 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-ffi-schema" +version = "0.1.0" +source = "git+https://github.com/stepfunc/tracing-ffi.git?tag=0.1.0#b1bcd1c4ad82c7adcdaf7e9a6ffed21d1e45008c" +dependencies = [ + "oo-bindgen", +] + [[package]] name = "tracing-log" version = "0.1.3" diff --git a/dep_config.json b/dep_config.json index b3fe7ccb..acc8a30a 100644 --- a/dep_config.json +++ b/dep_config.json @@ -21,6 +21,7 @@ "same-file", "semver", "semver-parser", + "tracing-ffi-schema", "ucd-trie", "unicode-segmentation", "version_check", diff --git a/ffi/rodbus-ffi/Cargo.toml b/ffi/rodbus-ffi/Cargo.toml index 01910086..1f81d880 100644 --- a/ffi/rodbus-ffi/Cargo.toml +++ b/ffi/rodbus-ffi/Cargo.toml @@ -24,6 +24,7 @@ num_cpus = "1" [build-dependencies] rodbus-schema = { path = "../rodbus-schema" } rust-oo-bindgen = { git = "https://github.com/stepfunc/oo_bindgen.git", tag = "0.3.0" } +tracing-ffi-schema = { git = "https://github.com/stepfunc/tracing-ffi.git", tag = "0.1.0" } [features] default = ["serial", "tls"] diff --git a/ffi/rodbus-ffi/build.rs b/ffi/rodbus-ffi/build.rs index 4a30f66d..6246a6ff 100644 --- a/ffi/rodbus-ffi/build.rs +++ b/ffi/rodbus-ffi/build.rs @@ -1,6 +1,16 @@ +use std::env; +use std::io::Write; +use std::path::Path; + fn main() { println!("cargo:rerun-if-changed=build.rs"); + let mut file = + std::fs::File::create(Path::new(&env::var_os("OUT_DIR").unwrap()).join("tracing.rs")) + .unwrap(); + file.write_all(tracing_ffi_schema::get_impl_file().as_bytes()) + .unwrap(); + match rodbus_schema::build_lib() { Ok(lib) => { rust_oo_bindgen::RustCodegen::new(&lib).generate().unwrap(); diff --git a/ffi/rodbus-ffi/src/helpers/conversions.rs b/ffi/rodbus-ffi/src/helpers/conversions.rs index 67c74047..77ba8b55 100644 --- a/ffi/rodbus-ffi/src/helpers/conversions.rs +++ b/ffi/rodbus-ffi/src/helpers/conversions.rs @@ -5,6 +5,29 @@ use rodbus::Shutdown; use crate::ffi; +impl From for rodbus::DecodeLevel { + fn from(level: ffi::DecodeLevel) -> Self { + rodbus::DecodeLevel { + app: match level.app() { + ffi::AppDecodeLevel::Nothing => rodbus::AppDecodeLevel::Nothing, + ffi::AppDecodeLevel::FunctionCode => rodbus::AppDecodeLevel::FunctionCode, + ffi::AppDecodeLevel::DataHeaders => rodbus::AppDecodeLevel::DataHeaders, + ffi::AppDecodeLevel::DataValues => rodbus::AppDecodeLevel::DataValues, + }, + frame: match level.frame() { + ffi::FrameDecodeLevel::Nothing => rodbus::FrameDecodeLevel::Nothing, + ffi::FrameDecodeLevel::Header => rodbus::FrameDecodeLevel::Header, + ffi::FrameDecodeLevel::Payload => rodbus::FrameDecodeLevel::Payload, + }, + physical: match level.physical() { + ffi::PhysDecodeLevel::Nothing => rodbus::PhysDecodeLevel::Nothing, + ffi::PhysDecodeLevel::Length => rodbus::PhysDecodeLevel::Length, + ffi::PhysDecodeLevel::Data => rodbus::PhysDecodeLevel::Data, + }, + } + } +} + impl From for ffi::RequestError { fn from(err: rodbus::RequestError) -> Self { match err { diff --git a/ffi/rodbus-ffi/src/lib.rs b/ffi/rodbus-ffi/src/lib.rs index 857e84f6..ebe0938e 100644 --- a/ffi/rodbus-ffi/src/lib.rs +++ b/ffi/rodbus-ffi/src/lib.rs @@ -6,9 +6,9 @@ mod database; mod error; mod iterator; mod list; -mod logging; mod runtime; mod server; +mod tracing; pub(crate) mod helpers { // From implementations for FFI types @@ -17,16 +17,22 @@ pub(crate) mod helpers { mod ext; } +pub(crate) use crate::tracing::*; pub use client::*; pub use database::*; pub use iterator::*; pub use list::*; -pub(crate) use logging::*; pub use runtime::*; pub use server::*; pub mod ffi; +impl From for std::os::raw::c_int { + fn from(_: crate::TracingInitError) -> Self { + crate::ffi::ParamError::LoggingAlreadyConfigured.into() + } +} + lazy_static::lazy_static! { static ref VERSION: std::ffi::CString = std::ffi::CString::new(rodbus::VERSION).unwrap(); } diff --git a/ffi/rodbus-ffi/src/logging.rs b/ffi/rodbus-ffi/src/logging.rs deleted file mode 100644 index 995e5223..00000000 --- a/ffi/rodbus-ffi/src/logging.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::ffi::CString; - -use rodbus::*; - -use tracing::span::{Attributes, Record}; -use tracing::{Event, Id, Metadata}; -use tracing_subscriber::fmt::time::{ChronoUtc, SystemTime}; -use tracing_subscriber::fmt::MakeWriter; - -use crate::ffi; - -thread_local! { - pub static LOG_BUFFER: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); -} - -pub fn configure_logging( - config: ffi::LoggingConfig, - handler: ffi::Logger, -) -> Result<(), ffi::ParamError> { - tracing::subscriber::set_global_default(adapter(config, handler)) - .map_err(|_| ffi::ParamError::LoggingAlreadyConfigured) -} - -struct ThreadLocalBufferWriter; - -struct ThreadLocalMakeWriter; - -impl MakeWriter for ThreadLocalMakeWriter { - type Writer = ThreadLocalBufferWriter; - - fn make_writer(&self) -> Self::Writer { - ThreadLocalBufferWriter - } -} - -impl std::io::Write for ThreadLocalBufferWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - LOG_BUFFER.with(|vec| vec.borrow_mut().extend_from_slice(buf)); - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -fn adapter( - config: ffi::LoggingConfig, - handler: ffi::Logger, -) -> impl tracing::Subscriber + Send + Sync + 'static { - Adapter { - handler, - inner: config.build(), - } -} - -impl ffi::LoggingConfig { - fn build(&self) -> Box { - let level: tracing::Level = self.level().into(); - - let builder = tracing_subscriber::fmt() - .with_ansi(false) - .with_max_level(level) - .with_level(self.print_level) - .with_target(self.print_module_info) - .with_writer(ThreadLocalMakeWriter); - - match self.time_format() { - ffi::TimeFormat::None => { - let builder = builder.without_time(); - match self.output_format() { - ffi::LogOutputFormat::Text => Box::new(builder.finish()), - ffi::LogOutputFormat::Json => Box::new(builder.json().finish()), - } - } - ffi::TimeFormat::Rfc3339 => { - let builder = builder.with_timer(ChronoUtc::default()); - match self.output_format() { - ffi::LogOutputFormat::Text => Box::new(builder.finish()), - ffi::LogOutputFormat::Json => Box::new(builder.json().finish()), - } - } - ffi::TimeFormat::System => { - let builder = builder.with_timer(SystemTime::default()); - match self.output_format() { - ffi::LogOutputFormat::Text => Box::new(builder.finish()), - ffi::LogOutputFormat::Json => Box::new(builder.json().finish()), - } - } - } - } -} - -struct Adapter { - handler: ffi::Logger, - inner: Box, -} - -impl tracing::Subscriber for Adapter { - fn enabled(&self, metadata: &Metadata<'_>) -> bool { - self.inner.enabled(metadata) - } - - fn new_span(&self, span: &Attributes<'_>) -> Id { - self.inner.new_span(span) - } - - fn record(&self, span: &Id, values: &Record<'_>) { - self.inner.record(span, values) - } - - fn record_follows_from(&self, span: &Id, follows: &Id) { - self.inner.record_follows_from(span, follows) - } - - fn event(&self, event: &Event<'_>) { - self.inner.event(event); - if let Ok(string) = LOG_BUFFER.with(|vec| CString::new(vec.borrow().as_slice())) { - self.handler - .on_message((*event.metadata().level()).into(), &string); - } - LOG_BUFFER.with(|vec| vec.borrow_mut().clear()) - } - - fn enter(&self, span: &Id) { - self.inner.enter(span) - } - - fn exit(&self, span: &Id) { - self.inner.exit(span) - } - - fn clone_span(&self, span: &Id) -> Id { - self.inner.clone_span(span) - } - - fn try_close(&self, span: Id) -> bool { - self.inner.try_close(span) - } - - fn current_span(&self) -> tracing_core::span::Current { - self.inner.current_span() - } -} - -impl From for ffi::LogLevel { - fn from(level: tracing::Level) -> Self { - match level { - tracing::Level::DEBUG => ffi::LogLevel::Debug, - tracing::Level::TRACE => ffi::LogLevel::Trace, - tracing::Level::INFO => ffi::LogLevel::Info, - tracing::Level::WARN => ffi::LogLevel::Warn, - tracing::Level::ERROR => ffi::LogLevel::Error, - } - } -} - -impl From for tracing::Level { - fn from(level: ffi::LogLevel) -> Self { - match level { - ffi::LogLevel::Debug => tracing::Level::DEBUG, - ffi::LogLevel::Trace => tracing::Level::TRACE, - ffi::LogLevel::Info => tracing::Level::INFO, - ffi::LogLevel::Warn => tracing::Level::WARN, - ffi::LogLevel::Error => tracing::Level::ERROR, - } - } -} - -impl From for DecodeLevel { - fn from(level: ffi::DecodeLevel) -> Self { - DecodeLevel { - app: match level.app() { - ffi::AppDecodeLevel::Nothing => AppDecodeLevel::Nothing, - ffi::AppDecodeLevel::FunctionCode => AppDecodeLevel::FunctionCode, - ffi::AppDecodeLevel::DataHeaders => AppDecodeLevel::DataHeaders, - ffi::AppDecodeLevel::DataValues => AppDecodeLevel::DataValues, - }, - frame: match level.frame() { - ffi::FrameDecodeLevel::Nothing => FrameDecodeLevel::Nothing, - ffi::FrameDecodeLevel::Header => FrameDecodeLevel::Header, - ffi::FrameDecodeLevel::Payload => FrameDecodeLevel::Payload, - }, - physical: match level.physical() { - ffi::PhysDecodeLevel::Nothing => PhysDecodeLevel::Nothing, - ffi::PhysDecodeLevel::Length => PhysDecodeLevel::Length, - ffi::PhysDecodeLevel::Data => PhysDecodeLevel::Data, - }, - } - } -} diff --git a/ffi/rodbus-ffi/src/tracing.rs b/ffi/rodbus-ffi/src/tracing.rs new file mode 100644 index 00000000..8ca78185 --- /dev/null +++ b/ffi/rodbus-ffi/src/tracing.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/tracing.rs")); diff --git a/ffi/rodbus-schema/Cargo.toml b/ffi/rodbus-schema/Cargo.toml index b904f6b9..721b829d 100644 --- a/ffi/rodbus-schema/Cargo.toml +++ b/ffi/rodbus-schema/Cargo.toml @@ -13,3 +13,4 @@ readme = "../README.md" [dependencies] oo-bindgen = { git = "https://github.com/stepfunc/oo_bindgen.git", tag = "0.3.0" } +tracing-ffi-schema = { git = "https://github.com/stepfunc/tracing-ffi.git", tag = "0.1.0" } diff --git a/ffi/rodbus-schema/src/common.rs b/ffi/rodbus-schema/src/common.rs index 9a1952e2..83fb2daa 100644 --- a/ffi/rodbus-schema/src/common.rs +++ b/ffi/rodbus-schema/src/common.rs @@ -23,7 +23,7 @@ impl CommonDefinitions { pub(crate) fn build(lib: &mut LibraryBuilder) -> BackTraced { let error_type = build_error_type(lib)?; let nothing = build_nothing_type(lib)?; - let decode_level = crate::logging::define(lib, error_type.clone())?; + let decode_level = crate::decoding::define(lib)?; let bit_value = build_bit_value(lib)?; let register_value = build_register_value(lib)?; diff --git a/ffi/rodbus-schema/src/decoding.rs b/ffi/rodbus-schema/src/decoding.rs new file mode 100644 index 00000000..9ab9e92a --- /dev/null +++ b/ffi/rodbus-schema/src/decoding.rs @@ -0,0 +1,67 @@ +use oo_bindgen::model::*; + +const NOTHING: &str = "nothing"; + +pub(crate) fn define(lib: &mut LibraryBuilder) -> BackTraced { + let app_decode_level_enum = lib + .define_enum("app_decode_level")? + .push(NOTHING, "Decode nothing")? + .push("function_code", "Decode the function code only")? + .push("data_headers", "Decode the function code and the general description of the data")? + .push( + "data_values", + "Decode the function code, the general description of the data and the actual data values", + )? + .doc( + doc("Controls how transmitted and received message at the application layer are decoded at the INFO log level") + .details("Application-layer messages are referred to as Protocol Data Units (PDUs) in the specification.") + )? + .build()?; + + let frame_decode_level_enum = lib + .define_enum("frame_decode_level")? + .push(NOTHING, "Log nothing")? + .push("header", " Decode the header")? + .push("payload", "Decode the header and the raw payload as hexadecimal")? + .doc( + doc("Controls how the transmitted and received frames are decoded at the INFO log level") + .details("Transport-specific framing wraps the application-layer traffic. You'll see these frames called ADUs in the Modbus specification.") + .details("On TCP, this is the MBAP decoding. On serial, this controls the serial line PDU.") + )? + .build()?; + + let phys_decode_level_enum = lib + .define_enum("phys_decode_level")? + .push(NOTHING, "Log nothing")? + .push( + "length", + "Log only the length of data that is sent and received", + )? + .push( + "data", + "Log the length and the actual data that is sent and received", + )? + .doc("Controls how data transmitted at the physical layer (TCP, serial, etc) is logged")? + .build()?; + + let app_field = Name::create("app")?; + let frame_field = Name::create("frame")?; + let physical_field = Name::create("physical")?; + + let decode_level_struct = lib.declare_universal_struct("decode_level")?; + let decode_level_struct = lib.define_universal_struct(decode_level_struct)? + .add(&app_field, app_decode_level_enum, "Controls decoding of the application layer (PDU)")? + .add(&frame_field, frame_decode_level_enum, "Controls decoding of frames (MBAP / Serial PDU)")? + .add(&physical_field, phys_decode_level_enum, "Controls the logging of physical layer read/write")? + .doc("Controls the decoding of transmitted and received data at the application, frame, and physical layer")? + .end_fields()? + .add_full_initializer("build")? + .begin_initializer("nothing", InitializerType::Static, "Initialize log levels to defaults which is to decode nothing")? + .default_variant(&app_field, NOTHING)? + .default_variant(&frame_field, NOTHING)? + .default_variant(&physical_field, NOTHING)? + .end_initializer()? + .build()?; + + Ok(decode_level_struct) +} diff --git a/ffi/rodbus-schema/src/lib.rs b/ffi/rodbus-schema/src/lib.rs index cc6c809a..7c9d57d5 100644 --- a/ffi/rodbus-schema/src/lib.rs +++ b/ffi/rodbus-schema/src/lib.rs @@ -5,7 +5,7 @@ use oo_bindgen::model::*; mod client; mod common; -mod logging; +mod decoding; mod runtime; mod server; @@ -64,6 +64,8 @@ pub fn build_lib() -> BackTraced { let common = CommonDefinitions::build(&mut builder)?; + tracing_ffi_schema::define(&mut builder, common.error_type.clone())?; + client::build(&mut builder, &common)?; server::build(&mut builder, &common)?; diff --git a/ffi/rodbus-schema/src/logging.rs b/ffi/rodbus-schema/src/logging.rs deleted file mode 100644 index eb1f4686..00000000 --- a/ffi/rodbus-schema/src/logging.rs +++ /dev/null @@ -1,209 +0,0 @@ -use oo_bindgen::model::*; - -fn define_log_level_enum(lib: &mut LibraryBuilder) -> BackTraced { - let definition = lib - .define_enum("log_level")? - .push("error", "Error log level")? - .push("warn", "Warning log level")? - .push("info", "Information log level")? - .push("debug", "Debugging log level")? - .push("trace", "Trace log level")? - .doc( - doc("Log level") - .details("Used in {interface:logger.on_message()} callback to identify the log level of a message.") - )? - .build()?; - - Ok(definition) -} - -fn define_time_format_enum(lib: &mut LibraryBuilder) -> BackTraced { - let definition = lib - .define_enum("time_format")? - .push("none", "Don't format the timestamp as part of the message")? - .push("rfc_3339", "Format the time using RFC 3339")? - .push( - "system", - "Format the time in a human readable format e.g. 'Jun 25 14:27:12.955'", - )? - .doc("Describes if and how the time will be formatted in log messages")? - .build()?; - - Ok(definition) -} - -fn define_log_output_format_enum(lib: &mut LibraryBuilder) -> BackTraced { - let definition = lib - .define_enum("log_output_format")? - .push("text", "A simple text-based format")? - .push("json", "Output formatted as JSON")? - .doc("Describes how each log event is formatted")? - .build()?; - - Ok(definition) -} - -fn define_logging_config_struct( - lib: &mut LibraryBuilder, - log_level_enum: EnumHandle, -) -> BackTraced { - let logging_config_struct = lib.declare_function_argument_struct("logging_config")?; - - let log_output_format_enum = define_log_output_format_enum(lib)?; - let time_format_enum = define_time_format_enum(lib)?; - - let level = Name::create("level")?; - let output_format = Name::create("output_format")?; - let time_format = Name::create("time_format")?; - let print_level = Name::create("print_level")?; - let print_module_info = Name::create("print_module_info")?; - - let logging_config_struct = lib - .define_function_argument_struct(logging_config_struct)? - .add(&level, log_level_enum, "logging level")? - .add( - &output_format, - log_output_format_enum, - "output formatting options", - )? - .add(&time_format, time_format_enum, "optional time format")? - .add( - &print_level, - Primitive::Bool, - "optionally print the log level as part to the message string", - )? - .add( - &print_module_info, - Primitive::Bool, - "optionally print the underlying Rust module information to the message string", - )? - .doc("Logging configuration options")? - .end_fields()? - .begin_initializer( - "init", - InitializerType::Normal, - "Initialize the configuration to default values", - )? - .default(&level, "info".default_variant())? - .default(&output_format, "text".default_variant())? - .default(&time_format, "system".default_variant())? - .default(&print_level, true)? - .default(&print_module_info, false)? - .end_initializer()? - .build()?; - - Ok(logging_config_struct) -} - -const NOTHING: &str = "nothing"; - -pub fn define( - lib: &mut LibraryBuilder, - error_type: ErrorTypeHandle, -) -> BackTraced { - let log_level_enum = define_log_level_enum(lib)?; - - let logging_config_struct = define_logging_config_struct(lib, log_level_enum.clone())?; - - let log_callback_interface = lib - .define_interface( - "logger", - "Logging interface that receives the log messages and writes them somewhere.", - )? - .begin_callback( - "on_message", - "Called when a log message was received and should be logged", - )? - .param("level", log_level_enum, "Level of the message")? - .param("message", StringType, "Actual formatted message")? - .end_callback()? - .build_async()?; - - let configure_logging_fn = lib - .define_function("configure_logging")? - .param( - "config", - logging_config_struct, - "Configuration options for logging" - )? - .param( - "logger", - log_callback_interface, - "Logger that will receive each logged message", - )? - .fails_with(error_type)? - .doc( - doc("Set the callback that will receive all the log messages") - .details("There is only a single globally allocated logger. Calling this method a second time will return an error.") - .details("If this method is never called, no logging will be performed.") - )? - .build_static("configure")?; - - let _logging_class = lib - .define_static_class("logging")? - .static_method(configure_logging_fn)? - .doc("Provides a static method for configuring logging")? - .build()?; - - let app_decode_level_enum = lib - .define_enum("app_decode_level")? - .push(NOTHING, "Decode nothing")? - .push("function_code", "Decode the function code only")? - .push("data_headers", "Decode the function code and the general description of the data")? - .push( - "data_values", - "Decode the function code, the general description of the data and the actual data values", - )? - .doc( - doc("Controls how transmitted and received message at the application layer are decoded at the INFO log level") - .details("Application-layer messages are referred to as Protocol Data Units (PDUs) in the specification.") - )? - .build()?; - - let frame_decode_level_enum = lib - .define_enum("frame_decode_level")? - .push(NOTHING, "Log nothing")? - .push("header", " Decode the header")? - .push("payload", "Decode the header and the raw payload as hexadecimal")? - .doc( - doc("Controls how the transmitted and received frames are decoded at the INFO log level") - .details("Transport-specific framing wraps the application-layer traffic. You'll see these frames called ADUs in the Modbus specification.") - .details("On TCP, this is the MBAP decoding. On serial, this controls the serial line PDU.") - )? - .build()?; - - let phys_decode_level_enum = lib - .define_enum("phys_decode_level")? - .push(NOTHING, "Log nothing")? - .push( - "length", - "Log only the length of data that is sent and received", - )? - .push( - "data", - "Log the length and the actual data that is sent and received", - )? - .doc("Controls how data transmitted at the physical layer (TCP, serial, etc) is logged")? - .build()?; - - let app_field = Name::create("app")?; - let frame_field = Name::create("frame")?; - let physical_field = Name::create("physical")?; - - let decode_level_struct = lib.declare_universal_struct("decode_level")?; - let decode_level_struct = lib.define_universal_struct(decode_level_struct)? - .add(&app_field, app_decode_level_enum, "Controls decoding of the application layer (PDU)")? - .add(&frame_field, frame_decode_level_enum, "Controls decoding of frames (MBAP / Serial PDU)")? - .add(&physical_field, phys_decode_level_enum, "Controls the logging of physical layer read/write")? - .doc("Controls the decoding of transmitted and received data at the application, frame, and physical layer")? - .end_fields()? - .add_full_initializer("build")? - .begin_initializer("nothing", InitializerType::Static, "Initialize log levels to defaults which is to decode nothing")? - .default_variant(&app_field, NOTHING)? - .default_variant(&frame_field, NOTHING)? - .default_variant(&physical_field, NOTHING)? - .end_initializer()? - .build()?; - - Ok(decode_level_struct) -}