Logging with loguru (#708)

* logging

* requirements

* add loguru dependency

* restore it

* add loguru

* set log level in .env file

* remove service fee print

* set log level

* more logging

* more logging

* more logging

* pyament.checking_id

* fix
This commit is contained in:
calle 2022-07-07 14:30:16 +02:00 committed by GitHub
parent 847fd18796
commit 089313f613
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 202 additions and 81 deletions

View file

@ -1,10 +1,8 @@
QUART_APP=lnbits.app:create_app()
QUART_ENV=development
QUART_DEBUG=true
HOST=127.0.0.1 HOST=127.0.0.1
PORT=5000 PORT=5000
DEBUG=true
LNBITS_ALLOWED_USERS="" LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS="" LNBITS_ADMIN_USERS=""
# Extensions only admin can access # Extensions only admin can access

View file

@ -12,6 +12,7 @@ cerberus = "*"
ecdsa = "*" ecdsa = "*"
environs = "*" environs = "*"
lnurl = "==0.3.6" lnurl = "==0.3.6"
loguru = "*"
pyscss = "*" pyscss = "*"
shortuuid = "*" shortuuid = "*"
typing-extensions = "*" typing-extensions = "*"

View file

@ -3,15 +3,19 @@ import asyncio
import uvloop import uvloop
from starlette.requests import Request from starlette.requests import Request
from loguru import logger
from .commands import bundle_vendored, migrate_databases, transpile_scss from .commands import bundle_vendored, migrate_databases, transpile_scss
from .settings import ( from .settings import (
DEBUG, DEBUG,
LNBITS_COMMIT, LNBITS_COMMIT,
LNBITS_DATA_FOLDER, LNBITS_DATA_FOLDER,
LNBITS_SITE_TITLE, LNBITS_SITE_TITLE,
HOST,
PORT, PORT,
SERVICE_FEE,
WALLET, WALLET,
LNBITS_DATABASE_URL,
LNBITS_DATA_FOLDER,
) )
uvloop.install() uvloop.install()
@ -24,13 +28,15 @@ from .app import create_app
app = create_app() app = create_app()
print( logger.info("Starting LNbits")
f"""Starting LNbits with logger.info(f"Host: {HOST}")
- git version: {LNBITS_COMMIT} logger.info(f"Port: {PORT}")
- site title: {LNBITS_SITE_TITLE} logger.info(f"Debug: {DEBUG}")
- debug: {DEBUG} logger.info(f"Site title: {LNBITS_SITE_TITLE}")
- data folder: {LNBITS_DATA_FOLDER} logger.info(f"Funding source: {WALLET.__class__.__name__}")
- funding source: {WALLET.__class__.__name__} logger.info(
- service fee: {SERVICE_FEE} f"Database: {'PostgreSQL' if LNBITS_DATABASE_URL.startswith('postgres://') else 'CockroachDB' if LNBITS_DATABASE_URL.startswith('cockroachdb://') else 'SQLite'}"
"""
) )
logger.info(f"Data folder: {LNBITS_DATA_FOLDER}")
logger.info(f"Git version: {LNBITS_COMMIT}")
# logger.info(f"Service fee: {SERVICE_FEE}")

View file

@ -3,6 +3,9 @@ import importlib
import sys import sys
import traceback import traceback
import warnings import warnings
from loguru import logger
from http import HTTPStatus from http import HTTPStatus
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
@ -41,6 +44,8 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory. """Create application factory.
:param config_object: The configuration object to use. :param config_object: The configuration object to use.
""" """
set_logging_level()
app = FastAPI() app = FastAPI()
app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
app.mount( app.mount(
@ -94,14 +99,14 @@ def check_funding_source(app: FastAPI) -> None:
error_message, balance = await WALLET.status() error_message, balance = await WALLET.status()
if not error_message: if not error_message:
break break
warnings.warn( logger.error(
f" × The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'", f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
RuntimeWarning, RuntimeWarning,
) )
print("Retrying connection to backend in 5 seconds...") logger.info("Retrying connection to backend in 5 seconds...")
await asyncio.sleep(5) await asyncio.sleep(5)
print( logger.info(
f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat." f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
) )
@ -124,9 +129,10 @@ def register_routes(app: FastAPI) -> None:
for s in ext_statics: for s in ext_statics:
app.mount(s["path"], s["app"], s["name"]) app.mount(s["path"], s["app"], s["name"])
logger.trace(f"adding route for extension {ext_module}")
app.include_router(ext_route) app.include_router(ext_route)
except Exception as e: except Exception as e:
print(str(e)) logger.error(str(e))
raise ImportError( raise ImportError(
f"Please make sure that the extension `{ext.code}` follows conventions." f"Please make sure that the extension `{ext.code}` follows conventions."
) )
@ -173,8 +179,8 @@ def register_async_tasks(app):
def register_exception_handlers(app: FastAPI): def register_exception_handlers(app: FastAPI):
@app.exception_handler(Exception) @app.exception_handler(Exception)
async def basic_error(request: Request, err): async def basic_error(request: Request, err):
print("handled error", traceback.format_exc()) logger.error("handled error", traceback.format_exc())
print("ERROR:", err) logger.error("ERROR:", err)
etype, _, tb = sys.exc_info() etype, _, tb = sys.exc_info()
traceback.print_exception(etype, err, tb) traceback.print_exception(etype, err, tb)
exc = traceback.format_exc() exc = traceback.format_exc()
@ -188,3 +194,10 @@ def register_exception_handlers(app: FastAPI):
status_code=HTTPStatus.NO_CONTENT, status_code=HTTPStatus.NO_CONTENT,
content={"detail": err}, content={"detail": err},
) )
def set_logging_level() -> None:
"""Set the logging level for the application."""
logger.remove()
log_level: str = "DEBUG" if lnbits.settings.DEBUG else "INFO"
logger.add(sys.stderr, level=log_level)

View file

@ -5,6 +5,8 @@ import importlib
import re import re
import os import os
from loguru import logger
from .db import SQLITE, POSTGRES, COCKROACH from .db import SQLITE, POSTGRES, COCKROACH
from .core import db as core_db, migrations as core_migrations from .core import db as core_db, migrations as core_migrations
from .helpers import ( from .helpers import (
@ -69,7 +71,7 @@ async def migrate_databases():
if match: if match:
version = int(match.group(1)) version = int(match.group(1))
if version > current_versions.get(db_name, 0): if version > current_versions.get(db_name, 0):
print(f"running migration {db_name}.{version}") logger.debug(f"running migration {db_name}.{version}")
await migrate(db) await migrate(db)
if db.schema == None: if db.schema == None:
@ -110,4 +112,4 @@ async def migrate_databases():
async with ext_db.connect() as ext_conn: async with ext_db.connect() as ext_conn:
await run_migration(ext_conn, ext_migrations) await run_migration(ext_conn, ext_migrations)
print(" ✔️ All migrations done.") logger.info("✔️ All migrations done.")

View file

@ -7,6 +7,9 @@ from lnurl import encode as lnurl_encode # type: ignore
from typing import List, NamedTuple, Optional, Dict from typing import List, NamedTuple, Optional, Dict
from sqlite3 import Row from sqlite3 import Row
from pydantic import BaseModel from pydantic import BaseModel
from loguru import logger
from lnbits.settings import WALLET from lnbits.settings import WALLET
@ -142,10 +145,12 @@ class Payment(BaseModel):
status = await WALLET.get_invoice_status(self.checking_id) status = await WALLET.get_invoice_status(self.checking_id)
if self.is_out and status.failed: if self.is_out and status.failed:
print(f" - deleting outgoing failed payment {self.checking_id}: {status}") logger.info(
f" - deleting outgoing failed payment {self.checking_id}: {status}"
)
await self.delete() await self.delete()
elif not status.pending: elif not status.pending:
print( logger.info(
f" - marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}" f" - marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}"
) )
await self.set_pending(status.pending) await self.set_pending(status.pending)

View file

@ -3,6 +3,9 @@ import json
from binascii import unhexlify from binascii import unhexlify
from io import BytesIO from io import BytesIO
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from loguru import logger
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
import httpx import httpx
@ -120,6 +123,7 @@ async def pay_invoice(
# check_internal() returns the checking_id of the invoice we're waiting for # check_internal() returns the checking_id of the invoice we're waiting for
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn) internal_checking_id = await check_internal(invoice.payment_hash, conn=conn)
if internal_checking_id: if internal_checking_id:
logger.debug(f"creating temporary internal payment with id {internal_id}")
# create a new payment from this wallet # create a new payment from this wallet
await create_payment( await create_payment(
checking_id=internal_id, checking_id=internal_id,
@ -129,6 +133,7 @@ async def pay_invoice(
**payment_kwargs, **payment_kwargs,
) )
else: else:
logger.debug(f"creating temporary payment with id {temp_id}")
# create a temporary payment here so we can check if # create a temporary payment here so we can check if
# the balance is enough in the next step # the balance is enough in the next step
await create_payment( await create_payment(
@ -142,6 +147,7 @@ async def pay_invoice(
wallet = await get_wallet(wallet_id, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
assert wallet assert wallet
if wallet.balance_msat < 0: if wallet.balance_msat < 0:
logger.debug("balance is too low, deleting temporary payment")
if not internal_checking_id and wallet.balance_msat > -fee_reserve_msat: if not internal_checking_id and wallet.balance_msat > -fee_reserve_msat:
raise PaymentFailure( raise PaymentFailure(
f"You must reserve at least 1% ({round(fee_reserve_msat/1000)} sat) to cover potential routing fees." f"You must reserve at least 1% ({round(fee_reserve_msat/1000)} sat) to cover potential routing fees."
@ -149,6 +155,7 @@ async def pay_invoice(
raise PermissionError("Insufficient balance.") raise PermissionError("Insufficient balance.")
if internal_checking_id: if internal_checking_id:
logger.debug(f"marking temporary payment as not pending {internal_checking_id}")
# mark the invoice from the other side as not pending anymore # mark the invoice from the other side as not pending anymore
# so the other side only has access to his new money when we are sure # so the other side only has access to his new money when we are sure
# the payer has enough to deduct from # the payer has enough to deduct from
@ -163,11 +170,14 @@ async def pay_invoice(
await internal_invoice_queue.put(internal_checking_id) await internal_invoice_queue.put(internal_checking_id)
else: else:
logger.debug(f"backend: sending payment {temp_id}")
# actually pay the external invoice # actually pay the external invoice
payment: PaymentResponse = await WALLET.pay_invoice( payment: PaymentResponse = await WALLET.pay_invoice(
payment_request, fee_reserve_msat payment_request, fee_reserve_msat
) )
logger.debug(f"backend: pay_invoice finished {temp_id}")
if payment.checking_id: if payment.checking_id:
logger.debug(f"creating final payment {payment.checking_id}")
async with db.connect() as conn: async with db.connect() as conn:
await create_payment( await create_payment(
checking_id=payment.checking_id, checking_id=payment.checking_id,
@ -177,15 +187,18 @@ async def pay_invoice(
conn=conn, conn=conn,
**payment_kwargs, **payment_kwargs,
) )
logger.debug(f"deleting temporary payment {temp_id}")
await delete_payment(temp_id, conn=conn) await delete_payment(temp_id, conn=conn)
else: else:
logger.debug(f"backend payment failed, no checking_id {temp_id}")
async with db.connect() as conn: async with db.connect() as conn:
logger.debug(f"deleting temporary payment {temp_id}")
await delete_payment(temp_id, conn=conn) await delete_payment(temp_id, conn=conn)
raise PaymentFailure( raise PaymentFailure(
payment.error_message payment.error_message
or "Payment failed, but backend didn't give us an error message." or "Payment failed, but backend didn't give us an error message."
) )
logger.debug(f"payment successful {payment.checking_id}")
return invoice.payment_hash return invoice.payment_hash
@ -216,7 +229,7 @@ async def redeem_lnurl_withdraw(
conn=conn, conn=conn,
) )
except: except:
print( logger.warn(
f"failed to create invoice on redeem_lnurl_withdraw from {lnurl}. params: {res}" f"failed to create invoice on redeem_lnurl_withdraw from {lnurl}. params: {res}"
) )
return None return None
@ -325,11 +338,11 @@ async def check_invoice_status(
if not payment.pending: if not payment.pending:
return status return status
if payment.is_out and status.failed: if payment.is_out and status.failed:
print(f" - deleting outgoing failed payment {payment.checking_id}: {status}") logger.info(f"deleting outgoing failed payment {payment.checking_id}: {status}")
await payment.delete() await payment.delete()
elif not status.pending: elif not status.pending:
print( logger.info(
f" - marking '{'in' if payment.is_in else 'out'}' {payment.checking_id} as not pending anymore: {status}" f"marking '{'in' if payment.is_in else 'out'}' {payment.checking_id} as not pending anymore: {status}"
) )
await payment.set_pending(status.pending) await payment.set_pending(status.pending)
return status return status

View file

@ -2,6 +2,8 @@ import asyncio
import httpx import httpx
from typing import List from typing import List
from loguru import logger
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from . import db from . import db
@ -20,7 +22,7 @@ async def register_task_listeners():
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
while True: while True:
payment = await invoice_paid_queue.get() payment = await invoice_paid_queue.get()
logger.debug("received invoice paid event")
# send information to sse channel # send information to sse channel
await dispatch_invoice_listener(payment) await dispatch_invoice_listener(payment)
@ -44,7 +46,7 @@ async def dispatch_invoice_listener(payment: Payment):
try: try:
send_channel.put_nowait(payment) send_channel.put_nowait(payment)
except asyncio.QueueFull: except asyncio.QueueFull:
print("removing sse listener", send_channel) logger.debug("removing sse listener", send_channel)
api_invoice_listeners.remove(send_channel) api_invoice_listeners.remove(send_channel)
@ -52,6 +54,7 @@ async def dispatch_webhook(payment: Payment):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
data = payment.dict() data = payment.dict()
try: try:
logger.debug("sending webhook", payment.webhook)
r = await client.post(payment.webhook, json=data, timeout=40) r = await client.post(payment.webhook, json=data, timeout=40)
await mark_webhook_sent(payment, r.status_code) await mark_webhook_sent(payment, r.status_code)
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):

View file

@ -7,6 +7,9 @@ from typing import Dict, List, Optional, Union
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx import httpx
from loguru import logger
from fastapi import Header, Query, Request from fastapi import Header, Query, Request
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.param_functions import Depends from fastapi.param_functions import Depends
@ -347,7 +350,7 @@ async def subscribe(request: Request, wallet: Wallet):
payment_queue: asyncio.Queue[Payment] = asyncio.Queue(0) payment_queue: asyncio.Queue[Payment] = asyncio.Queue(0)
print("adding sse listener", payment_queue) logger.debug("adding sse listener", payment_queue)
api_invoice_listeners.append(payment_queue) api_invoice_listeners.append(payment_queue)
send_queue: asyncio.Queue[tuple[str, Payment]] = asyncio.Queue(0) send_queue: asyncio.Queue[tuple[str, Payment]] = asyncio.Queue(0)
@ -356,6 +359,7 @@ async def subscribe(request: Request, wallet: Wallet):
while True: while True:
payment: Payment = await payment_queue.get() payment: Payment = await payment_queue.get()
if payment.wallet_id == this_wallet_id: if payment.wallet_id == this_wallet_id:
logger.debug("payment receieved", payment)
await send_queue.put(("payment-received", payment)) await send_queue.put(("payment-received", payment))
asyncio.create_task(payment_received()) asyncio.create_task(payment_received())
@ -391,7 +395,7 @@ async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
wallet = None wallet = None
try: try:
if X_Api_Key.extra: if X_Api_Key.extra:
print("No key") logger.warn("No key")
except: except:
wallet = await get_wallet_for_key(X_Api_Key) wallet = await get_wallet_for_key(X_Api_Key)
payment = await get_standalone_payment(payment_hash) payment = await get_standalone_payment(payment_hash)

View file

@ -10,6 +10,8 @@ from fastapi.routing import APIRouter
from pydantic.types import UUID4 from pydantic.types import UUID4
from starlette.responses import HTMLResponse, JSONResponse from starlette.responses import HTMLResponse, JSONResponse
from loguru import logger
from lnbits.core import db from lnbits.core import db
from lnbits.core.models import User from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
@ -66,10 +68,12 @@ async def extensions(
) )
if extension_to_enable: if extension_to_enable:
logger.info(f"Enabling extension: {extension_to_enable} for user {user.id}")
await update_user_extension( await update_user_extension(
user_id=user.id, extension=extension_to_enable, active=True user_id=user.id, extension=extension_to_enable, active=True
) )
elif extension_to_disable: elif extension_to_disable:
logger.info(f"Disabling extension: {extension_to_disable} for user {user.id}")
await update_user_extension( await update_user_extension(
user_id=user.id, extension=extension_to_disable, active=False user_id=user.id, extension=extension_to_disable, active=False
) )
@ -109,6 +113,7 @@ async def wallet(
if not user_id: if not user_id:
user = await get_user((await create_account()).id) user = await get_user((await create_account()).id)
logger.info(f"Created new account for user {user.id}")
else: else:
user = await get_user(user_id) user = await get_user(user_id)
if not user: if not user:
@ -126,12 +131,16 @@ async def wallet(
wallet = user.wallets[0] wallet = user.wallets[0]
else: else:
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name) wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
logger.info(
f"Created new wallet {wallet_name if wallet_name else '(no name)'} for user {user.id}"
)
return RedirectResponse( return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}", f"/wallet?usr={user.id}&wal={wallet.id}",
status_code=status.HTTP_307_TEMPORARY_REDIRECT, status_code=status.HTTP_307_TEMPORARY_REDIRECT,
) )
logger.info(f"Access wallet {wallet_name} of user {user.id}")
wallet = user.get_wallet(wallet_id) wallet = user.get_wallet(wallet_id)
if not wallet: if not wallet:
return template_renderer().TemplateResponse( return template_renderer().TemplateResponse(
@ -202,13 +211,13 @@ async def lnurl_full_withdraw_callback(request: Request):
async def deletewallet(request: Request, wal: str = Query(...), usr: str = Query(...)): async def deletewallet(request: Request, wal: str = Query(...), usr: str = Query(...)):
user = await get_user(usr) user = await get_user(usr)
user_wallet_ids = [u.id for u in user.wallets] user_wallet_ids = [u.id for u in user.wallets]
print("USR", user_wallet_ids)
if wal not in user_wallet_ids: if wal not in user_wallet_ids:
raise HTTPException(HTTPStatus.FORBIDDEN, "Not your wallet.") raise HTTPException(HTTPStatus.FORBIDDEN, "Not your wallet.")
else: else:
await delete_wallet(user_id=user.id, wallet_id=wal) await delete_wallet(user_id=user.id, wallet_id=wal)
user_wallet_ids.remove(wal) user_wallet_ids.remove(wal)
logger.debug("Deleted wallet {wal} of user {user.id}")
if user_wallet_ids: if user_wallet_ids:
return RedirectResponse( return RedirectResponse(

View file

@ -7,6 +7,8 @@ from fastapi import HTTPException
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
from loguru import logger
from lnbits import bolt11 from lnbits import bolt11
from .. import core_app from .. import core_app
@ -45,7 +47,7 @@ async def api_public_payment_longpolling(payment_hash):
payment_queue = asyncio.Queue(0) payment_queue = asyncio.Queue(0)
print("adding standalone invoice listener", payment_hash, payment_queue) logger.debug("adding standalone invoice listener", payment_hash, payment_queue)
api_invoice_listeners.append(payment_queue) api_invoice_listeners.append(payment_queue)
response = None response = None

View file

@ -5,6 +5,8 @@ import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import Optional from typing import Optional
from loguru import logger
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy_aio.base import AsyncConnection from sqlalchemy_aio.base import AsyncConnection
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
@ -139,7 +141,7 @@ class Database(Compat):
f"LNBITS_DATA_FOLDER named {LNBITS_DATA_FOLDER} was not created" f"LNBITS_DATA_FOLDER named {LNBITS_DATA_FOLDER} was not created"
f" - please 'mkdir {LNBITS_DATA_FOLDER}' and try again" f" - please 'mkdir {LNBITS_DATA_FOLDER}' and try again"
) )
logger.trace(f"database {self.type} added for {self.name}")
self.schema = self.name self.schema = self.name
if self.name.startswith("ext_"): if self.name.startswith("ext_"):
self.schema = self.name[4:] self.schema = self.name[4:]

View file

@ -5,6 +5,8 @@ from http import HTTPStatus
from starlette.requests import Request from starlette.requests import Request
from loguru import logger
from . import bleskomat_ext from . import bleskomat_ext
from .crud import ( from .crud import (
create_bleskomat_lnurl, create_bleskomat_lnurl,
@ -122,7 +124,7 @@ async def api_bleskomat_lnurl(req: Request):
except LnurlHttpError as e: except LnurlHttpError as e:
return {"status": "ERROR", "reason": str(e)} return {"status": "ERROR", "reason": str(e)}
except Exception as e: except Exception as e:
print(str(e)) logger.error(str(e))
return {"status": "ERROR", "reason": "Unexpected error"} return {"status": "ERROR", "reason": "Unexpected error"}
return {"status": "OK"} return {"status": "OK"}

View file

@ -6,6 +6,8 @@ from fastapi.params import Query
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from starlette.requests import Request from starlette.requests import Request
from loguru import logger
from lnbits import bolt11 from lnbits import bolt11
from lnbits.core.services import pay_invoice, PaymentFailure from lnbits.core.services import pay_invoice, PaymentFailure
@ -125,7 +127,7 @@ class BleskomatLnurl(BaseModel):
except (ValueError, PermissionError, PaymentFailure) as e: except (ValueError, PermissionError, PaymentFailure) as e:
raise LnurlValidationError("Failed to pay invoice: " + str(e)) raise LnurlValidationError("Failed to pay invoice: " + str(e))
except Exception as e: except Exception as e:
print(str(e)) logger.error(str(e))
raise LnurlValidationError("Unexpected error") raise LnurlValidationError("Unexpected error")
async def use(self, conn) -> bool: async def use(self, conn) -> bool:

View file

@ -3,6 +3,8 @@ from http import HTTPStatus
from fastapi import Depends, Query from fastapi import Depends, Query
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from loguru import logger
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.bleskomat.models import CreateBleskomat from lnbits.extensions.bleskomat.models import CreateBleskomat
@ -60,7 +62,7 @@ async def api_bleskomat_create_or_update(
currency=fiat_currency, provider=exchange_rate_provider currency=fiat_currency, provider=exchange_rate_provider
) )
except Exception as e: except Exception as e:
print(e) logger.error(e)
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f'Failed to fetch BTC/{fiat_currency} currency pair from "{exchange_rate_provider}"', detail=f'Failed to fetch BTC/{fiat_currency} currency pair from "{exchange_rate_provider}"',

View file

@ -1,6 +1,8 @@
import asyncio import asyncio
import json import json
from loguru import logger
from lnbits.core import db as core_db from lnbits.core import db as core_db
from lnbits.core.crud import create_payment from lnbits.core.crud import create_payment
from lnbits.core.models import Payment from lnbits.core.models import Payment
@ -26,11 +28,11 @@ async def on_invoice_paid(payment: Payment) -> None:
track = await get_track(payment.extra.get("track", -1)) track = await get_track(payment.extra.get("track", -1))
if not track: if not track:
print("this should never happen", payment) logger.error("this should never happen", payment)
return return
if payment.extra.get("shared_with"): if payment.extra.get("shared_with"):
print("payment was shared already", payment) logger.error("payment was shared already", payment)
return return
producer = await get_producer(track.producer) producer = await get_producer(track.producer)

View file

@ -1,6 +1,8 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Optional, Union from typing import List, Optional, Union
from loguru import logger
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from . import db from . import db
@ -186,9 +188,9 @@ async def purge_addresses(domain_id: str):
) # give user 1 day to topup is address ) # give user 1 day to topup is address
if not paid and pay_expire: if not paid and pay_expire:
print("DELETE UNP_PAY_EXP", r["username"]) logger.debug("DELETE UNP_PAY_EXP", r["username"])
await delete_address(r["id"]) await delete_address(r["id"])
if paid and expired: if paid and expired:
print("DELETE PAID_EXP", r["username"]) logger.debug("DELETE PAID_EXP", r["username"])
await delete_address(r["id"]) await delete_address(r["id"])

View file

@ -3,6 +3,9 @@ import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import httpx import httpx
from loguru import logger
from fastapi.params import Query from fastapi.params import Query
from lnurl import ( # type: ignore from lnurl import ( # type: ignore
LnurlErrorResponse, LnurlErrorResponse,
@ -38,13 +41,12 @@ async def lnurl_response(username: str, domain: str, request: Request):
"maxSendable": 1000000000, "maxSendable": 1000000000,
} }
print("RESP", resp) logger.debug("RESP", resp)
return resp return resp
@lnaddress_ext.get("/lnurl/cb/{address_id}", name="lnaddress.lnurl_callback") @lnaddress_ext.get("/lnurl/cb/{address_id}", name="lnaddress.lnurl_callback")
async def lnurl_callback(address_id, amount: int = Query(...)): async def lnurl_callback(address_id, amount: int = Query(...)):
print("PING")
address = await get_address(address_id) address = await get_address(address_id)
if not address: if not address:
return LnurlErrorResponse(reason=f"Address not found").dict() return LnurlErrorResponse(reason=f"Address not found").dict()

View file

@ -1,5 +1,7 @@
import asyncio import asyncio
from loguru import logger
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
@ -22,7 +24,7 @@ async def on_invoice_paid(payment: Payment) -> None:
ticket = await get_ticket(payment.checking_id) ticket = await get_ticket(payment.checking_id)
if not ticket: if not ticket:
print("this should never happen", payment) logger.error("this should never happen", payment)
return return
await payment.set_pending(False) await payment.set_pending(False)

View file

@ -150,7 +150,7 @@ async def lnurl_v1_params(
"defaultDescription": device.title, "defaultDescription": device.title,
} }
price_msat = int(price_msat * ((device.profit / 100) + 1) / 1000) price_msat = int(price_msat * ((device.profit / 100) + 1) / 1000)
print(price_msat)
lnurldevicepayment = await create_lnurldevicepayment( lnurldevicepayment = await create_lnurldevicepayment(
deviceid=device.id, deviceid=device.id,
payload=p, payload=p,
@ -204,7 +204,7 @@ async def lnurl_callback(
extra={"tag": "withdraw"}, extra={"tag": "withdraw"},
) )
return {"status": "OK"} return {"status": "OK"}
print(lnurldevicepayment.sats)
payment_hash, payment_request = await create_invoice( payment_hash, payment_request = await create_invoice(
wallet_id=device.wallet, wallet_id=device.wallet,
amount=lnurldevicepayment.sats / 1000, amount=lnurldevicepayment.sats / 1000,

View file

@ -2,6 +2,9 @@ import asyncio
from http import HTTPStatus from http import HTTPStatus
import httpx import httpx
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core import db as core_db from lnbits.core import db as core_db
@ -27,7 +30,7 @@ async def on_invoice_paid(payment: Payment) -> None:
try: try:
# Check its got a payout associated with it # Check its got a payout associated with it
lnurlpayout_link = await get_lnurlpayout_from_wallet(payment.wallet_id) lnurlpayout_link = await get_lnurlpayout_from_wallet(payment.wallet_id)
print("LNURLpayout", lnurlpayout_link) logger.debug("LNURLpayout", lnurlpayout_link)
if lnurlpayout_link: if lnurlpayout_link:
# Check the wallet balance is more than the threshold # Check the wallet balance is more than the threshold

View file

@ -1,5 +1,7 @@
import asyncio import asyncio
from loguru import logger
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.extensions.satspay.crud import check_address_balance, get_charge from lnbits.extensions.satspay.crud import check_address_balance, get_charge
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
@ -23,7 +25,7 @@ async def on_invoice_paid(payment: Payment) -> None:
charge = await get_charge(payment.memo) charge = await get_charge(payment.memo)
if not charge: if not charge:
print("this should never happen", payment) logger.error("this should never happen", payment)
return return
await payment.set_pending(False) await payment.set_pending(False)

View file

@ -1,6 +1,8 @@
import asyncio import asyncio
import json import json
from loguru import logger
from lnbits.core import db as core_db from lnbits.core import db as core_db
from lnbits.core.crud import create_payment from lnbits.core.crud import create_payment
from lnbits.core.models import Payment from lnbits.core.models import Payment
@ -34,7 +36,9 @@ async def on_invoice_paid(payment: Payment) -> None:
amount_left = payment.amount - sum([amount for _, amount in transfers]) amount_left = payment.amount - sum([amount for _, amount in transfers])
if amount_left < 0: if amount_left < 0:
print("splitpayments failure: amount_left is negative.", payment.payment_hash) logger.error(
"splitpayments failure: amount_left is negative.", payment.payment_hash
)
return return
if not targets: if not targets:

View file

@ -4,6 +4,8 @@ from fastapi import Query
from fastapi.params import Depends from fastapi.params import Depends
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from loguru import logger
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment from lnbits.core.views.api import api_payment
@ -88,6 +90,6 @@ async def api_tpos_check_invoice(tpos_id: str, payment_hash: str):
status = await api_payment(payment_hash) status = await api_payment(payment_hash)
except Exception as exc: except Exception as exc:
print(exc) logger.error(exc)
return {"paid": False} return {"paid": False}
return status return status

View file

@ -5,6 +5,8 @@ import httpx
from datetime import datetime from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
from loguru import logger
import shortuuid # type: ignore import shortuuid # type: ignore
from fastapi import HTTPException from fastapi import HTTPException
from fastapi.param_functions import Query from fastapi.param_functions import Query
@ -136,13 +138,13 @@ async def api_lnurl_callback(
) )
except Exception as exc: except Exception as exc:
# webhook fails shouldn't cause the lnurlw to fail since invoice is already paid # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
print("Caught exception when dispatching webhook url:", exc) logger.error("Caught exception when dispatching webhook url:", exc)
return {"status": "OK"} return {"status": "OK"}
except Exception as e: except Exception as e:
await update_withdraw_link(link.id, **changesback) await update_withdraw_link(link.id, **changesback)
print(traceback.format_exc()) logger.error(traceback.format_exc())
return {"status": "ERROR", "reason": "Link not working"} return {"status": "ERROR", "reason": "Link not working"}

View file

@ -13,8 +13,8 @@ wallet_class = getattr(
wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet") wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet")
) )
ENV = env.str("QUART_ENV", default="production") DEBUG = env.bool("DEBUG", default=False)
DEBUG = env.bool("QUART_DEBUG", default=False) or ENV == "development"
HOST = env.str("HOST", default="127.0.0.1") HOST = env.str("HOST", default="127.0.0.1")
PORT = env.int("PORT", default=5000) PORT = env.int("PORT", default=5000)

View file

@ -4,6 +4,8 @@ import traceback
from http import HTTPStatus from http import HTTPStatus
from typing import List, Callable from typing import List, Callable
from loguru import logger
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from lnbits.settings import WALLET from lnbits.settings import WALLET
@ -37,9 +39,9 @@ async def catch_everything_and_restart(func):
except asyncio.CancelledError: except asyncio.CancelledError:
raise # because we must pass this up raise # because we must pass this up
except Exception as exc: except Exception as exc:
print("caught exception in background task:", exc) logger.error("caught exception in background task:", exc)
print(traceback.format_exc()) logger.error(traceback.format_exc())
print("will restart the task in 5 seconds.") logger.error("will restart the task in 5 seconds.")
await asyncio.sleep(5) await asyncio.sleep(5)
await catch_everything_and_restart(func) await catch_everything_and_restart(func)
@ -77,7 +79,7 @@ async def internal_invoice_listener():
async def invoice_listener(): async def invoice_listener():
async for checking_id in WALLET.paid_invoices_stream(): async for checking_id in WALLET.paid_invoices_stream():
print("> got a payment notification", checking_id) logger.info("> got a payment notification", checking_id)
asyncio.create_task(invoice_callback_dispatcher(checking_id)) asyncio.create_task(invoice_callback_dispatcher(checking_id))
@ -117,6 +119,7 @@ async def perform_balance_checks():
async def invoice_callback_dispatcher(checking_id: str): async def invoice_callback_dispatcher(checking_id: str):
payment = await get_standalone_payment(checking_id, incoming=True) payment = await get_standalone_payment(checking_id, incoming=True)
if payment and payment.is_in: if payment and payment.is_in:
logger.trace("sending invoice callback for payment", checking_id)
await payment.set_pending(False) await payment.set_pending(False)
for send_chan in invoice_listeners: for send_chan in invoice_listeners:
await send_chan.put(payment) await send_chan.put(payment)

View file

@ -1,6 +1,8 @@
import asyncio import asyncio
from typing import Callable, NamedTuple from typing import Callable, NamedTuple
from loguru import logger
import httpx import httpx
currencies = { currencies = {
@ -280,7 +282,7 @@ async def btc_price(currency: str) -> float:
if not rates: if not rates:
return 9999999999 return 9999999999
elif len(rates) == 1: elif len(rates) == 1:
print("Warning could only fetch one Bitcoin price.") logger.warn("Could only fetch one Bitcoin price.")
return sum([rate for rate in rates]) / len(rates) return sum([rate for rate in rates]) / len(rates)

View file

@ -5,6 +5,8 @@ import urllib.parse
from os import getenv from os import getenv
from typing import AsyncGenerator, Dict, Optional from typing import AsyncGenerator, Dict, Optional
from loguru import logger
import httpx import httpx
from websockets import connect from websockets import connect
from websockets.exceptions import ( from websockets.exceptions import (
@ -193,8 +195,8 @@ class EclairWallet(Wallet):
ConnectionClosedError, ConnectionClosedError,
ConnectionClosed, ConnectionClosed,
) as ose: ) as ose:
print("OSE", ose) logger.error("OSE", ose)
pass pass
print("lost connection to eclair's websocket, retrying in 5 seconds") logger.error("lost connection to eclair's websocket, retrying in 5 seconds")
await asyncio.sleep(5) await asyncio.sleep(5)

View file

@ -1,11 +1,12 @@
import asyncio import asyncio
import json
import httpx
from os import getenv from os import getenv
from datetime import datetime, timedelta from datetime import datetime
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
import random import random
import string
from loguru import logger
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
import hashlib import hashlib
from ..bolt11 import encode, decode from ..bolt11 import encode, decode
@ -20,7 +21,7 @@ from .base import (
class FakeWallet(Wallet): class FakeWallet(Wallet):
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
print( logger.info(
"FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr." "FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr."
) )
return StatusResponse(None, float("inf")) return StatusResponse(None, float("inf"))

View file

@ -4,6 +4,8 @@ import httpx
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from loguru import logger
from .base import ( from .base import (
StatusResponse, StatusResponse,
InvoiceResponse, InvoiceResponse,
@ -144,5 +146,7 @@ class LNbitsWallet(Wallet):
except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout): except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout):
pass pass
print("lost connection to lnbits /payments/sse, retrying in 5 seconds") logger.error(
"lost connection to lnbits /payments/sse, retrying in 5 seconds"
)
await asyncio.sleep(5) await asyncio.sleep(5)

View file

@ -11,6 +11,9 @@ import base64
import hashlib import hashlib
from os import environ, error, getenv from os import environ, error, getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from loguru import logger
from .macaroon import load_macaroon, AESCipher from .macaroon import load_macaroon, AESCipher
if imports_ok: if imports_ok:
@ -187,6 +190,8 @@ class LndWallet(Wallet):
checking_id = stringify_checking_id(i.r_hash) checking_id = stringify_checking_id(i.r_hash)
yield checking_id yield checking_id
except error: except error:
print(error) logger.error(error)
print("lost connection to lnd InvoiceSubscription, please restart lnbits.") logger.error(
"lost connection to lnd InvoiceSubscription, please restart lnbits."
)

View file

@ -6,6 +6,8 @@ import base64
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from loguru import logger
from lnbits import bolt11 as lnbits_bolt11 from lnbits import bolt11 as lnbits_bolt11
from .macaroon import load_macaroon, AESCipher from .macaroon import load_macaroon, AESCipher
@ -191,5 +193,7 @@ class LndRestWallet(Wallet):
except (OSError, httpx.ConnectError, httpx.ReadError): except (OSError, httpx.ConnectError, httpx.ReadError):
pass pass
print("lost connection to lnd invoices stream, retrying in 5 seconds") logger.error(
"lost connection to lnd invoices stream, retrying in 5 seconds"
)
await asyncio.sleep(5) await asyncio.sleep(5)

View file

@ -6,6 +6,8 @@ from os import getenv
from http import HTTPStatus from http import HTTPStatus
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from loguru import logger
from .base import ( from .base import (
StatusResponse, StatusResponse,
InvoiceResponse, InvoiceResponse,
@ -127,7 +129,7 @@ class LNPayWallet(Wallet):
try: try:
data = json.loads(text) data = json.loads(text)
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
print(f"got something wrong on lnpay webhook endpoint: {text[:200]}") logger.error(f"got something wrong on lnpay webhook endpoint: {text[:200]}")
data = None data = None
if ( if (
type(data) is not dict type(data) is not dict

View file

@ -4,6 +4,8 @@ import httpx
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from loguru import logger
from .base import ( from .base import (
StatusResponse, StatusResponse,
InvoiceResponse, InvoiceResponse,
@ -143,5 +145,7 @@ class LntxbotWallet(Wallet):
except (OSError, httpx.ReadError, httpx.ReadTimeout, httpx.ConnectError): except (OSError, httpx.ReadError, httpx.ReadTimeout, httpx.ConnectError):
pass pass
print("lost connection to lntxbot /payments/stream, retrying in 5 seconds") logger.error(
"lost connection to lntxbot /payments/stream, retrying in 5 seconds"
)
await asyncio.sleep(5) await asyncio.sleep(5)

View file

@ -4,6 +4,8 @@ import base64
from hashlib import md5 from hashlib import md5
import getpass import getpass
from loguru import logger
BLOCK_SIZE = 16 BLOCK_SIZE = 16
import getpass import getpass
@ -103,5 +105,5 @@ if __name__ == "__main__":
macaroon = input("Enter macaroon: ") macaroon = input("Enter macaroon: ")
macaroon = load_macaroon(macaroon) macaroon = load_macaroon(macaroon)
macaroon = AESCipher(description="encryption").encrypt(macaroon.encode()) macaroon = AESCipher(description="encryption").encrypt(macaroon.encode())
print("Encrypted macaroon:") logger.info("Encrypted macaroon:")
print(macaroon) logger.info(macaroon)

View file

@ -8,6 +8,8 @@ from http import HTTPStatus
from os import getenv from os import getenv
from typing import Optional, AsyncGenerator from typing import Optional, AsyncGenerator
from loguru import logger
from .base import ( from .base import (
StatusResponse, StatusResponse,
InvoiceResponse, InvoiceResponse,
@ -139,7 +141,7 @@ class OpenNodeWallet(Wallet):
x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256") x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256")
x.update(charge_id.encode("ascii")) x.update(charge_id.encode("ascii"))
if x.hexdigest() != data["hashed_order"]: if x.hexdigest() != data["hashed_order"]:
print("invalid webhook, not from opennode") logger.error("invalid webhook, not from opennode")
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
await self.queue.put(charge_id) await self.queue.put(charge_id)

View file

@ -5,6 +5,8 @@ import random
from os import getenv from os import getenv
from typing import Optional, AsyncGenerator from typing import Optional, AsyncGenerator
from loguru import logger
from .base import ( from .base import (
StatusResponse, StatusResponse,
InvoiceResponse, InvoiceResponse,
@ -204,5 +206,5 @@ class SparkWallet(Wallet):
except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout): except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout):
pass pass
print("lost connection to spark /stream, retrying in 5 seconds") logger.error("lost connection to spark /stream, retrying in 5 seconds")
await asyncio.sleep(5) await asyncio.sleep(5)

View file

@ -1,5 +1,7 @@
from typing import AsyncGenerator, Optional from typing import AsyncGenerator, Optional
from loguru import logger
from .base import ( from .base import (
InvoiceResponse, InvoiceResponse,
PaymentResponse, PaymentResponse,
@ -20,7 +22,7 @@ class VoidWallet(Wallet):
raise Unsupported("") raise Unsupported("")
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
print( logger.info(
"This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits." "This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits."
) )
return StatusResponse(None, 0) return StatusResponse(None, 0)

View file

@ -21,6 +21,7 @@ idna==3.2
importlib-metadata==4.8.1 importlib-metadata==4.8.1
jinja2==3.0.1 jinja2==3.0.1
lnurl==0.3.6 lnurl==0.3.6
loguru==0.6.0
markupsafe==2.0.1 markupsafe==2.0.1
marshmallow==3.13.0 marshmallow==3.13.0
outcome==1.1.0 outcome==1.1.0