Skip to content

Commit

Permalink
fix(server): connection aborted logging (#5595)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 authored Dec 10, 2023
1 parent 3a794d7 commit b7b4483
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 13 deletions.
2 changes: 2 additions & 0 deletions server/src/domain/domain.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export type Options = {
each?: boolean;
};

export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED';

export function ValidateUUID({ optional, each }: Options = { optional: false, each: false }) {
return applyDecorators(
IsUUID('4', { each }),
Expand Down
27 changes: 15 additions & 12 deletions server/src/immich/api-v1/asset/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IAccessRepository,
IJobRepository,
ILibraryRepository,
isConnectionAborted,
JobName,
mapAsset,
mimeTypes,
Expand All @@ -20,6 +21,7 @@ import { constants } from 'fs';
import fs from 'fs/promises';
import path from 'path';
import { QueryFailedError } from 'typeorm';
import { promisify } from 'util';
import { IAssetRepository } from './asset-repository';
import { AssetCore } from './asset.core';
import { AssetBulkUploadCheckDto } from './dto/asset-check.dto';
Expand All @@ -42,6 +44,10 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon
type SendFile = Parameters<Response['sendFile']>;
type SendFileOptions = SendFile[1];

// TODO: move file sending logic to an interceptor
const sendFile = (res: Response, path: string, options: SendFileOptions) =>
promisify<string, SendFileOptions>(res.sendFile).bind(res)(path, options);

@Injectable()
export class AssetService {
readonly logger = new Logger(AssetService.name);
Expand Down Expand Up @@ -336,19 +342,16 @@ export class AssetService {

res.set('Cache-Control', 'private, max-age=86400, no-transform');
res.header('Content-Type', mimeTypes.lookup(filepath));
return new Promise((resolve, reject) => {
res.sendFile(filepath, options, (error: Error) => {
if (!error) {
resolve();
return;
}

if (error.message !== 'Request aborted') {
this.logger.error(`Unable to send file: ${error.name}`, error.stack);
}
reject(error);
});
});
try {
await sendFile(res, filepath, options);
} catch (error: Error | any) {
if (!isConnectionAborted(error)) {
this.logger.error(`Unable to send file: ${error.name}`, error.stack);
}
// throwing closes the connection and prevents `Error: write EPIPE`
throw error;
}
}

private async getLibraryId(authUser: AuthUserDto, libraryId?: string) {
Expand Down
5 changes: 4 additions & 1 deletion server/src/immich/interceptors/error.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
NestInterceptor,
} from '@nestjs/common';
import { Observable, catchError, throwError } from 'rxjs';
import { isConnectionAborted } from '../../domain';
import { routeToErrorMessage } from '../app.utils';

@Injectable()
Expand All @@ -20,7 +21,9 @@ export class ErrorInterceptor implements NestInterceptor {
throwError(() => {
if (error instanceof HttpException === false) {
const errorMessage = routeToErrorMessage(context.getHandler().name);
this.logger.error(errorMessage, error, error?.errors);
if (!isConnectionAborted(error)) {
this.logger.error(errorMessage, error, error?.errors);
}
return new InternalServerErrorException(errorMessage);
} else {
return error;
Expand Down

0 comments on commit b7b4483

Please sign in to comment.