From dbadd936ca55a4d7c74ce4701af3e449ebcea4ef Mon Sep 17 00:00:00 2001 From: mineseo-kim Date: Thu, 9 Apr 2026 10:00:58 +0900 Subject: [PATCH] feat(logging): wire LOG_DIR through env and align systemd helper - logger uses config/env for LOG_DIR and LOG_LEVEL (single source of truth) - Document absolute LOG_DIR for wipe/redeploy (Jenkins) in env and .env.example - setup-kord-user-log-file.sh reads LOG_DIR from KORD_HOME/.env and syncs StandardOutput/StandardError/ExecStartPre mkdir to the same path Made-with: Cursor --- .env.example | 5 ++- scripts/setup-kord-user-log-file.sh | 57 +++++++++++++++++++++++------ src/config/env.ts | 6 ++- src/utils/logger.ts | 18 ++++++--- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index fa5b785..4546796 100644 --- a/.env.example +++ b/.env.example @@ -9,5 +9,6 @@ DATABASE_URL="postgresql://kord:password@localhost:5432/kord_db?schema=public" # Logging (log4js — file only under LOG_DIR, no console appender) # Levels: trace, debug, info, warn, error, fatal LOG_LEVEL=info -# Daily log files; keep 7 rotated days (see logger.ts). Directory is created at startup if missing. -LOG_DIR=logs \ No newline at end of file +# Log directory (kord.log + dated rotations). Relative = from process cwd; use an absolute path on servers +# if the deploy directory is wiped (e.g. Jenkins): LOG_DIR=/var/lib/kord/logs +LOG_DIR=logs diff --git a/scripts/setup-kord-user-log-file.sh b/scripts/setup-kord-user-log-file.sh index bdc20b3..bd725f0 100644 --- a/scripts/setup-kord-user-log-file.sh +++ b/scripts/setup-kord-user-log-file.sh @@ -1,12 +1,35 @@ #!/usr/bin/env bash # Run ON THE SERVER as the same user that runs kord (e.g. psa), after: ssh psa@server -# Switches kord user service from journal-only to append logs under ~/kord/logs/kord.log +# Switches kord user service from journal-only to append stdout/stderr under LOG_DIR/kord.log +# LOG_DIR is read from $KORD_HOME/.env (LOG_DIR=...) when present, else $KORD_HOME/logs. set -euo pipefail +KORD_HOME="${KORD_HOME:-$HOME/kord}" +ENV_FILE="${KORD_ENV_FILE:-$KORD_HOME/.env}" UNIT="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/kord.service" -LOG_DIR="$HOME/kord/logs" -LOG_FILE="$LOG_DIR/kord.log" + +# Last LOG_DIR= line from .env; strip quotes and ~ ; relative paths are under KORD_HOME +resolve_log_dir() { + local default="${KORD_HOME}/logs" line raw + [[ -f "$ENV_FILE" ]] || { echo "$default"; return; } + line="$(grep -E '^[[:space:]]*LOG_DIR[[:space:]]*=' "$ENV_FILE" | tail -n1 || true)" + [[ -z "$line" ]] && { echo "$default"; return; } + raw="${line#*=}" + raw="$(printf '%s' "$raw" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e $'s/\r$//')" + if [[ "$raw" =~ ^\".*\"$ ]]; then raw="${raw#\"}"; raw="${raw%\"}"; fi + if [[ "$raw" =~ ^\'.*\'$ ]]; then raw="${raw#\'}"; raw="${raw%\'}"; fi + raw="${raw//\~/$HOME}" + [[ -z "$raw" ]] && { echo "$default"; return; } + if [[ "$raw" = /* ]]; then + echo "$raw" + else + echo "${KORD_HOME}/${raw#./}" + fi +} + +LOG_DIR="$(resolve_log_dir)" +LOG_FILE="${LOG_DIR}/kord.log" mkdir -p "$LOG_DIR" @@ -17,17 +40,28 @@ fi cp -a "$UNIT" "${UNIT}.bak.$(date +%Y%m%d%H%M%S)" -# Replace journal with file append (idempotent if already set) +# Point journal or any previous append paths at the log file derived from .env LOG_DIR sed -i \ - -e 's|^StandardOutput=journal|StandardOutput=append:'"$LOG_FILE"'|' \ - -e 's|^StandardError=journal|StandardError=append:'"$LOG_FILE"'|' \ + -e "s|^StandardOutput=journal|StandardOutput=append:${LOG_FILE}|" \ + -e "s|^StandardError=journal|StandardError=append:${LOG_FILE}|" \ + "$UNIT" +sed -i \ + -e "s|^StandardOutput=append:.*|StandardOutput=append:${LOG_FILE}|" \ + -e "s|^StandardError=append:.*|StandardError=append:${LOG_FILE}|" \ "$UNIT" -# systemd opens StandardOutput=append before ExecStart; if ~/kord/logs was deleted, -# the service fails with (code=exited, status=209/STDOUT). Ensure the dir exists first. -if ! grep -qE '^ExecStartPre=.*mkdir.*kord/logs' "$UNIT"; then - sed -i '/^ExecStart=/i ExecStartPre=-/usr/bin/mkdir -p %h/kord/logs' "$UNIT" -fi +# systemd opens StandardOutput=append before ExecStart; missing parent dir → status 209/STDOUT +sed -i '/^ExecStartPre=-\/usr\/bin\/mkdir -p /d' "$UNIT" +tmp="$(mktemp)" +inserted=0 +while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^ExecStart= ]] && [[ "$inserted" -eq 0 ]]; then + printf '%s\n' "ExecStartPre=-/usr/bin/mkdir -p ${LOG_DIR}" + inserted=1 + fi + printf '%s\n' "$line" +done <"$UNIT" >"$tmp" +mv "$tmp" "$UNIT" systemctl --user daemon-reload systemctl --user restart kord @@ -43,4 +77,5 @@ else fi echo +echo "LOG_DIR=$LOG_DIR" echo "Follow logs: tail -f $LOG_FILE" diff --git a/src/config/env.ts b/src/config/env.ts index 2be95c3..046a3cf 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -18,7 +18,11 @@ export const env = { VOICE_CATEGORY_ID: process.env.VOICE_CATEGORY_ID || '', /** log4js level: trace | debug | info | warn | error | fatal */ LOG_LEVEL: process.env.LOG_LEVEL || 'info', - /** Directory for rotated kord.log (see src/utils/logger.ts) */ + /** + * Directory for log4js `kord.log` (created at startup). Relative paths resolve from `process.cwd()`. + * For Jenkins or wipe-and-redeploy flows, set an absolute path **outside** the deploy tree (e.g. `/var/lib/kord/logs`) + * so logs survive redeploys and match `StandardOutput=append` in systemd if you point it at the same file. + */ LOG_DIR: process.env.LOG_DIR || 'logs', INSTANCE_ID: generateInstanceId(), }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 67e53d2..ac21e7a 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,19 +1,25 @@ import { mkdirSync } from 'fs'; -import { config } from 'dotenv'; import log4js from 'log4js'; import { resolve } from 'path'; - -// Load .env before reading LOG_LEVEL / LOG_DIR (same rule as config/env.ts). -config({ path: process.env.DOTENV_CONFIG_PATH || resolve(process.cwd(), '.env') }); +import { env } from '../config/env'; const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] as const; type LogLevel = (typeof LOG_LEVELS)[number]; function resolveLogLevel(): LogLevel { - const raw = (process.env.LOG_LEVEL || 'info').toLowerCase(); + const raw = env.LOG_LEVEL.toLowerCase(); return (LOG_LEVELS as readonly string[]).includes(raw) ? (raw as LogLevel) : 'info'; } +/** Resolves LOG_DIR from .env: absolute paths unchanged; relative paths from cwd. */ +function resolveLogDir(raw: string): string { + const trimmed = raw.trim(); + if (!trimmed) { + return resolve('logs'); + } + return resolve(trimmed); +} + function ensureLogDir(dir: string): void { try { mkdirSync(dir, { recursive: true }); @@ -24,7 +30,7 @@ function ensureLogDir(dir: string): void { } } -const logDir = resolve((process.env.LOG_DIR || 'logs').trim()); +const logDir = resolveLogDir(env.LOG_DIR); const level = resolveLogLevel(); ensureLogDir(logDir);