Skip to content

code-with-auto/loq

Repository files navigation

loq

Fast, friendly CLI log analyzer built with Bun. Think "jq for logs."

CI Documentation License: MIT Bun

Features

  • Auto-detects log formats - JSON, Apache, Nginx, Syslog, CLF
  • Simple query syntax - loq app.log where level=error
  • SQL-like queries - loq "SELECT * FROM app.log WHERE status >= 400"
  • Powerful filtering - equality, comparison, contains, regex
  • Aggregations - count, group by, sum, avg, min, max
  • Multiple outputs - colorized, table, JSON, CSV
  • Streaming - handles large files efficiently
  • Pipe support - cat *.log | loq where level=error
  • Tail mode - loq -f app.log where level=error
  • Extensible - add custom log formats via config

Installation

Using Bun

# Install Bun if you haven't curl -fsSL https://bun.sh/install | bash # Clone and install git clone https://github.com/code-with-auto/loq.git cd loq bun install bun link

Global binary

# After bun link, loq is available at ~/.bun/bin/loq # Add to your PATH or use directly: ~/.bun/bin/loq --help

Quick Start

# View all logs with colorized output loq app.log # Filter by level loq app.log where level=error # Filter HTTP logs by status loq access.log where "status >= 400" # Search in messages loq app.log where message contains "timeout" # Regex matching loq app.log where path matches "^/api/v[0-9]+" # Boolean logic loq app.log where level=error and message contains "database" loq app.log where level=error or level=warn # Aggregations loq app.log count by level loq access.log count by status # Limit results loq app.log where level=error limit 10 # Tail mode (like tail -f) loq -f app.log where level=error # Pipe support cat logs/*.log | loq where level=error kubectl logs pod/my-app | loq where level=error # Output formats loq app.log -o json where level=error loq app.log -o csv where level=error loq app.log -o table

Query Syntax

DSL Syntax

loq <file> [where <conditions>] [count [by <field>]] [limit <n>]

Operators:

  • =, != - equality
  • >, <, >=, <= - comparison
  • contains - substring match
  • matches - regex match

Boolean:

  • and, or, not
  • Parentheses for grouping: (level=error or level=warn) and status>=500

Time filters:

  • after yesterday
  • before today
  • between "10:00" and "11:00" today

SQL Syntax

loq "SELECT * FROM app.log WHERE level='error' LIMIT 10" loq "SELECT level, COUNT(*) FROM app.log GROUP BY level" loq "SELECT path, AVG(response_time) FROM access.log GROUP BY path"

Supported Log Formats

Auto-detected formats

Format Example
JSON {"level":"info","message":"Server started"}
Apache/Nginx 192.168.1.1 - - [20/Dec/2024:10:00:00 +0000] "GET /api HTTP/1.1" 200 1234
Syslog Dec 20 12:34:56 myhost myapp[1234]: Message
CLF 127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] "GET / HTTP/1.0" 200 2326
Plain text Fallback - entire line as message, no field extraction

Note: Plain text mode has limited query support. Use message contains "ERROR" instead of level=error. For full query capabilities, use structured logging (JSON) or create a custom format.

Custom formats

Create ~/.loqrc or ./loq.config.ts:

// loq.config.ts export default { formats: [ { name: 'my-app', detect: /^\[\d{4}-\d{2}-\d{2}/, parse: { pattern: /^\[(?<timestamp>[^\]]+)\] (?<level>\w+): (?<message>.+)$/, fields: { timestamp: 'timestamp', level: 'level', message: 'message', }, }, }, { name: 'nginx-json', detect: (line) => { try { const obj = JSON.parse(line); return 'request_uri' in obj; } catch { return false; } }, parse: (line) => { const obj = JSON.parse(line); return { timestamp: obj.time_iso8601, level: obj.status >= 400 ? 'error' : 'info', message: `${obj.request_method} ${obj.request_uri}`, fields: obj, }; }, }, ], aliases: { errors: 'where level=error', slow: 'where response_time>1000', }, };

Or use JSON (~/.loqrc):

{ "formats": [ { "name": "bracketed", "detect": "^\\[\\d{4}", "parse": { "pattern": "^\\[([^\\]]+)\\] (\\w+): (.+)$", "fields": { "timestamp": 1, "level": 2, "message": 3 } } } ] }

CLI Options

Options: -f, --follow Tail mode (like tail -f) -o, --output Output format: color, table, json, csv --format Force log format: json, apache, syslog, clf -n, --limit Limit number of results -h, --help Show help -v, --version Show version 

Development

# Install dependencies bun install # Run in development mode bun run dev # Run tests bun test # Run tests with coverage bun test --coverage # Type check bun run typecheck # Build standalone binary bun build src/index.ts --compile --outfile loq # Generate documentation bun run docs

Documentation

API documentation is automatically generated using TypeDoc and hosted on GitHub Pages.

# Generate docs locally bun run docs # Serve docs locally bun run docs:serve # Then open http://localhost:8080

Project Structure

loq/ ├── src/ │ ├── index.ts # CLI entry point │ ├── cli/ │ │ └── args.ts # Argument parsing │ ├── config/ │ │ └── loader.ts # Config file loading │ ├── parser/ │ │ ├── types.ts # Types & plugin system │ │ ├── auto-detect.ts # Format detection │ │ └── formats/ # Built-in parsers │ ├── query/ │ │ ├── lexer.ts # Tokenizer │ │ ├── parser.ts # Query parser │ │ ├── ast.ts # AST types │ │ └── executor.ts # Query execution │ ├── output/ │ │ ├── formatter.ts # Output formatting │ │ └── colors.ts # Terminal colors │ └── utils/ │ └── time.ts # Time utilities ├── tests/ # Test files ├── .github/workflows/ # CI/CD └── package.json 

Adding Custom Formats

Option 1: Config file

Create ~/.loqrc with your format definitions (see above).

Option 2: Contribute a parser

  1. Create a parser in src/parser/formats/:
// src/parser/formats/myformat.ts import type { LogEntry, LogParser } from '../types'; export const myFormatParser: LogParser = { name: 'myformat', detect(line: string): boolean { // Return true if this line matches your format return line.startsWith('MYFORMAT:'); }, parse(line: string): LogEntry | null { // Parse the line and return a LogEntry const match = line.match(/^MYFORMAT: \[(.+?)\] (\w+) - (.+)$/); if (!match) return null; return { raw: line, timestamp: match[1], level: match[2], message: match[3], fields: { /* any additional fields */ }, }; }, };
  1. Register in src/parser/auto-detect.ts:
import { myFormatParser } from './formats/myformat'; const builtinParsers: LogParser[] = [ myFormatParser, // Add here jsonParser, // ... ];
  1. Add tests in tests/parsers/myformat.test.ts

  2. Submit a PR!

License

MIT

Contributing

Contributions welcome! See CONTRIBUTORS.md for guidelines.