Skip to content

Commit f9e2351

Browse files
committed
feat(log): add remote logging with Supabase integration
Introduces real-time log streaming to Supabase, matching Swift LogRemote functionality. Implements ULID generation for time-sortable identifiers and buffered background processing for optimal performance. Adds SupabaseLogHandler with automatic batching, device tracking via MAC address, and session management. Includes comprehensive ULID implementation supporting Crockford Base32 encoding and monotonic generation guarantees. Enables remote logging through simple configuration with environment variables for seamless development and production monitoring.
1 parent 1f7159e commit f9e2351

File tree

8 files changed

+2444
-6
lines changed

8 files changed

+2444
-6
lines changed

REMOTE_LOGGING.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Remote Logging with PolyLog
2+
3+
PolyLog now supports streaming logs to Supabase in real-time, matching the functionality of the Swift LogRemote implementation!
4+
5+
## Setup
6+
7+
### 1. Create the Supabase table
8+
9+
Run the migration SQL from `polykit-swift/Documentation/Migrations/logs_table.sql` in your Supabase SQL editor to create the `polylogs` table with proper indexes and realtime support.
10+
11+
### 2. Set environment variables
12+
13+
```bash
14+
export POLYLOG_APP_ID="my-awesome-app"
15+
export POLYLOG_SUPABASE_URL="https://xyz.supabase.co"
16+
export POLYLOG_SUPABASE_KEY="your-anon-key"
17+
# Optional: Override table name (defaults to "polylogs")
18+
export POLYLOG_TABLE="polylogs"
19+
```
20+
21+
### 3. Use it in your code
22+
23+
```python
24+
from polykit.log import PolyLog
25+
26+
# Enable remote logging with a single parameter
27+
logger = PolyLog.get_logger(remote=True)
28+
29+
# Logs now stream to Supabase in real-time!
30+
logger.info("Application started")
31+
logger.debug("Debug information")
32+
logger.error("Something went wrong")
33+
```
34+
35+
## How It Works
36+
37+
- **Buffered**: Logs are buffered in memory (max 10 entries or 0.15s)
38+
- **Background thread**: All network operations happen in a background thread
39+
- **Best-effort**: If network fails, logs are dropped (no blocking, no retry)
40+
- **Fast**: Matches Swift implementation's ~100-250ms streaming performance
41+
- **Device tracking**: Stable device ID based on MAC address (with ULID fallback)
42+
- **Session tracking**: Each process run gets a unique session ID
43+
44+
## Schema
45+
46+
Logs are stored with:
47+
48+
- `id`: ULID (time-sortable, generated from log timestamp)
49+
- `timestamp`: When the log was created
50+
- `level`: debug, info, warning, error, critical
51+
- `message`: The log message
52+
- `device_id`: Stable device identifier
53+
- `app_bundle_id`: Your app identifier (from `POLYLOG_APP_ID`)
54+
- `session_id`: Unique per process run
55+
- `group_identifier`: Logger name (optional)
56+
- `created_at`: When inserted into Supabase
57+
58+
## Example: Viewing Logs
59+
60+
You can now use your Swift log viewer app to see Python logs streaming in real-time alongside your Swift app logs!
61+
62+
```python
63+
# Python app
64+
logger = PolyLog.get_logger("DataProcessor", remote=True)
65+
logger.info("Processing started")
66+
logger.info("Processed 1000 records")
67+
logger.info("Processing complete")
68+
```
69+
70+
All these logs will appear in your log viewer with ~100-250ms latency. 🚀
71+
72+
## ULID Support
73+
74+
Python PolyKit now includes ULID generation matching the Swift implementation:
75+
76+
```python
77+
from polykit.core import ULID, generate_ulid
78+
79+
# Generate a ULID (convenience function)
80+
ulid = generate_ulid() # e.g., "01ARZ3NDEKTSV4RRFFQ69G5FAV"
81+
82+
# Generate for a specific datetime
83+
from datetime import datetime
84+
ulid = ULID.generate(datetime(2024, 1, 1))
85+
86+
# Decode a ULID
87+
decoded = ULID.decode(ulid)
88+
print(decoded.timestamp_ms) # Milliseconds since epoch
89+
print(decoded.random_bytes) # 10 bytes of randomness
90+
91+
# Monotonic generation (guaranteed increasing)
92+
from polykit.core import get_ulid_generator
93+
94+
generator = get_ulid_generator()
95+
ulid1 = generator.next()
96+
ulid2 = generator.next() # Always sorts after ulid1
97+
```
98+
99+
## Migration Notes
100+
101+
If you're using environment variables in production:
102+
103+
- Add the three required env vars to your deployment config
104+
- No code changes needed if already using `PolyLog.get_logger()`
105+
- Just add `remote=True` to enable streaming
106+
- Missing env vars? A warning prints to stderr, logging continues normally
107+
108+
## Performance
109+
110+
Remote logging adds minimal overhead:
111+
112+
- Logs are buffered and sent in batches
113+
- Network operations happen on a background thread
114+
- No blocking of main application flow
115+
- Failed network requests are logged to stderr but don't crash
116+
117+
Perfect for development, staging, and production monitoring!

poetry.lock

Lines changed: 1654 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"python-dotenv (>=1.2.1,<2.0.0)",
1717
"requests (>=2.32.5,<3.0.0)",
1818
"send2trash (>=1.8.3,<2.0.0)",
19+
"supabase (>=2.27.0,<3.0.0)",
1920
"tzlocal (>=5.3.1,<6.0.0)",
2021
]
2122
classifiers = [

src/polykit/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ def get_setting(self, key):
4242
from .singleton import Singleton
4343
from .traceback import log_traceback
4444
from .type_utils import get_args, is_literal
45+
from .ulid import ULID, ULIDGenerator, generate_ulid, get_ulid_generator

0 commit comments

Comments
 (0)