Using Hardhat’s console.log for Solidity Debugging
Learn how Hardhat’s console.log lets you print debug messages directly from Solidity contracts during testing, with zero gas cost in production builds when the optimizer is enabled.
02 Aug 2025, 02:26 UTC

Problem: needing quick visibility into Solidity values
While developing smart contracts you often want to inspect a variable’s value or confirm that a branch was taken. In JavaScript you would add a console.log; in Solidity the usual alternative is to emit an event and listen for it, which adds gas cost and requires extra contract code.
Solution: Hardhat’s built‑in console.log
Hardhat provides a Solidity library that mimics JavaScript’s console.log. When a contract runs on Hardhat Network (or a fork), calls to this library are intercepted and their arguments are printed to the process’s standard output. On any other network the calls are ignored and the transaction still succeeds.
Enabling the library
Add the import at the top of any Solidity file:
import "hardhat/console.sol";
After the import you can call console.log(value) or console.log(string) inside any function, modifier, or constructor. The library accepts the same argument types as JavaScript’s console.log (uint, int, bool, address, string, bytes, and fixed‑size arrays of those types). For dynamic arrays or structs you must first convert them to a supported type (e.g., using abi.encodePacked or a custom helper).
How output reaches the terminal
Hardhat Network implements the console library as a special contract that forwards all arguments to process.stdout. This means the same statements appear when you run hardhat test, hardhat run, or a local hardhat node session. No extra plugins or network configuration are required as long as the active network is Hardhat Network or a fork.
Worked example
Create a contract that logs a number and a string:
// contracts/Logger.sol
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Logger {
function showNumber() public pure {
console.log(42);
}
function showMessage() public pure {
console.log("Hello from Solidity");
}
}
Write a test that invokes both functions. This example uses ethers v6 syntax; if you are using ethers v5 replace await logger.deployed() with await logger.deployed() (the call is optional in v5) and adjust the import accordingly.
// test/Logger.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Logger contract", function () {
it("should print to the console", async function () {
const Logger = await ethers.getContractFactory("Logger");
const logger = await Logger.deploy();
// ethers v6: deployment is already awaited; v5 may need await logger.deployed()
await logger.showNumber();
await logger.showMessage();
});
});
Run the test:
npx hardhat test
You should see the following lines in the terminal output (order may vary):
42
Hello from Solidity
Verifying that console.log is stripped in production builds
When the Solidity optimizer is enabled (the default in Hardhat), unused internal functions are removed from the bytecode. The console library consists of internal functions; if they are never called outside of Hardhat Network, the optimizer will omit them from the deployed code.
To check this:
- Compile with optimizer on (default):
npx hardhat compile - Open the generated artifact, e.g.,
artifacts/contracts/Logger.sol/Logger.json - Inspect the
deployedBytecodefield. You will not find the function signatures of the console library (e.g.,0x...corresponding toconsole.log(uint256)) when the optimizer is on. - Repeat the compile with optimizer off:
npx hardhat compile --optimizer-offand inspect the artifact again; the console library’s bytecode will now be present.
This comparison shows that the logging statements add no gas cost in production builds when the optimizer is active.
Trade‑offs and limitations
- Verbose output: Calling
console.loginside a loop or on large data structures can flood the terminal and slow test runs. - Network dependence: The feature works only on Hardhat Network or a fork. If you point
hardhat runat a live provider (e.g., Infura) without forking, the console calls are ignored and the transaction still succeeds but produces no output. - Optimizer interaction: With the optimizer enabled (default), console.log calls are removed from the deployed bytecode, incurring no gas cost. If you disable the optimizer for debugging, the calls remain and consume gas.
- Supported types: The library accepts primitive types, strings, bytes, and fixed‑size arrays. For dynamic arrays or structs you must first encode or convert them to a supported type.
Actionable closing
Add import "hardhat/console.sol"; to any contract where you need quick visibility during development. Run your tests or scripts locally with Hardhat Network and watch the terminal for the printed values. Before deploying to a testnet or mainnet, either remove the import/comment out the calls or rely on the optimizer (which is enabled by default) to strip them automatically. This gives you the convenience of JavaScript‑style logging without the overhead of custom events.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.