Skip to content

Commit

Permalink
refactor(server): no color env (immich-app#13166)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 authored Oct 3, 2024
1 parent 0eb7714 commit e2bf680
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 34 deletions.
1 change: 1 addition & 0 deletions server/src/interfaces/config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface EnvData {

workers: ImmichWorker[];

noColor: boolean;
nodeVersion?: string;
}

Expand Down
49 changes: 37 additions & 12 deletions server/src/repositories/config.repository.spec.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,76 @@
import { ConfigRepository } from 'src/repositories/config.repository';

const getWorkers = () => new ConfigRepository().getEnv().workers;
const getEnv = () => new ConfigRepository().getEnv();

describe('getWorkers', () => {
describe('getEnv', () => {
beforeEach(() => {
process.env.IMMICH_WORKERS_INCLUDE = '';
process.env.IMMICH_WORKERS_EXCLUDE = '';
delete process.env.IMMICH_WORKERS_INCLUDE;
delete process.env.IMMICH_WORKERS_EXCLUDE;
delete process.env.NO_COLOR;
});

it('should return default workers', () => {
expect(getWorkers()).toEqual(['api', 'microservices']);
const { workers } = getEnv();
expect(workers).toEqual(['api', 'microservices']);
});

it('should return included workers', () => {
process.env.IMMICH_WORKERS_INCLUDE = 'api';
expect(getWorkers()).toEqual(['api']);
const { workers } = getEnv();
expect(workers).toEqual(['api']);
});

it('should excluded workers from defaults', () => {
process.env.IMMICH_WORKERS_EXCLUDE = 'api';
expect(getWorkers()).toEqual(['microservices']);
const { workers } = getEnv();
expect(workers).toEqual(['microservices']);
});

it('should exclude workers from include list', () => {
process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice';
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', () => {
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', () => {
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', () => {
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices, randomservice,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', () => {
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);
});
});
5 changes: 5 additions & 0 deletions server/src/repositories/config.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,16 @@ export class ConfigRepository implements IConfigRepository {
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
vectorExtension: getVectorExtension(),
},

licensePublicKey: isProd ? productionKeys : stagingKeys,

storage: {
ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true',
},

workers,

noColor: !!process.env.NO_COLOR,
};
}
}
39 changes: 39 additions & 0 deletions server/src/repositories/logger.repository.spec.ts
Original file line number Diff line number Diff line change
@@ -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] ');
});
});
});
37 changes: 33 additions & 4 deletions server/src/repositories/logger.repository.ts
Original file line number Diff line number Diff line change
@@ -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 { ClsService } from 'nestjs-cls';
import { LogLevel } from 'src/enum';
import { IConfigRepository } from 'src/interfaces/config.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];

enum LogColor {
RED = 31,
GREEN = 32,
YELLOW = 33,
BLUE = 34,
MAGENTA_BRIGHT = 95,
CYAN_BRIGHT = 96,
}

@Injectable({ scope: Scope.TRANSIENT })
export class LoggerRepository extends ConsoleLogger implements ILoggerRepository {
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);

const { noColor } = configRepository.getEnv();
this.noColor = noColor;
}

private static appName?: string = undefined;
Expand Down Expand Up @@ -44,6 +60,19 @@ export class LoggerRepository extends ConsoleLogger implements ILoggerRepository
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`;
}
}
18 changes: 0 additions & 18 deletions server/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,6 @@ import { HttpException } from '@nestjs/common';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
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) => {
if (error instanceof HttpException) {
const status = error.getStatus();
Expand Down
2 changes: 2 additions & 0 deletions server/test/repositories/config.repository.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const envData: EnvData = {
},

workers: [ImmichWorker.API, ImmichWorker.MICROSERVICES],

noColor: false,
};

export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => {
Expand Down

0 comments on commit e2bf680

Please sign in to comment.