Skip to content

Commit 5dcbbf0

Browse files
authored
Merge pull request #70 from ArticPenguin/fix/logger-dev-mode
FIX : 로깅 모듈 버그 2종 수정
2 parents 117b8b5 + d9f34c6 commit 5dcbbf0

1 file changed

Lines changed: 92 additions & 12 deletions

File tree

src/utils/logger.ts

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
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

328
const MAX_STRING_LENGTH = 400;
429
const MAX_ARRAY_LENGTH = 20;
@@ -7,6 +32,8 @@ const MAX_DEPTH = 4;
732
const REDACTED = "[REDACTED]";
833
const TRUNCATED_ARRAY_META_KEY = "__truncated_items__";
934
const TRUNCATED_OBJECT_META_KEY = "__truncated_keys__";
35+
const ERROR_ACCESSING_PROPERTY = "[Error accessing property]";
36+
const UNINSPECTABLE_OBJECT = "[Uninspectable Object]";
1037

1138
const EMAIL_PATTERN =
1239
/\b([A-Z0-9._%+-])([A-Z0-9._%+-]*)(@[A-Z0-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

3078
function 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

Comments
 (0)