feat(server): implement switchable logging formats (console/json) (#24791)

* feat(server): add LogFormat enum and configuration

* feat(server): add structured logging formatters

* feat(server): implement switchable logging formats (console/json)

* Revert "feat(server): add LogFormat enum and configuration"

This reverts commit 565e95ae68.

* feat(server): implement JSON logging using NestJS native support

* refactor: rename LOG_FORMAT to IMMICH_LOG_FORMAT for consistency

* docs: add IMMICH_LOG_FORMAT documentation

* chore: format environment-variables.md

* chore: format monitoring.md
This commit is contained in:
Daniel Ramos
2026-01-05 16:21:02 +01:00
committed by GitHub
parent 4147f1d912
commit edd3ab7cc9
7 changed files with 67 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
import { Transform, Type } from 'class-transformer';
import { IsEnum, IsInt, IsString, Matches } from 'class-validator';
import { DatabaseSslMode, ImmichEnvironment, LogLevel } from 'src/enum';
import { DatabaseSslMode, ImmichEnvironment, LogFormat, LogLevel } from 'src/enum';
import { IsIPRange, Optional, ValidateBoolean } from 'src/validation';
export class EnvDto {
@@ -48,6 +48,10 @@ export class EnvDto {
@Optional()
IMMICH_LOG_LEVEL?: LogLevel;
@IsEnum(LogFormat)
@Optional()
IMMICH_LOG_FORMAT?: LogFormat;
@Optional()
@Matches(/^\//, { message: 'IMMICH_MEDIA_LOCATION must be an absolute path' })
IMMICH_MEDIA_LOCATION?: string;

View File

@@ -454,6 +454,11 @@ export enum LogLevel {
Fatal = 'fatal',
}
export enum LogFormat {
Console = 'console',
Json = 'json',
}
export enum ApiCustomExtension {
Permission = 'x-immich-permission',
AdminOnly = 'x-immich-admin-only',

View File

@@ -17,6 +17,7 @@ import {
ImmichHeader,
ImmichTelemetry,
ImmichWorker,
LogFormat,
LogLevel,
QueueName,
} from 'src/enum';
@@ -29,6 +30,7 @@ export interface EnvData {
environment: ImmichEnvironment;
configFile?: string;
logLevel?: LogLevel;
logFormat?: LogFormat;
buildMetadata: {
build?: string;
@@ -233,6 +235,7 @@ const getEnv = (): EnvData => {
environment,
configFile: dto.IMMICH_CONFIG_FILE,
logLevel: dto.IMMICH_LOG_LEVEL,
logFormat: dto.IMMICH_LOG_FORMAT || LogFormat.Console,
buildMetadata: {
build: dto.IMMICH_BUILD,

View File

@@ -2,7 +2,7 @@ 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 { Telemetry } from 'src/decorators';
import { LogLevel } from 'src/enum';
import { LogFormat, LogLevel } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
type LogDetails = any;
@@ -27,10 +27,12 @@ export class MyConsoleLogger extends ConsoleLogger {
constructor(
private cls: ClsService | undefined,
options?: { color?: boolean; context?: string },
options?: { json?: boolean; color?: boolean; context?: string },
) {
super(options?.context || MyConsoleLogger.name);
this.isColorEnabled = options?.color || false;
super(options?.context || MyConsoleLogger.name, {
json: options?.json ?? false,
});
this.isColorEnabled = !options?.json && (options?.color || false);
}
isLevelEnabled(level: LogLevel) {
@@ -79,10 +81,17 @@ export class LoggingRepository {
@Inject(ConfigRepository) configRepository: ConfigRepository | undefined,
) {
let noColor = false;
let logFormat = LogFormat.Console;
if (configRepository) {
noColor = configRepository.getEnv().noColor;
const env = configRepository.getEnv();
noColor = env.noColor;
logFormat = env.logFormat ?? logFormat;
}
this.logger = new MyConsoleLogger(cls, { context: LoggingRepository.name, color: !noColor });
this.logger = new MyConsoleLogger(cls, {
context: LoggingRepository.name,
json: logFormat === LogFormat.Json,
color: !noColor,
});
}
static create(context?: string) {