commit 9592d4218fca15abda9dc6df80332538e60fa39e
parent 5b62afbc6a3982f15e916bacd7be15e608553bab
Author: Kristaps Kaupe <kristaps@blogiem.lv>
Date: Tue, 11 Jan 2022 08:18:46 +0200
Use Python logging facility (#34)
Diffstat:
14 files changed, 95 insertions(+), 77 deletions(-)
diff --git a/config.py b/config.py
@@ -45,3 +45,4 @@ lightning_address_comment = get_opt("lightning_address_comment", None)
liquid_address = get_opt("liquid_address", None)
paynym = get_opt("paynym", None)
free_mode = get_opt("free_mode", False)
+loglevel = get_opt("loglevel", "DEBUG")
diff --git a/config.toml b/config.toml
@@ -65,6 +65,9 @@ required_confirmations = 2
# Global connection attempts
connection_attempts = 3
+# Console log level (DEBUG, INFO, WARNING, ERROR)
+loglevel = "DEBUG"
+
# Generic redirect url after payment
redirect = "https://github.com/nickfarrow/satsale"
diff --git a/gateways/lightning_address.py b/gateways/lightning_address.py
@@ -1,6 +1,7 @@
from flask import request
from flask_restplus import Resource, Api, Namespace, fields
import hashlib
+import logging
import config
@@ -19,7 +20,7 @@ def add_ln_address_decorators(app, api, node):
class get_ln_address(Resource):
def get(self):
try:
- print("Someone requested our ln address: {}!".format(config.lightning_address))
+ logging.info("Someone requested our ln address: {}!".format(config.lightning_address))
resp = {
"callback": "https://{}/lnaddr".format(config.lightning_address.split("@")[1]),
"maxSendable": max_sats*10**3,
@@ -30,7 +31,7 @@ def add_ln_address_decorators(app, api, node):
return resp
except Exception as e:
- print(e)
+ logging.error(e)
return {"status": "ERROR", "reason": e}
@@ -43,19 +44,19 @@ def add_ln_address_decorators(app, api, node):
amount_msats = int(request.args.get("amount"))
amount_btc = amount_msats / 10**(3+8)
- print("Received payment request from ln address for {} msats...".format(amount_msats))
+ logging.info("Received payment request from ln address for {} msats...".format(amount_msats))
description_hash = hashlib.sha256(metadata.encode()).digest()
try:
invoice, _ = node.create_lnd_invoice(amount_btc, memo="lightning address payment", description_hash=description_hash)
- print("Responding with invoice {}".format(invoice))
+ logging.info("Responding with invoice {}".format(invoice))
return {
"pr": invoice,
"routes": []
}
except Exception as e:
- print(e)
+ logging.error(e)
return {"status": "ERROR", "reason": e}
diff --git a/gateways/paynym.py b/gateways/paynym.py
@@ -1,4 +1,5 @@
import requests
+import logging
paynym_site = "https://paynym.is/"
@@ -21,14 +22,14 @@ def insert_paynym_html(nym):
donate_html = f.read()
if 'class="paynym"' in donate_html:
- print("Found existing paynym HTML in donate.html.")
+ logging.info("Found existing paynym HTML in donate.html.")
return
payment_code = get_paynym(nym)
avatar_url = paynym_site + "{}/avatar".format(payment_code)
codeimage_url = paynym_site + "{}/codeimage".format(payment_code)
- print("Fetching paynym images...")
+ logging.info("Fetching paynym images...")
avatar_data = requests.get(avatar_url).content
with open("static/avatar.png", "wb") as f:
@@ -93,6 +94,6 @@ def insert_paynym_html(nym):
with open(donate_file, "w") as f:
f.write(modified_html)
- print("Wrote donate.html with paynym tags")
+ logging.info("Wrote donate.html with paynym tags")
return
diff --git a/gateways/ssh_tunnel.py b/gateways/ssh_tunnel.py
@@ -1,6 +1,7 @@
import subprocess
import time
import os
+import logging
import config
from node import bitcoind
@@ -18,14 +19,14 @@ def open_tunnel(host, port):
"-L",
"{}:localhost:{}".format(port, port),
]
- print("Opening tunnel to {}.".format(" ".join(command)))
+ logging.info("Opening tunnel to {}.".format(" ".join(command)))
tunnel_proc = subprocess.Popen(command)
return tunnel_proc
else:
tunnel_proc = None
except Exception as e:
- print("FAILED TO OPEN TUNNEL. Exception: {}".format(e))
+ logging.error("FAILED TO OPEN TUNNEL. Exception: {}".format(e))
tunnel_proc = None
pass
return
@@ -44,12 +45,12 @@ def clightning_unix_domain_socket_ssh(rpc_store_dir=None):
"lightning-rpc:{}".format(config.clightning_rpc_file),
"{}".format(config.tunnel_host),
]
- print("Opening tunnel to {}.".format(" ".join(command)))
+ logging.info("Opening tunnel to {}.".format(" ".join(command)))
tunnel_proc = subprocess.Popen(command)
return tunnel_proc
except Exception as e:
- print("FAILED TO OPEN UNIX DOMAIN SOCKET OVER SSH. Exception: {}".format(e))
+ logging.error("FAILED TO OPEN UNIX DOMAIN SOCKET OVER SSH. Exception: {}".format(e))
tunnel_proc = None
pass
@@ -62,7 +63,7 @@ def clightning_unix_domain_socket_ssh(rpc_store_dir=None):
def close_tunnel():
if tunnel_proc is not None:
tunnel_proc.kill()
- print("Tunnel closed.")
+ logging.info("Tunnel closed.")
return
diff --git a/gateways/tor.py b/gateways/tor.py
@@ -10,15 +10,15 @@ time.sleep(3)
if config.tor_proxy is None:
config.tor_proxy = "127.0.0.1:9050"
-print("Using tor proxies {}".format(config.tor_proxy))
+logging.info("Using tor proxies {}".format(config.tor_proxy))
session = requests.session()
session.proxies = {
"http": "socks5h://{}".format(config.tor_proxy),
"https": "socks5h://{}".format(config.tor_proxy),
}
-print(
+logging.info(
"Checking tor circuit IP address... You should check this is different to your IP."
)
r = session.get("http://httpbin.org/ip")
-print(r.text)
+logging.info(r.text)
diff --git a/gateways/woo_webhook.py b/gateways/woo_webhook.py
@@ -4,6 +4,7 @@ import json
import codecs
import time
import requests
+import logging
def hook(satsale_secret, invoice, order_id):
@@ -12,7 +13,7 @@ def hook(satsale_secret, invoice, order_id):
# Calculate a secret that is required to send back to the
# woocommerce gateway, proving we did not modify id nor amount.
secret_seed = str(int(100 * float(invoice["fiat_value"]))).encode("utf-8")
- print("Secret seed: {}".format(secret_seed))
+ logging.info("Secret seed: {}".format(secret_seed))
secret = hmac.new(key, secret_seed, hashlib.sha256).hexdigest()
diff --git a/node/bitcoind.py b/node/bitcoind.py
@@ -2,6 +2,7 @@ import time
import uuid
import qrcode
import json
+import logging
import config
from payments.price_feed import get_btc_value
@@ -38,10 +39,10 @@ class btcd:
config.rpcport,
config.wallet,
)
- print("Attempting to connect to Bitcoin node RPC with user {}.".format(config.username))
+ logging.info("Attempting to connect to Bitcoin node RPC with user {}.".format(config.username))
else:
self.tor = True
- print(
+ logging.info(
"Attempting to contact bitcoind rpc tor hidden service: {}:{}".format(
config.tor_bitcoinrpc_host, config.rpcport
)
@@ -56,14 +57,14 @@ class btcd:
else:
info = call_tor_bitcoin_rpc("getblockchaininfo", None)
- print(info)
- print("Successfully contacted bitcoind.")
+ logging.info(info)
+ logging.info("Successfully contacted bitcoind.")
break
except Exception as e:
- print(e)
+ logging.error(e)
time.sleep(config.pollrate)
- print(
+ logging.info(
"Attempting again... {}/{}...".format(
i + 1, config.connection_attempts
)
@@ -109,13 +110,13 @@ class btcd:
return address, None
except Exception as e:
- print(e)
- print(
+ logging.error(e)
+ logging.info(
"Attempting again... {}/{}...".format(
i + 1, config.connection_attempts
)
)
if config.connection_attempts - i == 1:
- print("Reconnecting...")
+ logging.info("Reconnecting...")
self.__init__()
return None
diff --git a/node/clightning.py b/node/clightning.py
@@ -5,6 +5,7 @@ import os
import json
import uuid
import qrcode
+import logging
from payments.price_feed import get_btc_value
@@ -23,20 +24,20 @@ class clightning:
for i in range(config.connection_attempts):
try:
- print("Attempting to connect to clightning...")
+ logging.info("Attempting to connect to clightning...")
self.clightning = LightningRpc(config.clightning_rpc_file)
- print("Getting clightning info...")
+ logging.info("Getting clightning info...")
info = self.clightning.getinfo()
- print(info)
+ logging.info(info)
- print("Successfully clightning lnd.")
+ logging.info("Successfully clightning lnd.")
break
except Exception as e:
- print(e)
+ logging.error(e)
time.sleep(config.pollrate)
- print(
+ logging.info(
"Attempting again... {}/{}...".format(
i + 1, config.connection_attempts
)
@@ -46,7 +47,7 @@ class clightning:
"Could not connect to clightning. Check your port tunneling settings and try again."
)
- print("Ready for payments requests.")
+ logging.info("Ready for payments requests.")
return
def create_qr(self, uuid, address, value):
@@ -71,7 +72,7 @@ class clightning:
invoices = self.clightning.listinvoices(uuid)['invoices']
if len(invoices) == 0:
- print("Could not find invoice on node. Something's wrong.")
+ logging.error("Could not find invoice on node. Something's wrong.")
return 0, 0
invoice = invoices[0]
diff --git a/node/lnd.py b/node/lnd.py
@@ -7,6 +7,7 @@ from base64 import b64decode
from google.protobuf.json_format import MessageToJson
import uuid
import qrcode
+import logging
from payments.price_feed import get_btc_value
@@ -22,7 +23,7 @@ class lnd:
# Conect to lightning node
connection_str = "{}:{}".format(config.host, config.lnd_rpcport)
- print(
+ logging.info(
"Attempting to connect to lightning node {}. This may take a few seconds...".format(
connection_str
)
@@ -30,7 +31,7 @@ class lnd:
for i in range(config.connection_attempts):
try:
- print("Attempting to initialise lnd rpc client...")
+ logging.info("Attempting to initialise lnd rpc client...")
time.sleep(3)
self.lnd = LNDClient(
"{}:{}".format(config.host, config.lnd_rpcport),
@@ -39,21 +40,21 @@ class lnd:
)
if "invoice" in self.certs["macaroon"]:
- print("Testing we can fetch invoices...")
+ logging.info("Testing we can fetch invoices...")
inv, _ = self.create_lnd_invoice(1)
- print(inv)
+ logging.info(inv)
else:
- print("Getting lnd info...")
+ logging.info("Getting lnd info...")
info = self.lnd.get_info()
- print(info)
+ logging.info(info)
- print("Successfully contacted lnd.")
+ logging.info("Successfully contacted lnd.")
break
except Exception as e:
- print(e)
+ logging.error(e)
time.sleep(config.pollrate)
- print(
+ logging.info(
"Attempting again... {}/{}...".format(
i + 1, config.connection_attempts
)
@@ -63,7 +64,7 @@ class lnd:
"Could not connect to lnd. Check your gRPC / port tunneling settings and try again."
)
- print("Ready for payments requests.")
+ logging.info("Ready for payments requests.")
return
def create_qr(self, uuid, address, value):
@@ -85,7 +86,7 @@ class lnd:
# SSH copy
if config.tunnel_host is not None:
- print(
+ logging.warning(
"Could not find tls.cert or {} in SatSale folder. \
Attempting to download from remote lnd directory.".format(config.lnd_macaroon)
)
@@ -109,10 +110,10 @@ class lnd:
}
except Exception as e:
- print(e)
- print("Failed to copy tls and macaroon files to local machine.")
+ logging.error(e)
+ logging.error("Failed to copy tls and macaroon files to local machine.")
else:
- print("Found tls.cert and admin.macaroon.")
+ logging.info("Found tls.cert and admin.macaroon.")
return
# Create lightning invoice
@@ -132,7 +133,7 @@ class lnd:
ret = json.loads(
MessageToJson(self.lnd.send_payment(invoice, fee_limit_msat=20*1000))
)
- print(ret)
+ logging.info(ret)
return
diff --git a/payments/database.py b/payments/database.py
@@ -3,7 +3,7 @@ import sqlite3
def create_database(name="database.db"):
with sqlite3.connect("database.db") as conn:
- print("Creating new database.db...")
+ logging.info("Creating new database.db...")
conn.execute(
"CREATE TABLE payments (uuid TEXT, fiat_value DECIMAL, btc_value DECIMAL, method TEXT, address TEXT, time DECIMAL, webhook TEXT, rhash TEXT)"
)
diff --git a/payments/price_feed.py b/payments/price_feed.py
@@ -1,4 +1,5 @@
import requests
+import logging
import config
@@ -31,8 +32,8 @@ def get_price(currency, currency_provider=config.currency_provider):
break
except Exception as e:
- print(e)
- print(
+ logging.error(e)
+ logging.info(
"Attempting again... {}/{}...".format(i + 1, config.connection_attempts)
)
@@ -44,7 +45,7 @@ def get_price(currency, currency_provider=config.currency_provider):
return price
except:
- print("Failed to find currency {} from {}.".format(currency, price_feed))
+ logging.error("Failed to find currency {} from {}.".format(currency, price_feed))
return None
@@ -57,7 +58,7 @@ def get_btc_value(base_amount, currency):
if not isinstance(float_value, float):
raise Exception("Fiat value should be a float.")
except Exception as e:
- print(e)
+ logging.error(e)
return float_value
diff --git a/payments/weakhands.py b/payments/weakhands.py
@@ -1,4 +1,5 @@
import requests
+import logging
import config
@@ -15,12 +16,12 @@ def get_quote(amount_lnbtc):
"affiliateId": affiliate,
"depositAmount": str(amount_lnbtc)
}
- print("Getting quote to swap {:.8f} LN-BTC to USDT".format(amount_lnbtc))
+ logging.info("Getting quote to swap {:.8f} LN-BTC to USDT".format(amount_lnbtc))
resp = requests.post(quote_url, json=quote_data)
if resp.status_code != 201:
- print("Failed quote request:")
- print(resp.json())
+ logging.error("Failed quote request:")
+ logging.error(resp.json())
return False
return resp.json()
@@ -34,20 +35,20 @@ def get_swap(quote, amount_lnbtc, liquid_address):
"settleAddress": liquid_address,
"affiliateId": affiliate,
}
- print("Creating order to swap {:.8f} LN-BTC to USDT (liquid: {})".format(amount_lnbtc, liquid_address))
+ logging.info("Creating order to swap {:.8f} LN-BTC to USDT (liquid: {})".format(amount_lnbtc, liquid_address))
resp = requests.post(swap_url, json=data)
if resp.status_code != 201:
- print("Failed to create order:")
- print(resp.json())
+ logging.error("Failed to create order:")
+ logging.error(resp.json())
return False
return resp.json()
def pay_swap(node, swap):
payment_req = swap['depositAddress']['paymentRequest']
- print("Paying invoice: {}".format(payment_req))
+ logging.info("Paying invoice: {}".format(payment_req))
node.pay_invoice(payment_req)
return True
@@ -55,18 +56,18 @@ def swap_lnbtc_for_lusdt(node, amount_lnbtc, liquid_address):
try:
quote = get_quote(amount_lnbtc)
if not quote:
- print("Quote failed, not swapping this order.")
+ logging.error("Quote failed, not swapping this order.")
return False
swap = get_swap(quote, amount_lnbtc, liquid_address)
if not swap:
- print("Creating order failed, not swapping this payment.")
+ logging.error("Creating order failed, not swapping this payment.")
return False
pay_swap(node, swap)
- print("Paid invoice! Swapped ")
+ logging.info("Paid invoice! Swapped ")
except Exception as e:
- print("Error encountered during swap: {}".format(e))
+ logging.error("Error encountered during swap: {}".format(e))
return
diff --git a/satsale.py b/satsale.py
@@ -14,6 +14,7 @@ import uuid
import sqlite3
from pprint import pprint
import json
+import logging
from gateways import ssh_tunnel
from gateways import paynym
@@ -26,6 +27,10 @@ from node import clightning
from gateways import woo_webhook
+logging.basicConfig(format='[%(asctime)s] [%(levelname)s] %(message)s',
+ datefmt='%Y-%m-%d %H:%M:%S %z',
+ level=getattr(logging, config.loglevel))
+
app = Flask(__name__)
# Load a SatSale API key or create a new one
@@ -37,7 +42,7 @@ else:
app.config["SECRET_KEY"] = os.urandom(64).hex()
f.write(app.config["SECRET_KEY"])
-print("Initialised Flask with secret key: {}".format(app.config["SECRET_KEY"]))
+logging.info("Initialised Flask with secret key: {}".format(app.config["SECRET_KEY"]))
# Create payment database if it does not exist
if not os.path.exists("database.db"):
@@ -127,13 +132,13 @@ class create_payment(Resource):
if webhook is None:
webhook = None
else:
- print("Webhook payment: {}".format(webhook))
+ logging.info("Webhook payment: {}".format(webhook))
# Create the payment using one of the connected nodes as a base
# ready to recieve the invoice.
node = get_node(payment_method)
if node is None:
- print("Invalid payment method {}".format(payment_method))
+ logging.warning("Invalid payment method {}".format(payment_method))
return {"message": "Invalid payment method."}, 400
invoice = {
@@ -155,7 +160,7 @@ class create_payment(Resource):
database.write_to_database(invoice)
invoice["time_left"] = config.payment_timeout - (time.time() - invoice["time"])
- print("Created invoice:")
+ logging.info("Created invoice:")
pprint(invoice)
print()
@@ -222,17 +227,17 @@ class complete_payment(Resource):
# Call webhook to confirm payment with merchant
if (invoice["webhook"] != None) and (invoice["webhook"] != ""):
- print("Calling webhook {}".format(invoice["webhook"]))
+ logging.info("Calling webhook {}".format(invoice["webhook"]))
response = woo_webhook.hook(app.config["SECRET_KEY"], invoice, order_id)
if response.status_code != 200:
err = "Failed to confirm order payment via webhook {}, please contact the store to ensure the order has been confirmed, error response is: {}".format(
response.status_code, response.text
)
- print(err)
+ logging.error(err)
return {"message": err}, 500
- print("Successfully confirmed payment via webhook.")
+ logging.info("Successfully confirmed payment via webhook.")
return {"message": "Payment confirmed with store."}, 200
return {"message": "Payment confirmed."}, 200
@@ -283,7 +288,7 @@ def check_payment_status(uuid):
}
)
- print("Invoice {} status: {}".format(uuid, status))
+ logging.debug("Invoice {} status: {}".format(uuid, status))
return status
@@ -306,15 +311,15 @@ api.add_resource(complete_payment, "/api/completepayment")
# Test connections on startup:
-print("Connecting to node...")
+logging.info("Connecting to node...")
bitcoin_node = bitcoind.btcd()
-print("Connection to bitcoin node successful.")
+logging.info("Connection to bitcoin node successful.")
if config.pay_method == "lnd":
lightning_node = lnd.lnd()
- print("Connection to lightning node (lnd) successful.")
+ logging.info("Connection to lightning node (lnd) successful.")
elif config.pay_method == "clightning":
lightning_node = clightning.clightning()
- print("Connection to lightning node (clightning) successful.")
+ logging.info("Connection to lightning node (clightning) successful.")
if config.lightning_address is not None: