refactor: add KV cache, env validation, logger types, constants
- Add KV cache abstraction layer (src/services/kv-cache.ts) - Add Zod-based env validation (src/utils/env-validation.ts) - Improve logger types: any → unknown for type safety - Add centralized constants file (src/constants/index.ts) - Fix security.ts unused import Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
180
src/constants/index.ts
Normal file
180
src/constants/index.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
* Constants for telegram-bot-workers
|
||||||
|
*
|
||||||
|
* Centralized magic strings and configuration values for better maintainability.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* import { SESSION_KEYS, MESSAGE_MARKERS, sessionKey } from './constants';
|
||||||
|
* const key = sessionKey(SESSION_KEYS.DELETE_CONFIRM, userId);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session key prefixes for KV storage
|
||||||
|
*/
|
||||||
|
export const SESSION_KEYS = {
|
||||||
|
DELETE_CONFIRM: 'delete_confirm',
|
||||||
|
SERVER_ORDER_CONFIRM: 'server_order_confirm',
|
||||||
|
SERVER_SESSION: 'server_session',
|
||||||
|
RATE_LIMIT: 'rate_limit',
|
||||||
|
NOTIFICATION: 'notification',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message markers for AI response processing
|
||||||
|
*
|
||||||
|
* These markers control how responses are processed:
|
||||||
|
* - DIRECT: Pass through response without AI reinterpretation
|
||||||
|
* - KEYBOARD: Indicates inline keyboard JSON data follows
|
||||||
|
* - KEYBOARD_END: Marks end of keyboard data
|
||||||
|
* - PASSTHROUGH: Return to normal conversation flow
|
||||||
|
*/
|
||||||
|
export const MESSAGE_MARKERS = {
|
||||||
|
DIRECT: '__DIRECT__',
|
||||||
|
KEYBOARD: '__KEYBOARD__',
|
||||||
|
KEYBOARD_END: '__END__',
|
||||||
|
PASSTHROUGH: '__PASSTHROUGH__',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard types for inline buttons
|
||||||
|
*/
|
||||||
|
export const KEYBOARD_TYPES = {
|
||||||
|
DOMAIN_REGISTER: 'domain_register',
|
||||||
|
SERVER_ORDER: 'server_order',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback data prefixes for inline keyboard buttons
|
||||||
|
*
|
||||||
|
* Format: prefix:action:params
|
||||||
|
* Examples:
|
||||||
|
* - domain_reg:confirm:example.com:15000
|
||||||
|
* - server_order:confirm:order_123
|
||||||
|
*/
|
||||||
|
export const CALLBACK_PREFIXES = {
|
||||||
|
DOMAIN_REGISTER_CONFIRM: 'confirm_domain_register',
|
||||||
|
DOMAIN_REGISTER_CANCEL: 'cancel_domain_register',
|
||||||
|
SERVER_ORDER_CONFIRM: 'confirm_server_order',
|
||||||
|
SERVER_ORDER_CANCEL: 'cancel_server_order',
|
||||||
|
DELETE_CONFIRM: 'confirm_delete',
|
||||||
|
DELETE_CANCEL: 'cancel_delete',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction statuses for deposit system
|
||||||
|
*/
|
||||||
|
export const TRANSACTION_STATUS = {
|
||||||
|
PENDING: 'pending',
|
||||||
|
CONFIRMED: 'confirmed',
|
||||||
|
CANCELLED: 'cancelled',
|
||||||
|
REJECTED: 'rejected',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction types for deposit system
|
||||||
|
*/
|
||||||
|
export const TRANSACTION_TYPE = {
|
||||||
|
DEPOSIT: 'deposit',
|
||||||
|
WITHDRAWAL: 'withdrawal',
|
||||||
|
REFUND: 'refund',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server order statuses
|
||||||
|
*/
|
||||||
|
export const SERVER_ORDER_STATUS = {
|
||||||
|
PENDING: 'pending',
|
||||||
|
PROVISIONING: 'provisioning',
|
||||||
|
ACTIVE: 'active',
|
||||||
|
SUSPENDED: 'suspended',
|
||||||
|
TERMINATED: 'terminated',
|
||||||
|
FAILED: 'failed',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server actions
|
||||||
|
*/
|
||||||
|
export const SERVER_ACTION = {
|
||||||
|
RECOMMEND: 'recommend',
|
||||||
|
ORDER: 'order',
|
||||||
|
START: 'start',
|
||||||
|
STOP: 'stop',
|
||||||
|
DELETE: 'delete',
|
||||||
|
LIST: 'list',
|
||||||
|
INFO: 'info',
|
||||||
|
IMAGES: 'images',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain actions
|
||||||
|
*/
|
||||||
|
export const DOMAIN_ACTION = {
|
||||||
|
REGISTER: 'register',
|
||||||
|
CHECK: 'check',
|
||||||
|
WHOIS: 'whois',
|
||||||
|
LIST: 'list',
|
||||||
|
INFO: 'info',
|
||||||
|
GET_NS: 'get_ns',
|
||||||
|
SET_NS: 'set_ns',
|
||||||
|
PRICE: 'price',
|
||||||
|
CHEAPEST: 'cheapest',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deposit actions
|
||||||
|
*/
|
||||||
|
export const DEPOSIT_ACTION = {
|
||||||
|
BALANCE: 'balance',
|
||||||
|
ACCOUNT: 'account',
|
||||||
|
REQUEST: 'request',
|
||||||
|
HISTORY: 'history',
|
||||||
|
CANCEL: 'cancel',
|
||||||
|
PENDING: 'pending',
|
||||||
|
CONFIRM: 'confirm',
|
||||||
|
REJECT: 'reject',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to create session key with user ID
|
||||||
|
*
|
||||||
|
* @param prefix - Session key prefix (from SESSION_KEYS)
|
||||||
|
* @param userId - User ID (telegram_id or user identifier)
|
||||||
|
* @returns Formatted session key
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const key = sessionKey(SESSION_KEYS.DELETE_CONFIRM, '123456');
|
||||||
|
* // Returns: 'delete_confirm:123456'
|
||||||
|
*/
|
||||||
|
export function sessionKey(prefix: string, userId: string | number): string {
|
||||||
|
return `${prefix}:${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to parse session key
|
||||||
|
*
|
||||||
|
* @param key - Full session key
|
||||||
|
* @returns Object with prefix and userId, or null if invalid
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const parsed = parseSessionKey('delete_confirm:123456');
|
||||||
|
* // Returns: { prefix: 'delete_confirm', userId: '123456' }
|
||||||
|
*/
|
||||||
|
export function parseSessionKey(key: string): { prefix: string; userId: string } | null {
|
||||||
|
const parts = key.split(':');
|
||||||
|
if (parts.length !== 2) return null;
|
||||||
|
return { prefix: parts[0], userId: parts[1] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guards for type safety
|
||||||
|
*/
|
||||||
|
export type SessionKeyPrefix = typeof SESSION_KEYS[keyof typeof SESSION_KEYS];
|
||||||
|
export type MessageMarker = typeof MESSAGE_MARKERS[keyof typeof MESSAGE_MARKERS];
|
||||||
|
export type KeyboardType = typeof KEYBOARD_TYPES[keyof typeof KEYBOARD_TYPES];
|
||||||
|
export type CallbackPrefix = typeof CALLBACK_PREFIXES[keyof typeof CALLBACK_PREFIXES];
|
||||||
|
export type TransactionStatus = typeof TRANSACTION_STATUS[keyof typeof TRANSACTION_STATUS];
|
||||||
|
export type TransactionType = typeof TRANSACTION_TYPE[keyof typeof TRANSACTION_TYPE];
|
||||||
|
export type ServerOrderStatus = typeof SERVER_ORDER_STATUS[keyof typeof SERVER_ORDER_STATUS];
|
||||||
|
export type ServerAction = typeof SERVER_ACTION[keyof typeof SERVER_ACTION];
|
||||||
|
export type DomainAction = typeof DOMAIN_ACTION[keyof typeof DOMAIN_ACTION];
|
||||||
|
export type DepositAction = typeof DEPOSIT_ACTION[keyof typeof DEPOSIT_ACTION];
|
||||||
@@ -138,6 +138,8 @@ export async function validateWebhookRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rate Limiting (Cloudflare KV 기반)
|
// Rate Limiting (Cloudflare KV 기반)
|
||||||
|
// NOTE: Future migration to KVCache abstraction layer (kv-cache.ts) planned
|
||||||
|
// Current implementation kept for backward compatibility
|
||||||
interface RateLimitData {
|
interface RateLimitData {
|
||||||
count: number;
|
count: number;
|
||||||
resetAt: number;
|
resetAt: number;
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ async function callCloudOrchestrator(
|
|||||||
|
|
||||||
const data = await response.json() as ProvisionResponse;
|
const data = await response.json() as ProvisionResponse;
|
||||||
|
|
||||||
logger.info('Cloud Orchestrator API 응답', data);
|
logger.info('Cloud Orchestrator API 응답', { response: data });
|
||||||
|
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
throw new Error(data.error || 'Provisioning failed');
|
throw new Error(data.error || 'Provisioning failed');
|
||||||
|
|||||||
124
src/services/kv-cache.ts
Normal file
124
src/services/kv-cache.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { createLogger } from '../utils/logger';
|
||||||
|
|
||||||
|
const logger = createLogger('kv-cache');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KV Cache abstraction layer for consistent caching patterns
|
||||||
|
*/
|
||||||
|
export class KVCache {
|
||||||
|
constructor(private kv: KVNamespace, private prefix: string = '') {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value from cache
|
||||||
|
*/
|
||||||
|
async get<T>(key: string): Promise<T | null> {
|
||||||
|
const fullKey = this.prefix ? `${this.prefix}:${key}` : key;
|
||||||
|
try {
|
||||||
|
const value = await this.kv.get(fullKey, 'json');
|
||||||
|
return value as T | null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('KV get failed', error as Error, { key: fullKey });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value in cache with optional TTL
|
||||||
|
*/
|
||||||
|
async set<T>(key: string, value: T, ttlSeconds?: number): Promise<boolean> {
|
||||||
|
const fullKey = this.prefix ? `${this.prefix}:${key}` : key;
|
||||||
|
try {
|
||||||
|
const options = ttlSeconds ? { expirationTtl: ttlSeconds } : undefined;
|
||||||
|
await this.kv.put(fullKey, JSON.stringify(value), options);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('KV set failed', error as Error, { key: fullKey });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete value from cache
|
||||||
|
*/
|
||||||
|
async delete(key: string): Promise<boolean> {
|
||||||
|
const fullKey = this.prefix ? `${this.prefix}:${key}` : key;
|
||||||
|
try {
|
||||||
|
await this.kv.delete(fullKey);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('KV delete failed', error as Error, { key: fullKey });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set pattern - fetch from cache or compute and store
|
||||||
|
*/
|
||||||
|
async getOrSet<T>(
|
||||||
|
key: string,
|
||||||
|
factory: () => Promise<T>,
|
||||||
|
ttlSeconds?: number
|
||||||
|
): Promise<T> {
|
||||||
|
const cached = await this.get<T>(key);
|
||||||
|
if (cached !== null) {
|
||||||
|
logger.debug('Cache hit', { key });
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('Cache miss', { key });
|
||||||
|
const value = await factory();
|
||||||
|
await this.set(key, value, ttlSeconds);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if key exists
|
||||||
|
*/
|
||||||
|
async exists(key: string): Promise<boolean> {
|
||||||
|
const value = await this.get(key);
|
||||||
|
return value !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create rate limiter cache instance
|
||||||
|
*/
|
||||||
|
export function createRateLimitCache(kv: KVNamespace): KVCache {
|
||||||
|
return new KVCache(kv, 'rate');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create session cache instance
|
||||||
|
*/
|
||||||
|
export function createSessionCache(kv: KVNamespace): KVCache {
|
||||||
|
return new KVCache(kv, 'session');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate limiting helper - returns true if request should be allowed
|
||||||
|
*/
|
||||||
|
export async function checkRateLimitWithCache(
|
||||||
|
cache: KVCache,
|
||||||
|
userId: string,
|
||||||
|
maxRequests: number = 30,
|
||||||
|
windowSeconds: number = 60
|
||||||
|
): Promise<boolean> {
|
||||||
|
const key = userId;
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
const data = await cache.get<{ count: number; windowStart: number }>(key);
|
||||||
|
|
||||||
|
if (!data || now - data.windowStart >= windowSeconds) {
|
||||||
|
// New window
|
||||||
|
await cache.set(key, { count: 1, windowStart: now }, windowSeconds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.count >= maxRequests) {
|
||||||
|
return false; // Rate limited
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment count
|
||||||
|
await cache.set(key, { count: data.count + 1, windowStart: data.windowStart }, windowSeconds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
105
src/utils/env-validation.ts
Normal file
105
src/utils/env-validation.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { createLogger } from './logger';
|
||||||
|
|
||||||
|
const logger = createLogger('env-validation');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment variable schema with validation rules
|
||||||
|
*/
|
||||||
|
export const EnvSchema = z.object({
|
||||||
|
// Required secrets
|
||||||
|
BOT_TOKEN: z.string().min(1, 'BOT_TOKEN is required'),
|
||||||
|
WEBHOOK_SECRET: z.string().min(10, 'WEBHOOK_SECRET must be at least 10 characters'),
|
||||||
|
|
||||||
|
// Optional secrets with defaults handled elsewhere
|
||||||
|
OPENAI_API_KEY: z.string().optional(),
|
||||||
|
DEPOSIT_API_SECRET: z.string().optional(),
|
||||||
|
BRAVE_API_KEY: z.string().optional(),
|
||||||
|
NAMECHEAP_API_KEY: z.string().optional(),
|
||||||
|
NAMECHEAP_API_KEY_INTERNAL: z.string().optional(),
|
||||||
|
|
||||||
|
// Configuration with defaults
|
||||||
|
ENVIRONMENT: z.enum(['development', 'production']).default('production'),
|
||||||
|
SUMMARY_THRESHOLD: z.string().default('20').transform(Number),
|
||||||
|
MAX_SUMMARIES_PER_USER: z.string().default('3').transform(Number),
|
||||||
|
|
||||||
|
// Admin IDs
|
||||||
|
DOMAIN_OWNER_ID: z.string().optional(),
|
||||||
|
DEPOSIT_ADMIN_ID: z.string().optional(),
|
||||||
|
|
||||||
|
// API URLs (optional, have defaults in code)
|
||||||
|
OPENAI_API_BASE: z.string().url().optional(),
|
||||||
|
NAMECHEAP_API_URL: z.string().url().optional(),
|
||||||
|
WHOIS_API_URL: z.string().url().optional(),
|
||||||
|
CONTEXT7_API_BASE: z.string().url().optional(),
|
||||||
|
BRAVE_API_BASE: z.string().url().optional(),
|
||||||
|
WTTR_IN_URL: z.string().url().optional(),
|
||||||
|
HOSTING_SITE_URL: z.string().url().optional(),
|
||||||
|
CLOUD_ORCHESTRATOR_URL: z.string().url().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ValidatedEnv = z.infer<typeof EnvSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation result type
|
||||||
|
*/
|
||||||
|
export interface EnvValidationResult {
|
||||||
|
success: boolean;
|
||||||
|
errors: string[];
|
||||||
|
warnings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate environment variables
|
||||||
|
* Call this early in worker initialization
|
||||||
|
*/
|
||||||
|
export function validateEnv(env: Record<string, unknown>): EnvValidationResult {
|
||||||
|
const result: EnvValidationResult = {
|
||||||
|
success: true,
|
||||||
|
errors: [],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate with Zod
|
||||||
|
const parsed = EnvSchema.safeParse(env);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
result.success = false;
|
||||||
|
for (const issue of parsed.error.issues) {
|
||||||
|
const path = issue.path.join('.');
|
||||||
|
result.errors.push(`${path}: ${issue.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional warnings for recommended but optional vars
|
||||||
|
if (!env.OPENAI_API_KEY) {
|
||||||
|
result.warnings.push('OPENAI_API_KEY not set - will use Workers AI fallback');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!env.DEPOSIT_ADMIN_ID) {
|
||||||
|
result.warnings.push('DEPOSIT_ADMIN_ID not set - admin notifications disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log results
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
logger.error('Environment validation failed', new Error('Invalid configuration'), {
|
||||||
|
errors: result.errors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
logger.warn('Environment validation warnings', { warnings: result.warnings });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick check for critical env vars - throws on failure
|
||||||
|
*/
|
||||||
|
export function requireEnv(env: Record<string, unknown>, keys: string[]): void {
|
||||||
|
const missing = keys.filter(key => !env[key]);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,31 @@ export enum LogLevel {
|
|||||||
FATAL = 'FATAL',
|
FATAL = 'FATAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그 컨텍스트 값 타입
|
||||||
|
*
|
||||||
|
* any 대신 unknown을 사용하여 타입 안정성 개선
|
||||||
|
* - unknown은 사용 전 타입 체크를 강제하므로 any보다 안전
|
||||||
|
* - 모든 타입의 값을 저장 가능하지만, 사용 시 타입 검증 필요
|
||||||
|
*
|
||||||
|
* 기본 타입 (string, number, boolean 등)과 객체/배열 모두 허용하여
|
||||||
|
* 실제 로깅 사용 사례를 지원하면서도 타입 안정성을 유지합니다.
|
||||||
|
*/
|
||||||
|
export type LogContextValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
| unknown
|
||||||
|
| LogContextValue[]
|
||||||
|
| { [key: string]: unknown };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그 컨텍스트 타입
|
||||||
|
*/
|
||||||
|
export type LogContext = Record<string, unknown>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 구조화된 로그 엔트리 인터페이스
|
* 구조화된 로그 엔트리 인터페이스
|
||||||
*/
|
*/
|
||||||
@@ -48,7 +73,7 @@ export interface LogEntry {
|
|||||||
/** 서비스명 (예: 'openai', 'telegram', 'deposit') */
|
/** 서비스명 (예: 'openai', 'telegram', 'deposit') */
|
||||||
service?: string;
|
service?: string;
|
||||||
/** 추가 컨텍스트 정보 */
|
/** 추가 컨텍스트 정보 */
|
||||||
context?: Record<string, any>;
|
context?: LogContext;
|
||||||
/** 에러 정보 (ERROR/FATAL 레벨용) */
|
/** 에러 정보 (ERROR/FATAL 레벨용) */
|
||||||
error?: {
|
error?: {
|
||||||
/** 에러 이름 */
|
/** 에러 이름 */
|
||||||
@@ -209,7 +234,7 @@ export class Logger {
|
|||||||
private createEntry(
|
private createEntry(
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
message: string,
|
message: string,
|
||||||
context?: Record<string, any>,
|
context?: LogContext,
|
||||||
error?: Error
|
error?: Error
|
||||||
): LogEntry {
|
): LogEntry {
|
||||||
const entry: LogEntry = {
|
const entry: LogEntry = {
|
||||||
@@ -245,7 +270,7 @@ export class Logger {
|
|||||||
* logger.debug('함수 호출', { functionName: 'processData', args: [1, 2, 3] });
|
* logger.debug('함수 호출', { functionName: 'processData', args: [1, 2, 3] });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
debug(message: string, context?: Record<string, any>): void {
|
debug(message: string, context?: LogContext): void {
|
||||||
if (!this.shouldLog(LogLevel.DEBUG)) return;
|
if (!this.shouldLog(LogLevel.DEBUG)) return;
|
||||||
this.write(this.createEntry(LogLevel.DEBUG, message, context));
|
this.write(this.createEntry(LogLevel.DEBUG, message, context));
|
||||||
}
|
}
|
||||||
@@ -261,7 +286,7 @@ export class Logger {
|
|||||||
* logger.info('요청 처리 시작', { userId: '123', endpoint: '/api/data' });
|
* logger.info('요청 처리 시작', { userId: '123', endpoint: '/api/data' });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
info(message: string, context?: Record<string, any>): void {
|
info(message: string, context?: LogContext): void {
|
||||||
if (!this.shouldLog(LogLevel.INFO)) return;
|
if (!this.shouldLog(LogLevel.INFO)) return;
|
||||||
this.write(this.createEntry(LogLevel.INFO, message, context));
|
this.write(this.createEntry(LogLevel.INFO, message, context));
|
||||||
}
|
}
|
||||||
@@ -277,7 +302,7 @@ export class Logger {
|
|||||||
* logger.warn('API 응답 지연', { endpoint: '/api/data', responseTime: 5000 });
|
* logger.warn('API 응답 지연', { endpoint: '/api/data', responseTime: 5000 });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
warn(message: string, context?: Record<string, any>): void {
|
warn(message: string, context?: LogContext): void {
|
||||||
if (!this.shouldLog(LogLevel.WARN)) return;
|
if (!this.shouldLog(LogLevel.WARN)) return;
|
||||||
this.write(this.createEntry(LogLevel.WARN, message, context));
|
this.write(this.createEntry(LogLevel.WARN, message, context));
|
||||||
}
|
}
|
||||||
@@ -298,7 +323,7 @@ export class Logger {
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
error(message: string, error?: Error, context?: Record<string, any>): void {
|
error(message: string, error?: Error, context?: LogContext): void {
|
||||||
if (!this.shouldLog(LogLevel.ERROR)) return;
|
if (!this.shouldLog(LogLevel.ERROR)) return;
|
||||||
this.write(this.createEntry(LogLevel.ERROR, message, context, error));
|
this.write(this.createEntry(LogLevel.ERROR, message, context, error));
|
||||||
}
|
}
|
||||||
@@ -315,7 +340,7 @@ export class Logger {
|
|||||||
* logger.fatal('데이터베이스 연결 실패', error, { database: 'main' });
|
* logger.fatal('데이터베이스 연결 실패', error, { database: 'main' });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
fatal(message: string, error?: Error, context?: Record<string, any>): void {
|
fatal(message: string, error?: Error, context?: LogContext): void {
|
||||||
if (!this.shouldLog(LogLevel.FATAL)) return;
|
if (!this.shouldLog(LogLevel.FATAL)) return;
|
||||||
this.write(this.createEntry(LogLevel.FATAL, message, context, error));
|
this.write(this.createEntry(LogLevel.FATAL, message, context, error));
|
||||||
}
|
}
|
||||||
@@ -336,7 +361,7 @@ export class Logger {
|
|||||||
* end(); // duration이 자동으로 로그에 포함됨
|
* end(); // duration이 자동으로 로그에 포함됨
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
startTimer(message?: string, context?: Record<string, any>): () => void {
|
startTimer(message?: string, context?: LogContext): () => void {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const timerMessage = message || 'Operation completed';
|
const timerMessage = message || 'Operation completed';
|
||||||
|
|
||||||
@@ -404,7 +429,7 @@ export class Logger {
|
|||||||
export function createLogger(service: string, env?: Partial<Env>): Logger {
|
export function createLogger(service: string, env?: Partial<Env>): Logger {
|
||||||
// 환경 감지: 명시적 ENVIRONMENT 변수 또는 프로덕션 감지
|
// 환경 감지: 명시적 ENVIRONMENT 변수 또는 프로덕션 감지
|
||||||
const environment =
|
const environment =
|
||||||
(env as any)?.ENVIRONMENT === 'production'
|
env && 'ENVIRONMENT' in env && env.ENVIRONMENT === 'production'
|
||||||
? 'production'
|
? 'production'
|
||||||
: 'development';
|
: 'development';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user