diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md
index a76d569d..8ca208b0 100644
--- a/SUPPORTED_APIS.md
+++ b/SUPPORTED_APIS.md
@@ -14,6 +14,7 @@ The `status` options are:
| Namespace | API |
Status
| Description |
| --- | --- | --- | --- |
+| `ANVIL` | `anvil_setLoggingEnabled` | `SUPPORTED` | Enables or disables logging |
| `ANVIL` | `anvil_snapshot` | `SUPPORTED` | Snapshot the state of the blockchain at the current block |
| `ANVIL` | `anvil_revert` | `SUPPORTED` | Revert the state of the blockchain to a previous snapshot |
| `ANVIL` | `anvil_setTime` | `SUPPORTED` | Sets the internal clock time to the given timestamp |
diff --git a/e2e-tests/test/anvil-apis.test.ts b/e2e-tests/test/anvil-apis.test.ts
index 648ff80b..1f36470d 100644
--- a/e2e-tests/test/anvil-apis.test.ts
+++ b/e2e-tests/test/anvil-apis.test.ts
@@ -7,9 +7,34 @@ import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
import * as hre from "hardhat";
import { keccak256 } from "ethers/lib/utils";
import { BigNumber } from "ethers";
+import * as fs from "node:fs";
const provider = getTestProvider();
+describe("anvil_setLoggingEnabled", function () {
+ it("Should disable and enable logging", async function () {
+ // Arrange
+ const wallet = new Wallet(RichAccounts[0].PrivateKey, provider);
+ const userWallet = Wallet.createRandom().connect(provider);
+
+ // Act
+ await provider.send("anvil_setLoggingEnabled", [false]);
+ const logSizeBefore = fs.statSync("../era_test_node.log").size;
+
+ await wallet.sendTransaction({
+ to: userWallet.address,
+ value: ethers.utils.parseEther("0.1"),
+ });
+ const logSizeAfter = fs.statSync("../era_test_node.log").size;
+
+ // Reset
+ await provider.send("anvil_setLoggingEnabled", [true]);
+
+ // Assert
+ expect(logSizeBefore).to.equal(logSizeAfter);
+ });
+});
+
describe("anvil_snapshot", function () {
it("Should return incrementing snapshot ids", async function () {
const wallet = new Wallet(RichAccounts[6].PrivateKey);
diff --git a/src/namespaces/anvil.rs b/src/namespaces/anvil.rs
index 39c802a0..29e4db88 100644
--- a/src/namespaces/anvil.rs
+++ b/src/namespaces/anvil.rs
@@ -6,6 +6,14 @@ use crate::utils::Numeric;
#[rpc]
pub trait AnvilNamespaceT {
+ /// Enable or disable logging.
+ ///
+ /// # Arguments
+ ///
+ /// * `enable` - if `true` logging will be enabled, disabled otherwise
+ #[rpc(name = "anvil_setLoggingEnabled")]
+ fn set_logging_enabled(&self, enable: bool) -> RpcResult<()>;
+
/// Snapshot the state of the blockchain at the current block. Takes no parameters. Returns the id of the snapshot
/// that was created. A snapshot can only be reverted once. After a successful `anvil_revert`, the same snapshot id cannot
/// be used again. Consider creating a new snapshot after each `anvil_revert` if you need to revert to the same
diff --git a/src/node/anvil.rs b/src/node/anvil.rs
index a3251f5a..b0d57003 100644
--- a/src/node/anvil.rs
+++ b/src/node/anvil.rs
@@ -12,6 +12,15 @@ use crate::{
impl AnvilNamespaceT
for InMemoryNode
{
+ fn set_logging_enabled(&self, enable: bool) -> RpcResult<()> {
+ self.set_logging_enabled(enable)
+ .map_err(|err| {
+ tracing::error!("failed creating snapshot: {:?}", err);
+ into_jsrpc_error(Web3Error::InternalError(err))
+ })
+ .into_boxed_future()
+ }
+
fn snapshot(&self) -> RpcResult {
self.snapshot()
.map_err(|err| {
diff --git a/src/node/config_api.rs b/src/node/config_api.rs
index 37bfb1ee..cbc03ccc 100644
--- a/src/node/config_api.rs
+++ b/src/node/config_api.rs
@@ -184,48 +184,46 @@ impl Configurat
}
fn config_set_log_level(&self, level: LogLevel) -> Result {
- if let Some(observability) = &self
- .get_inner()
- .read()
- .map_err(|err| {
- tracing::error!("failed acquiring lock: {:?}", err);
- into_jsrpc_error(Web3Error::InternalError(anyhow::Error::msg(
- "Failed to acquire read lock for inner node state.",
- )))
- })?
- .observability
- {
- match observability.set_log_level(level) {
- Ok(_) => tracing::info!("set log level to '{}'", level),
- Err(err) => {
- tracing::error!("failed setting log level {:?}", err);
- return Ok(false);
- }
+ let Some(observability) = &self.observability else {
+ return Err(into_jsrpc_error(Web3Error::InternalError(
+ anyhow::Error::msg("Node's logging is not set up."),
+ )));
+ };
+ match observability.set_log_level(level) {
+ Ok(_) => {
+ tracing::info!("set log level to '{}'", level);
+ self.get_inner()
+ .write()
+ .map_err(|err| {
+ tracing::error!("failed acquiring lock: {:?}", err);
+ into_jsrpc_error(Web3Error::InternalError(anyhow::Error::msg(
+ "Failed to acquire write lock for inner node state.",
+ )))
+ })?
+ .config
+ .log_level = level;
+ Ok(true)
+ }
+ Err(err) => {
+ tracing::error!("failed setting log level {:?}", err);
+ Ok(false)
}
}
- Ok(true)
}
fn config_set_logging(&self, directive: String) -> Result {
- if let Some(observability) = &self
- .get_inner()
- .read()
- .map_err(|err| {
- tracing::error!("failed acquiring lock: {:?}", err);
- into_jsrpc_error(Web3Error::InternalError(anyhow::Error::msg(
- "Failed to acquire read lock for inner node state.",
- )))
- })?
- .observability
- {
- match observability.set_logging(&directive) {
- Ok(_) => tracing::info!("set logging to '{}'", directive),
- Err(err) => {
- tracing::error!("failed setting logging to '{}': {:?}", directive, err);
- return Ok(false);
- }
+ let Some(observability) = &self.observability else {
+ return Err(into_jsrpc_error(Web3Error::InternalError(
+ anyhow::Error::msg("Node's logging is not set up."),
+ )));
+ };
+ match observability.set_logging(directive.clone()) {
+ Ok(_) => tracing::info!("set logging to '{}'", directive),
+ Err(err) => {
+ tracing::error!("failed setting logging to '{}': {:?}", directive, err);
+ return Ok(false);
}
- }
+ };
Ok(true)
}
}
diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs
index 80ce0896..9fef3219 100644
--- a/src/node/in_memory.rs
+++ b/src/node/in_memory.rs
@@ -225,8 +225,6 @@ pub struct InMemoryNodeInner {
pub rich_accounts: HashSet,
/// Keeps track of historical states indexed via block hash. Limited to [MAX_PREVIOUS_STATES].
pub previous_states: IndexMap>,
- /// An optional handle to the observability stack
- pub observability: Option,
}
#[derive(Debug)]
@@ -238,11 +236,7 @@ pub struct TxExecutionOutput {
impl InMemoryNodeInner {
/// Create the state to be used implementing [InMemoryNode].
- pub fn new(
- fork: Option,
- observability: Option,
- config: &TestNodeConfig,
- ) -> Self {
+ pub fn new(fork: Option, config: &TestNodeConfig) -> Self {
let updated_config = config.clone();
if let Some(f) = &fork {
@@ -294,7 +288,6 @@ impl InMemoryNodeInner {
impersonation: impersonation_manager,
rich_accounts: HashSet::new(),
previous_states: Default::default(),
- observability,
}
} else {
let mut block_hashes = HashMap::::new();
@@ -335,7 +328,6 @@ impl InMemoryNodeInner {
impersonation: impersonation_manager,
rich_accounts: HashSet::new(),
previous_states: Default::default(),
- observability,
}
}
}
@@ -891,6 +883,8 @@ pub struct InMemoryNode {
pub(crate) system_contracts_options: system_contracts::Options,
pub(crate) time: TimestampManager,
pub(crate) impersonation: ImpersonationManager,
+ /// An optional handle to the observability stack
+ pub(crate) observability: Option,
}
fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs) -> Option {
@@ -915,7 +909,7 @@ impl InMemoryNode {
config: &TestNodeConfig,
) -> Self {
let system_contracts_options = config.system_contracts_options;
- let inner = InMemoryNodeInner::new(fork, observability, config);
+ let inner = InMemoryNodeInner::new(fork, config);
let time = inner.time.clone();
let impersonation = inner.impersonation.clone();
InMemoryNode {
@@ -924,6 +918,7 @@ impl InMemoryNode {
system_contracts_options,
time,
impersonation,
+ observability,
}
}
@@ -957,15 +952,8 @@ impl InMemoryNode {
}
pub fn reset(&self, fork: Option) -> Result<(), String> {
- let observability = self
- .inner
- .read()
- .map_err(|e| format!("Failed to acquire read lock: {}", e))?
- .observability
- .clone();
-
let config = self.get_config()?;
- let inner = InMemoryNodeInner::new(fork, observability, &config);
+ let inner = InMemoryNodeInner::new(fork, &config);
let mut writer = self
.snapshots
diff --git a/src/node/in_memory_ext.rs b/src/node/in_memory_ext.rs
index 6cc1c236..3d331281 100644
--- a/src/node/in_memory_ext.rs
+++ b/src/node/in_memory_ext.rs
@@ -1,3 +1,10 @@
+use crate::utils::Numeric;
+use crate::{
+ fork::{ForkDetails, ForkSource},
+ namespaces::ResetRequest,
+ node::InMemoryNode,
+ utils::bytecode_to_factory_dep,
+};
use anyhow::{anyhow, Context};
use std::convert::TryInto;
use zksync_types::{
@@ -8,14 +15,6 @@ use zksync_types::{
use zksync_types::{AccountTreeId, Address, U256, U64};
use zksync_utils::u256_to_h256;
-use crate::utils::Numeric;
-use crate::{
- fork::{ForkDetails, ForkSource},
- namespaces::ResetRequest,
- node::InMemoryNode,
- utils::bytecode_to_factory_dep,
-};
-
type Result = anyhow::Result;
/// The maximum number of [Snapshot]s to store. Each snapshot represents the node state
@@ -350,6 +349,17 @@ impl InMemoryNo
true
})
}
+
+ pub fn set_logging_enabled(&self, enable: bool) -> Result<()> {
+ let Some(observability) = &self.observability else {
+ anyhow::bail!("Node's logging is not set up");
+ };
+ if enable {
+ observability.enable_logging()
+ } else {
+ observability.disable_logging()
+ }
+ }
}
#[cfg(test)]
@@ -485,7 +495,6 @@ mod tests {
impersonation: Default::default(),
rich_accounts: Default::default(),
previous_states: Default::default(),
- observability: None,
};
let time = old_inner.time.clone();
let impersonation = old_inner.impersonation.clone();
@@ -496,6 +505,7 @@ mod tests {
system_contracts_options: old_system_contracts_options,
time,
impersonation,
+ observability: None,
};
let address = Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap();
diff --git a/src/observability.rs b/src/observability.rs
index 2e1b79e3..4dd933bb 100644
--- a/src/observability.rs
+++ b/src/observability.rs
@@ -1,8 +1,8 @@
-use core::fmt;
-use std::{fs::File, sync::Mutex};
-
use clap::ValueEnum;
+use core::fmt;
use serde::{Deserialize, Serialize};
+use std::sync::{Arc, RwLock};
+use std::{fs::File, sync::Mutex};
use tracing_subscriber::{
filter::LevelFilter, layer::SubscriberExt, reload, util::SubscriberInitExt, EnvFilter, Registry,
};
@@ -44,10 +44,12 @@ impl From for LevelFilter {
}
/// A sharable reference to the observability stack.
-#[derive(Debug, Default, Clone)]
+#[derive(Debug, Clone)]
pub struct Observability {
binary_names: Vec,
- reload_handle: Option>,
+ reload_handle: reload::Handle,
+ /// Last directives used to reload the underlying `EnvFilter` instance.
+ last_directives: Arc>,
}
impl Observability {
@@ -57,12 +59,12 @@ impl Observability {
log_level_filter: LevelFilter,
log_file: File,
) -> Result {
- let joined_filter = binary_names
+ let directives = binary_names
.iter()
.map(|x| format!("{}={}", x, log_level_filter.to_string().to_lowercase()))
.collect::>()
.join(",");
- let filter = Self::parse_filter(&joined_filter)?;
+ let filter = Self::parse_filter(&directives)?;
let (filter, reload_handle) = reload::Layer::new(filter);
let timer_format =
@@ -95,22 +97,23 @@ impl Observability {
Ok(Self {
binary_names,
- reload_handle: Some(reload_handle),
+ reload_handle,
+ last_directives: Arc::new(RwLock::new(directives)),
})
}
/// Set the log level for the binary.
- pub fn set_log_level(&self, level: LogLevel) -> Result<(), anyhow::Error> {
+ pub fn set_log_level(&self, level: LogLevel) -> anyhow::Result<()> {
let level = LevelFilter::from(level);
- let new_filter = Self::parse_filter(
- &self
- .binary_names
- .join(format!("={},", level.to_string().to_lowercase()).as_str()),
- )?;
-
- if let Some(handle) = &self.reload_handle {
- handle.modify(|filter| *filter = new_filter)?;
- }
+ let directives = self
+ .binary_names
+ .join(&format!("={},", level.to_string().to_lowercase()));
+ let new_filter = Self::parse_filter(&directives)?;
+ self.reload_handle.reload(new_filter)?;
+ *self
+ .last_directives
+ .write()
+ .expect("Observability lock is poisoned") = directives;
Ok(())
}
@@ -120,12 +123,13 @@ impl Observability {
/// * "my_crate=debug"
/// * "my_crate::module=trace"
/// * "my_crate=debug,other_crate=warn"
- pub fn set_logging(&self, directive: &str) -> Result<(), anyhow::Error> {
- let new_filter = Self::parse_filter(directive)?;
-
- if let Some(handle) = &self.reload_handle {
- handle.modify(|filter| *filter = new_filter)?;
- }
+ pub fn set_logging(&self, directives: String) -> Result<(), anyhow::Error> {
+ let new_filter = Self::parse_filter(&directives)?;
+ self.reload_handle.reload(new_filter)?;
+ *self
+ .last_directives
+ .write()
+ .expect("Observability lock is poisoned") = directives;
Ok(())
}
@@ -135,12 +139,32 @@ impl Observability {
/// * "my_crate=debug"
/// * "my_crate::module=trace"
/// * "my_crate=debug,other_crate=warn"
- fn parse_filter(directive: &str) -> Result {
+ fn parse_filter(directives: &str) -> Result {
let mut filter = EnvFilter::from_default_env();
- for directive in directive.split(',') {
+ for directive in directives.split(',') {
filter = filter.add_directive(directive.parse()?);
}
Ok(filter)
}
+
+ /// Enables logging with the latest used directives.
+ pub fn enable_logging(&self) -> Result<(), anyhow::Error> {
+ let last_directives = &*self
+ .last_directives
+ .read()
+ .expect("Observability lock is poisoned");
+ let new_filter = Self::parse_filter(last_directives)?;
+ self.reload_handle.reload(new_filter)?;
+
+ Ok(())
+ }
+
+ /// Disables all logging.
+ pub fn disable_logging(&self) -> Result<(), anyhow::Error> {
+ let new_filter = EnvFilter::new("off");
+ self.reload_handle.reload(new_filter)?;
+
+ Ok(())
+ }
}