1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-23 02:06:15 +02:00

feat(server): correlation id via injected logger (#8823)

* feat(server): correlation id via injected logger

* feat: cid response header
This commit is contained in:
Jason Rasmussen 2024-04-15 19:39:06 -04:00 committed by GitHub
parent 95e67a7b1d
commit 2db76034b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 152 additions and 26 deletions

View File

@ -48,6 +48,7 @@
"luxon": "^3.4.2", "luxon": "^3.4.2",
"mnemonist": "^0.39.8", "mnemonist": "^0.39.8",
"nest-commander": "^3.11.1", "nest-commander": "^3.11.1",
"nestjs-cls": "^4.3.0",
"nestjs-otel": "^5.1.5", "nestjs-otel": "^5.1.5",
"openid-client": "^5.4.3", "openid-client": "^5.4.3",
"pg": "^8.11.3", "pg": "^8.11.3",
@ -10685,6 +10686,20 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/nestjs-cls": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-4.3.0.tgz",
"integrity": "sha512-MVTun6tqCZih8AJXRj8uBuuFyJhQrIA9m9fStiQjbBXUkE3BrlMRvmLzyw8UcneB3xtFFTfwkAh5PYKRulyaOg==",
"engines": {
"node": ">=16"
},
"peerDependencies": {
"@nestjs/common": "> 7.0.0 < 11",
"@nestjs/core": "> 7.0.0 < 11",
"reflect-metadata": "*",
"rxjs": ">= 7"
}
},
"node_modules/nestjs-otel": { "node_modules/nestjs-otel": {
"version": "5.1.5", "version": "5.1.5",
"resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-5.1.5.tgz", "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-5.1.5.tgz",
@ -22266,6 +22281,12 @@
} }
} }
}, },
"nestjs-cls": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-4.3.0.tgz",
"integrity": "sha512-MVTun6tqCZih8AJXRj8uBuuFyJhQrIA9m9fStiQjbBXUkE3BrlMRvmLzyw8UcneB3xtFFTfwkAh5PYKRulyaOg==",
"requires": {}
},
"nestjs-otel": { "nestjs-otel": {
"version": "5.1.5", "version": "5.1.5",
"resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-5.1.5.tgz", "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-5.1.5.tgz",

View File

@ -72,6 +72,7 @@
"luxon": "^3.4.2", "luxon": "^3.4.2",
"mnemonist": "^0.39.8", "mnemonist": "^0.39.8",
"nest-commander": "^3.11.1", "nest-commander": "^3.11.1",
"nestjs-cls": "^4.3.0",
"nestjs-otel": "^5.1.5", "nestjs-otel": "^5.1.5",
"openid-client": "^5.4.3", "openid-client": "^5.4.3",
"pg": "^8.11.3", "pg": "^8.11.3",

View File

@ -5,9 +5,10 @@ import { APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ClsModule } from 'nestjs-cls';
import { OpenTelemetryModule } from 'nestjs-otel'; import { OpenTelemetryModule } from 'nestjs-otel';
import { commands } from 'src/commands'; import { commands } from 'src/commands';
import { bullConfig, bullQueues, immichAppConfig } from 'src/config'; import { bullConfig, bullQueues, clsConfig, immichAppConfig } from 'src/config';
import { controllers } from 'src/controllers'; import { controllers } from 'src/controllers';
import { databaseConfig } from 'src/database.config'; import { databaseConfig } from 'src/database.config';
import { entities } from 'src/entities'; import { entities } from 'src/entities';
@ -19,10 +20,8 @@ import { services } from 'src/services';
import { ApiService } from 'src/services/api.service'; import { ApiService } from 'src/services/api.service';
import { MicroservicesService } from 'src/services/microservices.service'; import { MicroservicesService } from 'src/services/microservices.service';
import { otelConfig } from 'src/utils/instrumentation'; import { otelConfig } from 'src/utils/instrumentation';
import { ImmichLogger } from 'src/utils/logger';
const providers = [ImmichLogger]; const common = [...services, ...repositories];
const common = [...services, ...providers, ...repositories];
const middleware = [ const middleware = [
FileUploadInterceptor, FileUploadInterceptor,
@ -34,6 +33,7 @@ const middleware = [
const imports = [ const imports = [
BullModule.forRoot(bullConfig), BullModule.forRoot(bullConfig),
BullModule.registerQueue(...bullQueues), BullModule.registerQueue(...bullQueues),
ClsModule.forRoot(clsConfig),
ConfigModule.forRoot(immichAppConfig), ConfigModule.forRoot(immichAppConfig),
EventEmitterModule.forRoot(), EventEmitterModule.forRoot(),
OpenTelemetryModule.forRoot(otelConfig), OpenTelemetryModule.forRoot(otelConfig),

View File

@ -1,8 +1,10 @@
import { RegisterQueueOptions } from '@nestjs/bullmq'; import { RegisterQueueOptions } from '@nestjs/bullmq';
import { ConfigModuleOptions } from '@nestjs/config'; import { ConfigModuleOptions } from '@nestjs/config';
import { QueueOptions } from 'bullmq'; import { QueueOptions } from 'bullmq';
import { Request, Response } from 'express';
import { RedisOptions } from 'ioredis'; import { RedisOptions } from 'ioredis';
import Joi from 'joi'; import Joi from 'joi';
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
import { LogLevel } from 'src/entities/system-config.entity'; import { LogLevel } from 'src/entities/system-config.entity';
import { QueueName } from 'src/interfaces/job.interface'; import { QueueName } from 'src/interfaces/job.interface';
@ -69,3 +71,17 @@ export const bullConfig: QueueOptions = {
}; };
export const bullQueues: RegisterQueueOptions[] = Object.values(QueueName).map((name) => ({ name })); export const bullQueues: RegisterQueueOptions[] = Object.values(QueueName).map((name) => ({ name }));
export const clsConfig: ClsModuleOptions = {
middleware: {
mount: true,
generateId: true,
setup: (cls, req: Request, res: Response) => {
const headerValues = req.headers['x-immich-cid'];
const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues;
const cid = headerValue || cls.get(CLS_ID);
cls.set(CLS_ID, headerValue);
res.header('x-immich-cid', cid);
},
},
};

View File

@ -0,0 +1,15 @@
import { LogLevel } from 'src/entities/system-config.entity';
export const ILoggerRepository = 'ILoggerRepository';
export interface ILoggerRepository {
setContext(message: string): void;
setLogLevel(level: LogLevel): void;
verbose(message: any, ...args: any): void;
debug(message: any, ...args: any): void;
log(message: any, ...args: any): void;
warn(message: any, ...args: any): void;
error(message: any, ...args: any): void;
fatal(message: any, ...args: any): void;
}

View File

@ -8,20 +8,21 @@ import sirv from 'sirv';
import { ApiModule, ImmichAdminModule, MicroservicesModule } from 'src/app.module'; import { ApiModule, ImmichAdminModule, MicroservicesModule } from 'src/app.module';
import { WEB_ROOT, envName, excludePaths, isDev, serverVersion } from 'src/constants'; import { WEB_ROOT, envName, excludePaths, isDev, serverVersion } from 'src/constants';
import { LogLevel } from 'src/entities/system-config.entity'; import { LogLevel } from 'src/entities/system-config.entity';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
import { ApiService } from 'src/services/api.service'; import { ApiService } from 'src/services/api.service';
import { otelSDK } from 'src/utils/instrumentation'; import { otelSDK } from 'src/utils/instrumentation';
import { ImmichLogger } from 'src/utils/logger';
import { useSwagger } from 'src/utils/misc'; import { useSwagger } from 'src/utils/misc';
async function bootstrapMicroservices() { async function bootstrapMicroservices() {
const logger = new ImmichLogger('ImmichMicroservice'); otelSDK.start();
const host = String(process.env.HOST || '0.0.0.0'); const host = String(process.env.HOST || '0.0.0.0');
const port = Number(process.env.MICROSERVICES_PORT) || 3002; const port = Number(process.env.MICROSERVICES_PORT) || 3002;
otelSDK.start();
const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true }); const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
app.useLogger(app.get(ImmichLogger)); const logger = app.get(ILoggerRepository);
logger.setContext('ImmichMicroservice');
app.useLogger(logger);
app.useWebSocketAdapter(new WebSocketAdapter(app)); app.useWebSocketAdapter(new WebSocketAdapter(app));
await app.listen(port, host); await app.listen(port, host);
@ -30,14 +31,15 @@ async function bootstrapMicroservices() {
} }
async function bootstrapApi() { async function bootstrapApi() {
const logger = new ImmichLogger('ImmichServer'); otelSDK.start();
const host = String(process.env.HOST || '0.0.0.0'); const host = String(process.env.HOST || '0.0.0.0');
const port = Number(process.env.SERVER_PORT) || 3001; const port = Number(process.env.SERVER_PORT) || 3001;
otelSDK.start();
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true }); const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
const logger = app.get(ILoggerRepository);
app.useLogger(app.get(ImmichLogger)); logger.setContext('ImmichServer');
app.useLogger(logger);
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
app.set('etag', 'strong'); app.set('etag', 'strong');
app.use(cookieParser()); app.use(cookieParser());

View File

@ -1,6 +1,7 @@
import { import {
CanActivate, CanActivate,
ExecutionContext, ExecutionContext,
Inject,
Injectable, Injectable,
SetMetadata, SetMetadata,
applyDecorators, applyDecorators,
@ -11,8 +12,8 @@ import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } fr
import { Request } from 'express'; import { Request } from 'express';
import { IMMICH_API_KEY_NAME } from 'src/constants'; import { IMMICH_API_KEY_NAME } from 'src/constants';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { AuthService, LoginDetails } from 'src/services/auth.service'; import { AuthService, LoginDetails } from 'src/services/auth.service';
import { ImmichLogger } from 'src/utils/logger';
import { UAParser } from 'ua-parser-js'; import { UAParser } from 'ua-parser-js';
export enum Metadata { export enum Metadata {
@ -79,12 +80,13 @@ export interface AuthRequest extends Request {
@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
private logger = new ImmichLogger(AuthGuard.name);
constructor( constructor(
@Inject(ILoggerRepository) private logger: ILoggerRepository,
private reflector: Reflector, private reflector: Reflector,
private authService: AuthService, private authService: AuthService,
) {} ) {
this.logger.setContext(AuthGuard.name);
}
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const targets = [context.getHandler(), context.getClass()]; const targets = [context.getHandler(), context.getClass()];

View File

@ -2,17 +2,20 @@ import {
CallHandler, CallHandler,
ExecutionContext, ExecutionContext,
HttpException, HttpException,
Inject,
Injectable, Injectable,
InternalServerErrorException, InternalServerErrorException,
NestInterceptor, NestInterceptor,
} from '@nestjs/common'; } from '@nestjs/common';
import { Observable, catchError, throwError } from 'rxjs'; import { Observable, catchError, throwError } from 'rxjs';
import { ImmichLogger } from 'src/utils/logger'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { isConnectionAborted, routeToErrorMessage } from 'src/utils/misc'; import { isConnectionAborted, routeToErrorMessage } from 'src/utils/misc';
@Injectable() @Injectable()
export class ErrorInterceptor implements NestInterceptor { export class ErrorInterceptor implements NestInterceptor {
private logger = new ImmichLogger(ErrorInterceptor.name); constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
this.logger.setContext(ErrorInterceptor.name);
}
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> { intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
return next.handle().pipe( return next.handle().pipe(

View File

@ -11,6 +11,7 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository } from 'src/interfaces/job.interface'; import { IJobRepository } from 'src/interfaces/job.interface';
import { ILibraryRepository } from 'src/interfaces/library.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { IMediaRepository } from 'src/interfaces/media.interface'; import { IMediaRepository } from 'src/interfaces/media.interface';
import { IMemoryRepository } from 'src/interfaces/memory.interface'; import { IMemoryRepository } from 'src/interfaces/memory.interface';
@ -41,6 +42,7 @@ import { DatabaseRepository } from 'src/repositories/database.repository';
import { EventRepository } from 'src/repositories/event.repository'; import { EventRepository } from 'src/repositories/event.repository';
import { JobRepository } from 'src/repositories/job.repository'; import { JobRepository } from 'src/repositories/job.repository';
import { LibraryRepository } from 'src/repositories/library.repository'; import { LibraryRepository } from 'src/repositories/library.repository';
import { LoggerRepository } from 'src/repositories/logger.repository';
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; import { MachineLearningRepository } from 'src/repositories/machine-learning.repository';
import { MediaRepository } from 'src/repositories/media.repository'; import { MediaRepository } from 'src/repositories/media.repository';
import { MemoryRepository } from 'src/repositories/memory.repository'; import { MemoryRepository } from 'src/repositories/memory.repository';
@ -71,6 +73,7 @@ export const repositories = [
{ provide: IDatabaseRepository, useClass: DatabaseRepository }, { provide: IDatabaseRepository, useClass: DatabaseRepository },
{ provide: IEventRepository, useClass: EventRepository }, { provide: IEventRepository, useClass: EventRepository },
{ provide: IJobRepository, useClass: JobRepository }, { provide: IJobRepository, useClass: JobRepository },
{ provide: ILoggerRepository, useClass: LoggerRepository },
{ provide: ILibraryRepository, useClass: LibraryRepository }, { provide: ILibraryRepository, useClass: LibraryRepository },
{ provide: IKeyRepository, useClass: ApiKeyRepository }, { provide: IKeyRepository, useClass: ApiKeyRepository },
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository }, { provide: IMachineLearningRepository, useClass: MachineLearningRepository },

View File

@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import { LogLevel } from 'src/entities/system-config.entity';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ImmichLogger } from 'src/utils/logger';
@Injectable()
export class LoggerRepository extends ImmichLogger implements ILoggerRepository {
constructor(private cls: ClsService) {
super(LoggerRepository.name);
}
protected formatContext(context: string): string {
let formattedContext = super.formatContext(context);
const correlationId = this.cls?.getId();
if (correlationId && this.isLevelEnabled(LogLevel.VERBOSE)) {
formattedContext += `[${correlationId}] `;
}
return formattedContext;
}
setLogLevel(level: LogLevel): void {
ImmichLogger.setLogLevel(level);
}
}

View File

@ -8,6 +8,7 @@ import { UserEntity } from 'src/entities/user.entity';
import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILibraryRepository } from 'src/interfaces/library.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; import { IUserTokenRepository } from 'src/interfaces/user-token.interface';
@ -23,6 +24,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock';
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { newUserTokenRepositoryMock } from 'test/repositories/user-token.repository.mock'; import { newUserTokenRepositoryMock } from 'test/repositories/user-token.repository.mock';
@ -60,6 +62,7 @@ describe('AuthService', () => {
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: jest.Mocked<ICryptoRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: jest.Mocked<IUserRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>; let libraryMock: jest.Mocked<ILibraryRepository>;
let loggerMock: jest.Mocked<ILoggerRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: jest.Mocked<ISystemConfigRepository>;
let userTokenMock: jest.Mocked<IUserTokenRepository>; let userTokenMock: jest.Mocked<IUserTokenRepository>;
let shareMock: jest.Mocked<ISharedLinkRepository>; let shareMock: jest.Mocked<ISharedLinkRepository>;
@ -92,12 +95,23 @@ describe('AuthService', () => {
cryptoMock = newCryptoRepositoryMock(); cryptoMock = newCryptoRepositoryMock();
userMock = newUserRepositoryMock(); userMock = newUserRepositoryMock();
libraryMock = newLibraryRepositoryMock(); libraryMock = newLibraryRepositoryMock();
loggerMock = newLoggerRepositoryMock();
configMock = newSystemConfigRepositoryMock(); configMock = newSystemConfigRepositoryMock();
userTokenMock = newUserTokenRepositoryMock(); userTokenMock = newUserTokenRepositoryMock();
shareMock = newSharedLinkRepositoryMock(); shareMock = newSharedLinkRepositoryMock();
keyMock = newKeyRepositoryMock(); keyMock = newKeyRepositoryMock();
sut = new AuthService(accessMock, cryptoMock, configMock, libraryMock, userMock, userTokenMock, shareMock, keyMock); sut = new AuthService(
accessMock,
cryptoMock,
configMock,
libraryMock,
loggerMock,
userMock,
userTokenMock,
shareMock,
keyMock,
);
}); });
it('should be defined', () => { it('should be defined', () => {

View File

@ -43,12 +43,12 @@ import { IAccessRepository } from 'src/interfaces/access.interface';
import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILibraryRepository } from 'src/interfaces/library.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; import { IUserTokenRepository } from 'src/interfaces/user-token.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { HumanReadableSize } from 'src/utils/bytes'; import { HumanReadableSize } from 'src/utils/bytes';
import { ImmichLogger } from 'src/utils/logger';
export interface LoginDetails { export interface LoginDetails {
isSecure: boolean; isSecure: boolean;
@ -76,7 +76,6 @@ interface ClaimOptions<T> {
export class AuthService { export class AuthService {
private access: AccessCore; private access: AccessCore;
private configCore: SystemConfigCore; private configCore: SystemConfigCore;
private logger = new ImmichLogger(AuthService.name);
private userCore: UserCore; private userCore: UserCore;
constructor( constructor(
@ -84,6 +83,7 @@ export class AuthService {
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(ILibraryRepository) libraryRepository: ILibraryRepository, @Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
@Inject(IUserRepository) private userRepository: IUserRepository, @Inject(IUserRepository) private userRepository: IUserRepository,
@Inject(IUserTokenRepository) private userTokenRepository: IUserTokenRepository, @Inject(IUserTokenRepository) private userTokenRepository: IUserTokenRepository,
@Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository, @Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
@ -92,6 +92,7 @@ export class AuthService {
this.access = AccessCore.create(accessRepository); this.access = AccessCore.create(accessRepository);
this.configCore = SystemConfigCore.create(configRepository); this.configCore = SystemConfigCore.create(configRepository);
this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository); this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
this.logger.setContext(AuthService.name);
custom.setHttpOptionsDefaults({ timeout: 30_000 }); custom.setHttpOptionsDefaults({ timeout: 30_000 });
} }

View File

@ -16,11 +16,13 @@ import {
} from 'src/entities/system-config.entity'; } from 'src/entities/system-config.entity';
import { IEventRepository, ServerEvent } from 'src/interfaces/event.interface'; import { IEventRepository, ServerEvent } from 'src/interfaces/event.interface';
import { QueueName } from 'src/interfaces/job.interface'; import { QueueName } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { SystemConfigService } from 'src/services/system-config.service'; import { SystemConfigService } from 'src/services/system-config.service';
import { ImmichLogger } from 'src/utils/logger'; import { ImmichLogger } from 'src/utils/logger';
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
const updates: SystemConfigEntity[] = [ const updates: SystemConfigEntity[] = [
@ -156,13 +158,15 @@ describe(SystemConfigService.name, () => {
let sut: SystemConfigService; let sut: SystemConfigService;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: jest.Mocked<ISystemConfigRepository>;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: jest.Mocked<IEventRepository>;
let loggerMock: jest.Mocked<ILoggerRepository>;
let smartInfoMock: jest.Mocked<ISearchRepository>; let smartInfoMock: jest.Mocked<ISearchRepository>;
beforeEach(() => { beforeEach(() => {
delete process.env.IMMICH_CONFIG_FILE; delete process.env.IMMICH_CONFIG_FILE;
configMock = newSystemConfigRepositoryMock(); configMock = newSystemConfigRepositoryMock();
eventMock = newEventRepositoryMock(); eventMock = newEventRepositoryMock();
sut = new SystemConfigService(configMock, eventMock, smartInfoMock); loggerMock = newLoggerRepositoryMock();
sut = new SystemConfigService(configMock, eventMock, loggerMock, smartInfoMock);
}); });
it('should work', () => { it('should work', () => {

View File

@ -22,22 +22,23 @@ import {
ServerAsyncEventMap, ServerAsyncEventMap,
ServerEvent, ServerEvent,
} from 'src/interfaces/event.interface'; } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { ImmichLogger } from 'src/utils/logger';
@Injectable() @Injectable()
export class SystemConfigService { export class SystemConfigService {
private logger = new ImmichLogger(SystemConfigService.name);
private core: SystemConfigCore; private core: SystemConfigCore;
constructor( constructor(
@Inject(ISystemConfigRepository) private repository: ISystemConfigRepository, @Inject(ISystemConfigRepository) private repository: ISystemConfigRepository,
@Inject(IEventRepository) private eventRepository: IEventRepository, @Inject(IEventRepository) private eventRepository: IEventRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
@Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
) { ) {
this.core = SystemConfigCore.create(repository); this.core = SystemConfigCore.create(repository);
this.core.config$.subscribe((config) => this.setLogLevel(config)); this.core.config$.subscribe((config) => this.setLogLevel(config));
this.logger.setContext(SystemConfigService.name);
} }
async init() { async init() {
@ -130,7 +131,7 @@ export class SystemConfigService {
const envLevel = this.getEnvLogLevel(); const envLevel = this.getEnvLogLevel();
const configLevel = logging.enabled ? logging.level : false; const configLevel = logging.enabled ? logging.level : false;
const level = envLevel ?? configLevel; const level = envLevel ?? configLevel;
ImmichLogger.setLogLevel(level); this.logger.setLogLevel(level);
this.logger.log(`LogLevel=${level} ${envLevel ? '(set via LOG_LEVEL)' : '(set via system config)'}`); this.logger.log(`LogLevel=${level} ${envLevel ? '(set via LOG_LEVEL)' : '(set via system config)'}`);
} }

View File

@ -4,6 +4,7 @@ import { LogLevel } from 'src/entities/system-config.entity';
const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
// TODO move implementation to logger.repository.ts
export class ImmichLogger extends ConsoleLogger { export class ImmichLogger extends ConsoleLogger {
private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];

View File

@ -0,0 +1,15 @@
import { ILoggerRepository } from 'src/interfaces/logger.interface';
export const newLoggerRepositoryMock = (): jest.Mocked<ILoggerRepository> => {
return {
setLogLevel: jest.fn(),
setContext: jest.fn(),
verbose: jest.fn(),
debug: jest.fn(),
log: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
fatal: jest.fn(),
};
};