@ga-components/logger 1.0.19 → 1.0.21
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 +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +49 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +49 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { Component, ReactNode, ErrorInfo } from 'react';
|
|
3
3
|
|
|
4
4
|
type LogLevel = "info" | "warn" | "error";
|
|
5
|
-
type LogEnv = "PROD" | "DEV";
|
|
5
|
+
type LogEnv = "PROD" | "DEV" | "localhost";
|
|
6
6
|
type BreadcrumbType = "navigation" | "click" | "fetch" | "auth" | "error" | "manual";
|
|
7
7
|
interface Breadcrumb {
|
|
8
8
|
timestamp: Date;
|
|
@@ -56,6 +56,10 @@ declare class Logger {
|
|
|
56
56
|
private sanitizeForFirestore;
|
|
57
57
|
interceptFetch(): void;
|
|
58
58
|
captureAction<T>(params: LogParams, fn: () => T | Promise<T>): Promise<T | undefined>;
|
|
59
|
+
withLogger<T extends (...args: any[]) => any>(fnName: string, fn: T, options?: {
|
|
60
|
+
endpointName?: string;
|
|
61
|
+
payload?: Record<string, unknown>;
|
|
62
|
+
}): T;
|
|
59
63
|
private send;
|
|
60
64
|
info(params: LogParams): Promise<void>;
|
|
61
65
|
warn(params: LogParams): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { Component, ReactNode, ErrorInfo } from 'react';
|
|
3
3
|
|
|
4
4
|
type LogLevel = "info" | "warn" | "error";
|
|
5
|
-
type LogEnv = "PROD" | "DEV";
|
|
5
|
+
type LogEnv = "PROD" | "DEV" | "localhost";
|
|
6
6
|
type BreadcrumbType = "navigation" | "click" | "fetch" | "auth" | "error" | "manual";
|
|
7
7
|
interface Breadcrumb {
|
|
8
8
|
timestamp: Date;
|
|
@@ -56,6 +56,10 @@ declare class Logger {
|
|
|
56
56
|
private sanitizeForFirestore;
|
|
57
57
|
interceptFetch(): void;
|
|
58
58
|
captureAction<T>(params: LogParams, fn: () => T | Promise<T>): Promise<T | undefined>;
|
|
59
|
+
withLogger<T extends (...args: any[]) => any>(fnName: string, fn: T, options?: {
|
|
60
|
+
endpointName?: string;
|
|
61
|
+
payload?: Record<string, unknown>;
|
|
62
|
+
}): T;
|
|
59
63
|
private send;
|
|
60
64
|
info(params: LogParams): Promise<void>;
|
|
61
65
|
warn(params: LogParams): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -179,30 +179,28 @@ var Logger = class {
|
|
|
179
179
|
})
|
|
180
180
|
);
|
|
181
181
|
}
|
|
182
|
-
// NEW: interceptFetch — auto breadcrumb,
|
|
182
|
+
// NEW: interceptFetch — auto breadcrumb, ke Firestore
|
|
183
183
|
interceptFetch() {
|
|
184
184
|
if (typeof window === "undefined") return;
|
|
185
185
|
const originalFetch = window.fetch;
|
|
186
|
+
const skipPatterns = [
|
|
187
|
+
// Third-party domains
|
|
188
|
+
"google.com",
|
|
189
|
+
"googletagmanager.com",
|
|
190
|
+
"googleapis.com",
|
|
191
|
+
"facebook.com",
|
|
192
|
+
"hotjar.com",
|
|
193
|
+
"analytics",
|
|
194
|
+
"gtm",
|
|
195
|
+
"ccm/collect",
|
|
196
|
+
"on.aws",
|
|
197
|
+
// Static assets ga-web
|
|
198
|
+
"/locales/",
|
|
199
|
+
"/sw.js",
|
|
200
|
+
"/manifest.json"
|
|
201
|
+
];
|
|
186
202
|
const shouldSkipUrl = (url) => {
|
|
187
|
-
|
|
188
|
-
const blacklist = [
|
|
189
|
-
"google.com",
|
|
190
|
-
"googletagmanager.com",
|
|
191
|
-
"googleapis.com",
|
|
192
|
-
"facebook.com",
|
|
193
|
-
"hotjar.com",
|
|
194
|
-
"on.aws",
|
|
195
|
-
"ccm/collect",
|
|
196
|
-
"/locales/",
|
|
197
|
-
"/sw.js",
|
|
198
|
-
"/manifest.json"
|
|
199
|
-
];
|
|
200
|
-
const isBlacklisted = blacklist.some((pattern) => url.includes(pattern));
|
|
201
|
-
if (isBlacklisted) return true;
|
|
202
|
-
if (allowedDomains.length > 0) {
|
|
203
|
-
return !allowedDomains.some((pattern) => url.includes(pattern));
|
|
204
|
-
}
|
|
205
|
-
return false;
|
|
203
|
+
return skipPatterns.some((pattern) => url.includes(pattern));
|
|
206
204
|
};
|
|
207
205
|
window.fetch = async (input, init) => {
|
|
208
206
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
@@ -249,13 +247,42 @@ var Logger = class {
|
|
|
249
247
|
return void 0;
|
|
250
248
|
});
|
|
251
249
|
}
|
|
250
|
+
withLogger(fnName, fn, options) {
|
|
251
|
+
return ((...args) => {
|
|
252
|
+
try {
|
|
253
|
+
const result = fn(...args);
|
|
254
|
+
if (result instanceof Promise) {
|
|
255
|
+
return result.catch((error) => {
|
|
256
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
257
|
+
this.error({
|
|
258
|
+
fnName,
|
|
259
|
+
messageError: err,
|
|
260
|
+
endpointName: options?.endpointName,
|
|
261
|
+
payload: options?.payload
|
|
262
|
+
});
|
|
263
|
+
return void 0;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
269
|
+
this.error({
|
|
270
|
+
fnName,
|
|
271
|
+
messageError: err,
|
|
272
|
+
endpointName: options?.endpointName,
|
|
273
|
+
payload: options?.payload
|
|
274
|
+
});
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
252
279
|
async send(level, message, payload = {}, error, endpoint) {
|
|
253
280
|
if (!this.initialized || !this.config) {
|
|
254
281
|
console.warn("[ga-logger] not initialized. Call logger.init() first.");
|
|
255
282
|
return;
|
|
256
283
|
}
|
|
257
|
-
if (this.config.env === "
|
|
258
|
-
console.error(`[ga-
|
|
284
|
+
if (this.config.env === "localhost") {
|
|
285
|
+
console.error(`[ga-localhost][DEV] ${message}`, { payload, error });
|
|
259
286
|
return;
|
|
260
287
|
}
|
|
261
288
|
const entry = {
|
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\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\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,\n interceptFetch() {\n if (typeof window === \"undefined\") return;\n\n const originalFetch = window.fetch;\n\n const shouldSkipUrl = (url: string): boolean => {\n const allowedDomains = this.config?.allowedDomains ?? [];\n\n // Blacklist — selalu skip third-party\n const blacklist = [\n \"google.com\",\n \"googletagmanager.com\",\n \"googleapis.com\",\n \"facebook.com\",\n \"hotjar.com\",\n \"on.aws\",\n \"ccm/collect\",\n \"/locales/\",\n \"/sw.js\",\n \"/manifest.json\",\n ];\n\n const isBlacklisted = blacklist.some((pattern) => url.includes(pattern));\n if (isBlacklisted) return true;\n\n // Whitelist — kalau allowedDomains di-set, hanya track URL dari domain itu\n if (allowedDomains.length > 0) {\n return !allowedDomains.some((pattern) => url.includes(pattern));\n }\n\n return false;\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 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 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 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 === \"DEV\") {\n console.error(`[ga-logger][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\n\n\n\n\n\n\n\n\n\nexport const logger = new Logger()","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\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\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 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","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
|
@@ -152,30 +152,28 @@ var Logger = class {
|
|
|
152
152
|
})
|
|
153
153
|
);
|
|
154
154
|
}
|
|
155
|
-
// NEW: interceptFetch — auto breadcrumb,
|
|
155
|
+
// NEW: interceptFetch — auto breadcrumb, ke Firestore
|
|
156
156
|
interceptFetch() {
|
|
157
157
|
if (typeof window === "undefined") return;
|
|
158
158
|
const originalFetch = window.fetch;
|
|
159
|
+
const skipPatterns = [
|
|
160
|
+
// Third-party domains
|
|
161
|
+
"google.com",
|
|
162
|
+
"googletagmanager.com",
|
|
163
|
+
"googleapis.com",
|
|
164
|
+
"facebook.com",
|
|
165
|
+
"hotjar.com",
|
|
166
|
+
"analytics",
|
|
167
|
+
"gtm",
|
|
168
|
+
"ccm/collect",
|
|
169
|
+
"on.aws",
|
|
170
|
+
// Static assets ga-web
|
|
171
|
+
"/locales/",
|
|
172
|
+
"/sw.js",
|
|
173
|
+
"/manifest.json"
|
|
174
|
+
];
|
|
159
175
|
const shouldSkipUrl = (url) => {
|
|
160
|
-
|
|
161
|
-
const blacklist = [
|
|
162
|
-
"google.com",
|
|
163
|
-
"googletagmanager.com",
|
|
164
|
-
"googleapis.com",
|
|
165
|
-
"facebook.com",
|
|
166
|
-
"hotjar.com",
|
|
167
|
-
"on.aws",
|
|
168
|
-
"ccm/collect",
|
|
169
|
-
"/locales/",
|
|
170
|
-
"/sw.js",
|
|
171
|
-
"/manifest.json"
|
|
172
|
-
];
|
|
173
|
-
const isBlacklisted = blacklist.some((pattern) => url.includes(pattern));
|
|
174
|
-
if (isBlacklisted) return true;
|
|
175
|
-
if (allowedDomains.length > 0) {
|
|
176
|
-
return !allowedDomains.some((pattern) => url.includes(pattern));
|
|
177
|
-
}
|
|
178
|
-
return false;
|
|
176
|
+
return skipPatterns.some((pattern) => url.includes(pattern));
|
|
179
177
|
};
|
|
180
178
|
window.fetch = async (input, init) => {
|
|
181
179
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
@@ -222,13 +220,42 @@ var Logger = class {
|
|
|
222
220
|
return void 0;
|
|
223
221
|
});
|
|
224
222
|
}
|
|
223
|
+
withLogger(fnName, fn, options) {
|
|
224
|
+
return ((...args) => {
|
|
225
|
+
try {
|
|
226
|
+
const result = fn(...args);
|
|
227
|
+
if (result instanceof Promise) {
|
|
228
|
+
return result.catch((error) => {
|
|
229
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
230
|
+
this.error({
|
|
231
|
+
fnName,
|
|
232
|
+
messageError: err,
|
|
233
|
+
endpointName: options?.endpointName,
|
|
234
|
+
payload: options?.payload
|
|
235
|
+
});
|
|
236
|
+
return void 0;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
242
|
+
this.error({
|
|
243
|
+
fnName,
|
|
244
|
+
messageError: err,
|
|
245
|
+
endpointName: options?.endpointName,
|
|
246
|
+
payload: options?.payload
|
|
247
|
+
});
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
225
252
|
async send(level, message, payload = {}, error, endpoint) {
|
|
226
253
|
if (!this.initialized || !this.config) {
|
|
227
254
|
console.warn("[ga-logger] not initialized. Call logger.init() first.");
|
|
228
255
|
return;
|
|
229
256
|
}
|
|
230
|
-
if (this.config.env === "
|
|
231
|
-
console.error(`[ga-
|
|
257
|
+
if (this.config.env === "localhost") {
|
|
258
|
+
console.error(`[ga-localhost][DEV] ${message}`, { payload, error });
|
|
232
259
|
return;
|
|
233
260
|
}
|
|
234
261
|
const entry = {
|
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\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\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,\n interceptFetch() {\n if (typeof window === \"undefined\") return;\n\n const originalFetch = window.fetch;\n\n const shouldSkipUrl = (url: string): boolean => {\n const allowedDomains = this.config?.allowedDomains ?? [];\n\n // Blacklist — selalu skip third-party\n const blacklist = [\n \"google.com\",\n \"googletagmanager.com\",\n \"googleapis.com\",\n \"facebook.com\",\n \"hotjar.com\",\n \"on.aws\",\n \"ccm/collect\",\n \"/locales/\",\n \"/sw.js\",\n \"/manifest.json\",\n ];\n\n const isBlacklisted = blacklist.some((pattern) => url.includes(pattern));\n if (isBlacklisted) return true;\n\n // Whitelist — kalau allowedDomains di-set, hanya track URL dari domain itu\n if (allowedDomains.length > 0) {\n return !allowedDomains.some((pattern) => url.includes(pattern));\n }\n\n return false;\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 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 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 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 === \"DEV\") {\n console.error(`[ga-logger][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\n\n\n\n\n\n\n\n\n\nexport const logger = new Logger()","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\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\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 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","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"]}
|