fix: slow get recipe memo
- fix: cannot adb command from blocking suspicious cmd - change: logging to logtape lib - change: adb payload handler format to send payload size as header for exact match payload Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
parent
aa6414b1cc
commit
3cda8aa60b
12 changed files with 494 additions and 174 deletions
|
|
@ -1,91 +1,216 @@
|
|||
/**
|
||||
* Lightweight logger that suppresses output in production.
|
||||
* Structured logger powered by LogTape (@logtape/logtape).
|
||||
*
|
||||
* In development (npm run dev): logs are shown with a prefix tag.
|
||||
* In production (npm run build): all console output is suppressed
|
||||
* so logs are never exposed to the browser inspector.
|
||||
* Two tiers of logging:
|
||||
* - **logger** — general-purpose logging (suppressed in production for security)
|
||||
* - Dev: `trace`, `debug`, `info`, `warn`, `error` (configurable via PUBLIC_APP_LOG_LEVEL)
|
||||
* - Prod: `info` by default (or what PUBLIC_APP_LOG_LEVEL says)
|
||||
* - **inspector** — error-only logging, ALWAYS active (never suppressed).
|
||||
* Use for security-critical events that must appear in the browser
|
||||
* developer console (e.g. auth failures, API errors).
|
||||
*
|
||||
* Usage:
|
||||
* import { logger } from '$lib/core/utils/logger';
|
||||
* logger.info('hello', someVar);
|
||||
* logger.error('failed', err);
|
||||
* API (100 % backward compatible):
|
||||
* logger.info('message', arg1, …)
|
||||
* logger.error('Failed:', err)
|
||||
* inspector.error('auth failure', uid) // always visible
|
||||
*/
|
||||
import { configureSync, getConsoleSink, withFilter, getLogger } from '@logtape/logtape';
|
||||
|
||||
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Environment helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LOG_LEVEL_RANK: Record<LogLevel, number> = {
|
||||
trace: -1,
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3
|
||||
const IS_DEV = import.meta.env.DEV;
|
||||
const ENV_LOG_LEVEL = (import.meta.env.PUBLIC_APP_LOG_LEVEL as string) || (IS_DEV ? 'trace' : 'info');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LogTape configuration (singleton guard for Vite HMR / config reload)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let _configured = false;
|
||||
function ensureConfigured() {
|
||||
if (_configured) return;
|
||||
_configured = true;
|
||||
configureSync({
|
||||
sinks: {
|
||||
console: getConsoleSink(),
|
||||
errorsOnly: withFilter(getConsoleSink(), 'error'),
|
||||
},
|
||||
loggers: [
|
||||
{
|
||||
category: ['supra-app'],
|
||||
sinks: ['console'],
|
||||
lowestLevel: ENV_LOG_LEVEL as 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal',
|
||||
},
|
||||
{
|
||||
category: ['supra-app', 'inspector'],
|
||||
sinks: ['errorsOnly'],
|
||||
lowestLevel: 'error',
|
||||
},
|
||||
{
|
||||
category: ['logtape', 'meta'],
|
||||
sinks: ['console'],
|
||||
lowestLevel: 'error',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal LogTape loggers (lazy-init)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getBaseLogger() {
|
||||
ensureConfigured();
|
||||
return getLogger(['supra-app']);
|
||||
}
|
||||
|
||||
function getInspectorLogger() {
|
||||
ensureConfigured();
|
||||
return getLogger(['supra-app', 'inspector']);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isRecord(v: unknown): v is Record<string, unknown> {
|
||||
return typeof v === 'object' && v !== null && !(v instanceof Error) && !Array.isArray(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert variadic args into a flat message string + optional extras.
|
||||
*
|
||||
* Patterns handled:
|
||||
* logger.info('text') → message, no props
|
||||
* logger.info('text', obj) → message, obj as props if obj is a plain Record
|
||||
* logger.info('text', error) → error overload
|
||||
* logger.info('text', arg1, arg2, …) → concatenated message
|
||||
*/
|
||||
function normalize(
|
||||
args: unknown[],
|
||||
): { message: string; error?: Error; properties?: Record<string, unknown> } {
|
||||
if (args.length === 0) return { message: '' };
|
||||
|
||||
const first = args[0];
|
||||
if (args.length === 1) {
|
||||
if (first instanceof Error) return { message: first.message, error: first };
|
||||
return { message: String(first) };
|
||||
}
|
||||
|
||||
if (args.length === 2 && isRecord(args[1])) {
|
||||
return { message: String(first), properties: args[1] };
|
||||
}
|
||||
|
||||
// error overload: logger.warn(msg, error) / logger.error('msg', err)
|
||||
if (args.length === 2 && args[1] instanceof Error) {
|
||||
return { message: String(first), error: args[1] as Error };
|
||||
}
|
||||
|
||||
// multi-arg: concatenate
|
||||
return {
|
||||
message: args.map((a) => (typeof a === 'object' ? safeStringify(a) : String(a))).join(' '),
|
||||
};
|
||||
}
|
||||
|
||||
function safeStringify(v: unknown): string {
|
||||
if (v instanceof Error) return `${v.name}: ${v.message}`;
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch {
|
||||
return String(v);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const logger = {
|
||||
trace(...args: unknown[]) {
|
||||
const { message } = normalize(args);
|
||||
getBaseLogger().trace(message);
|
||||
},
|
||||
|
||||
debug(...args: unknown[]) {
|
||||
const { message, properties } = normalize(args);
|
||||
if (properties) {
|
||||
getBaseLogger().debug(message, properties);
|
||||
} else {
|
||||
getBaseLogger().debug(message);
|
||||
}
|
||||
},
|
||||
|
||||
info(...args: unknown[]) {
|
||||
const { message, properties } = normalize(args);
|
||||
if (properties) {
|
||||
getBaseLogger().info(message, properties);
|
||||
} else {
|
||||
getBaseLogger().info(message);
|
||||
}
|
||||
},
|
||||
|
||||
warn(...args: unknown[]) {
|
||||
const { message, error, properties } = normalize(args);
|
||||
if (properties) {
|
||||
getBaseLogger().warn(message, properties);
|
||||
} else if (error) {
|
||||
getBaseLogger().warn(message, error);
|
||||
} else {
|
||||
getBaseLogger().warn(message);
|
||||
}
|
||||
},
|
||||
|
||||
error(...args: unknown[]) {
|
||||
const { message, error, properties } = normalize(args);
|
||||
if (error && properties) {
|
||||
getBaseLogger().error(error, properties);
|
||||
} else if (error) {
|
||||
getBaseLogger().error(error);
|
||||
} else if (properties) {
|
||||
getBaseLogger().error(message, properties);
|
||||
} else {
|
||||
getBaseLogger().error(message);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const ENV_LOG_LEVEL: LogLevel =
|
||||
(import.meta.env.PUBLIC_APP_LOG_LEVEL as LogLevel) || 'info';
|
||||
const IS_DEV = import.meta.env.DEV;
|
||||
|
||||
function shouldLog(level: LogLevel): boolean {
|
||||
return IS_DEV && LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[ENV_LOG_LEVEL];
|
||||
}
|
||||
|
||||
function formatArgs(level: LogLevel, args: any[]): any[] {
|
||||
return [`[${level.toUpperCase()}]`, ...args];
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Extract caller location (file:line) from a stack trace.
|
||||
* Stack frame format: " at method (file:///path/file.ts:10:20)"
|
||||
* The 3rd frame (index 2) is the caller of the method that created the Error.
|
||||
* Inspector logger — always active, error-only, never suppressed.
|
||||
* Messages are written via the same LogTape infrastructure but on a
|
||||
* separate category (supra-app → inspector) whose sink is hard-filtered to
|
||||
* `error` / `fatal` so security-sensitive events are always visible.
|
||||
*/
|
||||
function getCallerLocation(): string {
|
||||
try {
|
||||
const stack = new Error().stack?.split('\n');
|
||||
if (!stack || stack.length < 3) return 'unknown';
|
||||
// stack[0] = "Error", stack[1] = " at getCallerLocation (...)",
|
||||
// stack[2] = " at Logger.trace (...)" — skip both
|
||||
const caller = stack[2]?.trim();
|
||||
// Extract path from common formats:
|
||||
// "at method (file:///path/file.ts:10:20)" or "at file:///path/file.ts:10:20"
|
||||
const match = caller.match(/(?:\(|at\s+)([^(]+?\.\w+):(\d+)/);
|
||||
if (match) return `${match[1]}:${match[2]}`;
|
||||
return caller || 'unknown';
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
class Logger {
|
||||
trace(...args: any[]) {
|
||||
if (shouldLog('trace')) {
|
||||
const location = getCallerLocation();
|
||||
console.debug(`[TRACE:${location}]`, ...args);
|
||||
export const inspector = {
|
||||
error(...args: unknown[]) {
|
||||
const { message, error, properties } = normalize(args);
|
||||
if (error && properties) {
|
||||
getInspectorLogger().error(error, properties);
|
||||
} else if (error) {
|
||||
getInspectorLogger().error(error);
|
||||
} else if (properties) {
|
||||
getInspectorLogger().error(message, properties);
|
||||
} else {
|
||||
getInspectorLogger().error(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
debug(...args: any[]) {
|
||||
if (shouldLog('debug')) {
|
||||
console.debug(...formatArgs('debug', args));
|
||||
warn(...args: unknown[]) {
|
||||
const { message, error } = normalize(args);
|
||||
// inspector only logs errors; escalate to error level
|
||||
if (error) {
|
||||
getInspectorLogger().error(error);
|
||||
} else if (message) {
|
||||
getInspectorLogger().error(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
info(...args: any[]) {
|
||||
if (shouldLog('info')) {
|
||||
console.info(...formatArgs('info', args));
|
||||
}
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
if (shouldLog('warn')) {
|
||||
console.warn(...formatArgs('warn', args));
|
||||
}
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
if (shouldLog('error')) {
|
||||
console.error(...formatArgs('error', args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
info(...args: unknown[]) {
|
||||
const { message } = normalize(args);
|
||||
// promote to error so it passes the filter
|
||||
if (message) getInspectorLogger().error(message);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue