Log 循環紀錄部署(TAMS + HMI)#

  • TAMS:每小時更新當天 log;每日 00:10 完整匯出前一天。
  • HMI:每日 09:00172.18.35.29:3100 完整匯出前一天。
  • 兩者皆保留 90 天。
  • 輸出格式為 JSONL。
  • Log 儲存根目錄為 /Data/Log/
  • 使用 loki-readers 群組控管讀取權限。

目錄結構:

/Data/Log/
├── tams/
│   ├── 2026-08-04.jsonl
│   └── 2026-08-05.jsonl
├── hmi/
│   └── 2026-08-04.jsonl
├── loki-export-cron.log
└── loki-export-hmi-cron.log

1. 安裝 LogCLI#

已安裝者可跳過。

LogCLI 建議與 Loki 使用相同版本。以下以 Loki v2.9.2、Linux amd64 為例:

sudo apt-get update
sudo apt-get install -y curl unzip util-linux

tmp_dir="$(mktemp -d)"

curl -fL \
  https://github.com/grafana/loki/releases/download/v2.9.2/logcli-linux-amd64.zip \
  -o "$tmp_dir/logcli.zip"

unzip "$tmp_dir/logcli.zip" -d "$tmp_dir"

sudo install \
  -m 0755 \
  "$tmp_dir/logcli-linux-amd64" \
  /usr/local/bin/logcli

rm -rf "$tmp_dir"

sudo /usr/local/bin/logcli --version

2. 建立群組授權#

建議使用群組授權,不要直接開放所有帳號讀取 backend log。

建立系統群組:

sudo groupadd --system loki-readers 2>/dev/null || true

將一般使用者加入群組。以下以 compal 為例:

sudo usermod -aG loki-readers compal

使用者需重新登入,新的群組權限才會生效。

也可以在目前終端立即切換群組:

newgrp loki-readers

確認:

id compal

輸出應包含:

loki-readers

新增其他可讀取使用者:

sudo usermod -aG loki-readers 使用者名稱

3. 建立輸出與腳本目錄#

建立腳本目錄:

sudo install -d -m 0750 /opt/loki-export

建立 Log 目錄,並指定 loki-readers 群組:

sudo install \
  -d \
  -o root \
  -g loki-readers \
  -m 2750 \
  /Data/Log

sudo install \
  -d \
  -o root \
  -g loki-readers \
  -m 2750 \
  /Data/Log/tams

sudo install \
  -d \
  -o root \
  -g loki-readers \
  -m 2750 \
  /Data/Log/hmi

2750 中的 2 代表 setgid。

在這些目錄內建立的新檔案或子目錄,會自動繼承 loki-readers 群組。

確認目錄權限:

ls -ld \
  /Data/Log \
  /Data/Log/tams \
  /Data/Log/hmi

預期類似:

drwxr-s--- root loki-readers /Data/Log
drwxr-s--- root loki-readers /Data/Log/tams
drwxr-s--- root loki-readers /Data/Log/hmi

4. 建立共用匯出腳本#

sudo tee /opt/loki-export/fetch-backend-logs.sh > /dev/null <<'EOF'
#!/usr/bin/env bash

set -Eeuo pipefail

export TZ="${TZ:-Asia/Taipei}"
export LOKI_ADDR="${LOKI_ADDR:-http://localhost:3100}"

LOGCLI="${LOGCLI:-/usr/local/bin/logcli}"
OUTPUT_DIR="${OUTPUT_DIR:-/Data/Log/tams}"
RETENTION_DAYS="${RETENTION_DAYS:-90}"
MODE="${1:-today}"

if ! getent group loki-readers > /dev/null; then
  echo "Error: group loki-readers does not exist" >&2
  exit 1
fi

if [[ -z "${QUERY:-}" ]]; then
  QUERY='{compose_service="backend"}'
fi

today="$(date '+%Y-%m-%d')"

case "$MODE" in
  today)
    target_date="$today"
    start_time="$(
      date \
        --date="${target_date} 00:00:00" \
        '+%Y-%m-%dT%H:%M:%S%:z'
    )"
    end_time="$(date '+%Y-%m-%dT%H:%M:%S%:z')"
    ;;

  yesterday)
    target_date="$(date --date='yesterday' '+%Y-%m-%d')"
    start_time="$(
      date \
        --date="${target_date} 00:00:00" \
        '+%Y-%m-%dT%H:%M:%S%:z'
    )"
    end_time="$(
      date \
        --date="${today} 00:00:00" \
        '+%Y-%m-%dT%H:%M:%S%:z'
    )"
    ;;

  *)
    echo "Usage: $0 today|yesterday" >&2
    exit 2
    ;;
esac

mkdir -p "$OUTPUT_DIR"

output_file="${OUTPUT_DIR}/${target_date}.jsonl"
temp_file="$(mktemp "${output_file}.tmp.XXXXXX")"

cleanup() {
  rm -f "$temp_file"
}

trap cleanup EXIT HUP INT TERM

"$LOGCLI" query \
  --timezone=Local \
  --from="$start_time" \
  --to="$end_time" \
  --forward \
  --quiet \
  --output=jsonl \
  --limit=0 \
  --batch=1000 \
  --retries=3 \
  "$QUERY" > "$temp_file"

chgrp loki-readers "$temp_file"
chmod 0640 "$temp_file"
mv "$temp_file" "$output_file"

trap - EXIT HUP INT TERM

if [[ "$MODE" == "yesterday" ]]; then
  find "$OUTPUT_DIR" \
    -type f \
    -name '????-??-??.jsonl' \
    -mtime "+$((RETENTION_DAYS - 1))" \
    -delete
fi

echo "$(date --iso-8601=seconds) exported ${target_date} to ${output_file}"
EOF

設定腳本權限:

sudo chown root:root /opt/loki-export/fetch-backend-logs.sh
sudo chmod 0750 /opt/loki-export/fetch-backend-logs.sh

檢查 Shell 語法:

sudo bash -n /opt/loki-export/fetch-backend-logs.sh

注意:LogCLI 查詢最後必須是:

"$QUERY"

不要額外加入任何 }

5. 設定既有檔案權限#

/Data/Log/ 已有匯出的 JSONL 檔案:

sudo find /Data/Log \
  -type f \
  -name '*.jsonl' \
  -exec chown root:loki-readers {} \; \
  -exec chmod 0640 {} \;

預期檔案權限:

-rw-r----- 1 root loki-readers ... 2026-08-04.jsonl

6. 手動測試 TAMS#

匯出前一天的 TAMS backend log:

sudo /usr/bin/flock --verbose -n \
  /run/loki-export.lock \
  /usr/bin/env \
  OUTPUT_DIR=/Data/Log/tams \
  RETENTION_DAYS=90 \
  /opt/loki-export/fetch-backend-logs.sh yesterday

匯出今天截至目前的 TAMS backend log:

sudo /usr/bin/flock --verbose -n \
  /run/loki-export.lock \
  /usr/bin/env \
  OUTPUT_DIR=/Data/Log/tams \
  /opt/loki-export/fetch-backend-logs.sh today

7. 手動測試 HMI#

匯出前一天的 HMI backend log:

sudo /usr/bin/flock --verbose -n \
  /run/loki-export-hmi.lock \
  /usr/bin/env \
  LOKI_ADDR=http://172.18.35.29:3100 \
  OUTPUT_DIR=/Data/Log/hmi \
  RETENTION_DAYS=90 \
  QUERY='{container="polymed-hmi-backend"}' \
  /opt/loki-export/fetch-backend-logs.sh yesterday

8. 確認匯出結果#

列出所有 JSONL:

sudo find /Data/Log \
  -type f \
  -name '*.jsonl' \
  -ls

查看 TAMS 前一天檔案:

sudo head -n 5 \
  /Data/Log/tams/"$(date --date='yesterday' '+%Y-%m-%d')".jsonl

查看 HMI 前一天檔案:

sudo head -n 5 \
  /Data/Log/hmi/"$(date --date='yesterday' '+%Y-%m-%d')".jsonl

9. 建立 Cron 執行紀錄檔#

先建立 Cron log,並指定群組與權限:

sudo install \
  -o root \
  -g loki-readers \
  -m 0640 \
  /dev/null \
  /Data/Log/loki-export-cron.log

sudo install \
  -o root \
  -g loki-readers \
  -m 0640 \
  /dev/null \
  /Data/Log/loki-export-hmi-cron.log

確認:

ls -l \
  /Data/Log/loki-export-cron.log \
  /Data/Log/loki-export-hmi-cron.log

預期:

-rw-r----- 1 root loki-readers ... /Data/Log/loki-export-cron.log
-rw-r----- 1 root loki-readers ... /Data/Log/loki-export-hmi-cron.log

10. 設定 root Cron#

編輯 root crontab:

sudo crontab -e

加入:

# 每小時第 5 分鐘,覆寫更新當天 TAMS log
5 * * * * OUTPUT_DIR=/Data/Log/tams /usr/bin/flock -n /run/loki-export.lock /opt/loki-export/fetch-backend-logs.sh today >> /Data/Log/loki-export-cron.log 2>&1

# 每天 00:10,完整匯出前一天 TAMS log,保留 90 天
10 0 * * * OUTPUT_DIR=/Data/Log/tams RETENTION_DAYS=90 /usr/bin/flock -n /run/loki-export.lock /opt/loki-export/fetch-backend-logs.sh yesterday >> /Data/Log/loki-export-cron.log 2>&1

# 每天 09:00,完整匯出前一天 HMI backend log,保留 90 天
0 9 * * * /usr/bin/flock --verbose -n /run/loki-export-hmi.lock /usr/bin/env LOKI_ADDR=http://172.18.35.29:3100 OUTPUT_DIR=/Data/Log/hmi RETENTION_DAYS=90 QUERY='{container="polymed-hmi-backend"}' /opt/loki-export/fetch-backend-logs.sh yesterday >> /Data/Log/loki-export-hmi-cron.log 2>&1

確認 Cron:

sudo crontab -l

11. 查看 Cron 執行紀錄#

查看 TAMS 執行紀錄:

sudo tail -f /Data/Log/loki-export-cron.log

查看 HMI 執行紀錄:

sudo tail -f /Data/Log/loki-export-hmi-cron.log

清空 Cron log 時,建議使用:

sudo truncate -s 0 /Data/Log/loki-export-cron.log
sudo truncate -s 0 /Data/Log/loki-export-hmi-cron.log

不要直接刪除:

sudo rm /Data/Log/loki-export-cron.log
sudo rm /Data/Log/loki-export-hmi-cron.log

若檔案被刪除後由 Cron 重新建立,檔案群組與權限可能受到 root umask 影響。

12. 驗證群組讀取權限#

確認使用者群組:

id compal

確認目錄:

ls -ld \
  /Data/Log \
  /Data/Log/tams \
  /Data/Log/hmi

確認檔案:

ls -l /Data/Log/tams
ls -l /Data/Log/hmi

compal 使用者測試讀取:

sudo -u compal \
  head -n 1 \
  /Data/Log/tams/"$(date --date='yesterday' '+%Y-%m-%d')".jsonl

檢查路徑上每一層權限:

namei -l /Data/Log/tams

13. 常用維護指令#

查看目前匯出檔案容量:

sudo du -sh /Data/Log/tams
sudo du -sh /Data/Log/hmi

查看最新檔案:

ls -lht /Data/Log/tams | head
ls -lht /Data/Log/hmi | head

檢查腳本語法:

sudo bash -n /opt/loki-export/fetch-backend-logs.sh

測試 Loki 是否可連線:

LOKI_ADDR=http://localhost:3100 \
  /usr/local/bin/logcli labels

測試 HMI Loki:

LOKI_ADDR=http://172.18.35.29:3100 \
  /usr/local/bin/logcli labels