11# Custom Formats
22
3- Add custom log format parsers via config files.
3+ loq supports adding custom log format parsers in two ways:
4+ 1 . ** Config files** - Quick setup for personal/project use
5+ 2 . ** Code contribution** - Add built-in parsers for everyone
46
5- ## Config File Locations
7+ ## Config File Setup
8+
9+ ### Config Locations
610
711loq looks for config in these locations (in order):
812
@@ -14,7 +18,9 @@ loq looks for config in these locations (in order):
14186 . ` ~/.loqrc.json `
15197 . ` ~/.config/loq/config.json `
1620
17- ## TypeScript Config
21+ ### TypeScript Config
22+
23+ Best for complex parsing logic:
1824
1925``` typescript
2026// loq.config.ts
@@ -60,7 +66,9 @@ export default {
6066};
6167```
6268
63- ## JSON Config
69+ ### JSON Config
70+
71+ Simpler option using regex patterns:
6472
6573``` json
6674{
@@ -80,3 +88,286 @@ export default {
8088 ]
8189}
8290```
91+
92+ ## Format Definition
93+
94+ ### Required Fields
95+
96+ | Field | Type | Description |
97+ | -------| ------| -------------|
98+ | ` name ` | string | Unique identifier for the format |
99+ | ` detect ` | RegExp \| string \| function | How to identify this log format |
100+ | ` parse ` | object \| function | How to parse log lines |
101+
102+ ### Detection Methods
103+
104+ ** Regex pattern:**
105+ ``` typescript
106+ detect : / ^ \[ \d {4} -\d {2} -\d {2} /
107+ ```
108+
109+ ** String (converted to regex):**
110+ ``` json
111+ "detect" : " ^\\ [\\ d{4}"
112+ ```
113+
114+ ** Function (most flexible):**
115+ ``` typescript
116+ detect : (line ) => {
117+ try {
118+ const obj = JSON .parse (line );
119+ return ' my_special_field' in obj ;
120+ } catch {
121+ return false ;
122+ }
123+ }
124+ ```
125+
126+ ### Parse Methods
127+
128+ ** Pattern-based:**
129+ ``` typescript
130+ parse : {
131+ pattern : / ^ \[ (?<timestamp >[^ \] ] + )\] (?<level >\w + ): (?<message >. + )$ / ,
132+ fields : {
133+ timestamp : ' timestamp' , // named group
134+ level : ' level' ,
135+ message : ' message' ,
136+ },
137+ }
138+ ```
139+
140+ ** Function-based:**
141+ ``` typescript
142+ parse : (line ) => {
143+ const parts = line .split (' | ' );
144+ return {
145+ timestamp: parts [0 ],
146+ level: parts [1 ],
147+ message: parts [2 ],
148+ fields: {
149+ custom_field: parts [3 ],
150+ },
151+ };
152+ }
153+ ```
154+
155+ ### LogEntry Structure
156+
157+ Your parser should return this structure:
158+
159+ ``` typescript
160+ interface LogEntry {
161+ raw: string ; // Original line (added automatically)
162+ timestamp? : string ; // ISO timestamp or parseable date
163+ level? : string ; // error, warn, info, debug, etc.
164+ message? : string ; // Main log message
165+ fields? : Record <string , unknown >; // Additional fields
166+ }
167+ ```
168+
169+ ## Using Custom Formats
170+
171+ Once configured, loq auto-detects your format:
172+
173+ ``` bash
174+ # Auto-detect
175+ loq app.log
176+
177+ # Force specific format
178+ loq app.log --format my-app
179+
180+ # Query custom fields
181+ loq app.log where custom_field=value
182+ ```
183+
184+ ## Command Aliases
185+
186+ Define shortcuts for common queries:
187+
188+ ``` typescript
189+ aliases : {
190+ errors : ' where level=error' ,
191+ slow : ' where response_time>1000' ,
192+ today : ' where timestamp after today' ,
193+ ' 5xx' : ' where status>=500 and status<600' ,
194+ }
195+ ```
196+
197+ Usage:
198+ ``` bash
199+ loq app.log errors # expands to: where level=error
200+ loq access.log slow # expands to: where response_time>1000
201+ loq app.log errors limit 10 # can combine with other options
202+ ```
203+
204+ ## Examples
205+
206+ ### Rails Log Format
207+
208+ ``` typescript
209+ {
210+ name : ' rails' ,
211+ detect : / ^ [DIWEF] ,\s * \[ / ,
212+ parse : {
213+ pattern : / ^ (?<level >[DIWEF] ),\s * \[ (?<timestamp >[^ \] ] + )\] \s * (?<pid >\d + )\s * (?<source >\w + )\s * --\s * :\s * (?<message >. * )$ / ,
214+ fields : {
215+ level : ' level' ,
216+ timestamp : ' timestamp' ,
217+ message : ' message' ,
218+ pid : ' pid' ,
219+ source : ' source' ,
220+ },
221+ },
222+ }
223+ ```
224+
225+ ### Docker JSON Logs
226+
227+ ``` typescript
228+ {
229+ name : ' docker-json' ,
230+ detect : (line ) => {
231+ try {
232+ const obj = JSON .parse (line );
233+ return ' log' in obj && ' stream' in obj ;
234+ } catch {
235+ return false ;
236+ }
237+ },
238+ parse : (line ) => {
239+ const obj = JSON .parse (line );
240+ return {
241+ timestamp: obj .time ,
242+ level: obj .stream === ' stderr' ? ' error' : ' info' ,
243+ message: obj .log .trim (),
244+ fields: obj ,
245+ };
246+ },
247+ }
248+ ```
249+
250+ ### Custom App with Metadata
251+
252+ ``` typescript
253+ {
254+ name : ' my-service' ,
255+ detect : / ^ \d {4} -\d {2} -\d {2} T. * \| / ,
256+ parse : (line ) => {
257+ const [timestamp, level, service, traceId, ... rest] = line .split (' | ' );
258+ return {
259+ timestamp ,
260+ level: level .toLowerCase (),
261+ message: rest .join (' | ' ),
262+ fields: {
263+ service ,
264+ traceId ,
265+ },
266+ };
267+ },
268+ }
269+ ```
270+
271+ ## Contributing Built-in Parsers
272+
273+ Want to add a parser for everyone? Follow these steps:
274+
275+ ### 1. Create Parser File
276+
277+ ``` typescript
278+ // src/parser/formats/myformat.ts
279+ import type { LogEntry , LogParser } from ' ../types' ;
280+
281+ export const myFormatParser: LogParser = {
282+ name: ' myformat' ,
283+
284+ detect(line : string ): boolean {
285+ return line .startsWith (' MYFORMAT:' );
286+ },
287+
288+ parse(line : string ): LogEntry | null {
289+ const match = line .match (/ ^ MYFORMAT: \[ (. +? )\] (\w + ) - (. + )$ / );
290+ if (! match ) return null ;
291+
292+ return {
293+ raw: line ,
294+ timestamp: match [1 ],
295+ level: match [2 ],
296+ message: match [3 ],
297+ fields: {},
298+ };
299+ },
300+ };
301+ ```
302+
303+ ### 2. Register Parser
304+
305+ ``` typescript
306+ // src/parser/auto-detect.ts
307+ import { myFormatParser } from ' ./formats/myformat' ;
308+
309+ const builtinParsers: LogParser [] = [
310+ jsonParser ,
311+ apacheParser ,
312+ syslogParser ,
313+ clfParser ,
314+ myFormatParser , // Add here
315+ ];
316+ ```
317+
318+ ### 3. Add Tests
319+
320+ ``` typescript
321+ // tests/parser/myformat.test.ts
322+ import { describe , expect , test } from ' bun:test' ;
323+ import { myFormatParser } from ' ../../src/parser/formats/myformat' ;
324+
325+ describe (' myformat parser' , () => {
326+ test (' detects myformat lines' , () => {
327+ expect (myFormatParser .detect (' MYFORMAT: [2024-01-01] INFO - Hello' )).toBe (true );
328+ expect (myFormatParser .detect (' Some other log' )).toBe (false );
329+ });
330+
331+ test (' parses myformat correctly' , () => {
332+ const result = myFormatParser .parse (' MYFORMAT: [2024-01-01] INFO - Hello world' );
333+ expect (result ).toEqual ({
334+ raw: ' MYFORMAT: [2024-01-01] INFO - Hello world' ,
335+ timestamp: ' 2024-01-01' ,
336+ level: ' INFO' ,
337+ message: ' Hello world' ,
338+ fields: {},
339+ });
340+ });
341+
342+ test (' returns null for invalid lines' , () => {
343+ expect (myFormatParser .parse (' invalid line' )).toBeNull ();
344+ });
345+ });
346+ ```
347+
348+ ### 4. Submit PR
349+
350+ 1 . Fork the repo
351+ 2 . Create a branch: ` git checkout -b add-myformat-parser `
352+ 3 . Make your changes
353+ 4 . Run tests: ` bun test `
354+ 5 . Push and open a PR
355+
356+ ## Debugging Custom Formats
357+
358+ Test your format detection:
359+
360+ ``` bash
361+ # See which format is detected
362+ loq app.log --format auto --verbose
363+
364+ # Force your format to test parsing
365+ loq app.log --format my-custom-format
366+ ```
367+
368+ Check if fields are parsed correctly:
369+
370+ ``` bash
371+ # Output as JSON to see all fields
372+ loq app.log -o json limit 1
373+ ```
0 commit comments