pi-extension-utils 0.3.3 → 0.4.1

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.
@@ -1,6 +1,6 @@
1
1
  export { connect, type FullscreenComponentFactory, type FullscreenLease, type RemindersClient, type UtilsClient, type UtilsClientOptions, type WidgetSetOptions } from "./client/index.ts";
2
2
  export { defineConfig, type DeepPartial, type DefinedConfig, type DefineConfigOptions } from "./config/index.ts";
3
- export { createLogger, LOG_LEVELS, loggerLevelSchema, loggingConfigSchema, type Logger, type LoggerLevel, type LoggerOptions, type LoggingConfig, type LogLevel } from "./logger/index.ts";
3
+ export { createLogger, LOG_LEVELS, loggerLevelSchema, loggingConfigSchema, type Logger, type LoggerFields, type LoggerLevel, type LoggerOptions, type LoggerRecord, type LoggingConfig, type LogLevel } from "./logger/index.ts";
4
4
  export { utilsConfigSchema, type UtilsConfig } from "./utils-config.ts";
5
5
  export * from "./pane/key-dispatch.ts";
6
6
  export * from "./pane/state.ts";
@@ -3,18 +3,25 @@ export { LOG_LEVELS, loggerLevelSchema, loggingConfigSchema, type LoggerLevel, t
3
3
  export interface LoggerOptions {
4
4
  dir?: string;
5
5
  maxBytes?: number;
6
- /** Number of rotated files to retain: name.log.1, name.log.2, ... */
6
+ /** Number of rotated files to retain: name.jsonl.1, name.jsonl.2, ... */
7
7
  maxFiles?: number;
8
8
  /** Minimum level to write. Defaults to utilsConfig.logging.level. */
9
9
  level?: LoggerLevel;
10
10
  }
11
+ export type LoggerFields = Record<string, unknown>;
11
12
  export interface Logger {
12
- debug(message: string): void;
13
- info(message: string): void;
14
- warn(message: string): void;
15
- error(message: string): void;
16
- log(level: LogLevel, message: string): void;
13
+ debug(message: string, fields?: LoggerFields): void;
14
+ info(message: string, fields?: LoggerFields): void;
15
+ warn(message: string, fields?: LoggerFields): void;
16
+ error(message: string, fields?: LoggerFields): void;
17
+ log(level: LogLevel, message: string, fields?: LoggerFields): void;
17
18
  setLevel(level: LoggerLevel): void;
18
19
  isEnabled(level: LogLevel): boolean;
19
20
  }
21
+ export interface LoggerRecord {
22
+ ts: string;
23
+ level: LogLevel;
24
+ message: string;
25
+ [field: string]: unknown;
26
+ }
20
27
  export declare function createLogger(name: string, opts?: LoggerOptions): Logger;
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
4
  import { readUtilsConfigOrDefaults } from "../utils-config.js";
5
5
  export { LOG_LEVELS, loggerLevelSchema, loggingConfigSchema } from "./config.js";
6
+ const RESERVED_RECORD_FIELDS = new Set(["ts", "level", "message"]);
6
7
  const LEVEL_WEIGHT = {
7
8
  debug: 10,
8
9
  info: 20,
@@ -21,7 +22,7 @@ export function createLogger(name, opts = {}) {
21
22
  const maxBytes = Math.max(0, Math.floor(opts.maxBytes ?? configured?.maxBytes ?? 1024 * 1024));
22
23
  const maxFiles = Math.max(0, Math.floor(opts.maxFiles ?? configured?.maxFiles ?? 3));
23
24
  let level = opts.level ?? configured?.level ?? "info";
24
- const file = join(dir, `${name}.log`);
25
+ const file = join(dir, `${name}.jsonl`);
25
26
  mkdirSync(dirname(file), { recursive: true });
26
27
  function rotateIfNeeded(nextBytes) {
27
28
  if (maxBytes <= 0 || !existsSync(file))
@@ -46,26 +47,31 @@ export function createLogger(name, opts = {}) {
46
47
  function isEnabled(candidate) {
47
48
  return LEVEL_WEIGHT[candidate] >= LEVEL_WEIGHT[level];
48
49
  }
49
- function log(candidate, message) {
50
+ function log(candidate, message, fields = {}) {
50
51
  if (!isEnabled(candidate))
51
52
  return;
52
- const line = `${new Date().toISOString()} ${candidate} ${message}\n`;
53
+ const record = { ts: new Date().toISOString(), level: candidate, message };
54
+ for (const [key, value] of Object.entries(fields)) {
55
+ if (!RESERVED_RECORD_FIELDS.has(key))
56
+ record[key] = value;
57
+ }
58
+ const line = `${JSON.stringify(record)}\n`;
53
59
  mkdirSync(dirname(file), { recursive: true });
54
60
  rotateIfNeeded(Buffer.byteLength(line));
55
61
  appendFileSync(file, line);
56
62
  }
57
63
  return {
58
- debug(message) {
59
- log("debug", message);
64
+ debug(message, fields) {
65
+ log("debug", message, fields);
60
66
  },
61
- info(message) {
62
- log("info", message);
67
+ info(message, fields) {
68
+ log("info", message, fields);
63
69
  },
64
- warn(message) {
65
- log("warn", message);
70
+ warn(message, fields) {
71
+ log("warn", message, fields);
66
72
  },
67
- error(message) {
68
- log("error", message);
73
+ error(message, fields) {
74
+ log("error", message, fields);
69
75
  },
70
76
  log,
71
77
  setLevel(nextLevel) {
package/docs/client.md CHANGED
@@ -77,12 +77,12 @@ const log = createLogger("my-extension", {
77
77
  });
78
78
 
79
79
  log.debug("hidden at info level");
80
- log.info("started");
81
- log.warn("slow path");
80
+ log.info("started", { command: "example" });
81
+ log.warn("slow path", { durationMs: 1200 });
82
82
  log.error("failed");
83
83
  ```
84
84
 
85
- `createLogger(name)` writes to `getAgentDir()/log/<name>.log` by default. `level` is typed as `"debug" | "info" | "warn" | "error" | "silent"`; `maxFiles` controls retained rotations (`.1`, `.2`, ...). If `level`, `maxFiles`, or `maxBytes` are omitted, the value comes from `getAgentDir()/config/utils.jsonc` `logging` defaults. Explicit options always win.
85
+ `createLogger(name)` writes JSONL to `getAgentDir()/log/<name>.jsonl` by default. Each line is a flat object like `{"ts":"...","level":"info","message":"started","command":"example"}`. `ts`, `level`, and `message` are logger-owned fields and caller fields with those names are ignored. `level` is typed as `"debug" | "info" | "warn" | "error" | "silent"`; `maxFiles` controls retained rotations (`.1`, `.2`, ...). If `level`, `maxFiles`, or `maxBytes` are omitted, the value comes from `getAgentDir()/config/utils.jsonc` `logging` defaults. Explicit options always win.
86
86
 
87
87
  Override when needed:
88
88
 
@@ -8,7 +8,7 @@ const log = createLogger("example-extension", {
8
8
 
9
9
  export default function (pi: ExtensionAPI) {
10
10
  pi.on("session_start", (_event, ctx) => {
11
- log.info(`session started cwd=${ctx.cwd}`);
11
+ log.info("session started", { cwd: ctx.cwd });
12
12
  });
13
13
 
14
14
  pi.registerCommand("example-log", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extension-utils",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "Shared Pi extension utilities for coordinated widgets, fullscreen leases, and logging.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",