System logger for users (#2035)

* System logger

* remove outdated credits preference check on RECEIVE

* fix developer focused error message

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
ekzyis 2025-04-03 00:18:41 +02:00 committed by GitHub
parent d7a7273ca4
commit 644899469f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 30 additions and 21 deletions

View File

@ -1,5 +1,5 @@
import { PAID_ACTION_PAYMENT_METHODS } from '@/lib/constants' import { PAID_ACTION_PAYMENT_METHODS } from '@/lib/constants'
import { toPositiveBigInt, numWithUnits, msatsToSats, satsToMsats } from '@/lib/format' import { toPositiveBigInt, numWithUnits, msatsToSats } from '@/lib/format'
import { notifyDeposit } from '@/lib/webPush' import { notifyDeposit } from '@/lib/webPush'
import { getInvoiceableWallets } from '@/wallets/server' import { getInvoiceableWallets } from '@/wallets/server'
@ -23,10 +23,6 @@ export async function getInvoiceablePeer (_, { me, models, cost, paymentMethod }
return null return null
} }
if (cost < satsToMsats(me.receiveCreditsBelowSats)) {
return null
}
return me.id return me.id
} }

View File

@ -796,9 +796,9 @@ const logContextFromBolt11 = async (bolt11) => {
} }
} }
export const walletLogger = ({ wallet, models }) => { export const walletLogger = ({ wallet, models, me }) => {
// no-op logger if wallet is not provided // no-op logger if no wallet or user provided
if (!wallet) { if (!wallet && !me) {
return { return {
ok: () => {}, ok: () => {},
info: () => {}, info: () => {},
@ -822,8 +822,9 @@ export const walletLogger = ({ wallet, models }) => {
await models.walletLog.create({ await models.walletLog.create({
data: { data: {
userId: wallet.userId, userId: wallet?.userId ?? me.id,
wallet: wallet.type, // system logs have no wallet
wallet: wallet?.type,
level, level,
message, message,
context, context,

View File

@ -194,7 +194,7 @@ const typeDefs = `
type WalletLogEntry { type WalletLogEntry {
id: ID! id: ID!
createdAt: Date! createdAt: Date!
wallet: ID! wallet: ID
level: String! level: String!
message: String! message: String!
context: JSONObject context: JSONObject

View File

@ -226,7 +226,7 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) {
to = null to = null
} }
const { data } = await getWalletLogs({ const { data, error } = await getWalletLogs({
variables: { variables: {
type: walletDef?.walletType, type: walletDef?.walletType,
from, from,
@ -236,9 +236,14 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) {
} }
}) })
if (error) {
console.error('failed to query wallet logs:', error)
return { data: [], hasMore: false }
}
const newLogs = data.walletLogs.entries.map(({ createdAt, wallet: walletType, ...log }) => ({ const newLogs = data.walletLogs.entries.map(({ createdAt, wallet: walletType, ...log }) => ({
ts: +new Date(createdAt), ts: +new Date(createdAt),
wallet: walletTag(getWalletByType(walletType)), wallet: walletType ? walletTag(getWalletByType(walletType)) : 'system',
...log, ...log,
// required to resolve recv status // required to resolve recv status
context: { context: {

View File

@ -4,10 +4,11 @@ import { lnurlPayDescriptionHashForUser, lnurlPayMetadataString, lnurlPayDescrip
import { schnorr } from '@noble/curves/secp256k1' import { schnorr } from '@noble/curves/secp256k1'
import { createHash } from 'crypto' import { createHash } from 'crypto'
import { LNURLP_COMMENT_MAX_LENGTH, MAX_INVOICE_DESCRIPTION_LENGTH } from '@/lib/constants' import { LNURLP_COMMENT_MAX_LENGTH, MAX_INVOICE_DESCRIPTION_LENGTH } from '@/lib/constants'
import { toPositiveBigInt } from '@/lib/format' import { formatMsats, toPositiveBigInt } from '@/lib/format'
import assertGofacYourself from '@/api/resolvers/ofac' import assertGofacYourself from '@/api/resolvers/ofac'
import performPaidAction from '@/api/paidAction' import performPaidAction from '@/api/paidAction'
import { validateSchema, lud18PayerDataSchema } from '@/lib/validate' import { validateSchema, lud18PayerDataSchema } from '@/lib/validate'
import { walletLogger } from '@/api/resolvers/wallet'
export default async ({ query: { username, amount, nostr, comment, payerdata: payerData }, headers }, res) => { export default async ({ query: { username, amount, nostr, comment, payerdata: payerData }, headers }, res) => {
const user = await models.user.findUnique({ where: { name: username } }) const user = await models.user.findUnique({ where: { name: username } })
@ -15,6 +16,9 @@ export default async ({ query: { username, amount, nostr, comment, payerdata: pa
return res.status(400).json({ status: 'ERROR', reason: `user @${username} does not exist` }) return res.status(400).json({ status: 'ERROR', reason: `user @${username} does not exist` })
} }
const logger = walletLogger({ models, me: user })
logger.info(`${user.name}@stacker.news payment attempt`, { amount: formatMsats(amount), nostr, comment })
try { try {
await assertGofacYourself({ models, headers }) await assertGofacYourself({ models, headers })
// if nostr, decode, validate sig, check tags, set description hash // if nostr, decode, validate sig, check tags, set description hash
@ -96,6 +100,7 @@ export default async ({ query: { username, amount, nostr, comment, payerdata: pa
}) })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
logger.error(`${user.name}@stacker.news payment failed: ${error.message}`)
res.status(400).json({ status: 'ERROR', reason: 'could not generate invoice to customer\'s attached wallet' }) res.status(400).json({ status: 'ERROR', reason: 'could not generate invoice to customer\'s attached wallet' })
} }
} }

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "WalletLog" ALTER COLUMN "wallet" DROP NOT NULL;

View File

@ -264,18 +264,18 @@ model VaultEntry {
} }
model WalletLog { model WalletLog {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
userId Int userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
wallet WalletType wallet WalletType?
level LogLevel level LogLevel
message String message String
invoiceId Int? invoiceId Int?
invoice Invoice? @relation(fields: [invoiceId], references: [id]) invoice Invoice? @relation(fields: [invoiceId], references: [id])
withdrawalId Int? withdrawalId Int?
withdrawal Withdrawl? @relation(fields: [withdrawalId], references: [id]) withdrawal Withdrawl? @relation(fields: [withdrawalId], references: [id])
context Json? @db.JsonB context Json? @db.JsonB
@@index([userId, createdAt]) @@index([userId, createdAt])
} }