← All reusables
withLogger
extensionInstallation
npx jsrepo add github/reatom/reusables withLogger Copy the source code below and save it to the specified file path in your project.
with-logger.ts
import { isAction, withMiddleware, type GenericExt } from '@reatom/core'
/** Log entry passed to the logger function. */
export interface LogEntry {
/** Name of the atom or action. */
name: string
/** Parameters passed to the action or setter. */
params: unknown[]
/** Result value (new state or action return value). */
result: unknown
/** Timestamp of the log entry. */
timestamp: number
}
/** Logger function type. */
export type LoggerFn = (entry: LogEntry) => void
/** Default console logger. */
export const consoleLogger: LoggerFn = (entry) => {
console.log(`[${entry.name}]`, entry.params, '->', entry.result)
}
/**
* Atom/action extension that logs all state changes and action calls.
*
* Intercepts updates via middleware and calls the provided logger function with
* details about each operation.
*
* @example
* const counter = atom(0, 'counter').extend(withLogger())
* counter.set(5) // Logs: [counter] [5] -> 5
*
* @example
* // Custom logger
* const counter = atom(0, 'counter').extend(
* withLogger((entry) => {
* analytics.track('state_change', entry)
* }),
* )
*
* @param logger - Logger function (defaults to console.log)
*/
export const withLogger = (logger: LoggerFn = consoleLogger): GenericExt =>
withMiddleware((target) => (next, ...params) => {
// Just a state reading, do nothing
if (!isAction(target) && !params.length) return next()
const result = next(...params)
logger({
name: target.name,
params,
result,
timestamp: Date.now(),
})
return result
}) Documentation
withLogger
Atom/action extension that logs all state changes and action calls.
withLogger(logger?)
Adds logging middleware that intercepts all updates and calls the provided logger function.
Parameters
| Parameter | Type | Description |
|---|---|---|
logger |
LoggerFn |
Logger function (defaults to console.log) |
LogEntry
The logger function receives a LogEntry object:
| Property | Type | Description |
|---|---|---|
name |
string |
Name of the atom or action |
params |
unknown[] |
Parameters passed to the action or setter |
result |
unknown |
Result value (new state or return value) |
timestamp |
number |
Timestamp of the log entry |
Example
import { atom } from '@reatom/core'
import { withLogger } from '#reatom/extension/with-logger'
const counter = atom(0, 'counter').extend(withLogger())
counter.set(5) // Logs: [counter] [5] -> 5
counter.set(10) // Logs: [counter] [10] -> 10
Custom logger example
import { withLogger, type LogEntry } from '#reatom/extension/with-logger'
const logs: LogEntry[] = []
const counter = atom(0, 'counter').extend(
withLogger((entry) => {
logs.push(entry)
console.log(`${entry.name} changed to ${entry.result}`)
}),
)
counter.set(42) // Logs: "counter changed to 42"
Action logging
import { action } from '@reatom/core'
import { withLogger } from '#reatom/extension/with-logger'
const greet = action((name: string) => `Hello, ${name}!`, 'greet').extend(
withLogger(),
)
greet('World') // Logs: [greet] ["World"] -> "Hello, World!"
Analytics integration
const analyticsLogger = (entry: LogEntry) => {
analytics.track('state_change', {
action: entry.name,
params: entry.params,
result: entry.result,
timestamp: entry.timestamp,
})
}
const settings = atom({ theme: 'light' }, 'settings').extend(
withLogger(analyticsLogger),
)
Composing with other extensions
import { withReset } from '#reatom/extension/with-reset'
import { withLogger } from '#reatom/extension/with-logger'
const form = atom({ name: '', email: '' }, 'form')
.extend(withReset({ name: '', email: '' }))
.extend(withLogger())
form.set({ name: 'John', email: 'john@example.com' }) // Logged
form.reset() // Also logged
Example
import { atom, action, effect } from '@reatom/core'
import { withLogger, type LogEntry } from './with-logger'
// --- Basic usage with default console logger ---
const counter = atom(0, 'counter').extend(withLogger())
counter.set(5) // Logs: [counter] [5] -> 5
counter.set(10) // Logs: [counter] [10] -> 10
// --- Custom logger function ---
const logs: LogEntry[] = []
const customLogger = (entry: LogEntry) => {
logs.push(entry)
console.log(`${entry.name} changed to ${entry.result} at ${entry.timestamp}`)
}
const message = atom('', 'message').extend(withLogger(customLogger))
message.set('Hello') // Logs with timestamp
message.set('World')
console.log('All logs:', logs)
// --- Logging actions ---
const greet = action((name: string) => `Hello, ${name}!`, 'greet').extend(
withLogger(),
)
greet('Reatom') // Logs: [greet] ["Reatom"] -> "Hello, Reatom!"
// --- Analytics integration example ---
const analyticsLogger = (entry: LogEntry) => {
// Send to analytics service
// analytics.track('state_change', {
// action: entry.name,
// params: entry.params,
// result: entry.result,
// timestamp: entry.timestamp
// })
console.log('Analytics:', entry)
}
const settings = atom({ theme: 'light' }, 'settings').extend(
withLogger(analyticsLogger),
)
settings.set({ theme: 'dark' })
// --- Conditional logging ---
const DEBUG = true // or use import.meta.env.DEV in Vite
const debugLogger = (entry: LogEntry) => {
if (DEBUG) {
console.debug(`[DEBUG] ${entry.name}:`, entry.params, '->', entry.result)
}
}
const config = atom({}, 'config').extend(withLogger(debugLogger))
// --- Composing with other extensions ---
import { withReset } from '../reset/with-reset'
const form = atom({ name: '', email: '' }, 'form')
.extend(withReset({ name: '', email: '' }))
.extend(withLogger())
form.set({ name: 'John', email: 'john@example.com' })
form.reset() // Both set and reset are logged