diff --git a/include/proxy-wasm/exports.h b/include/proxy-wasm/exports.h index 376a4d3b..3ff96c5b 100644 --- a/include/proxy-wasm/exports.h +++ b/include/proxy-wasm/exports.h @@ -67,6 +67,7 @@ namespace exports { Word get_configuration(Word value_ptr_ptr, Word value_size_ptr); Word get_status(Word code_ptr, Word value_ptr_ptr, Word value_size_ptr); Word log(Word level, Word address, Word size); +Word log_destination(Word dest, Word dest_size, Word level, Word address, Word size); Word get_log_level(Word result_level_uint32_ptr); Word get_property(Word path_ptr, Word path_size, Word value_ptr_ptr, Word value_size_ptr); Word set_property(Word key_ptr, Word key_size, Word value_ptr, Word value_size); @@ -152,18 +153,19 @@ void emscripten_notify_memory_growth(Word); // Support for embedders, not exported to Wasm. #define FOR_ALL_HOST_FUNCTIONS(_f) \ - _f(log) _f(get_status) _f(set_property) _f(get_property) _f(send_local_response) \ - _f(get_shared_data) _f(set_shared_data) _f(register_shared_queue) _f(resolve_shared_queue) \ - _f(dequeue_shared_queue) _f(enqueue_shared_queue) _f(get_header_map_value) \ - _f(add_header_map_value) _f(replace_header_map_value) _f(remove_header_map_value) \ - _f(get_header_map_pairs) _f(set_header_map_pairs) _f(get_header_map_size) \ - _f(get_buffer_status) _f(get_buffer_bytes) _f(set_buffer_bytes) \ - _f(http_call) _f(grpc_call) _f(grpc_stream) _f(grpc_close) \ - _f(grpc_cancel) _f(grpc_send) _f(set_tick_period_milliseconds) \ - _f(get_current_time_nanoseconds) _f(define_metric) \ - _f(increment_metric) _f(record_metric) _f(get_metric) \ - _f(set_effective_context) _f(done) \ - _f(call_foreign_function) + _f(log) _f(log_destination) _f(get_status) _f(set_property) _f(get_property) \ + _f(send_local_response) _f(get_shared_data) _f(set_shared_data) _f(register_shared_queue) \ + _f(resolve_shared_queue) _f(dequeue_shared_queue) _f(enqueue_shared_queue) \ + _f(get_header_map_value) _f(add_header_map_value) _f(replace_header_map_value) \ + _f(remove_header_map_value) _f(get_header_map_pairs) _f(set_header_map_pairs) \ + _f(get_header_map_size) _f(get_buffer_status) _f(get_buffer_bytes) \ + _f(set_buffer_bytes) _f(http_call) _f(grpc_call) _f(grpc_stream) \ + _f(grpc_close) _f(grpc_cancel) _f(grpc_send) \ + _f(set_tick_period_milliseconds) \ + _f(get_current_time_nanoseconds) _f(define_metric) \ + _f(increment_metric) _f(record_metric) _f(get_metric) \ + _f(set_effective_context) _f(done) \ + _f(call_foreign_function) #define FOR_ALL_HOST_FUNCTIONS_ABI_SPECIFIC(_f) \ _f(get_configuration) _f(continue_request) _f(continue_response) _f(clear_route_cache) \ diff --git a/include/proxy-wasm/wasm.h b/include/proxy-wasm/wasm.h index 23ed3c1d..dc901658 100644 --- a/include/proxy-wasm/wasm.h +++ b/include/proxy-wasm/wasm.h @@ -51,6 +51,7 @@ class WasmBase : public std::enable_shared_from_this { WasmBase(std::unique_ptr wasm_vm, std::string_view vm_id, std::string_view vm_configuration, std::string_view vm_key, std::unordered_map envs, + std::unordered_map log_dest, AllowedCapabilitiesMap allowed_capabilities); WasmBase(const std::shared_ptr &base_wasm_handle, const WasmVmFactory &factory); virtual ~WasmBase(); @@ -136,6 +137,9 @@ class WasmBase : public std::enable_shared_from_this { AbiVersion abiVersion() const { return abi_version_; } const std::unordered_map &envs() { return envs_; } + const std::unordered_map &log_destinations() { + return log_destinations_; + } // Called to raise the flag which indicates that the context should stop iteration regardless of // returned filter status from Proxy-Wasm extensions. For example, we ignore @@ -222,7 +226,7 @@ class WasmBase : public std::enable_shared_from_this { std::unique_ptr shutdown_handle_; std::unordered_map envs_; // environment variables passed through wasi.environ_get - + std::unordered_map log_destinations_; WasmCallVoid<0> _initialize_; /* WASI reactor (Emscripten v1.39.17+, Rust nightly) */ WasmCallVoid<0> _start_; /* WASI command (Emscripten v1.39.0+, TinyGo) */ @@ -394,7 +398,7 @@ inline void *WasmBase::allocMemory(uint64_t size, uint64_t *address) { } wasm_vm_->setRestrictedCallback( true, {// logging (Proxy-Wasm) - "env.proxy_log", + "env.proxy_log", "env.proxy_log_destination", // logging (stdout/stderr) "wasi_unstable.fd_write", "wasi_snapshot_preview1.fd_write", // time diff --git a/src/exports.cc b/src/exports.cc index 0290dcf0..64da8de5 100644 --- a/src/exports.cc +++ b/src/exports.cc @@ -19,6 +19,8 @@ #include +#include +#include #include namespace proxy_wasm { @@ -916,6 +918,41 @@ Word log(Word level, Word address, Word size) { return context->log(level, message.value()); } +Word log_destination(Word destination, Word dest_size, Word level, Word address, Word size) { + if (level > static_cast(LogLevel::Max)) { + return WasmResult::BadArgument; + } + auto *context = contextOrEffectiveContext(); + const auto &log_destinations = context->wasm()->log_destinations(); + + auto message = context->wasmVm()->getMemory(address, size); + if (!message) { + return WasmResult::InvalidMemoryAccess; + } + auto dest = context->wasmVm()->getMemory(destination, dest_size); + if (!dest) { + return WasmResult::InvalidMemoryAccess; + } + context->log(level, dest.value()); + // iterate over log_destinations map to check if dest + // destination requested by plugin exists + for (const auto &e : log_destinations) { + if (e.first == dest.value()) { + // write message to the file which is the value of the key if it exists + std::ofstream log_file; + log_file.open(e.second, std::ios::out | std::ios_base::app); + if (!log_file) { + return WasmResult::InvalidMemoryAccess; + } + log_file << message.value() << std::endl; + log_file.close(); + return WasmResult::Ok; + } + } + // As a fallback, write to the default log destination. + return context->log(level, message.value()); +} + Word get_log_level(Word result_level_uint32_ptr) { auto *context = contextOrEffectiveContext(); uint32_t level = context->getLogLevel(); diff --git a/src/wasm.cc b/src/wasm.cc index 5519b3e7..bc1dced2 100644 --- a/src/wasm.cc +++ b/src/wasm.cc @@ -190,6 +190,7 @@ WasmBase::WasmBase(const std::shared_ptr &base_wasm_handle, vm_id_(base_wasm_handle->wasm()->vm_id_), vm_key_(base_wasm_handle->wasm()->vm_key_), started_from_(base_wasm_handle->wasm()->wasm_vm()->cloneable()), envs_(base_wasm_handle->wasm()->envs()), + log_destinations_(base_wasm_handle->wasm()->log_destinations()), allowed_capabilities_(base_wasm_handle->wasm()->allowed_capabilities_), base_wasm_handle_(base_wasm_handle) { if (started_from_ != Cloneable::NotCloneable) { @@ -207,9 +208,11 @@ WasmBase::WasmBase(const std::shared_ptr &base_wasm_handle, WasmBase::WasmBase(std::unique_ptr wasm_vm, std::string_view vm_id, std::string_view vm_configuration, std::string_view vm_key, std::unordered_map envs, + std::unordered_map log_dest, AllowedCapabilitiesMap allowed_capabilities) : vm_id_(std::string(vm_id)), vm_key_(std::string(vm_key)), wasm_vm_(std::move(wasm_vm)), - envs_(std::move(envs)), allowed_capabilities_(std::move(allowed_capabilities)), + envs_(std::move(envs)), log_destinations_(std::move(log_dest)), + allowed_capabilities_(std::move(allowed_capabilities)), vm_configuration_(std::string(vm_configuration)), vm_id_handle_(getVmIdHandle(vm_id)) { if (!wasm_vm_) { failed_ = FailState::UnableToCreateVm; @@ -358,7 +361,7 @@ void WasmBase::startVm(ContextBase *root_context) { // wasi_snapshot_preview1.clock_time_get wasm_vm_->setRestrictedCallback( true, {// logging (Proxy-Wasm) - "env.proxy_log", + "env.proxy_log", "env.proxy_log_destination", // logging (stdout/stderr) "wasi_unstable.fd_write", "wasi_snapshot_preview1.fd_write", // args diff --git a/test/utility.h b/test/utility.h index 06114e2c..3bc074ef 100644 --- a/test/utility.h +++ b/test/utility.h @@ -140,7 +140,7 @@ class TestWasm : public WasmBase { TestWasm(std::unique_ptr wasm_vm, std::unordered_map envs = {}, std::string_view vm_id = "", std::string_view vm_configuration = "", std::string_view vm_key = "") - : WasmBase(std::move(wasm_vm), vm_id, vm_configuration, vm_key, std::move(envs), {}) {} + : WasmBase(std::move(wasm_vm), vm_id, vm_configuration, vm_key, std::move(envs), {}, {}) {} TestWasm(const std::shared_ptr &base_wasm_handle, const WasmVmFactory &factory) : WasmBase(base_wasm_handle, factory) {} diff --git a/test/wasm_test.cc b/test/wasm_test.cc index 387348e8..1af8d5cb 100644 --- a/test/wasm_test.cc +++ b/test/wasm_test.cc @@ -14,6 +14,7 @@ #include "include/proxy-wasm/wasm.h" +#include #include #include "gtest/gtest.h" @@ -43,9 +44,9 @@ TEST_P(TestVm, GetOrCreateThreadLocalWasmFailCallbacks) { // Define callbacks. WasmHandleFactory wasm_handle_factory = [this, vm_id, vm_config](std::string_view vm_key) -> std::shared_ptr { - auto base_wasm = std::make_shared(newVm(), vm_id, vm_config, vm_key, - std::unordered_map{}, - AllowedCapabilitiesMap{}); + auto base_wasm = std::make_shared( + newVm(), vm_id, vm_config, vm_key, std::unordered_map{}, + std::unordered_map{}, AllowedCapabilitiesMap{}); return std::make_shared(base_wasm); }; @@ -171,7 +172,6 @@ TEST_P(TestVm, AlwaysApplyCanary) { // For each create Wasm, canary should be done. EXPECT_EQ(canary_count, 1); - std::unordered_set> reference_holder; for (const auto &root_id : root_ids) { for (const auto &vm_id : vm_ids) { @@ -209,7 +209,6 @@ TEST_P(TestVm, AlwaysApplyCanary) { // Keep the reference of wasm_handle_comp in order to utilize the WasmHandleBase // cache of createWasm. If we don't keep the reference, WasmHandleBase and VM will be // destroyed for each iteration. - reference_holder.insert(wasm_handle_comp); EXPECT_TRUE(TestContext::isGlobalLogged("onConfigure: " + root_id));