logstr
Send log messages and status updates as encrypted Nostr direct messages.
Logstr bridges Python's standard logging module with the Nostr protocol, allowing you to receive encrypted log notifications on any Nostr client that supports direct messages. Perfect for monitoring long-running jobs from your phone.
Installation
pip install pylogstr
Requires Python 3.10+.
For development (includes test dependencies):
pip install -e ".[dev]"
Quick Start
import logstr
# Initialize with your logstr's nsec and the receiving npub
logstr.init(
nsec_file="~/.config/logstr/nsec", # Logstr's private nsec
recipient_npub="npub...", # Where to send notifications
expiry_in_days=7 # Expire messages after 7 days
)
# Standard logging can now also send encrypted DMs
import logging
logging.error("Database connection failed!", extra={"nostr": True})
# Or bypass logging and only send to over Nostr
logstr.notify("Server is on fire!")
Features
- Encrypted: Uses NIP-04 encrypted direct messages (upgrade to NIP-17 is planned)
- Relay-friendly: NIP-40 expiration timestamps reduce storage load
- Flexible key management: Use CLI args, files, or environment variables
- Optional key generation: Generate a new nsec for your project
- Standard logging: Works with existing
logginginfrastructure - Mobile alerts: Receive logs on any Nostr client
- Async by default: Non-blocking message queue with background flushing
- Decorators: Auto-notify on function entry/exit or exceptions
Usage
Initialization
The init() function sets up the Nostr connection and optionally attaches a handler to the root logger:
import logstr
service = logstr.init(
# Key sources (checked in order: arg > file > env)
nsec="nsec...", # Direct key (optional)
nsec_file="/path/to/nsec", # File containing key (optional)
recipient_npub="npub...", # Recipient's public key or
recipient_npub_file="/path/to/npub", # File containing recipient
# Behavior
auto_create_keys=True, # Generate and store new keys if missing
relays=["wss://relay.damus.io"], # Use custom relays (optional)
expiry_in_days=14, # Message lifetime (None = forever)
flush_interval=20, # Seconds between message flushes
# Logging integration
attach_to_root=True, # Auto-attach NostrHandler to root handler
format_str="%(asctime)s - %(message)s" # Message format
)
Key environment variables: LOGSTR_NSEC, LOGSTR_RECIPIENT_NPUB
Sending Messages
Via standard logging (requires extra={"nostr": True}):
import logging
logging.info("Job started", extra={"nostr": True})
logging.warning("Disk space low", extra={"nostr": True})
logging.error("Payment failed", extra={"nostr": True})
Direct notification (bypasses logging):
logstr.notify("Critical: API is down!")
Decorators
Auto-notify when functions run:
@logstr.notify_on_call
def process_data():
"""Notifies on success and failure"""
pass
@logstr.notify_on_call(only_on_exception=True)
def risky_operation():
"""Only notifies if an exception is raised"""
pass
@logstr.notify_on_call(include_args=False)
def handle_sensitive(user_password):
"""Notifies but redacts arguments"""
pass
Sensitive parameters (password, token, api_key, secret, nsec) are automatically redacted. A custom list can be used via the sensitive_params parameter.
Manual Setup
Alternatively bypass init() and configure manually:
import logging
from logstr import Logstr, NostrHandler
# Create service
service = Logstr(
nsec_file="~/.logstr/nsec",
recipient_npub="npub...",
relays=["wss://relay.damus.io"],
expiry_in_days=7
)
# Create handler
handler = NostrHandler(service)
handler.setLevel(logging.ERROR)
# Attach to specific logger
logger = logging.getLogger("myapp")
logger.addHandler(handler)
Context Manager
with Logstr(nsec_file="nsec.txt", recipient_npub="npub...") as service:
service.queue_message("Hello from context manager")
Cleanup
logstr.close() # Flush pending messages and disconnect
Message Destinations
| Function | Logging handlers | Nostr | When to Use |
|---|---|---|---|
logging.info(..., extra={"nostr": True}) |
✅ | ✅ | Standard logging that also alerts you |
logstr.info() / error() / warning() |
✅ | ✅ | Shorthand for the above |
logstr.notify() |
❌ | ✅ | Nostr only: bypasses logging. Use for urgent alerts you don't want in log files. |
Configuration
Key Management Priority
Keys are loaded in this priority order:
- Direct parameter (
nsec=,recipient_npub=) - File (
nsec_file=,recipient_npub_file=) - Environment variable (
LOGSTR_NSEC,LOGSTR_RECIPIENT_NPUB)
Auto-Generated Keys
If auto_create_keys=True and no key is found, logstr generates a new identity and saves it to ./.logstr/nsec (or the specified nsec_file). You should immediately add it to .gitignore.
Relays
Default relays:
wss://relay.damus.iowss://nos.lol
NIP-40 (expiration) support is automatically detected per relay. Messages sent to non-supporting relays may persist indefinitely.
Message Expiration
Set expiry_in_days to automatically tag messages with NIP-40 expiration timestamps. After this period, relays may delete the messages. This is mostly to reduce storage load on relays. Depending on the Nostr clients even expired message are still shown if they are received before expiry.
This is not a security feature: Relays may retain expired messages
Rate-Limiting
Use flush_interval to set how often messages are sent to relays. Make sure to be conservative, to reduce the risk of rate-limiting. We strongly advise against using logstr for high-frequency logging. Most relays will quickly block your messages.
Examples
Production Error Alerting
import logstr
import logging
logstr.init(
nsec_file="/etc/myapp/nsec",
recipient_npub="npub...", # Your mobile client's npub
expiry_in_days=30
)
try:
process_payment()
except Exception as e:
logging.critical(f"Payment processing failed: {e}", extra={"nostr": True})
raise
Long-Running Job Monitoring
@logstr.notify_on_call
def nightly_backup():
"""Get notified when backup starts and finishes"""
# ... backup logic ...
pass
Status update
import time
while True:
if not check_system_health():
logstr.notify("Health check failed!")
time.sleep(60)
Advanced
Custom Formatting
logstr.init(
format_str="%(name)s | %(levelname)s | %(message)s"
)
Contributing
git clone https://github.com/GitHappens2Me/logstr.git
cd logstr
pip install -e ".[dev]"
pytest
Before submitting a PR:
- Lint: Run
ruff check --fix .to auto-fix style issues (trailing whitespace, import sorting, etc.) - Type check: Run
mypy logstr/to verify type annotations are correct - Test: Run
pytestto ensure all tests pass
Or run all checks at once with task check.
All contributions (suggestions, issues, PRs) are welcome!
License
MIT License - see LICENSE.
Metadata
Release files for pylogstr 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pylogstr-0.1.1.tar.gz | 23.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pylogstr-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.5 kB
Release files / pylogstr-0.1.1.tar.gz
| Download URL | pylogstr-0.1.1.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8901f526f82da2422157fcf2d5aff4bcb2cb61cbe130c9080a31d4a45cf03d55
|
|
BLAKE2b-256 checksum How to use checksums |
b9563153756905f20a3eb64d7833edb45994e8964bf336763da59480ac857fbf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pylogstr-0.1.1-py3-none-any.whl
| Download URL | pylogstr-0.1.1-py3-none-any.whl |
|---|---|
| Size | 16.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0801e7dcf907ebc81047c857e6ed5753a333cf47b48c8c127bf2fd3340d85805
|
|
BLAKE2b-256 checksum How to use checksums |
1500bf934944ebf812251ff34caec554436950f22a918cebffbbcae2fa2c5806
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|