diff --git a/build.sh b/build.sh index 52a5081b2..13ce26a00 100755 --- a/build.sh +++ b/build.sh @@ -98,7 +98,7 @@ fi # Evaluate switches LINK_TYPE= -CMAKE_OPTS="${CMAKE_OPTS:--DBUILD_SHARED_LIBS=OFF}" +CMAKE_OPTS="${CMAKE_OPTS:-DBUILD_SHARED_LIBS=OFF}" while getopts "h?vl:D:" opt; do case "$opt" in h|\?) usage diff --git a/clean-repo.cmd b/clean-repo.cmd new file mode 100644 index 000000000..7b67987c2 --- /dev/null +++ b/clean-repo.cmd @@ -0,0 +1 @@ +git clean -xd -f \ No newline at end of file diff --git a/wrappers/rust/.gitignore b/wrappers/rust/.gitignore new file mode 100644 index 000000000..4b89d686a --- /dev/null +++ b/wrappers/rust/.gitignore @@ -0,0 +1,25 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +out/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb +*.exe +*.exe +*.o +*.lib +*.so +*.dll + +# Need to rename this to something better ... +lib/ +include/ \ No newline at end of file diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml new file mode 100644 index 000000000..336687a2d --- /dev/null +++ b/wrappers/rust/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +resolver = "2" + +members = ["cpp-client-telemetry-sys", "oneds-telemetry", "telemetry-sample"] diff --git a/wrappers/rust/build.ps1 b/wrappers/rust/build.ps1 new file mode 100644 index 000000000..e5f91e9eb --- /dev/null +++ b/wrappers/rust/build.ps1 @@ -0,0 +1,13 @@ +# @echo off + +# set VSTOOLS_VERSION=vs2019 +# cd %~dp0 + +# call ..\..\tools\vcvars.cmd +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#deploy-dll.cmd +#generate-bindings.cmd + +cargo run \ No newline at end of file diff --git a/wrappers/rust/cpp-client-telemetry-sys/Cargo.toml b/wrappers/rust/cpp-client-telemetry-sys/Cargo.toml new file mode 100644 index 000000000..b8c68892d --- /dev/null +++ b/wrappers/rust/cpp-client-telemetry-sys/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cpp-client-telemetry-sys" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[build-dependencies] +bindgen = { version = "0.65.1", features = ["experimental"] } +subprocess = "0.2.9" diff --git a/wrappers/rust/cpp-client-telemetry-sys/build.rs b/wrappers/rust/cpp-client-telemetry-sys/build.rs new file mode 100644 index 000000000..0d244ebe6 --- /dev/null +++ b/wrappers/rust/cpp-client-telemetry-sys/build.rs @@ -0,0 +1,76 @@ +use std::env; +use std::fs; +use std::path::PathBuf; +use subprocess::Exec; + +static PROJECT_ROOT: &str = "../../../"; + +fn copy_static_libs() { + // TODO add compatibility for x86 and ARM + let cpp_project_out = PathBuf::from(PROJECT_ROOT).join("Solutions/out/Release/x64"); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + std::fs::copy(cpp_project_out.join("win32-lib/ClientTelemetry.lib"), PathBuf::from(&out_dir).join("ClientTelemetry.lib")) + .expect("Failed to copy ClientTelemetry lib"); + std::fs::copy(cpp_project_out.join("sqlite/sqlite.lib"), out_dir.join("sqlite.lib")) + .expect("Failed to copy sqlite native library"); + std::fs::copy(cpp_project_out.join("zlib/zlib.lib"), out_dir.join("zlib.lib")) + .expect("Failed to copy zlib native library"); + + // Tell cargo to look for shared libraries in the specified directory + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rustc-link-lib=ClientTelemetry"); + println!("cargo:rustc-link-lib=wininet"); + println!("cargo:rustc-link-lib=crypt32"); + println!("cargo:rustc-link-lib=sqlite"); + println!("cargo:rustc-link-lib=zlib"); +} + +fn write_bindings() { + // Precompile header with the appropriate preprocessor options + let mat_h_location = PathBuf::from(PROJECT_ROOT).join("lib/include/public/mat.h"); + println!("cargo:rerun-if-changed={}", mat_h_location.to_string_lossy()); + + // TODO use clang crate instead of invoking CLI directly + let header_out = Exec::cmd("clang") + .arg("-E") + .arg(mat_h_location) + .arg("-D") + .arg("MATSDK_STATIC_LIB=1") + .capture() + .expect("Failed to open clang process") + .stdout_str(); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let mat_out_path = out_dir.join("mat.out.h"); + + fs::write(&mat_out_path, header_out).unwrap(); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .rust_target(bindgen::RustTarget::Stable_1_68) + .header(PathBuf::from(out_dir).join("mat.out.h").to_string_lossy()) + .allowlist_type("evt_.*") + .allowlist_function("evt_.*") + .allowlist_var("evt_.*") + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +fn main() { + write_bindings(); + copy_static_libs(); +} diff --git a/wrappers/rust/cpp-client-telemetry-sys/src/lib.rs b/wrappers/rust/cpp-client-telemetry-sys/src/lib.rs new file mode 100644 index 000000000..7f8dcb4d6 --- /dev/null +++ b/wrappers/rust/cpp-client-telemetry-sys/src/lib.rs @@ -0,0 +1,90 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +use std::ffi::*; + +fn evt_api_call_wrapper(evt_context: Box) -> (evt_status_t, Box) { + let raw_pointer = Box::into_raw(evt_context); + + let mut result: evt_status_t = -1; + unsafe { + result = evt_api_call_default(raw_pointer); + } + + let out_context = unsafe { Box::from_raw(raw_pointer) }; + (result, out_context) +} + +pub fn evt_open(config: &CString) -> Option { + let config_bytes = config.to_bytes_with_nul().to_vec(); + + let context: Box = Box::new(evt_context_t { + call: evt_call_t_EVT_OP_OPEN, + handle: 0, + data: config_bytes.as_ptr() as *mut c_void, + result: 0, + size: 0, + }); + + let (result, context) = evt_api_call_wrapper(context); + + if result == -1 { + return Option::None; + } + + return Some(context.handle.clone()); +} + +pub fn evt_close(handle: &evt_handle_t) -> evt_status_t { + let context: Box = Box::new(evt_context_t { + call: evt_call_t_EVT_OP_CLOSE, + handle: *handle, + data: std::ptr::null_mut(), + result: 0, + size: 0, + }); + + let (result, _) = evt_api_call_wrapper(context); + result +} + +pub fn evt_upload(handle: &evt_handle_t) -> evt_status_t { + let context: Box = Box::new(evt_context_t { + call: evt_call_t_EVT_OP_UPLOAD, + handle: *handle, + data: std::ptr::null_mut(), + result: 0, + size: 0, + }); + + evt_api_call_wrapper(context).0 +} + +pub fn evt_flush(handle: &evt_handle_t) -> evt_status_t { + let context: Box = Box::new(evt_context_t { + call: evt_call_t_EVT_OP_FLUSH, + handle: *handle, + data: std::ptr::null_mut(), + result: 0, + size: 0, + }); + + evt_api_call_wrapper(context).0 +} + +pub fn evt_log(handle: &evt_handle_t, data: &mut [evt_prop]) -> evt_status_t { + let data_len = data.len() as u32; + let data_pointer = data.as_mut_ptr() as *mut c_void; + + let context: Box = Box::new(evt_context_t { + call: evt_call_t_EVT_OP_LOG, + handle: *handle, + data: data_pointer, + result: 0, + size: data_len, + }); + + evt_api_call_wrapper(context).0 +} diff --git a/wrappers/rust/cpp-client-telemetry-sys/tests/sdk_tests.rs b/wrappers/rust/cpp-client-telemetry-sys/tests/sdk_tests.rs new file mode 100644 index 000000000..d4d3aeaeb --- /dev/null +++ b/wrappers/rust/cpp-client-telemetry-sys/tests/sdk_tests.rs @@ -0,0 +1,25 @@ +use cpp_client_telemetry_sys::*; +use std::ffi::CString; + +#[test] +fn test_open_close() { + // Simple sanity check to ensure the SDK was linked correctly. + let config = r#"{ + "eventCollectorUri": "http://localhost:64099/OneCollector/track", + "cacheFilePath":"hackathon_storage.db", + "config":{"host": "*"}, + "name":"Rust-API-Client-0", + "version":"1.0.0", + "primaryToken":"99999999999999999999999999999999-99999999-9999-9999-9999-999999999999-9999", + "maxTeardownUploadTimeInSec":5, + "hostMode":false, + "traceLevelMask": 4294967295, + "minimumTraceLevel":0, + "sdkmode":0, + "compat": {"customTypePrefix": "compat_event"} + }"#; + + let handle = evt_open(&CString::new(config).unwrap()).expect("Failed to get SDK handle"); + + assert_eq!(evt_close(&handle), 0); +} \ No newline at end of file diff --git a/wrappers/rust/deploy-dll.cmd b/wrappers/rust/deploy-dll.cmd new file mode 100644 index 000000000..30294fb08 --- /dev/null +++ b/wrappers/rust/deploy-dll.cmd @@ -0,0 +1,13 @@ +@echo off +set PROJECT_DIR=%~dp0 + +@mkdir %PROJECT_DIR%\include +copy %PROJECT_DIR%..\..\lib\include\public\mat.h %PROJECT_DIR%\include +copy %PROJECT_DIR%..\..\lib\include\public\Version.h %PROJECT_DIR%\include +copy %PROJECT_DIR%..\..\lib\include\public\ctmacros.hpp %PROJECT_DIR%\include + +@mkdir %PROJECT_DIR%\lib\%1\%2 +copy %PROJECT_DIR%..\..\Solutions\out\%1\%2\win32-dll\*.lib %PROJECT_DIR%\lib\%1\%2 +copy %PROJECT_DIR%..\..\Solutions\out\Release\x64\lib\Release\*.lib %PROJECT_DIR%\lib\%1\%2 + +exit /b 0 \ No newline at end of file diff --git a/wrappers/rust/oneds-telemetry/Cargo.toml b/wrappers/rust/oneds-telemetry/Cargo.toml new file mode 100644 index 000000000..a65e6eb59 --- /dev/null +++ b/wrappers/rust/oneds-telemetry/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "oneds-telemetry" +version = "0.1.0" +edition = "2021" +publish = false +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.30" } +log = { version = "0.4.20" } +once_cell = "1.12.0" +cpp-client-telemetry-sys = { path = "../cpp-client-telemetry-sys" } + +[dev-dependencies] + +[build-dependencies] +bindgen = { version = "0.65.1", features = ["experimental"] } diff --git a/wrappers/rust/oneds-telemetry/src/appender.rs b/wrappers/rust/oneds-telemetry/src/appender.rs new file mode 100644 index 000000000..81cf83993 --- /dev/null +++ b/wrappers/rust/oneds-telemetry/src/appender.rs @@ -0,0 +1,58 @@ +use log::{Level, Metadata, Record}; + +use crate::log_manager_provider; + +pub static LOGGER: TelemetryCollectorLogBridge = TelemetryCollectorLogBridge; +pub static mut CONSOLE_ENABLED: bool = true; +pub static mut COLLECTOR_ENABLED: bool = true; + +pub struct TelemetryCollectorLogBridge; + +impl log::Log for TelemetryCollectorLogBridge { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Debug + } + + fn log(&self, record: &Record) { + if unsafe { CONSOLE_ENABLED } && self.enabled(record.metadata()) { + let utc_now = chrono::Utc::now(); + let nanos = utc_now.timestamp_subsec_nanos(); + + println!( + "[{} {}.{}] {} <{}> - {}: {}", + utc_now.date_naive().to_string(), + utc_now.time().format("%H:%M:%S"), + nanos, + record.target(), + record.module_path().unwrap(), + record.level(), + record.args() + ); + } + + if unsafe { COLLECTOR_ENABLED } + // Default + && record.target() != "oneds_telemetry" + // Items from deeper in the `oneds_telemetry` crate + && !record.target().starts_with("oneds_telemetry::") + { + log_manager_provider().trace(format!("{}", record.args()).as_str()); + } + } + + fn flush(&self) { + if unsafe { COLLECTOR_ENABLED } { + log_manager_provider().flush(false); + } + } +} + +// fn map_severity_to_otel_severity(level: Level) -> Severity { +// match level { +// Level::Error => Severity::Error, +// Level::Warn => Severity::Warn, +// Level::Info => Severity::Info, +// Level::Debug => Severity::Debug, +// Level::Trace => Severity::Trace, +// } +// } diff --git a/wrappers/rust/oneds-telemetry/src/conversion.rs b/wrappers/rust/oneds-telemetry/src/conversion.rs new file mode 100644 index 000000000..ed23ac143 --- /dev/null +++ b/wrappers/rust/oneds-telemetry/src/conversion.rs @@ -0,0 +1,49 @@ +use crate::*; +use std::ffi::{CStr, CString}; +use std::mem; +use std::os::raw::c_char; + +/** + * This will leak memory + */ +pub fn to_leaky_c_str(value: &str) -> *const i8 { + let c_str = Box::new(CString::new(value.clone()).unwrap()); + let ptr = c_str.as_ptr() as *const i8; + mem::forget(c_str); + return ptr; +} + +pub fn to_c_str(value: &str) -> *const c_char { + let null_terminated_str = format!("{}\0", value); + let c_str = CStr::from_bytes_with_nul(null_terminated_str.as_bytes()) + .expect("to_c_str: CString conversion failed"); + c_str.as_ptr() +} + +pub fn string_prop(name: &str, value: &str, is_pii: bool) -> evt_prop { + let mut pii_kind = 0; + + if is_pii { + pii_kind = 1; + } + + evt_prop { + name: to_leaky_c_str(name), + type_: evt_prop_t_TYPE_STRING, + value: evt_prop_v { + as_string: to_leaky_c_str(value), + }, + piiKind: pii_kind, + } +} + +pub fn int_prop(name: &str, value: i64) -> evt_prop { + evt_prop { + name: to_c_str(name), + type_: evt_prop_t_TYPE_INT64, + value: evt_prop_v { + as_int64: value.clone(), + }, + piiKind: 0, + } +} diff --git a/wrappers/rust/oneds-telemetry/src/lib.rs b/wrappers/rust/oneds-telemetry/src/lib.rs new file mode 100644 index 000000000..662569902 --- /dev/null +++ b/wrappers/rust/oneds-telemetry/src/lib.rs @@ -0,0 +1,161 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use log::debug; +use once_cell::sync::Lazy; +use std::{sync::RwLock, ffi::CString}; +use types::{StringProperty, TelemetryItem, TelemetryProperty}; +use cpp_client_telemetry_sys::{ + evt_open, + evt_close, + evt_log, + evt_flush, + evt_upload, + evt_prop +}; + +pub mod appender; +pub mod types; + +#[derive(Clone)] +pub struct LogManager { + is_started: bool, + configuration: CString, + log_handle: Option, +} + +/// The global `Tracer` provider singleton. +static GLOBAL_LOG_MANAGER: Lazy> = Lazy::new(|| RwLock::new(LogManager::new())); + +pub fn log_manager_provider() -> LogManager { + GLOBAL_LOG_MANAGER + .read() + .expect("GLOBAL_LOG_MANAGER RwLock poisoned") + .clone() +} + +pub fn init(config: &str) { + let mut log_manager = GLOBAL_LOG_MANAGER + .write() + .expect("GLOBAL_LOG_MANAGER RwLock poisoned"); + + if log_manager.is_started == false { + log_manager.configure(config); + log_manager.start(); + } +} + +pub fn flush() { + let mut log_manager = GLOBAL_LOG_MANAGER + .write() + .expect("GLOBAL_LOG_MANAGER RwLock poisoned"); + + log_manager.flush(true); +} + +pub fn offline_flush() { + let mut log_manager = GLOBAL_LOG_MANAGER + .write() + .expect("GLOBAL_LOG_MANAGER RwLock poisoned"); + + log_manager.flush(false); +} + +impl LogManager { + fn new() -> Self { + LogManager { + is_started: false, + configuration: CString::new("{}").unwrap(), + log_handle: Option::None, + } + } + + /** + * Validates that a configuration has been set and + */ + pub fn start(&mut self) -> bool { + if self.is_started { + return true; + } + + let handle = evt_open(&self.configuration); + + if handle.is_none() { + debug!("LogManager.start: failed"); + return false; + } + + self.log_handle = handle; + self.is_started = true; + + debug!("LogManager.start: success"); + + return true; + } + + pub fn flush(&mut self, upload: bool) { + if self.is_started == false { + return; + } + + let handle = self.log_handle.as_ref().unwrap(); + let status = evt_flush(handle); + debug!("LogManager.flush(EVT_OP_FLUSH) returned: {}", status); + + if upload { + let status = evt_upload(handle); + debug!("LogManager.flush(EVT_OP_UPLOAD) returned: {}", status); + } + } + + pub fn stop(&mut self) { + if self.is_started == false { + return; + } + + self.flush(false); + + let handle = self.log_handle.as_ref().unwrap(); + evt_close(handle); + } + + pub fn configure(&mut self, config: &str) { + self.configuration = CString::new(config).unwrap(); + debug!("LogManager.configure:\n{}", config); + } + + pub fn trace(&mut self, message: &str) { + let mut item = TelemetryItem::new("trace"); + + item.add_property( + "message", + TelemetryProperty::String(StringProperty::new(message)), + ); + + self.track_item(&item); + } + + pub fn track_item(&mut self, item: &TelemetryItem) { + let mut event_props = item.to_evt(); + + debug!( + "LogManager.track_item(event_props_len:{})", + event_props.len() + ); + + let handle = self.log_handle.clone().unwrap(); + + evt_log(&handle, &mut event_props); + } + + pub fn track_evt(&self, mut event_props: &mut [evt_prop]) { + debug!( + "LogManager.track_evt(event_props_len:{})", + event_props.len() + ); + + let handle = self.log_handle.clone().unwrap(); + evt_log(&handle, &mut event_props); + } +} diff --git a/wrappers/rust/oneds-telemetry/src/types.rs b/wrappers/rust/oneds-telemetry/src/types.rs new file mode 100644 index 000000000..1cbcc0fb1 --- /dev/null +++ b/wrappers/rust/oneds-telemetry/src/types.rs @@ -0,0 +1,182 @@ +use std::{ + collections::HashMap, + ffi::{CStr, CString}, + os::raw::c_char, +}; + +use chrono::{DateTime, Utc}; +use log::{debug, error}; +use once_cell::sync::Lazy; + +use cpp_client_telemetry_sys::{evt_prop, evt_prop_v, evt_prop_t_TYPE_STRING, evt_prop_t_TYPE_TIME, evt_prop_t_TYPE_INT64}; + +#[derive(Clone, Debug)] +pub struct StringProperty { + bytes: Vec, +} + +impl StringProperty { + pub fn new(value: &str) -> Self { + let bytes = CString::new(value).expect("Unable to convert value to CString"); + + StringProperty { + bytes: bytes.to_bytes_with_nul().to_vec(), + } + } + + pub fn as_ptr(&self) -> *const c_char { + self.bytes.as_ptr() as *const c_char + } + + pub fn as_bytes(&self) -> Vec { + self.bytes.clone() + } + + fn to_str(&self) -> Result<&str, std::str::Utf8Error> { + unsafe { CStr::from_ptr(self.as_ptr()).to_str() } + } +} + +pub static NAME_PROPERTY: Lazy = Lazy::new(|| StringProperty::new("name")); +pub static TIME_PROPERTY: Lazy = Lazy::new(|| StringProperty::new("time")); +pub static EMPTY_STRING: Lazy = Lazy::new(|| StringProperty::new("")); + +#[derive(Clone, Debug)] +pub enum TelemetryProperty { + String(StringProperty), + Int(i64), +} + +pub type TelemetryPropertyBag = HashMap<&'static str, TelemetryProperty>; + +#[derive(Clone, Debug)] +pub struct TelemetryItem { + name: StringProperty, + data: TelemetryPropertyBag, + time: Option>, +} + +impl TelemetryItem { + pub fn new(name: &'static str) -> TelemetryItem { + TelemetryItem { + name: StringProperty::new(name), + data: TelemetryPropertyBag::new(), + time: Option::None, + } + } + + pub fn new_with_time(name: &'static str, time: DateTime) -> TelemetryItem { + TelemetryItem { + name: StringProperty::new(name), + data: TelemetryPropertyBag::new(), + time: Some(time), + } + } + + pub fn add_property(&mut self, name: &'static str, value: TelemetryProperty) { + self.data.insert(name, value); + } + + pub fn to_evt(&self) -> Box> { + let mut properties = Box::new(Vec::new()); + + properties.push(evt_prop { + name: NAME_PROPERTY.as_ptr(), + type_: evt_prop_t_TYPE_STRING, + value: evt_prop_v { + as_string: self.name.as_ptr(), + }, + piiKind: 0, + }); + + if self.time != Option::None { + // Convert UTC time to a Unix timestamp as i64 + let timestamp_i64 = self.time.unwrap().timestamp(); + + // Convert the i64 timestamp to u64 + let millis = timestamp_i64 as u64; + + properties.push(evt_prop { + name: TIME_PROPERTY.as_ptr(), + type_: evt_prop_t_TYPE_TIME, + value: evt_prop_v { as_time: millis }, + piiKind: 0, + }); + } + + for key in self.data.keys() { + let value = self.data.get(key).expect("Unable to get value"); + match value { + TelemetryProperty::String(value) => { + properties.push(evt_prop { + name: StringProperty::new(key).as_ptr(), + type_: evt_prop_t_TYPE_STRING, + value: evt_prop_v { + as_string: value.as_ptr() + }, + piiKind: 0 + }) + } + TelemetryProperty::Int(value) => { + properties.push(evt_prop { + name: StringProperty::new(key).as_ptr(), + type_: evt_prop_t_TYPE_INT64, + value: evt_prop_v { + as_int64: value.clone() + }, + piiKind: 0 + }) + } + } + } + + debug!("{}", self.name.to_str().unwrap()); + + properties.shrink_to_fit(); + + properties + } +} + +// // This is new +// impl Drop for TelemetryItem { +// fn drop(&mut self) { +// debug!( +// "[TelemetryItem] Dropping Item: {}", +// self.name.to_str().unwrap() +// ); +// } +// } + +#[cfg(test)] +mod tests { + use crate::types::{StringProperty, EMPTY_STRING, NAME_PROPERTY, TIME_PROPERTY}; + + #[test] + fn validate_c_str_arrays() { + let mut iteration = 0; + + while iteration < 5 { + iteration = iteration + 1; + + let value1 = StringProperty::new("TEST_VALUE1"); + let value2 = StringProperty::new("TEST_VALUE2"); + println!("{:?}", value1.as_bytes()); + assert_eq!("TEST_VALUE1", value1.to_str().unwrap()); + assert_eq!("TEST_VALUE2", value2.to_str().unwrap()); + } + } + + #[test] + fn validate_static_props() { + let mut iteration = 0; + + while iteration < 5 { + iteration = iteration + 1; + + assert_eq!("name", NAME_PROPERTY.to_str().unwrap()); + assert_eq!("time", TIME_PROPERTY.to_str().unwrap()); + assert_eq!("", EMPTY_STRING.to_str().unwrap()); + } + } +} diff --git a/wrappers/rust/telemetry-sample/Cargo.toml b/wrappers/rust/telemetry-sample/Cargo.toml new file mode 100644 index 000000000..e91e63269 --- /dev/null +++ b/wrappers/rust/telemetry-sample/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "telemetry-sample" +version = "0.1.0" +edition = "2021" +publish = false +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +oneds-telemetry = { path = "../oneds-telemetry" } +log = { version = "0.4.20" } +ctrlc = "3.4.0" + +[dev-dependencies] diff --git a/wrappers/rust/telemetry-sample/src/main.rs b/wrappers/rust/telemetry-sample/src/main.rs new file mode 100644 index 000000000..9e86b8e6f --- /dev/null +++ b/wrappers/rust/telemetry-sample/src/main.rs @@ -0,0 +1,48 @@ +// This is a comment, and is ignored by the compiler. +// You can test this code by clicking the "Run" button over there -> +// or if you prefer to use your keyboard, you can use the "Ctrl + Enter" +// shortcut. + +// This code is editable, feel free to hack it! +// You can always return to the original code by clicking the "Reset" button -> + +// This is the main function. + +use log::{error, Level}; +use std::{thread, time::Duration}; + +pub const API_KEY: &str = + "99999999999999999999999999999999-99999999-9999-9999-9999-999999999999-9999"; + +fn main() { + // Setup Log Appender for the log crate. + log::set_logger(&oneds_telemetry::appender::LOGGER).unwrap(); + log::set_max_level(Level::Debug.to_level_filter()); + + let config = r#"{ + "eventCollectorUri": "http://localhost:64099/OneCollector/track", + "cacheFilePath":"hackathon_storage.db", + "config":{"host": "*"}, + "name":"Rust-API-Client-0", + "version":"1.0.0", + "primaryToken":"99999999999999999999999999999999-99999999-9999-9999-9999-999999999999-9999", + "maxTeardownUploadTimeInSec":5, + "hostMode":false, + "traceLevelMask": 4294967295, + "minimumTraceLevel":0, + "sdkmode":0, + "compat": {"customTypePrefix": "compat_event"} + }"#; + + oneds_telemetry::init(config); + + loop { + error!(target: "app_events", "App Error: {}, Port: {}", "Connection Error", 22); + + oneds_telemetry::flush(); + + thread::sleep(Duration::from_secs(3)); + } + + // telemetry::shutdown(); +}