You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
4.3 KiB
146 lines
4.3 KiB
package config |
|
|
|
import ( |
|
"os" |
|
"strconv" |
|
"strings" |
|
|
|
"github.com/joho/godotenv" |
|
) |
|
|
|
// Config holds all runtime settings, loaded from environment (.env optional). |
|
type Config struct { |
|
// Slack |
|
SlackBotToken string |
|
DefaultSlackChannel string // 모든 이벤트 브로드캐스트 채널 |
|
MonitorChannel string // 에러(slog.Error) 발생 시 알림을 보낼 채널 (예: monitor-dev) |
|
|
|
// Gitea |
|
GiteaWebhookSecret string |
|
GiteaBroadcast bool // true면 모든 Gitea 이벤트를 DefaultSlackChannel로 브로드캐스트(기본 off, DM만) |
|
|
|
// Notion |
|
NotionVerificationToken string |
|
NotionAPIToken string |
|
NotionAssigneeProperty string |
|
|
|
// App |
|
MappingFile string |
|
Addr string |
|
|
|
// Logging |
|
Env string // development | production |
|
LogLevel string // debug | info | warn | error |
|
LogFormat string // text | json |
|
LogFile string // optional file path; empty = stdout only (날짜별 회전의 기준 경로) |
|
LogRetentionDays int // 날짜별 로그 파일 보존 일수 (기본 7) |
|
} |
|
|
|
// Load reads configuration for the given environment and loads its .env files. |
|
// |
|
// The environment is decided by the run command (the -env flag passed as |
|
// envOverride). If empty, it falls back to the APP_ENV variable, then |
|
// "development". This keeps env selection command-driven, not ambient. |
|
// |
|
// Precedence: an earlier-loaded file wins (godotenv.Load never overrides an |
|
// already-set variable), and missing files are ignored: |
|
// |
|
// .env.{env}.local # 개인 비밀값 (git 제외, 선택) |
|
// .env.{env} # 환경별 설정 (.env.dev / .env.prod) |
|
// .env # 공통 기본값 (fallback) |
|
func Load(envOverride string) Config { |
|
env := envOverride |
|
if env == "" { |
|
env = getenv("APP_ENV", "dev") |
|
} |
|
env = normalizeEnv(env) |
|
|
|
_ = godotenv.Load(".env." + env + ".local") |
|
_ = godotenv.Load(".env." + env) |
|
_ = godotenv.Load(".env") |
|
|
|
return Config{ |
|
SlackBotToken: os.Getenv("SLACK_BOT_TOKEN"), |
|
DefaultSlackChannel: os.Getenv("DEFAULT_SLACK_CHANNEL"), |
|
MonitorChannel: os.Getenv("MONITOR_SLACK_CHANNEL"), |
|
GiteaWebhookSecret: os.Getenv("GITEA_WEBHOOK_SECRET"), |
|
GiteaBroadcast: getenvBool("GITEA_BROADCAST", false), |
|
NotionVerificationToken: os.Getenv("NOTION_VERIFICATION_TOKEN"), |
|
NotionAPIToken: os.Getenv("NOTION_API_TOKEN"), |
|
NotionAssigneeProperty: getenv("NOTION_ASSIGNEE_PROPERTY", "담당자"), |
|
MappingFile: getenv("MAPPING_FILE", "data/mappings.json"), |
|
Addr: getenv("ADDR", ":8000"), |
|
|
|
Env: env, |
|
LogLevel: getenv("LOG_LEVEL", defaultLogLevel(env)), |
|
LogFormat: getenv("LOG_FORMAT", defaultLogFormat(env)), |
|
LogFile: getenv("LOG_FILE", defaultLogFile(env)), |
|
LogRetentionDays: getenvInt("LOG_RETENTION_DAYS", 7), |
|
} |
|
} |
|
|
|
// normalizeEnv canonicalizes the env name to the short form "dev" or "prod". |
|
// Long forms (development/production) and unknown values map sensibly. |
|
func normalizeEnv(s string) string { |
|
switch strings.ToLower(strings.TrimSpace(s)) { |
|
case "prod", "production": |
|
return "prod" |
|
default: |
|
return "dev" |
|
} |
|
} |
|
|
|
// IsProduction reports whether the app runs in the production environment. |
|
func (c Config) IsProduction() bool { return c.Env == "prod" } |
|
|
|
func defaultLogLevel(env string) string { |
|
if env == "prod" { |
|
return "info" |
|
} |
|
return "debug" |
|
} |
|
|
|
func defaultLogFormat(env string) string { |
|
if env == "prod" { |
|
return "json" |
|
} |
|
return "text" |
|
} |
|
|
|
// defaultLogFile decides where logs are written when LOG_FILE is unset: |
|
// - dev → "" (콘솔/IDE 출력만) |
|
// - prod → 파일로 추출 (logs/app.log) |
|
func defaultLogFile(env string) string { |
|
if env == "prod" { |
|
return "logs/app.log" |
|
} |
|
return "" |
|
} |
|
|
|
func getenv(key, fallback string) string { |
|
if v := os.Getenv(key); v != "" { |
|
return v |
|
} |
|
return fallback |
|
} |
|
|
|
func getenvInt(key string, fallback int) int { |
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" { |
|
if n, err := strconv.Atoi(v); err == nil { |
|
return n |
|
} |
|
} |
|
return fallback |
|
} |
|
|
|
// getenvBool은 on/off, true/false, 1/0, yes/no를 모두 받는다. 미설정/인식불가 시 fallback. |
|
func getenvBool(key string, fallback bool) bool { |
|
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) { |
|
case "1", "true", "t", "yes", "y", "on": |
|
return true |
|
case "0", "false", "f", "no", "n", "off": |
|
return false |
|
default: |
|
return fallback |
|
} |
|
}
|
|
|