From 01188b2ef0db1996a992e318672b866582ba7806 Mon Sep 17 00:00:00 2001 From: fast1597 Date: Mon, 27 Apr 2026 18:59:09 +0900 Subject: [PATCH 1/3] fix(logger): use MODE instead of DEV for dev log detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit import.meta.env.DEV는 vite build 시 명령(command)을 기준으로 false로 치환되어 --mode development로 빌드해도 debugLog가 항상 묵음이 됨. import.meta.env.MODE는 --mode 플래그 값을 그대로 반영하므로 build:local(--mode development)에서 debug/info 로그가 정상 출력됨. Co-Authored-By: Claude Sonnet 4.6 docs(logger): add comment explaining DEV vs MODE distinction import.meta.env.DEV가 vite build 시 항상 false가 되는 이유와 MODE로 교체한 배경을 주석으로 명시함. Co-Authored-By: Claude Sonnet 4.6 --- src/utils/logger.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 2b92cae..6f855ba 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,4 +1,29 @@ -const IS_DEV = import.meta.env.DEV; +/** + * 개발 환경 여부 — debug/info 로그 출력 기준 + * + * ## 왜 import.meta.env.DEV를 쓰지 않는가 + * + * Vite의 `import.meta.env.DEV`는 실행 **명령(command)** 을 기준으로 결정된다. + * - `vite` (dev server 실행) → DEV = true + * - `vite build` → DEV = false ← --mode development를 붙여도 마찬가지 + * + * Vite는 빌드 시 `import.meta.env.*`를 정적 문자열로 치환(static replace)하는데, + * DEV/PROD의 치환 기준이 mode가 아닌 command이기 때문에 build 산출물에서는 + * 항상 false가 된다. + * + * 결과적으로 `pnpm run build:local` (--mode development) 로 빌드한 + * 확장 프로그램에서 debugLog/infoLog가 전부 묵음이 되는 버그가 발생한다. + * + * ## 왜 import.meta.env.MODE를 쓰는가 + * + * `import.meta.env.MODE`는 `--mode` 플래그 값을 그대로 반영한다. + * - `vite build --mode development` → MODE = "development" ✓ + * - `vite build --mode production` → MODE = "production" + * - `vite build` (기본값) → MODE = "production" + * + * 이를 통해 build:local 환경에서 debug 로그가 정상 출력된다. + */ +const IS_DEV = import.meta.env.MODE === 'development'; const MAX_STRING_LENGTH = 400; const MAX_ARRAY_LENGTH = 20; From 431a1674d5edb3bdd182668d1b4a4e984cdc3584 Mon Sep 17 00:00:00 2001 From: fast1597 Date: Tue, 28 Apr 2026 03:21:08 +0900 Subject: [PATCH 2/3] fix(logger): handle non-plain objects in sanitizeValue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에는 isPlainObject가 false인 객체(클래스 인스턴스, chrome API 객체 등)를 String()으로 변환해 "[object Object]"로 로깅되는 문제가 있었음. Object.getOwnPropertyNames으로 교체해 non-enumerable 프로퍼티까지 추출하도록 수정. 이를 통해 chrome.runtime.lastError.message(non-enumerable)나 DOMException 등 Chrome 내부 객체도 의미있는 정보로 출력됨. 클래스 인스턴스에는 "[type]" 힌트를 추가해 타입 파악을 용이하게 함. Co-Authored-By: Claude Sonnet 4.6 --- src/utils/logger.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 6f855ba..4eb0fb8 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -140,23 +140,35 @@ function sanitizeValue( seen.add(value); - if (!isPlainObject(value)) { - return sanitizeString(String(value)); - } - - const entries = Object.entries(value).slice(0, MAX_OBJECT_KEYS); - const sanitizedObject = entries.reduce>((acc, [key, entryValue]) => { + // non-plain 객체(커스텀 클래스, chrome API 객체 등)는 isPlainObject를 통과 못 해 + // String() 변환 시 "[object Object]"가 되어 디버깅 불가. + // Object.getOwnPropertyNames로 non-enumerable 포함 own property를 추출한다. + // (예: chrome.runtime.lastError.message는 non-enumerable이라 Object.keys에 안 잡힘) + const allKeys = isPlainObject(value) + ? Object.keys(value) + : Object.getOwnPropertyNames(value as object); + + const keys = allKeys.slice(0, MAX_OBJECT_KEYS); + const sanitizedObject = keys.reduce>((acc, key) => { acc[key] = SENSITIVE_KEY_PATTERN.test(key) ? REDACTED - : sanitizeValue(entryValue, depth + 1, seen); + : sanitizeValue((value as Record)[key], depth + 1, seen); return acc; }, {}); - const omittedCount = Object.keys(value).length - entries.length; + const omittedCount = allKeys.length - keys.length; if (omittedCount > 0) { sanitizedObject[TRUNCATED_OBJECT_META_KEY] = omittedCount; } + // 클래스 인스턴스라면 타입 힌트 추가 + if (!isPlainObject(value)) { + const typeName = (value as object).constructor?.name; + if (typeName && typeName !== "Object") { + sanitizedObject["[type]"] = typeName; + } + } + return sanitizedObject; } From d9f34c6c02731193e64f44e6421daea1a1a85670 Mon Sep 17 00:00:00 2001 From: fast1597 Date: Wed, 29 Apr 2026 01:57:35 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix(logger):=20sanitizeValue=20=EC=95=88?= =?UTF-8?q?=EC=A0=84=EC=84=B1=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RegExp 는 패턴 문자열로 바로 로깅되도록 분기했습니다. throwing getter, Proxy, inspect 불가능한 객체로 인해 로거가 다시 실패하지 않도록 property access 와 type 조회를 방어하고 fallback 값을 추가했습니다. --- src/utils/logger.ts | 65 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 4eb0fb8..6149fb5 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -32,6 +32,8 @@ const MAX_DEPTH = 4; const REDACTED = "[REDACTED]"; const TRUNCATED_ARRAY_META_KEY = "__truncated_items__"; const TRUNCATED_OBJECT_META_KEY = "__truncated_keys__"; +const ERROR_ACCESSING_PROPERTY = "[Error accessing property]"; +const UNINSPECTABLE_OBJECT = "[Uninspectable Object]"; const EMAIL_PATTERN = /\b([A-Z0-9._%+-])([A-Z0-9._%+-]*)(@[A-Z0-9.-]+\.[A-Z]{2,})\b/gi; @@ -48,8 +50,29 @@ function isPlainObject(value: unknown): value is Record { return false; } - const prototype = Object.getPrototypeOf(value); - return prototype === Object.prototype || prototype === null; + try { + const prototype = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; + } catch { + return false; + } +} + +function getObjectKeys(value: object, isPlain: boolean): string[] { + try { + return isPlain ? Object.keys(value) : Object.getOwnPropertyNames(value); + } catch { + return []; + } +} + +function getObjectTypeName(value: object): string | null { + try { + const typeName = value.constructor?.name; + return typeName && typeName !== "Object" ? typeName : null; + } catch { + return null; + } } function sanitizeString(value: string): string { @@ -104,6 +127,10 @@ function sanitizeValue( return sanitizeString(value.toString()); } + if (value instanceof RegExp) { + return sanitizeString(value.toString()); + } + if (value instanceof Error) { return getErrorLogDetails(value); } @@ -140,19 +167,31 @@ function sanitizeValue( seen.add(value); + const plainObject = isPlainObject(value); + // non-plain 객체(커스텀 클래스, chrome API 객체 등)는 isPlainObject를 통과 못 해 // String() 변환 시 "[object Object]"가 되어 디버깅 불가. // Object.getOwnPropertyNames로 non-enumerable 포함 own property를 추출한다. // (예: chrome.runtime.lastError.message는 non-enumerable이라 Object.keys에 안 잡힘) - const allKeys = isPlainObject(value) - ? Object.keys(value) - : Object.getOwnPropertyNames(value as object); + const allKeys = getObjectKeys(value as object, plainObject); const keys = allKeys.slice(0, MAX_OBJECT_KEYS); const sanitizedObject = keys.reduce>((acc, key) => { - acc[key] = SENSITIVE_KEY_PATTERN.test(key) - ? REDACTED - : sanitizeValue((value as Record)[key], depth + 1, seen); + if (SENSITIVE_KEY_PATTERN.test(key)) { + acc[key] = REDACTED; + return acc; + } + + try { + acc[key] = sanitizeValue( + (value as Record)[key], + depth + 1, + seen, + ); + } catch { + acc[key] = ERROR_ACCESSING_PROPERTY; + } + return acc; }, {}); @@ -162,13 +201,17 @@ function sanitizeValue( } // 클래스 인스턴스라면 타입 힌트 추가 - if (!isPlainObject(value)) { - const typeName = (value as object).constructor?.name; - if (typeName && typeName !== "Object") { + if (!plainObject) { + const typeName = getObjectTypeName(value as object); + if (typeName) { sanitizedObject["[type]"] = typeName; } } + if (allKeys.length === 0 && Object.keys(sanitizedObject).length === 0) { + return getObjectTypeName(value as object) ?? UNINSPECTABLE_OBJECT; + } + return sanitizedObject; }