Thank you for your interest in contributing to loq! This document provides guidelines and instructions for contributing.
Be kind, respectful, and constructive. We're all here to build something useful together.
- Check existing issues to avoid duplicates
- Use the bug report template
- Include:
- loq version (
loq --version) - OS and version
- Steps to reproduce
- Expected vs actual behavior
- Sample log line(s) if relevant
- loq version (
- Check existing issues/discussions
- Describe the use case
- Propose a solution if you have one
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Add/update tests
- Ensure all tests pass:
bun test - Ensure coverage stays above 95%:
bun test --coverage - Submit a PR
# Clone your fork
git clone https://github.com/YOUR_USERNAME/loq.git
cd loq
# Install dependencies
bun install
# Run tests
bun test
# Run in dev mode
bun run devsrc/
├── cli/ # Command-line interface
├── config/ # Configuration loading
├── parser/ # Log parsing
│ └── formats/ # Individual format parsers
├── query/ # Query language
├── output/ # Output formatting
└── utils/ # Utilities
tests/ # Mirror of src/ structure
// src/parser/formats/myformat.ts
import type { LogEntry, LogParser } from '../types';
export const myFormatParser: LogParser = {
name: 'myformat',
detect(line: string): boolean {
// Fast check - is this line in my format?
return /^MYFORMAT:/.test(line);
},
parse(line: string): LogEntry | null {
const match = line.match(/^MYFORMAT: \[(.+?)\] (\w+) - (.+)$/);
if (!match) return null;
return {
raw: line,
timestamp: match[1],
level: match[2].toLowerCase(),
message: match[3],
fields: {
// Include all parsed fields for querying
timestamp: match[1],
level: match[2],
message: match[3],
},
};
},
};// src/parser/auto-detect.ts
import { myFormatParser } from './formats/myformat';
const builtinParsers: LogParser[] = [
jsonParser,
myFormatParser, // Add here - order matters for detection
apacheParser,
// ...
];// tests/parsers/myformat.test.ts
import { describe, expect, test } from 'bun:test';
import { myFormatParser } from '../../src/parser/formats/myformat';
describe('MyFormat Parser', () => {
describe('detect', () => {
test('detects valid format', () => {
expect(myFormatParser.detect('MYFORMAT: [2024-01-01] INFO - test')).toBe(true);
});
test('rejects other formats', () => {
expect(myFormatParser.detect('{"level":"info"}')).toBe(false);
});
});
describe('parse', () => {
test('parses correctly', () => {
const result = myFormatParser.parse('MYFORMAT: [2024-01-01] ERROR - Something failed');
expect(result).not.toBeNull();
expect(result!.timestamp).toBe('2024-01-01');
expect(result!.level).toBe('error');
expect(result!.message).toBe('Something failed');
});
test('returns null for invalid line', () => {
expect(myFormatParser.parse('invalid')).toBeNull();
});
});
});- Use strict mode
- Prefer explicit types for public APIs
- Use
typeimports where possible
- Aim for 95%+ coverage
- Test both success and failure cases
- Use descriptive test names
- Group related tests with
describe()
- Parser
detect()should be fast (regex test, not full parse) - Stream large files, don't load into memory
- Avoid unnecessary allocations in hot paths
- Don't crash on invalid input
- Return
nullfrom parsers for non-matching lines - Log warnings for config issues, don't fail
Use conventional commits:
feat: add support for Docker log format
fix: handle missing timestamp in syslog
docs: update README with new examples
test: add coverage for edge cases
refactor: simplify query executor
Releases are automated via GitHub Actions when a tag is pushed:
git tag v0.2.0
git push origin v0.2.0Thanks to everyone who has contributed to loq!
Questions? Open an issue or discussion!