Wallet debug logs (#2307)

* Add wallet debug logs

* Add checkbox to toggle diagnostics

* Require authentication for /wallets/debug

* Update debug log messages

* Use me.privates.diagnostics as source of truth
This commit is contained in:
ekzyis 2025-07-23 17:42:14 +02:00 committed by GitHub
parent 2913e9a9b5
commit 243b094fcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 127 additions and 45 deletions

View File

@ -919,6 +919,14 @@ export default {
await models.user.update({ where: { id: me.id }, data: { hideWalletRecvPrompt: true } }) await models.user.update({ where: { id: me.id }, data: { hideWalletRecvPrompt: true } })
return true return true
},
setDiagnostics: async (parent, { diagnostics }, { me, models }) => {
if (!me) {
throw new GqlAuthenticationError()
}
await models.user.update({ where: { id: me.id }, data: { diagnostics } })
return diagnostics
} }
}, },

View File

@ -56,6 +56,7 @@ export default gql`
generateApiKey(id: ID!): String generateApiKey(id: ID!): String
deleteApiKey(id: ID!): User deleteApiKey(id: ID!): User
disableFreebies: Boolean disableFreebies: Boolean
setDiagnostics(diagnostics: Boolean!): Boolean
} }
type User { type User {
@ -86,7 +87,6 @@ export default gql`
input SettingsInput { input SettingsInput {
autoDropBolt11s: Boolean! autoDropBolt11s: Boolean!
diagnostics: Boolean @deprecated
noReferralLinks: Boolean! noReferralLinks: Boolean!
fiatCurrency: String! fiatCurrency: String!
satsFilter: Int! satsFilter: Int!
@ -155,12 +155,12 @@ export default gql`
hasInvites: Boolean! hasInvites: Boolean!
apiKeyEnabled: Boolean! apiKeyEnabled: Boolean!
showPassphrase: Boolean! showPassphrase: Boolean!
diagnostics: Boolean!
""" """
mirrors SettingsInput mirrors SettingsInput
""" """
autoDropBolt11s: Boolean! autoDropBolt11s: Boolean!
diagnostics: Boolean @deprecated
noReferralLinks: Boolean! noReferralLinks: Boolean!
fiatCurrency: String! fiatCurrency: String!
satsFilter: Int! satsFilter: Int!

View File

@ -11,7 +11,7 @@ const typeDefs = gql`
wallets: [WalletOrTemplate!]! wallets: [WalletOrTemplate!]!
wallet(id: ID, name: String): WalletOrTemplate wallet(id: ID, name: String): WalletOrTemplate
walletSettings: WalletSettings! walletSettings: WalletSettings!
walletLogs(protocolId: Int, cursor: String): WalletLogs! walletLogs(protocolId: Int, cursor: String, debug: Boolean): WalletLogs!
failedInvoices: [Invoice!]! failedInvoices: [Invoice!]!
} }
@ -21,7 +21,7 @@ const typeDefs = gql`
cancelInvoice(hash: String!, hmac: String, userCancel: Boolean): Invoice! cancelInvoice(hash: String!, hmac: String, userCancel: Boolean): Invoice!
dropBolt11(hash: String!): Boolean dropBolt11(hash: String!): Boolean
removeWallet(id: ID!): Boolean removeWallet(id: ID!): Boolean
deleteWalletLogs(protocolId: Int): Boolean deleteWalletLogs(protocolId: Int, debug: Boolean): Boolean
setWalletPriorities(priorities: [WalletPriorityUpdate!]!): Boolean setWalletPriorities(priorities: [WalletPriorityUpdate!]!): Boolean
buyCredits(credits: Int!): BuyCreditsPaidAction! buyCredits(credits: Int!): BuyCreditsPaidAction!
@ -44,7 +44,7 @@ const typeDefs = gql`
resetWallets(newKeyHash: String!): Boolean resetWallets(newKeyHash: String!): Boolean
disablePassphraseExport: Boolean disablePassphraseExport: Boolean
setWalletSettings(settings: WalletSettingsInput!): Boolean setWalletSettings(settings: WalletSettingsInput!): Boolean
addWalletLog(protocolId: Int!, level: String!, message: String!, timestamp: Date!, invoiceId: Int): Boolean addWalletLog(protocolId: Int, level: String!, message: String!, timestamp: Date!, invoiceId: Int): Boolean
} }
type BuyCreditsResult { type BuyCreditsResult {

View File

@ -51,6 +51,7 @@ ${STREAK_FIELDS}
vaultKeyHashUpdatedAt vaultKeyHashUpdatedAt
walletsUpdatedAt walletsUpdatedAt
showPassphrase showPassphrase
diagnostics
} }
optional { optional {
isContributor isContributor
@ -392,3 +393,9 @@ export const MY_SUBSCRIBED_SUBS = gql`
} }
} }
` `
export const SET_DIAGNOSTICS = gql`
mutation setDiagnostics($diagnostics: Boolean!) {
setDiagnostics(diagnostics: $diagnostics)
}
`

View File

@ -1,7 +1,7 @@
import { getGetServerSideProps } from '@/api/ssrApollo' import { getGetServerSideProps } from '@/api/ssrApollo'
import { WalletLayout, WalletLayoutHeader, WalletDebugSettings } from '@/wallets/client/components' import { WalletLayout, WalletLayoutHeader, WalletDebugSettings, WalletLogs } from '@/wallets/client/components'
export const getServerSideProps = getGetServerSideProps({}) export const getServerSideProps = getGetServerSideProps({ authRequired: true })
export default function WalletDebug () { export default function WalletDebug () {
return ( return (
@ -9,6 +9,7 @@ export default function WalletDebug () {
<div className='py-5 mx-auto w-100' style={{ maxWidth: '600px' }}> <div className='py-5 mx-auto w-100' style={{ maxWidth: '600px' }}>
<WalletLayoutHeader>wallet debug</WalletLayoutHeader> <WalletLayoutHeader>wallet debug</WalletLayoutHeader>
<WalletDebugSettings /> <WalletDebugSettings />
<WalletLogs className='mt-3' debug />
</div> </div>
</WalletLayout> </WalletLayout>
) )

View File

@ -117,7 +117,7 @@ model User {
followees UserSubscription[] @relation("followee") followees UserSubscription[] @relation("followee")
hideWelcomeBanner Boolean @default(false) hideWelcomeBanner Boolean @default(false)
hideWalletRecvPrompt Boolean @default(false) hideWalletRecvPrompt Boolean @default(false)
diagnostics Boolean @default(false) @ignore diagnostics Boolean @default(false)
hideIsContributor Boolean @default(false) hideIsContributor Boolean @default(false)
lnAddr String? lnAddr String?
autoWithdrawMaxFeePercent Float? autoWithdrawMaxFeePercent Float?

View File

@ -1,7 +1,7 @@
import { formatBytes } from '@/lib/format' import { formatBytes } from '@/lib/format'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useKeyHash, useKeyUpdatedAt } from '@/wallets/client/context' import { useKeyHash, useKeyUpdatedAt } from '@/wallets/client/context'
import { useRemoteKeyHash, useRemoteKeyHashUpdatedAt, useWalletsUpdatedAt } from '@/wallets/client/hooks' import { useDiagnostics, useRemoteKeyHash, useRemoteKeyHashUpdatedAt, useWalletsUpdatedAt } from '@/wallets/client/hooks'
import { timeSince } from '@/lib/time' import { timeSince } from '@/lib/time'
export function WalletDebugSettings () { export function WalletDebugSettings () {
@ -13,6 +13,7 @@ export function WalletDebugSettings () {
const [persistent, setPersistent] = useState(null) const [persistent, setPersistent] = useState(null)
const [quota, setQuota] = useState(null) const [quota, setQuota] = useState(null)
const [usage, setUsage] = useState(null) const [usage, setUsage] = useState(null)
const [diagnostics, setDiagnostics] = useDiagnostics()
useEffect(() => { useEffect(() => {
async function init () { async function init () {
@ -59,6 +60,14 @@ export function WalletDebugSettings () {
<div className='text-end' suppressHydrationWarning> <div className='text-end' suppressHydrationWarning>
{walletsUpdatedAt ? `${timeSince(new Date(walletsUpdatedAt).getTime())} ago` : 'unknown'} {walletsUpdatedAt ? `${timeSince(new Date(walletsUpdatedAt).getTime())} ago` : 'unknown'}
</div> </div>
<div className='text-nowrap'>diagnostics:</div>
{/* not using Formik here because we want to submit immediately on change */}
<input
type='checkbox'
checked={diagnostics}
style={{ justifySelf: 'end', accentColor: 'var(--bs-primary)' }}
onChange={(e) => setDiagnostics(e.target.checked)}
/>
</div> </div>
) )
} }

View File

@ -10,9 +10,9 @@ import { ModalClosedError } from '@/components/modal'
// when we delete logs for a protocol, the cache is not updated // when we delete logs for a protocol, the cache is not updated
// so when we go to all wallet logs, we still see the deleted logs until the query is refetched // so when we go to all wallet logs, we still see the deleted logs until the query is refetched
export function WalletLogs ({ protocol, className }) { export function WalletLogs ({ protocol, className, debug }) {
const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol) const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol, debug)
const deleteLogs = useDeleteWalletLogs(protocol) const deleteLogs = useDeleteWalletLogs(protocol, debug)
const onDelete = useCallback(async () => { const onDelete = useCallback(async () => {
try { try {
@ -73,8 +73,11 @@ export function LogMessage ({ tag, level, message, context, ts }) {
case 'warning': case 'warning':
level = 'warn' level = 'warn'
className = 'text-warning'; break className = 'text-warning'; break
case 'info':
className = 'text-info'; break
case 'debug':
default: default:
className = 'text-info' className = 'text-muted'; break
} }
const filtered = context const filtered = context

View File

@ -6,7 +6,8 @@ import useInvoice from '@/components/use-invoice'
import { useMe } from '@/components/me' import { useMe } from '@/components/me'
import { import {
useWalletsQuery, useWalletPayment, useGenerateRandomKey, useSetKey, useLoadKey, useLoadOldKey, useWalletsQuery, useWalletPayment, useGenerateRandomKey, useSetKey, useLoadKey, useLoadOldKey,
useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey,
useWalletLogger
} from '@/wallets/client/hooks' } from '@/wallets/client/hooks'
import { WalletConfigurationError } from '@/wallets/client/errors' import { WalletConfigurationError } from '@/wallets/client/errors'
import { SET_WALLETS, WRONG_KEY, KEY_MATCH, useWalletsDispatch, WALLETS_QUERY_ERROR, KEY_STORAGE_UNAVAILABLE } from '@/wallets/client/context' import { SET_WALLETS, WRONG_KEY, KEY_MATCH, useWalletsDispatch, WALLETS_QUERY_ERROR, KEY_STORAGE_UNAVAILABLE } from '@/wallets/client/context'
@ -108,6 +109,8 @@ export function useKeyInit () {
const dispatch = useWalletsDispatch() const dispatch = useWalletsDispatch()
const wrongKey = useIsWrongKey() const wrongKey = useIsWrongKey()
const logger = useWalletLogger()
useEffect(() => { useEffect(() => {
if (typeof window.indexedDB === 'undefined') { if (typeof window.indexedDB === 'undefined') {
dispatch({ type: KEY_STORAGE_UNAVAILABLE }) dispatch({ type: KEY_STORAGE_UNAVAILABLE })
@ -165,17 +168,20 @@ export function useKeyInit () {
const read = tx.objectStore('vault').get('key') const read = tx.objectStore('vault').get('key')
read.onerror = () => { read.onerror = () => {
logger.debug('key init: error reading key: ' + read.error)
reject(read.error) reject(read.error)
} }
read.onsuccess = () => { read.onsuccess = () => {
if (read.result) { if (read.result) {
// return key+hash found in db // return key+hash found in db
logger.debug('key init: key found in IndexedDB')
return resolve(read.result) return resolve(read.result)
} }
if (oldKeyAndHash) { if (oldKeyAndHash) {
// return key+hash found in old db // return key+hash found in old db
logger.debug('key init: key found in old IndexedDB')
return resolve(oldKeyAndHash) return resolve(oldKeyAndHash)
} }
@ -184,11 +190,13 @@ export function useKeyInit () {
const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash, updatedAt }, 'key') const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash, updatedAt }, 'key')
write.onerror = () => { write.onerror = () => {
logger.debug('key init: error writing new random key: ' + write.error)
reject(write.error) reject(write.error)
} }
write.onsuccess = (event) => { write.onsuccess = (event) => {
// return key+hash we just wrote to db // return key+hash we just wrote to db
logger.debug('key init: saved new random key')
resolve({ key: randomKey, hash: randomHash, updatedAt }) resolve({ key: randomKey, hash: randomHash, updatedAt })
} }
} }
@ -196,11 +204,12 @@ export function useKeyInit () {
await setKey({ key, hash, updatedAt }, { updateDb: false }) await setKey({ key, hash, updatedAt }, { updateDb: false })
} catch (err) { } catch (err) {
console.error('key init failed:', err) logger.debug('key init: error: ' + err)
console.error('key init: error:', err)
} }
} }
keyInit() keyInit()
}, [me?.id, db, generateRandomKey, loadOldKey, setKey, loadKey]) }, [me?.id, db, generateRandomKey, loadOldKey, setKey, loadKey, logger])
} }
// TODO(wallet-v2): remove migration code // TODO(wallet-v2): remove migration code

View File

@ -229,14 +229,14 @@ export const SET_WALLET_SETTINGS = gql`
` `
export const ADD_WALLET_LOG = gql` export const ADD_WALLET_LOG = gql`
mutation AddWalletLog($protocolId: Int!, $level: String!, $message: String!, $timestamp: Date!, $invoiceId: Int) { mutation AddWalletLog($protocolId: Int, $level: String!, $message: String!, $timestamp: Date!, $invoiceId: Int) {
addWalletLog(protocolId: $protocolId, level: $level, message: $message, timestamp: $timestamp, invoiceId: $invoiceId) addWalletLog(protocolId: $protocolId, level: $level, message: $message, timestamp: $timestamp, invoiceId: $invoiceId)
} }
` `
export const WALLET_LOGS = gql` export const WALLET_LOGS = gql`
query WalletLogs($protocolId: Int, $cursor: String) { query WalletLogs($protocolId: Int, $cursor: String, $debug: Boolean) {
walletLogs(protocolId: $protocolId, cursor: $cursor) { walletLogs(protocolId: $protocolId, cursor: $cursor, debug: $debug) {
entries { entries {
id id
level level
@ -253,7 +253,7 @@ export const WALLET_LOGS = gql`
` `
export const DELETE_WALLET_LOGS = gql` export const DELETE_WALLET_LOGS = gql`
mutation DeleteWalletLogs($protocolId: Int) { mutation DeleteWalletLogs($protocolId: Int, $debug: Boolean) {
deleteWalletLogs(protocolId: $protocolId) deleteWalletLogs(protocolId: $protocolId, debug: $debug)
} }
` `

View File

@ -9,7 +9,7 @@ import bip39Words from '@/lib/bip39-words'
import { Form, PasswordInput, SubmitButton } from '@/components/form' import { Form, PasswordInput, SubmitButton } from '@/components/form'
import { object, string } from 'yup' import { object, string } from 'yup'
import { SET_KEY, useKey, useKeyHash, useWalletsDispatch } from '@/wallets/client/context' import { SET_KEY, useKey, useKeyHash, useWalletsDispatch } from '@/wallets/client/context'
import { useDisablePassphraseExport, useUpdateKeyHash, useWalletEncryptionUpdate, useWalletReset } from '@/wallets/client/hooks' import { useDisablePassphraseExport, useUpdateKeyHash, useWalletEncryptionUpdate, useWalletLogger, useWalletReset } from '@/wallets/client/hooks'
import { useToast } from '@/components/toast' import { useToast } from '@/components/toast'
export class CryptoKeyRequiredError extends Error { export class CryptoKeyRequiredError extends Error {
@ -41,6 +41,7 @@ export function useSetKey () {
const { set } = useIndexedDB() const { set } = useIndexedDB()
const dispatch = useWalletsDispatch() const dispatch = useWalletsDispatch()
const updateKeyHash = useUpdateKeyHash() const updateKeyHash = useUpdateKeyHash()
const logger = useWalletLogger()
return useCallback(async ({ key, hash, updatedAt }, { updateDb = true } = {}) => { return useCallback(async ({ key, hash, updatedAt }, { updateDb = true } = {}) => {
if (updateDb) { if (updateDb) {
@ -49,7 +50,8 @@ export function useSetKey () {
} }
await updateKeyHash(hash) await updateKeyHash(hash)
dispatch({ type: SET_KEY, key, hash, updatedAt }) dispatch({ type: SET_KEY, key, hash, updatedAt })
}, [set, dispatch, updateKeyHash]) logger.debug(`using key ${hash}`)
}, [set, dispatch, updateKeyHash, logger])
} }
export function useEncryption () { export function useEncryption () {
@ -159,12 +161,14 @@ export function useSavePassphrase () {
const setKey = useSetKey() const setKey = useSetKey()
const salt = useKeySalt() const salt = useKeySalt()
const disablePassphraseExport = useDisablePassphraseExport() const disablePassphraseExport = useDisablePassphraseExport()
const logger = useWalletLogger()
return useCallback(async ({ passphrase }) => { return useCallback(async ({ passphrase }) => {
logger.debug('passphrase entered')
const { key, hash } = await deriveKey(passphrase, salt) const { key, hash } = await deriveKey(passphrase, salt)
await setKey({ key, hash }) await setKey({ key, hash })
await disablePassphraseExport() await disablePassphraseExport()
}, [setKey, disablePassphraseExport]) }, [setKey, disablePassphraseExport, logger])
} }
export function useResetPassphrase () { export function useResetPassphrase () {
@ -173,19 +177,22 @@ export function useResetPassphrase () {
const generateRandomKey = useGenerateRandomKey() const generateRandomKey = useGenerateRandomKey()
const setKey = useSetKey() const setKey = useSetKey()
const toaster = useToast() const toaster = useToast()
const logger = useWalletLogger()
const resetPassphrase = useCallback((close) => const resetPassphrase = useCallback((close) =>
async () => { async () => {
try { try {
logger.debug('passphrase reset')
const { key: randomKey, hash } = await generateRandomKey() const { key: randomKey, hash } = await generateRandomKey()
await setKey({ key: randomKey, hash }) await setKey({ key: randomKey, hash })
await walletReset({ newKeyHash: hash }) await walletReset({ newKeyHash: hash })
close() close()
} catch (err) { } catch (err) {
logger.debug('failed to reset passphrase: ' + err)
console.error('failed to reset passphrase:', err) console.error('failed to reset passphrase:', err)
toaster.error('failed to reset passphrase') toaster.error('failed to reset passphrase')
} }
}, [walletReset, generateRandomKey, setKey, toaster]) }, [walletReset, generateRandomKey, setKey, toaster, logger])
return useCallback(async () => { return useCallback(async () => {
showModal(close => ( showModal(close => (

View File

@ -0,0 +1,23 @@
import { useMe } from '@/components/me'
import { useToast } from '@/components/toast'
import { SET_DIAGNOSTICS } from '@/fragments/users'
import { useMutation } from '@apollo/client'
import { useCallback } from 'react'
export function useDiagnostics () {
const { me, refreshMe } = useMe()
const [mutate] = useMutation(SET_DIAGNOSTICS)
const toaster = useToast()
const setDiagnostics = useCallback(async (diagnostics) => {
try {
await mutate({ variables: { diagnostics } })
await refreshMe()
} catch (err) {
console.error('failed to toggle diagnostics:', err)
toaster.danger('failed to toggle diagnostics')
}
}, [mutate, toaster, refreshMe])
return [me?.privates?.diagnostics ?? false, setDiagnostics]
}

View File

@ -6,3 +6,4 @@ export * from './wallet'
export * from './crypto' export * from './crypto'
export * from './query' export * from './query'
export * from './logger' export * from './logger'
export * from './diagnostics'

View File

@ -6,6 +6,7 @@ import { ModalClosedError, useShowModal } from '@/components/modal'
import { useToast } from '@/components/toast' import { useToast } from '@/components/toast'
import { FAST_POLL_INTERVAL } from '@/lib/constants' import { FAST_POLL_INTERVAL } from '@/lib/constants'
import { isTemplate } from '@/wallets/lib/util' import { isTemplate } from '@/wallets/lib/util'
import { useDiagnostics } from '@/wallets/client/hooks/diagnostics'
const TemplateLogsContext = createContext({}) const TemplateLogsContext = createContext({})
@ -38,17 +39,18 @@ export function TemplateLogsProvider ({ children }) {
export function useWalletLoggerFactory () { export function useWalletLoggerFactory () {
const { addTemplateLog } = useContext(TemplateLogsContext) const { addTemplateLog } = useContext(TemplateLogsContext)
const [addWalletLog] = useMutation(ADD_WALLET_LOG) const [addWalletLog] = useMutation(ADD_WALLET_LOG)
const [diagnostics] = useDiagnostics()
const log = useCallback(({ protocol, level, message, invoiceId }) => { const log = useCallback(({ protocol, level, message, invoiceId }) => {
console[mapLevelToConsole(level)](`[${protocol.name}] ${message}`) console[mapLevelToConsole(level)](`[${protocol ? protocol.name : 'system'}] ${message}`)
if (isTemplate(protocol)) { if (protocol && isTemplate(protocol)) {
// this is a template, so there's no protocol yet to which we could attach logs in the db // this is a template, so there's no protocol yet to which we could attach logs in the db
addTemplateLog?.({ level, message }) addTemplateLog?.({ level, message })
return return
} }
return addWalletLog({ variables: { protocolId: Number(protocol.id), level, message, invoiceId, timestamp: new Date() } }) return addWalletLog({ variables: { protocolId: protocol ? Number(protocol.id) : null, level, message, invoiceId, timestamp: new Date() } })
.catch(err => { .catch(err => {
console.error('error adding wallet log:', err) console.error('error adding wallet log:', err)
}) })
@ -68,9 +70,13 @@ export function useWalletLoggerFactory () {
}, },
warn: (message) => { warn: (message) => {
log({ protocol, level: 'WARN', message, invoiceId }) log({ protocol, level: 'WARN', message, invoiceId })
},
debug: (message) => {
if (!diagnostics) return
log({ protocol, level: 'DEBUG', message, invoiceId })
} }
} }
}, [log]) }, [log, diagnostics])
} }
export function useWalletLogger (protocol) { export function useWalletLogger (protocol) {
@ -78,7 +84,7 @@ export function useWalletLogger (protocol) {
return useMemo(() => loggerFactory(protocol), [loggerFactory, protocol]) return useMemo(() => loggerFactory(protocol), [loggerFactory, protocol])
} }
export function useWalletLogs (protocol) { export function useWalletLogs (protocol, debug) {
const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext) const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext)
const [cursor, setCursor] = useState(null) const [cursor, setCursor] = useState(null)
@ -90,7 +96,7 @@ export function useWalletLogs (protocol) {
const protocolId = protocol ? Number(protocol.id) : undefined const protocolId = protocol ? Number(protocol.id) : undefined
const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, { const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, {
variables: { protocolId }, variables: { protocolId, debug },
skip, skip,
fetchPolicy: 'network-only' fetchPolicy: 'network-only'
}) })
@ -99,7 +105,11 @@ export function useWalletLogs (protocol) {
if (skip) return if (skip) return
const interval = setInterval(async () => { const interval = setInterval(async () => {
const { data } = await fetchLogs({ variables: { protocolId } }) const { data, error } = await fetchLogs({ variables: { protocolId, debug } })
if (error) {
console.error('failed to fetch wallet logs:', error.message)
return
}
const { entries: updatedLogs, cursor } = data.walletLogs const { entries: updatedLogs, cursor } = data.walletLogs
setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs]) setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs])
if (!called) { if (!called) {
@ -108,14 +118,14 @@ export function useWalletLogs (protocol) {
}, FAST_POLL_INTERVAL) }, FAST_POLL_INTERVAL)
return () => clearInterval(interval) return () => clearInterval(interval)
}, [fetchLogs, called, skip]) }, [fetchLogs, called, skip, debug])
const loadMore = useCallback(async () => { const loadMore = useCallback(async () => {
const { data } = await fetchLogs({ variables: { protocolId, cursor } }) const { data } = await fetchLogs({ variables: { protocolId, cursor, debug } })
const { entries: cursorLogs, cursor: newCursor } = data.walletLogs const { entries: cursorLogs, cursor: newCursor } = data.walletLogs
setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))]) setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))])
setCursor(newCursor) setCursor(newCursor)
}, [fetchLogs, cursor, protocolId]) }, [fetchLogs, cursor, protocolId, debug])
const clearLogs = useCallback(() => { const clearLogs = useCallback(() => {
setLogs([]) setLogs([])
@ -149,7 +159,7 @@ function mapLevelToConsole (level) {
} }
} }
export function useDeleteWalletLogs (protocol) { export function useDeleteWalletLogs (protocol, debug) {
const showModal = useShowModal() const showModal = useShowModal()
return useCallback(async () => { return useCallback(async () => {
@ -174,6 +184,7 @@ export function useDeleteWalletLogs (protocol) {
protocol={protocol} protocol={protocol}
onClose={onClose} onClose={onClose}
onDelete={onDelete} onDelete={onDelete}
debug={debug}
/> />
) )
}, { onClose }) }, { onClose })
@ -181,7 +192,7 @@ export function useDeleteWalletLogs (protocol) {
}, [showModal]) }, [showModal])
} }
function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) { function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete, debug }) {
const toaster = useToast() const toaster = useToast()
const [deleteWalletLogs] = useMutation(DELETE_WALLET_LOGS) const [deleteWalletLogs] = useMutation(DELETE_WALLET_LOGS)
@ -190,9 +201,9 @@ function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
if (protocol && isTemplate(protocol)) return if (protocol && isTemplate(protocol)) return
await deleteWalletLogs({ await deleteWalletLogs({
variables: { protocolId: protocol ? Number(protocol.id) : undefined } variables: { protocolId: protocol ? Number(protocol.id) : undefined, debug }
}) })
}, [protocol, deleteWalletLogs]) }, [protocol, deleteWalletLogs, debug])
const onClick = useCallback(async () => { const onClick = useCallback(async () => {
try { try {
@ -206,7 +217,7 @@ function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
} }
}, [onClose, deleteLogs, toaster]) }, [onClose, deleteLogs, toaster])
let prompt = 'Do you really want to delete all wallet logs?' let prompt = debug ? 'Do you really want to delete all debug logs?' : 'Do you really want to delete all logs?'
if (protocol) { if (protocol) {
prompt = 'Do you really want to delete all logs of this protocol?' prompt = 'Do you really want to delete all logs of this protocol?'
} }

View File

@ -56,7 +56,8 @@ export function walletLogger ({
ok: (message, context) => log('OK')(message, context), ok: (message, context) => log('OK')(message, context),
info: (message, context) => log('INFO')(message, context), info: (message, context) => log('INFO')(message, context),
error: (message, context) => log('ERROR')(message, context), error: (message, context) => log('ERROR')(message, context),
warn: (message, context) => log('WARNING')(message, context) warn: (message, context) => log('WARNING')(message, context),
debug: (message, context) => log('DEBUG')(message, context)
} }
} }

View File

@ -237,7 +237,7 @@ export async function removeWalletProtocol (parent, { id }, { me, models, tx })
return await (tx ? transaction(tx) : models.$transaction(transaction)) return await (tx ? transaction(tx) : models.$transaction(transaction))
} }
async function walletLogs (parent, { protocolId, cursor }, { me, models }) { async function walletLogs (parent, { protocolId, cursor, debug }, { me, models }) {
if (!me) throw new GqlAuthenticationError() if (!me) throw new GqlAuthenticationError()
const decodedCursor = decodeCursor(cursor) const decodedCursor = decodeCursor(cursor)
@ -248,7 +248,8 @@ async function walletLogs (parent, { protocolId, cursor }, { me, models }) {
protocolId, protocolId,
createdAt: { createdAt: {
lt: decodedCursor.time lt: decodedCursor.time
} },
level: debug ? 'DEBUG' : { not: 'DEBUG' }
}, },
orderBy: { orderBy: {
createdAt: 'desc' createdAt: 'desc'
@ -295,13 +296,14 @@ async function addWalletLog (parent, { protocolId, level, message, timestamp, in
return true return true
} }
async function deleteWalletLogs (parent, { protocolId }, { me, models }) { async function deleteWalletLogs (parent, { protocolId, debug }, { me, models }) {
if (!me) throw new GqlAuthenticationError() if (!me) throw new GqlAuthenticationError()
await models.walletLog.deleteMany({ await models.walletLog.deleteMany({
where: { where: {
userId: me.id, userId: me.id,
protocolId protocolId,
level: debug ? 'DEBUG' : { not: 'DEBUG' }
} }
}) })