From 9692c17aaae910d7d40665bac1547775e92de680 Mon Sep 17 00:00:00 2001 From: shabaraba Date: Tue, 13 Jan 2026 10:08:45 +0900 Subject: [PATCH 1/2] chore: merge main --- packages/create-plugin/package.json | 1 + packages/create-plugin/src/logger.ts | 14 +- packages/create-plugin/tsconfig.json | 5 +- packages/dts-gen/package.json | 1 + packages/dts-gen/src/utils/logger.ts | 4 +- packages/dts-gen/tsconfig.json | 5 +- packages/logger/.gitignore | 5 + packages/logger/__tests__/logger.test.ts | 117 +++++++++++ packages/logger/package.json | 51 +++++ packages/logger/src/index.ts | 9 + packages/logger/src/logger.ts | 108 ++++++++++ packages/logger/src/types.ts | 35 +++ packages/logger/tsconfig.json | 12 ++ packages/logger/vitest.config.ts | 5 + packages/plugin-packer/package.json | 1 + packages/plugin-packer/src/console.ts | 11 +- packages/plugin-packer/tsconfig.json | 1 + packages/tsconfig.json | 1 + pnpm-lock.yaml | 257 ++++++++++++++++++++++- 19 files changed, 625 insertions(+), 18 deletions(-) create mode 100644 packages/logger/.gitignore create mode 100644 packages/logger/__tests__/logger.test.ts create mode 100644 packages/logger/package.json create mode 100644 packages/logger/src/index.ts create mode 100644 packages/logger/src/logger.ts create mode 100644 packages/logger/src/types.ts create mode 100644 packages/logger/tsconfig.json create mode 100644 packages/logger/vitest.config.ts diff --git a/packages/create-plugin/package.json b/packages/create-plugin/package.json index 9f1c8242ce..14a97b6d47 100644 --- a/packages/create-plugin/package.json +++ b/packages/create-plugin/package.json @@ -40,6 +40,7 @@ "test:generator": "cross-env NODE_ENV=e2e vitest run --config=vitest.generator.config.ts" }, "dependencies": { + "@kintone/logger": "workspace:^", "@inquirer/prompts": "^5.5.0", "chalk": "^4.1.2", "glob": "^10.4.5", diff --git a/packages/create-plugin/src/logger.ts b/packages/create-plugin/src/logger.ts index 36824a9f9e..bb464297bd 100644 --- a/packages/create-plugin/src/logger.ts +++ b/packages/create-plugin/src/logger.ts @@ -1,17 +1,9 @@ -"use strict"; +import { logger } from "@kintone/logger"; -/** - * Print logs - * @param texts - */ export const printLog = (...texts: string[]) => { - texts.forEach((t) => console.log(t)); + texts.forEach((t) => logger.info(t)); }; -/** - * Print errors - * @param errors - */ export const printError = (...errors: string[]) => { - errors.forEach((e) => console.error(e)); + errors.forEach((e) => logger.error(e)); }; diff --git a/packages/create-plugin/tsconfig.json b/packages/create-plugin/tsconfig.json index 4cd4533af2..980ef5dde1 100644 --- a/packages/create-plugin/tsconfig.json +++ b/packages/create-plugin/tsconfig.json @@ -10,5 +10,8 @@ ] }, "include": ["src/**/*.ts"], - "exclude": ["**/__tests__/**", "**/*.test.ts", "**/*.spec.ts", "vitest.config.ts"] + "exclude": ["**/__tests__/**", "**/*.test.ts", "**/*.spec.ts", "vitest.config.ts"], + "references": [ + { "path": "../logger" } + ] } diff --git a/packages/dts-gen/package.json b/packages/dts-gen/package.json index 45bf8a6334..8e6f97ba97 100644 --- a/packages/dts-gen/package.json +++ b/packages/dts-gen/package.json @@ -44,6 +44,7 @@ "test:ci": "cross-env NODE_OPTIONS=--experimental-vm-modules vitest run" }, "dependencies": { + "@kintone/logger": "workspace:^", "@cybozu/eslint-config": "^24.3.0", "axios": "^1.12.2", "commander": "^12.1.0", diff --git a/packages/dts-gen/src/utils/logger.ts b/packages/dts-gen/src/utils/logger.ts index 77168cd787..53f17114c9 100644 --- a/packages/dts-gen/src/utils/logger.ts +++ b/packages/dts-gen/src/utils/logger.ts @@ -1,3 +1,5 @@ +import { logger } from "@kintone/logger"; + export const log = (message: string) => { - console.log(message); + logger.info(message); }; diff --git a/packages/dts-gen/tsconfig.json b/packages/dts-gen/tsconfig.json index bad6c6390b..ad6b4fa0ea 100644 --- a/packages/dts-gen/tsconfig.json +++ b/packages/dts-gen/tsconfig.json @@ -14,5 +14,8 @@ "outDir": "./dist", "rootDir": "./src", "strict": false // TODO enable strict mode - } + }, + "references": [ + { "path": "../logger" } + ] } diff --git a/packages/logger/.gitignore b/packages/logger/.gitignore new file mode 100644 index 0000000000..82da6f72a5 --- /dev/null +++ b/packages/logger/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +lib/ +vitest.config.js +vitest.config.d.ts +vitest.config.js.map diff --git a/packages/logger/__tests__/logger.test.ts b/packages/logger/__tests__/logger.test.ts new file mode 100644 index 0000000000..38ed9f1c51 --- /dev/null +++ b/packages/logger/__tests__/logger.test.ts @@ -0,0 +1,117 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import type { LogConfigLevel, LogEventLevel, Printer } from "../src/types"; +import { StandardLogger } from "../src/logger"; + +describe("StandardLogger", () => { + const mockDate = new Date(0); + let dateSpy: ReturnType; + + beforeEach(() => { + dateSpy = vi.spyOn(global, "Date").mockImplementation(() => mockDate); + }); + + afterEach(() => { + dateSpy.mockRestore(); + }); + + const patternTest: [string, LogEventLevel][] = [ + ["TRACE", "trace"], + ["DEBUG", "debug"], + ["INFO", "info"], + ["WARN", "warn"], + ["ERROR", "error"], + ["FATAL", "fatal"], + ]; + + it.each(patternTest)( + 'should display %s message when calling logger with "%s" level', + (logDisplay, logLevel) => { + const message = "This is example message"; + const mockPrinter = vi.fn(); + + const options: { logConfigLevel: LogConfigLevel; printer: Printer } = { + logConfigLevel: logLevel, + printer: mockPrinter, + }; + const standardLogger = new StandardLogger(options); + standardLogger[logLevel](message); + + const expectedMessage = new RegExp( + `\\[${mockDate.toISOString()}] (.*)${logDisplay}(.*): ${message}`, + ); + + expect(mockPrinter).toHaveBeenCalledWith( + expect.stringMatching(expectedMessage), + ); + }, + ); + + it("should not display any message when calling logger with 'none' level", () => { + const message = "This is example message"; + const mockPrinter = vi.fn(); + + const options: { logConfigLevel: LogConfigLevel; printer: Printer } = { + logConfigLevel: "none", + printer: mockPrinter, + }; + const standardLogger = new StandardLogger(options); + standardLogger.info(message); + + expect(mockPrinter).not.toHaveBeenCalled(); + }); + + it("should display the correct log message with multiple lines message", () => { + const firstLineMessage = "This is first line message"; + const secondLineMessage = "This is second line message"; + const mockPrinter = vi.fn(); + + const options: { logConfigLevel: LogConfigLevel; printer: Printer } = { + logConfigLevel: "info", + printer: mockPrinter, + }; + const standardLogger = new StandardLogger(options); + standardLogger.info(`${firstLineMessage}\n${secondLineMessage}`); + + const expectedMessage = new RegExp( + `\\[${mockDate.toISOString()}] (.*)INFO(.*): ${firstLineMessage}\n\\[${mockDate.toISOString()}] (.*)INFO(.*): ${secondLineMessage}`, + ); + + expect(mockPrinter).toHaveBeenCalledWith( + expect.stringMatching(expectedMessage), + ); + }); + + it("should display the correct log message corresponding to the log config level", () => { + const message = "This is example message"; + const mockPrinter = vi.fn(); + + const options: { logConfigLevel?: LogConfigLevel; printer: Printer } = { + printer: mockPrinter, + }; + const standardLogger = new StandardLogger(options); + standardLogger.setLogConfigLevel("warn"); + standardLogger.trace(message); + standardLogger.debug(message); + standardLogger.info(message); + standardLogger.warn(message); + standardLogger.error(message); + standardLogger.fatal(message); + + expect(mockPrinter).toHaveBeenCalledTimes(3); + }); + + it("should format Error objects correctly", () => { + const mockPrinter = vi.fn(); + const options: { logConfigLevel: LogConfigLevel; printer: Printer } = { + logConfigLevel: "error", + printer: mockPrinter, + }; + const standardLogger = new StandardLogger(options); + const error = new Error("Test error message"); + standardLogger.error(error); + + expect(mockPrinter).toHaveBeenCalledWith( + expect.stringContaining("Error: Test error message"), + ); + }); +}); diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 0000000000..d60941ec30 --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,51 @@ +{ + "name": "@kintone/logger", + "version": "0.1.0", + "private": true, + "description": "Logger utility for kintone CLI tools", + "homepage": "https://github.com/kintone/js-sdk/tree/main/packages/logger#readme", + "bugs": { + "url": "https://github.com/kintone/js-sdk/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/kintone/js-sdk.git", + "directory": "packages/logger" + }, + "license": "MIT", + "author": { + "name": "Cybozu, Inc.", + "url": "https://cybozu.co.jp" + }, + "main": "lib/index.js", + "types": "lib/index.d.ts", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib", + "!**/*.tsbuildinfo" + ], + "scripts": { + "prebuild": "pnpm clean", + "build": "tsc --build --force", + "clean": "rimraf lib", + "fix": "pnpm lint --fix", + "lint": "eslint 'src/**/*.ts' --max-warnings 0", + "start": "pnpm build --watch", + "test": "vitest run", + "test:ci": "vitest run" + }, + "dependencies": {}, + "devDependencies": { + "rimraf": "^6.0.1", + "vitest": "^3.0.0" + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts new file mode 100644 index 0000000000..99e0ce0ce1 --- /dev/null +++ b/packages/logger/src/index.ts @@ -0,0 +1,9 @@ +export { StandardLogger, logger } from "./logger"; +export { + LOG_CONFIG_LEVELS, + type Logger, + type LogConfigLevel, + type LogEvent, + type LogEventLevel, + type Printer, +} from "./types"; diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts new file mode 100644 index 0000000000..a83d937722 --- /dev/null +++ b/packages/logger/src/logger.ts @@ -0,0 +1,108 @@ +import type { + Logger, + LogConfigLevel, + LogEvent, + LogEventLevel, + Printer, +} from "./types"; + +export class StandardLogger implements Logger { + private readonly printer: Printer = console.error; + private logConfigLevel: LogConfigLevel = "info"; + + constructor(options?: { + logConfigLevel?: LogConfigLevel; + printer?: Printer; + }) { + if (options?.printer) { + this.printer = options.printer; + } + if (options?.logConfigLevel) { + this.logConfigLevel = options.logConfigLevel; + } + } + + trace(message: unknown): void { + this.log({ level: "trace", message }); + } + + debug(message: unknown): void { + this.log({ level: "debug", message }); + } + + info(message: unknown): void { + this.log({ level: "info", message }); + } + + warn(message: unknown): void { + this.log({ level: "warn", message }); + } + + error(message: unknown): void { + this.log({ level: "error", message }); + } + + fatal(message: unknown): void { + this.log({ level: "fatal", message }); + } + + setLogConfigLevel(logConfigLevel: LogConfigLevel): void { + this.logConfigLevel = logConfigLevel; + } + + private log(event: LogEvent): void { + if (!this.isPrintable(event)) { + return; + } + const formattedMessage = this.format(event); + this.print(formattedMessage); + } + + private isPrintable(event: LogEvent): boolean { + const logConfigLevelMatrix: { + [configLevel in LogConfigLevel]: LogEventLevel[]; + } = { + trace: ["trace", "debug", "info", "warn", "error", "fatal"], + debug: ["debug", "info", "warn", "error", "fatal"], + info: ["info", "warn", "error", "fatal"], + warn: ["warn", "error", "fatal"], + error: ["error", "fatal"], + fatal: ["fatal"], + none: [], + }; + return logConfigLevelMatrix[this.logConfigLevel].includes(event.level); + } + + private format(event: LogEvent): string { + const timestamp = new Date().toISOString(); + const eventLevelLabels: { [level in LogEventLevel]: string } = { + trace: "TRACE", + debug: "DEBUG", + info: "INFO", + warn: "WARN", + error: "ERROR", + fatal: "FATAL", + }; + const stringifiedMessage = stringifyMessage(event.message); + const prefix = `[${timestamp}] ${eventLevelLabels[event.level]}:`; + + return stringifiedMessage + .split("\n") + .filter((line) => line.length > 0) + .map((line) => `${prefix} ${line}`) + .join("\n"); + } + + private print(message: string): void { + this.printer(message); + } +} + +export const logger = new StandardLogger(); + +const stringifyMessage = (message: unknown): string => { + if (message instanceof Error) { + return message.toString(); + } + return String(message); +}; diff --git a/packages/logger/src/types.ts b/packages/logger/src/types.ts new file mode 100644 index 0000000000..d4441799b1 --- /dev/null +++ b/packages/logger/src/types.ts @@ -0,0 +1,35 @@ +export interface Logger { + trace: (message: unknown) => void; + debug: (message: unknown) => void; + info: (message: unknown) => void; + warn: (message: unknown) => void; + error: (message: unknown) => void; + fatal: (message: unknown) => void; +} + +export const LOG_CONFIG_LEVELS = [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "none", +] as const; + +export type LogConfigLevel = (typeof LOG_CONFIG_LEVELS)[number]; + +export type LogEventLevel = + | "trace" + | "debug" + | "info" + | "warn" + | "error" + | "fatal"; + +export type LogEvent = { + level: LogEventLevel; + message: unknown; +}; + +export type Printer = (data: unknown) => void; diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 0000000000..816955c013 --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src", + "declaration": true, + "sourceMap": true, + "composite": true + }, + "exclude": ["__tests__/**", "vitest.config.ts", "lib/**"], + "references": [] +} diff --git a/packages/logger/vitest.config.ts b/packages/logger/vitest.config.ts new file mode 100644 index 0000000000..77a73cf2ed --- /dev/null +++ b/packages/logger/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: {}, +}); diff --git a/packages/plugin-packer/package.json b/packages/plugin-packer/package.json index 5be14d1e93..38bad5e263 100644 --- a/packages/plugin-packer/package.json +++ b/packages/plugin-packer/package.json @@ -55,6 +55,7 @@ "test:ci": "pnpm test" }, "dependencies": { + "@kintone/logger": "workspace:^", "@kintone/plugin-manifest-validator": "^11.0.1", "chokidar": "^3.6.0", "debug": "^4.4.3", diff --git a/packages/plugin-packer/src/console.ts b/packages/plugin-packer/src/console.ts index 83978d7b57..1a4489fc3c 100644 --- a/packages/plugin-packer/src/console.ts +++ b/packages/plugin-packer/src/console.ts @@ -1,5 +1,10 @@ +import { logger } from "@kintone/logger"; + +const formatArgs = (...args: unknown[]): string => + args.map((arg) => String(arg)).join(" "); + export = { - log: console.log, - error: console.error, - warn: console.warn, + log: (...args: unknown[]) => logger.info(formatArgs(...args)), + error: (...args: unknown[]) => logger.error(formatArgs(...args)), + warn: (...args: unknown[]) => logger.warn(formatArgs(...args)), }; diff --git a/packages/plugin-packer/tsconfig.json b/packages/plugin-packer/tsconfig.json index 775d728f08..2eaf890c26 100644 --- a/packages/plugin-packer/tsconfig.json +++ b/packages/plugin-packer/tsconfig.json @@ -7,6 +7,7 @@ }, "exclude": ["site/**", "test/**", "dist/**", "vitest.config.ts"], "references": [ + { "path": "../logger" }, { "path": "../plugin-manifest-validator" } ] } diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 646283cc89..95f8625ac2 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -2,6 +2,7 @@ "files": [], "include": [], "references": [ + { "path": "./logger"}, { "path": "./create-plugin"}, { "path": "./customize-uploader"}, { "path": "./dts-gen"}, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00fe05c373..d264093006 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,9 @@ importers: '@inquirer/prompts': specifier: ^5.5.0 version: 5.5.0 + '@kintone/logger': + specifier: workspace:^ + version: link:../logger chalk: specifier: ^4.1.2 version: 4.1.2 @@ -174,6 +177,9 @@ importers: '@cybozu/eslint-config': specifier: ^24.3.0 version: 24.3.0(@types/eslint@9.6.1)(@typescript-eslint/utils@8.47.0(eslint@9.38.0)(typescript@5.9.3))(eslint@9.38.0)(prettier@3.6.2)(typescript@5.9.3) + '@kintone/logger': + specifier: workspace:^ + version: link:../logger axios: specifier: ^1.12.2 version: 1.12.2 @@ -252,6 +258,15 @@ importers: specifier: ^5.9.3 version: 5.9.3 + packages/logger: + devDependencies: + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + vitest: + specifier: ^3.0.0 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(jsdom@26.1.0)(terser@5.44.0) + packages/plugin-manifest-validator: dependencies: ajv: @@ -273,6 +288,9 @@ importers: packages/plugin-packer: dependencies: + '@kintone/logger': + specifier: workspace:^ + version: link:../logger '@kintone/plugin-manifest-validator': specifier: ^11.0.1 version: 11.0.1 @@ -2636,9 +2654,23 @@ packages: cpu: [x64] os: [win32] + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.0.16': resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@4.0.16': resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} peerDependencies: @@ -2650,18 +2682,33 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.0.16': resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.0.16': resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.0.16': resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.0.16': resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.0.16': resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} @@ -3179,6 +3226,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -3212,6 +3263,10 @@ packages: caniuse-lite@1.0.30001754: resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -3238,6 +3293,10 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3548,7 +3607,10 @@ packages: deep-diff@0.3.8: resolution: {integrity: sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4665,6 +4727,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -4800,6 +4865,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -5387,6 +5455,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + pbkdf2@3.1.5: resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} engines: {node: '>= 0.10'} @@ -6283,6 +6355,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + stylehacks@7.0.6: resolution: {integrity: sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -6384,6 +6459,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -6392,10 +6470,22 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -6655,6 +6745,11 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite-plugin-dts@4.5.4: resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} peerDependencies: @@ -6746,6 +6841,34 @@ packages: yaml: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@4.0.16: resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -9215,6 +9338,14 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + '@vitest/expect@4.0.16': dependencies: '@standard-schema/spec': 1.0.0 @@ -9224,6 +9355,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 + '@vitest/mocker@3.2.4(vite@5.4.21(@types/node@22.19.3)(terser@5.44.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.3)(terser@5.44.0) + '@vitest/mocker@4.0.16(vite@7.3.1(@types/node@18.19.130)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 4.0.16 @@ -9232,23 +9371,49 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@18.19.130)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.0.16': dependencies: tinyrainbow: 3.0.3 + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + '@vitest/runner@4.0.16': dependencies: '@vitest/utils': 4.0.16 pathe: 2.0.3 + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/snapshot@4.0.16': dependencies: '@vitest/pretty-format': 4.0.16 magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@4.0.16': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.0.16': dependencies: '@vitest/pretty-format': 4.0.16 @@ -9848,6 +10013,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -9886,6 +10053,14 @@ snapshots: caniuse-lite@1.0.30001754: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} chalk@2.4.2: @@ -9907,6 +10082,8 @@ snapshots: chardet@0.7.0: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -10260,6 +10437,8 @@ snapshots: deep-diff@0.3.8: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -11556,6 +11735,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -11708,6 +11889,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@10.4.3: {} lru-cache@11.2.2: {} @@ -12227,6 +12410,8 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + pbkdf2@3.1.5: dependencies: create-hash: 1.2.0 @@ -13310,6 +13495,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + stylehacks@7.0.6(postcss@8.5.6): dependencies: browserslist: 4.28.0 @@ -13416,6 +13605,8 @@ snapshots: tinybench@2.9.0: {} + tinyexec@0.3.2: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -13423,8 +13614,14 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} + tinyspy@4.0.4: {} + tldts-core@6.1.86: {} tldts@6.1.86: @@ -13691,6 +13888,24 @@ snapshots: vary@1.1.2: {} + vite-node@3.2.4(@types/node@22.19.3)(terser@5.44.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@10.2.2) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.21(@types/node@22.19.3)(terser@5.44.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.55.1)(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.3)(terser@5.44.0)): dependencies: '@microsoft/api-extractor': 7.52.13(@types/node@22.19.3) @@ -13751,6 +13966,46 @@ snapshots: tsx: 4.21.0 yaml: 2.8.1 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(jsdom@26.1.0)(terser@5.44.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@22.19.3)(terser@5.44.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3(supports-color@10.2.2) + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 5.4.21(@types/node@22.19.3)(terser@5.44.0) + vite-node: 3.2.4(@types/node@22.19.3)(terser@5.44.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.19.3 + jsdom: 26.1.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@4.0.16(@types/node@18.19.130)(jsdom@26.1.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.16 From 8fa5a7f87148619f77c6e6b2997347063264ff74 Mon Sep 17 00:00:00 2001 From: shabaraba Date: Tue, 13 Jan 2026 12:02:33 +0900 Subject: [PATCH 2/2] refactor: refactor --- packages/logger/CLAUDE.md | 167 ++++++++++++++++++++++++++++++++++ packages/logger/README.md | 118 ++++++++++++++++++++++++ packages/logger/src/index.ts | 9 +- packages/logger/src/logger.ts | 53 +++++------ packages/logger/src/types.ts | 8 +- 5 files changed, 308 insertions(+), 47 deletions(-) create mode 100644 packages/logger/CLAUDE.md create mode 100644 packages/logger/README.md diff --git a/packages/logger/CLAUDE.md b/packages/logger/CLAUDE.md new file mode 100644 index 0000000000..3b54fa8a7b --- /dev/null +++ b/packages/logger/CLAUDE.md @@ -0,0 +1,167 @@ +# CLAUDE.md - @kintone/logger + +This file provides guidance to Claude Code when working with the logger package. + +## Package Overview + +`@kintone/logger` is a lightweight logging utility designed for kintone CLI tools. It provides structured logging with level-based filtering and customizable output. + +## Architecture + +### Core Components + +1. **StandardLogger** (`src/logger.ts`) + - Main logger implementation + - Handles log level filtering + - Formats messages with timestamps + - Supports custom printers + +2. **Types** (`src/types.ts`) + - Logger interface + - Log level definitions + - Type exports + +3. **Public API** (`src/index.ts`) + - `logger`: Pre-configured logger instance + - `StandardLogger`: Class for custom instances + - `LOG_CONFIG_LEVELS`: Array of valid log levels (for CLI choices) + - `LogConfigLevel`: Type for log configuration + +## Development Guidelines + +### File Structure + +``` +src/ +├── index.ts # Public API exports +├── logger.ts # StandardLogger implementation +└── types.ts # Type definitions +``` + +### Code Style + +- Use TypeScript strict mode +- No runtime dependencies (keep it lightweight) +- Prefer composition over inheritance +- Keep methods focused and single-purpose + +### Testing + +Run tests with: +```bash +pnpm test +``` + +Test coverage should include: +- All log levels (trace, debug, info, warn, error, fatal) +- Log level filtering logic +- Message formatting +- Custom printer support +- Error object handling + +### Building + +Build the package: +```bash +pnpm build +``` + +This compiles TypeScript to JavaScript in the `lib/` directory. + +### Linting + +```bash +pnpm lint # Check for issues +pnpm fix # Auto-fix issues +``` + +## Implementation Notes + +### Log Level Filtering + +The logger uses a numeric ordering system to determine which messages to print: + +```typescript +// LEVEL_ORDER is generated from LOG_CONFIG_LEVELS +const LEVEL_ORDER = LOG_CONFIG_LEVELS.reduce( + (acc, level, index) => { + acc[level] = index; + return acc; + }, + {} as Record, +); + +// Level filtering uses simple numeric comparison +private isPrintable(event: LogEvent): boolean { + if (this.logConfigLevel === "none") return false; + return LEVEL_ORDER[event.level] >= LEVEL_ORDER[this.logConfigLevel]; +} +``` + +This approach is more efficient than the matrix-based approach, using O(1) object lookup instead of array operations. + +### Message Formatting + +- Timestamps use ISO 8601 format +- Log levels are displayed in uppercase (using `event.level.toUpperCase()`) +- Multi-line messages are split and prefixed individually +- Error objects are converted to strings using `.toString()` +- All other values use `String(value)` + +### Custom Printers + +The default printer is `console.error`, but custom printers can be provided: + +```typescript +const logger = new StandardLogger({ + printer: (message) => { + // Custom logic (e.g., write to file, send to logging service) + } +}); +``` + +## Common Tasks + +### Adding a New Log Level + +1. Add the new level to `LOG_CONFIG_LEVELS` array in `src/types.ts` +2. `LogConfigLevel` type will be automatically updated (derived from array) +3. `LogEventLevel` type will be automatically updated (uses `Exclude`) +4. Add method to `Logger` interface +5. Add method to `StandardLogger` class +6. `LEVEL_ORDER` will be automatically updated (generated from `LOG_CONFIG_LEVELS`) +7. Add tests for the new level + +Note: Most type definitions are derived from `LOG_CONFIG_LEVELS` as the single source of truth, so changes propagate automatically. + +### Modifying Message Format + +Edit the `format()` method in `src/logger.ts`. Keep in mind: +- The format is used by CLI tools that parse output +- Changes may affect downstream consumers +- Maintain backwards compatibility when possible + +The current format uses `toUpperCase()` to convert log levels to uppercase for display, while keeping the internal representation lowercase for consistency with CLI options. + +## Dependencies + +This package has ZERO runtime dependencies to keep it lightweight. Only dev dependencies are: +- `vitest` - Testing framework +- `rimraf` - Clean build artifacts + +## Publishing + +This package is currently private (`"private": true` in package.json). If it needs to be published: + +1. Remove `"private": true` +2. Ensure version follows semver +3. Run `pnpm build` and `pnpm test` +4. Publish with `npm publish` (configured for public access) + +## Related Packages + +This logger is designed to be used by: +- `cli-kintone` - CSV import/export CLI +- Other kintone CLI tools in the monorepo + +When making changes, consider the impact on these consumers. diff --git a/packages/logger/README.md b/packages/logger/README.md new file mode 100644 index 0000000000..c8619ecb5f --- /dev/null +++ b/packages/logger/README.md @@ -0,0 +1,118 @@ +# @kintone/logger + +Logger utility for kintone CLI tools. + +## Features + +- **6 log levels**: trace, debug, info, warn, error, fatal +- **Configurable log level filtering**: Control which messages are printed +- **Timestamp support**: All messages include ISO 8601 timestamps +- **Custom printer support**: Use custom output functions instead of console.error +- **TypeScript support**: Full type definitions included + +## Installation + +```bash +npm install @kintone/logger +# or +pnpm add @kintone/logger +``` + +## Usage + +### Basic Usage + +```typescript +import { logger } from "@kintone/logger"; + +logger.info("Application started"); +logger.warn("This is a warning"); +logger.error(new Error("Something went wrong")); +``` + +### Custom Logger Instance + +```typescript +import { StandardLogger } from "@kintone/logger"; + +const customLogger = new StandardLogger({ + logConfigLevel: "debug", + printer: (message) => { + // Custom output logic + console.log(message); + }, +}); + +customLogger.debug("Debug message"); +customLogger.info("Info message"); +``` + +### Dynamic Log Level Configuration + +```typescript +import { logger } from "@kintone/logger"; + +// Set log level to only show warnings and above +logger.setLogConfigLevel("warn"); + +logger.info("This won't be printed"); +logger.warn("This will be printed"); +logger.error("This will be printed"); + +// Disable all logging +logger.setLogConfigLevel("none"); +``` + +## API Reference + +### Logger Interface + +```typescript +interface Logger { + trace: (message: unknown) => void; + debug: (message: unknown) => void; + info: (message: unknown) => void; + warn: (message: unknown) => void; + error: (message: unknown) => void; + fatal: (message: unknown) => void; +} +``` + +### StandardLogger Constructor Options + +```typescript +new StandardLogger({ + logConfigLevel?: "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "none"; + printer?: (data: unknown) => void; +}); +``` + +### Log Levels + +Log levels control which messages are printed based on severity: + +- `trace`: Most verbose - prints all messages +- `debug`: Debug and above +- `info`: Info and above (default) +- `warn`: Warnings and above +- `error`: Errors and above +- `fatal`: Only fatal errors +- `none`: No output + +## Output Format + +All log messages are formatted with timestamp and level: + +``` +[2024-01-15T12:34:56.789Z] INFO: Application started +[2024-01-15T12:34:57.123Z] WARN: Low memory warning +[2024-01-15T12:34:58.456Z] ERROR: Failed to connect +``` + +## License + +MIT + +## Author + +Cybozu, Inc. diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 99e0ce0ce1..18e3db7f15 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,9 +1,2 @@ export { StandardLogger, logger } from "./logger"; -export { - LOG_CONFIG_LEVELS, - type Logger, - type LogConfigLevel, - type LogEvent, - type LogEventLevel, - type Printer, -} from "./types"; +export { LOG_CONFIG_LEVELS, type LogConfigLevel } from "./types"; diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts index a83d937722..c0f2160d5b 100644 --- a/packages/logger/src/logger.ts +++ b/packages/logger/src/logger.ts @@ -5,6 +5,15 @@ import type { LogEventLevel, Printer, } from "./types"; +import { LOG_CONFIG_LEVELS } from "./types"; + +const LEVEL_ORDER = LOG_CONFIG_LEVELS.reduce( + (acc, level, index) => { + acc[level] = index; + return acc; + }, + {} as Record, +); export class StandardLogger implements Logger { private readonly printer: Printer = console.error; @@ -55,36 +64,20 @@ export class StandardLogger implements Logger { return; } const formattedMessage = this.format(event); - this.print(formattedMessage); + this.printer(formattedMessage); } private isPrintable(event: LogEvent): boolean { - const logConfigLevelMatrix: { - [configLevel in LogConfigLevel]: LogEventLevel[]; - } = { - trace: ["trace", "debug", "info", "warn", "error", "fatal"], - debug: ["debug", "info", "warn", "error", "fatal"], - info: ["info", "warn", "error", "fatal"], - warn: ["warn", "error", "fatal"], - error: ["error", "fatal"], - fatal: ["fatal"], - none: [], - }; - return logConfigLevelMatrix[this.logConfigLevel].includes(event.level); + if (this.logConfigLevel === "none") { + return false; + } + return LEVEL_ORDER[event.level] >= LEVEL_ORDER[this.logConfigLevel]; } private format(event: LogEvent): string { const timestamp = new Date().toISOString(); - const eventLevelLabels: { [level in LogEventLevel]: string } = { - trace: "TRACE", - debug: "DEBUG", - info: "INFO", - warn: "WARN", - error: "ERROR", - fatal: "FATAL", - }; - const stringifiedMessage = stringifyMessage(event.message); - const prefix = `[${timestamp}] ${eventLevelLabels[event.level]}:`; + const stringifiedMessage = this.stringifyMessage(event.message); + const prefix = `[${timestamp}] ${event.level.toUpperCase()}:`; return stringifiedMessage .split("\n") @@ -93,16 +86,12 @@ export class StandardLogger implements Logger { .join("\n"); } - private print(message: string): void { - this.printer(message); + private stringifyMessage(message: unknown): string { + if (message instanceof Error) { + return message.toString(); + } + return String(message); } } export const logger = new StandardLogger(); - -const stringifyMessage = (message: unknown): string => { - if (message instanceof Error) { - return message.toString(); - } - return String(message); -}; diff --git a/packages/logger/src/types.ts b/packages/logger/src/types.ts index d4441799b1..3bd56f6bd2 100644 --- a/packages/logger/src/types.ts +++ b/packages/logger/src/types.ts @@ -19,13 +19,7 @@ export const LOG_CONFIG_LEVELS = [ export type LogConfigLevel = (typeof LOG_CONFIG_LEVELS)[number]; -export type LogEventLevel = - | "trace" - | "debug" - | "info" - | "warn" - | "error" - | "fatal"; +export type LogEventLevel = Exclude; export type LogEvent = { level: LogEventLevel;