workspace-v1/orchestration/patrones/PATRON-LOGGING.md
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

664 lines
17 KiB
Markdown

# PATRON DE LOGGING
**Version:** 1.0.0
**Fecha:** 2025-12-08
**Prioridad:** RECOMENDADA - Seguir para consistencia
**Sistema:** SIMCO + CAPVED
---
## PROPOSITO
Definir patrones estandarizados de logging para todas las capas del sistema, asegurando trazabilidad, debugging efectivo y monitoreo en produccion.
---
## 1. NIVELES DE LOG
```
╔══════════════════════════════════════════════════════════════════════╗
║ NIVELES DE LOG (de menor a mayor severidad) ║
╠══════════════════════════════════════════════════════════════════════╣
║ ║
║ TRACE → Detalle extremo (solo desarrollo) ║
║ DEBUG → Informacion de debugging ║
║ INFO → Eventos normales del sistema ║
║ WARN → Situaciones anormales pero manejables ║
║ ERROR → Errores que afectan funcionalidad ║
║ FATAL → Errores criticos que detienen el sistema ║
║ ║
╚══════════════════════════════════════════════════════════════════════╝
```
### Cuando Usar Cada Nivel
| Nivel | Uso | Ejemplo |
|-------|-----|---------|
| **TRACE** | Flujo detallado de ejecucion | `Entering method findById with id=123` |
| **DEBUG** | Variables, estados internos | `User cache hit: userId=123` |
| **INFO** | Eventos de negocio normales | `Order created: orderId=456` |
| **WARN** | Situaciones inesperadas no criticas | `Retry attempt 2/3 for external API` |
| **ERROR** | Errores que necesitan atencion | `Failed to process payment: timeout` |
| **FATAL** | Sistema no puede continuar | `Database connection lost` |
---
## 2. ESTRUCTURA DE LOG
### Formato Estandar
```typescript
{
timestamp: "2025-12-08T10:30:45.123Z", // ISO 8601
level: "INFO", // Nivel
context: "UserService", // Clase/modulo origen
message: "User created successfully", // Mensaje descriptivo
correlationId: "req-abc123", // ID de request/transaccion
userId: "user-456", // Usuario (si aplica)
data: { // Datos adicionales
email: "user@example.com",
action: "create"
},
duration: 45 // Duracion en ms (si aplica)
}
```
### Campos Obligatorios
| Campo | Descripcion | Siempre |
|-------|-------------|---------|
| `timestamp` | Fecha/hora ISO 8601 | SI |
| `level` | Nivel del log | SI |
| `context` | Origen del log | SI |
| `message` | Descripcion del evento | SI |
| `correlationId` | ID para trazar request | EN PRODUCCION |
### Campos Opcionales Recomendados
| Campo | Cuando Usar |
|-------|-------------|
| `userId` | Cuando hay usuario autenticado |
| `data` | Datos relevantes al evento |
| `duration` | Para operaciones medibles |
| `error` | Cuando es log de error |
| `stack` | Stack trace en errores |
---
## 3. BACKEND (NestJS)
### Configuracion del Logger
```typescript
// src/shared/logger/logger.service.ts
import { Injectable, LoggerService, Scope } from '@nestjs/common';
import { Logger } from 'winston';
@Injectable({ scope: Scope.TRANSIENT })
export class AppLogger implements LoggerService {
private context: string;
private correlationId: string;
constructor(private readonly logger: Logger) {}
setContext(context: string) {
this.context = context;
}
setCorrelationId(correlationId: string) {
this.correlationId = correlationId;
}
log(message: string, data?: Record<string, any>) {
this.logger.info(message, {
context: this.context,
correlationId: this.correlationId,
...data,
});
}
error(message: string, trace?: string, data?: Record<string, any>) {
this.logger.error(message, {
context: this.context,
correlationId: this.correlationId,
stack: trace,
...data,
});
}
warn(message: string, data?: Record<string, any>) {
this.logger.warn(message, {
context: this.context,
correlationId: this.correlationId,
...data,
});
}
debug(message: string, data?: Record<string, any>) {
this.logger.debug(message, {
context: this.context,
correlationId: this.correlationId,
...data,
});
}
}
```
### Configuracion Winston
```typescript
// src/shared/logger/winston.config.ts
import * as winston from 'winston';
const { combine, timestamp, json, printf, colorize } = winston.format;
// Formato para desarrollo
const devFormat = combine(
colorize(),
timestamp(),
printf(({ timestamp, level, message, context, ...meta }) => {
return `${timestamp} [${context}] ${level}: ${message} ${
Object.keys(meta).length ? JSON.stringify(meta) : ''
}`;
}),
);
// Formato para produccion (JSON estructurado)
const prodFormat = combine(
timestamp(),
json(),
);
export const winstonConfig: winston.LoggerOptions = {
level: process.env.LOG_LEVEL || 'info',
format: process.env.NODE_ENV === 'production' ? prodFormat : devFormat,
transports: [
new winston.transports.Console(),
// En produccion: agregar transports adicionales
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
};
```
### Uso en Service
```typescript
// src/modules/user/services/user.service.ts
@Injectable()
export class UserService {
constructor(
private readonly logger: AppLogger,
private readonly repository: Repository<UserEntity>,
) {
this.logger.setContext(UserService.name);
}
async create(dto: CreateUserDto): Promise<UserEntity> {
this.logger.log('Creating new user', { email: dto.email });
try {
const user = await this.repository.save(dto);
this.logger.log('User created successfully', {
userId: user.id,
email: user.email,
});
return user;
} catch (error) {
this.logger.error('Failed to create user', error.stack, {
email: dto.email,
errorCode: error.code,
});
throw error;
}
}
async findById(id: string): Promise<UserEntity> {
this.logger.debug('Finding user by ID', { userId: id });
const startTime = Date.now();
const user = await this.repository.findOne({ where: { id } });
const duration = Date.now() - startTime;
if (!user) {
this.logger.warn('User not found', { userId: id, duration });
throw new NotFoundException(`User ${id} not found`);
}
this.logger.debug('User found', { userId: id, duration });
return user;
}
}
```
### Interceptor para Correlation ID
```typescript
// src/shared/interceptors/correlation.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class CorrelationInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
// Usar header existente o generar nuevo
const correlationId = request.headers['x-correlation-id'] || uuidv4();
// Guardar en request para uso posterior
request.correlationId = correlationId;
// Agregar a response headers
const response = context.switchToHttp().getResponse();
response.setHeader('x-correlation-id', correlationId);
return next.handle();
}
}
```
### Interceptor de Logging de Requests
```typescript
// src/shared/interceptors/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(private readonly logger: AppLogger) {
this.logger.setContext('HTTP');
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url, correlationId, user } = request;
const startTime = Date.now();
this.logger.log('Incoming request', {
method,
url,
correlationId,
userId: user?.id,
});
return next.handle().pipe(
tap({
next: () => {
const duration = Date.now() - startTime;
this.logger.log('Request completed', {
method,
url,
correlationId,
duration,
statusCode: context.switchToHttp().getResponse().statusCode,
});
},
error: (error) => {
const duration = Date.now() - startTime;
this.logger.error('Request failed', error.stack, {
method,
url,
correlationId,
duration,
errorMessage: error.message,
});
},
}),
);
}
}
```
---
## 4. FRONTEND (React)
### Logger Service
```typescript
// src/shared/services/logger.service.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
context?: string;
data?: Record<string, any>;
userId?: string;
}
class LoggerService {
private isDev = process.env.NODE_ENV === 'development';
private userId: string | null = null;
setUserId(userId: string | null) {
this.userId = userId;
}
private log(level: LogLevel, message: string, context?: string, data?: Record<string, any>) {
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
message,
context,
data,
userId: this.userId || undefined,
};
// En desarrollo: console
if (this.isDev) {
const consoleMethod = level === 'error' ? console.error :
level === 'warn' ? console.warn :
level === 'debug' ? console.debug : console.log;
consoleMethod(`[${entry.context}] ${message}`, data || '');
}
// En produccion: enviar a servicio de logging
if (!this.isDev && (level === 'error' || level === 'warn')) {
this.sendToServer(entry);
}
}
private async sendToServer(entry: LogEntry) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entry),
});
} catch {
// Silently fail - don't cause more errors
}
}
debug(message: string, context?: string, data?: Record<string, any>) {
this.log('debug', message, context, data);
}
info(message: string, context?: string, data?: Record<string, any>) {
this.log('info', message, context, data);
}
warn(message: string, context?: string, data?: Record<string, any>) {
this.log('warn', message, context, data);
}
error(message: string, context?: string, data?: Record<string, any>) {
this.log('error', message, context, data);
}
}
export const logger = new LoggerService();
```
### Uso en Componentes
```typescript
// src/apps/web/pages/UsersPage.tsx
import { logger } from '@/shared/services/logger.service';
export const UsersPage = () => {
const { data, error, isLoading } = useUsers();
useEffect(() => {
logger.info('Users page mounted', 'UsersPage');
}, []);
useEffect(() => {
if (error) {
logger.error('Failed to load users', 'UsersPage', {
errorMessage: error.message,
});
}
}, [error]);
const handleDelete = async (userId: string) => {
logger.info('Deleting user', 'UsersPage', { userId });
try {
await deleteUser(userId);
logger.info('User deleted successfully', 'UsersPage', { userId });
} catch (err) {
logger.error('Failed to delete user', 'UsersPage', {
userId,
error: err.message,
});
}
};
return (/* ... */);
};
```
### Error Boundary con Logging
```typescript
// src/shared/components/ErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from 'react';
import { logger } from '@/shared/services/logger.service';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends Component<Props, State> {
state = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
logger.error('React error boundary caught error', 'ErrorBoundary', {
error: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
});
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Something went wrong</div>;
}
return this.props.children;
}
}
```
---
## 5. DATABASE
### Logging de Queries (TypeORM)
```typescript
// src/shared/config/typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const typeOrmConfig: TypeOrmModuleOptions = {
// ... otras opciones
logging: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
logger: 'advanced-console', // o custom logger
maxQueryExecutionTime: 1000, // Log queries > 1s
};
```
### Custom Query Logger
```typescript
// src/shared/logger/typeorm.logger.ts
import { Logger as TypeOrmLogger } from 'typeorm';
import { AppLogger } from './logger.service';
export class CustomTypeOrmLogger implements TypeOrmLogger {
constructor(private readonly logger: AppLogger) {
this.logger.setContext('TypeORM');
}
logQuery(query: string, parameters?: any[]) {
this.logger.debug('Query executed', {
query: query.substring(0, 500), // Truncar queries largas
parameters,
});
}
logQueryError(error: string, query: string, parameters?: any[]) {
this.logger.error('Query failed', undefined, {
error,
query: query.substring(0, 500),
parameters,
});
}
logQuerySlow(time: number, query: string, parameters?: any[]) {
this.logger.warn('Slow query detected', {
duration: time,
query: query.substring(0, 500),
parameters,
});
}
logSchemaBuild(message: string) {
this.logger.info(message);
}
logMigration(message: string) {
this.logger.info(message);
}
log(level: 'log' | 'info' | 'warn', message: any) {
if (level === 'warn') {
this.logger.warn(message);
} else {
this.logger.log(message);
}
}
}
```
---
## 6. QUE LOGUEAR Y QUE NO
### SI Loguear
```
✅ Inicio/fin de operaciones importantes
✅ Errores y excepciones (con contexto)
✅ Eventos de autenticacion (login, logout, failed attempts)
✅ Operaciones de negocio criticas (pagos, cambios de estado)
✅ Llamadas a APIs externas (request/response resumido)
✅ Queries lentas (>1s)
✅ Warnings de recursos (memoria, conexiones)
✅ Cambios de configuracion en runtime
```
### NO Loguear
```
❌ Datos sensibles (passwords, tokens, tarjetas)
❌ PII sin necesidad (emails completos, nombres)
❌ Cada iteracion de loops
❌ Contenido completo de requests/responses grandes
❌ Logs de debug en produccion
❌ Informacion redundante
❌ Stack traces en logs INFO/DEBUG
```
### Sanitizacion de Datos Sensibles
```typescript
// src/shared/utils/log-sanitizer.ts
const SENSITIVE_FIELDS = ['password', 'token', 'secret', 'authorization', 'credit_card'];
export function sanitizeForLogging(data: Record<string, any>): Record<string, any> {
const sanitized = { ...data };
for (const key of Object.keys(sanitized)) {
if (SENSITIVE_FIELDS.some(field => key.toLowerCase().includes(field))) {
sanitized[key] = '[REDACTED]';
} else if (typeof sanitized[key] === 'object' && sanitized[key] !== null) {
sanitized[key] = sanitizeForLogging(sanitized[key]);
}
}
return sanitized;
}
// Uso
this.logger.log('User login attempt', sanitizeForLogging({
email: dto.email,
password: dto.password, // Se convierte en [REDACTED]
}));
```
---
## 7. CONFIGURACION POR AMBIENTE
```typescript
// src/shared/config/logger.config.ts
export const loggerConfig = {
development: {
level: 'debug',
format: 'pretty',
includeTimestamp: true,
colorize: true,
},
staging: {
level: 'info',
format: 'json',
includeTimestamp: true,
colorize: false,
},
production: {
level: 'warn',
format: 'json',
includeTimestamp: true,
colorize: false,
// Enviar a servicio externo
externalService: {
enabled: true,
endpoint: process.env.LOG_ENDPOINT,
},
},
};
```
---
## 8. CHECKLIST DE LOGGING
```
Antes de hacer deploy:
[ ] Logs no contienen datos sensibles
[ ] Nivel de log apropiado para ambiente
[ ] Errores tienen contexto suficiente para debug
[ ] Correlation ID implementado
[ ] Queries lentas se detectan
[ ] Error boundary implementado en frontend
En cada Service nuevo:
[ ] Logger inyectado y contexto configurado
[ ] Operaciones principales logueadas
[ ] Errores logueados con stack trace
[ ] Tiempos de operaciones criticas medidos
```
---
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Codigo