docs/log-ingesting.md
Flow includes a log ingestion system for collecting and querying structured logs from your projects. Logs are stored in SQLite for later analysis.
f server
This starts the HTTP server on 127.0.0.1:9060 (default). Options:
--host <IP> - Bind address (default: 127.0.0.1)--port <PORT> - Port number (default: 9060)GET /health
Returns {"status": "ok"} when the server is running.
POST /logs/ingest
Content-Type: application/json
Single log:
{
"project": "my-app",
"content": "TypeError: Cannot read property 'x' of undefined",
"timestamp": 1733150000000,
"type": "error",
"service": "api",
"stack": "at handler (api.ts:42)\nat processRequest (server.ts:100)",
"format": "text"
}
Batch:
[
{
"project": "my-app",
"content": "Request received",
"timestamp": 1733150000000,
"type": "log",
"service": "api",
"format": "text"
},
{
"project": "my-app",
"content": "Database query",
"timestamp": 1733150001000,
"type": "log",
"service": "db",
"format": "text"
}
]
Response:
{ "inserted": 1, "ids": [42] }
GET /logs/query
Query parameters:
| Parameter | Description |
|---|---|
project | Filter by project name |
service | Filter by service |
type | Filter by log type (log or error) |
since | Timestamp (ms) - logs after this time |
until | Timestamp (ms) - logs before this time |
limit | Max results (default: 100) |
offset | Skip N results for pagination |
Examples:
# All logs
curl "http://127.0.0.1:9060/logs/query"
# Errors for a project
curl "http://127.0.0.1:9060/logs/query?project=my-app&type=error"
# Logs from the last hour
curl "http://127.0.0.1:9060/logs/query?since=$(($(date +%s) * 1000 - 3600000))"
| Field | Type | Required | Description |
|---|---|---|---|
project | string | yes | Project identifier |
content | string | yes | Log message or error text |
timestamp | integer | yes | Unix timestamp in milliseconds |
type | string | yes | "log" or "error" |
service | string | yes | Service/task name that produced the log |
stack | string | no | Stack trace for errors |
format | string | no | "text" (default) or "json" |
Logs are stored in ~/.config/flow/flow.db in the logs table. You can query directly:
sqlite3 ~/.config/flow/flow.db "SELECT * FROM logs WHERE log_type='error' ORDER BY timestamp DESC LIMIT 10;"
async function sendLog(entry: {
project: string;
content: string;
type: "log" | "error";
service: string;
stack?: string;
}) {
await fetch("http://127.0.0.1:9060/logs/ingest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...entry,
timestamp: Date.now(),
format: "text",
}),
});
}
// Usage
sendLog({
project: "my-app",
content: "User login failed",
type: "error",
service: "auth",
});
import requests
import time
def send_log(project, content, log_type, service, stack=None):
requests.post("http://127.0.0.1:9060/logs/ingest", json={
"project": project,
"content": content,
"timestamp": int(time.time() * 1000),
"type": log_type,
"service": service,
"stack": stack,
"format": "text"
})
# Usage
send_log("my-app", "Database connection failed", "error", "db")
curl -X POST http://127.0.0.1:9060/logs/ingest \
-H "Content-Type: application/json" \
-d '{"project":"my-app","content":"Test error","timestamp":'$(date +%s000)',"type":"error","service":"cli","format":"text"}'
Run the test task to verify the system is working:
# Terminal 1: Start the server
f server
# Terminal 2: Run tests
f test-log-server