linxus fs fixes
This commit is contained in:
parent
ac23b70146
commit
0b7846fe67
292 changed files with 41947 additions and 41947 deletions
|
|
@ -1,337 +1,337 @@
|
|||
# @stock-bot/logger
|
||||
|
||||
Enhanced logging library with Loki integration for the Stock Bot platform (June 2025).
|
||||
|
||||
## Features
|
||||
|
||||
- 🎯 **Multiple Log Levels**: debug, info, warn, error, http
|
||||
- 🌐 **Loki Integration**: Centralized logging with Grafana visualization
|
||||
- 📁 **File Logging**: Daily rotating log files with compression
|
||||
- 🎨 **Console Logging**: Colored, formatted console output
|
||||
- 📊 **Structured Logging**: JSON-formatted logs with metadata
|
||||
- ⚡ **Performance Optimized**: Batching and async logging
|
||||
- 🔐 **Security**: Automatic sensitive data masking
|
||||
- 🎭 **Express Middleware**: Request/response logging
|
||||
- 📈 **Business Events**: Specialized logging for trading operations
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Using Bun (current runtime)
|
||||
bun install
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Simple Logging
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('my-service');
|
||||
|
||||
logger.info('Service started');
|
||||
logger.warn('This is a warning');
|
||||
logger.error('An error occurred', new Error('Something went wrong'));
|
||||
```
|
||||
|
||||
### With Context
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('trading-service');
|
||||
|
||||
logger.info('Trade executed', {
|
||||
symbol: 'AAPL',
|
||||
quantity: 100,
|
||||
price: 150.25,
|
||||
userId: '12345',
|
||||
sessionId: 'abc-def-ghi'
|
||||
});
|
||||
```
|
||||
|
||||
### Performance Logging
|
||||
|
||||
```typescript
|
||||
import { getLogger, createTimer } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('data-processor');
|
||||
const timer = createTimer('data-processing');
|
||||
|
||||
// ... do some work ...
|
||||
|
||||
const timing = timer.end();
|
||||
logger.performance('Data processing completed', timing);
|
||||
```
|
||||
|
||||
### Business Events
|
||||
|
||||
```typescript
|
||||
import { getLogger, createBusinessEvent } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('order-service');
|
||||
|
||||
logger.business('Order placed', createBusinessEvent(
|
||||
'order',
|
||||
'place',
|
||||
{
|
||||
entity: 'order-123',
|
||||
result: 'success',
|
||||
symbol: 'TSLA',
|
||||
amount: 50000
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
### Security Events
|
||||
|
||||
```typescript
|
||||
import { getLogger, createSecurityEvent } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('auth-service');
|
||||
|
||||
logger.security('Failed login attempt', createSecurityEvent(
|
||||
'authentication',
|
||||
{
|
||||
user: 'john@example.com',
|
||||
result: 'failure',
|
||||
ip: '192.168.1.100',
|
||||
severity: 'medium'
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
## Express Middleware
|
||||
|
||||
### Basic Request Logging
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import { loggingMiddleware } from '@stock-bot/logger';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(loggingMiddleware({
|
||||
serviceName: 'api-gateway',
|
||||
skipPaths: ['/health', '/metrics']
|
||||
}));
|
||||
```
|
||||
|
||||
### Error Logging
|
||||
|
||||
```typescript
|
||||
import { errorLoggingMiddleware, getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('api-gateway');
|
||||
|
||||
// Add after your routes but before error handlers
|
||||
app.use(errorLoggingMiddleware(logger));
|
||||
```
|
||||
|
||||
### Request-scoped Logger
|
||||
|
||||
```typescript
|
||||
import { createRequestLogger, getLogger } from '@stock-bot/logger';
|
||||
|
||||
const baseLogger = getLogger('api-gateway');
|
||||
|
||||
app.use((req, res, next) => {
|
||||
req.logger = createRequestLogger(req, baseLogger);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/api/data', (req, res) => {
|
||||
req.logger.info('Processing data request');
|
||||
// ... handle request ...
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The logger uses configuration from `@stock-bot/config`. Key environment variables:
|
||||
|
||||
```bash
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
LOG_CONSOLE=true
|
||||
LOG_FILE=true
|
||||
LOG_FILE_PATH=./logs
|
||||
|
||||
# Loki
|
||||
LOKI_HOST=localhost
|
||||
LOKI_PORT=3100
|
||||
LOKI_BATCH_SIZE=1024
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Child Loggers
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const parentLogger = getLogger('trading-service');
|
||||
const orderLogger = parentLogger.child({
|
||||
module: 'order-processing',
|
||||
orderId: '12345'
|
||||
});
|
||||
|
||||
orderLogger.info('Order validated'); // Will include parent context
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
// Uses standard getLogger with service-specific configuration
|
||||
const logger = getLogger('custom-service');
|
||||
```
|
||||
|
||||
### Sensitive Data Masking
|
||||
|
||||
```typescript
|
||||
import { sanitizeMetadata, maskSensitiveData } from '@stock-bot/logger';
|
||||
|
||||
const unsafeData = {
|
||||
username: 'john',
|
||||
password: 'secret123',
|
||||
apiKey: 'abc123def456'
|
||||
};
|
||||
|
||||
const safeData = sanitizeMetadata(unsafeData);
|
||||
// { username: 'john', password: '[REDACTED]', apiKey: '[REDACTED]' }
|
||||
|
||||
const message = maskSensitiveData('User API key: abc123def456');
|
||||
// 'User API key: [API_KEY]'
|
||||
```
|
||||
|
||||
### Log Throttling
|
||||
|
||||
```typescript
|
||||
import { LogThrottle } from '@stock-bot/logger';
|
||||
|
||||
const throttle = new LogThrottle(10, 60000); // 10 logs per minute
|
||||
|
||||
if (throttle.shouldLog('error-key')) {
|
||||
logger.error('This error will be throttled');
|
||||
}
|
||||
```
|
||||
|
||||
## Viewing Logs
|
||||
|
||||
### Grafana Dashboard
|
||||
|
||||
1. Start the monitoring stack: `docker-compose up grafana loki`
|
||||
2. Open Grafana at http://localhost:3000
|
||||
3. Use the "Stock Bot Logs" dashboard
|
||||
4. Query logs with LogQL: `{service="your-service"}`
|
||||
|
||||
### Log Files
|
||||
|
||||
When file logging is enabled, logs are written to:
|
||||
- `./logs/{service-name}-YYYY-MM-DD.log` - All logs
|
||||
- `./logs/{service-name}-error-YYYY-MM-DD.log` - Error logs only
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use appropriate log levels**:
|
||||
- `debug`: Detailed development information
|
||||
- `info`: General operational messages
|
||||
- `warn`: Potential issues
|
||||
- `error`: Actual errors requiring attention
|
||||
|
||||
2. **Include context**: Always provide relevant metadata
|
||||
```typescript
|
||||
logger.info('Trade executed', { symbol, quantity, price, orderId });
|
||||
```
|
||||
|
||||
3. **Use structured logging**: Avoid string concatenation
|
||||
```typescript
|
||||
// Good
|
||||
logger.info('User logged in', { userId, ip, userAgent });
|
||||
|
||||
// Avoid
|
||||
logger.info(`User ${userId} logged in from ${ip}`);
|
||||
```
|
||||
|
||||
4. **Handle sensitive data**: Use sanitization utilities
|
||||
```typescript
|
||||
const safeMetadata = sanitizeMetadata(requestData);
|
||||
logger.info('API request', safeMetadata);
|
||||
```
|
||||
|
||||
5. **Use correlation IDs**: Track requests across services
|
||||
```typescript
|
||||
const logger = getLogger('service').child({
|
||||
correlationId: req.headers['x-correlation-id']
|
||||
});
|
||||
```
|
||||
|
||||
## Integration with Services
|
||||
|
||||
To use in your service:
|
||||
|
||||
1. Add dependency to your service's `package.json`:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@stock-bot/logger": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Update your service's `tsconfig.json` references:
|
||||
```json
|
||||
{
|
||||
"references": [
|
||||
{ "path": "../../../libs/logger" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. Import and use:
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('my-service');
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Logs are batched and sent asynchronously to Loki
|
||||
- File logging uses daily rotation to prevent large files
|
||||
- Console logging can be disabled in production
|
||||
- Use log throttling for high-frequency events
|
||||
- Sensitive data is automatically masked
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Logs not appearing in Loki
|
||||
|
||||
1. Check Loki connection:
|
||||
```bash
|
||||
curl http://localhost:3100/ready
|
||||
```
|
||||
|
||||
2. Verify environment variables:
|
||||
```bash
|
||||
echo $LOKI_HOST $LOKI_PORT
|
||||
```
|
||||
|
||||
3. Check container logs:
|
||||
```bash
|
||||
docker logs stock-bot-loki
|
||||
```
|
||||
|
||||
### High memory usage
|
||||
|
||||
- Reduce `LOKI_BATCH_SIZE` if batching too many logs
|
||||
- Disable file logging if not needed
|
||||
|
||||
### Missing logs
|
||||
|
||||
- Check log level configuration
|
||||
- Verify service name matches expectations
|
||||
- Ensure proper error handling around logger calls
|
||||
# @stock-bot/logger
|
||||
|
||||
Enhanced logging library with Loki integration for the Stock Bot platform (June 2025).
|
||||
|
||||
## Features
|
||||
|
||||
- 🎯 **Multiple Log Levels**: debug, info, warn, error, http
|
||||
- 🌐 **Loki Integration**: Centralized logging with Grafana visualization
|
||||
- 📁 **File Logging**: Daily rotating log files with compression
|
||||
- 🎨 **Console Logging**: Colored, formatted console output
|
||||
- 📊 **Structured Logging**: JSON-formatted logs with metadata
|
||||
- ⚡ **Performance Optimized**: Batching and async logging
|
||||
- 🔐 **Security**: Automatic sensitive data masking
|
||||
- 🎭 **Express Middleware**: Request/response logging
|
||||
- 📈 **Business Events**: Specialized logging for trading operations
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Using Bun (current runtime)
|
||||
bun install
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Simple Logging
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('my-service');
|
||||
|
||||
logger.info('Service started');
|
||||
logger.warn('This is a warning');
|
||||
logger.error('An error occurred', new Error('Something went wrong'));
|
||||
```
|
||||
|
||||
### With Context
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('trading-service');
|
||||
|
||||
logger.info('Trade executed', {
|
||||
symbol: 'AAPL',
|
||||
quantity: 100,
|
||||
price: 150.25,
|
||||
userId: '12345',
|
||||
sessionId: 'abc-def-ghi'
|
||||
});
|
||||
```
|
||||
|
||||
### Performance Logging
|
||||
|
||||
```typescript
|
||||
import { getLogger, createTimer } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('data-processor');
|
||||
const timer = createTimer('data-processing');
|
||||
|
||||
// ... do some work ...
|
||||
|
||||
const timing = timer.end();
|
||||
logger.performance('Data processing completed', timing);
|
||||
```
|
||||
|
||||
### Business Events
|
||||
|
||||
```typescript
|
||||
import { getLogger, createBusinessEvent } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('order-service');
|
||||
|
||||
logger.business('Order placed', createBusinessEvent(
|
||||
'order',
|
||||
'place',
|
||||
{
|
||||
entity: 'order-123',
|
||||
result: 'success',
|
||||
symbol: 'TSLA',
|
||||
amount: 50000
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
### Security Events
|
||||
|
||||
```typescript
|
||||
import { getLogger, createSecurityEvent } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('auth-service');
|
||||
|
||||
logger.security('Failed login attempt', createSecurityEvent(
|
||||
'authentication',
|
||||
{
|
||||
user: 'john@example.com',
|
||||
result: 'failure',
|
||||
ip: '192.168.1.100',
|
||||
severity: 'medium'
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
## Express Middleware
|
||||
|
||||
### Basic Request Logging
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import { loggingMiddleware } from '@stock-bot/logger';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(loggingMiddleware({
|
||||
serviceName: 'api-gateway',
|
||||
skipPaths: ['/health', '/metrics']
|
||||
}));
|
||||
```
|
||||
|
||||
### Error Logging
|
||||
|
||||
```typescript
|
||||
import { errorLoggingMiddleware, getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('api-gateway');
|
||||
|
||||
// Add after your routes but before error handlers
|
||||
app.use(errorLoggingMiddleware(logger));
|
||||
```
|
||||
|
||||
### Request-scoped Logger
|
||||
|
||||
```typescript
|
||||
import { createRequestLogger, getLogger } from '@stock-bot/logger';
|
||||
|
||||
const baseLogger = getLogger('api-gateway');
|
||||
|
||||
app.use((req, res, next) => {
|
||||
req.logger = createRequestLogger(req, baseLogger);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/api/data', (req, res) => {
|
||||
req.logger.info('Processing data request');
|
||||
// ... handle request ...
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The logger uses configuration from `@stock-bot/config`. Key environment variables:
|
||||
|
||||
```bash
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
LOG_CONSOLE=true
|
||||
LOG_FILE=true
|
||||
LOG_FILE_PATH=./logs
|
||||
|
||||
# Loki
|
||||
LOKI_HOST=localhost
|
||||
LOKI_PORT=3100
|
||||
LOKI_BATCH_SIZE=1024
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Child Loggers
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const parentLogger = getLogger('trading-service');
|
||||
const orderLogger = parentLogger.child({
|
||||
module: 'order-processing',
|
||||
orderId: '12345'
|
||||
});
|
||||
|
||||
orderLogger.info('Order validated'); // Will include parent context
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
// Uses standard getLogger with service-specific configuration
|
||||
const logger = getLogger('custom-service');
|
||||
```
|
||||
|
||||
### Sensitive Data Masking
|
||||
|
||||
```typescript
|
||||
import { sanitizeMetadata, maskSensitiveData } from '@stock-bot/logger';
|
||||
|
||||
const unsafeData = {
|
||||
username: 'john',
|
||||
password: 'secret123',
|
||||
apiKey: 'abc123def456'
|
||||
};
|
||||
|
||||
const safeData = sanitizeMetadata(unsafeData);
|
||||
// { username: 'john', password: '[REDACTED]', apiKey: '[REDACTED]' }
|
||||
|
||||
const message = maskSensitiveData('User API key: abc123def456');
|
||||
// 'User API key: [API_KEY]'
|
||||
```
|
||||
|
||||
### Log Throttling
|
||||
|
||||
```typescript
|
||||
import { LogThrottle } from '@stock-bot/logger';
|
||||
|
||||
const throttle = new LogThrottle(10, 60000); // 10 logs per minute
|
||||
|
||||
if (throttle.shouldLog('error-key')) {
|
||||
logger.error('This error will be throttled');
|
||||
}
|
||||
```
|
||||
|
||||
## Viewing Logs
|
||||
|
||||
### Grafana Dashboard
|
||||
|
||||
1. Start the monitoring stack: `docker-compose up grafana loki`
|
||||
2. Open Grafana at http://localhost:3000
|
||||
3. Use the "Stock Bot Logs" dashboard
|
||||
4. Query logs with LogQL: `{service="your-service"}`
|
||||
|
||||
### Log Files
|
||||
|
||||
When file logging is enabled, logs are written to:
|
||||
- `./logs/{service-name}-YYYY-MM-DD.log` - All logs
|
||||
- `./logs/{service-name}-error-YYYY-MM-DD.log` - Error logs only
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use appropriate log levels**:
|
||||
- `debug`: Detailed development information
|
||||
- `info`: General operational messages
|
||||
- `warn`: Potential issues
|
||||
- `error`: Actual errors requiring attention
|
||||
|
||||
2. **Include context**: Always provide relevant metadata
|
||||
```typescript
|
||||
logger.info('Trade executed', { symbol, quantity, price, orderId });
|
||||
```
|
||||
|
||||
3. **Use structured logging**: Avoid string concatenation
|
||||
```typescript
|
||||
// Good
|
||||
logger.info('User logged in', { userId, ip, userAgent });
|
||||
|
||||
// Avoid
|
||||
logger.info(`User ${userId} logged in from ${ip}`);
|
||||
```
|
||||
|
||||
4. **Handle sensitive data**: Use sanitization utilities
|
||||
```typescript
|
||||
const safeMetadata = sanitizeMetadata(requestData);
|
||||
logger.info('API request', safeMetadata);
|
||||
```
|
||||
|
||||
5. **Use correlation IDs**: Track requests across services
|
||||
```typescript
|
||||
const logger = getLogger('service').child({
|
||||
correlationId: req.headers['x-correlation-id']
|
||||
});
|
||||
```
|
||||
|
||||
## Integration with Services
|
||||
|
||||
To use in your service:
|
||||
|
||||
1. Add dependency to your service's `package.json`:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@stock-bot/logger": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Update your service's `tsconfig.json` references:
|
||||
```json
|
||||
{
|
||||
"references": [
|
||||
{ "path": "../../../libs/logger" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. Import and use:
|
||||
```typescript
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('my-service');
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Logs are batched and sent asynchronously to Loki
|
||||
- File logging uses daily rotation to prevent large files
|
||||
- Console logging can be disabled in production
|
||||
- Use log throttling for high-frequency events
|
||||
- Sensitive data is automatically masked
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Logs not appearing in Loki
|
||||
|
||||
1. Check Loki connection:
|
||||
```bash
|
||||
curl http://localhost:3100/ready
|
||||
```
|
||||
|
||||
2. Verify environment variables:
|
||||
```bash
|
||||
echo $LOKI_HOST $LOKI_PORT
|
||||
```
|
||||
|
||||
3. Check container logs:
|
||||
```bash
|
||||
docker logs stock-bot-loki
|
||||
```
|
||||
|
||||
### High memory usage
|
||||
|
||||
- Reduce `LOKI_BATCH_SIZE` if batching too many logs
|
||||
- Disable file logging if not needed
|
||||
|
||||
### Missing logs
|
||||
|
||||
- Check log level configuration
|
||||
- Verify service name matches expectations
|
||||
- Ensure proper error handling around logger calls
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
# Logger library Bun configuration
|
||||
|
||||
[test]
|
||||
# Configure coverage and test behavior
|
||||
coverage = true
|
||||
timeout = "30s"
|
||||
|
||||
# Configure test environment
|
||||
preload = ["./test/setup.ts"]
|
||||
|
||||
# Environment variables for tests
|
||||
[test.env]
|
||||
NODE_ENV = "test"
|
||||
LOG_LEVEL = "silent"
|
||||
LOG_CONSOLE = "false"
|
||||
LOG_FILE = "false"
|
||||
LOKI_HOST = ""
|
||||
LOKI_URL = ""
|
||||
# Logger library Bun configuration
|
||||
|
||||
[test]
|
||||
# Configure coverage and test behavior
|
||||
coverage = true
|
||||
timeout = "30s"
|
||||
|
||||
# Configure test environment
|
||||
preload = ["./test/setup.ts"]
|
||||
|
||||
# Environment variables for tests
|
||||
[test.env]
|
||||
NODE_ENV = "test"
|
||||
LOG_LEVEL = "silent"
|
||||
LOG_CONSOLE = "false"
|
||||
LOG_FILE = "false"
|
||||
LOKI_HOST = ""
|
||||
LOKI_URL = ""
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
/**
|
||||
* @stock-bot/logger - Simplified logging library
|
||||
*
|
||||
* Main exports for the logger library
|
||||
*/
|
||||
|
||||
// Core logger classes and functions
|
||||
export {
|
||||
Logger,
|
||||
getLogger,
|
||||
shutdownLoggers
|
||||
} from './logger';
|
||||
|
||||
// Type definitions
|
||||
export type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
|
||||
// Default export
|
||||
export { getLogger as default } from './logger';
|
||||
/**
|
||||
* @stock-bot/logger - Simplified logging library
|
||||
*
|
||||
* Main exports for the logger library
|
||||
*/
|
||||
|
||||
// Core logger classes and functions
|
||||
export {
|
||||
Logger,
|
||||
getLogger,
|
||||
shutdownLoggers
|
||||
} from './logger';
|
||||
|
||||
// Type definitions
|
||||
export type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
|
||||
// Default export
|
||||
export { getLogger as default } from './logger';
|
||||
|
|
|
|||
|
|
@ -1,271 +1,271 @@
|
|||
/**
|
||||
* Simplified Pino-based logger for Stock Bot platform
|
||||
*
|
||||
* Features:
|
||||
* - High performance JSON logging with Pino
|
||||
* - Console, file, and Loki transports
|
||||
* - Structured logging with metadata
|
||||
* - Service-specific context
|
||||
*/
|
||||
|
||||
import pino from 'pino';
|
||||
import { loggingConfig, lokiConfig } from '@stock-bot/config';
|
||||
import type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
|
||||
// Simple cache for logger instances
|
||||
const loggerCache = new Map<string, pino.Logger>();
|
||||
console.log('Logger cache initialized: ', loggingConfig.LOG_LEVEL);
|
||||
/**
|
||||
* Create transport configuration
|
||||
*/
|
||||
function createTransports(serviceName: string): any {
|
||||
const targets: any[] = [];
|
||||
// const isDev = loggingConfig.LOG_ENVIRONMENT === 'development';
|
||||
// Console transport
|
||||
if (loggingConfig.LOG_CONSOLE) {
|
||||
targets.push({
|
||||
target: 'pino-pretty',
|
||||
level: loggingConfig.LOG_LEVEL, // Only show errors on console
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'yyyy-mm-dd HH:MM:ss.l',
|
||||
messageFormat: '[{service}{childName}] {msg}',
|
||||
singleLine: true,
|
||||
hideObject: false,
|
||||
ignore: 'pid,hostname,service,environment,version,childName',
|
||||
errorLikeObjectKeys: ['err', 'error'],
|
||||
errorProps: 'message,stack,name,code',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// File transport
|
||||
if (loggingConfig.LOG_FILE) {
|
||||
targets.push({
|
||||
target: 'pino/file',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
destination: `${loggingConfig.LOG_FILE_PATH}/${serviceName}.log`,
|
||||
mkdir: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loki transport
|
||||
if (lokiConfig.LOKI_HOST) {
|
||||
targets.push({
|
||||
target: 'pino-loki',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
host: lokiConfig.LOKI_URL || `http://${lokiConfig.LOKI_HOST}:${lokiConfig.LOKI_PORT}`,
|
||||
labels: {
|
||||
service: serviceName,
|
||||
environment: lokiConfig.LOKI_ENVIRONMENT_LABEL
|
||||
},
|
||||
ignore: 'childName',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return targets.length > 0 ? { targets } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create pino logger
|
||||
*/
|
||||
function getPinoLogger(serviceName: string): pino.Logger {
|
||||
if (!loggerCache.has(serviceName)) {
|
||||
const transport = createTransports(serviceName);
|
||||
|
||||
const config: pino.LoggerOptions = {
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
base: {
|
||||
service: serviceName,
|
||||
environment: loggingConfig.LOG_ENVIRONMENT,
|
||||
version: loggingConfig.LOG_SERVICE_VERSION
|
||||
}
|
||||
};
|
||||
|
||||
if (transport) {
|
||||
config.transport = transport;
|
||||
}
|
||||
|
||||
loggerCache.set(serviceName, pino(config));
|
||||
}
|
||||
|
||||
return loggerCache.get(serviceName)!;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simplified Logger class
|
||||
*/
|
||||
export class Logger {
|
||||
private pino: pino.Logger;
|
||||
private context: LogContext;
|
||||
private serviceName: string;
|
||||
private childName?: string;
|
||||
|
||||
constructor(serviceName: string, context: LogContext = {}) {
|
||||
this.pino = getPinoLogger(serviceName);
|
||||
this.context = context;
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core log method
|
||||
*/
|
||||
private log(level: LogLevel, message: string | object, metadata?: LogMetadata): void {
|
||||
const data = { ...this.context, ...metadata };
|
||||
|
||||
if (typeof message === 'string') {
|
||||
(this.pino as any)[level](data, message);
|
||||
} else {
|
||||
(this.pino as any)[level]({ ...data, data: message }, 'Object logged');
|
||||
}
|
||||
}
|
||||
|
||||
// Simple log level methods
|
||||
debug(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('debug', message, metadata);
|
||||
}
|
||||
|
||||
info(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('info', message, metadata);
|
||||
}
|
||||
|
||||
warn(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('warn', message, metadata);
|
||||
}
|
||||
|
||||
error(message: string | object, metadata?: LogMetadata & { error?: any } | unknown): void {
|
||||
let data: any = {};
|
||||
|
||||
// Handle metadata parameter normalization
|
||||
if (metadata instanceof Error) {
|
||||
// Direct Error object as metadata
|
||||
data = { error: metadata };
|
||||
} else if (metadata !== null && typeof metadata === 'object') {
|
||||
// Object metadata (including arrays, but not null)
|
||||
data = { ...metadata };
|
||||
} else if (metadata !== undefined) {
|
||||
// Primitive values (string, number, boolean, etc.)
|
||||
data = { metadata };
|
||||
}
|
||||
|
||||
// Handle multiple error properties in metadata
|
||||
const errorKeys = ['error', 'err', 'primaryError', 'secondaryError'];
|
||||
errorKeys.forEach(key => {
|
||||
if (data[key]) {
|
||||
const normalizedKey = key === 'error' ? 'err' : `${key}_normalized`;
|
||||
data[normalizedKey] = this.normalizeError(data[key]);
|
||||
|
||||
// Only delete the original 'error' key to maintain other error properties
|
||||
if (key === 'error') {
|
||||
delete data.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.log('error', message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize any error type to a structured format
|
||||
*/
|
||||
private normalizeError(error: any): any {
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
}
|
||||
|
||||
if (error && typeof error === 'object') {
|
||||
// Handle error-like objects
|
||||
return {
|
||||
name: error.name || 'UnknownError',
|
||||
message: error.message || error.toString(),
|
||||
...(error.stack && { stack: error.stack }),
|
||||
...(error.code && { code: error.code }),
|
||||
...(error.status && { status: error.status })
|
||||
};
|
||||
}
|
||||
|
||||
// Handle primitives (string, number, etc.)
|
||||
return {
|
||||
name: 'UnknownError',
|
||||
message: String(error)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Create child logger with additional context
|
||||
*/
|
||||
child(serviceName: string, context?: LogContext): Logger {
|
||||
// Create child logger that shares the same pino instance with additional context
|
||||
const childLogger = Object.create(Logger.prototype);
|
||||
childLogger.serviceName = this.serviceName;
|
||||
childLogger.childName = serviceName;
|
||||
childLogger.context = { ...this.context, ...context };
|
||||
const childBindings = {
|
||||
service: this.serviceName,
|
||||
childName: ' -> ' + serviceName,
|
||||
...(context || childLogger.context)
|
||||
};
|
||||
|
||||
childLogger.pino = this.pino.child(childBindings);
|
||||
return childLogger;
|
||||
// }
|
||||
// childLogger.pino = this.pino.child(context || childLogger.context); // Let pino handle level inheritance naturally
|
||||
// return childLogger;
|
||||
}
|
||||
|
||||
// Getters for service and context
|
||||
getServiceName(): string {
|
||||
return this.serviceName;
|
||||
}
|
||||
getChildName(): string | undefined {
|
||||
return this.childName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main factory function
|
||||
*/
|
||||
export function getLogger(serviceName: string, context?: LogContext): Logger {
|
||||
return new Logger(serviceName, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully shutdown all logger instances
|
||||
* This should be called during application shutdown to ensure all logs are flushed
|
||||
*/
|
||||
export async function shutdownLoggers(): Promise<void> {
|
||||
const flushPromises = Array.from(loggerCache.values()).map(logger => {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (typeof logger.flush === 'function') {
|
||||
logger.flush((err) => {
|
||||
if (err) {
|
||||
console.error('Logger flush error:', err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.allSettled(flushPromises);
|
||||
console.log('All loggers flushed successfully');
|
||||
} catch (error) {
|
||||
console.error('Logger flush failed:', error);
|
||||
} finally {
|
||||
loggerCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for convenience
|
||||
export type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
/**
|
||||
* Simplified Pino-based logger for Stock Bot platform
|
||||
*
|
||||
* Features:
|
||||
* - High performance JSON logging with Pino
|
||||
* - Console, file, and Loki transports
|
||||
* - Structured logging with metadata
|
||||
* - Service-specific context
|
||||
*/
|
||||
|
||||
import pino from 'pino';
|
||||
import { loggingConfig, lokiConfig } from '@stock-bot/config';
|
||||
import type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
|
||||
// Simple cache for logger instances
|
||||
const loggerCache = new Map<string, pino.Logger>();
|
||||
console.log('Logger cache initialized: ', loggingConfig.LOG_LEVEL);
|
||||
/**
|
||||
* Create transport configuration
|
||||
*/
|
||||
function createTransports(serviceName: string): any {
|
||||
const targets: any[] = [];
|
||||
// const isDev = loggingConfig.LOG_ENVIRONMENT === 'development';
|
||||
// Console transport
|
||||
if (loggingConfig.LOG_CONSOLE) {
|
||||
targets.push({
|
||||
target: 'pino-pretty',
|
||||
level: loggingConfig.LOG_LEVEL, // Only show errors on console
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'yyyy-mm-dd HH:MM:ss.l',
|
||||
messageFormat: '[{service}{childName}] {msg}',
|
||||
singleLine: true,
|
||||
hideObject: false,
|
||||
ignore: 'pid,hostname,service,environment,version,childName',
|
||||
errorLikeObjectKeys: ['err', 'error'],
|
||||
errorProps: 'message,stack,name,code',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// File transport
|
||||
if (loggingConfig.LOG_FILE) {
|
||||
targets.push({
|
||||
target: 'pino/file',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
destination: `${loggingConfig.LOG_FILE_PATH}/${serviceName}.log`,
|
||||
mkdir: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loki transport
|
||||
if (lokiConfig.LOKI_HOST) {
|
||||
targets.push({
|
||||
target: 'pino-loki',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
host: lokiConfig.LOKI_URL || `http://${lokiConfig.LOKI_HOST}:${lokiConfig.LOKI_PORT}`,
|
||||
labels: {
|
||||
service: serviceName,
|
||||
environment: lokiConfig.LOKI_ENVIRONMENT_LABEL
|
||||
},
|
||||
ignore: 'childName',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return targets.length > 0 ? { targets } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create pino logger
|
||||
*/
|
||||
function getPinoLogger(serviceName: string): pino.Logger {
|
||||
if (!loggerCache.has(serviceName)) {
|
||||
const transport = createTransports(serviceName);
|
||||
|
||||
const config: pino.LoggerOptions = {
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
base: {
|
||||
service: serviceName,
|
||||
environment: loggingConfig.LOG_ENVIRONMENT,
|
||||
version: loggingConfig.LOG_SERVICE_VERSION
|
||||
}
|
||||
};
|
||||
|
||||
if (transport) {
|
||||
config.transport = transport;
|
||||
}
|
||||
|
||||
loggerCache.set(serviceName, pino(config));
|
||||
}
|
||||
|
||||
return loggerCache.get(serviceName)!;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simplified Logger class
|
||||
*/
|
||||
export class Logger {
|
||||
private pino: pino.Logger;
|
||||
private context: LogContext;
|
||||
private serviceName: string;
|
||||
private childName?: string;
|
||||
|
||||
constructor(serviceName: string, context: LogContext = {}) {
|
||||
this.pino = getPinoLogger(serviceName);
|
||||
this.context = context;
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core log method
|
||||
*/
|
||||
private log(level: LogLevel, message: string | object, metadata?: LogMetadata): void {
|
||||
const data = { ...this.context, ...metadata };
|
||||
|
||||
if (typeof message === 'string') {
|
||||
(this.pino as any)[level](data, message);
|
||||
} else {
|
||||
(this.pino as any)[level]({ ...data, data: message }, 'Object logged');
|
||||
}
|
||||
}
|
||||
|
||||
// Simple log level methods
|
||||
debug(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('debug', message, metadata);
|
||||
}
|
||||
|
||||
info(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('info', message, metadata);
|
||||
}
|
||||
|
||||
warn(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('warn', message, metadata);
|
||||
}
|
||||
|
||||
error(message: string | object, metadata?: LogMetadata & { error?: any } | unknown): void {
|
||||
let data: any = {};
|
||||
|
||||
// Handle metadata parameter normalization
|
||||
if (metadata instanceof Error) {
|
||||
// Direct Error object as metadata
|
||||
data = { error: metadata };
|
||||
} else if (metadata !== null && typeof metadata === 'object') {
|
||||
// Object metadata (including arrays, but not null)
|
||||
data = { ...metadata };
|
||||
} else if (metadata !== undefined) {
|
||||
// Primitive values (string, number, boolean, etc.)
|
||||
data = { metadata };
|
||||
}
|
||||
|
||||
// Handle multiple error properties in metadata
|
||||
const errorKeys = ['error', 'err', 'primaryError', 'secondaryError'];
|
||||
errorKeys.forEach(key => {
|
||||
if (data[key]) {
|
||||
const normalizedKey = key === 'error' ? 'err' : `${key}_normalized`;
|
||||
data[normalizedKey] = this.normalizeError(data[key]);
|
||||
|
||||
// Only delete the original 'error' key to maintain other error properties
|
||||
if (key === 'error') {
|
||||
delete data.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.log('error', message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize any error type to a structured format
|
||||
*/
|
||||
private normalizeError(error: any): any {
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
}
|
||||
|
||||
if (error && typeof error === 'object') {
|
||||
// Handle error-like objects
|
||||
return {
|
||||
name: error.name || 'UnknownError',
|
||||
message: error.message || error.toString(),
|
||||
...(error.stack && { stack: error.stack }),
|
||||
...(error.code && { code: error.code }),
|
||||
...(error.status && { status: error.status })
|
||||
};
|
||||
}
|
||||
|
||||
// Handle primitives (string, number, etc.)
|
||||
return {
|
||||
name: 'UnknownError',
|
||||
message: String(error)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Create child logger with additional context
|
||||
*/
|
||||
child(serviceName: string, context?: LogContext): Logger {
|
||||
// Create child logger that shares the same pino instance with additional context
|
||||
const childLogger = Object.create(Logger.prototype);
|
||||
childLogger.serviceName = this.serviceName;
|
||||
childLogger.childName = serviceName;
|
||||
childLogger.context = { ...this.context, ...context };
|
||||
const childBindings = {
|
||||
service: this.serviceName,
|
||||
childName: ' -> ' + serviceName,
|
||||
...(context || childLogger.context)
|
||||
};
|
||||
|
||||
childLogger.pino = this.pino.child(childBindings);
|
||||
return childLogger;
|
||||
// }
|
||||
// childLogger.pino = this.pino.child(context || childLogger.context); // Let pino handle level inheritance naturally
|
||||
// return childLogger;
|
||||
}
|
||||
|
||||
// Getters for service and context
|
||||
getServiceName(): string {
|
||||
return this.serviceName;
|
||||
}
|
||||
getChildName(): string | undefined {
|
||||
return this.childName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main factory function
|
||||
*/
|
||||
export function getLogger(serviceName: string, context?: LogContext): Logger {
|
||||
return new Logger(serviceName, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully shutdown all logger instances
|
||||
* This should be called during application shutdown to ensure all logs are flushed
|
||||
*/
|
||||
export async function shutdownLoggers(): Promise<void> {
|
||||
const flushPromises = Array.from(loggerCache.values()).map(logger => {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (typeof logger.flush === 'function') {
|
||||
logger.flush((err) => {
|
||||
if (err) {
|
||||
console.error('Logger flush error:', err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.allSettled(flushPromises);
|
||||
console.log('All loggers flushed successfully');
|
||||
} catch (error) {
|
||||
console.error('Logger flush failed:', error);
|
||||
} finally {
|
||||
loggerCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for convenience
|
||||
export type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Simplified type definitions for the logger library
|
||||
*/
|
||||
|
||||
// Standard log levels (simplified to pino defaults)
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||
|
||||
// Context that persists across log calls
|
||||
export interface LogContext {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Metadata for individual log entries
|
||||
export interface LogMetadata {
|
||||
[key: string]: any;
|
||||
}
|
||||
/**
|
||||
* Simplified type definitions for the logger library
|
||||
*/
|
||||
|
||||
// Standard log levels (simplified to pino defaults)
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||
|
||||
// Context that persists across log calls
|
||||
export interface LogContext {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Metadata for individual log entries
|
||||
export interface LogMetadata {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,200 +1,200 @@
|
|||
/**
|
||||
* Advanced Logger Tests
|
||||
*
|
||||
* Tests for advanced logger functionality including complex metadata handling,
|
||||
* child loggers, and advanced error scenarios.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { Logger, shutdownLoggers } from '../src';
|
||||
import { loggerTestHelpers } from './setup';
|
||||
|
||||
describe('Advanced Logger Features', () => {
|
||||
let logger: Logger;
|
||||
let testLoggerInstance: ReturnType<typeof loggerTestHelpers.createTestLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
testLoggerInstance = loggerTestHelpers.createTestLogger('advanced-features');
|
||||
logger = testLoggerInstance.logger;
|
||||
}); afterEach(async () => {
|
||||
testLoggerInstance.clearCapturedLogs();
|
||||
// Clear any global logger cache
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
describe('Complex Metadata Handling', () => {
|
||||
it('should handle nested metadata objects', () => {
|
||||
const complexMetadata = {
|
||||
user: { id: '123', name: 'John Doe' },
|
||||
session: { id: 'sess-456', timeout: 3600 },
|
||||
request: { method: 'POST', path: '/api/test' }
|
||||
};
|
||||
|
||||
logger.info('Complex operation', complexMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].user).toEqual({ id: '123', name: 'John Doe' });
|
||||
expect(logs[0].session).toEqual({ id: 'sess-456', timeout: 3600 });
|
||||
expect(logs[0].request).toEqual({ method: 'POST', path: '/api/test' });
|
||||
});
|
||||
|
||||
it('should handle arrays in metadata', () => {
|
||||
const arrayMetadata = {
|
||||
tags: ['user', 'authentication', 'success'],
|
||||
ids: [1, 2, 3, 4]
|
||||
};
|
||||
|
||||
logger.info('Array metadata test', arrayMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].tags).toEqual(['user', 'authentication', 'success']);
|
||||
expect(logs[0].ids).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('should handle null and undefined metadata values', () => {
|
||||
const nullMetadata = {
|
||||
nullValue: null,
|
||||
undefinedValue: undefined,
|
||||
emptyString: '',
|
||||
zeroValue: 0
|
||||
};
|
||||
|
||||
logger.info('Null metadata test', nullMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].nullValue).toBe(null);
|
||||
expect(logs[0].emptyString).toBe('');
|
||||
expect(logs[0].zeroValue).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Child Logger Functionality', () => {
|
||||
it('should create child logger with additional context', () => {
|
||||
const childLogger = logger.child({
|
||||
component: 'auth-service',
|
||||
version: '1.2.3'
|
||||
});
|
||||
|
||||
childLogger.info('Child logger message');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].component).toBe('auth-service');
|
||||
expect(logs[0].version).toBe('1.2.3');
|
||||
expect(logs[0].msg).toBe('Child logger message');
|
||||
});
|
||||
|
||||
it('should support nested child loggers', () => {
|
||||
const childLogger = logger.child({ level1: 'parent' });
|
||||
const grandChildLogger = childLogger.child({ level2: 'child' });
|
||||
|
||||
grandChildLogger.warn('Nested child message');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level1).toBe('parent');
|
||||
expect(logs[0].level2).toBe('child');
|
||||
expect(logs[0].level).toBe('warn');
|
||||
});
|
||||
|
||||
it('should merge child context with log metadata', () => {
|
||||
const childLogger = logger.child({ service: 'api' });
|
||||
|
||||
childLogger.info('Request processed', {
|
||||
requestId: 'req-789',
|
||||
duration: 150
|
||||
});
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].service).toBe('api');
|
||||
expect(logs[0].requestId).toBe('req-789');
|
||||
expect(logs[0].duration).toBe(150);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Advanced Error Handling', () => {
|
||||
it('should handle Error objects with custom properties', () => {
|
||||
const customError = new Error('Custom error message');
|
||||
(customError as any).code = 'ERR_CUSTOM';
|
||||
(customError as any).statusCode = 500;
|
||||
|
||||
logger.error('Custom error occurred', { error: customError });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Custom error occurred');
|
||||
});
|
||||
|
||||
it('should handle multiple errors in metadata', () => {
|
||||
const error1 = new Error('First error');
|
||||
const error2 = new Error('Second error');
|
||||
|
||||
logger.error('Multiple errors', {
|
||||
primaryError: error1,
|
||||
secondaryError: error2,
|
||||
context: 'batch processing'
|
||||
});
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].context).toBe('batch processing');
|
||||
});
|
||||
it('should handle error objects with circular references', () => {
|
||||
const errorWithCircular: any = { name: 'CircularError', message: 'Circular reference error' };
|
||||
// Create a simple circular reference
|
||||
errorWithCircular.self = errorWithCircular;
|
||||
|
||||
// Should not throw when logging circular references
|
||||
expect(() => {
|
||||
logger.error('Circular error test', { error: errorWithCircular });
|
||||
}).not.toThrow();
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
|
||||
// Clean up circular reference to prevent memory issues
|
||||
delete errorWithCircular.self;
|
||||
});
|
||||
});
|
||||
describe('Performance and Edge Cases', () => {
|
||||
it('should handle moderate metadata objects', () => {
|
||||
const moderateMetadata: any = {};
|
||||
for (let i = 0; i < 10; i++) {
|
||||
moderateMetadata[`key${i}`] = `value${i}`;
|
||||
}
|
||||
|
||||
logger.debug('Moderate metadata test', moderateMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].key0).toBe('value0');
|
||||
expect(logs[0].key9).toBe('value9');
|
||||
});
|
||||
|
||||
it('should handle special characters in messages', () => {
|
||||
const specialMessage = 'Special chars: 🚀 ñ ü';
|
||||
|
||||
logger.info(specialMessage);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].msg).toBe(specialMessage);
|
||||
});
|
||||
|
||||
it('should handle empty and whitespace-only messages', () => {
|
||||
logger.info('');
|
||||
logger.info(' ');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(2);
|
||||
expect(logs[0].msg).toBe('');
|
||||
expect(logs[1].msg).toBe(' ');
|
||||
});
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Advanced Logger Tests
|
||||
*
|
||||
* Tests for advanced logger functionality including complex metadata handling,
|
||||
* child loggers, and advanced error scenarios.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { Logger, shutdownLoggers } from '../src';
|
||||
import { loggerTestHelpers } from './setup';
|
||||
|
||||
describe('Advanced Logger Features', () => {
|
||||
let logger: Logger;
|
||||
let testLoggerInstance: ReturnType<typeof loggerTestHelpers.createTestLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
testLoggerInstance = loggerTestHelpers.createTestLogger('advanced-features');
|
||||
logger = testLoggerInstance.logger;
|
||||
}); afterEach(async () => {
|
||||
testLoggerInstance.clearCapturedLogs();
|
||||
// Clear any global logger cache
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
describe('Complex Metadata Handling', () => {
|
||||
it('should handle nested metadata objects', () => {
|
||||
const complexMetadata = {
|
||||
user: { id: '123', name: 'John Doe' },
|
||||
session: { id: 'sess-456', timeout: 3600 },
|
||||
request: { method: 'POST', path: '/api/test' }
|
||||
};
|
||||
|
||||
logger.info('Complex operation', complexMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].user).toEqual({ id: '123', name: 'John Doe' });
|
||||
expect(logs[0].session).toEqual({ id: 'sess-456', timeout: 3600 });
|
||||
expect(logs[0].request).toEqual({ method: 'POST', path: '/api/test' });
|
||||
});
|
||||
|
||||
it('should handle arrays in metadata', () => {
|
||||
const arrayMetadata = {
|
||||
tags: ['user', 'authentication', 'success'],
|
||||
ids: [1, 2, 3, 4]
|
||||
};
|
||||
|
||||
logger.info('Array metadata test', arrayMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].tags).toEqual(['user', 'authentication', 'success']);
|
||||
expect(logs[0].ids).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('should handle null and undefined metadata values', () => {
|
||||
const nullMetadata = {
|
||||
nullValue: null,
|
||||
undefinedValue: undefined,
|
||||
emptyString: '',
|
||||
zeroValue: 0
|
||||
};
|
||||
|
||||
logger.info('Null metadata test', nullMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].nullValue).toBe(null);
|
||||
expect(logs[0].emptyString).toBe('');
|
||||
expect(logs[0].zeroValue).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Child Logger Functionality', () => {
|
||||
it('should create child logger with additional context', () => {
|
||||
const childLogger = logger.child({
|
||||
component: 'auth-service',
|
||||
version: '1.2.3'
|
||||
});
|
||||
|
||||
childLogger.info('Child logger message');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].component).toBe('auth-service');
|
||||
expect(logs[0].version).toBe('1.2.3');
|
||||
expect(logs[0].msg).toBe('Child logger message');
|
||||
});
|
||||
|
||||
it('should support nested child loggers', () => {
|
||||
const childLogger = logger.child({ level1: 'parent' });
|
||||
const grandChildLogger = childLogger.child({ level2: 'child' });
|
||||
|
||||
grandChildLogger.warn('Nested child message');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level1).toBe('parent');
|
||||
expect(logs[0].level2).toBe('child');
|
||||
expect(logs[0].level).toBe('warn');
|
||||
});
|
||||
|
||||
it('should merge child context with log metadata', () => {
|
||||
const childLogger = logger.child({ service: 'api' });
|
||||
|
||||
childLogger.info('Request processed', {
|
||||
requestId: 'req-789',
|
||||
duration: 150
|
||||
});
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].service).toBe('api');
|
||||
expect(logs[0].requestId).toBe('req-789');
|
||||
expect(logs[0].duration).toBe(150);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Advanced Error Handling', () => {
|
||||
it('should handle Error objects with custom properties', () => {
|
||||
const customError = new Error('Custom error message');
|
||||
(customError as any).code = 'ERR_CUSTOM';
|
||||
(customError as any).statusCode = 500;
|
||||
|
||||
logger.error('Custom error occurred', { error: customError });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Custom error occurred');
|
||||
});
|
||||
|
||||
it('should handle multiple errors in metadata', () => {
|
||||
const error1 = new Error('First error');
|
||||
const error2 = new Error('Second error');
|
||||
|
||||
logger.error('Multiple errors', {
|
||||
primaryError: error1,
|
||||
secondaryError: error2,
|
||||
context: 'batch processing'
|
||||
});
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].context).toBe('batch processing');
|
||||
});
|
||||
it('should handle error objects with circular references', () => {
|
||||
const errorWithCircular: any = { name: 'CircularError', message: 'Circular reference error' };
|
||||
// Create a simple circular reference
|
||||
errorWithCircular.self = errorWithCircular;
|
||||
|
||||
// Should not throw when logging circular references
|
||||
expect(() => {
|
||||
logger.error('Circular error test', { error: errorWithCircular });
|
||||
}).not.toThrow();
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
|
||||
// Clean up circular reference to prevent memory issues
|
||||
delete errorWithCircular.self;
|
||||
});
|
||||
});
|
||||
describe('Performance and Edge Cases', () => {
|
||||
it('should handle moderate metadata objects', () => {
|
||||
const moderateMetadata: any = {};
|
||||
for (let i = 0; i < 10; i++) {
|
||||
moderateMetadata[`key${i}`] = `value${i}`;
|
||||
}
|
||||
|
||||
logger.debug('Moderate metadata test', moderateMetadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].key0).toBe('value0');
|
||||
expect(logs[0].key9).toBe('value9');
|
||||
});
|
||||
|
||||
it('should handle special characters in messages', () => {
|
||||
const specialMessage = 'Special chars: 🚀 ñ ü';
|
||||
|
||||
logger.info(specialMessage);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].msg).toBe(specialMessage);
|
||||
});
|
||||
|
||||
it('should handle empty and whitespace-only messages', () => {
|
||||
logger.info('');
|
||||
logger.info(' ');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(2);
|
||||
expect(logs[0].msg).toBe('');
|
||||
expect(logs[1].msg).toBe(' ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,169 +1,169 @@
|
|||
/**
|
||||
* Basic Logger Tests
|
||||
*
|
||||
* Tests for the core logger functionality and utilities.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { Logger, getLogger, shutdownLoggers } from '../src';
|
||||
import { loggerTestHelpers } from './setup';
|
||||
|
||||
describe('Basic Logger Tests', () => {
|
||||
let logger: Logger;
|
||||
let testLoggerInstance: ReturnType<typeof loggerTestHelpers.createTestLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
testLoggerInstance = loggerTestHelpers.createTestLogger('utils-test');
|
||||
logger = testLoggerInstance.logger;
|
||||
});
|
||||
afterEach(async () => {
|
||||
testLoggerInstance.clearCapturedLogs();
|
||||
// Clear any global logger cache
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
describe('Logger Factory Functions', () => {
|
||||
it('should create logger with getLogger', () => {
|
||||
expect(typeof getLogger).toBe('function');
|
||||
|
||||
// Test that getLogger doesn't throw
|
||||
expect(() => {
|
||||
const anotherTestLoggerInstance = loggerTestHelpers.createTestLogger('factory-test');
|
||||
anotherTestLoggerInstance.logger.info('Factory test');
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Logger Methods', () => {
|
||||
it('should have all required logging methods', () => {
|
||||
expect(typeof logger.debug).toBe('function');
|
||||
expect(typeof logger.info).toBe('function');
|
||||
expect(typeof logger.warn).toBe('function');
|
||||
expect(typeof logger.error).toBe('function');
|
||||
expect(typeof logger.child).toBe('function');
|
||||
});
|
||||
|
||||
it('should log with different message types', () => {
|
||||
// String message
|
||||
logger.info('String message');
|
||||
|
||||
// Object message
|
||||
logger.info({ event: 'object_message', data: 'test' });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(2);
|
||||
expect(logs[0].msg).toBe('String message');
|
||||
expect(logs[1].level).toBe('info');
|
||||
});
|
||||
|
||||
it('should handle metadata correctly', () => {
|
||||
const metadata = {
|
||||
userId: 'user123',
|
||||
sessionId: 'session456',
|
||||
requestId: 'req789'
|
||||
};
|
||||
|
||||
logger.info('Request processed', metadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].userId).toBe('user123');
|
||||
expect(logs[0].sessionId).toBe('session456');
|
||||
expect(logs[0].requestId).toBe('req789');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Child Logger Functionality', () => {
|
||||
it('should create child loggers with additional context', () => {
|
||||
const childLogger = logger.child({
|
||||
module: 'payment',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
childLogger.info('Payment processed');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].msg).toBe('Payment processed');
|
||||
});
|
||||
|
||||
it('should inherit service name in child loggers', () => {
|
||||
const childLogger = logger.child({ operation: 'test' });
|
||||
childLogger.info('Child operation');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].service).toBe('utils-test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Normalization', () => {
|
||||
it('should handle Error objects', () => {
|
||||
const error = new Error('Test error');
|
||||
error.stack = 'Error stack trace';
|
||||
|
||||
logger.error('Error test', error);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
});
|
||||
|
||||
it('should handle error-like objects', () => {
|
||||
const errorLike = {
|
||||
name: 'ValidationError',
|
||||
message: 'Invalid input',
|
||||
code: 'VALIDATION_FAILED'
|
||||
};
|
||||
|
||||
logger.error('Validation failed', { error: errorLike });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
});
|
||||
|
||||
it('should handle primitive error values', () => {
|
||||
logger.error('Simple error', { error: 'Error string' });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Service Context', () => {
|
||||
it('should include service name in all logs', () => {
|
||||
logger.debug('Debug message');
|
||||
logger.info('Info message');
|
||||
logger.warn('Warn message');
|
||||
logger.error('Error message');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(4);
|
||||
|
||||
logs.forEach(log => {
|
||||
expect(log.service).toBe('utils-test');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support different service names', () => {
|
||||
const logger1Instance = loggerTestHelpers.createTestLogger('service-one');
|
||||
const logger2Instance = loggerTestHelpers.createTestLogger('service-two');
|
||||
|
||||
logger1Instance.logger.info('Message from service one');
|
||||
logger2Instance.logger.info('Message from service two');
|
||||
|
||||
// Since each logger instance has its own capture, we check them separately
|
||||
// or combine them if that's the desired test logic.
|
||||
// For this test, it seems we want to ensure they are separate.
|
||||
const logs1 = logger1Instance.getCapturedLogs();
|
||||
expect(logs1.length).toBe(1);
|
||||
expect(logs1[0].service).toBe('service-one');
|
||||
|
||||
const logs2 = logger2Instance.getCapturedLogs();
|
||||
expect(logs2.length).toBe(1);
|
||||
expect(logs2[0].service).toBe('service-two');
|
||||
});
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Basic Logger Tests
|
||||
*
|
||||
* Tests for the core logger functionality and utilities.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { Logger, getLogger, shutdownLoggers } from '../src';
|
||||
import { loggerTestHelpers } from './setup';
|
||||
|
||||
describe('Basic Logger Tests', () => {
|
||||
let logger: Logger;
|
||||
let testLoggerInstance: ReturnType<typeof loggerTestHelpers.createTestLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
testLoggerInstance = loggerTestHelpers.createTestLogger('utils-test');
|
||||
logger = testLoggerInstance.logger;
|
||||
});
|
||||
afterEach(async () => {
|
||||
testLoggerInstance.clearCapturedLogs();
|
||||
// Clear any global logger cache
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
describe('Logger Factory Functions', () => {
|
||||
it('should create logger with getLogger', () => {
|
||||
expect(typeof getLogger).toBe('function');
|
||||
|
||||
// Test that getLogger doesn't throw
|
||||
expect(() => {
|
||||
const anotherTestLoggerInstance = loggerTestHelpers.createTestLogger('factory-test');
|
||||
anotherTestLoggerInstance.logger.info('Factory test');
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Logger Methods', () => {
|
||||
it('should have all required logging methods', () => {
|
||||
expect(typeof logger.debug).toBe('function');
|
||||
expect(typeof logger.info).toBe('function');
|
||||
expect(typeof logger.warn).toBe('function');
|
||||
expect(typeof logger.error).toBe('function');
|
||||
expect(typeof logger.child).toBe('function');
|
||||
});
|
||||
|
||||
it('should log with different message types', () => {
|
||||
// String message
|
||||
logger.info('String message');
|
||||
|
||||
// Object message
|
||||
logger.info({ event: 'object_message', data: 'test' });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(2);
|
||||
expect(logs[0].msg).toBe('String message');
|
||||
expect(logs[1].level).toBe('info');
|
||||
});
|
||||
|
||||
it('should handle metadata correctly', () => {
|
||||
const metadata = {
|
||||
userId: 'user123',
|
||||
sessionId: 'session456',
|
||||
requestId: 'req789'
|
||||
};
|
||||
|
||||
logger.info('Request processed', metadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].userId).toBe('user123');
|
||||
expect(logs[0].sessionId).toBe('session456');
|
||||
expect(logs[0].requestId).toBe('req789');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Child Logger Functionality', () => {
|
||||
it('should create child loggers with additional context', () => {
|
||||
const childLogger = logger.child({
|
||||
module: 'payment',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
childLogger.info('Payment processed');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].msg).toBe('Payment processed');
|
||||
});
|
||||
|
||||
it('should inherit service name in child loggers', () => {
|
||||
const childLogger = logger.child({ operation: 'test' });
|
||||
childLogger.info('Child operation');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].service).toBe('utils-test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Normalization', () => {
|
||||
it('should handle Error objects', () => {
|
||||
const error = new Error('Test error');
|
||||
error.stack = 'Error stack trace';
|
||||
|
||||
logger.error('Error test', error);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
});
|
||||
|
||||
it('should handle error-like objects', () => {
|
||||
const errorLike = {
|
||||
name: 'ValidationError',
|
||||
message: 'Invalid input',
|
||||
code: 'VALIDATION_FAILED'
|
||||
};
|
||||
|
||||
logger.error('Validation failed', { error: errorLike });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
});
|
||||
|
||||
it('should handle primitive error values', () => {
|
||||
logger.error('Simple error', { error: 'Error string' });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Service Context', () => {
|
||||
it('should include service name in all logs', () => {
|
||||
logger.debug('Debug message');
|
||||
logger.info('Info message');
|
||||
logger.warn('Warn message');
|
||||
logger.error('Error message');
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(4);
|
||||
|
||||
logs.forEach(log => {
|
||||
expect(log.service).toBe('utils-test');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support different service names', () => {
|
||||
const logger1Instance = loggerTestHelpers.createTestLogger('service-one');
|
||||
const logger2Instance = loggerTestHelpers.createTestLogger('service-two');
|
||||
|
||||
logger1Instance.logger.info('Message from service one');
|
||||
logger2Instance.logger.info('Message from service two');
|
||||
|
||||
// Since each logger instance has its own capture, we check them separately
|
||||
// or combine them if that's the desired test logic.
|
||||
// For this test, it seems we want to ensure they are separate.
|
||||
const logs1 = logger1Instance.getCapturedLogs();
|
||||
expect(logs1.length).toBe(1);
|
||||
expect(logs1[0].service).toBe('service-one');
|
||||
|
||||
const logs2 = logger2Instance.getCapturedLogs();
|
||||
expect(logs2.length).toBe(1);
|
||||
expect(logs2[0].service).toBe('service-two');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,192 +1,192 @@
|
|||
/**
|
||||
* Logger Integration Tests
|
||||
*
|
||||
* Tests the core functionality of the simplified @stock-bot/logger package.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import {
|
||||
Logger,
|
||||
getLogger,
|
||||
shutdownLoggers
|
||||
} from '../src';
|
||||
import { loggerTestHelpers } from './setup';
|
||||
|
||||
describe('Logger Integration Tests', () => {
|
||||
let logger: Logger;
|
||||
let testLoggerInstance: ReturnType<typeof loggerTestHelpers.createTestLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
testLoggerInstance = loggerTestHelpers.createTestLogger('integration-test');
|
||||
logger = testLoggerInstance.logger;
|
||||
});
|
||||
afterEach(async () => {
|
||||
testLoggerInstance.clearCapturedLogs();
|
||||
// Clear any global logger cache
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
describe('Core Logger Functionality', () => {
|
||||
it('should log messages at different levels', () => {
|
||||
// Test multiple log levels
|
||||
logger.debug('Debug message');
|
||||
logger.info('Info message');
|
||||
logger.warn('Warning message');
|
||||
logger.error('Error message');
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify logs were captured
|
||||
expect(logs.length).toBe(4);
|
||||
expect(logs[0].level).toBe('debug');
|
||||
expect(logs[0].msg).toBe('Debug message');
|
||||
expect(logs[1].level).toBe('info');
|
||||
expect(logs[1].msg).toBe('Info message');
|
||||
expect(logs[2].level).toBe('warn');
|
||||
expect(logs[2].msg).toBe('Warning message');
|
||||
expect(logs[3].level).toBe('error');
|
||||
expect(logs[3].msg).toBe('Error message');
|
||||
});
|
||||
|
||||
it('should log objects as structured logs', () => {
|
||||
// Log an object
|
||||
logger.info('User logged in', { userId: '123', action: 'login' });
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify structured log
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].userId).toBe('123');
|
||||
expect(logs[0].action).toBe('login');
|
||||
expect(logs[0].msg).toBe('User logged in');
|
||||
});
|
||||
|
||||
it('should handle error objects in error logs', () => {
|
||||
const testError = new Error('Test error message');
|
||||
|
||||
// Log error with error object
|
||||
logger.error('Something went wrong', { error: testError });
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify error was logged
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Something went wrong');
|
||||
});
|
||||
|
||||
it('should create child loggers with additional context', () => {
|
||||
// Create a child logger with additional context
|
||||
const childLogger = logger.child({
|
||||
transactionId: 'tx-789',
|
||||
operation: 'payment'
|
||||
});
|
||||
|
||||
// Log with child logger
|
||||
childLogger.info('Child logger test');
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify child logger logged something
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].msg).toBe('Child logger test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Factory Functions', () => {
|
||||
it('should export factory functions', () => {
|
||||
// Verify that the factory functions are exported and callable
|
||||
expect(typeof getLogger).toBe('function');
|
||||
});
|
||||
|
||||
it('should create different logger instances', () => {
|
||||
const logger1Instance = loggerTestHelpers.createTestLogger('service-1');
|
||||
const logger2Instance = loggerTestHelpers.createTestLogger('service-2');
|
||||
|
||||
logger1Instance.logger.info('Message from service 1');
|
||||
logger2Instance.logger.info('Message from service 2');
|
||||
|
||||
const logs1 = logger1Instance.getCapturedLogs();
|
||||
expect(logs1.length).toBe(1);
|
||||
expect(logs1[0].service).toBe('service-1');
|
||||
|
||||
const logs2 = logger2Instance.getCapturedLogs();
|
||||
expect(logs2.length).toBe(1);
|
||||
expect(logs2[0].service).toBe('service-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should normalize Error objects', () => {
|
||||
const error = new Error('Test error');
|
||||
error.stack = 'Error stack trace';
|
||||
|
||||
logger.error('Error occurred', error);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Error occurred');
|
||||
});
|
||||
|
||||
it('should handle error-like objects', () => {
|
||||
const errorLike = {
|
||||
name: 'CustomError',
|
||||
message: 'Custom error message',
|
||||
code: 'ERR_CUSTOM'
|
||||
};
|
||||
|
||||
logger.error('Custom error occurred', { error: errorLike });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Custom error occurred');
|
||||
});
|
||||
|
||||
it('should handle primitive error values', () => {
|
||||
logger.error('String error occurred', { error: 'Simple string error' });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('String error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metadata Handling', () => {
|
||||
it('should include metadata in logs', () => {
|
||||
const metadata = {
|
||||
requestId: 'req-123',
|
||||
userId: 'user-456',
|
||||
operation: 'data-fetch'
|
||||
};
|
||||
|
||||
logger.info('Operation completed', metadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].requestId).toBe('req-123');
|
||||
expect(logs[0].userId).toBe('user-456');
|
||||
expect(logs[0].operation).toBe('data-fetch');
|
||||
});
|
||||
|
||||
it('should handle object messages', () => {
|
||||
const objectMessage = {
|
||||
event: 'user_action',
|
||||
action: 'login',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
logger.info(objectMessage);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('info');
|
||||
});
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Logger Integration Tests
|
||||
*
|
||||
* Tests the core functionality of the simplified @stock-bot/logger package.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import {
|
||||
Logger,
|
||||
getLogger,
|
||||
shutdownLoggers
|
||||
} from '../src';
|
||||
import { loggerTestHelpers } from './setup';
|
||||
|
||||
describe('Logger Integration Tests', () => {
|
||||
let logger: Logger;
|
||||
let testLoggerInstance: ReturnType<typeof loggerTestHelpers.createTestLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
testLoggerInstance = loggerTestHelpers.createTestLogger('integration-test');
|
||||
logger = testLoggerInstance.logger;
|
||||
});
|
||||
afterEach(async () => {
|
||||
testLoggerInstance.clearCapturedLogs();
|
||||
// Clear any global logger cache
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
describe('Core Logger Functionality', () => {
|
||||
it('should log messages at different levels', () => {
|
||||
// Test multiple log levels
|
||||
logger.debug('Debug message');
|
||||
logger.info('Info message');
|
||||
logger.warn('Warning message');
|
||||
logger.error('Error message');
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify logs were captured
|
||||
expect(logs.length).toBe(4);
|
||||
expect(logs[0].level).toBe('debug');
|
||||
expect(logs[0].msg).toBe('Debug message');
|
||||
expect(logs[1].level).toBe('info');
|
||||
expect(logs[1].msg).toBe('Info message');
|
||||
expect(logs[2].level).toBe('warn');
|
||||
expect(logs[2].msg).toBe('Warning message');
|
||||
expect(logs[3].level).toBe('error');
|
||||
expect(logs[3].msg).toBe('Error message');
|
||||
});
|
||||
|
||||
it('should log objects as structured logs', () => {
|
||||
// Log an object
|
||||
logger.info('User logged in', { userId: '123', action: 'login' });
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify structured log
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].userId).toBe('123');
|
||||
expect(logs[0].action).toBe('login');
|
||||
expect(logs[0].msg).toBe('User logged in');
|
||||
});
|
||||
|
||||
it('should handle error objects in error logs', () => {
|
||||
const testError = new Error('Test error message');
|
||||
|
||||
// Log error with error object
|
||||
logger.error('Something went wrong', { error: testError });
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify error was logged
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Something went wrong');
|
||||
});
|
||||
|
||||
it('should create child loggers with additional context', () => {
|
||||
// Create a child logger with additional context
|
||||
const childLogger = logger.child({
|
||||
transactionId: 'tx-789',
|
||||
operation: 'payment'
|
||||
});
|
||||
|
||||
// Log with child logger
|
||||
childLogger.info('Child logger test');
|
||||
|
||||
// Get captured logs
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
|
||||
// Verify child logger logged something
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].msg).toBe('Child logger test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Factory Functions', () => {
|
||||
it('should export factory functions', () => {
|
||||
// Verify that the factory functions are exported and callable
|
||||
expect(typeof getLogger).toBe('function');
|
||||
});
|
||||
|
||||
it('should create different logger instances', () => {
|
||||
const logger1Instance = loggerTestHelpers.createTestLogger('service-1');
|
||||
const logger2Instance = loggerTestHelpers.createTestLogger('service-2');
|
||||
|
||||
logger1Instance.logger.info('Message from service 1');
|
||||
logger2Instance.logger.info('Message from service 2');
|
||||
|
||||
const logs1 = logger1Instance.getCapturedLogs();
|
||||
expect(logs1.length).toBe(1);
|
||||
expect(logs1[0].service).toBe('service-1');
|
||||
|
||||
const logs2 = logger2Instance.getCapturedLogs();
|
||||
expect(logs2.length).toBe(1);
|
||||
expect(logs2[0].service).toBe('service-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should normalize Error objects', () => {
|
||||
const error = new Error('Test error');
|
||||
error.stack = 'Error stack trace';
|
||||
|
||||
logger.error('Error occurred', error);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Error occurred');
|
||||
});
|
||||
|
||||
it('should handle error-like objects', () => {
|
||||
const errorLike = {
|
||||
name: 'CustomError',
|
||||
message: 'Custom error message',
|
||||
code: 'ERR_CUSTOM'
|
||||
};
|
||||
|
||||
logger.error('Custom error occurred', { error: errorLike });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('Custom error occurred');
|
||||
});
|
||||
|
||||
it('should handle primitive error values', () => {
|
||||
logger.error('String error occurred', { error: 'Simple string error' });
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('error');
|
||||
expect(logs[0].msg).toBe('String error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metadata Handling', () => {
|
||||
it('should include metadata in logs', () => {
|
||||
const metadata = {
|
||||
requestId: 'req-123',
|
||||
userId: 'user-456',
|
||||
operation: 'data-fetch'
|
||||
};
|
||||
|
||||
logger.info('Operation completed', metadata);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].requestId).toBe('req-123');
|
||||
expect(logs[0].userId).toBe('user-456');
|
||||
expect(logs[0].operation).toBe('data-fetch');
|
||||
});
|
||||
|
||||
it('should handle object messages', () => {
|
||||
const objectMessage = {
|
||||
event: 'user_action',
|
||||
action: 'login',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
logger.info(objectMessage);
|
||||
|
||||
const logs = testLoggerInstance.getCapturedLogs();
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].level).toBe('info');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,137 +1,137 @@
|
|||
/**
|
||||
* Logger Test Setup
|
||||
*
|
||||
* Setup file specific to Logger library tests.
|
||||
* Provides utilities and mocks for testing logging operations.
|
||||
*/
|
||||
|
||||
import { Logger, LogMetadata, shutdownLoggers } from '../src';
|
||||
import { afterAll, afterEach, beforeAll, beforeEach } from 'bun:test';
|
||||
|
||||
// Store original console methods
|
||||
const originalConsole = {
|
||||
log: console.log,
|
||||
info: console.info,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
// Create a test logger helper
|
||||
export const loggerTestHelpers = {
|
||||
|
||||
|
||||
/**
|
||||
* Mock Loki transport
|
||||
*/
|
||||
mockLokiTransport: () => ({
|
||||
on: () => {},
|
||||
write: () => {}
|
||||
}),
|
||||
/**
|
||||
* Create a mock Hono context for middleware tests
|
||||
*/ createHonoContextMock: (options: any = {}) => {
|
||||
// Default path and method
|
||||
const path = options.path || '/test';
|
||||
const method = options.method || 'GET';
|
||||
|
||||
// Create request headers
|
||||
const headerEntries = Object.entries(options.req?.headers || {});
|
||||
const headerMap = new Map(headerEntries);
|
||||
const rawHeaders = new Headers();
|
||||
headerEntries.forEach(([key, value]) => rawHeaders.set(key, value as string));
|
||||
|
||||
// Create request with standard properties needed for middleware
|
||||
const req = {
|
||||
method,
|
||||
url: `http://localhost${path}`,
|
||||
path,
|
||||
raw: {
|
||||
url: `http://localhost${path}`,
|
||||
method,
|
||||
headers: rawHeaders
|
||||
},
|
||||
query: {},
|
||||
param: () => undefined,
|
||||
header: (name: string) => rawHeaders.get(name.toLowerCase()),
|
||||
headers: headerMap,
|
||||
...options.req
|
||||
};
|
||||
|
||||
// Create mock response
|
||||
const res = {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
body: null,
|
||||
headers: new Map(),
|
||||
clone: function() { return { ...this, text: async () => JSON.stringify(this.body) }; },
|
||||
text: async () => JSON.stringify(res.body),
|
||||
...options.res
|
||||
};
|
||||
|
||||
// Create context with all required Hono methods
|
||||
const c: any = {
|
||||
req,
|
||||
env: {},
|
||||
res,
|
||||
header: (name: string, value: string) => {
|
||||
c.res.headers.set(name.toLowerCase(), value);
|
||||
return c;
|
||||
},
|
||||
get: (key: string) => c[key],
|
||||
set: (key: string, value: any) => { c[key] = value; return c; },
|
||||
status: (code: number) => { c.res.status = code; return c; },
|
||||
json: (body: any) => { c.res.body = body; return c; },
|
||||
executionCtx: { waitUntil: (fn: Function) => { fn(); } }
|
||||
};
|
||||
|
||||
return c;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a mock Next function for middleware tests
|
||||
*/
|
||||
createNextMock: () => {
|
||||
return async () => {
|
||||
// Do nothing, simulate middleware completion
|
||||
return;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Setup environment before tests
|
||||
beforeAll(() => {
|
||||
// Don't let real logs through during tests
|
||||
console.log = () => {};
|
||||
console.info = () => {};
|
||||
console.warn = () => {};
|
||||
console.error = () => {};
|
||||
console.debug = () => {};
|
||||
|
||||
// Override NODE_ENV for tests
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
// Disable real logging during tests
|
||||
process.env.LOG_LEVEL = 'silent';
|
||||
process.env.LOG_CONSOLE = 'false';
|
||||
process.env.LOG_FILE = 'false';
|
||||
|
||||
// Mock Loki config to prevent real connections
|
||||
process.env.LOKI_HOST = '';
|
||||
process.env.LOKI_URL = '';
|
||||
});
|
||||
|
||||
// Clean up after each test
|
||||
afterEach(async () => {
|
||||
// Clear logger cache to prevent state pollution between tests
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
// Restore everything after tests
|
||||
afterAll(() => {
|
||||
console.log = originalConsole.log;
|
||||
console.info = originalConsole.info;
|
||||
console.warn = originalConsole.warn;
|
||||
console.error = originalConsole.error;
|
||||
console.debug = originalConsole.debug;
|
||||
});
|
||||
/**
|
||||
* Logger Test Setup
|
||||
*
|
||||
* Setup file specific to Logger library tests.
|
||||
* Provides utilities and mocks for testing logging operations.
|
||||
*/
|
||||
|
||||
import { Logger, LogMetadata, shutdownLoggers } from '../src';
|
||||
import { afterAll, afterEach, beforeAll, beforeEach } from 'bun:test';
|
||||
|
||||
// Store original console methods
|
||||
const originalConsole = {
|
||||
log: console.log,
|
||||
info: console.info,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
// Create a test logger helper
|
||||
export const loggerTestHelpers = {
|
||||
|
||||
|
||||
/**
|
||||
* Mock Loki transport
|
||||
*/
|
||||
mockLokiTransport: () => ({
|
||||
on: () => {},
|
||||
write: () => {}
|
||||
}),
|
||||
/**
|
||||
* Create a mock Hono context for middleware tests
|
||||
*/ createHonoContextMock: (options: any = {}) => {
|
||||
// Default path and method
|
||||
const path = options.path || '/test';
|
||||
const method = options.method || 'GET';
|
||||
|
||||
// Create request headers
|
||||
const headerEntries = Object.entries(options.req?.headers || {});
|
||||
const headerMap = new Map(headerEntries);
|
||||
const rawHeaders = new Headers();
|
||||
headerEntries.forEach(([key, value]) => rawHeaders.set(key, value as string));
|
||||
|
||||
// Create request with standard properties needed for middleware
|
||||
const req = {
|
||||
method,
|
||||
url: `http://localhost${path}`,
|
||||
path,
|
||||
raw: {
|
||||
url: `http://localhost${path}`,
|
||||
method,
|
||||
headers: rawHeaders
|
||||
},
|
||||
query: {},
|
||||
param: () => undefined,
|
||||
header: (name: string) => rawHeaders.get(name.toLowerCase()),
|
||||
headers: headerMap,
|
||||
...options.req
|
||||
};
|
||||
|
||||
// Create mock response
|
||||
const res = {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
body: null,
|
||||
headers: new Map(),
|
||||
clone: function() { return { ...this, text: async () => JSON.stringify(this.body) }; },
|
||||
text: async () => JSON.stringify(res.body),
|
||||
...options.res
|
||||
};
|
||||
|
||||
// Create context with all required Hono methods
|
||||
const c: any = {
|
||||
req,
|
||||
env: {},
|
||||
res,
|
||||
header: (name: string, value: string) => {
|
||||
c.res.headers.set(name.toLowerCase(), value);
|
||||
return c;
|
||||
},
|
||||
get: (key: string) => c[key],
|
||||
set: (key: string, value: any) => { c[key] = value; return c; },
|
||||
status: (code: number) => { c.res.status = code; return c; },
|
||||
json: (body: any) => { c.res.body = body; return c; },
|
||||
executionCtx: { waitUntil: (fn: Function) => { fn(); } }
|
||||
};
|
||||
|
||||
return c;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a mock Next function for middleware tests
|
||||
*/
|
||||
createNextMock: () => {
|
||||
return async () => {
|
||||
// Do nothing, simulate middleware completion
|
||||
return;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Setup environment before tests
|
||||
beforeAll(() => {
|
||||
// Don't let real logs through during tests
|
||||
console.log = () => {};
|
||||
console.info = () => {};
|
||||
console.warn = () => {};
|
||||
console.error = () => {};
|
||||
console.debug = () => {};
|
||||
|
||||
// Override NODE_ENV for tests
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
// Disable real logging during tests
|
||||
process.env.LOG_LEVEL = 'silent';
|
||||
process.env.LOG_CONSOLE = 'false';
|
||||
process.env.LOG_FILE = 'false';
|
||||
|
||||
// Mock Loki config to prevent real connections
|
||||
process.env.LOKI_HOST = '';
|
||||
process.env.LOKI_URL = '';
|
||||
});
|
||||
|
||||
// Clean up after each test
|
||||
afterEach(async () => {
|
||||
// Clear logger cache to prevent state pollution between tests
|
||||
await shutdownLoggers();
|
||||
});
|
||||
|
||||
// Restore everything after tests
|
||||
afterAll(() => {
|
||||
console.log = originalConsole.log;
|
||||
console.info = originalConsole.info;
|
||||
console.warn = originalConsole.warn;
|
||||
console.error = originalConsole.error;
|
||||
console.debug = originalConsole.debug;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"references": [
|
||||
{ "path": "../types" },
|
||||
{ "path": "../config" }
|
||||
]
|
||||
}
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"references": [
|
||||
{ "path": "../types" },
|
||||
{ "path": "../config" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["@stock-bot/types#build", "@stock-bot/config#build"],
|
||||
"outputs": ["dist/**"],
|
||||
"inputs": ["src/**", "package.json", "tsconfig.json", "!**/*.test.ts", "!**/*.spec.ts", "!**/test/**", "!**/tests/**", "!**/__tests__/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["@stock-bot/types#build", "@stock-bot/config#build"],
|
||||
"outputs": ["dist/**"],
|
||||
"inputs": ["src/**", "package.json", "tsconfig.json", "!**/*.test.ts", "!**/*.spec.ts", "!**/test/**", "!**/tests/**", "!**/__tests__/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue