라라벨 시작하기 14 – 배포

코드 작업이 끝나면 배포을 하여 세상에 나의 코드를 적용시키는 과정을 거쳐야 한다. 그런데 이 과정이 상당히 까다롭다. 배포시 고려해야할 사항이 많기 때문이다.

먼저 시작하기 문서(https://laravel.kr/docs/9.x/installation)를 통해 라라벨 프로젝트를 시작하면 다음과 같은 구조의 파일이 나온다.

curl -s https://laravel.build/example-app | bash

자, 그리고 gitignore에는 이렇게 되어있다.

저장소에는 vendor와 .env를 제외하라고 되어있다. 그 말은 배포서버에서 컴포저 인스톨을 해야 한다는거고 node_modules도 마찬가지이다. 배포 과정에 이것들이 포함되어 있어야 한다는 거다.

필요한건 node, php-fpm, nginx 이 세 가지이다. 설치하고나서 기다리고 있는건 권한문제가 있다. 만약 nginx와 php-fpm을 잘 설치했다면 nginx user는 nginx로 되어있을거고 php-fpm 유저는 www-data로 되어있을거다.

이 두 서비스의 user를 하나로 만들어야 권한문제가 발생하지 않는다. 배포한 소스도 user를 맞추자.

내경우엔 여러 복잡한 사정이 더 포함되어있어 서버에서 해당 서비스를 도커로 진행해야하기에 젠킨스 배포과정이 더 복잡해져서 시간을 많이 잡아먹었다.

오늘은 여기까지.

라라벨 시작하기 13 – Debug

PHP는 Xdebug라는 강력한 툴을 제공한다. 그런데 이거 초반에 많이 헤맸다. 로컬에서는 아무 문제가 없다.

내 경우엔 윈도우에 php를 직접 설치하여 개발을 하지 못했다. PHP버전때문. 회사 프로젝트의 php버전이 낮아 최신 버전을 사용할 수 없었다. 그래서 도커로 예전 버전 이미지를 사용하여 개발을 할 수밖에 없었다. 오히려 이게 깔끔하다. 그런데 이렇게 하니 디버깅을 어떻게 해야 할지 난감했다.

그렇지만 방법을 찾았다.

나는 vscode를 사용하여 개발한다. 여기에 remote explorer 확장이 있다. 이걸로 해당 서버에 원격으로 접속할 수 있다. 그리고 미리 준비된 xdebug가 포함된 php 서버이미지를 사용하여 프로젝트를 시작한다. 라라벨은 이미 준비되어있다.

remote explorer의 Dev Containers에 해당 컨테이너를 선택하여 진입하고 Explorer에서 Open Folder를 선택하여 프로젝트 폴더를 선택한다.

이런 유사한 화면이 나올것이다. 여기서 프로젝트 폴더를 선택한다. 라라벨에선 /var/www/html이다.

그리고 xdebug 설정을 한다.

xdebug_mode는 develop, client_host는 localhost로 했다. idekey=docker는 phpstorm에서 사용한다던데 써본적이 없어 잘 모르겠다.

vscode의 디버깅 툴(Run and Debug)을 사용하자. 처음엔 어떤 디버깅 툴을 사용하려고 하는지 선택하라고 나오는데 PHP를 선택하자.

톱니바퀴 모양을 누르면 launch.json을 만들어줄거다. 자동으로 만들어준다. 설정은 바꾸지 않아도 된다.

이제 저 play버튼을 누르면 서버의 debug port와 연결한다. 이제부터 소스에 브레이크포인트를지정하여 디버깅을 할 수 있다.

알면 별거 아닌데.

오늘은 여기까지.

라라벨 시작하기 12 – 로그

사용하기는 쉽다.

use Illuminate\Support\Facades\Log;

를 상단에 선언해주고 하단에

Log::info('Log');

형태로 작성해주면 된다.

그럼 이렇게 로그가 생긴다.로그로테이트는 daily 채널을 사용하자.

추가로 해당 클래스 또는 함수명을 출력해주도록 하는건 나중에 추가한다.

로그에 함수명, 클래스명을 출력하기

이렇게 하고 싶은 이유는 스프링의 로그가 이렇게 되어있었기 때문이다. 그리고 이렇게 하면 꽤나 도움을 많이 받았다.

CustomFormatter를 만들자

나는 daily 체널을 그대로 활용하고 싶었다. 그리고 최대한 제공하는걸 사용하고 싶어 커스터마이징을 따라서 tab에 포메터를 추가하고 싶었다. 그런데 이게 작동을 하지 않는다. 작동하지 않았던 이유는 마지막에…

하는수 없이 프로바이더에 커스텀로그서비스를 추가하였다.(feat, ChatGPT)

class CustomFormatter implements FormatterInterface {
    public function format($record): string {
        // 디버그 백트레이스에서 클래스와 함수명 추출
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
        $caller = isset($trace[2]) ? $trace[2] : null;
        $class = $caller['class'] ?? 'N/A';
        $function = $caller['function'] ?? 'N/A';

        // 로그 메시지 포맷
        return sprintf(
            "[%s] %s: [%s::%s] %s\n",
            $record['datetime']->format('Y-m-d H:i:s'),
            strtoupper($record['level_name']),
            $class,
            $function,
            $record['message']
        );
    }

    public function formatBatch(array $records): array {
        return array_map([$this, 'format'], $records);
    }
}

그런데 이렇게 하니 문제가 발생. 당연하게도 Logger 그자체를 출력해버림. 이번엔 Logger를 제외한 첫번째 클래스를 찾아서 출력하라고 했더니 별반 다르지 않는 코드를 줬다.

대강 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6); 3에서 6으로 늘었다. xdebug로 뭘 출력하는지 알아봤다. 전체 stack trace를 출력하는걸 확인했다. 뒤에 숫자는 몇 번째까지 출력하는지 지정하는거였다.

결국 최종 원하는 결과물을 얻을 수 있는 코드는 다음과 같았다.

<?php
namespace App\Logging;

use Monolog\Formatter\FormatterInterface;

class CustomTabFormatter implements FormatterInterface {
    public function format($record): string {
        // 클래스와 함수명을 포함한 로그 메시지 포맷
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 9);
        $caller = isset($trace[8]) ? $trace[8] : null;
        $class = $caller['class'] ?? 'N/A';
        $function = $caller['function'] ?? 'N/A';

        $message = sprintf(
            "[%s] %s\t[%s::%s]\t%s\n",
            $record['datetime']->format('Y-m-d H:i:s'),
            $record['level_name'],
            $class ?? 'N/A',
            $function ?? 'N/A',
            $record['message'],
        );

        return $message;
    }

    public function formatBatch(array $records): array {
        return array_map([$this, 'format'], $records);
    }
}

그런데 이 코드, 성능이슈가 있을 수 있다. debug옵션에서만 쓰던지 해야지.

오늘은 여기까지.

라라벨 시작하기 9 – 마이그레이션 관리

날짜, 시간별로 파일명이 생성된다는건 그렇게 관리 할 수 있다는 말과도 같다. 그러니 이를 어떻게 관리하는지 찾아보자. 내가 이상하게 생각하는건 django에서는 모델의 설정이 따로 있고 마이그레이션 파일이 있어서 모델의 컬럼설정을 자동으로 마이그레이션 파일로 생성해주었다. 그런데 라라벨에서는 모델과 마이그레이션 파일이 별도로 움직이는듯한 느낌을 받았다.

데이터베이스 스키마를 모두 마이그레이션에서 한다. 모델은 fillable이라 해서 사용자의 입력을 받는 곳을 지정한다. artisan으로 모델을 생성할 때 마이그레이션 파일도 같이 생성되고 이때 id, created_at, updated_at, user_id 같이 데이터의 관리를 위한 칼럼이 자동으로 추가된다.

그렇다면 마이그레이션을 이렇게 밖에 할 수 없을까? 뭔가 다른 방법이 있을거 같은데?

마이그레이션 방법

php artisan make:migration create_flights_table

실행하면 현재일시로 기본상태(id, timestamp)만 가진체 마이그레이션 파일이 생성된다. 그런데 내가 원한건 현재상태(기존 칼럼을 그대로 가진)의 마이그레이션 파일이다. 좀더 알아보니 같은 테이블을 마이그레이션 하는데에는 좀더 신경써야할 부분들이 있었다.

최초에 테이블을 생성하면 위 그림과 같이 id와 timestamps가 생성된다. 그리고 내가 추가하고 싶은 칼럼을 추가한다. 이렇게 사용하다가 추가 칼럼이 필요하면 새로 마이그레이션을 생성한다. 그리고 나서 create가 아닌 “Schema::table(‘table_name’, function(Blueprint $table){” 형태로 해서 추가 칼럼을 등록한다.

이렇게 정확히 반대로 구성해야 마이그레이션과 롤백이 제대로 작동한다.

정리해보면

  1. command ./vendor/bin/sail php artisan make:migration new_table
  2. new_table.php의 create에 컬럼 추가
  3. 적용 command ./vendor/bin/sail php artisan migrate
  4. command ./vendor/bin/sail php artisan make:migration new_table
  5. new_table.php의 create를 table로 변경하고 변경할 컬럼을 추가
  6. 적용 command ./vendor/bin/sail php artisan migrate

./vendor/bin/sail php artisan migrate:fresh

이 코드를 실행하면 전체 테이블을 드롭하고 마이그레이션을 다시 실행한다.

./vendor/bin/sail php artisan migrate:refresh

이건 전부 롤백 했다 마이그레이션을 다시 실행한다. 이 두 개는 현실에선 사용할 일은 없을거 같은데?

실제 운영에 마이그레이션을 써야 하겠지만 철저하게 코드화 하여 적용하지 않으면 큰일을 당할거 같다. 마이그레이션이야 그래도 문제가 없겠지만 롤백이 문제다.

디비를 보니 migration테이블이 있어 여기에 마이그레이션상태를 저장한다.

batch는 마이그레이션을 실행할 때마다 하나씩 올라간다. rollback하면 하나씩 내려가고.

오늘은 여기까지.