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

allow foreign language to receive socket manager log #48

Merged
merged 5 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ jobs:
- run:
name: "Test"
command: cd build && ctest -C Release --output-on-failure && cd ..
environment:
SOCKET_LOG: debug
- run:
name: "Install"
command: cmake --install build --config Release
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:

- name: Install LLVM and Clang
run: |
brew update
brew install llvm@17
brew update || true
brew install llvm@17 || true

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
Expand Down Expand Up @@ -54,8 +54,6 @@ jobs:
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure
env:
SOCKET_LOG: debug

- name: Install
run: sudo cmake --install build --config Release
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "concurrentqueue"]
path = tests/concurrentqueue
url = https://github.com/cameron314/concurrentqueue.git
[submodule "tests/spdlog"]
path = tests/spdlog-repo
url = https://github.com/gabime/spdlog
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ edition = "2021"
crate-type = ["staticlib"]

[dependencies]
async-ringbuf = "0.2.0-rc.4"
dashmap = { version = "5.4.0", features = ["inline"] }
libc = "0.2.146"
socket2 = "0.5.3"

[dependencies.async-ringbuf]
git = "https://github.com/Congyuwang/ringbuf.git"

[dependencies.tokio]
version = "1.29.1"
default-features = false
Expand All @@ -31,4 +33,4 @@ features = ["std"]
[dependencies.tracing-subscriber]
version = "0.3.17"
default-features = false
features = ["std", "fmt", "env-filter"]
features = ["std", "fmt", "env-filter", "registry"]
2 changes: 1 addition & 1 deletion examples/echo_server/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ build:
codesign -s - -v -f --entitlements debug.plist ./build/echo_server

run:
SOCKET_LOG=info ./build/echo_server
./build/echo_server
30 changes: 30 additions & 0 deletions include/socket_manager/socket_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,39 @@
#include <functional>
#include <memory>
#include <string>
#include <string_view>

namespace socket_manager {

/**
* @brief The log data structure.
*
* This structure is used to pass log data from C to C++.
*/
struct LogData {
SOCKET_MANAGER_C_API_TraceLevel level;
std::string_view target;
std::string_view file;
std::string_view message;
};

/**
* Helper function to convert from C log data to C++ log data.
*/
LogData from_c_log_data(SOCKET_MANAGER_C_API_LogData log_data);

/**
* @brief Initialize the logger for the socket manager.
*
* This function cannot be called more than once,
* otherwise it will throw an exception..
*
* Tracer must be thread safe (as most loggers are thread safe).
*/
void init_logger(void (*tracer)(SOCKET_MANAGER_C_API_LogData),
SOCKET_MANAGER_C_API_TraceLevel tracer_max_level,
SOCKET_MANAGER_C_API_TraceLevel log_print_level);

/**
* @brief Manages a set of sockets.
*
Expand Down
71 changes: 71 additions & 0 deletions include/socket_manager_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,48 @@ enum class SOCKET_MANAGER_C_API_ConnStateCode {
ConnectError = 3,
};

/**
* Trace Level
*/
enum class SOCKET_MANAGER_C_API_TraceLevel {
/**
* The "trace" level.
*
* Designates very low priority, often extremely verbose, information.
*/
Trace = 0,
/**
* The "debug" level.
*
* Designates lower priority information.
*/
Debug = 1,
/**
* The "info" level.
*
* Designates useful information.
*/
Info = 2,
/**
* The "warn" level.
*
* Designates hazardous situations.
*/
Warn = 3,
/**
* The "error" level.
*
* Designates very serious errors.
*/
Error = 4,
/**
* Turn off all levels.
*
* Disable log output.
*/
Off = 5,
};

struct SOCKET_MANAGER_C_API_Connection;

/**
Expand Down Expand Up @@ -158,6 +200,22 @@ struct SOCKET_MANAGER_C_API_ConnMsg {
size_t Len;
};

/**
* Log Data
*/
struct SOCKET_MANAGER_C_API_LogData {
SOCKET_MANAGER_C_API_TraceLevel Level;
const char *Target;
size_t TargetN;
const char *File;
size_t FileN;
/**
* The `message` pointer is only valid for the duration of the callback.
*/
const char *Message;
size_t MessageN;
};

extern "C" {

/**
Expand Down Expand Up @@ -463,6 +521,19 @@ int socket_manager_join(SOCKET_MANAGER_C_API_SocketManager *manager, char **err)
*/
void socket_manager_free(SOCKET_MANAGER_C_API_SocketManager *manager);

/**
* Init logger.
*
* # Arguments
* - `tracer`: The tracer object.
* - `tracer_max_level`: The max level of the tracer.
* - `log_print_level`: The level of the log to print.
*/
void socket_manager_logger_init(void (*tracer)(SOCKET_MANAGER_C_API_LogData),
SOCKET_MANAGER_C_API_TraceLevel tracer_max_level,
SOCKET_MANAGER_C_API_TraceLevel log_print_level,
char **err);

} // extern "C"

#endif // SOCKET_MANAGER_C_API_H
3 changes: 1 addition & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ dev-docker:
docker build -f ./dockerfile/dev-containers/jammy/Dockerfile -t congyuwang/socket-manager-dev:jammy .

test:
cd build && SOCKET_LOG=debug ctest --output-on-failure && cd ..

cd build && ctest --output-on-failure && cd ..
time:
/usr/bin/time -l -h -p ./build/tests/CommonCxxTests test_transfer_data_large
/usr/bin/time -l -h -p ./build/tests/CommonCxxTests test_transfer_data_large_async
Expand Down
23 changes: 23 additions & 0 deletions socket_manager/socket_manager.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
#include "socket_manager/socket_manager.h"
#include "socket_manager_c_api.h"
#include <string_view>

namespace socket_manager {

LogData from_c_log_data(SOCKET_MANAGER_C_API_LogData log_data) {
return {
log_data.Level,
std::string_view(log_data.Target, log_data.TargetN),
std::string_view(log_data.File, log_data.FileN),
std::string_view(log_data.Message, log_data.MessageN),
};
}

void init_logger(void (*tracer)(SOCKET_MANAGER_C_API_LogData),
SOCKET_MANAGER_C_API_TraceLevel tracer_max_level,
SOCKET_MANAGER_C_API_TraceLevel log_print_level) {
char *err = nullptr;
socket_manager_logger_init(tracer, tracer_max_level, log_print_level, &err);
if (err != nullptr) {
const std::string err_str(err);
free(err);
throw std::runtime_error(err_str);
}
}

SocketManager::SocketManager(const std::shared_ptr<ConnCallback> &conn_cb,
size_t n_threads)
: conn_cb(conn_cb) {
Expand Down
1 change: 1 addition & 0 deletions src/c_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ mod msg_sender;
pub(crate) mod on_conn;
pub(crate) mod on_msg;
mod socket_manager;
pub(crate) mod tracer;
mod utils;
144 changes: 144 additions & 0 deletions src/c_api/tracer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! Define a layer to pass log message to foreign interface.
use super::utils::write_error_c_str;
use crate::init_logger;
use libc::size_t;
use std::{ffi::c_char, fmt, ptr::null_mut};
use tracing::{field::Field, Level};
use tracing_subscriber::{filter::LevelFilter, Layer};

const MESSAGE: &str = "message";
const EMPTY: &str = "";

/// Trace Level
#[repr(C)]
pub enum TraceLevel {
/// The "trace" level.
///
/// Designates very low priority, often extremely verbose, information.
Trace = 0,
/// The "debug" level.
///
/// Designates lower priority information.
Debug = 1,
/// The "info" level.
///
/// Designates useful information.
Info = 2,
/// The "warn" level.
///
/// Designates hazardous situations.
Warn = 3,
/// The "error" level.
///
/// Designates very serious errors.
Error = 4,
/// Turn off all levels.
///
/// Disable log output.
Off = 5,
}

/// Log Data
#[repr(C)]
pub struct LogData {
pub level: TraceLevel,
pub target: *const c_char,
pub target_n: size_t,
pub file: *const c_char,
pub file_n: size_t,
/// The `message` pointer is only valid for the duration of the callback.
pub message: *const c_char,
pub message_n: size_t,
}

/// Init logger.
///
/// # Arguments
/// - `tracer`: The tracer object.
/// - `tracer_max_level`: The max level of the tracer.
/// - `log_print_level`: The level of the log to print.
#[no_mangle]
pub unsafe extern "C" fn socket_manager_logger_init(
tracer: unsafe extern "C" fn(LogData) -> (),
tracer_max_level: TraceLevel,
log_print_level: TraceLevel,
err: *mut *mut c_char,
) {
let foreign_logger = ForeignLogger(tracer).with_filter(tracer_max_level.into());
match init_logger(log_print_level.into(), foreign_logger) {
Ok(_) => *err = null_mut(),
Err(e) => write_error_c_str(e, err),
}
}

pub struct ForeignLogger(unsafe extern "C" fn(LogData) -> ());

impl<S> Layer<S> for ForeignLogger
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut get_msg = GetMsgVisitor(None);
event.record(&mut get_msg);
let file = if let (Some(f), Some(l)) = (event.metadata().file(), event.metadata().line()) {
format!("{}:{}", f, l)
} else {
String::new()
};
let data = LogData {
level: event.metadata().level().into(),
target: event.metadata().target().as_ptr() as *const c_char,
target_n: event.metadata().target().len(),
file: file.as_ptr() as *const c_char,
file_n: file.len(),
message: get_msg
.0
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(EMPTY.as_ptr()) as *const c_char,
message_n: get_msg.0.as_ref().map(|s| s.len()).unwrap_or(0),
};
unsafe { self.0(data) }
}
}

// Helper methods and structs.

struct GetMsgVisitor(Option<String>);

impl tracing::field::Visit for GetMsgVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
if field.name() == MESSAGE {
self.0 = Some(format!("{:?}", value));
}
}
}

impl Into<LevelFilter> for TraceLevel {
fn into(self) -> LevelFilter {
match self {
TraceLevel::Trace => LevelFilter::TRACE,
TraceLevel::Debug => LevelFilter::DEBUG,
TraceLevel::Info => LevelFilter::INFO,
TraceLevel::Warn => LevelFilter::WARN,
TraceLevel::Error => LevelFilter::ERROR,
TraceLevel::Off => LevelFilter::OFF,
}
}
}

impl From<&Level> for TraceLevel {
fn from(value: &Level) -> Self {
match *value {
Level::TRACE => TraceLevel::Trace,
Level::DEBUG => TraceLevel::Debug,
Level::INFO => TraceLevel::Info,
Level::WARN => TraceLevel::Warn,
Level::ERROR => TraceLevel::Error,
}
}
}
Loading
Loading