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:
pakintada@gmail.com 2026-07-01 17:08:46 +07:00
parent aa6414b1cc
commit 3cda8aa60b
12 changed files with 494 additions and 174 deletions

View file

@ -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);
},
};