Skip to content

Commit caf59bf

Browse files
committed
add logs
1 parent 20efe81 commit caf59bf

4 files changed

Lines changed: 199 additions & 1 deletion

File tree

README.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
# pg0
22

3+
[![PyPI version](https://badge.fury.io/py/pg0-embedded.svg)](https://pypi.org/project/pg0-embedded/)
4+
[![PyPI downloads](https://img.shields.io/pypi/dm/pg0-embedded.svg)](https://pypi.org/project/pg0-embedded/)
5+
[![Python versions](https://img.shields.io/pypi/pyversions/pg0-embedded.svg)](https://pypi.org/project/pg0-embedded/)
6+
37
**Zero-dependency embedded PostgreSQL** - Run PostgreSQL locally without installation. A single binary that downloads and manages PostgreSQL for you.
48

59
Includes **pgvector** for AI/vector workloads out of the box.
610

711
## Features
812

913
- **Zero dependencies** - single binary, no installation required
10-
- **Embedded PostgreSQL 16** with pgvector pre-installed
14+
- **Embedded PostgreSQL 18** with pgvector 0.8.1 pre-installed
1115
- **Multiple instances** - run multiple PostgreSQL servers simultaneously
1216
- Works on macOS (Apple Silicon), Linux (x86_64 & ARM64), and Windows (x64)
17+
- **Python & Node.js SDKs** - programmatic API for embedding in your applications
1318
- Bundled `psql` client - no separate installation needed
1419
- Data persists between restarts
1520
- **Smart binary selection** - automatically downloads the right binary for your Linux distribution (Debian/Ubuntu vs Alpine)
1621

1722
## Installation
1823

24+
### CLI Binary
25+
1926
The install script automatically detects your platform and downloads the correct binary:
2027

2128
```bash
@@ -28,6 +35,51 @@ Or with a custom install directory:
2835
INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/vectorize-io/pg0/main/install.sh | bash
2936
```
3037

38+
### Python SDK
39+
40+
Install via pip:
41+
42+
```bash
43+
pip install pg0-embedded
44+
```
45+
46+
Quick start:
47+
48+
```python
49+
from pg0 import Pg0
50+
51+
# Start PostgreSQL
52+
pg = Pg0()
53+
pg.start()
54+
print(pg.uri) # postgresql://postgres:postgres@localhost:5432/postgres
55+
56+
# Or use context manager
57+
with Pg0() as pg:
58+
result = pg.execute("SELECT version();")
59+
print(result)
60+
```
61+
62+
See [PyPI package](https://pypi.org/project/pg0-embedded/) for more details.
63+
64+
### Node.js SDK
65+
66+
Install via npm:
67+
68+
```bash
69+
npm install @vectorize-io/pg0
70+
```
71+
72+
Quick start:
73+
74+
```typescript
75+
import { Pg0 } from '@vectorize-io/pg0';
76+
77+
const pg = new Pg0();
78+
await pg.start();
79+
console.log(await pg.getUri());
80+
await pg.stop();
81+
```
82+
3183
### Linux Distributions
3284

3385
pg0 provides separate binaries optimized for different Linux distributions:
@@ -162,6 +214,7 @@ pg0 provides the following commands:
162214
4. **info** - Display instance information (status, connection URI, etc.)
163215
5. **list** - List all PostgreSQL instances
164216
6. **psql** - Open an interactive psql shell connected to an instance
217+
7. **logs** - View PostgreSQL logs for debugging
165218

166219
### Start PostgreSQL
167220

@@ -229,6 +282,26 @@ pg0 psql -c "SELECT version();"
229282
pg0 psql -f schema.sql
230283
```
231284

285+
### View Logs
286+
287+
View PostgreSQL logs for debugging startup issues or errors:
288+
289+
```bash
290+
# View all logs
291+
pg0 logs
292+
293+
# View last 50 lines
294+
pg0 logs -n 50
295+
296+
# Follow logs in real-time (like tail -f)
297+
pg0 logs --follow
298+
299+
# Logs for a specific instance
300+
pg0 logs --name myapp
301+
```
302+
303+
Logs are stored in `~/.pg0/instances/<name>/data/log/`.
304+
232305
### Using pgvector
233306

234307
pgvector is pre-installed. Just enable it:

sdk/node/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,18 @@ export function listInstancesSync(): InstanceInfo[] {
517517
return JSON.parse(runPg0Sync("list", "-o", "json"));
518518
}
519519

520+
/** List available PostgreSQL extensions. */
521+
export async function listExtensions(): Promise<string[]> {
522+
const { stdout } = await runPg0("list-extensions");
523+
return stdout.trim().split("\n").filter(line => line.trim());
524+
}
525+
526+
/** List available PostgreSQL extensions (synchronous). */
527+
export function listExtensionsSync(): string[] {
528+
const stdout = runPg0Sync("list-extensions");
529+
return stdout.trim().split("\n").filter(line => line.trim());
530+
}
531+
520532
/** Start a PostgreSQL instance (convenience function). */
521533
export async function start(options: Pg0Options = {}): Promise<InstanceInfo> {
522534
return new Pg0(options).start();

sdk/python/pg0/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,22 @@ def list_instances() -> list[InstanceInfo]:
436436
return [InstanceInfo.from_dict(item) for item in data]
437437

438438

439+
def list_extensions() -> list[str]:
440+
"""
441+
List available PostgreSQL extensions.
442+
443+
Returns:
444+
List of available extension names
445+
446+
Example:
447+
extensions = pg0.list_extensions()
448+
print(extensions) # ['vector', 'postgis', ...]
449+
"""
450+
result = _run_pg0("list-extensions", check=False)
451+
lines = result.stdout.strip().split("\n")
452+
return [line.strip() for line in lines if line.strip()]
453+
454+
439455
def start(
440456
name: str = "default",
441457
port: int = 5432,
@@ -531,6 +547,7 @@ def info(name: str = "default") -> InstanceInfo:
531547
"Pg0AlreadyRunningError",
532548
"install",
533549
"list_instances",
550+
"list_extensions",
534551
"start",
535552
"stop",
536553
"drop",

src/main.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ enum Commands {
125125
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
126126
args: Vec<String>,
127127
},
128+
/// Show PostgreSQL logs
129+
Logs {
130+
/// Instance name
131+
#[arg(long, default_value = DEFAULT_INSTANCE_NAME)]
132+
name: String,
133+
134+
/// Number of lines to show (default: all)
135+
#[arg(short = 'n', long)]
136+
lines: Option<usize>,
137+
138+
/// Follow log output (like tail -f)
139+
#[arg(short, long)]
140+
follow: bool,
141+
},
128142
/// Install a PostgreSQL extension (e.g., pgvector)
129143
InstallExtension {
130144
/// Instance name
@@ -865,6 +879,87 @@ fn psql(name: String, args: Vec<String>) -> Result<(), CliError> {
865879
Ok(())
866880
}
867881

882+
fn logs(name: String, lines: Option<usize>, follow: bool) -> Result<(), CliError> {
883+
let instance_dir = get_instance_dir(&name)?;
884+
let log_dir = instance_dir.join("data").join("log");
885+
886+
if !log_dir.exists() {
887+
return Err(CliError::Other(format!(
888+
"Log directory not found for instance '{}'. Has PostgreSQL been started?",
889+
name
890+
)));
891+
}
892+
893+
// Find the most recent log file
894+
let mut log_files: Vec<_> = fs::read_dir(&log_dir)?
895+
.filter_map(|e| e.ok())
896+
.filter(|e| e.path().is_file())
897+
.collect();
898+
899+
if log_files.is_empty() {
900+
return Err(CliError::Other(format!(
901+
"No log files found for instance '{}'",
902+
name
903+
)));
904+
}
905+
906+
// Sort by modification time, most recent first
907+
log_files.sort_by_key(|e| std::cmp::Reverse(
908+
e.metadata().and_then(|m| m.modified()).ok()
909+
));
910+
911+
let log_file = &log_files[0].path();
912+
913+
if follow {
914+
// Follow mode - use tail -f equivalent
915+
println!("Following logs for instance '{}' (Ctrl+C to exit):", name);
916+
println!("Log file: {}", log_file.display());
917+
println!();
918+
919+
let mut file = fs::File::open(log_file)?;
920+
let mut pos = file.metadata()?.len();
921+
922+
// Print existing content first
923+
use std::io::{BufRead, BufReader, Seek, SeekFrom};
924+
file.seek(SeekFrom::Start(0))?;
925+
let reader = BufReader::new(&file);
926+
for line in reader.lines() {
927+
println!("{}", line?);
928+
}
929+
930+
// Now follow new content
931+
loop {
932+
file.seek(SeekFrom::Start(pos))?;
933+
let reader = BufReader::new(&file);
934+
for line in reader.lines() {
935+
println!("{}", line?);
936+
}
937+
pos = file.metadata()?.len();
938+
std::thread::sleep(std::time::Duration::from_millis(100));
939+
}
940+
} else {
941+
// Show logs (optionally limited to N lines)
942+
use std::io::{BufRead, BufReader};
943+
let file = fs::File::open(log_file)?;
944+
let reader = BufReader::new(file);
945+
let all_lines: Vec<_> = reader.lines().collect::<Result<_, _>>()?;
946+
947+
let lines_to_show = if let Some(n) = lines {
948+
&all_lines[all_lines.len().saturating_sub(n)..]
949+
} else {
950+
&all_lines[..]
951+
};
952+
953+
println!("Logs for instance '{}' ({})", name, log_file.display());
954+
println!();
955+
for line in lines_to_show {
956+
println!("{}", line);
957+
}
958+
}
959+
960+
Ok(())
961+
}
962+
868963
fn find_installed_version(installation_dir: &PathBuf) -> Result<String, CliError> {
869964
if let Ok(entries) = fs::read_dir(installation_dir) {
870965
for entry in entries.flatten() {
@@ -1070,6 +1165,7 @@ fn main() {
10701165
Commands::Info { name, output } => info(name, output),
10711166
Commands::List { output } => list(output),
10721167
Commands::Psql { name, args } => psql(name, args),
1168+
Commands::Logs { name, lines, follow } => logs(name, lines, follow),
10731169
Commands::InstallExtension { name, extension } => install_extension(name, extension),
10741170
Commands::ListExtensions => list_extensions(),
10751171
};

0 commit comments

Comments
 (0)