mirror of
https://github.com/immich-app/immich.git
synced 2025-01-28 06:32:44 +01:00
refactor(server): no color env (#13166)
This commit is contained in:
parent
0eb77147ef
commit
e2bf6808ca
7 changed files with 117 additions and 34 deletions
server
src
interfaces
repositories
utils
test/repositories
|
@ -41,6 +41,7 @@ export interface EnvData {
|
||||||
|
|
||||||
workers: ImmichWorker[];
|
workers: ImmichWorker[];
|
||||||
|
|
||||||
|
noColor: boolean;
|
||||||
nodeVersion?: string;
|
nodeVersion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,51 +1,76 @@
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
|
||||||
const getWorkers = () => new ConfigRepository().getEnv().workers;
|
const getEnv = () => new ConfigRepository().getEnv();
|
||||||
|
|
||||||
describe('getWorkers', () => {
|
describe('getEnv', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env.IMMICH_WORKERS_INCLUDE = '';
|
delete process.env.IMMICH_WORKERS_INCLUDE;
|
||||||
process.env.IMMICH_WORKERS_EXCLUDE = '';
|
delete process.env.IMMICH_WORKERS_EXCLUDE;
|
||||||
|
delete process.env.NO_COLOR;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return default workers', () => {
|
it('should return default workers', () => {
|
||||||
expect(getWorkers()).toEqual(['api', 'microservices']);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual(['api', 'microservices']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return included workers', () => {
|
it('should return included workers', () => {
|
||||||
process.env.IMMICH_WORKERS_INCLUDE = 'api';
|
process.env.IMMICH_WORKERS_INCLUDE = 'api';
|
||||||
expect(getWorkers()).toEqual(['api']);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual(['api']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should excluded workers from defaults', () => {
|
it('should excluded workers from defaults', () => {
|
||||||
process.env.IMMICH_WORKERS_EXCLUDE = 'api';
|
process.env.IMMICH_WORKERS_EXCLUDE = 'api';
|
||||||
expect(getWorkers()).toEqual(['microservices']);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual(['microservices']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exclude workers from include list', () => {
|
it('should exclude workers from include list', () => {
|
||||||
process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice';
|
process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice';
|
||||||
process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices';
|
process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices';
|
||||||
expect(getWorkers()).toEqual(['api']);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual(['api']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove whitespace from included workers before parsing', () => {
|
it('should remove whitespace from included workers before parsing', () => {
|
||||||
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices';
|
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices';
|
||||||
expect(getWorkers()).toEqual(['api', 'microservices']);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual(['api', 'microservices']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove whitespace from excluded workers before parsing', () => {
|
it('should remove whitespace from excluded workers before parsing', () => {
|
||||||
process.env.IMMICH_WORKERS_EXCLUDE = 'api, microservices';
|
process.env.IMMICH_WORKERS_EXCLUDE = 'api, microservices';
|
||||||
expect(getWorkers()).toEqual([]);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove whitespace from included and excluded workers before parsing', () => {
|
it('should remove whitespace from included and excluded workers before parsing', () => {
|
||||||
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices, randomservice,randomservice2';
|
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices, randomservice,randomservice2';
|
||||||
process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices, randomservice2';
|
process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices, randomservice2';
|
||||||
expect(getWorkers()).toEqual(['api']);
|
const { workers } = getEnv();
|
||||||
|
expect(workers).toEqual(['api']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error for invalid workers', () => {
|
it('should throw error for invalid workers', () => {
|
||||||
process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice';
|
process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice';
|
||||||
expect(getWorkers).toThrowError('Invalid worker(s) found: api,microservices,randomservice');
|
expect(getEnv).toThrowError('Invalid worker(s) found: api,microservices,randomservice');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default noColor to false', () => {
|
||||||
|
const { noColor } = getEnv();
|
||||||
|
expect(noColor).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map NO_COLOR=1 to true', () => {
|
||||||
|
process.env.NO_COLOR = '1';
|
||||||
|
const { noColor } = getEnv();
|
||||||
|
expect(noColor).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map NO_COLOR=true to true', () => {
|
||||||
|
process.env.NO_COLOR = 'true';
|
||||||
|
const { noColor } = getEnv();
|
||||||
|
expect(noColor).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -68,11 +68,16 @@ export class ConfigRepository implements IConfigRepository {
|
||||||
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
|
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
|
||||||
vectorExtension: getVectorExtension(),
|
vectorExtension: getVectorExtension(),
|
||||||
},
|
},
|
||||||
|
|
||||||
licensePublicKey: isProd ? productionKeys : stagingKeys,
|
licensePublicKey: isProd ? productionKeys : stagingKeys,
|
||||||
|
|
||||||
storage: {
|
storage: {
|
||||||
ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true',
|
ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true',
|
||||||
},
|
},
|
||||||
|
|
||||||
workers,
|
workers,
|
||||||
|
|
||||||
|
noColor: !!process.env.NO_COLOR,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
39
server/src/repositories/logger.repository.spec.ts
Normal file
39
server/src/repositories/logger.repository.spec.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
|
import { LoggerRepository } from 'src/repositories/logger.repository';
|
||||||
|
import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
|
||||||
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
|
describe(LoggerRepository.name, () => {
|
||||||
|
let sut: LoggerRepository;
|
||||||
|
|
||||||
|
let configMock: Mocked<IConfigRepository>;
|
||||||
|
let clsMock: Mocked<ClsService>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
configMock = newConfigRepositoryMock();
|
||||||
|
clsMock = {
|
||||||
|
getId: vitest.fn(),
|
||||||
|
} as unknown as Mocked<ClsService>;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatContext', () => {
|
||||||
|
it('should use colors', () => {
|
||||||
|
configMock.getEnv.mockReturnValue(mockEnvData({ noColor: false }));
|
||||||
|
|
||||||
|
sut = new LoggerRepository(clsMock, configMock);
|
||||||
|
sut.setAppName('api');
|
||||||
|
|
||||||
|
expect(sut['formatContext']('context')).toBe('\u001B[33m[api:context]\u001B[39m ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use colors when noColor is true', () => {
|
||||||
|
configMock.getEnv.mockReturnValue(mockEnvData({ noColor: true }));
|
||||||
|
|
||||||
|
sut = new LoggerRepository(clsMock, configMock);
|
||||||
|
sut.setAppName('api');
|
||||||
|
|
||||||
|
expect(sut['formatContext']('context')).toBe('[api:context] ');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,18 +1,34 @@
|
||||||
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common';
|
import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
||||||
import { ClsService } from 'nestjs-cls';
|
import { ClsService } from 'nestjs-cls';
|
||||||
import { LogLevel } from 'src/enum';
|
import { LogLevel } from 'src/enum';
|
||||||
|
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { LogColor } from 'src/utils/logger';
|
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
|
enum LogColor {
|
||||||
|
RED = 31,
|
||||||
|
GREEN = 32,
|
||||||
|
YELLOW = 33,
|
||||||
|
BLUE = 34,
|
||||||
|
MAGENTA_BRIGHT = 95,
|
||||||
|
CYAN_BRIGHT = 96,
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({ scope: Scope.TRANSIENT })
|
@Injectable({ scope: Scope.TRANSIENT })
|
||||||
export class LoggerRepository extends ConsoleLogger implements ILoggerRepository {
|
export class LoggerRepository extends ConsoleLogger implements ILoggerRepository {
|
||||||
private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
||||||
|
private noColor: boolean;
|
||||||
|
|
||||||
constructor(private cls: ClsService) {
|
constructor(
|
||||||
|
private cls: ClsService,
|
||||||
|
@Inject(IConfigRepository) configRepository: IConfigRepository,
|
||||||
|
) {
|
||||||
super(LoggerRepository.name);
|
super(LoggerRepository.name);
|
||||||
|
|
||||||
|
const { noColor } = configRepository.getEnv();
|
||||||
|
this.noColor = noColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static appName?: string = undefined;
|
private static appName?: string = undefined;
|
||||||
|
@ -44,6 +60,19 @@ export class LoggerRepository extends ConsoleLogger implements ILoggerRepository
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return LogColor.yellow(`[${prefix}]`) + ' ';
|
return this.colors.yellow(`[${prefix}]`) + ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
private colors = {
|
||||||
|
red: (text: string) => this.withColor(text, LogColor.RED),
|
||||||
|
green: (text: string) => this.withColor(text, LogColor.GREEN),
|
||||||
|
yellow: (text: string) => this.withColor(text, LogColor.YELLOW),
|
||||||
|
blue: (text: string) => this.withColor(text, LogColor.BLUE),
|
||||||
|
magentaBright: (text: string) => this.withColor(text, LogColor.MAGENTA_BRIGHT),
|
||||||
|
cyanBright: (text: string) => this.withColor(text, LogColor.CYAN_BRIGHT),
|
||||||
|
};
|
||||||
|
|
||||||
|
private withColor(text: string, color: LogColor) {
|
||||||
|
return this.noColor ? text : `\u001B[${color}m${text}\u001B[39m`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,6 @@ import { HttpException } from '@nestjs/common';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { TypeORMError } from 'typeorm';
|
import { TypeORMError } from 'typeorm';
|
||||||
|
|
||||||
type ColorTextFn = (text: string) => string;
|
|
||||||
|
|
||||||
const isColorAllowed = () => !process.env.NO_COLOR;
|
|
||||||
const colorIfAllowed = (colorFn: ColorTextFn) => (text: string) => (isColorAllowed() ? colorFn(text) : text);
|
|
||||||
|
|
||||||
export const LogColor = {
|
|
||||||
red: colorIfAllowed((text: string) => `\u001B[31m${text}\u001B[39m`),
|
|
||||||
green: colorIfAllowed((text: string) => `\u001B[32m${text}\u001B[39m`),
|
|
||||||
yellow: colorIfAllowed((text: string) => `\u001B[33m${text}\u001B[39m`),
|
|
||||||
blue: colorIfAllowed((text: string) => `\u001B[34m${text}\u001B[39m`),
|
|
||||||
magentaBright: colorIfAllowed((text: string) => `\u001B[95m${text}\u001B[39m`),
|
|
||||||
cyanBright: colorIfAllowed((text: string) => `\u001B[96m${text}\u001B[39m`),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LogStyle = {
|
|
||||||
bold: colorIfAllowed((text: string) => `\u001B[1m${text}\u001B[0m`),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const logGlobalError = (logger: ILoggerRepository, error: Error) => {
|
export const logGlobalError = (logger: ILoggerRepository, error: Error) => {
|
||||||
if (error instanceof HttpException) {
|
if (error instanceof HttpException) {
|
||||||
const status = error.getStatus();
|
const status = error.getStatus();
|
||||||
|
|
|
@ -24,6 +24,8 @@ const envData: EnvData = {
|
||||||
},
|
},
|
||||||
|
|
||||||
workers: [ImmichWorker.API, ImmichWorker.MICROSERVICES],
|
workers: [ImmichWorker.API, ImmichWorker.MICROSERVICES],
|
||||||
|
|
||||||
|
noColor: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => {
|
export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => {
|
||||||
|
|
Loading…
Reference in a new issue