Skip to content
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

Wallet Logs #994

Merged
merged 36 commits into from Apr 3, 2024
Merged

Wallet Logs #994

merged 36 commits into from Apr 3, 2024

Conversation

ekzyis
Copy link
Member

@ekzyis ekzyis commented Mar 29, 2024

Description

Close #823

This PR adds a new React context: WalletLoggerContext

This context can be used with useWalletLogger to show wallet logs at /wallet/logs.

Screenshots

desktop (72ca741)

2024-04-03-175341_1920x1080_scrot

2024-04-03-175328_1920x1080_scrot

mobile (72ca741)

localhost_3000_wallet_logs(iPhone SE) (4)

localhost_3000_wallet_logs(iPhone SE) (5)

UX video (72ca741)
2024-04-03.17-52-10.mp4

How to test

Sending via NWC

  1. Clone and build nostr-wallet-connect-lnd
  2. Copy TLS certificate and admin macaroon from stacker_lnd container:
$ docker cp stacker_lnd:/home/lnd/.lnd/tls.cert tls.cert
$ docker cp stacker_lnd:/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon admin.macaroon
  1. Run nostr-wallet-connect-lnd:
$ RUST_LOG=info nostr-wallet-connect-lnd \
  --relay wss://relay.damus.io --network regtest \
  --lnd-host 127.0.0.1 --lnd-port 20010 \
  --cert-file stacker_lnd_tls.cert --macaroon-file stacker_lnd_admin.macaroon \
  --keys-file $(pwd)/keys.json
  1. Copy and paste nostr+walletconnect:// URL

Receiving via LND

Use stacker_lnd:10009 and xxd -p -c0 on invoice.macaroon and tls.cert from container.

Open Questions

1. Where should link to full logs be?

It's currently at /wallet below wallet history:

click to show screenshot

localhost_3000_wallet_logs(iPhone SE) (2)

I thought about putting it into attached wallets since it's logs about attached wallets but that's too many clicks imo

update: Just saw that you mentioned it to be on the wallet attach page in #823:

When on a wallet attach page display or link to the log for the wallet.

However, I planned to have all logs for every wallet at the same page with an optional dropdown filter to filter for specific wallets instead of having individual pages with no option to see all logs together. I think it's useful to see all interaction with attached wallets at one place (send + receive). When we have wallet fallbacks, that will be even more useful. What do you think @huumn?

update: I think I could also link to /wallet/logs on every wallet attach page with a query param to immediately filter the logs by that wallet.

update: Added embedded logs and skipped adding a filter. You can just go to the individual page.

2. Show full payment hash in logs + more info like payment preimage?

The hash was truncated because of overflow issues and not sure how relevant the full hash is.

update: I think it's useful for debugging to be able to easily copy-paste payment hashes etc.

I now show the full hash + preimage in the logs with a horizontal scrollbar on x-overflow.

3 . Font size too small?

I am using x-small as the font size since it makes it easy to look at a lot of logs at the same time (good overview)

4. Expose secret in logs but don't persist it?

The original wallet logs screenshots show that the first and last 6 characters of the secret are shown in the log. I originally planned to not persist the secret in the logs so for loaded logs, the secret would show ******.

update: I decided against that now since it's probably confusing. Secret is now never shown in the logs.

5. Delete logs on server for privacy reasons?

I shortly considered to delete all logs about a wallet if it was unattached but that might be confusing behavior.

I think we can keep this out of this PR and do it later.


TODO:

  • [feature] log statements for LNbits
  • [feature] fetch autowithdrawal logs from server
  • [feature] persist logs in local storage or IndexedDB | only store last 24h or more if unbounded client-side storage amount is actually not a problem
  • [feature] ability to sort logs by recent: if we store a lot of logs, I don't think one is interested in the oldest logs first. autoscroll is better
  • [refactor] fetching all logs from IndexedDB at once and then using Array.filter for pagination should make the code less complex

Summary by CodeRabbit

  • New Features
    • Introduced a new component for displaying log messages in a styled manner.
    • Added comprehensive logging functionality across various components and pages, including service workers and wallets.
    • Implemented a new WalletLogs component for real-time log following, filtering, and display in wallet settings.
    • Enhanced error handling and logging for wallet transactions and settings adjustments.
    • Added a new GraphQL query for fetching wallet logs.
  • Enhancements
    • Improved the logging mechanism by introducing context providers for service workers and wallets.
    • Modified existing components to utilize the new logging contexts for better clarity and error reporting.
  • Bug Fixes
    • Removed unnecessary validation check for wss:// protocol in relay URLs to prevent false errors.
  • Refactor
    • Updated various components to use the enhanced logging functionalities and contexts.
  • Style
    • Added new styles for log message display, navigation, and table formatting.
  • Chores
    • Updated the database schema to support wallet logs.
    • Enhanced the auto withdrawal process with better logging.

@ekzyis ekzyis added the feature new product features that weren't there before label Mar 29, 2024
@ekzyis ekzyis marked this pull request as draft March 29, 2024 02:22
Copy link
Contributor

coderabbitai bot commented Mar 29, 2024

Walkthrough

Walkthrough

This update introduces comprehensive logging mechanisms across various components, including the addition of new logger providers for service workers and wallets. It enhances error handling and logging capabilities, particularly for wallet-related operations, and removes a specific validation check. The changes aim to improve debugging, error tracking, and operational transparency within the application.

Changes

Files Change Summary
components/log-message.js, ...module.css Introduced LogMessage React component for displaying log messages.
components/logger.js, components/serviceworker.js Restructured logger providers and contexts, added new logger contexts and providers for service workers and wallets, updated logging usage.
components/webln/nwc.js, components/webln/lnbits.js Enhanced wallet logging with additional imports, improved error handling, and added logging for various events.
lib/validate.js Removed specific validation check for wss:// protocol, added error logging for validation errors.
middleware.js Updated environment-specific variable assignment within middleware function.
pages/settings/index.js, pages/wallet/index.js, pages/wallet/logs.js Updated logging mechanism usage in settings, wallet components, and added wallet log display functionality.

Assessment against linked issues

Objective Addressed Explanation
Add user-exposed logs for attached wallets (#823) The changes introduce user-exposed logs for attached wallets, enhancing visibility and error reporting.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment on lines 11 to 15
export default function WalletLogs () {
const { logs } = useWalletLogger()
// TODO add filter by wallet, add sort by timestamp
return (
<>
<CenterLayout>
<h2 className='text-center'>wallet logs</h2>
<div>
{logs.map((log, i) => <LogMessage key={i} {...log} />)}
</div>
</CenterLayout>
</>
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation of the WalletLogs page is straightforward and correctly uses the useWalletLogger hook to fetch and display wallet logs. However, as mentioned in the PR objectives, there's a TODO comment for adding filter and sort functionality. It's important to track this feature enhancement for future development.

Would you like me to help implement the filter and sort functionality or open a GitHub issue to track this task?

Comment on lines 125 to 235
const WalletLoggerProvider = ({ children }) => {
// TODO: persist logs in local storage
// limit to last 24h?
const [logs, setLogs] = useState([])

const appendLog = useCallback((wallet, level, message) => {
setLogs((prevLogs) => [...prevLogs, { wallet, level, message, ts: +new Date() }])
}, [setLogs])

return (
<WalletLoggerContext.Provider value={{ logs, appendLog }}>
{children}
</LoggerContext.Provider>
</WalletLoggerContext.Provider>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For WalletLoggerProvider, the comment mentions a TODO for persisting logs in local storage. It's important to address this to ensure logs are not lost between sessions. Consider implementing this feature or tracking it as a task for future development.

Would you like me to implement the log persistence feature or open a GitHub issue to track this task?

@huumn
Copy link
Member

huumn commented Mar 29, 2024

update: I think I could also link to /wallet/logs on every wallet attach page with a query param to immediately filter the logs by that wallet.

If this component is composable it should be trivial to add an accordian (or a tab) of the wallet logs to every wallet while still having a single superlog on another page. That's the route I'd take at least.

@ekzyis
Copy link
Member Author

ekzyis commented Apr 1, 2024

If this component is composable it should be trivial to add an accordian (or a tab) of the wallet logs to every wallet while still having a single superlog on another page. That's the route I'd take at least.

Makes sense! I forgot about the use case of using the logs while configuring the wallet. I mostly had in mind to use the logs for payment failures etc.

Current state (6368d4d):

2024-04-01.03-07-12.mp4

Only thing left to do is to have logs for the autowithdrawal methods.

I was also thinking about having a dropdown to filter by wallet in the superlog but maybe that's MVP feature creep. It shouldn't be difficult to add but I probably shouldn't judge features by "how difficult they would be to add", lol.

Additional context

In cb1f1a6, I started to simply load all logs from IndexedDB at once (instead of only the last 5m) and remove pagination that looked like this:

2024-04-01-031541_1920x1080_scrot

Simply load all logs and remove navigation

I realized the code for navigation was most likely premature optimization which even resulted in worse UX:
Using the buttons to load logs from 5m, 1h, 6h ago sometimes meant that nothing happened at all since there were no logs from 5m, 1h, 6h ago.
That's why I added a time string as "start of logs" so it's at least visible that it changed but that looked bad so I removed it.

But all of this was not necessary: I can simply load all logs at once and then the user can scroll around however they like.

I was worried that it would be bad for performance to load all logs at once since we might store a lot of logs but as mentioned, that's probably premature optimization.

WHEN a lot of logs are stored AND this becomes a problem (What problem even? Slow page load?), THEN we can think about this.

If page load ever becomes slow because of loading logs, we could probably simply not load the logs at page load but only when /wallet/logs is visited.

But for now, this works fine.

-- cb1f1a6, commit message

Deciding against any form of navigation and "performance optimization" and simply relying on a scrollbar in all cases made it possible to delete a lot of complicated code:

 components/logger.js | 51 +++++----------------------------------------------
 pages/wallet/logs.js | 30 ++++--------------------------
 2 files changed, 9 insertions(+), 72 deletions(-)

I am still a little bit worried what happens if one has a lot of logs (since storage is currently unbounded) but I am pretty sure it's not that big of a problem as I initially thought.

@ekzyis ekzyis force-pushed the wallet-logs branch 2 times, most recently from 7a97702 to 5d062c7 Compare April 3, 2024 06:55
Comment on lines +161 to +172
model WalletLog {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at")
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
wallet String
level LogLevel
message String

@@index([userId, createdAt])
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to remove a foreign key to the wallet table since on errors, we don't create wallets.

Instead of doing some mental gymnastics to have a wallet that doesn't work in the wallet table, I simply abandoned the idea to have a link between the wallet table and the logs.

This also makes it possible to persist logs about wallets that have been detached.

ekzyis added 20 commits April 3, 2024 18:32
* <table> fixes bad format with fixed width and message overflow into start of next row
* also using ---start of log--- instead of ---end of log--- now
* removed time string in header nav
I realized the code for navigation was most likely premature optimization which even resulted in worse UX:
Using the buttons to load logs from 5m, 1h, 6h ago sometimes meant that nothing happened at all since there were no logs from 5m, 1h, 6h ago.
That's why I added a time string as "start of logs" so it's at least visible that it changed but that looked bad so I removed it.

But all of this was not necessary: I can simply load all logs at once and then the user can scroll around however they like.

I was worried that it would be bad for performance to load all logs at once since we might store a lot of logs but as mentioned, that's probably premature optimization.

WHEN a lot of logs are stored AND this becomes a problem (What problem even? Slow page load?), THEN we can think about this.

If page load ever becomes slow because of loading logs, we could probably simply not load the logs at page load but only when /wallet/logs is visited.

But for now, this works fine.
Comment on lines +308 to +320
walletLogs: async (parent, args, { me, models }) => {
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
}

return await models.walletLog.findMany({
where: {
userId: me.id
},
orderBy: {
createdAt: 'asc'
}
})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are wasting a lot of bandwidth per wallet save here since we always fetch all logs instead of passing the last id the frontend already knows.

But this is as simple as it can be and maybe it's as unnecessary as trying to optimize log performance by not loading all logs from IDB at once but have pagination but that made the code a lot more complicated (see cb1f1a6).

@ekzyis ekzyis marked this pull request as ready for review April 3, 2024 16:48
@ekzyis ekzyis requested a review from huumn April 3, 2024 16:48
Comment on lines +161 to +171
model WalletLog {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at")
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
wallet String
level LogLevel
message String

@@index([userId, createdAt])
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding an index on the level field of the WalletLog model to optimize queries filtering by log level.

model WalletLog {
  id        Int @id @default(autoincrement())
  createdAt DateTime @default(now()) @map("created_at")
  userId    Int
  user      User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  wallet    String
  level     LogLevel
  message   String

  @@index([userId, createdAt])
+ @@index([level])
}

This index can significantly improve the performance of log retrieval operations, especially when filtering logs by their severity level, which is a common use case.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
model WalletLog {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at")
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
wallet String
level LogLevel
message String
@@index([userId, createdAt])
}
model WalletLog {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at")
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
wallet String
level LogLevel
message String
@@index([userId, createdAt])
@@index([level])
}

@@ -1,7 +1,7 @@
import { authenticatedLndGrpc, createInvoice } from 'ln-service'
import { msatsToSats, satsToMsats } from '@/lib/format'
import { msatsToSats, numWithUnits, satsToMsats } from '@/lib/format'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider removing the unused import authenticatedLndGrpc and createInvoice if they are not being used elsewhere in this file.

Comment on lines 165 to 235
const WalletLoggerProvider = ({ children }) => {
const [logs, setLogs] = useState([])
const idbStoreName = 'wallet_logs'
const idb = useRef()
const logQueue = useRef([])

useQuery(WALLET_LOGS, {
fetchPolicy: 'network-only',
// required to trigger onCompleted on refetches
notifyOnNetworkStatusChange: true,
onCompleted: ({ walletLogs }) => {
setLogs((prevLogs) => {
const existingIds = prevLogs.map(({ id }) => id)
const logs = walletLogs
.filter(({ id }) => !existingIds.includes(id))
.map(({ createdAt, wallet, ...log }) => ({ ts: +new Date(createdAt), wallet: renameWallet(wallet), ...log }))
return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
})
}
})

const saveLog = useCallback((log) => {
if (!idb.current) {
// IDB may not be ready yet
return logQueue.current.push(log)
}
const tx = idb.current.transaction(idbStoreName, 'readwrite')
const request = tx.objectStore(idbStoreName).add(log)
request.onerror = () => console.error('failed to save log:', log)
}, [])

useEffect(() => {
initIndexedDB(idbStoreName)
.then(db => {
idb.current = db

// load all logs from IDB
const tx = idb.current.transaction(idbStoreName, 'readonly')
const store = tx.objectStore(idbStoreName)
const index = store.index('ts')
const request = index.getAll()
request.onsuccess = () => {
const logs = request.result
setLogs((prevLogs) => {
// sort oldest first to keep same order as logs are appended
return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
})
}

// flush queued logs to IDB
logQueue.current.forEach(q => {
const isLog = !!q.wallet
if (isLog) saveLog(q)
})

logQueue.current = []
})
.catch(console.error)
return () => idb.current?.close()
}, [])

const appendLog = useCallback((wallet, level, message) => {
const log = { wallet, level, message, ts: +new Date() }
saveLog(log)
setLogs((prevLogs) => [...prevLogs, log])
}, [setLogs, saveLog])

return (
<WalletLoggerContext.Provider value={{ logs, appendLog }}>
{children}
</LoggerContext.Provider>
</WalletLoggerContext.Provider>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WalletLoggerProvider component's implementation, including log persistence and state management, is well-structured. However, consider addressing the TODO for sending logs to a server when diagnostics are enabled, as this could provide valuable insights for debugging and monitoring.

Would you like me to implement the server-side log collection feature or open a GitHub issue to track this task?


.logTable {
width: 100%;
max-height: 60svh;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the typo in the max-height property value.

-    max-height: 60svh;
+    max-height: 60vh;

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
max-height: 60svh;
max-height: 60vh;


@media screen and (min-width: 768px) {
.logTable {
max-height: 70svh;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the typo in the max-height property values within the media queries.

-        max-height: 70svh;
+        max-height: 70vh;
-        max-height: 30svh;
+        max-height: 30vh;

Also applies to: 22-22


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
max-height: 70svh;
max-height: 70vh;

{filtered.length === 0 && <div className='w-100 text-center'>empty</div>}
<table>
<tbody>
{filtered.map((log, i) => <LogMessage key={i} {...log} />)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more stable identifier than the array index for the key prop in the list rendering. If logs have unique identifiers, use those to help React optimize rendering and re-rendering of list items.

-            {filtered.map((log, i) => <LogMessage key={i} {...log} />)}
+            {filtered.map((log) => <LogMessage key={log.id} {...log} />)}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
{filtered.map((log, i) => <LogMessage key={i} {...log} />)}
{filtered.map((log) => <LogMessage key={log.id} {...log} />)}

@huumn
Copy link
Member

huumn commented Apr 3, 2024

I don't see any glaring issues and I haven't QA'd it. Is there anything you'd like feedback on?

If not, can I assume you are happy and confident enough in it that I can just merge it and deploy it?

@ekzyis
Copy link
Member Author

ekzyis commented Apr 3, 2024

I don't see any glaring issues and I haven't QA'd it. Is there anything you'd like feedback on?

If not, can I assume you are happy and confident enough in it that I can just merge it and deploy it?

I see what you're doing 👀

I am happy with the UX, the code is simple enough and I tested everything except logs for successful autowithdrawals.

But I also tested that now and had to fund my wallet for it. Then I noticed that there is a pretty serious render bug where sendPayment gets called over and over again. Probably related to context usage and what you mentioned with re-renders in another PR. Will fix that now then it should be good to go!

@huumn
Copy link
Member

huumn commented Apr 3, 2024

I see what you're doing 👀

🥸😆

@ekzyis
Copy link
Member Author

ekzyis commented Apr 3, 2024

Mhh, 2b632cb fixes it but for a proper fix which does no longer make provider change just because I logged something, I need to split the context value for WalletLoggerContext in two, I think.

Then I can use logger in NWCProvider etc. without making it change because logging something changes the logs 🤔

I am also recreating the context value on every render currently but that's the smaller issue.

Comment on lines +43 to +51
export const LoggerProvider = ({ children }) => {
return (
<ServiceWorkerLoggerProvider>
<WalletLoggerProvider>
{children}
</WalletLoggerProvider>
</ServiceWorkerLoggerProvider>
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert LoggerProvider to an arrow function for consistency with other components in the file.

- export const LoggerProvider = ({ children }) => {
+ export const LoggerProvider = ({ children }) => (
    <ServiceWorkerLoggerProvider>
      <WalletLoggerProvider>
        {children}
      </WalletLoggerProvider>
    </ServiceWorkerLoggerProvider>
-  )
+ );

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const LoggerProvider = ({ children }) => {
return (
<ServiceWorkerLoggerProvider>
<WalletLoggerProvider>
{children}
</WalletLoggerProvider>
</ServiceWorkerLoggerProvider>
)
}
export const LoggerProvider = ({ children }) => (
<ServiceWorkerLoggerProvider>
<WalletLoggerProvider>
{children}
</WalletLoggerProvider>
</ServiceWorkerLoggerProvider>
);

Comment on lines +128 to +158
const initIndexedDB = async (storeName) => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
return reject(new Error('IndexedDB not supported'))
}

// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
const request = window.indexedDB.open('app:storage', 1)

let db
request.onupgradeneeded = () => {
// this only runs if version was changed during open
db = request.result
if (!db.objectStoreNames.contains(storeName)) {
const objectStore = db.createObjectStore(storeName, { autoIncrement: true })
objectStore.createIndex('ts', 'ts')
objectStore.createIndex('wallet_ts', ['wallet', 'ts'])
}
}

request.onsuccess = () => {
// this gets called after onupgradeneeded finished
db = request.result
resolve(db)
}

request.onerror = () => {
reject(new Error('failed to open IndexedDB'))
}
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for the onupgradeneeded event in initIndexedDB to ensure robustness.

    request.onupgradeneeded = () => {
      db = request.result
      if (!db.objectStoreNames.contains(storeName)) {
        const objectStore = db.createObjectStore(storeName, { autoIncrement: true })
        objectStore.createIndex('ts', 'ts')
        objectStore.createIndex('wallet_ts', ['wallet', 'ts'])
      }
+     request.onerror = (event) => {
+       console.error('Error during onupgradeneeded:', event);
+     };
    }

This addition ensures that any errors that occur during the database upgrade process are properly logged, aiding in debugging and ensuring the application's resilience.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const initIndexedDB = async (storeName) => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
return reject(new Error('IndexedDB not supported'))
}
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
const request = window.indexedDB.open('app:storage', 1)
let db
request.onupgradeneeded = () => {
// this only runs if version was changed during open
db = request.result
if (!db.objectStoreNames.contains(storeName)) {
const objectStore = db.createObjectStore(storeName, { autoIncrement: true })
objectStore.createIndex('ts', 'ts')
objectStore.createIndex('wallet_ts', ['wallet', 'ts'])
}
}
request.onsuccess = () => {
// this gets called after onupgradeneeded finished
db = request.result
resolve(db)
}
request.onerror = () => {
reject(new Error('failed to open IndexedDB'))
}
})
}
const initIndexedDB = async (storeName) => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
return reject(new Error('IndexedDB not supported'))
}
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
const request = window.indexedDB.open('app:storage', 1)
let db
request.onupgradeneeded = () => {
// this only runs if version was changed during open
db = request.result
if (!db.objectStoreNames.contains(storeName)) {
const objectStore = db.createObjectStore(storeName, { autoIncrement: true })
objectStore.createIndex('ts', 'ts')
objectStore.createIndex('wallet_ts', ['wallet', 'ts'])
}
request.onerror = (event) => {
console.error('Error during onupgradeneeded:', event);
};
}
request.onsuccess = () => {
// this gets called after onupgradeneeded finished
db = request.result
resolve(db)
}
request.onerror = () => {
reject(new Error('failed to open IndexedDB'))
}
})
}

Comment on lines +187 to +195
const saveLog = useCallback((log) => {
if (!idb.current) {
// IDB may not be ready yet
return logQueue.current.push(log)
}
const tx = idb.current.transaction(idbStoreName, 'readwrite')
const request = tx.objectStore(idbStoreName).add(log)
request.onerror = () => console.error('failed to save log:', log)
}, [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider batching log saves to IndexedDB in WalletLoggerProvider to improve performance.

  const saveLog = useCallback((log) => {
    if (!idb.current) {
      return logQueue.current.push(log)
    }
    const tx = idb.current.transaction(idbStoreName, 'readwrite')
-   const request = tx.objectStore(idbStoreName).add(log)
+   logQueue.current.push(log);
+   if (logQueue.current.length >= 10) { // Batch size of 10, adjust as necessary
+     const store = tx.objectStore(idbStoreName);
+     logQueue.current.forEach(log => store.add(log));
+     logQueue.current = [];
+   }
    request.onerror = () => console.error('failed to save log:', log)
  }, [])

Batching log saves can significantly reduce the overhead of writing to IndexedDB, especially under heavy logging conditions. This change introduces a simple batching mechanism that accumulates logs and writes them in batches, improving the efficiency of log persistence.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const saveLog = useCallback((log) => {
if (!idb.current) {
// IDB may not be ready yet
return logQueue.current.push(log)
}
const tx = idb.current.transaction(idbStoreName, 'readwrite')
const request = tx.objectStore(idbStoreName).add(log)
request.onerror = () => console.error('failed to save log:', log)
}, [])
const saveLog = useCallback((log) => {
if (!idb.current) {
return logQueue.current.push(log)
}
const tx = idb.current.transaction(idbStoreName, 'readwrite')
logQueue.current.push(log);
if (logQueue.current.length >= 10) { // Batch size of 10, adjust as necessary
const store = tx.objectStore(idbStoreName);
logQueue.current.forEach(log => store.add(log));
logQueue.current = [];
}
request.onerror = () => console.error('failed to save log:', log)
}, [])

Comment on lines +245 to +251
const log = useCallback(level => message => {
// TODO:
// also send this to us if diagnostics was enabled,
// very similar to how the service worker logger works.
appendLog(wallet, level, message)
console[level !== 'error' ? 'info' : 'error'](`[${wallet}]`, message)
}, [appendLog, wallet])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement the TODO for sending logs to a server when diagnostics are enabled in useWalletLogger.

  const log = useCallback(level => message => {
-   // TODO:
-   //   also send this to us if diagnostics was enabled,
-   //   very similar to how the service worker logger works.
+   if (diagnosticsEnabled) { // Assume diagnosticsEnabled is a state or prop indicating if diagnostics are enabled
+     fetch('/api/diagnostics/log', {
+       method: 'post',
+       headers: {
+         'Content-type': 'application/json'
+       },
+       body: JSON.stringify({ wallet, level, message })
+     }).catch(console.error);
+   }
    appendLog(wallet, level, message)
    console[level !== 'error' ? 'info' : 'error'](`[${wallet}]`, message)
  }, [appendLog, wallet])

This change implements the previously mentioned TODO by conditionally sending logs to a server endpoint when diagnostics are enabled. This allows for remote monitoring and debugging of wallet-related issues.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const log = useCallback(level => message => {
// TODO:
// also send this to us if diagnostics was enabled,
// very similar to how the service worker logger works.
appendLog(wallet, level, message)
console[level !== 'error' ? 'info' : 'error'](`[${wallet}]`, message)
}, [appendLog, wallet])
const log = useCallback(level => message => {
if (diagnosticsEnabled) { // Assume diagnosticsEnabled is a state or prop indicating if diagnostics are enabled
fetch('/api/diagnostics/log', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({ wallet, level, message })
}).catch(console.error);
}
appendLog(wallet, level, message)
console[level !== 'error' ? 'info' : 'error'](`[${wallet}]`, message)
}, [appendLog, wallet])

@ekzyis
Copy link
Member Author

ekzyis commented Apr 3, 2024

Ok, 2b4e634 fixes that the provider context value changes due to logging.

Tested that logs still show up fine and logs are still appended fine.

We can merge this now.

@huumn huumn merged commit 15fb7f4 into master Apr 3, 2024
5 checks passed
@ekzyis ekzyis deleted the wallet-logs branch April 4, 2024 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature new product features that weren't there before
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add user-exposed logs for attached wallets
2 participants