PDFが0バイトになる原因と対策:ファイル保存エラーのチェックリスト

注意
この記事はPDFが0バイトになってしまうケースを網羅的に解説します。
途中で問題が見つかったら、すぐに対策チェックリストで確認してみてください。


PDF生成プロセスの概要

Webアプリやデスクトップアプリでよく使われるPDF生成の流れ

  1. データ取得(DB、API、フォーム入力など)
  2. レイアウト設計(テンプレート、CSS、フォント)
  3. PDFエンジン呼び出しwkhtmltopdf, ReportLab, TCPDF など)
  4. ストリーム/ファイル書き込み
  5. クライアントへ返却(ダウンロードリンク, 直接保存)

このうち「4. ストリーム/ファイル書き込み」に失敗すると、生成されたファイルは0バイトになることが多いです。


0バイトになる主な原因

原因 典型的な症状 主な原因
ファイルパス/ディレクトリ名の不正 ファイルが作成できない、名前が重複 ../ を含むパス、特殊文字、長すぎるパス
書き込み権限が不足 生成失敗、エラーログに Permission denied ファイル/フォルダに実行ユーザーが書き込み不可
PDFライブラリのバージョン不整合 例外が投げられる、途中で終了 依存ライブラリの旧版、C++/Python バイナリの不一致
メモリ不足 生成途中でクラッシュ、OOM エラー 大容量画像/テキストを一括読み込む
テンプレートやCSSのパース失敗 PDFが空になる、スタイルが崩れる HTML/CSS の構文エラー
ストリームバッファのフラッシュ漏れ 0バイトファイル flush() していない、close() していない
一時ファイルの削除タイミング 0バイトになる、存在しない 生成途中で一時ファイルを削除してしまうコード
非同期処理の競合 途中でファイルが削除される、競合状態 async / await でファイル書き込み前に次の処理が走る
環境変数の設定ミス ランタイムで例外が発生 フォントパス、PATH の設定漏れ
エラーハンドリングの不備 例外が捉えられず 0バイトファイル生成 例外時にファイル削除処理を行わない、ログが出ない

ポイント
PDFが0バイトになるのは「書き込み前に失敗」していることがほとんど。
したがって、エラーログやスタックトレースを必ず確認しましょう。


対策・解決策

1. パスと権限のチェック

# Linux での例
ls -ld /var/www/app/tmp
# または
stat -c "%A %U" /var/www/app/tmp/file.pdf
  • パスを正規化realpath で絶対パス化)
  • ユーザー権限:Apache/Nginxは通常 www-data で走る。
    chown -R www-data:www-data /var/www/app/tmp
  • パーミッションchmod 775 /var/www/app/tmp

2. PDFライブラリのバージョンとキャッシュのクリア

  • 公式ドキュメントで推奨バージョンを確認
  • 依存関係の更新後、キャッシュをクリア
    rm -rf vendor/cache # PHPの場合
    pip cache purge      # Pythonの場合
    

3. メモリ・バッファ管理

  • 大きな画像は 圧縮 してから投入
  • バッファリングを利用し 一度に書き込わない
    with open(path, "wb") as f:
        f.write(pdf_bytes)  # pdf_bytes は bytearray
    
  • ストリームフラッシュf.flush() を呼ぶ
  • 生成時のログ--verbose オプションで詳細ログ取得

4. テンプレートエラーの検証

  • HTML/CSS を 独立でブラウザで表示 → Ctrl+U
  • HTML Linter (htmlhint, tidy) でエラーを洗い出す
  • PDF生成時の例外メッセージをキャッチし、テンプレートの失敗箇所を特定

5. エラーハンドリングとログ

try {
    $pdf = $pdfEngine->render($template, $data);
    file_put_contents($filePath, $pdf);
} catch (Exception $e) {
    error_log("PDF生成失敗: " . $e->getMessage());
    // 既に作成されているか0バイトなら削除
    if (file_exists($filePath) && filesize($filePath) == 0) {
        unlink($filePath);
    }
    throw $e; // 必要ならフロントに表示
}

6. 一時ファイル管理と同期

  • 一時ディレクトリは OS が管理(tempfile / tmpfile
  • tempfile() で扱えば 自動削除が保証される
  • async で作成した場合は wait してから次処理

PDF生成時チェックリスト

# チェック項目 実行手順 期待結果
1 パスの有効性 realpath() で存在確認 失敗なら修正
2 ディレクトリ・ファイル権限 ls -l で確認 777 付与/所有者を適切に
3 書き込みテスト 空ファイルを作成→削除 書き込み OK
4 ライブラリバージョン composer show, pip list 公式バージョンと一致
5 依存ファイルの存在 docker ps, ldd 必要 DLL が見つかる
6 テンプレート検証 ブラウザで確認 例外無し
7 メモリ使用量 ps, top 1-2GB 以内
8 バイト列・サイズチェック filesize() 非ゼロ
9 ログ確認 tail -f error.log エラー無し
10 再現性テスト 5 回生成 一貫して生成

備考
1 から 4 は「事前にセットアップ」を確実に。
5 以降は「実行時」にチェックリストを実行し、失敗箇所を特定します。


さらに先へ:自動化と監視

CI で PDF 生成確認

  • GitHub Actions に「PDF生成テスト」を追加

    - name: Generate PDF
      run: |
        npm run test:pdf
        mv output.pdf result.pdf
    - name: Upload PDF artifact
      uses: actions/upload-artifact@v3
      with:
        name: pdf-result
        path: result.pdf
    

アプリ監視

  • Prometheuspdf_generation_duration_ms のメトリクス
  • alertmanager で「PDF生成が0バイト時にアラート」

エラーレポート用に UI で表示

<div class="pdf-error" style="color:red;">
  PDF の生成に失敗しました。<br>
  詳細: {{ error_message }}
</div>

まとめ

  • **0バイト PDF は「書き込み前に失敗」**という強いシグナル。
  • 原因は「パス/権限」「ライブラリ」「テンプレート」「メモリ」「バッファ」等に集約。
  • 対策チェックリストで一つずつ検証すれば、再発を防止できます。
  • さらに CI/CI の自動化や監視を組み合わせれば、「いつ・どこで」問題が起きたかを即座に把握可能です。

ぜひ、上記リストを日常の開発フローに組み込んで、PDF生成の安定化を図ってください。

コメント