# 帳票設計書 7-log.txt

## 概要

本ドキュメントは、Lua Language Serverが出力する実行ログファイル`log.txt`の帳票設計を定義する。この帳票は、Language Serverの実行時ログをテキスト形式で出力し、デバッグやトラブルシューティングに活用される。

### 本帳票の処理概要

本帳票は、`script/log.lua`モジュールによって提供されるログ出力機能であり、Language Serverの実行中に発生するイベント、エラー、デバッグ情報を時系列で記録する。

**業務上の目的・背景**：Language Serverの運用・保守において、問題発生時の原因特定、パフォーマンス分析、動作確認のために実行ログが必要である。ユーザーからのバグ報告時にログを添付することで、再現・調査を効率化できる。

**帳票の利用シーン**：Language Serverの異常動作調査時、パフォーマンス問題の分析時、開発中のデバッグ時、またユーザーサポート対応時に利用される。

**主要な出力内容**：
1. タイムスタンプ（時:分:秒.ミリ秒）
2. ログレベル（trace/debug/info/warn/error）
3. スレッドID
4. ソースファイル位置（ファイル名:行番号）
5. ログメッセージ
6. エラー時のスタックトレース

**帳票の出力タイミング**：Language Server起動時から終了時まで、各ログレベルのAPI呼び出し時に出力される。

**帳票の利用者**：Language Server開発者、サポート担当者、上級ユーザー

## 帳票種別

実行ログ（テキスト形式）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| API | log.trace(...) | Luaコード内から呼び出し | traceレベルログ出力 |
| API | log.debug(...) | Luaコード内から呼び出し | debugレベルログ出力 |
| API | log.info(...) | Luaコード内から呼び出し | infoレベルログ出力 |
| API | log.warn(...) | Luaコード内から呼び出し | warnレベルログ出力 |
| API | log.error(...) | Luaコード内から呼び出し | errorレベルログ出力 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（.txt） |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | `log.txt`（m.pathで設定） |
| 出力方法 | ファイル追記 |
| 文字コード | UTF-8 |

### ログ出力設定

| 項目 | 内容 |
|-----|------|
| 最大ファイルサイズ | 100MB（m.maxSize） |
| バッファリング | 無効（setvbuf 'no'） |
| ログレベル | trace/debug/info/warn/error |
| デフォルトレベル | info |

## 帳票レイアウト

### レイアウト概要

各ログエントリは1行で構成され、以下のフォーマットで出力される。

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

または、ソースファイル情報がない場合:

```
[HH:MM:SS.mmm][level]     [#thread]: message
```

### ヘッダー部

N/A（ログファイルのためヘッダーなし）

### 明細部（各ログエントリ）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | タイムスタンプ | ログ出力時刻 | m.startTime + clock | [HH:MM:SS.mmm] |
| 2 | ログレベル | ログの重要度 | level引数 | [trace]/[debug]/[info]/[warn ]/[error] |
| 3 | スレッドID | 実行スレッド番号 | thd引数 | #N |
| 4 | ソースファイル | 呼び出し元ファイル | debug.getinfo(3, 'Sl').source | ファイルパス |
| 5 | 行番号 | 呼び出し元行番号 | debug.getinfo(3, 'Sl').currentline | 整数 |
| 6 | メッセージ | ログ内容 | msg引数 | 文字列 |
| 7 | スタックトレース | エラー時のコールスタック | debug.traceback() | 複数行（errorレベルのみ） |

### フッター部

N/A（ログファイルのためフッターなし）

### ログエントリ例

```
[10:30:45.123][info ]     [#0:script/workspace/init.lua:42]: Loading workspace: file:///project
[10:30:45.456][debug]     [#0:script/files.lua:128]: File opened: test.lua
[10:30:46.789][error]     [#0:script/provider/diagnostic.lua:100]: Unexpected error
stack traceback:
    [C]: in function 'error'
    script/provider/diagnostic.lua:100: in function 'doDiagnostic'
    ...
```

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| ログパス設定 | m.path が設定されている | Yes |
| ログレベル | levelMap[level] >= levelMap[m.level] | Yes |
| ファイルサイズ | m.size <= m.maxSize | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | 出力時刻 | 昇順（時系列） |

### 改ページ条件

N/A（テキストログのため改ページなし、100MB超過で出力停止）

## データベース参照仕様

### 参照テーブル一覧

本帳票はデータベースを使用せず、Luaデバッグ機能を参照する。

| データソース | 用途 | 取得方法 |
|-------------|------|---------|
| debug.getinfo() | 呼び出し元情報 | Lua Debug API |
| debug.traceback() | スタックトレース | Lua Debug API |
| bee.time | 時刻情報 | bee.time.monotonic() |
| os.date | 日時フォーマット | Lua標準 |

### テーブル別参照項目詳細

#### debug.getinfo()

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| source | ソースファイル | 'Sl'オプション | @prefix除去 |
| currentline | 行番号 | 'Sl'オプション | 整数 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| タイムスタンプ | (m.startTime + clock) / 1000 | ミリ秒3桁 | 秒とミリ秒を分離 |
| レベル幅調整 | ' ':rep(5 - #level) | N/A | 5文字に右寄せ |
| ソースパス | src:sub(2)（@除去） | N/A | @prefixを除去 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[log.xxx呼び出し] --> B[pushLog呼び出し]
    B --> C[引数をtablePack]
    C --> D[各引数をtostring変換]
    D --> E[table.concatで結合]
    E --> F{level == 'error'?}
    F -->|Yes| G[debug.traceback追加]
    F -->|No| H[debug.getinfo取得]
    G --> H
    H --> I[m.raw呼び出し]
    I --> J{levelMap[level] >= levelMap[m.level]?}
    J -->|No| K[メッセージのみ返却]
    J -->|Yes| L{level == 'error'?}
    L -->|Yes| M[stderr出力]
    L -->|No| N{m.size > m.maxSize?}
    M --> N
    N -->|Yes| K
    N -->|No| O[init_log_file]
    O --> P[タイムスタンプ生成]
    P --> Q[ログ行フォーマット]
    Q --> R[ファイル書き込み]
    R --> S[m.size更新]
    S --> T{m.print == true?}
    T -->|Yes| U[コンソール出力]
    T -->|No| V[終了]
    U --> V
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ファイル作成失敗 | ディレクトリ不存在・権限不足 | （内部でpcallで抑制） | ログディレクトリ作成 |
| 最大サイズ超過 | m.size > m.maxSize | [REACH MAX SIZE] | ログローテーション検討 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | セッションあたり数千〜数万エントリ |
| 目標出力時間 | 各ログ出力は1ms以下 |
| 最大ファイルサイズ | 100MB（m.maxSize） |

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

- ログにはファイルパスやソースコード位置が含まれるため、共有時は注意
- エラーメッセージにユーザーデータが含まれる可能性があるため、ログ共有時は機密情報の有無を確認
- ログファイルのアクセス権限を適切に設定

## 備考

- `m.level`でログレベルを設定可能（デフォルト: 'info'）
- `m.print = true`でコンソールにも出力
- `m.init(root, path)`でログファイルパスを初期化
- errorレベルのログは最初の1件を`m.firstError`に保持
- ログファイルは追記モード（'ab'）で開かれ、バッファリングなし
- m.init()で前回のログ内容を引き継ぎ可能

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | log.lua | `script/log.lua` | モジュール状態（17-28行目）でm.file, m.path, m.size, m.maxSize, m.levelMap等を確認 |

**読解のコツ**: `m.levelMap`でログレベルの優先度を確認。trace=1 < debug=2 < info=3 < warn=4 < error=5。

#### Step 2: エントリーポイントを理解する

ログ出力APIを特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | log.lua | `script/log.lua` | log.trace/debug/info/warn/error関数（71-92行目） |
| 2-2 | log.lua | `script/log.lua` | pushLog関数（53-69行目） |

**主要処理フロー**:
1. **71-72行目**: `m.trace(...)`でtraceレベルログ
2. **75-76行目**: `m.debug(...)`でdebugレベルログ
3. **79-80行目**: `m.info(...)`でinfoレベルログ
4. **83-84行目**: `m.warn(...)`でwarnレベルログ
5. **87-92行目**: `m.error(...)`でerrorレベルログ（戻り値あり）

#### Step 3: ログ出力処理を理解する

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

**主要処理フロー**:
- **53-69行目**: `pushLog()`で引数処理、スタックトレース追加（error時）
- **94-135行目**: `m.raw()`で実際のログ出力

#### Step 4: ファイル出力処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | log.lua | `script/log.lua` | init_log_file関数（37-51行目） |
| 4-2 | log.lua | `script/log.lua` | m.raw関数のファイル書き込み部分（121-128行目） |

**主要処理フロー**:
- **37-51行目**: `init_log_file()`でファイルオープン（なければ作成）
- **107行目**: `init_log_file()`呼び出し
- **108-109行目**: タイムスタンプ生成
- **114-118行目**: ログ行フォーマット
- **121-128行目**: ファイル書き込み（サイズ超過時は[REACH MAX SIZE]）

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

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

**主要処理フロー**:
- **139-146行目**: 既存ログファイルの読み込み（引き継ぎ）
- **148-149行目**: 新しいパス設定
- **151-155行目**: ディレクトリ作成
- **156-162行目**: 前回ログ内容の復元

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

```
m.trace/debug/info/warn/error(...)
    │
    └─ pushLog(level, ...)
           │
           ├─ tablePack(...) → 引数を配列化
           │
           ├─ tostring(t[i]) × N → 各引数を文字列化
           │
           ├─ tableConcat(t, '\t') → タブ区切りで結合
           │
           ├─ (level == 'error') → debugTraceBack追加
           │
           ├─ debugGetInfo(3, 'Sl') → 呼び出し元情報取得
           │
           └─ m.raw(thd, level, str, source, line, clock)
                  │
                  ├─ levelMap比較 → レベルフィルタ
                  │
                  ├─ (level == 'error') → ioStdErr出力
                  │
                  ├─ (m.size > m.maxSize) → 出力停止
                  │
                  ├─ init_log_file() → ファイルオープン
                  │      │
                  │      ├─ io.open(m.path, 'w') → 新規作成
                  │      ├─ io.open(m.path, 'ab') → 追記オープン
                  │      └─ file:setvbuf('no') → バッファ無効化
                  │
                  ├─ osDate('%H:%M:%S', sec) → 時刻フォーマット
                  │
                  ├─ ログ行フォーマット生成
                  │      └─ [時:分:秒.ミリ秒][レベル][#スレッド:ソース:行]: メッセージ
                  │
                  ├─ m.file:write(buf) → ファイル書き込み
                  │
                  ├─ m.size更新
                  │
                  └─ (m.print) → print(buf)
```

### データフロー図

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

log.xxx(...) ──────────▶ pushLog(level, ...)
                              │
                              ├─ 引数をタブ区切りで結合
                              │
                              ├─ error時: スタックトレース追加
                              │
                              ▼
                      m.raw(thd, level, str, ...)
                              │
                              ├─ レベルフィルタリング
                              │
                              ├─ error時: stderr出力
                              │
                              ├─ サイズチェック
                              │
                              ├─ タイムスタンプ生成
                              │
                              ├─ ログ行フォーマット
                              │
                              ▼
                    ─────────────────────▶ log.txt
                                           (追記)

                    ─────────────────────▶ stderr
                                           (error時)

                    ─────────────────────▶ stdout
                                           (m.print時)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| log.lua | `script/log.lua` | ソース | ログ出力の全機能を提供 |
