# 機能設計書 86-ログ機能

## 概要

本ドキュメントは、lua-language-serverにおけるログ機能について記載する。この機能は、デバッグログの出力、ログレベル管理、ファイル出力、サイズ制限などを提供する。

### 本機能の処理概要

**業務上の目的・背景**：Language Serverの動作を追跡・デバッグするためには、適切なログ出力が不可欠である。本機能は、複数のログレベル（trace, debug, info, warn, error）をサポートし、タイムスタンプ、ソース位置、スレッドID付きのログをファイルに出力する。また、ログファイルのサイズ制限により、ディスク使用量を管理する。

**機能の利用シーン**：サーバー起動時の初期化ログ、LSPリクエスト処理時のデバッグログ、エラー発生時のスタックトレース出力、パフォーマンス問題の調査など、あらゆる場面で利用される。

**主要な処理内容**：
1. ログレベル別の出力関数（trace, debug, info, warn, error）
2. タイムスタンプ付きのログフォーマット
3. ソース位置（ファイル名:行番号）の自動取得
4. ログファイルへの出力とサイズ管理
5. エラーログの標準エラー出力
6. 最初のエラーメッセージの保持

**関連システム・外部連携**：bee.timeモジュールで高精度タイムスタンプを取得。ファイルシステム（bee.filesystem）でログファイルを管理。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は画面を持たず、システム全体のログ基盤として動作 |

## 機能種別

ユーティリティ / ログ出力

## 入力仕様

### 入力パラメータ

#### log.trace / log.debug / log.info / log.warn / log.error関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| ... | any | No | ログメッセージ（複数可、タブ区切りで連結） | - |

#### log.init関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| root | path | Yes | ログファイルのルートパス | パスオブジェクト |
| path | path | Yes | ログファイルのフルパス | パスオブジェクト |

#### log.raw関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| thd | number | Yes | スレッドID | 整数 |
| level | string | Yes | ログレベル | trace/debug/info/warn/error |
| msg | string | Yes | ログメッセージ | 文字列 |
| source | string | Yes | ソースファイル | 文字列 |
| currentline | number | Yes | 行番号（-1の場合は出力しない） | 整数 |
| clock | number | Yes | タイムスタンプ（ミリ秒） | 数値 |

### 入力データソース

- 各モジュールからのログ出力呼び出し
- エラーハンドラーからのエラー通知

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ログ行 | string | フォーマットされたログ文字列 |

### ログ出力フォーマット

```
[HH:MM:SS.mmm][level] [#thd:source:line]: message
```

例:
```
[10:30:45.123][info ] [#0:script/main.lua:42]: Server started
[10:30:45.456][error] [#0:script/provider.lua:100]: Failed to process request
```

### 出力先

- ログファイル（m.pathで指定されたパス）
- 標準エラー出力（errorレベルのみ）
- コンソール出力（m.print=trueの場合）

## 処理フロー

### 処理シーケンス

```
ログ出力処理:
1. pushLog関数でログ内容を準備
   └─ 引数をタブ区切りで連結
2. errorレベルの場合はスタックトレースを追加
   └─ debug.traceback()
3. 呼び出し元情報を取得
   └─ debug.getinfo(3, 'Sl')
4. log.raw関数でフォーマットと出力
   └─ タイムスタンプ、レベル、ソース情報を含む

log.raw処理:
1. ログレベルチェック
   └─ 設定レベル未満なら出力しない
2. errorレベルは標準エラーに出力
   └─ io.stderr:write()
3. サイズ制限チェック
   └─ maxSize (100MB) を超えたら出力停止
4. ログファイル初期化
   └─ init_log_file()
5. タイムスタンプ計算
   └─ startTime + clockからHH:MM:SS.mmmを生成
6. ログ行フォーマットと出力
   └─ file:write()
```

### フローチャート

```mermaid
flowchart TD
    subgraph pushLog["pushLog処理"]
        P1[引数受け取り] --> P2[tostring変換]
        P2 --> P3[タブ区切りで連結]
        P3 --> P4{error?}
        P4 -->|Yes| P5[スタックトレース追加]
        P4 -->|No| P6[呼び出し元情報取得]
        P5 --> P6
        P6 --> P7[log.raw呼び出し]
    end

    subgraph raw["log.raw処理"]
        R1[引数受け取り] --> R2{レベル >= 設定?}
        R2 -->|No| R3[メッセージ返却のみ]
        R2 -->|Yes| R4{error?}
        R4 -->|Yes| R5[stderr出力]
        R5 --> R6[firstError保持]
        R4 -->|No| R7{size > maxSize?}
        R6 --> R7
        R7 -->|Yes| R8[出力スキップ]
        R7 -->|No| R9[ファイル初期化]
        R9 --> R10[タイムスタンプ計算]
        R10 --> R11[ログ行フォーマット]
        R11 --> R12[ファイル出力]
        R12 --> R13{print?}
        R13 -->|Yes| R14[コンソール出力]
        R13 -->|No| R15[完了]
        R14 --> R15
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-86-01 | ログレベルフィルタ | 設定レベル未満のログは出力しない | 全ログ出力 |
| BR-86-02 | エラースタックトレース | errorレベルのログにはスタックトレースを自動付与 | log.error呼び出し時 |
| BR-86-03 | サイズ制限 | ログファイルは100MBまで出力、超過時は停止 | ファイル出力時 |
| BR-86-04 | 最初のエラー保持 | 最初に発生したエラーメッセージをfirstErrorに保持 | errorレベル出力時 |
| BR-86-05 | 標準エラー出力 | errorレベルはstderrにも出力 | errorレベル出力時 |
| BR-86-06 | ログローテーション | 初期化時に既存ログを引き継ぎ | log.init呼び出し時 |

### 計算ロジック

タイムスタンプ計算:
```lua
local sec, ms = mathModf((m.startTime + clock) / 1000)
local timestr = osDate('%H:%M:%S', sec)
-- 出力: [HH:MM:SS.mmm]
```

ログレベル数値:
- trace: 1
- debug: 2
- info: 3
- warn: 4
- error: 5

## データベース操作仕様

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ファイルオープン失敗 | ログファイルを開けない | ログ出力をスキップ |
| - | ディレクトリ作成失敗 | 親ディレクトリを作成できない | pcallでエラーを抑制 |

### リトライ仕様

リトライは行わない。ログ出力失敗時はサイレントにスキップする。

## トランザクション仕様

本機能はトランザクションを使用しない。

## パフォーマンス要件

- ログ出力はバッファなし（setvbuf 'no'）で即座にフラッシュ
- debug.getinfoの呼び出しは最小限の情報（'Sl'）に限定
- サイズ制限により無制限なディスク使用を防止

## セキュリティ考慮事項

- 機密情報のログ出力は呼び出し元で制御
- ログファイルはローカルに保存

## 備考

ログファイルはLOGPATHグローバル変数で指定されたディレクトリに保存される。マルチスレッド環境ではスレッドIDが付与される。

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ログモジュールの状態変数と設定を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | log.lua | `script/log.lua` | モジュール変数（15-28行目） |

**読解のコツ**:
- `m.file`: ログファイルハンドル
- `m.startTime`: サーバー起動時刻（絶対時刻計算用）
- `m.size`: 現在のログファイルサイズ
- `m.maxSize`: 最大サイズ（100MB）
- `m.level`: 現在のログレベル（デフォルト: 'info'）
- `m.levelMap`: レベル名→数値のマッピング

#### Step 2: ログ出力関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | log.lua | `script/log.lua` | ログ関数（71-92行目） |

**主要処理フロー**:
1. **71-73行目**: log.trace - traceレベルログ
2. **75-77行目**: log.debug - debugレベルログ
3. **79-81行目**: log.info - infoレベルログ
4. **83-85行目**: log.warn - warnレベルログ
5. **87-92行目**: log.error - errorレベルログ（戻り値あり）

#### Step 3: pushLog関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | log.lua | `script/log.lua` | pushLog関数（53-69行目） |

**主要処理フロー**:
- **54-56行目**: m.pathがなければ即リターン
- **57-61行目**: 引数をtostringしてタブ区切りで連結
- **62-64行目**: errorレベルはスタックトレース追加
- **65行目**: debug.getinfoで呼び出し元情報取得
- **66行目**: log.raw呼び出し

#### Step 4: log.raw関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | log.lua | `script/log.lua` | raw関数（94-135行目） |

**主要処理フロー**:
- **95-97行目**: レベルフィルタ
- **98-103行目**: errorレベルの特別処理（stderr、firstError）
- **104-106行目**: サイズ制限チェック
- **107行目**: ファイル初期化
- **108-113行目**: タイムスタンプ計算
- **114-119行目**: ログ行フォーマット（行番号あり/なし）
- **120-128行目**: ファイル出力（サイズ制限対応）
- **130-132行目**: コンソール出力（m.print=true時）

#### Step 5: 初期化処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | log.lua | `script/log.lua` | init関数（137-163行目）、init_log_file関数（37-51行目） |

**主要処理フロー**:
- **37-51行目**: init_log_file - ファイルオープン、バッファなし設定
- **137-163行目**: init - パス設定、既存ログ引き継ぎ

### プログラム呼び出し階層図

```
log.info(...) / log.warn(...) / log.error(...)
    │
    └─ pushLog(level, ...)
           ├─ tablePack(...) → タブ区切り連結
           ├─ [error時] debugTraceBack(nil, 3)
           ├─ debugGetInfo(3, 'Sl') → source, currentline
           └─ m.raw(0, level, str, source, currentline, clock)
                  │
                  ├─ levelMapでレベル比較
                  ├─ [error時] ioStdErr:write()
                  ├─ init_log_file()
                  │      ├─ ioOpen(m.path, 'w') → 空で作成
                  │      ├─ ioOpen(m.path, 'ab') → 追記モード
                  │      └─ file:setvbuf('no')
                  ├─ osDate('%H:%M:%S', sec)
                  └─ m.file:write(buf)
```

### データフロー図

```
[入力]                     [処理]                         [出力]

log.info("message")
    │
    └───▶ pushLog ───▶ tableConcat ───▶ log.raw
              │              │               │
              │              │               ├─ レベルフィルタ
              │              │               ├─ タイムスタンプ生成
              │              │               └─ フォーマット
              │              │                      │
              │              │                      ├─ ログファイル
              │              │                      ├─ [error] stderr
              │              │                      └─ [print] console
              │              │
              └─ error時 ───▶ スタックトレース追加
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| log.lua | `script/log.lua` | ソース | ログ機能の実装 |
| bee.time | `(外部モジュール)` | ライブラリ | 高精度タイムスタンプ |
| bee.filesystem | `(外部モジュール)` | ライブラリ | ファイルシステム操作 |
