1- const IS_DEV = import . meta. env . DEV ;
1+ /**
2+ * 개발 환경 여부 — debug/info 로그 출력 기준
3+ *
4+ * ## 왜 import.meta.env.DEV를 쓰지 않는가
5+ *
6+ * Vite의 `import.meta.env.DEV`는 실행 **명령(command)** 을 기준으로 결정된다.
7+ * - `vite` (dev server 실행) → DEV = true
8+ * - `vite build` → DEV = false ← --mode development를 붙여도 마찬가지
9+ *
10+ * Vite는 빌드 시 `import.meta.env.*`를 정적 문자열로 치환(static replace)하는데,
11+ * DEV/PROD의 치환 기준이 mode가 아닌 command이기 때문에 build 산출물에서는
12+ * 항상 false가 된다.
13+ *
14+ * 결과적으로 `pnpm run build:local` (--mode development) 로 빌드한
15+ * 확장 프로그램에서 debugLog/infoLog가 전부 묵음이 되는 버그가 발생한다.
16+ *
17+ * ## 왜 import.meta.env.MODE를 쓰는가
18+ *
19+ * `import.meta.env.MODE`는 `--mode` 플래그 값을 그대로 반영한다.
20+ * - `vite build --mode development` → MODE = "development" ✓
21+ * - `vite build --mode production` → MODE = "production"
22+ * - `vite build` (기본값) → MODE = "production"
23+ *
24+ * 이를 통해 build:local 환경에서 debug 로그가 정상 출력된다.
25+ */
26+ const IS_DEV = import . meta. env . MODE === 'development' ;
227
328const MAX_STRING_LENGTH = 400 ;
429const MAX_ARRAY_LENGTH = 20 ;
@@ -7,6 +32,8 @@ const MAX_DEPTH = 4;
732const REDACTED = "[REDACTED]" ;
833const TRUNCATED_ARRAY_META_KEY = "__truncated_items__" ;
934const TRUNCATED_OBJECT_META_KEY = "__truncated_keys__" ;
35+ const ERROR_ACCESSING_PROPERTY = "[Error accessing property]" ;
36+ const UNINSPECTABLE_OBJECT = "[Uninspectable Object]" ;
1037
1138const EMAIL_PATTERN =
1239 / \b ( [ A - Z 0 - 9 . _ % + - ] ) ( [ A - Z 0 - 9 . _ % + - ] * ) ( @ [ A - Z 0 - 9 . - ] + \. [ A - Z ] { 2 , } ) \b / gi;
@@ -23,8 +50,29 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
2350 return false ;
2451 }
2552
26- const prototype = Object . getPrototypeOf ( value ) ;
27- return prototype === Object . prototype || prototype === null ;
53+ try {
54+ const prototype = Object . getPrototypeOf ( value ) ;
55+ return prototype === Object . prototype || prototype === null ;
56+ } catch {
57+ return false ;
58+ }
59+ }
60+
61+ function getObjectKeys ( value : object , isPlain : boolean ) : string [ ] {
62+ try {
63+ return isPlain ? Object . keys ( value ) : Object . getOwnPropertyNames ( value ) ;
64+ } catch {
65+ return [ ] ;
66+ }
67+ }
68+
69+ function getObjectTypeName ( value : object ) : string | null {
70+ try {
71+ const typeName = value . constructor ?. name ;
72+ return typeName && typeName !== "Object" ? typeName : null ;
73+ } catch {
74+ return null ;
75+ }
2876}
2977
3078function sanitizeString ( value : string ) : string {
@@ -79,6 +127,10 @@ function sanitizeValue(
79127 return sanitizeString ( value . toString ( ) ) ;
80128 }
81129
130+ if ( value instanceof RegExp ) {
131+ return sanitizeString ( value . toString ( ) ) ;
132+ }
133+
82134 if ( value instanceof Error ) {
83135 return getErrorLogDetails ( value ) ;
84136 }
@@ -115,23 +167,51 @@ function sanitizeValue(
115167
116168 seen . add ( value ) ;
117169
118- if ( ! isPlainObject ( value ) ) {
119- return sanitizeString ( String ( value ) ) ;
120- }
170+ const plainObject = isPlainObject ( value ) ;
171+
172+ // non-plain 객체(커스텀 클래스, chrome API 객체 등)는 isPlainObject를 통과 못 해
173+ // String() 변환 시 "[object Object]"가 되어 디버깅 불가.
174+ // Object.getOwnPropertyNames로 non-enumerable 포함 own property를 추출한다.
175+ // (예: chrome.runtime.lastError.message는 non-enumerable이라 Object.keys에 안 잡힘)
176+ const allKeys = getObjectKeys ( value as object , plainObject ) ;
177+
178+ const keys = allKeys . slice ( 0 , MAX_OBJECT_KEYS ) ;
179+ const sanitizedObject = keys . reduce < Record < string , unknown > > ( ( acc , key ) => {
180+ if ( SENSITIVE_KEY_PATTERN . test ( key ) ) {
181+ acc [ key ] = REDACTED ;
182+ return acc ;
183+ }
184+
185+ try {
186+ acc [ key ] = sanitizeValue (
187+ ( value as Record < string , unknown > ) [ key ] ,
188+ depth + 1 ,
189+ seen ,
190+ ) ;
191+ } catch {
192+ acc [ key ] = ERROR_ACCESSING_PROPERTY ;
193+ }
121194
122- const entries = Object . entries ( value ) . slice ( 0 , MAX_OBJECT_KEYS ) ;
123- const sanitizedObject = entries . reduce < Record < string , unknown > > ( ( acc , [ key , entryValue ] ) => {
124- acc [ key ] = SENSITIVE_KEY_PATTERN . test ( key )
125- ? REDACTED
126- : sanitizeValue ( entryValue , depth + 1 , seen ) ;
127195 return acc ;
128196 } , { } ) ;
129- const omittedCount = Object . keys ( value ) . length - entries . length ;
130197
198+ const omittedCount = allKeys . length - keys . length ;
131199 if ( omittedCount > 0 ) {
132200 sanitizedObject [ TRUNCATED_OBJECT_META_KEY ] = omittedCount ;
133201 }
134202
203+ // 클래스 인스턴스라면 타입 힌트 추가
204+ if ( ! plainObject ) {
205+ const typeName = getObjectTypeName ( value as object ) ;
206+ if ( typeName ) {
207+ sanitizedObject [ "[type]" ] = typeName ;
208+ }
209+ }
210+
211+ if ( allKeys . length === 0 && Object . keys ( sanitizedObject ) . length === 0 ) {
212+ return getObjectTypeName ( value as object ) ?? UNINSPECTABLE_OBJECT ;
213+ }
214+
135215 return sanitizedObject ;
136216 }
137217
0 commit comments