opencode-logger 0.4.5 → 0.5.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/README.md CHANGED
@@ -20,7 +20,7 @@ and add:
20
20
  ```jsonc
21
21
  {
22
22
  "$schema": "https://opencode.ai/config.json",
23
- "plugin": ["opencode-logger"]
23
+ "plugin": ["opencode-logger"],
24
24
  }
25
25
  ```
26
26
 
@@ -28,13 +28,13 @@ and add:
28
28
 
29
29
  You can customize the log directory, filename, and logging scope using environment variables.
30
30
 
31
- | Variable | Description | Default |
32
- |----------|-------------|---------|
33
- | `OPENCODE_LOGGER_DIR` | The directory where logs are stored. Can be absolute or relative to project root. | `logs/opencode` |
34
- | `OPENCODE_LOGGER_FILENAME` | The filename for the log file. | `log.jsonl` |
35
- | `OPENCODE_LOGGER_SCOPE` | Comma-separated list of event types to log. Supports wildcards (e.g., `session.*`). | `*` (Log all events) |
36
-
37
-
31
+ | Variable | Description | Default |
32
+ | ------------------------------- | ----------------------------------------------------------------------------------------- | -------------------- |
33
+ | `OPENCODE_LOGGER_DIR` | The directory where logs are stored. Can be absolute or relative to project root. | `logs/opencode` |
34
+ | `OPENCODE_LOGGER_FILENAME` | The filename for the log file. | `log.jsonl` |
35
+ | `OPENCODE_LOGGER_SCOPE` | Comma-separated list of event types to log. Supports wildcards (e.g., `session.*`). | `*` (Log all events) |
36
+ | `OPENCODE_LOGGER_MAX_FILE_SIZE` | Maximum log file size in bytes before automatic rotation. Set to `0` to disable rotation. | `104857600` (100 MB) |
37
+ | `OPENCODE_LOGGER_MAX_FILES` | Maximum number of rotated log files to keep. Set to `0` to keep all. | `0` (unlimited) |
38
38
 
39
39
  ### Setting via CLI
40
40
 
@@ -68,6 +68,17 @@ It accepts a comma-separated string of event types or patterns.
68
68
  export OPENCODE_LOGGER_SCOPE="session.*,command.executed"
69
69
  ```
70
70
 
71
+ ### Log Rotation
72
+
73
+ You can control when log files are rotated and how many archived files are retained.
71
74
 
75
+ - `OPENCODE_LOGGER_MAX_FILE_SIZE`: Once the active log file reaches this size (in bytes), it is rotated. Set to `0` to disable rotation entirely.
76
+ - `OPENCODE_LOGGER_MAX_FILES`: After rotation, only this many archived files are kept (oldest deleted first). Set to `0` to keep all rotated files indefinitely.
72
77
 
78
+ **Example:**
73
79
 
80
+ ```bash
81
+ # Rotate at 10 MB, keep last 5 archived files
82
+ export OPENCODE_LOGGER_MAX_FILE_SIZE=10485760
83
+ export OPENCODE_LOGGER_MAX_FILES=5
84
+ ```
@@ -6,6 +6,17 @@ export declare const DEFAULT_LOG_DIRECTORY = "logs/opencode";
6
6
  * Filename for the log file.
7
7
  */
8
8
  export declare const DEFAULT_LOG_FILENAME = "log.jsonl";
9
+ /**
10
+ * Default maximum log file size in bytes before rotation occurs (100 MB).
11
+ * Set OPENCODE_LOGGER_MAX_FILE_SIZE to override. Set to 0 to disable rotation.
12
+ */
13
+ export declare const DEFAULT_MAX_FILE_SIZE = 104857600;
14
+ /**
15
+ * Default maximum number of rotated log files to retain.
16
+ * 0 means unlimited — all rotated files are kept.
17
+ * Set OPENCODE_LOGGER_MAX_FILES to override.
18
+ */
19
+ export declare const DEFAULT_MAX_FILES = 0;
9
20
  /**
10
21
  * List of event types supported by the logger plugin.
11
22
  * These events correspond to various lifecycle hooks and actions within the Opencode environment.
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD;;GAEG;AACH,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,ulBA2CnB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD;;GAEG;AACH,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,ulBA2CnB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC"}
package/dist/constants.js CHANGED
@@ -6,6 +6,17 @@ export const DEFAULT_LOG_DIRECTORY = "logs/opencode";
6
6
  * Filename for the log file.
7
7
  */
8
8
  export const DEFAULT_LOG_FILENAME = "log.jsonl";
9
+ /**
10
+ * Default maximum log file size in bytes before rotation occurs (100 MB).
11
+ * Set OPENCODE_LOGGER_MAX_FILE_SIZE to override. Set to 0 to disable rotation.
12
+ */
13
+ export const DEFAULT_MAX_FILE_SIZE = 104857600;
14
+ /**
15
+ * Default maximum number of rotated log files to retain.
16
+ * 0 means unlimited — all rotated files are kept.
17
+ * Set OPENCODE_LOGGER_MAX_FILES to override.
18
+ */
19
+ export const DEFAULT_MAX_FILES = 0;
9
20
  /**
10
21
  * List of event types supported by the logger plugin.
11
22
  * These events correspond to various lifecycle hooks and actions within the Opencode environment.
@@ -12,9 +12,14 @@ export interface LogEntry {
12
12
  }
13
13
  /**
14
14
  * Handles initialization of the log directory and writing log entries to the file system.
15
+ * Supports automatic log file rotation when the active file exceeds a configurable size.
15
16
  */
16
17
  export declare class FileLogger {
18
+ private logDir;
19
+ private baseFilename;
17
20
  private logFilePath;
21
+ private maxFileSize;
22
+ private maxFiles;
18
23
  private pluginInput;
19
24
  /**
20
25
  * Creates a new instance of FileLogger.
@@ -24,6 +29,7 @@ export declare class FileLogger {
24
29
  constructor(projectRoot: string, pluginInput: PluginInput);
25
30
  private resolveLogDirectory;
26
31
  private getLogFilename;
32
+ private parseEnvInt;
27
33
  /**
28
34
  * Initializes the logger by ensuring the log directory exists.
29
35
  * This method must be called before attempting to log any events.
@@ -31,9 +37,24 @@ export declare class FileLogger {
31
37
  init(): Promise<void>;
32
38
  /**
33
39
  * Logs an event and its payload to the log file in JSONL format.
40
+ * Automatically rotates the file if the configured maximum size has been reached.
34
41
  * @param eventType - The type of event to log.
35
42
  * @param payload - The data associated with the event.
36
43
  */
37
44
  log(eventType: string, payload: unknown): Promise<void>;
45
+ /**
46
+ * Checks whether the active log file has exceeded the maximum size and, if so, rotates it.
47
+ * If maxFileSize is 0, rotation is disabled.
48
+ */
49
+ private checkAndRotate;
50
+ /**
51
+ * Renames the active log file to a timestamped archive name and resets the active path.
52
+ * Prunes oldest rotated files if maxFiles is configured.
53
+ */
54
+ private rotate;
55
+ /**
56
+ * Deletes the oldest rotated log files when the number of archived files exceeds maxFiles.
57
+ */
58
+ private pruneOldFiles;
38
59
  }
39
60
  //# sourceMappingURL=file-logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-logger.d.ts","sourceRoot":"","sources":["../src/file-logger.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAc;IAEjC;;;;OAIG;gBACS,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW;IAQzD,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,cAAc;IAItB;;;OAGG;IACG,IAAI;IASV;;;;OAIG;IACG,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;CAwB7C"}
1
+ {"version":3,"file":"file-logger.d.ts","sourceRoot":"","sources":["../src/file-logger.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAQvD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAc;IAEjC;;;;OAIG;gBACS,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW;IAezD,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,WAAW;IAOnB;;;OAGG;IACG,IAAI;IAQV;;;;;OAKG;IACG,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IA0B7C;;;OAGG;YACW,cAAc;IAiB5B;;;OAGG;YACW,MAAM;IA+BpB;;OAEG;YACW,aAAa;CA+B3B"}
@@ -1,11 +1,16 @@
1
- import { appendFile, mkdir } from "node:fs/promises";
2
- import { isAbsolute, join, resolve } from "node:path";
3
- import { DEFAULT_LOG_DIRECTORY, DEFAULT_LOG_FILENAME } from "./constants.js";
1
+ import { appendFile, mkdir, readdir, rename, stat, unlink, } from "node:fs/promises";
2
+ import { basename, extname, isAbsolute, join, resolve } from "node:path";
3
+ import { DEFAULT_LOG_DIRECTORY, DEFAULT_LOG_FILENAME, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_FILES, } from "./constants.js";
4
4
  /**
5
5
  * Handles initialization of the log directory and writing log entries to the file system.
6
+ * Supports automatic log file rotation when the active file exceeds a configurable size.
6
7
  */
7
8
  export class FileLogger {
9
+ logDir;
10
+ baseFilename;
8
11
  logFilePath;
12
+ maxFileSize;
13
+ maxFiles;
9
14
  pluginInput;
10
15
  /**
11
16
  * Creates a new instance of FileLogger.
@@ -13,10 +18,12 @@ export class FileLogger {
13
18
  * @param pluginInput - Opencode plugin input
14
19
  */
15
20
  constructor(projectRoot, pluginInput) {
16
- const logDir = this.resolveLogDirectory(projectRoot);
17
- const logFilename = this.getLogFilename();
18
- this.logFilePath = join(logDir, logFilename);
21
+ this.logDir = this.resolveLogDirectory(projectRoot);
22
+ this.baseFilename = this.getLogFilename();
23
+ this.logFilePath = join(this.logDir, this.baseFilename);
19
24
  this.pluginInput = pluginInput;
25
+ this.maxFileSize = this.parseEnvInt("OPENCODE_LOGGER_MAX_FILE_SIZE", DEFAULT_MAX_FILE_SIZE);
26
+ this.maxFiles = this.parseEnvInt("OPENCODE_LOGGER_MAX_FILES", DEFAULT_MAX_FILES);
20
27
  }
21
28
  resolveLogDirectory(projectRoot) {
22
29
  const logDir = process.env["OPENCODE_LOGGER_DIR"] ||
@@ -29,14 +36,20 @@ export class FileLogger {
29
36
  getLogFilename() {
30
37
  return process.env["OPENCODE_LOGGER_FILENAME"] || DEFAULT_LOG_FILENAME;
31
38
  }
39
+ parseEnvInt(varName, defaultValue) {
40
+ const raw = process.env[varName];
41
+ if (raw === undefined || raw === "")
42
+ return defaultValue;
43
+ const parsed = Number.parseInt(raw, 10);
44
+ return Number.isNaN(parsed) ? defaultValue : parsed;
45
+ }
32
46
  /**
33
47
  * Initializes the logger by ensuring the log directory exists.
34
48
  * This method must be called before attempting to log any events.
35
49
  */
36
50
  async init() {
37
51
  try {
38
- const dirPath = join(this.logFilePath, "..");
39
- await mkdir(dirPath, { recursive: true });
52
+ await mkdir(this.logDir, { recursive: true });
40
53
  }
41
54
  catch (error) {
42
55
  console.error(`[Opencode-logger]: Failed to initialize.\n${error}`);
@@ -44,6 +57,7 @@ export class FileLogger {
44
57
  }
45
58
  /**
46
59
  * Logs an event and its payload to the log file in JSONL format.
60
+ * Automatically rotates the file if the configured maximum size has been reached.
47
61
  * @param eventType - The type of event to log.
48
62
  * @param payload - The data associated with the event.
49
63
  */
@@ -55,6 +69,7 @@ export class FileLogger {
55
69
  };
56
70
  const line = `${JSON.stringify(entry)}\n`;
57
71
  try {
72
+ await this.checkAndRotate();
58
73
  await appendFile(this.logFilePath, line, "utf-8");
59
74
  }
60
75
  catch (error) {
@@ -70,4 +85,91 @@ export class FileLogger {
70
85
  });
71
86
  }
72
87
  }
88
+ /**
89
+ * Checks whether the active log file has exceeded the maximum size and, if so, rotates it.
90
+ * If maxFileSize is 0, rotation is disabled.
91
+ */
92
+ async checkAndRotate() {
93
+ if (this.maxFileSize === 0)
94
+ return;
95
+ let fileSize;
96
+ try {
97
+ const fileStat = await stat(this.logFilePath);
98
+ fileSize = fileStat.size;
99
+ }
100
+ catch {
101
+ // File does not exist yet — no rotation needed
102
+ return;
103
+ }
104
+ if (fileSize >= this.maxFileSize) {
105
+ await this.rotate();
106
+ }
107
+ }
108
+ /**
109
+ * Renames the active log file to a timestamped archive name and resets the active path.
110
+ * Prunes oldest rotated files if maxFiles is configured.
111
+ */
112
+ async rotate() {
113
+ const ext = extname(this.baseFilename);
114
+ const base = basename(this.baseFilename, ext);
115
+ const timestamp = new Date()
116
+ .toISOString()
117
+ .replace(/:/g, "-")
118
+ .replace(/\..+$/, "");
119
+ const shortId = crypto.randomUUID().slice(0, 8);
120
+ const rotatedFilename = `${base}.${timestamp}-${shortId}${ext}`;
121
+ const rotatedFilePath = join(this.logDir, rotatedFilename);
122
+ try {
123
+ await rename(this.logFilePath, rotatedFilePath);
124
+ }
125
+ catch (error) {
126
+ await this.pluginInput.client.app.log({
127
+ body: {
128
+ service: "opencode-logger",
129
+ level: "error",
130
+ message: "Failed to rotate log file.",
131
+ extra: { error },
132
+ },
133
+ });
134
+ return;
135
+ }
136
+ // logFilePath stays pointing to the base filename — next appendFile will create a fresh file
137
+ if (this.maxFiles > 0) {
138
+ await this.pruneOldFiles();
139
+ }
140
+ }
141
+ /**
142
+ * Deletes the oldest rotated log files when the number of archived files exceeds maxFiles.
143
+ */
144
+ async pruneOldFiles() {
145
+ const ext = extname(this.baseFilename);
146
+ const base = basename(this.baseFilename, ext);
147
+ // Rotated files match: <base>.<timestamp>-<shortId><ext>
148
+ const rotationPattern = new RegExp(`^${escapeRegExp(base)}\\..+${escapeRegExp(ext)}$`);
149
+ let entries;
150
+ try {
151
+ entries = await readdir(this.logDir);
152
+ }
153
+ catch {
154
+ return;
155
+ }
156
+ const rotatedFiles = entries
157
+ .filter((name) => rotationPattern.test(name))
158
+ .sort(); // ISO timestamp prefix sorts lexicographically = chronologically
159
+ const excess = rotatedFiles.length - this.maxFiles;
160
+ if (excess <= 0)
161
+ return;
162
+ const toDelete = rotatedFiles.slice(0, excess);
163
+ for (const filename of toDelete) {
164
+ try {
165
+ await unlink(join(this.logDir, filename));
166
+ }
167
+ catch {
168
+ // Best-effort: ignore individual deletion failures
169
+ }
170
+ }
171
+ }
172
+ }
173
+ function escapeRegExp(str) {
174
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
73
175
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAKzD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,MAkB1B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,MAe1B,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import { SUPPORTED_EVENTS } from "./constants.js";
2
1
  import { FileLogger } from "./file-logger.js";
3
2
  import { shouldLogEvent } from "./utils.js";
4
3
  /**
@@ -14,8 +13,7 @@ export const loggerPlugin = async (ctx) => {
14
13
  console.log("[Opencode Logger] Plugin initialized!");
15
14
  const hooks = {
16
15
  event: async ({ event }) => {
17
- if (SUPPORTED_EVENTS.includes(event.type) &&
18
- shouldLogEvent(event.type, process.env["OPENCODE_LOGGER_SCOPE"])) {
16
+ if (shouldLogEvent(event.type, process.env["OPENCODE_LOGGER_SCOPE"])) {
19
17
  await logger.log(event.type, event);
20
18
  }
21
19
  },
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "opencode-logger",
3
3
  "main": "dist/index.js",
4
4
  "types": "dist/index.d.ts",
5
- "version": "0.4.5",
5
+ "version": "0.5.0",
6
6
  "repository": {
7
7
  "url": "git+https://github.com/radekBednarik/opencode-logger.git"
8
8
  },
@@ -33,6 +33,7 @@
33
33
  },
34
34
  "private": false,
35
35
  "scripts": {
36
+ "test": "bun test",
36
37
  "build-dist": "bunx tsc",
37
38
  "lint": "bunx biome lint --write ./src",
38
39
  "format": "bunx biome format --write",
@@ -42,7 +43,7 @@
42
43
  },
43
44
  "type": "module",
44
45
  "lint-staged": {
45
- "*.{js, ts}": [
46
+ "*.{js,ts}": [
46
47
  "bun forlint"
47
48
  ],
48
49
  "*.{ts,json}": [