Metadata-Version: 2.4
Name: pylogstr
Version: 0.1.1
Summary: Send notifications over Nostr
License-Expression: MIT
Project-URL: Homepage, https://github.com/GitHappens2Me/logstr
Project-URL: Repository, https://github.com/GitHappens2Me/logstr.git
Project-URL: Issues, https://github.com/GitHappens2Me/logstr/issues
Keywords: nostr,logging,notifications,status updates,encrypted
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Logging
Classifier: Topic :: Communications :: Chat
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pynostr
Requires-Dist: requests
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-mock>=3.10; extra == "dev"
Requires-Dist: ruff>=0.8.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: taskipy>=1.0; extra == "dev"
Dynamic: license-file

# logstr

[![PyPI version](https://badge.fury.io/py/pylogstr.svg)](https://badge.fury.io/py/pylogstr)
[![Python versions](https://img.shields.io/pypi/pyversions/pylogstr.svg)](https://pypi.org/project/pylogstr/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**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

```bash
pip install pylogstr
```

Requires Python 3.10+.

For development (includes test dependencies):
```bash
pip install -e ".[dev]"
```

## Quick Start

```python
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 `logging` infrastructure
- **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:

```python
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}`):

```python
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):

```python
logstr.notify("Critical: API is down!")
```

### Decorators

Auto-notify when functions run:

```python
@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:

```python
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

```python
with Logstr(nsec_file="nsec.txt", recipient_npub="npub...") as service:
    service.queue_message("Hello from context manager")
```

### Cleanup

```python
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:

1. **Direct parameter** (`nsec=`, `recipient_npub=`)
2. **File** (`nsec_file=`, `recipient_npub_file=`)
3. **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.io`
- `wss://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

```python
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

```python
@logstr.notify_on_call
def nightly_backup():
    """Get notified when backup starts and finishes"""
    # ... backup logic ...
    pass
```

### Status update

```python
import time

while True:
    if not check_system_health():
        logstr.notify("Health check failed!")
    time.sleep(60)
```



## Advanced

### Custom Formatting

```python
logstr.init(
    format_str="%(name)s | %(levelname)s | %(message)s"
)
```


## Contributing

```bash
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 `pytest` to 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](LICENSE).
