@ga-components/logger 1.0.25 → 1.0.27
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.
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -52,6 +52,7 @@ declare class Logger {
|
|
|
52
52
|
initAnonymousUser(): void;
|
|
53
53
|
addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, unknown>): void;
|
|
54
54
|
initGlobalCapture(): void;
|
|
55
|
+
private lastErrorMessage;
|
|
55
56
|
private shouldSkipGlobalError;
|
|
56
57
|
private sanitizePayload;
|
|
57
58
|
private sanitizeForFirestore;
|
package/dist/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ declare class Logger {
|
|
|
52
52
|
initAnonymousUser(): void;
|
|
53
53
|
addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, unknown>): void;
|
|
54
54
|
initGlobalCapture(): void;
|
|
55
|
+
private lastErrorMessage;
|
|
55
56
|
private shouldSkipGlobalError;
|
|
56
57
|
private sanitizePayload;
|
|
57
58
|
private sanitizeForFirestore;
|
package/dist/index.js
CHANGED
|
@@ -70,6 +70,8 @@ var Logger = class {
|
|
|
70
70
|
this.config = null;
|
|
71
71
|
this.initialized = false;
|
|
72
72
|
this.breadcrumbs = [];
|
|
73
|
+
// Smart filter — skip noise dari CORS, network, third-party
|
|
74
|
+
this.lastErrorMessage = "";
|
|
73
75
|
}
|
|
74
76
|
init(config) {
|
|
75
77
|
this.config = config;
|
|
@@ -121,7 +123,7 @@ var Logger = class {
|
|
|
121
123
|
this.breadcrumbs.shift();
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
|
-
//
|
|
126
|
+
// Global error capture — hanya aktif di non-development
|
|
125
127
|
initGlobalCapture() {
|
|
126
128
|
if (!this.config || this.config.env === "DEV") {
|
|
127
129
|
console.info("[ga-logger] global capture skipped \u2014 DEV environment");
|
|
@@ -151,9 +153,22 @@ var Logger = class {
|
|
|
151
153
|
messageError: error
|
|
152
154
|
});
|
|
153
155
|
});
|
|
156
|
+
const originalConsoleError = console.error;
|
|
157
|
+
console.error = (...args) => {
|
|
158
|
+
originalConsoleError(...args);
|
|
159
|
+
const message = args.map((a) => a instanceof Error ? a.message : String(a)).join(" ");
|
|
160
|
+
if (this.shouldSkipGlobalError(message)) return;
|
|
161
|
+
this.addBreadcrumb("error", `console.error: ${message}`);
|
|
162
|
+
if (this.config?.env !== "DEV") {
|
|
163
|
+
this.error({
|
|
164
|
+
fnName: "console.error",
|
|
165
|
+
messageError: new Error(message),
|
|
166
|
+
payload: { args: args.map(String) }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
154
170
|
console.info("[ga-logger] global capture active");
|
|
155
171
|
}
|
|
156
|
-
// ✅ NEW: Smart filter — skip noise dari CORS, network, third-party
|
|
157
172
|
shouldSkipGlobalError(message, source) {
|
|
158
173
|
if (!message) return true;
|
|
159
174
|
const noisePatterns = [
|
|
@@ -163,7 +178,11 @@ var Logger = class {
|
|
|
163
178
|
"Failed to load resource",
|
|
164
179
|
"NetworkError",
|
|
165
180
|
"ResizeObserver loop",
|
|
166
|
-
"Script error"
|
|
181
|
+
"Script error",
|
|
182
|
+
"ServiceWorker",
|
|
183
|
+
"sw.js",
|
|
184
|
+
"dev-sw.js",
|
|
185
|
+
"Warning:"
|
|
167
186
|
];
|
|
168
187
|
const isNoise = noisePatterns.some((pattern) => message.toLowerCase().includes(pattern.toLowerCase()));
|
|
169
188
|
if (isNoise) return true;
|
|
@@ -172,6 +191,8 @@ var Logger = class {
|
|
|
172
191
|
const isThirdParty = thirdPartyPatterns.some((pattern) => source.includes(pattern));
|
|
173
192
|
if (isThirdParty) return true;
|
|
174
193
|
}
|
|
194
|
+
if (message === this.lastErrorMessage) return true;
|
|
195
|
+
this.lastErrorMessage = message;
|
|
175
196
|
return false;
|
|
176
197
|
}
|
|
177
198
|
sanitizePayload(obj) {
|
|
@@ -355,7 +376,6 @@ var Logger = class {
|
|
|
355
376
|
repo: this.config.repo,
|
|
356
377
|
endpoint: endpoint ?? null,
|
|
357
378
|
breadcrumbs: [...this.breadcrumbs]
|
|
358
|
-
// ← snapshot saat error terjadi
|
|
359
379
|
};
|
|
360
380
|
try {
|
|
361
381
|
const db2 = getDb();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/logger.ts","../src/config/firebase.ts","../src/components/LoggerErrorBoundary.tsx"],"sourcesContent":["export { logger } from \"./core/logger\";\nexport { LoggerErrorBoundary } from \"./components/LoggerErrorBoundary\"; // ← baru\nexport type { LogEntry, LoggerConfig, LogLevel, LogEnv, Breadcrumb, BreadcrumbType } from \"./types\";\n","import { collection, addDoc, Timestamp } from \"firebase/firestore\";\nimport { initFirebase, getDb } from \"../config/firebase\";\nimport type { Breadcrumb, BreadcrumbType, LogEntry, LogLevel, LogParams, LoggerConfig } from \"../types\";\n\nconst sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\nconst MAX_BREADCRUMBS = 50;\n\nconst CLICK_SKIP_PATTERNS = [\n /^:r[a-z0-9]+:$/, \n /^\\[object/,\n /^#/,\n];\n\n// ✅ Sanitize PII sebelum masuk Firestore\nconst sanitizeClickText = (text: string): string => {\n return text\n .replace(/\\b[\\w.-]+@[\\w.-]+\\.\\w+\\b/g, '[email]')\n .replace(/\\b\\d{10,}\\b/g, '[number]')\n .replace(/Rp[\\s]?\\d[\\d.,]*/gi, '[amount]')\n .trim();\n};\n\nclass Logger {\n private config: LoggerConfig | null = null;\n private initialized = false;\n private breadcrumbs: Breadcrumb[] = [];\n\n init(config: LoggerConfig) {\n this.config = config;\n initFirebase(config);\n this.initialized = true;\n console.info(\"[ga-logger] initialized —\", config.env);\n }\n\n setUserId(userId: string | null) {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n this.config.userId = userId;\n }\n // ✅ NEW METHOD\n initAnonymousUser(): void {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n const STORAGE_KEY = \"ga_anon_id\";\n\n try {\n let anonId = localStorage.getItem(STORAGE_KEY);\n\n if (!anonId) {\n anonId =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n\n localStorage.setItem(STORAGE_KEY, anonId);\n }\n\n const shortId = anonId.slice(-8);\n this.setUserId(`Anonymous #${shortId}`);\n } catch (e) {\n // localStorage blocked (private mode / HTTP restriction)\n console.warn(\"[ga-logger] initAnonymousUser failed:\", e);\n this.setUserId(\"Anonymous\");\n }\n }\n\n // ✅ NEW: Add breadcrumb ke in-memory array\n addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, unknown>) {\n const breadcrumb: Breadcrumb = {\n timestamp: new Date(),\n type,\n message,\n data,\n };\n\n this.breadcrumbs.push(breadcrumb);\n\n // Jaga memory — max 50 breadcrumbs, buang yang paling lama\n if (this.breadcrumbs.length > MAX_BREADCRUMBS) {\n this.breadcrumbs.shift();\n }\n }\n\n // ✅ NEW: Global error capture — hanya aktif di non-development\n initGlobalCapture() {\n if (!this.config || this.config.env === \"DEV\") {\n console.info(\"[ga-logger] global capture skipped — DEV environment\");\n return;\n }\n\n // Capture JS runtime error\n window.onerror = (message, source, lineno, colno, error) => {\n if (this.shouldSkipGlobalError(message?.toString(), source)) return false;\n\n this.addBreadcrumb(\"error\", `window.onerror: ${message}`, {\n source,\n lineno,\n colno,\n });\n\n this.error({\n fnName: \"window.onerror\",\n messageError: error ?? new Error(String(message)),\n payload: { source, lineno, colno },\n });\n\n return false;\n };\n\n // Capture unhandled Promise rejection\n window.addEventListener(\"unhandledrejection\", (event) => {\n const message = event.reason?.message ?? String(event.reason);\n\n if (this.shouldSkipGlobalError(message)) return;\n\n const error = event.reason instanceof Error ? event.reason : new Error(message);\n\n this.addBreadcrumb(\"error\", `unhandledrejection: ${message}`);\n\n this.error({\n fnName: \"window.unhandledrejection\",\n messageError: error,\n });\n });\n\n console.info(\"[ga-logger] global capture active\");\n }\n\n // ✅ NEW: Smart filter — skip noise dari CORS, network, third-party\n private shouldSkipGlobalError(message?: string, source?: string): boolean {\n if (!message) return true;\n\n const noisePatterns = [\n \"CORS\",\n \"cross-origin\",\n \"ERR_FAILED\",\n \"Failed to load resource\",\n \"NetworkError\",\n \"ResizeObserver loop\",\n \"Script error\",\n ];\n\n const isNoise = noisePatterns.some((pattern) => message.toLowerCase().includes(pattern.toLowerCase()));\n if (isNoise) return true;\n\n // Skip error dari third-party source\n if (source) {\n const thirdPartyPatterns = [\"magnolia\", \"gtm.js\", \"analytics\", \"googletagmanager\", \"facebook\", \"hotjar\"];\n const isThirdParty = thirdPartyPatterns.some((pattern) => source.includes(pattern));\n if (isThirdParty) return true;\n }\n\n return false;\n }\n\n private sanitizePayload(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return [key, this.sanitizePayload(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n private sanitizeForFirestore(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (typeof value === \"function\") return [key, \"[function]\"];\n if (value instanceof Date) return [key, value.toISOString()];\n if (Array.isArray(value)) {\n return [\n key,\n value.map((item) => {\n if (item instanceof Date) return item.toISOString();\n if (typeof item === \"function\") return \"[function]\"; // ← tambah\n if (item !== null && typeof item === \"object\") {\n return this.sanitizeForFirestore(item as Record<string, unknown>);\n }\n return item ?? null;\n }),\n ];\n }\n if (value !== null && typeof value === \"object\") {\n return [key, this.sanitizeForFirestore(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n // NEW: interceptFetch — auto breadcrumb, ke Firestore\n interceptFetch() {\n if (typeof window === \"undefined\") return;\n\n const originalFetch = window.fetch;\n\n const skipPatterns = [\n // Third-party domains\n \"google.com\",\n \"googletagmanager.com\",\n \"googleapis.com\",\n \"facebook.com\",\n \"hotjar.com\",\n \"analytics\",\n \"gtm\",\n \"ccm/collect\",\n \"on.aws\",\n\n // Static assets ga-web\n \"/locales/\",\n \"/sw.js\",\n \"/manifest.json\",\n ];\n\n const shouldSkipUrl = (url: string): boolean => {\n return skipPatterns.some((pattern) => url.includes(pattern));\n };\n\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n // ← Skip third-party domains\n if (shouldSkipUrl(url)) {\n return originalFetch(input, init);\n }\n\n const method = init?.method ?? \"GET\";\n const startTime = Date.now();\n\n try {\n const response = await originalFetch(input, init);\n const duration = Date.now() - startTime;\n\n this.addBreadcrumb(\"fetch\", `${method} ${url}`, {\n status: response.status,\n duration: `${duration}ms`,\n });\n\n if (response.status >= 400) {\n console.warn(`[ga-logger] HTTP ${response.status} skipped — Kibana handles this`, url);\n }\n\n return response;\n } catch (networkError) {\n const duration = Date.now() - startTime;\n const error = networkError instanceof Error ? networkError : new Error(String(networkError));\n\n this.addBreadcrumb(\"fetch\", `${method} ${url} — NETWORK ERROR`, {\n duration: `${duration}ms`,\n error: error.message,\n });\n\n if (this.config?.env !== \"DEV\") {\n this.error({\n fnName: \"interceptFetch\",\n messageError: error,\n endpointName: url,\n payload: { method, duration: `${duration}ms` },\n });\n }\n\n throw networkError;\n }\n };\n\n console.info(\"[ga-logger] fetch intercepted\");\n }\n\n interceptClick(): void {\n if (typeof window === \"undefined\") return;\n\n if ((window as any).__ga_click_intercepted) return;\n (window as any).__ga_click_intercepted = true;\n\n document.addEventListener(\n \"click\",\n (event: MouseEvent) => {\n const raw = event.target as HTMLElement;\n if (!raw) return;\n\n const target = (raw.closest('button, a, [role=\"button\"], [data-log]') as HTMLElement) ?? raw;\n\n const innerText = sanitizeClickText(target.innerText?.trim().slice(0, 50) ?? \"\");\n const id = target.id ?? \"\";\n const tagName = target.tagName?.toLowerCase() ?? \"\";\n\n if (!innerText && !id) return;\n\n const message = innerText || id;\n\n // Skip internal React/Router identifiers\n if (CLICK_SKIP_PATTERNS.some((p) => p.test(message))) return;\n\n this.addBreadcrumb(\"click\", message, {\n tagName,\n ...(id ? { id } : {}),\n ...(innerText ? { text: innerText } : {}),\n });\n },\n true,\n );\n }\n\n captureAction<T>(params: LogParams, fn: () => T | Promise<T>): Promise<T | undefined> {\n return Promise.resolve()\n .then(() => fn())\n .catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.send(\"error\", params.fnName, params.payload, err, params.endpointName);\n return undefined;\n });\n }\n\n withLogger<T extends (...args: any[]) => any>(\n fnName: string,\n fn: T,\n options?: {\n endpointName?: string;\n payload?: Record<string, unknown>;\n },\n ): T {\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n const result = fn(...args);\n\n // Handle async function\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }) as ReturnType<T>;\n }\n\n return result;\n } catch (error: unknown) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }\n }) as T;\n }\n\n private async send(\n level: LogLevel,\n message: string,\n payload: Record<string, unknown> = {},\n error?: Error,\n endpoint?: string,\n ) {\n if (!this.initialized || !this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n // DEV → console only, skip Firestore\n if (this.config.env === \"localhost\") {\n console.error(`[ga-localhost][DEV] ${message}`, { payload, error });\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date(),\n level,\n message,\n env: this.config.env,\n userId: this.config.userId ?? null,\n sessionId,\n page: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n stackTrace: error?.stack ?? null,\n payload: this.sanitizePayload(payload),\n repo: this.config.repo,\n endpoint: endpoint ?? null,\n breadcrumbs: [...this.breadcrumbs], // ← snapshot saat error terjadi\n };\n\n try {\n const db = getDb();\n const safeEntry = this.sanitizeForFirestore(entry as unknown as Record<string, unknown>);\n await addDoc(collection(db, \"logs\"), {\n ...safeEntry,\n timestamp: Timestamp.fromDate(entry.timestamp),\n });\n } catch (e) {\n console.error(\"[ga-logger] failed to send log:\", e);\n }\n }\n\n info(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"info\", fnName, payload, undefined, endpointName);\n }\n\n warn(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"warn\", fnName, payload, undefined, endpointName);\n }\n\n error(params: LogParams) {\n const { fnName, messageError, payload, endpointName } = params;\n return this.send(\"error\", fnName, payload, messageError, endpointName);\n }\n}\n\nexport const logger = new Logger();\n\n\n\n\n\n","import { initializeApp, getApps, getApp } from \"firebase/app\";\nimport { getFirestore } from \"firebase/firestore\";\nimport type { Firestore } from \"firebase/firestore\";\nimport type { LoggerConfig } from \"../types\";\n\nconst APP_NAME = \"ga-logger\";\nlet db: Firestore | null = null;\n\nexport const initFirebase = (config: LoggerConfig) => {\n // Cek apakah named instance \"ga-logger\" sudah ada\n const existingApp = getApps().find((app) => app.name === APP_NAME);\n\n const app =\n existingApp ??\n initializeApp(\n {\n apiKey: config.apiKey,\n authDomain: config.authDomain,\n projectId: config.projectId,\n storageBucket: config.storageBucket,\n messagingSenderId: config.messagingSenderId,\n appId: config.appId,\n },\n APP_NAME,\n );\n\n db = getFirestore(app);\n return db;\n};\n\nexport const getDb = (): Firestore => {\n if (!db) throw new Error(\"[ga-logger] Firebase not initialized. Call logger.init() first.\");\n return db;\n};\n","import { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport { logger } from \"../core/logger\";\n\ninterface Props {\n children: ReactNode;\n fallback?: ReactNode; // ← optional custom UI saat crash\n}\n\ninterface State {\n hasError: boolean;\n}\n\nexport class LoggerErrorBoundary extends Component<Props, State> {\n constructor(props: Props) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n logger.addBreadcrumb(\"error\", `React crash: ${error.message}`, {\n componentStack: info.componentStack ?? undefined,\n });\n\n logger.error({\n fnName: \"LoggerErrorBoundary\",\n messageError: error,\n payload: {\n componentStack: info.componentStack ?? undefined,\n },\n });\n }\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback ?? (\n <div style={{ padding: \"1rem\", color: \"red\" }}>\n Something went wrong.\n </div>\n );\n }\n\n return this.props.children;\n }\n}"],"mappings":"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","names":["import_firestore","app","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/logger.ts","../src/config/firebase.ts","../src/components/LoggerErrorBoundary.tsx"],"sourcesContent":["export { logger } from \"./core/logger\";\nexport { LoggerErrorBoundary } from \"./components/LoggerErrorBoundary\"; // ← baru\nexport type { LogEntry, LoggerConfig, LogLevel, LogEnv, Breadcrumb, BreadcrumbType } from \"./types\";\n","import { collection, addDoc, Timestamp } from \"firebase/firestore\";\nimport { initFirebase, getDb } from \"../config/firebase\";\nimport type { Breadcrumb, BreadcrumbType, LogEntry, LogLevel, LogParams, LoggerConfig } from \"../types\";\n\nconst sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\nconst MAX_BREADCRUMBS = 50;\n\nconst CLICK_SKIP_PATTERNS = [\n /^:r[a-z0-9]+:$/, \n /^\\[object/,\n /^#/,\n];\n\n// ✅ Sanitize PII sebelum masuk Firestore\nconst sanitizeClickText = (text: string): string => {\n return text\n .replace(/\\b[\\w.-]+@[\\w.-]+\\.\\w+\\b/g, '[email]')\n .replace(/\\b\\d{10,}\\b/g, '[number]')\n .replace(/Rp[\\s]?\\d[\\d.,]*/gi, '[amount]')\n .trim();\n};\n\nclass Logger {\n private config: LoggerConfig | null = null;\n private initialized = false;\n private breadcrumbs: Breadcrumb[] = [];\n\n init(config: LoggerConfig) {\n this.config = config;\n initFirebase(config);\n this.initialized = true;\n console.info(\"[ga-logger] initialized —\", config.env);\n }\n\n setUserId(userId: string | null) {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n this.config.userId = userId;\n }\n // ✅ NEW METHOD\n initAnonymousUser(): void {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n const STORAGE_KEY = \"ga_anon_id\";\n\n try {\n let anonId = localStorage.getItem(STORAGE_KEY);\n\n if (!anonId) {\n anonId =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n\n localStorage.setItem(STORAGE_KEY, anonId);\n }\n\n const shortId = anonId.slice(-8);\n this.setUserId(`Anonymous #${shortId}`);\n } catch (e) {\n // localStorage blocked (private mode / HTTP restriction)\n console.warn(\"[ga-logger] initAnonymousUser failed:\", e);\n this.setUserId(\"Anonymous\");\n }\n }\n\n // ✅ NEW: Add breadcrumb ke in-memory array\n addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, unknown>) {\n const breadcrumb: Breadcrumb = {\n timestamp: new Date(),\n type,\n message,\n data,\n };\n\n this.breadcrumbs.push(breadcrumb);\n\n // Jaga memory — max 50 breadcrumbs, buang yang paling lama\n if (this.breadcrumbs.length > MAX_BREADCRUMBS) {\n this.breadcrumbs.shift();\n }\n }\n\n // Global error capture — hanya aktif di non-development\n initGlobalCapture() {\n if (!this.config || this.config.env === \"DEV\") {\n console.info(\"[ga-logger] global capture skipped — DEV environment\");\n return;\n }\n\n // Capture JS runtime error\n window.onerror = (message, source, lineno, colno, error) => {\n if (this.shouldSkipGlobalError(message?.toString(), source)) return false;\n\n this.addBreadcrumb(\"error\", `window.onerror: ${message}`, {\n source,\n lineno,\n colno,\n });\n\n this.error({\n fnName: \"window.onerror\",\n messageError: error ?? new Error(String(message)),\n payload: { source, lineno, colno },\n });\n\n return false;\n };\n\n // Capture unhandled Promise rejection\n window.addEventListener(\"unhandledrejection\", (event) => {\n const message = event.reason?.message ?? String(event.reason);\n\n if (this.shouldSkipGlobalError(message)) return;\n\n const error = event.reason instanceof Error ? event.reason : new Error(message);\n\n this.addBreadcrumb(\"error\", `unhandledrejection: ${message}`);\n\n this.error({\n fnName: \"window.unhandledrejection\",\n messageError: error,\n });\n });\n\n // Capture console.error dari browser\n const originalConsoleError = console.error;\n console.error = (...args: unknown[]) => {\n originalConsoleError(...args);\n\n const message = args.map((a) => (a instanceof Error ? a.message : String(a))).join(\" \");\n\n if (this.shouldSkipGlobalError(message)) return;\n\n this.addBreadcrumb(\"error\", `console.error: ${message}`);\n\n if (this.config?.env !== \"DEV\") {\n this.error({\n fnName: \"console.error\",\n messageError: new Error(message),\n payload: { args: args.map(String) },\n });\n }\n };\n\n console.info(\"[ga-logger] global capture active\");\n }\n // Smart filter — skip noise dari CORS, network, third-party\n private lastErrorMessage: string = \"\";\n private shouldSkipGlobalError(message?: string, source?: string): boolean {\n if (!message) return true;\n\n const noisePatterns = [\n \"CORS\",\n \"cross-origin\",\n \"ERR_FAILED\",\n \"Failed to load resource\",\n \"NetworkError\",\n \"ResizeObserver loop\",\n \"Script error\",\n \"ServiceWorker\",\n \"sw.js\",\n \"dev-sw.js\",\n \"Warning:\",\n ];\n\n const isNoise = noisePatterns.some((pattern) => message.toLowerCase().includes(pattern.toLowerCase()));\n if (isNoise) return true;\n\n if (source) {\n const thirdPartyPatterns = [\"magnolia\", \"gtm.js\", \"analytics\", \"googletagmanager\", \"facebook\", \"hotjar\"];\n const isThirdParty = thirdPartyPatterns.some((pattern) => source.includes(pattern));\n if (isThirdParty) return true;\n }\n\n // ✅ NEW: dedup — skip jika message sama persis dengan error sebelumnya\n if (message === this.lastErrorMessage) return true;\n this.lastErrorMessage = message;\n\n return false;\n }\n\n private sanitizePayload(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return [key, this.sanitizePayload(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n private sanitizeForFirestore(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (typeof value === \"function\") return [key, \"[function]\"];\n if (value instanceof Date) return [key, value.toISOString()];\n if (Array.isArray(value)) {\n return [\n key,\n value.map((item) => {\n if (item instanceof Date) return item.toISOString();\n if (typeof item === \"function\") return \"[function]\"; // ← tambah\n if (item !== null && typeof item === \"object\") {\n return this.sanitizeForFirestore(item as Record<string, unknown>);\n }\n return item ?? null;\n }),\n ];\n }\n if (value !== null && typeof value === \"object\") {\n return [key, this.sanitizeForFirestore(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n // NEW: interceptFetch — auto breadcrumb, ke Firestore\n interceptFetch() {\n if (typeof window === \"undefined\") return;\n\n const originalFetch = window.fetch;\n\n const skipPatterns = [\n // Third-party domains\n \"google.com\",\n \"googletagmanager.com\",\n \"googleapis.com\",\n \"facebook.com\",\n \"hotjar.com\",\n \"analytics\",\n \"gtm\",\n \"ccm/collect\",\n \"on.aws\",\n\n // Static assets ga-web\n \"/locales/\",\n \"/sw.js\",\n \"/manifest.json\",\n ];\n\n const shouldSkipUrl = (url: string): boolean => {\n return skipPatterns.some((pattern) => url.includes(pattern));\n };\n\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n // ← Skip third-party domains\n if (shouldSkipUrl(url)) {\n return originalFetch(input, init);\n }\n\n const method = init?.method ?? \"GET\";\n const startTime = Date.now();\n\n try {\n const response = await originalFetch(input, init);\n const duration = Date.now() - startTime;\n\n this.addBreadcrumb(\"fetch\", `${method} ${url}`, {\n status: response.status,\n duration: `${duration}ms`,\n });\n\n if (response.status >= 400) {\n console.warn(`[ga-logger] HTTP ${response.status} skipped — Kibana handles this`, url);\n }\n\n return response;\n } catch (networkError) {\n const duration = Date.now() - startTime;\n const error = networkError instanceof Error ? networkError : new Error(String(networkError));\n\n this.addBreadcrumb(\"fetch\", `${method} ${url} — NETWORK ERROR`, {\n duration: `${duration}ms`,\n error: error.message,\n });\n\n if (this.config?.env !== \"DEV\") {\n this.error({\n fnName: \"interceptFetch\",\n messageError: error,\n endpointName: url,\n payload: { method, duration: `${duration}ms` },\n });\n }\n\n throw networkError;\n }\n };\n\n console.info(\"[ga-logger] fetch intercepted\");\n }\n\n interceptClick(): void {\n if (typeof window === \"undefined\") return;\n\n if ((window as any).__ga_click_intercepted) return;\n (window as any).__ga_click_intercepted = true;\n\n document.addEventListener(\n \"click\",\n (event: MouseEvent) => {\n const raw = event.target as HTMLElement;\n if (!raw) return;\n\n const target = (raw.closest('button, a, [role=\"button\"], [data-log]') as HTMLElement) ?? raw;\n\n const innerText = sanitizeClickText(target.innerText?.trim().slice(0, 50) ?? \"\");\n const id = target.id ?? \"\";\n const tagName = target.tagName?.toLowerCase() ?? \"\";\n\n if (!innerText && !id) return;\n\n const message = innerText || id;\n\n // Skip internal React/Router identifiers\n if (CLICK_SKIP_PATTERNS.some((p) => p.test(message))) return;\n\n this.addBreadcrumb(\"click\", message, {\n tagName,\n ...(id ? { id } : {}),\n ...(innerText ? { text: innerText } : {}),\n });\n },\n true,\n );\n }\n\n captureAction<T>(params: LogParams, fn: () => T | Promise<T>): Promise<T | undefined> {\n return Promise.resolve()\n .then(() => fn())\n .catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.send(\"error\", params.fnName, params.payload, err, params.endpointName);\n return undefined;\n });\n }\n\n withLogger<T extends (...args: any[]) => any>(\n fnName: string,\n fn: T,\n options?: {\n endpointName?: string;\n payload?: Record<string, unknown>;\n },\n ): T {\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n const result = fn(...args);\n\n // Handle async function\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }) as ReturnType<T>;\n }\n\n return result;\n } catch (error: unknown) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }\n }) as T;\n }\n\n private async send(\n level: LogLevel,\n message: string,\n payload: Record<string, unknown> = {},\n error?: Error,\n endpoint?: string,\n ) {\n if (!this.initialized || !this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n // DEV → console only, skip Firestore\n if (this.config.env === \"localhost\") {\n console.error(`[ga-localhost][DEV] ${message}`, { payload, error });\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date(),\n level,\n message,\n env: this.config.env,\n userId: this.config.userId ?? null,\n sessionId,\n page: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n stackTrace: error?.stack ?? null,\n payload: this.sanitizePayload(payload),\n repo: this.config.repo,\n endpoint: endpoint ?? null,\n breadcrumbs: [...this.breadcrumbs],\n };\n\n try {\n const db = getDb();\n const safeEntry = this.sanitizeForFirestore(entry as unknown as Record<string, unknown>);\n await addDoc(collection(db, \"logs\"), {\n ...safeEntry,\n timestamp: Timestamp.fromDate(entry.timestamp),\n });\n } catch (e) {\n console.error(\"[ga-logger] failed to send log:\", e);\n }\n }\n\n info(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"info\", fnName, payload, undefined, endpointName);\n }\n\n warn(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"warn\", fnName, payload, undefined, endpointName);\n }\n\n error(params: LogParams) {\n const { fnName, messageError, payload, endpointName } = params;\n return this.send(\"error\", fnName, payload, messageError, endpointName);\n }\n}\n\nexport const logger = new Logger();\n\n\n\n\n\n\n","import { initializeApp, getApps, getApp } from \"firebase/app\";\nimport { getFirestore } from \"firebase/firestore\";\nimport type { Firestore } from \"firebase/firestore\";\nimport type { LoggerConfig } from \"../types\";\n\nconst APP_NAME = \"ga-logger\";\nlet db: Firestore | null = null;\n\nexport const initFirebase = (config: LoggerConfig) => {\n // Cek apakah named instance \"ga-logger\" sudah ada\n const existingApp = getApps().find((app) => app.name === APP_NAME);\n\n const app =\n existingApp ??\n initializeApp(\n {\n apiKey: config.apiKey,\n authDomain: config.authDomain,\n projectId: config.projectId,\n storageBucket: config.storageBucket,\n messagingSenderId: config.messagingSenderId,\n appId: config.appId,\n },\n APP_NAME,\n );\n\n db = getFirestore(app);\n return db;\n};\n\nexport const getDb = (): Firestore => {\n if (!db) throw new Error(\"[ga-logger] Firebase not initialized. Call logger.init() first.\");\n return db;\n};\n","import { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport { logger } from \"../core/logger\";\n\ninterface Props {\n children: ReactNode;\n fallback?: ReactNode; // ← optional custom UI saat crash\n}\n\ninterface State {\n hasError: boolean;\n}\n\nexport class LoggerErrorBoundary extends Component<Props, State> {\n constructor(props: Props) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n logger.addBreadcrumb(\"error\", `React crash: ${error.message}`, {\n componentStack: info.componentStack ?? undefined,\n });\n\n logger.error({\n fnName: \"LoggerErrorBoundary\",\n messageError: error,\n payload: {\n componentStack: info.componentStack ?? undefined,\n },\n });\n }\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback ?? (\n <div style={{ padding: \"1rem\", color: \"red\" }}>\n Something went wrong.\n </div>\n );\n }\n\n return this.props.children;\n }\n}"],"mappings":"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","names":["import_firestore","app","db"]}
|
package/dist/index.mjs
CHANGED
|
@@ -43,6 +43,8 @@ var Logger = class {
|
|
|
43
43
|
this.config = null;
|
|
44
44
|
this.initialized = false;
|
|
45
45
|
this.breadcrumbs = [];
|
|
46
|
+
// Smart filter — skip noise dari CORS, network, third-party
|
|
47
|
+
this.lastErrorMessage = "";
|
|
46
48
|
}
|
|
47
49
|
init(config) {
|
|
48
50
|
this.config = config;
|
|
@@ -94,7 +96,7 @@ var Logger = class {
|
|
|
94
96
|
this.breadcrumbs.shift();
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
//
|
|
99
|
+
// Global error capture — hanya aktif di non-development
|
|
98
100
|
initGlobalCapture() {
|
|
99
101
|
if (!this.config || this.config.env === "DEV") {
|
|
100
102
|
console.info("[ga-logger] global capture skipped \u2014 DEV environment");
|
|
@@ -124,9 +126,22 @@ var Logger = class {
|
|
|
124
126
|
messageError: error
|
|
125
127
|
});
|
|
126
128
|
});
|
|
129
|
+
const originalConsoleError = console.error;
|
|
130
|
+
console.error = (...args) => {
|
|
131
|
+
originalConsoleError(...args);
|
|
132
|
+
const message = args.map((a) => a instanceof Error ? a.message : String(a)).join(" ");
|
|
133
|
+
if (this.shouldSkipGlobalError(message)) return;
|
|
134
|
+
this.addBreadcrumb("error", `console.error: ${message}`);
|
|
135
|
+
if (this.config?.env !== "DEV") {
|
|
136
|
+
this.error({
|
|
137
|
+
fnName: "console.error",
|
|
138
|
+
messageError: new Error(message),
|
|
139
|
+
payload: { args: args.map(String) }
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
127
143
|
console.info("[ga-logger] global capture active");
|
|
128
144
|
}
|
|
129
|
-
// ✅ NEW: Smart filter — skip noise dari CORS, network, third-party
|
|
130
145
|
shouldSkipGlobalError(message, source) {
|
|
131
146
|
if (!message) return true;
|
|
132
147
|
const noisePatterns = [
|
|
@@ -136,7 +151,11 @@ var Logger = class {
|
|
|
136
151
|
"Failed to load resource",
|
|
137
152
|
"NetworkError",
|
|
138
153
|
"ResizeObserver loop",
|
|
139
|
-
"Script error"
|
|
154
|
+
"Script error",
|
|
155
|
+
"ServiceWorker",
|
|
156
|
+
"sw.js",
|
|
157
|
+
"dev-sw.js",
|
|
158
|
+
"Warning:"
|
|
140
159
|
];
|
|
141
160
|
const isNoise = noisePatterns.some((pattern) => message.toLowerCase().includes(pattern.toLowerCase()));
|
|
142
161
|
if (isNoise) return true;
|
|
@@ -145,6 +164,8 @@ var Logger = class {
|
|
|
145
164
|
const isThirdParty = thirdPartyPatterns.some((pattern) => source.includes(pattern));
|
|
146
165
|
if (isThirdParty) return true;
|
|
147
166
|
}
|
|
167
|
+
if (message === this.lastErrorMessage) return true;
|
|
168
|
+
this.lastErrorMessage = message;
|
|
148
169
|
return false;
|
|
149
170
|
}
|
|
150
171
|
sanitizePayload(obj) {
|
|
@@ -328,7 +349,6 @@ var Logger = class {
|
|
|
328
349
|
repo: this.config.repo,
|
|
329
350
|
endpoint: endpoint ?? null,
|
|
330
351
|
breadcrumbs: [...this.breadcrumbs]
|
|
331
|
-
// ← snapshot saat error terjadi
|
|
332
352
|
};
|
|
333
353
|
try {
|
|
334
354
|
const db2 = getDb();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/logger.ts","../src/config/firebase.ts","../src/components/LoggerErrorBoundary.tsx"],"sourcesContent":["import { collection, addDoc, Timestamp } from \"firebase/firestore\";\nimport { initFirebase, getDb } from \"../config/firebase\";\nimport type { Breadcrumb, BreadcrumbType, LogEntry, LogLevel, LogParams, LoggerConfig } from \"../types\";\n\nconst sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\nconst MAX_BREADCRUMBS = 50;\n\nconst CLICK_SKIP_PATTERNS = [\n /^:r[a-z0-9]+:$/, \n /^\\[object/,\n /^#/,\n];\n\n// ✅ Sanitize PII sebelum masuk Firestore\nconst sanitizeClickText = (text: string): string => {\n return text\n .replace(/\\b[\\w.-]+@[\\w.-]+\\.\\w+\\b/g, '[email]')\n .replace(/\\b\\d{10,}\\b/g, '[number]')\n .replace(/Rp[\\s]?\\d[\\d.,]*/gi, '[amount]')\n .trim();\n};\n\nclass Logger {\n private config: LoggerConfig | null = null;\n private initialized = false;\n private breadcrumbs: Breadcrumb[] = [];\n\n init(config: LoggerConfig) {\n this.config = config;\n initFirebase(config);\n this.initialized = true;\n console.info(\"[ga-logger] initialized —\", config.env);\n }\n\n setUserId(userId: string | null) {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n this.config.userId = userId;\n }\n // ✅ NEW METHOD\n initAnonymousUser(): void {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n const STORAGE_KEY = \"ga_anon_id\";\n\n try {\n let anonId = localStorage.getItem(STORAGE_KEY);\n\n if (!anonId) {\n anonId =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n\n localStorage.setItem(STORAGE_KEY, anonId);\n }\n\n const shortId = anonId.slice(-8);\n this.setUserId(`Anonymous #${shortId}`);\n } catch (e) {\n // localStorage blocked (private mode / HTTP restriction)\n console.warn(\"[ga-logger] initAnonymousUser failed:\", e);\n this.setUserId(\"Anonymous\");\n }\n }\n\n // ✅ NEW: Add breadcrumb ke in-memory array\n addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, unknown>) {\n const breadcrumb: Breadcrumb = {\n timestamp: new Date(),\n type,\n message,\n data,\n };\n\n this.breadcrumbs.push(breadcrumb);\n\n // Jaga memory — max 50 breadcrumbs, buang yang paling lama\n if (this.breadcrumbs.length > MAX_BREADCRUMBS) {\n this.breadcrumbs.shift();\n }\n }\n\n // ✅ NEW: Global error capture — hanya aktif di non-development\n initGlobalCapture() {\n if (!this.config || this.config.env === \"DEV\") {\n console.info(\"[ga-logger] global capture skipped — DEV environment\");\n return;\n }\n\n // Capture JS runtime error\n window.onerror = (message, source, lineno, colno, error) => {\n if (this.shouldSkipGlobalError(message?.toString(), source)) return false;\n\n this.addBreadcrumb(\"error\", `window.onerror: ${message}`, {\n source,\n lineno,\n colno,\n });\n\n this.error({\n fnName: \"window.onerror\",\n messageError: error ?? new Error(String(message)),\n payload: { source, lineno, colno },\n });\n\n return false;\n };\n\n // Capture unhandled Promise rejection\n window.addEventListener(\"unhandledrejection\", (event) => {\n const message = event.reason?.message ?? String(event.reason);\n\n if (this.shouldSkipGlobalError(message)) return;\n\n const error = event.reason instanceof Error ? event.reason : new Error(message);\n\n this.addBreadcrumb(\"error\", `unhandledrejection: ${message}`);\n\n this.error({\n fnName: \"window.unhandledrejection\",\n messageError: error,\n });\n });\n\n console.info(\"[ga-logger] global capture active\");\n }\n\n // ✅ NEW: Smart filter — skip noise dari CORS, network, third-party\n private shouldSkipGlobalError(message?: string, source?: string): boolean {\n if (!message) return true;\n\n const noisePatterns = [\n \"CORS\",\n \"cross-origin\",\n \"ERR_FAILED\",\n \"Failed to load resource\",\n \"NetworkError\",\n \"ResizeObserver loop\",\n \"Script error\",\n ];\n\n const isNoise = noisePatterns.some((pattern) => message.toLowerCase().includes(pattern.toLowerCase()));\n if (isNoise) return true;\n\n // Skip error dari third-party source\n if (source) {\n const thirdPartyPatterns = [\"magnolia\", \"gtm.js\", \"analytics\", \"googletagmanager\", \"facebook\", \"hotjar\"];\n const isThirdParty = thirdPartyPatterns.some((pattern) => source.includes(pattern));\n if (isThirdParty) return true;\n }\n\n return false;\n }\n\n private sanitizePayload(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return [key, this.sanitizePayload(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n private sanitizeForFirestore(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (typeof value === \"function\") return [key, \"[function]\"];\n if (value instanceof Date) return [key, value.toISOString()];\n if (Array.isArray(value)) {\n return [\n key,\n value.map((item) => {\n if (item instanceof Date) return item.toISOString();\n if (typeof item === \"function\") return \"[function]\"; // ← tambah\n if (item !== null && typeof item === \"object\") {\n return this.sanitizeForFirestore(item as Record<string, unknown>);\n }\n return item ?? null;\n }),\n ];\n }\n if (value !== null && typeof value === \"object\") {\n return [key, this.sanitizeForFirestore(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n // NEW: interceptFetch — auto breadcrumb, ke Firestore\n interceptFetch() {\n if (typeof window === \"undefined\") return;\n\n const originalFetch = window.fetch;\n\n const skipPatterns = [\n // Third-party domains\n \"google.com\",\n \"googletagmanager.com\",\n \"googleapis.com\",\n \"facebook.com\",\n \"hotjar.com\",\n \"analytics\",\n \"gtm\",\n \"ccm/collect\",\n \"on.aws\",\n\n // Static assets ga-web\n \"/locales/\",\n \"/sw.js\",\n \"/manifest.json\",\n ];\n\n const shouldSkipUrl = (url: string): boolean => {\n return skipPatterns.some((pattern) => url.includes(pattern));\n };\n\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n // ← Skip third-party domains\n if (shouldSkipUrl(url)) {\n return originalFetch(input, init);\n }\n\n const method = init?.method ?? \"GET\";\n const startTime = Date.now();\n\n try {\n const response = await originalFetch(input, init);\n const duration = Date.now() - startTime;\n\n this.addBreadcrumb(\"fetch\", `${method} ${url}`, {\n status: response.status,\n duration: `${duration}ms`,\n });\n\n if (response.status >= 400) {\n console.warn(`[ga-logger] HTTP ${response.status} skipped — Kibana handles this`, url);\n }\n\n return response;\n } catch (networkError) {\n const duration = Date.now() - startTime;\n const error = networkError instanceof Error ? networkError : new Error(String(networkError));\n\n this.addBreadcrumb(\"fetch\", `${method} ${url} — NETWORK ERROR`, {\n duration: `${duration}ms`,\n error: error.message,\n });\n\n if (this.config?.env !== \"DEV\") {\n this.error({\n fnName: \"interceptFetch\",\n messageError: error,\n endpointName: url,\n payload: { method, duration: `${duration}ms` },\n });\n }\n\n throw networkError;\n }\n };\n\n console.info(\"[ga-logger] fetch intercepted\");\n }\n\n interceptClick(): void {\n if (typeof window === \"undefined\") return;\n\n if ((window as any).__ga_click_intercepted) return;\n (window as any).__ga_click_intercepted = true;\n\n document.addEventListener(\n \"click\",\n (event: MouseEvent) => {\n const raw = event.target as HTMLElement;\n if (!raw) return;\n\n const target = (raw.closest('button, a, [role=\"button\"], [data-log]') as HTMLElement) ?? raw;\n\n const innerText = sanitizeClickText(target.innerText?.trim().slice(0, 50) ?? \"\");\n const id = target.id ?? \"\";\n const tagName = target.tagName?.toLowerCase() ?? \"\";\n\n if (!innerText && !id) return;\n\n const message = innerText || id;\n\n // Skip internal React/Router identifiers\n if (CLICK_SKIP_PATTERNS.some((p) => p.test(message))) return;\n\n this.addBreadcrumb(\"click\", message, {\n tagName,\n ...(id ? { id } : {}),\n ...(innerText ? { text: innerText } : {}),\n });\n },\n true,\n );\n }\n\n captureAction<T>(params: LogParams, fn: () => T | Promise<T>): Promise<T | undefined> {\n return Promise.resolve()\n .then(() => fn())\n .catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.send(\"error\", params.fnName, params.payload, err, params.endpointName);\n return undefined;\n });\n }\n\n withLogger<T extends (...args: any[]) => any>(\n fnName: string,\n fn: T,\n options?: {\n endpointName?: string;\n payload?: Record<string, unknown>;\n },\n ): T {\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n const result = fn(...args);\n\n // Handle async function\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }) as ReturnType<T>;\n }\n\n return result;\n } catch (error: unknown) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }\n }) as T;\n }\n\n private async send(\n level: LogLevel,\n message: string,\n payload: Record<string, unknown> = {},\n error?: Error,\n endpoint?: string,\n ) {\n if (!this.initialized || !this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n // DEV → console only, skip Firestore\n if (this.config.env === \"localhost\") {\n console.error(`[ga-localhost][DEV] ${message}`, { payload, error });\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date(),\n level,\n message,\n env: this.config.env,\n userId: this.config.userId ?? null,\n sessionId,\n page: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n stackTrace: error?.stack ?? null,\n payload: this.sanitizePayload(payload),\n repo: this.config.repo,\n endpoint: endpoint ?? null,\n breadcrumbs: [...this.breadcrumbs], // ← snapshot saat error terjadi\n };\n\n try {\n const db = getDb();\n const safeEntry = this.sanitizeForFirestore(entry as unknown as Record<string, unknown>);\n await addDoc(collection(db, \"logs\"), {\n ...safeEntry,\n timestamp: Timestamp.fromDate(entry.timestamp),\n });\n } catch (e) {\n console.error(\"[ga-logger] failed to send log:\", e);\n }\n }\n\n info(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"info\", fnName, payload, undefined, endpointName);\n }\n\n warn(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"warn\", fnName, payload, undefined, endpointName);\n }\n\n error(params: LogParams) {\n const { fnName, messageError, payload, endpointName } = params;\n return this.send(\"error\", fnName, payload, messageError, endpointName);\n }\n}\n\nexport const logger = new Logger();\n\n\n\n\n\n","import { initializeApp, getApps, getApp } from \"firebase/app\";\nimport { getFirestore } from \"firebase/firestore\";\nimport type { Firestore } from \"firebase/firestore\";\nimport type { LoggerConfig } from \"../types\";\n\nconst APP_NAME = \"ga-logger\";\nlet db: Firestore | null = null;\n\nexport const initFirebase = (config: LoggerConfig) => {\n // Cek apakah named instance \"ga-logger\" sudah ada\n const existingApp = getApps().find((app) => app.name === APP_NAME);\n\n const app =\n existingApp ??\n initializeApp(\n {\n apiKey: config.apiKey,\n authDomain: config.authDomain,\n projectId: config.projectId,\n storageBucket: config.storageBucket,\n messagingSenderId: config.messagingSenderId,\n appId: config.appId,\n },\n APP_NAME,\n );\n\n db = getFirestore(app);\n return db;\n};\n\nexport const getDb = (): Firestore => {\n if (!db) throw new Error(\"[ga-logger] Firebase not initialized. Call logger.init() first.\");\n return db;\n};\n","import { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport { logger } from \"../core/logger\";\n\ninterface Props {\n children: ReactNode;\n fallback?: ReactNode; // ← optional custom UI saat crash\n}\n\ninterface State {\n hasError: boolean;\n}\n\nexport class LoggerErrorBoundary extends Component<Props, State> {\n constructor(props: Props) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n logger.addBreadcrumb(\"error\", `React crash: ${error.message}`, {\n componentStack: info.componentStack ?? undefined,\n });\n\n logger.error({\n fnName: \"LoggerErrorBoundary\",\n messageError: error,\n payload: {\n componentStack: info.componentStack ?? undefined,\n },\n });\n }\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback ?? (\n <div style={{ padding: \"1rem\", color: \"red\" }}>\n Something went wrong.\n </div>\n );\n }\n\n return this.props.children;\n }\n}"],"mappings":"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","names":["app","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/logger.ts","../src/config/firebase.ts","../src/components/LoggerErrorBoundary.tsx"],"sourcesContent":["import { collection, addDoc, Timestamp } from \"firebase/firestore\";\nimport { initFirebase, getDb } from \"../config/firebase\";\nimport type { Breadcrumb, BreadcrumbType, LogEntry, LogLevel, LogParams, LoggerConfig } from \"../types\";\n\nconst sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\nconst MAX_BREADCRUMBS = 50;\n\nconst CLICK_SKIP_PATTERNS = [\n /^:r[a-z0-9]+:$/, \n /^\\[object/,\n /^#/,\n];\n\n// ✅ Sanitize PII sebelum masuk Firestore\nconst sanitizeClickText = (text: string): string => {\n return text\n .replace(/\\b[\\w.-]+@[\\w.-]+\\.\\w+\\b/g, '[email]')\n .replace(/\\b\\d{10,}\\b/g, '[number]')\n .replace(/Rp[\\s]?\\d[\\d.,]*/gi, '[amount]')\n .trim();\n};\n\nclass Logger {\n private config: LoggerConfig | null = null;\n private initialized = false;\n private breadcrumbs: Breadcrumb[] = [];\n\n init(config: LoggerConfig) {\n this.config = config;\n initFirebase(config);\n this.initialized = true;\n console.info(\"[ga-logger] initialized —\", config.env);\n }\n\n setUserId(userId: string | null) {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n this.config.userId = userId;\n }\n // ✅ NEW METHOD\n initAnonymousUser(): void {\n if (!this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n const STORAGE_KEY = \"ga_anon_id\";\n\n try {\n let anonId = localStorage.getItem(STORAGE_KEY);\n\n if (!anonId) {\n anonId =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n\n localStorage.setItem(STORAGE_KEY, anonId);\n }\n\n const shortId = anonId.slice(-8);\n this.setUserId(`Anonymous #${shortId}`);\n } catch (e) {\n // localStorage blocked (private mode / HTTP restriction)\n console.warn(\"[ga-logger] initAnonymousUser failed:\", e);\n this.setUserId(\"Anonymous\");\n }\n }\n\n // ✅ NEW: Add breadcrumb ke in-memory array\n addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, unknown>) {\n const breadcrumb: Breadcrumb = {\n timestamp: new Date(),\n type,\n message,\n data,\n };\n\n this.breadcrumbs.push(breadcrumb);\n\n // Jaga memory — max 50 breadcrumbs, buang yang paling lama\n if (this.breadcrumbs.length > MAX_BREADCRUMBS) {\n this.breadcrumbs.shift();\n }\n }\n\n // Global error capture — hanya aktif di non-development\n initGlobalCapture() {\n if (!this.config || this.config.env === \"DEV\") {\n console.info(\"[ga-logger] global capture skipped — DEV environment\");\n return;\n }\n\n // Capture JS runtime error\n window.onerror = (message, source, lineno, colno, error) => {\n if (this.shouldSkipGlobalError(message?.toString(), source)) return false;\n\n this.addBreadcrumb(\"error\", `window.onerror: ${message}`, {\n source,\n lineno,\n colno,\n });\n\n this.error({\n fnName: \"window.onerror\",\n messageError: error ?? new Error(String(message)),\n payload: { source, lineno, colno },\n });\n\n return false;\n };\n\n // Capture unhandled Promise rejection\n window.addEventListener(\"unhandledrejection\", (event) => {\n const message = event.reason?.message ?? String(event.reason);\n\n if (this.shouldSkipGlobalError(message)) return;\n\n const error = event.reason instanceof Error ? event.reason : new Error(message);\n\n this.addBreadcrumb(\"error\", `unhandledrejection: ${message}`);\n\n this.error({\n fnName: \"window.unhandledrejection\",\n messageError: error,\n });\n });\n\n // Capture console.error dari browser\n const originalConsoleError = console.error;\n console.error = (...args: unknown[]) => {\n originalConsoleError(...args);\n\n const message = args.map((a) => (a instanceof Error ? a.message : String(a))).join(\" \");\n\n if (this.shouldSkipGlobalError(message)) return;\n\n this.addBreadcrumb(\"error\", `console.error: ${message}`);\n\n if (this.config?.env !== \"DEV\") {\n this.error({\n fnName: \"console.error\",\n messageError: new Error(message),\n payload: { args: args.map(String) },\n });\n }\n };\n\n console.info(\"[ga-logger] global capture active\");\n }\n // Smart filter — skip noise dari CORS, network, third-party\n private lastErrorMessage: string = \"\";\n private shouldSkipGlobalError(message?: string, source?: string): boolean {\n if (!message) return true;\n\n const noisePatterns = [\n \"CORS\",\n \"cross-origin\",\n \"ERR_FAILED\",\n \"Failed to load resource\",\n \"NetworkError\",\n \"ResizeObserver loop\",\n \"Script error\",\n \"ServiceWorker\",\n \"sw.js\",\n \"dev-sw.js\",\n \"Warning:\",\n ];\n\n const isNoise = noisePatterns.some((pattern) => message.toLowerCase().includes(pattern.toLowerCase()));\n if (isNoise) return true;\n\n if (source) {\n const thirdPartyPatterns = [\"magnolia\", \"gtm.js\", \"analytics\", \"googletagmanager\", \"facebook\", \"hotjar\"];\n const isThirdParty = thirdPartyPatterns.some((pattern) => source.includes(pattern));\n if (isThirdParty) return true;\n }\n\n // ✅ NEW: dedup — skip jika message sama persis dengan error sebelumnya\n if (message === this.lastErrorMessage) return true;\n this.lastErrorMessage = message;\n\n return false;\n }\n\n private sanitizePayload(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return [key, this.sanitizePayload(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n private sanitizeForFirestore(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (value === undefined) return [key, null];\n if (typeof value === \"function\") return [key, \"[function]\"];\n if (value instanceof Date) return [key, value.toISOString()];\n if (Array.isArray(value)) {\n return [\n key,\n value.map((item) => {\n if (item instanceof Date) return item.toISOString();\n if (typeof item === \"function\") return \"[function]\"; // ← tambah\n if (item !== null && typeof item === \"object\") {\n return this.sanitizeForFirestore(item as Record<string, unknown>);\n }\n return item ?? null;\n }),\n ];\n }\n if (value !== null && typeof value === \"object\") {\n return [key, this.sanitizeForFirestore(value as Record<string, unknown>)];\n }\n return [key, value];\n }),\n );\n }\n\n // NEW: interceptFetch — auto breadcrumb, ke Firestore\n interceptFetch() {\n if (typeof window === \"undefined\") return;\n\n const originalFetch = window.fetch;\n\n const skipPatterns = [\n // Third-party domains\n \"google.com\",\n \"googletagmanager.com\",\n \"googleapis.com\",\n \"facebook.com\",\n \"hotjar.com\",\n \"analytics\",\n \"gtm\",\n \"ccm/collect\",\n \"on.aws\",\n\n // Static assets ga-web\n \"/locales/\",\n \"/sw.js\",\n \"/manifest.json\",\n ];\n\n const shouldSkipUrl = (url: string): boolean => {\n return skipPatterns.some((pattern) => url.includes(pattern));\n };\n\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n // ← Skip third-party domains\n if (shouldSkipUrl(url)) {\n return originalFetch(input, init);\n }\n\n const method = init?.method ?? \"GET\";\n const startTime = Date.now();\n\n try {\n const response = await originalFetch(input, init);\n const duration = Date.now() - startTime;\n\n this.addBreadcrumb(\"fetch\", `${method} ${url}`, {\n status: response.status,\n duration: `${duration}ms`,\n });\n\n if (response.status >= 400) {\n console.warn(`[ga-logger] HTTP ${response.status} skipped — Kibana handles this`, url);\n }\n\n return response;\n } catch (networkError) {\n const duration = Date.now() - startTime;\n const error = networkError instanceof Error ? networkError : new Error(String(networkError));\n\n this.addBreadcrumb(\"fetch\", `${method} ${url} — NETWORK ERROR`, {\n duration: `${duration}ms`,\n error: error.message,\n });\n\n if (this.config?.env !== \"DEV\") {\n this.error({\n fnName: \"interceptFetch\",\n messageError: error,\n endpointName: url,\n payload: { method, duration: `${duration}ms` },\n });\n }\n\n throw networkError;\n }\n };\n\n console.info(\"[ga-logger] fetch intercepted\");\n }\n\n interceptClick(): void {\n if (typeof window === \"undefined\") return;\n\n if ((window as any).__ga_click_intercepted) return;\n (window as any).__ga_click_intercepted = true;\n\n document.addEventListener(\n \"click\",\n (event: MouseEvent) => {\n const raw = event.target as HTMLElement;\n if (!raw) return;\n\n const target = (raw.closest('button, a, [role=\"button\"], [data-log]') as HTMLElement) ?? raw;\n\n const innerText = sanitizeClickText(target.innerText?.trim().slice(0, 50) ?? \"\");\n const id = target.id ?? \"\";\n const tagName = target.tagName?.toLowerCase() ?? \"\";\n\n if (!innerText && !id) return;\n\n const message = innerText || id;\n\n // Skip internal React/Router identifiers\n if (CLICK_SKIP_PATTERNS.some((p) => p.test(message))) return;\n\n this.addBreadcrumb(\"click\", message, {\n tagName,\n ...(id ? { id } : {}),\n ...(innerText ? { text: innerText } : {}),\n });\n },\n true,\n );\n }\n\n captureAction<T>(params: LogParams, fn: () => T | Promise<T>): Promise<T | undefined> {\n return Promise.resolve()\n .then(() => fn())\n .catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.send(\"error\", params.fnName, params.payload, err, params.endpointName);\n return undefined;\n });\n }\n\n withLogger<T extends (...args: any[]) => any>(\n fnName: string,\n fn: T,\n options?: {\n endpointName?: string;\n payload?: Record<string, unknown>;\n },\n ): T {\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n const result = fn(...args);\n\n // Handle async function\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }) as ReturnType<T>;\n }\n\n return result;\n } catch (error: unknown) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.error({\n fnName,\n messageError: err,\n endpointName: options?.endpointName,\n payload: options?.payload,\n });\n return undefined;\n }\n }) as T;\n }\n\n private async send(\n level: LogLevel,\n message: string,\n payload: Record<string, unknown> = {},\n error?: Error,\n endpoint?: string,\n ) {\n if (!this.initialized || !this.config) {\n console.warn(\"[ga-logger] not initialized. Call logger.init() first.\");\n return;\n }\n\n // DEV → console only, skip Firestore\n if (this.config.env === \"localhost\") {\n console.error(`[ga-localhost][DEV] ${message}`, { payload, error });\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date(),\n level,\n message,\n env: this.config.env,\n userId: this.config.userId ?? null,\n sessionId,\n page: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n stackTrace: error?.stack ?? null,\n payload: this.sanitizePayload(payload),\n repo: this.config.repo,\n endpoint: endpoint ?? null,\n breadcrumbs: [...this.breadcrumbs],\n };\n\n try {\n const db = getDb();\n const safeEntry = this.sanitizeForFirestore(entry as unknown as Record<string, unknown>);\n await addDoc(collection(db, \"logs\"), {\n ...safeEntry,\n timestamp: Timestamp.fromDate(entry.timestamp),\n });\n } catch (e) {\n console.error(\"[ga-logger] failed to send log:\", e);\n }\n }\n\n info(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"info\", fnName, payload, undefined, endpointName);\n }\n\n warn(params: LogParams) {\n const { fnName, payload, endpointName } = params;\n return this.send(\"warn\", fnName, payload, undefined, endpointName);\n }\n\n error(params: LogParams) {\n const { fnName, messageError, payload, endpointName } = params;\n return this.send(\"error\", fnName, payload, messageError, endpointName);\n }\n}\n\nexport const logger = new Logger();\n\n\n\n\n\n\n","import { initializeApp, getApps, getApp } from \"firebase/app\";\nimport { getFirestore } from \"firebase/firestore\";\nimport type { Firestore } from \"firebase/firestore\";\nimport type { LoggerConfig } from \"../types\";\n\nconst APP_NAME = \"ga-logger\";\nlet db: Firestore | null = null;\n\nexport const initFirebase = (config: LoggerConfig) => {\n // Cek apakah named instance \"ga-logger\" sudah ada\n const existingApp = getApps().find((app) => app.name === APP_NAME);\n\n const app =\n existingApp ??\n initializeApp(\n {\n apiKey: config.apiKey,\n authDomain: config.authDomain,\n projectId: config.projectId,\n storageBucket: config.storageBucket,\n messagingSenderId: config.messagingSenderId,\n appId: config.appId,\n },\n APP_NAME,\n );\n\n db = getFirestore(app);\n return db;\n};\n\nexport const getDb = (): Firestore => {\n if (!db) throw new Error(\"[ga-logger] Firebase not initialized. Call logger.init() first.\");\n return db;\n};\n","import { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport { logger } from \"../core/logger\";\n\ninterface Props {\n children: ReactNode;\n fallback?: ReactNode; // ← optional custom UI saat crash\n}\n\ninterface State {\n hasError: boolean;\n}\n\nexport class LoggerErrorBoundary extends Component<Props, State> {\n constructor(props: Props) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n logger.addBreadcrumb(\"error\", `React crash: ${error.message}`, {\n componentStack: info.componentStack ?? undefined,\n });\n\n logger.error({\n fnName: \"LoggerErrorBoundary\",\n messageError: error,\n payload: {\n componentStack: info.componentStack ?? undefined,\n },\n });\n }\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback ?? (\n <div style={{ padding: \"1rem\", color: \"red\" }}>\n Something went wrong.\n </div>\n );\n }\n\n return this.props.children;\n }\n}"],"mappings":"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","names":["app","db"]}
|