diff --git a/Cargo.lock b/Cargo.lock index a2c0d8f5..9b0756a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5259,12 +5259,17 @@ dependencies = [ "backtrace", "ffi-support", "futures", + "hostname", "jni", "lazy_static", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", "parking_lot 0.12.1", "serde 1.0.137", "serde_json", "tracing", + "tracing-opentelemetry", "tracing-subscriber", "veilid-core", ] diff --git a/setup_macos.sh b/setup_macos.sh index 83d9638b..38777903 100755 --- a/setup_macos.sh +++ b/setup_macos.sh @@ -50,5 +50,5 @@ if [ "$BREW_USER" == "" ]; then BREW_USER=`whoami` fi fi -sudo -H -u $BREW_USER brew install capnp +sudo -H -u $BREW_USER brew install capnp cmake diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index e81e9be5..6f3ccb8c 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -203,6 +203,16 @@ impl VeilidConfigLogLevel { Self::Trace => Some(VeilidLogLevel::Trace), } } + pub fn to_tracing_level_filter(&self) -> level_filters::LevelFilter { + match self { + Self::Off => level_filters::LevelFilter::OFF, + Self::Error => level_filters::LevelFilter::ERROR, + Self::Warn => level_filters::LevelFilter::WARN, + Self::Info => level_filters::LevelFilter::INFO, + Self::Debug => level_filters::LevelFilter::DEBUG, + Self::Trace => level_filters::LevelFilter::TRACE, + } + } pub fn from_veilid_log_level(level: Option) -> Self { match level { None => Self::Off, diff --git a/veilid-flutter/example/lib/config.dart b/veilid-flutter/example/lib/config.dart index 6fea6365..2267e7a5 100644 --- a/veilid-flutter/example/lib/config.dart +++ b/veilid-flutter/example/lib/config.dart @@ -77,9 +77,9 @@ Future getDefaultVeilidConfig() async { setValueTimeoutMs: null, setValueCount: 20, setValueFanout: 5, - minPeerCount: 20, + minPeerCount: 1, //20, minPeerRefreshTimeMs: 2000, - validateDialInfoReceiptTimeMs: 5000, + validateDialInfoReceiptTimeMs: 2000, ), upnp: true, natpmp: true, diff --git a/veilid-flutter/example/lib/main.dart b/veilid-flutter/example/lib/main.dart index fb0758d3..a36456f6 100644 --- a/veilid-flutter/example/lib/main.dart +++ b/veilid-flutter/example/lib/main.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:typed_data'; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:veilid/veilid.dart'; import 'package:flutter_loggy/flutter_loggy.dart'; import 'package:loggy/loggy.dart'; @@ -74,6 +76,29 @@ void main() { WidgetsFlutterBinding.ensureInitialized(); initLoggy(); + if (kIsWeb) { + var platformConfig = VeilidWASMConfig( + logging: VeilidWASMConfigLogging( + performance: VeilidWASMConfigLoggingPerformance( + enabled: true, + level: VeilidLogLevel.trace, + logsInTimings: true, + logsInConsole: false))); + Veilid.instance.configureVeilidPlatform(platformConfig.json); + } else { + var platformConfig = VeilidFFIConfig( + logging: VeilidFFIConfigLogging( + terminal: VeilidFFIConfigLoggingTerminal( + enabled: false, + level: VeilidLogLevel.trace, + ), + otlp: VeilidFFIConfigLoggingOtlp( + enabled: false, + level: VeilidLogLevel.trace, + grpcEndpoint: "localhost:4317", + serviceName: "VeilidExample"))); + Veilid.instance.configureVeilidPlatform(platformConfig.json); + } runApp(MaterialApp( title: 'Veilid Plugin Demo', @@ -100,6 +125,7 @@ class _MyAppState extends State with UiLoggy { @override void initState() { super.initState(); + setRootLogLevel(LogLevel.info); initPlatformState(); } diff --git a/veilid-flutter/lib/veilid.dart b/veilid-flutter/lib/veilid.dart index 4361c7cf..682492dc 100644 --- a/veilid-flutter/lib/veilid.dart +++ b/veilid-flutter/lib/veilid.dart @@ -8,8 +8,162 @@ import 'veilid_stub.dart' ////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// +// FFI Platform-specific config + +class VeilidFFIConfigLoggingTerminal { + bool enabled; + VeilidLogLevel level; + + VeilidFFIConfigLoggingTerminal({ + required this.enabled, + required this.level, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + }; + } + + VeilidFFIConfigLoggingTerminal.fromJson(Map json) + : enabled = json['enabled'], + level = veilidLogLevelFromJson(json['level']); +} + +class VeilidFFIConfigLoggingOtlp { + bool enabled; + VeilidLogLevel level; + String grpcEndpoint; + String serviceName; + + VeilidFFIConfigLoggingOtlp({ + required this.enabled, + required this.level, + required this.grpcEndpoint, + required this.serviceName, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + 'grpc_endpoint': grpcEndpoint, + 'service_name': serviceName, + }; + } + + VeilidFFIConfigLoggingOtlp.fromJson(Map json) + : enabled = json['enabled'], + level = veilidLogLevelFromJson(json['level']), + grpcEndpoint = json['grpc_endpoint'], + serviceName = json['service_name']; +} + +class VeilidFFIConfigLogging { + VeilidFFIConfigLoggingTerminal terminal; + VeilidFFIConfigLoggingOtlp otlp; + + VeilidFFIConfigLogging({required this.terminal, required this.otlp}); + + Map get json { + return { + 'terminal': terminal.json, + 'otlp': otlp.json, + }; + } + + VeilidFFIConfigLogging.fromJson(Map json) + : terminal = VeilidFFIConfigLoggingTerminal.fromJson(json['terminal']), + otlp = VeilidFFIConfigLoggingOtlp.fromJson(json['otlp']); +} + +class VeilidFFIConfig { + VeilidFFIConfigLogging logging; + + VeilidFFIConfig({ + required this.logging, + }); + + Map get json { + return { + 'logging': logging.json, + }; + } + + VeilidFFIConfig.fromJson(Map json) + : logging = VeilidFFIConfigLogging.fromJson(json['logging']); +} + +////////////////////////////////////////////////////////// +// WASM Platform-specific config + +class VeilidWASMConfigLoggingPerformance { + bool enabled; + VeilidLogLevel level; + bool logsInTimings; + bool logsInConsole; + + VeilidWASMConfigLoggingPerformance({ + required this.enabled, + required this.level, + required this.logsInTimings, + required this.logsInConsole, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + 'logs_in_timings': logsInTimings, + 'logs_in_console': logsInConsole, + }; + } + + VeilidWASMConfigLoggingPerformance.fromJson(Map json) + : enabled = json['enabled'], + level = veilidLogLevelFromJson(json['level']), + logsInTimings = json['logs_in_timings'], + logsInConsole = json['logs_in_console']; +} + +class VeilidWASMConfigLogging { + VeilidWASMConfigLoggingPerformance performance; + + VeilidWASMConfigLogging({required this.performance}); + + Map get json { + return { + 'performance': performance.json, + }; + } + + VeilidWASMConfigLogging.fromJson(Map json) + : performance = + VeilidWASMConfigLoggingPerformance.fromJson(json['performance']); +} + +class VeilidWASMConfig { + VeilidWASMConfigLogging logging; + + VeilidWASMConfig({ + required this.logging, + }); + + Map get json { + return { + 'logging': logging.json, + }; + } + + VeilidWASMConfig.fromJson(Map json) + : logging = VeilidWASMConfigLogging.fromJson(json['logging']); +} + ////////////////////////////////////// /// JSON Encode Helper + Object? veilidApiToEncodable(Object? value) { if (value == null) { return value; @@ -797,7 +951,7 @@ abstract class VeilidUpdate { case "Log": { return VeilidUpdateLog( - veilidLogLevelFromJson(json["api_log_level"]), json["message"]); + veilidLogLevelFromJson(json["log_level"]), json["message"]); } case "Attachment": { @@ -1098,6 +1252,7 @@ class VeilidVersion { abstract class Veilid { static late Veilid instance = getVeilid(); + void configureVeilidPlatform(Map platformConfigJson); Stream startupVeilidCore(VeilidConfig config); Future getVeilidState(); Future changeApiLogLevel(VeilidConfigLogLevel logLevel); diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index 0601f944..0dc257f9 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -29,6 +29,9 @@ typedef _FreeStringDart = void Function(Pointer); // fn initialize_veilid_flutter(dart_post_c_object_ptr: ffi::DartPostCObjectFnType) typedef _InitializeVeilidFlutterC = Void Function(Pointer<_DartPostCObject>); typedef _InitializeVeilidFlutterDart = void Function(Pointer<_DartPostCObject>); +// fn configure_veilid_platform(platform_config: FfiStr) +typedef _ConfigureVeilidPlatformC = Void Function(Pointer); +typedef _ConfigureVeilidPlatformDart = void Function(Pointer); // fn startup_veilid_core(port: i64, config: FfiStr) typedef _StartupVeilidCoreC = Void Function(Int64, Pointer); typedef _StartupVeilidCoreDart = void Function(int, Pointer); @@ -228,9 +231,10 @@ Stream processStreamJson( } } } - } catch (e) { + } catch (e, s) { // Wrap all other errors in VeilidAPIExceptionInternal - throw VeilidAPIExceptionInternal(e.toString()); + throw VeilidAPIExceptionInternal( + "${e.toString()}\nStack Trace:\n${s.toString()}"); } } @@ -241,6 +245,7 @@ class VeilidFFI implements Veilid { // Shared library functions final _FreeStringDart _freeString; + final _ConfigureVeilidPlatformDart _configureVeilidPlatform; final _StartupVeilidCoreDart _startupVeilidCore; final _GetVeilidStateDart _getVeilidState; final _ChangeApiLogLevelDart _changeApiLogLevel; @@ -253,6 +258,9 @@ class VeilidFFI implements Veilid { : _dylib = dylib, _freeString = dylib.lookupFunction<_FreeStringC, _FreeStringDart>('free_string'), + _configureVeilidPlatform = dylib.lookupFunction< + _ConfigureVeilidPlatformC, + _ConfigureVeilidPlatformDart>('configure_veilid_platform'), _startupVeilidCore = dylib.lookupFunction<_StartupVeilidCoreC, _StartupVeilidCoreDart>( 'startup_veilid_core'), @@ -278,6 +286,17 @@ class VeilidFFI implements Veilid { initializeVeilidFlutter(NativeApi.postCObject); } + @override + void configureVeilidPlatform(Map platformConfigJson) { + var nativePlatformConfig = + jsonEncode(platformConfigJson, toEncodable: veilidApiToEncodable) + .toNativeUtf8(); + + _configureVeilidPlatform(nativePlatformConfig); + + malloc.free(nativePlatformConfig); + } + @override Stream startupVeilidCore(VeilidConfig config) { var nativeConfig = diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index fb490601..f73af88b 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -19,6 +19,14 @@ Future _wrapApiPromise(Object p) { } class VeilidJS implements Veilid { + @override + void configureVeilidPlatform(Map platformConfigJson) { + var platformConfigJsonString = + jsonEncode(platformConfigJson, toEncodable: veilidApiToEncodable); + js_util.callMethod( + wasm, "configure_veilid_platform", [platformConfigJsonString]); + } + @override Stream startupVeilidCore(VeilidConfig config) async* { var streamController = StreamController(); diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index d7b42161..6c216160 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -23,9 +23,14 @@ async-std = { version = "^1", features = ["unstable"] } allo-isolate = "^0" ffi-support = "^0" lazy_static = "^1" +tracing-opentelemetry = "^0" +opentelemetry = { version = "^0", features = ["rt-async-std"] } +opentelemetry-otlp = { version = "^0", features = ["grpc-sys"] } +opentelemetry-semantic-conventions = "^0" +hostname = "^0" # Dependencies for WASM builds only -# [target.'cfg(target_arch = "wasm32")'.dependencies] +[target.'cfg(target_arch = "wasm32")'.dependencies] # Dependencies for Android builds only [target.'cfg(target_os = "android")'.dependencies] diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 27b14914..7a5b7267 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -3,9 +3,15 @@ use allo_isolate::*; use async_std::sync::Mutex as AsyncMutex; use ffi_support::*; use lazy_static::*; +use opentelemetry::sdk::*; +use opentelemetry::*; +use opentelemetry_otlp::WithExportConfig; +use serde::*; use std::os::raw::c_char; use std::sync::Arc; use tracing::*; +use tracing_subscriber::prelude::*; +use tracing_subscriber::*; // Globals @@ -28,6 +34,17 @@ async fn take_veilid_api() -> Result, V: AsRef<[T]>>(metadata: &Metadata, ignore_list: V) -> bool { + // Skip filtered targets + !match (metadata.target(), ignore_list.as_ref()) { + (path, ignore) if !ignore.is_empty() => { + // Check that the module path does not match any ignore filters + ignore.iter().any(|v| path.starts_with(v.as_ref())) + } + _ => false, + } +} + ///////////////////////////////////////// // FFI Helpers @@ -51,6 +68,34 @@ macro_rules! check_err_json { }; } +///////////////////////////////////////// +// FFI-specific cofnig + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidFFIConfigLoggingTerminal { + pub enabled: bool, + pub level: veilid_core::VeilidLogLevel, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidFFIConfigLoggingOtlp { + pub enabled: bool, + pub level: veilid_core::VeilidLogLevel, + pub grpc_endpoint: String, + pub service_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidFFIConfigLogging { + pub terminal: VeilidFFIConfigLoggingTerminal, + pub otlp: VeilidFFIConfigLoggingOtlp, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidFFIConfig { + pub logging: VeilidFFIConfigLogging, +} + ///////////////////////////////////////// // Initializer #[no_mangle] @@ -91,6 +136,96 @@ pub extern "C" fn initialize_veilid_flutter(dart_post_c_object_ptr: ffi::DartPos ////////////////////////////////////////////////////////////////////////////////// /// C-compatible FFI Functions +#[no_mangle] +#[instrument] +pub extern "C" fn configure_veilid_platform(platform_config: FfiStr) { + let platform_config = platform_config.into_opt_string(); + let platform_config: VeilidFFIConfig = veilid_core::deserialize_opt_json(platform_config) + .expect("failed to deserialize plaform config json"); + + // Set up subscriber and layers + let mut ignore_list = Vec::::new(); + for ig in veilid_core::DEFAULT_LOG_IGNORE_LIST { + ignore_list.push(ig.to_owned()); + } + + let subscriber = Registry::default(); + + // Terminal logger + let subscriber = subscriber.with(if platform_config.logging.terminal.enabled { + let terminal_max_log_level: level_filters::LevelFilter = platform_config + .logging + .terminal + .level + .to_tracing_level() + .into(); + + let ignore_list = ignore_list.clone(); + Some( + fmt::Layer::new() + .compact() + .with_writer(std::io::stdout) + .with_filter(terminal_max_log_level) + .with_filter(filter::FilterFn::new(move |metadata| { + logfilter(metadata, &ignore_list) + })), + ) + } else { + None + }); + + // OpenTelemetry logger + let subscriber = subscriber.with(if platform_config.logging.otlp.enabled { + let otlp_max_log_level: level_filters::LevelFilter = + platform_config.logging.otlp.level.to_tracing_level().into(); + let grpc_endpoint = platform_config.logging.otlp.grpc_endpoint.clone(); + + let tracer = + opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter() + .grpcio() + .with_endpoint(grpc_endpoint), + ) + .with_trace_config(opentelemetry::sdk::trace::config().with_resource( + Resource::new(vec![KeyValue::new( + opentelemetry_semantic_conventions::resource::SERVICE_NAME, + format!( + "{}:{}", + platform_config.logging.otlp.service_name, + hostname::get() + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or_else(|_| "unknown".to_owned()) + ), + )]), + )) + .install_batch(opentelemetry::runtime::AsyncStd) + .map_err(|e| format!("failed to install OpenTelemetry tracer: {}", e)) + .expect("failed to initalize ffi platform"); + + let ignore_list = ignore_list.clone(); + Some( + tracing_opentelemetry::layer() + .with_tracer(tracer) + .with_filter(otlp_max_log_level) + .with_filter(filter::FilterFn::new(move |metadata| { + logfilter(metadata, &ignore_list) + })), + ) + } else { + None + }); + + // API logger (always add layer, startup will init this if it is enabled in settings) + let subscriber = subscriber.with(veilid_core::ApiTracingLayer::get()); + + subscriber + .try_init() + .map_err(|e| format!("failed to initialize logging: {}", e)) + .expect("failed to initalize ffi platform"); +} + #[no_mangle] #[instrument] pub extern "C" fn startup_veilid_core(port: i64, config: FfiStr) { @@ -149,8 +284,9 @@ pub extern "C" fn change_api_log_level(port: i64, log_level: FfiStr) { DartIsolateWrapper::new(port).spawn_result_json(async move { let log_level: veilid_core::VeilidConfigLogLevel = veilid_core::deserialize_opt_json(log_level)?; - let veilid_api = get_veilid_api().await?; - veilid_api.change_api_log_level(log_level).await; + //let veilid_api = get_veilid_api().await?; + //veilid_api.change_api_log_level(log_level).await; + veilid_core::ApiTracingLayer::change_api_log_level(log_level.to_veilid_log_level()); APIRESULT_VOID }); } diff --git a/veilid-server/src/veilid_logs.rs b/veilid-server/src/veilid_logs.rs index 4cddd87a..f29fe069 100644 --- a/veilid-server/src/veilid_logs.rs +++ b/veilid-server/src/veilid_logs.rs @@ -29,7 +29,6 @@ impl VeilidLogs { let settingsr = settings.read(); // Set up subscriber and layers - let mut ignore_list = Vec::::new(); for ig in veilid_core::DEFAULT_LOG_IGNORE_LIST { ignore_list.push(ig.to_owned()); @@ -65,9 +64,6 @@ impl VeilidLogs { .into(); let grpc_endpoint = settingsr.logging.otlp.grpc_endpoint.name.clone(); - // Required for GRPC dns resolution to work - std::env::set_var("GRPC_DNS_RESOLVER", "native"); - let tracer = opentelemetry_otlp::new_pipeline() .tracing() .with_exporter( diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 7ce52537..55954943 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -14,6 +14,7 @@ use lazy_static::*; use log::*; use send_wrapper::*; use serde::*; +use tracing_wasm::{WASMLayerConfigBuilder, *}; use veilid_core::xx::*; use veilid_core::*; use wasm_bindgen_futures::*; @@ -98,6 +99,27 @@ where })) } +///////////////////////////////////////// +// WASM-specific cofnig + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidWASMConfigLoggingPerformance { + pub enabled: bool, + pub level: veilid_core::VeilidLogLevel, + pub logs_in_timings: bool, + pub logs_in_console: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidWASMConfigLogging { + pub performance: VeilidWASMConfigLoggingPerformance, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidWASMConfig { + pub logging: VeilidWASMConfigLogging, +} + // WASM Bindings #[wasm_bindgen()] @@ -105,6 +127,55 @@ pub fn initialize_veilid_wasm() { console_error_panic_hook::set_once(); } +#[wasm_bindgen()] +pub fn configure_veilid_platform(platform_config: String) { + let platform_config = platform_config.into_opt_string(); + let platform_config: VeilidWASMConfig = veilid_core::deserialize_opt_json(platform_config) + .expect("failed to deserialize plaform config json"); + + // Set up subscriber and layers + let mut ignore_list = Vec::::new(); + for ig in veilid_core::DEFAULT_LOG_IGNORE_LIST { + ignore_list.push(ig.to_owned()); + } + + let subscriber = Registry::default(); + + // Performance logger + let subscriber = subscriber.with(if platform_config.logging.performance.enabled { + let performance_max_log_level = + platform_config.logging.performance.level.to_tracing_level(); + + let ignore_list = ignore_list.clone(); + Some( + WASMLayer::new( + WASMLayerConfigBuilder::new() + .set_report_logs_in_timings(platform_config.logging.performance.logs_in_timings) + .set_console_config(if platform_config.logging.performance.logs_in_console { + ConsoleConfig::ReportWithConsoleColor + } else { + ConsoleConfig::NoReporting + }) + .set_max_level(performance_max_log_level) + .build(), + ) + .with_filter(filter::FilterFn::new(move |metadata| { + logfilter(metadata, &ignore_list) + })), + ) + } else { + None + }); + + // API logger (always add layer, startup will init this if it is enabled in settings) + let subscriber = subscriber.with(veilid_core::ApiTracingLayer::get()); + + subscriber + .try_init() + .map_err(|e| format!("failed to initialize logging: {}", e)) + .expect("failed to initalize WASM platform"); +} + #[wasm_bindgen()] pub fn startup_veilid_core(update_callback: Function, json_config: String) -> Promise { wrap_api_future(async move { @@ -141,9 +212,10 @@ pub fn get_veilid_state() -> Promise { #[wasm_bindgen()] pub fn change_api_log_level(log_level: String) -> Promise { wrap_api_future(async move { - let veilid_api = get_veilid_api()?; let log_level: veilid_core::VeilidConfigLogLevel = deserialize_json(&log_level)?; - veilid_api.change_api_log_level(log_level).await; + //let veilid_api = get_veilid_api()?; + //veilid_api.change_api_log_level(log_level).await; + veilid_core::ApiTracingLayer::change_api_log_level(log_level.to_veilid_log_level()); APIRESULT_UNDEFINED }) }