@orgloop/logger-file 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OrgLoop contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * JSONL file logger with buffering and rotation.
3
+ *
4
+ * The default production logger for OrgLoop.
5
+ */
6
+ import type { LogEntry, Logger } from '@orgloop/sdk';
7
+ export declare class FileLogger implements Logger {
8
+ readonly id = "file";
9
+ private filePath;
10
+ private buffer;
11
+ private bufferSize;
12
+ private flushTimer;
13
+ private rotation;
14
+ private dirEnsured;
15
+ init(config: Record<string, unknown>): Promise<void>;
16
+ log(entry: LogEntry): Promise<void>;
17
+ flush(): Promise<void>;
18
+ shutdown(): Promise<void>;
19
+ private ensureDir;
20
+ }
21
+ //# sourceMappingURL=file-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-logger.d.ts","sourceRoot":"","sources":["../src/file-logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAmBrD,qBAAa,UAAW,YAAW,MAAM;IACxC,QAAQ,CAAC,EAAE,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,QAAQ,CAKd;IACF,OAAO,CAAC,UAAU,CAAS;IAErB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA2DpD,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAWnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAQjB,SAAS;CAUvB"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * JSONL file logger with buffering and rotation.
3
+ *
4
+ * The default production logger for OrgLoop.
5
+ */
6
+ import { appendFile, mkdir } from 'node:fs/promises';
7
+ import { homedir } from 'node:os';
8
+ import { dirname, resolve } from 'node:path';
9
+ import { parseDuration } from '@orgloop/sdk';
10
+ import { needsRotation, parseSize, rotateFile } from './rotation.js';
11
+ export class FileLogger {
12
+ id = 'file';
13
+ filePath = '';
14
+ buffer = [];
15
+ bufferSize = 100;
16
+ flushTimer = null;
17
+ rotation = {
18
+ maxSize: 100 * 1024 * 1024, // 100MB
19
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7d
20
+ maxFiles: 10,
21
+ compress: true,
22
+ };
23
+ dirEnsured = false;
24
+ async init(config) {
25
+ const cfg = config;
26
+ // Resolve file path (expand ~ to home directory)
27
+ const rawPath = cfg.path ?? `${homedir()}/.orgloop/logs/orgloop.log`;
28
+ const expanded = rawPath.startsWith('~/') ? rawPath.replace('~', homedir()) : rawPath;
29
+ this.filePath = resolve(expanded);
30
+ // Parse rotation config
31
+ if (cfg.rotation) {
32
+ if (cfg.rotation.max_size) {
33
+ this.rotation.maxSize = parseSize(cfg.rotation.max_size);
34
+ }
35
+ if (cfg.rotation.max_age) {
36
+ this.rotation.maxAge = parseDuration(cfg.rotation.max_age);
37
+ }
38
+ if (cfg.rotation.max_files !== undefined) {
39
+ this.rotation.maxFiles = cfg.rotation.max_files;
40
+ }
41
+ if (cfg.rotation.compress !== undefined) {
42
+ this.rotation.compress = cfg.rotation.compress;
43
+ }
44
+ }
45
+ // Parse buffer config
46
+ if (cfg.buffer) {
47
+ if (cfg.buffer.size !== undefined) {
48
+ this.bufferSize = cfg.buffer.size;
49
+ }
50
+ if (cfg.buffer.flush_interval) {
51
+ const intervalMs = parseDuration(cfg.buffer.flush_interval);
52
+ this.flushTimer = setInterval(() => {
53
+ void this.flush();
54
+ }, intervalMs);
55
+ if (this.flushTimer.unref) {
56
+ this.flushTimer.unref();
57
+ }
58
+ }
59
+ }
60
+ // Default flush interval of 1s if not configured
61
+ if (!this.flushTimer) {
62
+ this.flushTimer = setInterval(() => {
63
+ void this.flush();
64
+ }, 1000);
65
+ if (this.flushTimer.unref) {
66
+ this.flushTimer.unref();
67
+ }
68
+ }
69
+ // Create log file on init so tail -f works immediately
70
+ await this.ensureDir();
71
+ try {
72
+ await appendFile(this.filePath, '', 'utf-8');
73
+ }
74
+ catch {
75
+ // Best-effort — will retry on first flush
76
+ }
77
+ }
78
+ async log(entry) {
79
+ try {
80
+ this.buffer.push(JSON.stringify(entry));
81
+ if (this.buffer.length >= this.bufferSize) {
82
+ await this.flush();
83
+ }
84
+ }
85
+ catch {
86
+ // Loggers must not throw
87
+ }
88
+ }
89
+ async flush() {
90
+ if (this.buffer.length === 0)
91
+ return;
92
+ const entries = this.buffer.splice(0);
93
+ const data = entries.map((e) => `${e}\n`).join('');
94
+ try {
95
+ await this.ensureDir();
96
+ await appendFile(this.filePath, data, 'utf-8');
97
+ // Check if rotation is needed
98
+ if (await needsRotation(this.filePath, this.rotation.maxSize)) {
99
+ await rotateFile(this.filePath, this.rotation);
100
+ }
101
+ }
102
+ catch {
103
+ // Loggers must not throw — entries are lost on write failure
104
+ }
105
+ }
106
+ async shutdown() {
107
+ if (this.flushTimer) {
108
+ clearInterval(this.flushTimer);
109
+ this.flushTimer = null;
110
+ }
111
+ await this.flush();
112
+ }
113
+ async ensureDir() {
114
+ if (this.dirEnsured)
115
+ return;
116
+ try {
117
+ await mkdir(dirname(this.filePath), { recursive: true });
118
+ this.dirEnsured = true;
119
+ }
120
+ catch {
121
+ // Directory may already exist
122
+ this.dirEnsured = true;
123
+ }
124
+ }
125
+ }
126
+ //# sourceMappingURL=file-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-logger.js","sourceRoot":"","sources":["../src/file-logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @orgloop/logger-file — registration entry point.
3
+ */
4
+ import type { LoggerRegistration } from '@orgloop/sdk';
5
+ export declare function register(): LoggerRegistration;
6
+ export { FileLogger } from './file-logger.js';
7
+ export { parseSize, rotateFile, needsRotation } from './rotation.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvD,wBAAgB,QAAQ,IAAI,kBAAkB,CA+C7C;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @orgloop/logger-file — registration entry point.
3
+ */
4
+ import { FileLogger } from './file-logger.js';
5
+ export function register() {
6
+ return {
7
+ id: 'file',
8
+ logger: FileLogger,
9
+ configSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ path: {
13
+ type: 'string',
14
+ description: 'Log file path (default: ~/.orgloop/logs/orgloop.log)',
15
+ },
16
+ format: {
17
+ type: 'string',
18
+ enum: ['jsonl'],
19
+ description: 'Log format (only "jsonl" for MVP).',
20
+ default: 'jsonl',
21
+ },
22
+ rotation: {
23
+ type: 'object',
24
+ properties: {
25
+ max_size: {
26
+ type: 'string',
27
+ description: 'Rotate when file exceeds this size (e.g., "100MB")',
28
+ },
29
+ max_age: {
30
+ type: 'string',
31
+ description: 'Delete rotated files after this duration (e.g., "7d")',
32
+ },
33
+ max_files: { type: 'number', description: 'Keep at most N rotated files', default: 10 },
34
+ compress: { type: 'boolean', description: 'Gzip rotated files', default: true },
35
+ },
36
+ },
37
+ buffer: {
38
+ type: 'object',
39
+ properties: {
40
+ size: { type: 'number', description: 'Buffer N entries before flushing', default: 100 },
41
+ flush_interval: {
42
+ type: 'string',
43
+ description: 'Flush at least every N (e.g., "1s")',
44
+ default: '1s',
45
+ },
46
+ },
47
+ },
48
+ },
49
+ additionalProperties: false,
50
+ },
51
+ };
52
+ }
53
+ export { FileLogger } from './file-logger.js';
54
+ export { parseSize, rotateFile, needsRotation } from './rotation.js';
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,UAAU,QAAQ;IACvB,OAAO;QACN,EAAE,EAAE,MAAM;QACV,MAAM,EAAE,UAAU;QAClB,YAAY,EAAE;YACb,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,IAAI,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,sDAAsD;iBACnE;gBACD,MAAM,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,OAAO,CAAC;oBACf,WAAW,EAAE,oCAAoC;oBACjD,OAAO,EAAE,OAAO;iBAChB;gBACD,QAAQ,EAAE;oBACT,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACX,QAAQ,EAAE;4BACT,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,oDAAoD;yBACjE;wBACD,OAAO,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,uDAAuD;yBACpE;wBACD,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8BAA8B,EAAE,OAAO,EAAE,EAAE,EAAE;wBACvF,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,OAAO,EAAE,IAAI,EAAE;qBAC/E;iBACD;gBACD,MAAM,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACX,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kCAAkC,EAAE,OAAO,EAAE,GAAG,EAAE;wBACvF,cAAc,EAAE;4BACf,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,qCAAqC;4BAClD,OAAO,EAAE,IAAI;yBACb;qBACD;iBACD;aACD;YACD,oBAAoB,EAAE,KAAK;SAC3B;KACD,CAAC;AACH,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * File rotation logic for the JSONL file logger.
3
+ *
4
+ * Handles: renaming current file to timestamped name, optional gzip compression,
5
+ * and cleanup of old rotated files.
6
+ */
7
+ export interface RotationConfig {
8
+ maxSize: number;
9
+ maxAge: number;
10
+ maxFiles: number;
11
+ compress: boolean;
12
+ }
13
+ /**
14
+ * Parse a size string (e.g., "100MB", "1GB") to bytes.
15
+ */
16
+ export declare function parseSize(size: string): number;
17
+ /**
18
+ * Check if a file exceeds the max size.
19
+ */
20
+ export declare function needsRotation(filePath: string, maxSize: number): Promise<boolean>;
21
+ /**
22
+ * Rotate the current log file:
23
+ * 1. Rename to timestamped name
24
+ * 2. Optionally gzip
25
+ * 3. Clean up old files
26
+ */
27
+ export declare function rotateFile(filePath: string, config: RotationConfig): Promise<void>;
28
+ //# sourceMappingURL=rotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../src/rotation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB9C;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOvF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BxF"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * File rotation logic for the JSONL file logger.
3
+ *
4
+ * Handles: renaming current file to timestamped name, optional gzip compression,
5
+ * and cleanup of old rotated files.
6
+ */
7
+ import { createReadStream, createWriteStream } from 'node:fs';
8
+ import { mkdir, readdir, rename, stat, unlink } from 'node:fs/promises';
9
+ import { dirname, join } from 'node:path';
10
+ import { pipeline } from 'node:stream/promises';
11
+ import { createGzip } from 'node:zlib';
12
+ /**
13
+ * Parse a size string (e.g., "100MB", "1GB") to bytes.
14
+ */
15
+ export function parseSize(size) {
16
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i);
17
+ if (!match) {
18
+ throw new Error(`Invalid size format: "${size}". Expected format: <number><unit> (e.g., 100MB, 1GB)`);
19
+ }
20
+ const value = Number.parseFloat(match[1]);
21
+ const unit = match[2].toUpperCase();
22
+ switch (unit) {
23
+ case 'B':
24
+ return value;
25
+ case 'KB':
26
+ return value * 1024;
27
+ case 'MB':
28
+ return value * 1024 * 1024;
29
+ case 'GB':
30
+ return value * 1024 * 1024 * 1024;
31
+ default:
32
+ throw new Error(`Unknown size unit: ${unit}`);
33
+ }
34
+ }
35
+ /**
36
+ * Check if a file exceeds the max size.
37
+ */
38
+ export async function needsRotation(filePath, maxSize) {
39
+ try {
40
+ const st = await stat(filePath);
41
+ return st.size >= maxSize;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * Rotate the current log file:
49
+ * 1. Rename to timestamped name
50
+ * 2. Optionally gzip
51
+ * 3. Clean up old files
52
+ */
53
+ export async function rotateFile(filePath, config) {
54
+ const dir = dirname(filePath);
55
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
56
+ const baseName = filePath.split('/').pop();
57
+ const rotatedName = `${baseName}.${timestamp}`;
58
+ const rotatedPath = join(dir, rotatedName);
59
+ // Rename current → timestamped
60
+ try {
61
+ await rename(filePath, rotatedPath);
62
+ }
63
+ catch {
64
+ // File may not exist or be locked; skip rotation
65
+ return;
66
+ }
67
+ // Optionally compress
68
+ if (config.compress) {
69
+ try {
70
+ const gzPath = `${rotatedPath}.gz`;
71
+ await pipeline(createReadStream(rotatedPath), createGzip(), createWriteStream(gzPath));
72
+ await unlink(rotatedPath);
73
+ }
74
+ catch {
75
+ // Compression failed; leave the uncompressed file
76
+ }
77
+ }
78
+ // Clean up old files
79
+ await cleanupOldFiles(dir, baseName, config);
80
+ }
81
+ /**
82
+ * Remove rotated files that exceed maxFiles or maxAge.
83
+ */
84
+ async function cleanupOldFiles(dir, baseName, config) {
85
+ try {
86
+ await mkdir(dir, { recursive: true });
87
+ const files = await readdir(dir);
88
+ // Find rotated files matching our base name
89
+ const rotated = [];
90
+ for (const file of files) {
91
+ if (file.startsWith(`${baseName}.`) && file !== baseName) {
92
+ try {
93
+ const st = await stat(join(dir, file));
94
+ rotated.push({ name: file, mtime: st.mtimeMs });
95
+ }
96
+ catch {
97
+ // Skip files we can't stat
98
+ }
99
+ }
100
+ }
101
+ // Sort by mtime descending (newest first)
102
+ rotated.sort((a, b) => b.mtime - a.mtime);
103
+ const now = Date.now();
104
+ for (let i = 0; i < rotated.length; i++) {
105
+ const entry = rotated[i];
106
+ const isOverLimit = i >= config.maxFiles;
107
+ const isExpired = now - entry.mtime > config.maxAge;
108
+ if (isOverLimit || isExpired) {
109
+ try {
110
+ await unlink(join(dir, entry.name));
111
+ }
112
+ catch {
113
+ // Best effort cleanup
114
+ }
115
+ }
116
+ }
117
+ }
118
+ catch {
119
+ // Directory issues — skip cleanup
120
+ }
121
+ }
122
+ //# sourceMappingURL=rotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.js","sourceRoot":"","sources":["../src/rotation.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@orgloop/logger-file",
3
+ "version": "0.1.0",
4
+ "description": "OrgLoop file logger — JSONL with rotation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "dependencies": {
9
+ "@orgloop/sdk": "0.1.0"
10
+ },
11
+ "orgloop": {
12
+ "type": "logger",
13
+ "id": "file"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "license": "MIT",
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "clean": "rm -rf dist",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "vitest run"
27
+ }
28
+ }