Skip to content

Commit

Permalink
refactor(server): telemetry (#13588)
Browse files Browse the repository at this point in the history
refactor: telemetry
  • Loading branch information
jrasm91 authored Oct 21, 2024
1 parent 16f2364 commit e6a666f
Show file tree
Hide file tree
Showing 45 changed files with 143 additions and 202 deletions.
5 changes: 5 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import { entities } from 'src/entities';
import { ImmichWorker } from 'src/enum';
import { IEventRepository } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
import { AuthGuard } from 'src/middleware/auth.guard';
import { ErrorInterceptor } from 'src/middleware/error.interceptor';
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
import { repositories } from 'src/repositories';
import { ConfigRepository } from 'src/repositories/config.repository';
import { teardownTelemetry } from 'src/repositories/telemetry.repository';
import { services } from 'src/services';
import { DatabaseService } from 'src/services/database.service';

Expand Down Expand Up @@ -66,19 +68,22 @@ abstract class BaseModule implements OnModuleInit, OnModuleDestroy {
constructor(
@Inject(ILoggerRepository) logger: ILoggerRepository,
@Inject(IEventRepository) private eventRepository: IEventRepository,
@Inject(ITelemetryRepository) private telemetryRepository: ITelemetryRepository,
) {
logger.setAppName(this.worker);
}

abstract getWorker(): ImmichWorker;

async onModuleInit() {
this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) });
this.eventRepository.setup({ services });
await this.eventRepository.emit('app.bootstrap', this.worker);
}

async onModuleDestroy() {
await this.eventRepository.emit('app.shutdown', this.worker);
await teardownTelemetry();
}
}

Expand Down
6 changes: 3 additions & 3 deletions server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,9 @@ export const immichAppConfig: ConfigModuleOptions = {
),

IMMICH_METRICS: Joi.boolean().optional().default(false),
IMMICH_HOST_METRICS: Joi.boolean().optional().default(false),
IMMICH_API_METRICS: Joi.boolean().optional().default(false),
IMMICH_IO_METRICS: Joi.boolean().optional().default(false),
IMMICH_HOST_METRICS: Joi.boolean().optional(),
IMMICH_API_METRICS: Joi.boolean().optional(),
IMMICH_IO_METRICS: Joi.boolean().optional(),
}),
};

Expand Down
24 changes: 3 additions & 21 deletions server/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,6 @@ export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator {
return Chunked({ ...options, mergeFn: setUnion });
}

// https://stackoverflow.com/a/74898678
export function DecorateAll(
decorator: <T>(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>,
) => TypedPropertyDescriptor<T> | void,
) {
return (target: any) => {
const descriptors = Object.getOwnPropertyDescriptors(target.prototype);
for (const [propName, descriptor] of Object.entries(descriptors)) {
const isMethod = typeof descriptor.value == 'function' && propName !== 'constructor';
if (!isMethod) {
continue;
}
decorator({ ...target, constructor: { ...target.constructor, name: target.name } as any }, propName, descriptor);
Object.defineProperty(target.prototype, propName, descriptor);
}
};
}

const UUID = '00000000-0000-4000-a000-000000000000';

export const DummyValue = {
Expand All @@ -128,6 +107,9 @@ export interface GenerateSqlQueries {
params: unknown[];
}

export const Telemetry = (options: { enabled?: boolean }) =>
SetMetadata(MetadataKey.TELEMETRY_ENABLED, options?.enabled ?? true);

/** Decorator to enable versioning/tracking of generated Sql */
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);

Expand Down
1 change: 1 addition & 0 deletions server/src/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export enum MetadataKey {
SHARED_ROUTE = 'shared_route',
API_KEY_SECURITY = 'api_key',
EVENT_CONFIG = 'event_config',
TELEMETRY_ENABLED = 'telemetry_enabled',
}

export enum RouteKey {
Expand Down
2 changes: 2 additions & 0 deletions server/src/interfaces/telemetry.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MetricOptions } from '@opentelemetry/api';
import { ClassConstructor } from 'class-transformer';

export const ITelemetryRepository = 'ITelemetryRepository';

Expand All @@ -14,6 +15,7 @@ export interface IMetricGroupRepository {
}

export interface ITelemetryRepository {
setup(options: { repositories: ClassConstructor<unknown>[] }): void;
api: IMetricGroupRepository;
host: IMetricGroupRepository;
jobs: IMetricGroupRepository;
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/access.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { StackEntity } from 'src/entities/stack.entity';
import { TagEntity } from 'src/entities/tag.entity';
import { AlbumUserRole } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { Brackets, In, Repository } from 'typeorm';

type IActivityAccess = IAccessRepository['activity'];
Expand All @@ -29,7 +28,6 @@ type IStackAccess = IAccessRepository['stack'];
type ITagAccess = IAccessRepository['tag'];
type ITimelineAccess = IAccessRepository['timeline'];

@Instrumentation()
@Injectable()
class ActivityAccess implements IActivityAccess {
constructor(
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/activity.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import { DummyValue, GenerateSql } from 'src/decorators';
import { ActivityEntity } from 'src/entities/activity.entity';
import { IActivityRepository } from 'src/interfaces/activity.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { IsNull, Repository } from 'typeorm';

export interface ActivitySearch {
Expand All @@ -13,7 +12,6 @@ export interface ActivitySearch {
isLiked?: boolean;
}

@Instrumentation()
@Injectable()
export class ActivityRepository implements IActivityRepository {
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/album-user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AlbumUserEntity } from 'src/entities/album-user.entity';
import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { Repository } from 'typeorm';

@Instrumentation()
@Injectable()
export class AlbumUserRepository implements IAlbumUserRepository {
constructor(@InjectRepository(AlbumUserEntity) private repository: Repository<AlbumUserEntity>) {}
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/album.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/
import { AlbumEntity } from 'src/entities/album.entity';
import { AssetEntity } from 'src/entities/asset.entity';
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import {
DataSource,
EntityManager,
Expand All @@ -23,7 +22,6 @@ const withoutDeletedUsers = <T extends AlbumEntity | null>(album: T) => {
return album;
};

@Instrumentation()
@Injectable()
export class AlbumRepository implements IAlbumRepository {
constructor(
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/api-key.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
import { DummyValue, GenerateSql } from 'src/decorators';
import { APIKeyEntity } from 'src/entities/api-key.entity';
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { Repository } from 'typeorm';

@Instrumentation()
@Injectable()
export class ApiKeyRepository implements IKeyRepository {
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/asset.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
} from 'src/interfaces/asset.interface';
import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface';
import { searchAssetBuilder } from 'src/utils/database';
import { Instrumentation } from 'src/utils/instrumentation';
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
import {
Brackets,
Expand All @@ -54,7 +53,6 @@ const dateTrunc = (options: TimeBucketOptions) =>
truncateMap[options.size]
}', (asset."localDateTime" at time zone 'UTC')) at time zone 'UTC')::timestamptz`;

@Instrumentation()
@Injectable()
export class AssetRepository implements IAssetRepository {
constructor(
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/audit.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AuditEntity } from 'src/entities/audit.entity';
import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { In, LessThan, MoreThan, Repository } from 'typeorm';

@Instrumentation()
@Injectable()
export class AuditRepository implements IAuditRepository {
constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
Expand Down
5 changes: 2 additions & 3 deletions server/src/repositories/config.repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { join } from 'node:path';
import { citiesFile, excludePaths } from 'src/constants';
import { Telemetry } from 'src/decorators';
import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum';
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
import { DatabaseExtension } from 'src/interfaces/database.interface';
Expand Down Expand Up @@ -74,9 +75,6 @@ const getEnv = (): EnvData => {
const repoMetrics = parseBoolean(process.env.IMMICH_IO_METRICS, globalEnabled);
const jobMetrics = parseBoolean(process.env.IMMICH_JOB_METRICS, globalEnabled);
const telemetryEnabled = globalEnabled || hostMetrics || apiMetrics || repoMetrics || jobMetrics;
if (!telemetryEnabled && process.env.OTEL_SDK_DISABLED === undefined) {
process.env.OTEL_SDK_DISABLED = 'true';
}

return {
host: process.env.IMMICH_HOST,
Expand Down Expand Up @@ -186,6 +184,7 @@ const getEnv = (): EnvData => {
let cached: EnvData | undefined;

@Injectable()
@Telemetry({ enabled: false })
export class ConfigRepository implements IConfigRepository {
getEnv(): EnvData {
if (!cached) {
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/crypto.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { compareSync, hash } from 'bcrypt';
import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { Instrumentation } from 'src/utils/instrumentation';

@Instrumentation()
@Injectable()
export class CryptoRepository implements ICryptoRepository {
randomUUID() {
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/database.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ import {
VectorUpdateResult,
} from 'src/interfaces/database.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { isValidInteger } from 'src/validation';
import { DataSource, EntityManager, QueryRunner } from 'typeorm';

@Instrumentation()
@Injectable()
export class DatabaseRepository implements IDatabaseRepository {
private vectorExtension: VectorExtension;
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/event.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
} from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { AuthService } from 'src/services/auth.service';
import { Instrumentation } from 'src/utils/instrumentation';
import { handlePromiseError } from 'src/utils/misc';

type EmitHandlers = Partial<{ [T in EmitEvent]: Array<EventItem<T>> }>;
Expand All @@ -37,7 +36,6 @@ type Item<T extends EmitEvent> = {
label: string;
};

@Instrumentation()
@WebSocketGateway({
cors: true,
path: '/api/socket.io',
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/job.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
QueueStatus,
} from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { Instrumentation } from 'src/utils/instrumentation';

export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
// misc
Expand Down Expand Up @@ -99,7 +98,6 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
[JobName.QUEUE_TRASH_EMPTY]: QueueName.BACKGROUND_TASK,
};

@Instrumentation()
@Injectable()
export class JobRepository implements IJobRepository {
private workers: Partial<Record<QueueName, Worker>> = {};
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/library.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import { DummyValue, GenerateSql } from 'src/decorators';
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
import { LibraryEntity } from 'src/entities/library.entity';
import { ILibraryRepository } from 'src/interfaces/library.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { IsNull, Not } from 'typeorm';
import { Repository } from 'typeorm/repository/Repository.js';

@Instrumentation()
@Injectable()
export class LibraryRepository implements ILibraryRepository {
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
Expand Down
2 changes: 2 additions & 0 deletions server/src/repositories/logger.repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,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 { IConfigRepository } from 'src/interfaces/config.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
Expand All @@ -17,6 +18,7 @@ enum LogColor {
}

@Injectable({ scope: Scope.TRANSIENT })
@Telemetry({ enabled: false })
export class LoggerRepository extends ConsoleLogger implements ILoggerRepository {
private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
private noColor: boolean;
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/machine-learning.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import {
ModelTask,
ModelType,
} from 'src/interfaces/machine-learning.interface';
import { Instrumentation } from 'src/utils/instrumentation';

const errorPrefix = 'Machine learning request';

@Instrumentation()
@Injectable()
export class MachineLearningRepository implements IMachineLearningRepository {
private async predict<T>(url: string, payload: ModelPayload, config: MachineLearningRequest): Promise<T> {
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/map.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ import {
} from 'src/interfaces/map.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { OptionalBetween } from 'src/utils/database';
import { Instrumentation } from 'src/utils/instrumentation';
import { DataSource, In, IsNull, Not, QueryRunner, Repository } from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';

@Instrumentation()
@Injectable()
export class MapRepository implements IMapRepository {
constructor(
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/media.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
TranscodeCommand,
VideoInfo,
} from 'src/interfaces/media.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { handlePromiseError } from 'src/utils/misc';

const probe = (input: string, options: string[]): Promise<FfprobeData> =>
Expand All @@ -36,7 +35,6 @@ type ProgressEvent = {
percent?: number;
};

@Instrumentation()
@Injectable()
export class MediaRepository implements IMediaRepository {
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/memory.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { MemoryEntity } from 'src/entities/memory.entity';
import { IMemoryRepository } from 'src/interfaces/memory.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { DataSource, In, Repository } from 'typeorm';

@Instrumentation()
@Injectable()
export class MemoryRepository implements IMemoryRepository {
constructor(
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/metadata.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored';
import geotz from 'geo-tz';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
import { Instrumentation } from 'src/utils/instrumentation';

@Instrumentation()
@Injectable()
export class MetadataRepository implements IMetadataRepository {
private exiftool = new ExifTool({
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/move.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { DummyValue, GenerateSql } from 'src/decorators';
import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum';
import { IMoveRepository, MoveCreate } from 'src/interfaces/move.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { Repository } from 'typeorm';

@Instrumentation()
@Injectable()
export class MoveRepository implements IMoveRepository {
constructor(@InjectRepository(MoveEntity) private repository: Repository<MoveEntity>) {}
Expand Down
2 changes: 0 additions & 2 deletions server/src/repositories/notification.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import {
SendEmailResponse,
SmtpOptions,
} from 'src/interfaces/notification.interface';
import { Instrumentation } from 'src/utils/instrumentation';

@Instrumentation()
@Injectable()
export class NotificationRepository implements INotificationRepository {
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
Expand Down
Loading

0 comments on commit e6a666f

Please sign in to comment.