Skip to content

Configuration (ENV)

idin-core v2 อ่าน config ผ่าน core.IENV — ชื่อและ method เหมือน v1 แต่ข้างในเป็น koanf แบบ instance (ไม่มี global viper) และ ไม่ต้อง maintain รายการ key ซ้ำ อีกต่อไป (v1 ต้องแก้ทั้ง ENVConfig และ envKeys)

โหลด config

go
import core "gitlab.finema.co/finema/idin-core/v2"

env, err := core.NewEnv()          // อ่านจาก ./.env (หรือ ./test.env เมื่อ APP_ENV=test)
if err != nil {
    log.Fatal(err)
}

env, err := core.NewEnvPath("./config")   // ระบุ directory เอง

ทั้งสองฟังก์ชันคืน core.IError และ fail fast — config ผิดรู้ตั้งแต่ boot ไม่ใช่ตอน request แรก

ลำดับความสำคัญ

ทีหลังทับก่อนหน้า:

  1. ไฟล์ .env (หรือ test.env) — ไม่บังคับ ไม่มีไฟล์ก็ทำงานได้
  2. environment variables ที่ขึ้นต้นด้วย APP_

กติกาการตั้งชื่อ key (จุดที่พลาดกันบ่อย)

ที่ตั้งเขียนยังไงกลายเป็น key
OS environmentAPP_DB_HOST=localhostdb_host
ไฟล์ .envDB_HOST=localhostdb_host
  • OS env ต้องมี prefix APP_ — ตัวที่ไม่มี prefix จะถูกมองข้ามทั้งหมด (กัน env var ของระบบอย่าง HOST, PATH มาชนกับ config ของเรา)
  • ในไฟล์ .env ต้อง ไม่ มี prefix — ถ้าเขียน APP_DB_HOST=... ในไฟล์ จะได้ key app_db_host ซึ่งไม่ผูกกับ field ไหนเลย และ ไม่มี error แจ้ง
  • key ถูกแปลงเป็นตัวพิมพ์เล็กเสมอ — DB_HOST, db_host, Db_Host ค่าเท่ากัน

รูปแบบไฟล์ .env

รองรับไวยากรณ์ dotenv มาตรฐาน (ผ่าน godotenv):

sh
# คอมเมนต์ได้
ENV=dev
SERVICE=payment-api

DB_PASSWORD="ค่าที่มี ช่องว่าง ใส่ quote"
DB_CONNECTION_STRING='postgres://u:p@localhost:5432/app?sslmode=disable'

export CACHE_HOST=localhost      # ใส่ export นำหน้าได้ (ถูกตัดทิ้ง)

FIREBASE_CREDENTIAL="{
  \"type\": \"service_account\"
}"                                # multi-line ต้องอยู่ใน double quote

APP_ENV เลือกไฟล์ที่จะอ่าน

APP_ENV=test → อ่าน test.env แทน .env

⚠️ ค่านี้ถูกอ่านจาก OS environment เท่านั้น — ใส่ ENV=test ในไฟล์ .env ไม่ทำให้สลับไปอ่าน test.env (ตอนนั้นยังไม่ได้อ่านไฟล์เลย) ตอนรัน test ให้ใช้ APP_ENV=test go test ./... หรือ t.Setenv("APP_ENV", "test")

ค่าที่ยอมรับคือ dev / test / mock / prod หรือปล่อยว่าง — นอกจากนี้ NewEnv คืน error INVALID_CONFIG ทันที


อ้างอิง key ทั้งหมด

ตารางข้างล่างใช้ชื่อแบบที่เขียนใน .env (เติม APP_ นำหน้าเมื่อตั้งผ่าน OS env) คอลัมน์ default คือค่าที่ framework ใช้เมื่อ ไม่ได้ตั้ง

Application

KeyชนิดDefaultคำอธิบาย
ENVstring(ว่าง)dev / test / mock / prod มีผลต่อพฤติกรรมหลายอย่าง (ดูตารางถัดไป) ค่าอื่นทำให้ boot ไม่ผ่าน
SERVICEstring(ว่าง)ชื่อ service ใช้เป็น tag service ในทุก Sentry event ควรตั้งเสมอเมื่อมีหลาย service ส่งเข้า Sentry เดียวกัน
HOSTstring:8080address ที่ StartHTTPServer bind — รูปแบบ host:port เช่น :3000 หรือ 127.0.0.1:3000

ENV เปลี่ยนอะไรบ้าง

พฤติกรรมdevอื่นๆ
message ใน error responseเปิดเผยข้อความจริงจาก root cause (ข้อความของ driver/library ตัวจริง ไม่ใช่ป้ายของ wrapper) รวมถึงข้อความ panicใช้ message ของ error type เท่านั้น (ไม่รั่วรายละเอียดภายใน)
StartHTTPServerblock ตรงๆ ปิดทันทีเมื่อ Ctrl-Cรอ signal แล้ว graceful shutdown 10 วินาที
ไฟล์ config.envtest.env เมื่อ APP_ENV=test
Sentry environmentdevใช้ค่า ENV (ถ้าไม่ได้ตั้ง SENTRY_ENVIRONMENT)

Logging

KeyชนิดDefaultคำอธิบาย
LOG_LEVELstringinfodebug / info / warn / error — ค่าที่ไม่รู้จักถือเป็น info debug เปิด log ของ SQL ทุก statement ด้วย (ดู Database)
LOG_SIMPLEboolfalsetrue = text handler อ่านง่ายสำหรับ dev, false = JSON สำหรับ production log pipeline

LOG_HOST / LOG_PORT ยังมีอยู่ใน ENVConfig เพื่อความเข้ากันได้กับ v1 (graylog) แต่ v2 ยังไม่ได้ใช้ — ตั้งไว้ก็ไม่มีผล

Sentry

ตั้งแค่ SENTRY_DSN ตัวเดียวก็ครบทุกอย่าง ที่เหลือคือการปรับจูน — รายละเอียดพฤติกรรมอยู่ที่ Sentry

KeyชนิดDefaultคำอธิบาย
SENTRY_DSNstring(ว่าง)ว่าง = ปิดทั้งระบบ (ทุก method เป็น no-op) DSN ผิดรูปแบบ = boot ไม่ผ่าน
SENTRY_ENVIRONMENTstringค่า ENVใช้แยก issue ระหว่าง staging/production ใน Sentry
SENTRY_RELEASEstring(ว่าง)version หรือ commit sha — ทำให้ Sentry บอกได้ว่า bug นี้เกิดใหม่ใน release ไหน (regression tracking)
SENTRY_SERVER_NAMEstringhostnameชื่อเครื่อง/pod ที่ส่ง event
SENTRY_DEBUGboolfalseให้ SDK พิมพ์การทำงานของตัวเองออก stderr ใช้ตอน "ทำไมไม่เห็น event"
SENTRY_SAMPLE_RATEfloat1.0สัดส่วน error ที่ส่งจริง (0.25 = 25%) ลดเมื่อ error เยอะจนกินโควตา
SENTRY_TRACES_SAMPLE_RATEfloat0สัดส่วน transaction ที่ส่ง — ตั้ง > 0 ถือว่าเปิด tracing โดยปริยาย
SENTRY_ENABLE_TRACINGboolfalseเปิด transaction ของ request/job/HTTP call ขาออก ถ้าเปิดโดยไม่ตั้ง sample rate จะใช้ 1.0
SENTRY_ENABLE_CRONSboolfalseส่ง check-in ของ job ที่มี Schedule ทำให้ Sentry เตือนเมื่อ run ที่ควรเกิดแต่ไม่เกิด ⚠️ เปิดแล้ว Sentry จะสร้าง monitor อัตโนมัติ (มีโควตาแยก)
SENTRY_MIN_STATUSint500เส้นแบ่งเดียวของทั้งระบบ: error ที่ status ต่ำกว่านี้ไม่ถือเป็น incident ตั้ง 400 ถ้าอยากเห็น 4xx ด้วย
SENTRY_CAPTURE_RETRIESboolfalsefalse = job รายงานเฉพาะตอน attempt หมดแล้ว, true = รายงานทุก attempt ที่ fail
SENTRY_BREADCRUMB_LEVELstringinfoระดับ log ต่ำสุดที่กลายเป็น breadcrumb — debug / info / warn / error / off
SENTRY_MAX_BREADCRUMBSint50จำนวน breadcrumb สูงสุดต่อ event (อันเก่าสุดถูกดันออก)
SENTRY_ATTACH_STACKTRACEbooltrueแนบ stack แม้กับ event ที่ไม่ได้มาจาก error
SENTRY_SEND_DEFAULT_PIIboolfalseให้ SDK ส่ง IP/cookie ตาม default ของมัน (framework redact header ที่อ่อนไหวให้อยู่แล้ว)
SENTRY_CAPTURE_BODYbooltrueแนบ request body ของ request ที่ fail (scrub แล้ว, ข้าม multipart/binary)
SENTRY_MAX_BODY_BYTESint16384เพดาน body ที่เก็บ (16 KiB) — เกินจากนี้ถูกตัด
SENTRY_SEND_ENVbooltrueแนบ config ทั้งชุด (scrub แล้ว) เป็น context config ของทุก event
SENTRY_IGNORE_ERRORSstring(ว่าง)regex คั่นด้วย , — event ที่ message ตรงจะถูกทิ้ง เช่น context canceled,broken pipe
SENTRY_IGNORE_TRANSACTIONSstring(ว่าง)regex คั่นด้วย , สำหรับ transaction เช่น GET /health
SENTRY_FLUSH_TIMEOUTint (วินาที)5เวลารอส่ง event ที่ค้างตอน app.Shutdown

Database (SQL)

KeyชนิดDefaultคำอธิบาย
DB_CONNECTION_STRINGstring(ว่าง)URI เต็ม — ถ้าตั้งไว้ ฟิลด์ DB_* ที่เหลือถูกมองข้ามทั้งหมด
DB_DRIVERstring(ว่าง)postgres หรือ mysqlบังคับ เมื่อไม่ได้ใช้ connection string (ถ้าใช้ URI จะเดาจาก scheme ให้)
DB_HOSTstring(ว่าง)hostname ของ DB
DB_PORTstring(ว่าง)port (เป็น string เพราะต่อเข้า DSN ตรงๆ)
DB_NAMEstring(ว่าง)ชื่อ database
DB_USERstring(ว่าง)user
DB_PASSWORDstring(ว่าง)password — ถูก mask ในทุก Sentry event เสมอ
DB_SSLMODEstringdisableเฉพาะ postgres: disable / require / verify-full
  • scheme ที่เดา driver ได้: postgres://, postgresql://, mysql://
  • MySQL: ใส่เป็น URI ได้เลย framework แปลงเป็น Go DSN (u:p@tcp(host:port)/db) พร้อมเติม parseTime=True&charset=utf8mb4&loc=UTC ให้เมื่อไม่ได้ระบุ
  • ขนาด connection pool ไม่ได้อยู่ใน env — ตั้งผ่าน option ตอนสร้าง:
    go
    db, _ := core.NewDatabase(env,
        core.WithMaxOpenConns(50),   // default 20
        core.WithMaxIdleConns(10),   // default 5
        core.WithConnMaxLifetime(30*time.Minute), // default 1h
    )
  • DB_SID มีอยู่ใน struct (Oracle ของ v1) แต่ v2 ไม่ได้ใช้

MongoDB

KeyชนิดDefaultคำอธิบาย
DB_MONGO_CONNECTION_STRINGstring(ว่าง)URI เต็ม ใช้ก่อนฟิลด์แยกเสมอ — จำเป็นถ้าต้องการ replica set / TLS / options อื่น
DB_MONGO_HOSTstring(ว่าง)ใช้เมื่อไม่มี connection string
DB_MONGO_PORTstring(ว่าง)
DB_MONGO_USERNAMEstring(ว่าง)
DB_MONGO_PASSWORDstring(ว่าง)ถูก mask ใน Sentry
DB_MONGO_NAMEstring(ว่าง)ชื่อ database ที่ ctx.DBMongo() ใช้ (ไม่ได้อยู่ใน URI) — ต้องตั้งเสมอ แม้ใช้ connection string

DB_MONGO_REPLICA_NAME และ DB_MONGO_TLS ยังไม่ถูกใช้ใน v2 — ต้องการ replica set หรือ TLS ให้ใส่ใน DB_MONGO_CONNECTION_STRING แทน (mongodb://u:p@a,b,c/?replicaSet=rs0&tls=true)

Cache (Redis)

KeyชนิดDefaultคำอธิบาย
CACHE_CONNECTION_STRINGstring(ว่าง)redis://:pass@host:6379/0 หรือ rediss:// สำหรับ TLS — ใช้ก่อนฟิลด์แยก
CACHE_HOSTstring(ว่าง)
CACHE_PORTstring(ว่าง)
CACHE_PASSWORDstring(ว่าง)ถูก mask ใน Sentry
CACHE_DBint0หมายเลข database ของ redis

Message Queue (RabbitMQ)

KeyชนิดDefaultคำอธิบาย
MQ_CONNECTION_STRINGstring(ว่าง)amqp://u:p@host:5672/vhost (amqps:// = TLS) — ใช้ก่อนฟิลด์แยก จำเป็นถ้าต้องระบุ vhost
MQ_HOSTstring(ว่าง)ฟิลด์แยกจะต่อ URL เป็น amqp://user:pass@host:port/ (vhost = /)
MQ_PORTstring(ว่าง)
MQ_USERstring(ว่าง)
MQ_PASSWORDstring(ว่าง)ถูก mask ใน Sentry

Storage (S3 / MinIO)

KeyชนิดDefaultคำอธิบาย
S3_ENDPOINTstring(ว่าง)ปล่อยว่าง = AWS S3 จริง ตั้งเมื่อใช้ MinIO/Ceph เช่น http://minio:9000 (ใส่ scheme ให้ครบ)
S3_REGIONstring(ว่าง)เช่น ap-southeast-1 — MinIO ใส่อะไรก็ได้แต่ต้องไม่ว่าง
S3_BUCKETstring(ว่าง)bucket ที่ IStorage ทุก method ทำงานด้วย
S3_ACCESS_KEYstring(ว่าง)ถูก mask ใน Sentry
S3_SECRET_KEYstring(ว่าง)ถูก mask ใน Sentry
S3_FORCE_PATH_STYLEboolfalsetrue สำหรับ MinIO และ endpoint ที่ไม่รองรับ virtual-host style

S3_HTTPS ยังไม่ถูกใช้ใน v2 — ระบุ scheme ใน S3_ENDPOINT แทน

Mailer (SMTP)

KeyชนิดDefaultคำอธิบาย
EMAIL_SERVERstring(ว่าง)SMTP host
EMAIL_PORTint0เช่น 587 (STARTTLS) หรือ 465
EMAIL_USERNAMEstring(ว่าง)ใช้ auth แบบ PLAIN
EMAIL_PASSWORDstring(ว่าง)ถูก mask ใน Sentry
EMAIL_SENDERstring(ว่าง)ที่อยู่ผู้ส่ง (From) ของทุกฉบับ

Push (FCM)

KeyชนิดDefaultคำอธิบาย
FIREBASE_CREDENTIALstring(ว่าง)เนื้อ service-account JSON ทั้งก้อน (ไม่ใช่ path) ส่งเข้า core.NewPusher(env.Config().FirebaseCredential)

JWT

KeyชนิดDefaultคำอธิบาย
JWT_SECRETstring(ว่าง)framework ไม่ได้อ่านเอง — ส่งให้ชัดเจนตอนสร้าง auth: core.NewJWTAuth(env.Config().JWTSecret) ค่านี้ถูก mask ในทุก Sentry event

Connection string หรือ discrete fields

ทุก connection รองรับสองแบบ ถ้ามี connection string จะใช้ตัวนั้นและมองข้ามฟิลด์แยกทั้งหมด

บริการURI (แนะนำ)discrete fields
SQLDB_CONNECTION_STRING=postgres://u:p@host:5432/db?sslmode=disableDB_DRIVER, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME, DB_SSLMODE
CacheCACHE_CONNECTION_STRING=redis://:pass@host:6379/0CACHE_HOST, CACHE_PORT, CACHE_PASSWORD, CACHE_DB
MongoDB_MONGO_CONNECTION_STRING=mongodb://u:p@host:27017/dbDB_MONGO_HOST, DB_MONGO_PORT, DB_MONGO_USERNAME, DB_MONGO_PASSWORD
MQMQ_CONNECTION_STRING=amqp://u:p@host:5672/vhostMQ_HOST, MQ_PORT, MQ_USER, MQ_PASSWORD
go
db, _    := core.NewDatabase(env)   // อ่าน DB_CONNECTION_STRING หรือ DB_* ให้เอง
redis, _ := core.NewCache(env)
mongo, _ := core.NewMongoDB(env)    // ยังต้องมี DB_MONGO_NAME เสมอ
mq, _    := core.NewMQ(env)

แนะนำ URI ในทุก environment ที่ไม่ใช่ dev เพราะ option ปลีกย่อย (replica set, TLS, vhost, pool params) ใส่ได้ครบในสายเดียว และมักเป็นรูปแบบที่ managed service ให้มาอยู่แล้ว

อ่านค่าแบบ typed

go
cfg := env.Config()          // *core.ENVConfig
cfg.Service
cfg.DBHost
cfg.EmailPort               // int
cfg.SentryTracesSampleRate  // float64

Environment gates

go
env.IsDev()   // ENV=dev
env.IsTest()  // ENV=test
env.IsMock()  // ENV=mock
env.IsProd()  // ENV=prod

ถ้าไม่ได้ตั้ง ENV เลย ทั้งสี่ตัวคืน false — โค้ดที่เขียนแบบ if !env.IsProd() จะทำงานเหมือนอยู่ใน dev โดยไม่ตั้งใจ จึงควรตั้ง ENV เสมอ

อ่าน key ที่ไม่ได้อยู่ใน struct

go
env.String("some_key")     // APP_SOME_KEY หรือ SOME_KEY ในไฟล์
env.Int("some_count")      // แปลงจาก string ให้
env.Bool("some_flag")      // "true"/"1" → true
env.Float64("some_rate")
env.All()                  // map[string]string ของทุก key ที่โหลดมา
  • key ที่ไม่มีอยู่จริงคืน zero value ("", 0, false) ไม่ error — ถ้าต้องแยก "ไม่ได้ตั้ง" กับ "ตั้งเป็น 0/false" ให้เช็ค env.String(key) != "" ก่อน
  • env.All() คือสิ่งที่ถูกแนบไปกับ Sentry event (หลัง scrub) — key ที่ชื่อเข้าข่าย ความลับถูก mask ให้อัตโนมัติ

แนะนำให้เพิ่ม field ใน ENVConfig เป็นหลัก accessor พวกนี้เป็น escape hatch

เพิ่ม config key ใหม่

เพิ่ม field เดียว ใน ENVConfig พร้อม tag koanf:"..." — loader bind ให้เอง (ไม่มี list ที่สองให้ลืมแก้เหมือน v1):

go
// ใน ENVConfig
NewFeatureURL string `koanf:"new_feature_url"`
// ตั้งค่า: APP_NEW_FEATURE_URL=... หรือใน .env: NEW_FEATURE_URL=...

ข้อควรรู้: bool ที่อยากให้ default เป็น true ใส่เป็น field ตรงๆ ไม่ได้ เพราะ "ไม่ได้ตั้ง" กับ "ตั้งเป็น false" หน้าตาเหมือนกัน — ให้เช็คการมีอยู่ของค่าแทน (แบบเดียวกับที่ SENTRY_CAPTURE_BODY ทำ):

go
enabled := true
if env.String("my_flag") != "" {
    enabled = env.Bool("my_flag")
}

ตัวอย่างไฟล์เต็ม

.env สำหรับ dev:

sh
ENV=dev
SERVICE=payment-api
HOST=:3000
LOG_LEVEL=debug
LOG_SIMPLE=true

DB_CONNECTION_STRING=postgres://postgres:postgres@localhost:5432/payment?sslmode=disable
CACHE_CONNECTION_STRING=redis://localhost:6379/0

production (ตั้งผ่าน OS env / secret manager — สังเกต prefix APP_):

sh
APP_ENV=prod
APP_SERVICE=payment-api
APP_HOST=:8080
APP_LOG_LEVEL=info

APP_DB_CONNECTION_STRING=postgres://app:***@db.internal:5432/payment?sslmode=require
APP_CACHE_CONNECTION_STRING=rediss://:***@cache.internal:6379/0
APP_MQ_CONNECTION_STRING=amqps://app:***@mq.internal:5671/payment

APP_SENTRY_DSN=https://***@o1.ingest.sentry.io/123
APP_SENTRY_RELEASE=[email protected]
APP_SENTRY_ENABLE_CRONS=true
APP_SENTRY_TRACES_SAMPLE_RATE=0.1
APP_SENTRY_IGNORE_TRANSACTIONS=GET /health

test.env (ใช้เมื่อ APP_ENV=test):

sh
ENV=test
SERVICE=payment-api-test
DB_CONNECTION_STRING=postgres://postgres:postgres@localhost:5432/payment_test?sslmode=disable
# ไม่ตั้ง SENTRY_DSN → tracker เป็น no-op, test ไม่ยิงออกเน็ต

ต่างจาก v1

v1v2
global viper.GetString(...)koanf instance (ไม่มี global, test แยกกันได้, ปลอด race)
เพิ่ม key = แก้ ENVConfig และ envKeys listเพิ่ม field เดียว (bind อัตโนมัติ)
config ผิดรู้ตอน runtimevalidate APP_ENV + DSN ของ Sentry ตอนโหลด
NewEnv() ไม่คืน error — อ่านไฟล์พลาดก็เงียบคืน (IENV, IError) ให้ caller ตัดสินใจ
ตั้ง key ผิด/ลืมใส่ใน envKeys → ค่าว่างแบบเงียบยัง bind อัตโนมัติ แต่ key ที่ผูกไม่ติดสังเกตได้จาก env.All()

Maintained by Passakon Puttasuwan & Dev Core Team.