Skip to content

Commit

Permalink
feat(logger): implement configurable logging system (#264)
Browse files Browse the repository at this point in the history
* refactor: replace every console.log with console.info

These will all be replaced with the debugging logger that will be introduced in this branch.

* refactor(*): update logging system for wallet clients

- Replaced `console.info`, `console.warn`, and `console.error` calls with appropriate logger methods (`this.logger.info`, `this.logger.warn`, `this.logger.error`, `this.logger.debug`)
- Wrapped `signTransactions` method logic in a try/catch block to handle and log errors consistently
- Ensured that all wallet clients leverage the scoped logger created in the `BaseWallet` class
- Added more logging statements to provide better visibility into the wallets' operations

* refactor(wallets): enhance signTransactions logging

- Add detailed logging to `signTransactions` methods across wallet clients.
- Include debug logs for transaction processing steps and results.

* refactor(WalletManager): update createAlgodClient method

This commit refactors the createAlgodClient method in the WalletManager class to improve its functionality and logging. The main changes are:

- Update method signature to accept `networkId` instead of `config`
- Move network config retrieval inside the method
- Adjust logging to use the passed `networkId` parameter

These changes should resolve issues related to accessing the active network property on initialization and improve the overall structure of the method.

* test(wallets): update wallet tests to use mocked Logger

This commit updates the wallet test files to use the new `Logger` system instead of `console` logging. The main changes include:

- Update mock setup for logger in all wallet test files
- Replace `console.warn` and `console.error` expectations with `logger.warn` and `logger.error` in relevant tests
- Adjust test assertions to check for `logger` calls instead of `console` calls
- Remove unused `console` mocks from test files

* refactor(store): replace console with logger in store functions

- Replace `console.warn` calls with `logger.warn` in store functions
- Update tests to mock the `logger` to suppress output during test runs

* test(logger): add unit tests for Logger class

- Create new test file for `Logger` class
- Implement tests for singleton instance, log levels, and scoped logging
- Add `setIsClient` method to Logger for testing purposes
- Ensure tests cover all logging scenarios and respect log levels
  • Loading branch information
drichar authored Sep 18, 2024
1 parent 98b62a8 commit 53057b5
Show file tree
Hide file tree
Showing 31 changed files with 1,084 additions and 457 deletions.
2 changes: 1 addition & 1 deletion examples/vanilla-ts/src/WalletComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class WalletComponent {
this.element = document.createElement('div')

this.unsubscribe = wallet.subscribe((state) => {
console.log('State change:', state)
console.info('[App] State change:', state)
this.render()
})

Expand Down
87 changes: 87 additions & 0 deletions packages/use-wallet/src/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Logger, LogLevel } from 'src/logger'

describe('Logger', () => {
let logger: Logger
let consoleWarnSpy: ReturnType<typeof vi.spyOn>
let consoleErrorSpy: ReturnType<typeof vi.spyOn>
let consoleInfoSpy: ReturnType<typeof vi.spyOn>

beforeEach(() => {
// Reset singleton instance
Logger['instance'] = null
logger = Logger.getInstance()

// Set isClient to true for testing
logger.setIsClient(true)

// Mock console methods
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {})

// Set log level to DEBUG
Logger.setLevel(LogLevel.DEBUG)
})

afterEach(() => {
vi.restoreAllMocks()
})

it('should return the same instance', () => {
const logger1 = Logger.getInstance()
const logger2 = Logger.getInstance()
expect(logger1).toBe(logger2)
})

it('should log at all levels when set to DEBUG', () => {
logger.debug('Test debug')
logger.info('Test info')
logger.warn('Test warn')
logger.error('Test error')

expect(consoleInfoSpy).toHaveBeenCalledWith('Test debug')
expect(consoleInfoSpy).toHaveBeenCalledWith('Test info')
expect(consoleWarnSpy).toHaveBeenCalledWith('Test warn')
expect(consoleErrorSpy).toHaveBeenCalledWith('Test error')
})

it('should respect log level changes', () => {
Logger.setLevel(LogLevel.WARN)

logger.debug('Test debug')
logger.info('Test info')
logger.warn('Test warn')
logger.error('Test error')

expect(consoleInfoSpy).not.toHaveBeenCalled()
expect(consoleWarnSpy).toHaveBeenCalledWith('Test warn')
expect(consoleErrorSpy).toHaveBeenCalledWith('Test error')
})

it('should only log errors when set to ERROR level', () => {
Logger.setLevel(LogLevel.ERROR)

logger.debug('Test debug')
logger.info('Test info')
logger.warn('Test warn')
logger.error('Test error')

expect(consoleInfoSpy).not.toHaveBeenCalled()
expect(consoleWarnSpy).not.toHaveBeenCalled()
expect(consoleErrorSpy).toHaveBeenCalledWith('Test error')
})

it('should log with scope when using scoped logger', () => {
const scopedLogger = logger.createScopedLogger('MyScope')

scopedLogger.debug('Scoped debug')
scopedLogger.info('Scoped info')
scopedLogger.warn('Scoped warn')
scopedLogger.error('Scoped error')

expect(consoleInfoSpy).toHaveBeenCalledWith('[MyScope] Scoped debug')
expect(consoleInfoSpy).toHaveBeenCalledWith('[MyScope] Scoped info')
expect(consoleWarnSpy).toHaveBeenCalledWith('[MyScope] Scoped warn')
expect(consoleErrorSpy).toHaveBeenCalledWith('[MyScope] Scoped error')
})
})
66 changes: 50 additions & 16 deletions packages/use-wallet/src/__tests__/manager.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Store } from '@tanstack/store'
import algosdk from 'algosdk'
import { logger } from 'src/logger'
import { NetworkId } from 'src/network'
import { LOCAL_STORAGE_KEY, State, defaultState } from 'src/store'
import { WalletManager } from 'src/manager'
Expand All @@ -8,7 +10,29 @@ import { DeflyWallet } from 'src/wallets/defly'
import { KibisisWallet } from 'src/wallets/kibisis'
import { WalletId } from 'src/wallets/types'
import type { Mock } from 'vitest'
import { Algodv2 } from 'algosdk'

vi.mock('src/logger', () => {
const mockLogger = {
createScopedLogger: vi.fn().mockReturnValue({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
})
}
return {
Logger: {
setLevel: vi.fn()
},
LogLevel: {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3
},
logger: mockLogger
}
})

// Mock storage adapter
vi.mock('src/storage', () => ({
Expand All @@ -22,10 +46,20 @@ vi.mock('src/storage', () => ({
vi.spyOn(console, 'info').mockImplementation(() => {})

// Mock console.warn
const mockConsoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {})

// Mock console.error
const mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
let mockLoggerWarn: Mock
let mockLoggerError: Mock

beforeEach(() => {
vi.clearAllMocks()
mockLoggerWarn = vi.fn()
mockLoggerError = vi.fn()
vi.mocked(logger.createScopedLogger).mockReturnValue({
debug: vi.fn(),
info: vi.fn(),
warn: mockLoggerWarn,
error: mockLoggerError
})
})

vi.mock('src/wallets/defly', () => ({
DeflyWallet: class DeflyWallet {
Expand Down Expand Up @@ -258,7 +292,7 @@ describe('WalletManager', () => {
},
activeWallet: WalletId.KIBISIS,
activeNetwork: NetworkId.BETANET,
algodClient: new Algodv2('', 'https://betanet-api.4160.nodely.dev/')
algodClient: new algosdk.Algodv2('', 'https://betanet-api.4160.nodely.dev/')
}
})

Expand All @@ -285,16 +319,16 @@ describe('WalletManager', () => {
})

it('returns null and logs warning and error if persisted state is invalid', () => {
const invalidState = { foo: 'bar' }
// @ts-expect-error - Set invalid state
mockInitialState = invalidState
const invalidState = { invalid: 'state' }
vi.mocked(StorageAdapter.getItem).mockReturnValueOnce(JSON.stringify(invalidState))

const manager = new WalletManager({
wallets: [WalletId.DEFLY, WalletId.KIBISIS]
})
expect(mockConsoleWarn).toHaveBeenCalledWith('[Store] Parsed state:', invalidState)
expect(mockConsoleError).toHaveBeenCalledWith(
'[Store] Could not load state from local storage: Persisted state is invalid'

expect(mockLoggerWarn).toHaveBeenCalledWith('Parsed state:', invalidState)
expect(mockLoggerError).toHaveBeenCalledWith(
'Could not load state from local storage: Persisted state is invalid'
)
// Store initializes with default state if null is returned
expect(manager.store.state).toEqual(defaultState)
Expand Down Expand Up @@ -344,7 +378,7 @@ describe('WalletManager', () => {
},
activeWallet: WalletId.KIBISIS,
activeNetwork: NetworkId.BETANET,
algodClient: new Algodv2('', 'https://betanet-api.4160.nodely.dev/')
algodClient: new algosdk.Algodv2('', 'https://betanet-api.4160.nodely.dev/')
}
})

Expand Down Expand Up @@ -490,7 +524,7 @@ describe('WalletManager', () => {
wallets: {},
activeWallet: null,
activeNetwork: NetworkId.MAINNET,
algodClient: new Algodv2('', 'https://mainnet-api.algonode.cloud')
algodClient: new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud')
}

const manager = new WalletManager({
Expand All @@ -507,7 +541,7 @@ describe('WalletManager', () => {
wallets: {},
activeWallet: null,
activeNetwork: NetworkId.MAINNET,
algodClient: new Algodv2('', 'https://mainnet-api.algonode.cloud')
algodClient: new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud')
}

const manager = new WalletManager({
Expand Down Expand Up @@ -539,7 +573,7 @@ describe('WalletManager', () => {
},
activeWallet: WalletId.PERA,
activeNetwork: NetworkId.MAINNET,
algodClient: new Algodv2('', 'https://mainnet-api.algonode.cloud')
algodClient: new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud')
}

const manager = new WalletManager({
Expand Down
10 changes: 10 additions & 0 deletions packages/use-wallet/src/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ import {
} from 'src/store'
import { WalletId } from 'src/wallets/types'

// Mock the logger
vi.mock('src/logger', () => ({
logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}
}))

describe('Mutations', () => {
let store: Store<State>

Expand Down
40 changes: 32 additions & 8 deletions packages/use-wallet/src/__tests__/wallets/custom.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { Store } from '@tanstack/store'
import algosdk from 'algosdk'
import { logger } from 'src/logger'
import { StorageAdapter } from 'src/storage'
import { LOCAL_STORAGE_KEY, State, defaultState } from 'src/store'
import { CustomProvider, CustomWallet, WalletId } from 'src/wallets'
import type { Mock } from 'vitest'

// Mock logger
vi.mock('src/logger', () => ({
logger: {
createScopedLogger: vi.fn().mockReturnValue({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
})
}
}))

// Mock storage adapter
vi.mock('src/storage', () => ({
Expand All @@ -12,10 +26,6 @@ vi.mock('src/storage', () => ({
}
}))

// Spy/suppress console output
vi.spyOn(console, 'info').mockImplementation(() => {})
vi.spyOn(console, 'warn').mockImplementation(() => {})

// Mock a custom provider
class MockProvider implements CustomProvider {
constructor() {}
Expand Down Expand Up @@ -48,6 +58,12 @@ describe('CustomWallet', () => {
let wallet: CustomWallet
let store: Store<State>
let mockInitialState: State | null = null
let mockLogger: {
debug: Mock
info: Mock
warn: Mock
error: Mock
}

const account1 = {
name: 'Account 1',
Expand All @@ -74,6 +90,14 @@ describe('CustomWallet', () => {
}
})

mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}
vi.mocked(logger.createScopedLogger).mockReturnValue(mockLogger)

store = new Store<State>(defaultState)
wallet = createWalletWithStore(store)
})
Expand All @@ -96,7 +120,7 @@ describe('CustomWallet', () => {
store,
subscribe: vi.fn()
})
).toThrowError('[Custom] Missing required option: provider')
).toThrowError('Missing required option: provider')
})
})

Expand Down Expand Up @@ -143,7 +167,7 @@ describe('CustomWallet', () => {
subscribe: vi.fn()
})

await expect(wallet.connect()).rejects.toThrowError('[Custom] Method not supported: connect')
await expect(wallet.connect()).rejects.toThrowError('Method not supported: connect')
})
})

Expand Down Expand Up @@ -305,7 +329,7 @@ describe('CustomWallet', () => {
})

await expect(wallet.signTransactions(txnGroup, indexesToSign)).rejects.toThrowError(
'[Custom] Method not supported: signTransactions'
'Method not supported: signTransactions'
)
})
})
Expand Down Expand Up @@ -349,7 +373,7 @@ describe('CustomWallet', () => {
})

await expect(wallet.transactionSigner(txnGroup, indexesToSign)).rejects.toThrowError(
'[Custom] Method not supported: transactionSigner'
'Method not supported: transactionSigner'
)
})
})
Expand Down
Loading

0 comments on commit 53057b5

Please sign in to comment.