diff --git a/docker/.env.example b/docker/.env.example index a88dcb2059..c91f12ae3d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -36,6 +36,11 @@ REDIS_HOSTNAME=immich_redis UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup +################################################################################### +# Log message level - [simple|verbose] +################################################################################### + +LOG_LEVEL=simple ################################################################################### diff --git a/server/apps/microservices/src/microservices.module.ts b/server/apps/microservices/src/microservices.module.ts index 2c890d481e..46b3b6afe0 100644 --- a/server/apps/microservices/src/microservices.module.ts +++ b/server/apps/microservices/src/microservices.module.ts @@ -13,7 +13,7 @@ import { } from '@app/job/constants/queue-name.constant'; import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module'; import { MicroservicesService } from './microservices.service'; @@ -40,42 +40,48 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' }, }), }), - BullModule.registerQueue({ - name: thumbnailGeneratorQueueName, - defaultJobOptions: { - attempts: 3, - removeOnComplete: true, - removeOnFail: false, + BullModule.registerQueue( + { + name: thumbnailGeneratorQueueName, + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, }, - }, { - name: assetUploadedQueueName, - defaultJobOptions: { - attempts: 3, - removeOnComplete: true, - removeOnFail: false, + { + name: assetUploadedQueueName, + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, }, - }, { - name: metadataExtractionQueueName, - defaultJobOptions: { - attempts: 3, - removeOnComplete: true, - removeOnFail: false, + { + name: metadataExtractionQueueName, + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, }, - }, { - name: videoConversionQueueName, - defaultJobOptions: { - attempts: 3, - removeOnComplete: true, - removeOnFail: false, + { + name: videoConversionQueueName, + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, }, - }, { - name: generateChecksumQueueName, - defaultJobOptions: { - attempts: 3, - removeOnComplete: true, - removeOnFail: false, + { + name: generateChecksumQueueName, + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, }, - }), + ), CommunicationModule, ], controllers: [], @@ -86,6 +92,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' MetadataExtractionProcessor, VideoTranscodeProcessor, GenerateChecksumProcessor, + ConfigService, ], exports: [], }) diff --git a/server/apps/microservices/src/processors/metadata-extraction.processor.ts b/server/apps/microservices/src/processors/metadata-extraction.processor.ts index 195ffdbca3..799dd4ac6b 100644 --- a/server/apps/microservices/src/processors/metadata-extraction.processor.ts +++ b/server/apps/microservices/src/processors/metadata-extraction.processor.ts @@ -1,3 +1,4 @@ +import { ImmichLogLevel } from '@app/common/constants/log-level.constant'; import { AssetEntity } from '@app/database/entities/asset.entity'; import { ExifEntity } from '@app/database/entities/exif.entity'; import { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; @@ -16,6 +17,7 @@ import { MapiResponse } from '@mapbox/mapbox-sdk/lib/classes/mapi-response'; import mapboxGeocoding, { GeocodeService } from '@mapbox/mapbox-sdk/services/geocoding'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import axios from 'axios'; import { Job } from 'bull'; @@ -28,6 +30,7 @@ import { Repository } from 'typeorm/repository/Repository'; @Processor(metadataExtractionQueueName) export class MetadataExtractionProcessor { private geocodingClient?: GeocodeService; + private logLevel: ImmichLogLevel; constructor( @InjectRepository(AssetEntity) @@ -38,12 +41,16 @@ export class MetadataExtractionProcessor { @InjectRepository(SmartInfoEntity) private smartInfoRepository: Repository, + + private configService: ConfigService, ) { if (process.env.ENABLE_MAPBOX == 'true' && process.env.MAPBOX_KEY) { this.geocodingClient = mapboxGeocoding({ accessToken: process.env.MAPBOX_KEY, }); } + + this.logLevel = this.configService.get('LOG_LEVEL') || ImmichLogLevel.SIMPLE; } @Process(exifExtractionProcessorName) @@ -139,6 +146,10 @@ export class MetadataExtractionProcessor { await this.exifRepository.save(newExif); } catch (e) { Logger.error(`Error extracting EXIF ${String(e)}`, 'extractExif'); + + if (this.logLevel === ImmichLogLevel.VERBOSE) { + console.trace('Error extracting EXIF', e); + } } } diff --git a/server/apps/microservices/src/processors/thumbnail.processor.ts b/server/apps/microservices/src/processors/thumbnail.processor.ts index c4bb211136..530f526b17 100644 --- a/server/apps/microservices/src/processors/thumbnail.processor.ts +++ b/server/apps/microservices/src/processors/thumbnail.processor.ts @@ -1,3 +1,4 @@ +import { ImmichLogLevel } from '@app/common/constants/log-level.constant'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; import { WebpGeneratorProcessor, @@ -11,6 +12,7 @@ import { } from '@app/job'; import { InjectQueue, Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import { mapAsset } from 'apps/immich/src/api-v1/asset/response-dto/asset-response.dto'; import { Job, Queue } from 'bull'; @@ -23,6 +25,8 @@ import { CommunicationGateway } from '../../../immich/src/api-v1/communication/c @Processor(thumbnailGeneratorQueueName) export class ThumbnailGeneratorProcessor { + private logLevel: ImmichLogLevel; + constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, @@ -34,7 +38,11 @@ export class ThumbnailGeneratorProcessor { @InjectQueue(metadataExtractionQueueName) private metadataExtractionQueue: Queue, - ) {} + + private configService: ConfigService, + ) { + this.logLevel = this.configService.get('LOG_LEVEL') || ImmichLogLevel.SIMPLE; + } @Process({ name: generateJPEGThumbnailProcessorName, concurrency: 3 }) async generateJPEGThumbnail(job: Job) { @@ -51,8 +59,16 @@ export class ThumbnailGeneratorProcessor { const jpegThumbnailPath = resizePath + originalFilename + '.jpeg'; if (asset.type == AssetType.IMAGE) { - await sharp(asset.originalPath).resize(1440, 2560, { fit: 'inside' }).jpeg().rotate().toFile(jpegThumbnailPath); - await this.assetRepository.update({ id: asset.id }, { resizePath: jpegThumbnailPath }); + try { + await sharp(asset.originalPath).resize(1440, 2560, { fit: 'inside' }).jpeg().rotate().toFile(jpegThumbnailPath); + await this.assetRepository.update({ id: asset.id }, { resizePath: jpegThumbnailPath }); + } catch (error) { + Logger.error('Failed to generate jpeg thumbnail for asset: ' + asset.id); + + if (this.logLevel == ImmichLogLevel.VERBOSE) { + console.trace('Failed to generate jpeg thumbnail for asset', error); + } + } // Update resize path to send to generate webp queue asset.resizePath = jpegThumbnailPath; @@ -105,7 +121,15 @@ export class ThumbnailGeneratorProcessor { const webpPath = asset.resizePath.replace('jpeg', 'webp'); - await sharp(asset.resizePath).resize(250).webp().rotate().toFile(webpPath); - await this.assetRepository.update({ id: asset.id }, { webpPath: webpPath }); + try { + await sharp(asset.resizePath).resize(250).webp().rotate().toFile(webpPath); + await this.assetRepository.update({ id: asset.id }, { webpPath: webpPath }); + } catch (error) { + Logger.error('Failed to generate webp thumbnail for asset: ' + asset.id); + + if (this.logLevel == ImmichLogLevel.VERBOSE) { + console.trace('Failed to generate webp thumbnail for asset', error); + } + } } } diff --git a/server/libs/common/src/config/app.config.ts b/server/libs/common/src/config/app.config.ts index 304bee8643..4b6f1d91e1 100644 --- a/server/libs/common/src/config/app.config.ts +++ b/server/libs/common/src/config/app.config.ts @@ -16,5 +16,6 @@ export const immichAppConfig: ConfigModuleOptions = { then: Joi.string().optional().allow(null, ''), otherwise: Joi.string().required(), }), + LOG_LEVEL: Joi.string().optional().valid('simple', 'verbose').default('simple'), }), }; diff --git a/server/libs/common/src/constants/log-level.constant.ts b/server/libs/common/src/constants/log-level.constant.ts new file mode 100644 index 0000000000..219959e642 --- /dev/null +++ b/server/libs/common/src/constants/log-level.constant.ts @@ -0,0 +1,4 @@ +export enum ImmichLogLevel { + SIMPLE = 'simple', + VERBOSE = 'verbose', +}