Skip to content

Commit 249aece

Browse files
authored
logfire implementation optional with env variable and measuaring logger and traces of handle messages (#10)
1 parent d1db2df commit 249aece

File tree

9 files changed

+72
-13
lines changed

9 files changed

+72
-13
lines changed

.env.example

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
TELEGRAM_TOKEN="fancyTOKENfromGODFATHER"
1+
TELEGRAM_TOKEN="fancyTOKENfromGODFATHER"
2+
LOGFIRE_ENABLED=true
3+
LOGFIRE_TOKEN="somepydanticToken"

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@ If you prefer to use docker run sentence and build the image yourself follow the
7575
```
7676
3. Open Telegram and send a message to your bot clicking in any button like a URL. The bot will respond with a QR code image.
7777

78+
## Observability (Optional)
79+
80+
This project includes optional observability features powered by [Pydantic Logfire](https://logfire.pydantic.dev/docs/). You can enable logging and tracing by configuring the following environment variables in your `.env` file:
81+
82+
```env
83+
LOGFIRE_ENABLED=true
84+
LOGFIRE_TOKEN="your_logfire_token"
85+
```
86+
87+
### How to Enable Observability
88+
1. Set `LOGFIRE_ENABLED` to `true` in your `.env` file.
89+
2. Provide your Logfire token in the `LOGFIRE_TOKEN` variable.
90+
91+
When enabled, the bot will log events and traces to Logfire, providing insights into its runtime behavior and performance.
92+
93+
> **Note:** Observability is disabled by default. If `LOGFIRE_ENABLED` is set to `false` or the `LOGFIRE_TOKEN` is not provided, the bot will use a no-op logger as a fallback.
94+
7895
## Contributing
7996

8097
Contributions are welcome! Please open an issue or submit a pull request.

app/app.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
ContextTypes,
1414
CallbackQueryHandler,
1515
)
16-
from app.core.config import settings, logger
16+
from app.core.config import settings, logger, logfire
1717
from app.core.models import (
1818
UserState,
1919
)
@@ -64,7 +64,8 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
6464

6565
# Call the appropriate handler or fallback
6666
handler = state_handlers.get(user_state, handle_invalid_state)
67-
await handler(update, context)
67+
with logfire.span(str(handler.__name__)):
68+
await handler(update, context)
6869

6970

7071
# Handle Button Callbacks

app/core/config.py

+28
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,39 @@
44

55
class AppSettings(BaseSettings):
66
TELEGRAM_TOKEN: str = ""
7+
LOGFIRE_ENABLED: bool = False
8+
LOGFIRE_TOKEN: str = ""
79

810

911
logging.basicConfig(
1012
format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO
1113
)
1214

15+
16+
def logfire_init(): # pragma: no cover
17+
# Check if Logfire is enabled in settings
18+
if settings.LOGFIRE_ENABLED:
19+
import logfire
20+
21+
logfire.configure(token=settings.LOGFIRE_TOKEN)
22+
logger.addHandler(logfire.LogfireLoggingHandler())
23+
logger.info("🪵🔥 Logging to Logfire enabled")
24+
else:
25+
logger.info("🪵🔥 Logging to Logfire disabled")
26+
# Define a no-op context manager as fallback
27+
from contextlib import contextmanager
28+
29+
@contextmanager
30+
def noop_span(*args, **kwargs):
31+
yield
32+
33+
class DummyLogfire:
34+
span = noop_span
35+
36+
logfire = DummyLogfire()
37+
return logfire
38+
39+
1340
logger = logging.getLogger(__name__)
1441
settings = AppSettings()
42+
logfire = logfire_init()

app/functions/text_qr.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from telegram import Update
22
from telegram.ext import ContextTypes
3-
from pydantic import ValidationError
43
from app.functions.shared import command_options
54
from app.qrcodegen import generate_text_qr
65

7-
async def text_qr_handle_text_state(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
6+
7+
async def text_qr_handle_text_state(
8+
update: Update, context: ContextTypes.DEFAULT_TYPE
9+
) -> None:
810
qr_code = await generate_text_qr(text=update.message.text)
911
# Send QR code image
1012
await update.message.reply_photo(photo=qr_code, caption="Here is your QR code!")
1113
# Clear user data to prevent unwanted behavior
1214
context.user_data.clear()
13-
await command_options(update, context)
15+
await command_options(update, context)

app/functions/url_qr.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from app.functions.shared import command_options
66
from app.qrcodegen import generate_url_qr
77

8-
async def url_qr_handle_url_state(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
8+
9+
async def url_qr_handle_url_state(
10+
update: Update, context: ContextTypes.DEFAULT_TYPE
11+
) -> None:
912
try:
1013
qr_code = await generate_url_qr(URLQR(url=update.message.text.strip()))
1114
# Send QR code image
@@ -16,4 +19,4 @@ async def url_qr_handle_url_state(update: Update, context: ContextTypes.DEFAULT_
1619
except ValidationError:
1720
await update.message.reply_text(
1821
"❌ Invalid URL. Please send a valid URL starting with 'http://' or 'https://'."
19-
)
22+
)

app/functions/vcard_qr.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
)
1212

1313

14-
async def vcard_qr_handle_name_state(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
14+
async def vcard_qr_handle_name_state(
15+
update: Update, context: ContextTypes.DEFAULT_TYPE
16+
) -> None:
1517
context.user_data["name"] = update.message.text
1618
context.user_data["state"] = UserState.VCARD_AWAITING_SURNAME
1719
await update.message.reply_text("Please send the surname:")

app/functions/wifi_qr.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from telegram import Update
22
from telegram.ext import ContextTypes
33
from pydantic import ValidationError
4-
from app.core.models import WifiQR,WiFiSSIDModel,UserState
4+
from app.core.models import WifiQR, WiFiSSIDModel, UserState
55
from app.functions.shared import command_options
66
from app.qrcodegen import generate_wifi_qr
77

8-
async def wifi_qr_handle_ssid_state(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
8+
9+
async def wifi_qr_handle_ssid_state(
10+
update: Update, context: ContextTypes.DEFAULT_TYPE
11+
) -> None:
912
try:
1013
wifi = WiFiSSIDModel(ssid=update.message.text) # Validation using Pydantic
1114
context.user_data["ssid"] = wifi.ssid
@@ -32,4 +35,4 @@ async def wifi_qr_handle_password_state(
3235
except ValidationError:
3336
await update.message.reply_text(
3437
"❌ Invalid SSID or Password. Please send a valid SSID (1-32 characters) and a Valid Password between 8 and 63 characters."
35-
)
38+
)

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
python-telegram-bot==22.0
22
qrcode[pil]==8.0
33
pydantic-settings==2.8.1
4-
pydantic[email]==2.11.1
4+
pydantic[email]==2.11.1
5+
logfire==3.12.0

0 commit comments

Comments
 (0)