Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ LOG_LEVEL=info
# The directory to store log files
LOG_DIR=logs

# leave unset (defaults to false in production)
ENABLE_FILE_LOGGING=true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider setting ENABLE_FILE_LOGGING to false by default in the example environment file to prevent accidental file logging in production environments.


# -------------------------------------
# Kafka Configuration
# -------------------------------------
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Open the `.env` file and configure the variables for your environment. It is cru
PORT=3000
LOG_LEVEL=debug
LOG_DIR=logs
# Enable file logging in production environments (default: false)
# In development environments, file logging is enabled by default
ENABLE_FILE_LOGGING=false

# -------------------------------------
# Kafka Configuration
Expand Down Expand Up @@ -206,6 +209,31 @@ tsconfig.json # TypeScript config
README.md # Documentation
```

## Logging Configuration

The autopilot service uses a flexible logging strategy that adapts to different deployment environments:

### Development Environment
- **File Logging**: Enabled by default for debugging purposes
- **Console Logging**: Always enabled with colorized output
- **Log Files**: Created in the `logs/` directory (configurable via `LOG_DIR`)

### Production Environment (ECS)
- **File Logging**: Disabled by default to support read-only filesystems
- **Console Logging**: Primary logging method, suitable for container log collection
- **Override**: Set `ENABLE_FILE_LOGGING=true` to enable file logging if the filesystem is writable

### Environment Variables
- `LOG_LEVEL`: Controls log verbosity (`error`, `warn`, `info`, `debug`, `verbose`)
- `LOG_DIR`: Directory for log files (default: `logs`)
- `ENABLE_FILE_LOGGING`: Boolean flag to control file logging in production

### ECS Deployment Notes
When deploying to ECS, ensure:
1. `NODE_ENV=production` is set
2. `ENABLE_FILE_LOGGING` is either unset or set to `false` for read-only filesystems
3. Container logging is configured to collect stdout/stderr logs

## Further Documentation

For more detailed technical information, please see the documents in the `docs/` directory:
Expand All @@ -228,3 +256,13 @@ For more detailed technical information, please see the documents in the `docs/`
```

- **API Authentication Errors**: Ensure Auth0 credentials (`AUTH0_URL`, `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`) in your `.env` file are valid and not expired.

- **ECS Deployment - Read-only Filesystem Error**: If the application fails to start in ECS with errors about creating a 'logs' directory:
- Ensure `NODE_ENV=production` is set in your ECS task definition
- Verify `ENABLE_FILE_LOGGING` is not set or is set to `false`
- Check that your ECS task definition is configured to collect logs from stdout/stderr

- **Logging Issues**:
- **No log files in development**: Check that `ENABLE_FILE_LOGGING` is not set to `false`
- **File permission errors**: Ensure the application has write permissions to the `LOG_DIR` directory
- **Missing logs in production**: Verify your container logging configuration is collecting stdout/stderr output
2 changes: 2 additions & 0 deletions src/common/constants/config.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface AppConfig {
DEFAULT_NODE_ENV: 'development' | 'production' | 'test';
DEFAULT_LOG_LEVEL: 'error' | 'warn' | 'info' | 'debug' | 'verbose';
DEFAULT_LOG_DIR: string;
DEFAULT_ENABLE_FILE_LOGGING: boolean;
}

export interface KafkaConfig {
Expand Down Expand Up @@ -51,6 +52,7 @@ export const CONFIG: Config = {
DEFAULT_NODE_ENV: 'development',
DEFAULT_LOG_LEVEL: 'info',
DEFAULT_LOG_DIR: 'logs',
DEFAULT_ENABLE_FILE_LOGGING: false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making DEFAULT_ENABLE_FILE_LOGGING configurable through environment variables or configuration files to allow flexibility in different deployment environments.

},
KAFKA: {
DEFAULT_CLIENT_ID: 'autopilot-service',
Expand Down
105 changes: 70 additions & 35 deletions src/common/services/logger.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,86 @@ export class LoggerService implements NestLoggerService {
}

private createWinstonLogger(): Logger {
const baseTransports: any[] = [

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using any[] for baseTransports. Consider specifying a more precise type for the array elements, such as TransportStream[], to improve type safety and maintainability.

new transports.Console({
level: process.env.LOG_LEVEL || CONFIG.APP.DEFAULT_LOG_LEVEL,
format: format.combine(
format.colorize(),
format.printf(({ timestamp, level, message, context, ...meta }) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format.printf function is used to format log messages. Ensure that all possible types of message and context are handled to avoid unexpected runtime errors. Consider adding validation or default handling for unexpected types.

const formattedTimestamp =
timestamp instanceof Date
? timestamp.toISOString()
: String(timestamp);
const formattedContext =
typeof context === 'string' ? context : 'App';
const formattedMessage =
typeof message === 'string'
? message
: typeof message === 'object' && message !== null
? JSON.stringify(message)
: String(message);

return `${formattedTimestamp} [${formattedContext}] ${String(level)}: ${formattedMessage}${
Object.keys(meta).length
? ' ' + JSON.stringify(meta, null, 1)
: ''
}`;
}),
),
}),
];

// Add file transports conditionally based on environment
const shouldEnableFileLogging = this.shouldEnableFileLogging();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method shouldEnableFileLogging is called to determine if file logging should be enabled. Ensure this method is implemented correctly and returns a boolean value. Consider adding error handling or logging if the method fails or returns an unexpected value.

if (shouldEnableFileLogging) {
try {
baseTransports.push(
new transports.File({
filename: `${process.env.LOG_DIR || CONFIG.APP.DEFAULT_LOG_DIR}/error.log`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file path for the log files is constructed using environment variables or default configuration. Ensure that process.env.LOG_DIR and CONFIG.APP.DEFAULT_LOG_DIR are correctly set and accessible. Consider validating these paths to prevent potential runtime errors.

level: 'error',
}),
new transports.File({
filename: `${process.env.LOG_DIR || CONFIG.APP.DEFAULT_LOG_DIR}/combined.log`,
}),
);
} catch (error) {
// If file transport creation fails (e.g., read-only filesystem),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling for file transport creation logs a warning to the console. Consider using the existing logger to log this warning instead of console.warn to maintain consistency in logging practices.

// log a warning but continue with console-only logging
console.warn(
'Failed to initialize file transports, falling back to console-only logging:',
error instanceof Error ? error.message : String(error),
);
}
}

return createLogger({
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.json(),
),
defaultMeta: { context: this.context },
transports: [
new transports.Console({
level: process.env.LOG_LEVEL || CONFIG.APP.DEFAULT_LOG_LEVEL,
format: format.combine(
format.colorize(),
format.printf(({ timestamp, level, message, context, ...meta }) => {
const formattedTimestamp =
timestamp instanceof Date
? timestamp.toISOString()
: String(timestamp);
const formattedContext =
typeof context === 'string' ? context : 'App';
const formattedMessage =
typeof message === 'string'
? message
: typeof message === 'object' && message !== null
? JSON.stringify(message)
: String(message);

return `${formattedTimestamp} [${formattedContext}] ${String(level)}: ${formattedMessage}${
Object.keys(meta).length
? ' ' + JSON.stringify(meta, null, 1)
: ''
}`;
}),
),
}),
new transports.File({
filename: `${process.env.LOG_DIR || CONFIG.APP.DEFAULT_LOG_DIR}/error.log`,
level: 'error',
}),
new transports.File({
filename: `${process.env.LOG_DIR || CONFIG.APP.DEFAULT_LOG_DIR}/combined.log`,
}),
],
transports: baseTransports,
});
}

/**
* Determines whether file logging should be enabled based on environment configuration
*/
private shouldEnableFileLogging(): boolean {
const nodeEnv = process.env.NODE_ENV;
const enableFileLogging = process.env.ENABLE_FILE_LOGGING;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding validation for the ENABLE_FILE_LOGGING environment variable to ensure it only accepts 'true' or 'false' values. This can prevent unexpected behavior if the variable is set to an invalid value.


// In production, only enable file logging if explicitly requested
if (nodeEnv === 'production') {
return enableFileLogging === 'true';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition enableFileLogging === 'true' is a string comparison. Ensure that ENABLE_FILE_LOGGING is always set as a string in the environment variables to avoid potential issues.

}

// In non-production environments (development, test), enable file logging by default
// unless explicitly disabled
return enableFileLogging !== 'false';
}

private formatMessage(message: unknown): string {
if (typeof message === 'string') {
return message;
Expand Down
1 change: 1 addition & 0 deletions src/config/sections/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export default registerAs('app', () => ({
logging: {
level: process.env.LOG_LEVEL || 'info',
directory: process.env.LOG_DIR || 'logs',
enableFileLogging: process.env.ENABLE_FILE_LOGGING === 'true',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more explicit check for the environment variable, such as process.env.ENABLE_FILE_LOGGING?.toLowerCase() === 'true', to ensure that the comparison is case-insensitive and handles undefined values gracefully.

},
}));
1 change: 1 addition & 0 deletions src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const validationSchema = Joi.object({
.valid('error', 'warn', 'info', 'debug', 'verbose')
.default('info'),
LOG_DIR: Joi.string().default('logs'),
ENABLE_FILE_LOGGING: Joi.boolean().default(false),

// Kafka Configuration
KAFKA_BROKERS: Joi.string().required(),
Expand Down