The Log Effect
In Fe, logging is an effect, a capability that must be explicitly declared. This makes event emission visible in function signatures and enables powerful patterns for testing and composition.
Logging as an Effect
Section titled “Logging as an Effect”Unlike languages where logging is implicit, Fe treats it as a tracked capability:
struct Transfer { #[indexed] from: u256, #[indexed] to: u256, amount: u256,}
// This function CAN emit eventsfn transfer_with_event(from: u256, to: u256, amount: u256) uses (mut store: TokenStorage, mut log: Log){ // ... transfer logic ... log.emit(Transfer { from, to, amount })}
// This function CANNOT emit eventsfn transfer_silent(from: u256, to: u256, amount: u256) uses (mut store: TokenStorage){ // ... transfer logic only ... // log.emit(...) would be a compile error here}Use it in function signatures:
// Read-only logging isn't meaningful, so always use mutfn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) { log.emit(Transfer { from, to, amount })}Why Explicit Logging?
Section titled “Why Explicit Logging?”Clear Function Contracts
Section titled “Clear Function Contracts”Function signatures reveal side effects:
// Looking at this signature, you know:// - It reads Config (immutable)// - It modifies Balances (mutable)// - It emits events (mutable Log)fn process_payment(amount: u256) -> bool uses (config: Config, mut balances: Balances, mut log: Log){ true}In Solidity, you’d need to read the implementation to know if events are emitted.
Testability
Section titled “Testability”Mock or replace the Log effect in tests:
pub struct MockEventLog { pub events: Vec<u256>, // Track emitted events}
fn test_transfer() { let storage = TokenStorage { ... } let mock_log = MockEventLog { events: Vec::new() }
with (TokenStorage = storage, EventLog = mock_log) { transfer(alice, bob, 100) }
// Verify events were emitted assert(mock_log.events.len() == 1)}Composition Control
Section titled “Composition Control”Compose functions while controlling which can log:
// Internal helper - no loggingfn update_balance(account: u256, delta: u256) uses (mut balances: Balances) { // Pure state update, no events}
// Public interface - with loggingfn deposit(account: u256, amount: u256) uses (mut balances: Balances, mut log: Log){ update_balance(account, amount) log.emit(Deposit { account, amount })}Effect Propagation
Section titled “Effect Propagation”Functions calling logging functions must declare the effect:
fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) { log.emit(Transfer { from, to, amount })}
// Must declare Log because it calls emit_transferfn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage, mut log: Log){ // ... transfer logic ... emit_transfer(from, to, amount) // Requires Log effect true}// Compile error: missing Log effectfn broken_transfer(from: u256, to: u256, amount: u256) uses (mut store: TokenStorage) -> bool{ // ... transfer logic ... emit_transfer(from, to, amount) // Error: Log not available true}Binding in Contracts
Section titled “Binding in Contracts”Contracts provide the Log effect via the uses clause on handlers:
contract Token { mut store: TokenStorage, mut log: Log,
recv TokenMsg { Transfer { to, amount } -> bool uses (mut store, mut log) { do_transfer(caller(), to, amount) } }}Separate Log Effects
Section titled “Separate Log Effects”Use different Log effects for different event categories:
pub struct TransferLog {}impl TransferLog { pub fn emit<T>(self, event: T) { todo() }}
pub struct AdminLog {}impl AdminLog { pub fn emit<T>(self, event: T) { todo() }}
struct Transfer { #[indexed] from: u256, #[indexed] to: u256, amount: u256,}
struct OwnershipTransferred { #[indexed] previous_owner: u256, #[indexed] new_owner: u256,}
fn transfer(from: u256, to: u256, amount: u256) uses (mut store: TokenStorage, mut log: TransferLog){ // ... transfer logic ... log.emit(Transfer { from, to, amount })}
fn transfer_ownership(new_owner: u256) uses (mut admin: AdminStorage, mut log: AdminLog){ let previous = admin.owner admin.owner = new_owner log.emit(OwnershipTransferred { previous_owner: previous, new_owner })}This gives fine-grained control over which functions can emit which events.
Log Effect Patterns
Section titled “Log Effect Patterns”Combined Storage and Log
Section titled “Combined Storage and Log”Often storage and its events are paired:
pub struct TokenStorage { pub balances: Map<u256, u256>, pub total_supply: u256,}
pub struct TokenEvents {}impl TokenEvents { pub fn emit<T>(self, event: T) { todo() }}
fn mint(to: u256, amount: u256) uses (mut store: TokenStorage, mut log: TokenEvents){ store.balances.set(to, store.balances.get(to) + amount) store.total_supply = store.total_supply + amount log.emit(Transfer { from: 0, to, amount })}Event-Only Functions
Section titled “Event-Only Functions”Some functions exist solely to emit events:
pub struct DebugLog {}impl DebugLog { pub fn emit<T>(self, event: T) { todo() }}
struct DebugMessage { value: u256,}
fn log_debug(message: u256) uses (mut log: DebugLog) { log.emit(DebugMessage { value: message })}Optional Logging
Section titled “Optional Logging”Make logging optional by separating concerns:
pub struct Config { pub fee_rate: u256 }
// Core logic - no loggingfn compute_fee(amount: u256) -> u256 uses (config: Config) { amount * config.fee_rate / 10000}
// With logging wrapperfn compute_fee_logged(amount: u256) -> u256 uses (config: Config, mut log: Log){ let fee = compute_fee(amount) log.emit(FeeComputed { amount, fee }) fee}Comparison with Implicit Logging
Section titled “Comparison with Implicit Logging”| Aspect | Fe (Explicit) | Implicit Logging |
|---|---|---|
| Signature | Shows uses (mut log: Log) | No indication |
| Testing | Easy to mock | Harder to intercept |
| Composition | Fine-grained control | All-or-nothing |
| Refactoring | Compiler catches missing effects | Silent failures |
Summary
Section titled “Summary”| Concept | Description |
|---|---|
pub struct Log {} | Define a log effect type |
uses (mut log: Log) | Declare logging capability |
log.emit(...) | Emit an event |
| Effect propagation | Callers must declare effects of callees |
Handler uses | Bind effect in contract handlers |
Explicit logging effects make your contract’s behavior transparent. Every function signature tells the full story of what it can do.