Skip to content

feat(logger): implement configurable logging system #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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