DB 시간포멧 수정. access log 추가
This commit is contained in:
@@ -3,13 +3,18 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
|
||||
"geoip-rest/internal/geo"
|
||||
"geoip-rest/internal/schedule"
|
||||
@@ -45,6 +50,8 @@ func main() {
|
||||
ReadBufferSize: 16 * 1024, // allow larger request headers (e.g., proxy cookies)
|
||||
})
|
||||
|
||||
app.Use(newFileLogger(env("ACCESS_LOG_PATH", "/log/api-access.log")))
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"service": "geoip-rest",
|
||||
@@ -107,6 +114,86 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newFileLogger(path string) fiber.Handler {
|
||||
if path == "" {
|
||||
return func(c *fiber.Ctx) error { return c.Next() }
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
log.Printf("access log disabled (mkdir failed: %v)", err)
|
||||
return func(c *fiber.Ctx) error { return c.Next() }
|
||||
}
|
||||
maxBytes := int64(envInt("ACCESS_LOG_MAX_BYTES", 10*1024*1024))
|
||||
writer, err := newRotatingWriter(path, maxBytes)
|
||||
if err != nil {
|
||||
log.Printf("access log disabled (open failed: %v)", err)
|
||||
return func(c *fiber.Ctx) error { return c.Next() }
|
||||
}
|
||||
|
||||
format := "${time} ${ip} ${method} ${path} ${protocol} ${status} ${latency_human} headers=${reqHeaders}\n"
|
||||
cfg := logger.Config{
|
||||
Format: format,
|
||||
TimeFormat: time.RFC3339,
|
||||
TimeZone: "Asia/Seoul",
|
||||
Output: writer,
|
||||
}
|
||||
return logger.New(cfg)
|
||||
}
|
||||
|
||||
type rotatingWriter struct {
|
||||
mu sync.Mutex
|
||||
path string
|
||||
maxBytes int64
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func newRotatingWriter(path string, maxBytes int64) (*rotatingWriter, error) {
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rotatingWriter{
|
||||
path: path,
|
||||
maxBytes: maxBytes,
|
||||
file: f,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *rotatingWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if err := w.rotateIfNeeded(len(p)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return w.file.Write(p)
|
||||
}
|
||||
|
||||
func (w *rotatingWriter) rotateIfNeeded(incoming int) error {
|
||||
info, err := w.file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Size()+int64(incoming) <= w.maxBytes {
|
||||
return nil
|
||||
}
|
||||
_ = w.file.Close()
|
||||
|
||||
ts := time.Now().Format("20060102-150405")
|
||||
rotated := fmt.Sprintf("%s.%s", w.path, ts)
|
||||
if err := os.Rename(w.path, rotated); err != nil {
|
||||
// attempt to reopen original to keep logging
|
||||
w.file, _ = os.OpenFile(w.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(w.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.file = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func env(key, fallback string) string {
|
||||
if val := os.Getenv(key); val != "" {
|
||||
return val
|
||||
@@ -129,6 +216,18 @@ func envBool(key string, fallback bool) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func envInt(key string, fallback int) int {
|
||||
val := os.Getenv(key)
|
||||
if val == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func sanitizeDBURL(raw string) string {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
|
||||
csvPath := env("USER_PROGRAM_INFO_CSV", defaultCSVPath)
|
||||
updateDir := env("USER_PROGRAM_UPDATE_DIR", defaultUpdateDir)
|
||||
schema := env("USER_PROGRAM_INFO_SCHEMA", defaultSchema)
|
||||
schema := env("USER_PROGRAM_INFO_SCHEMA", env("POSTGRES_SCHEMA", defaultSchema))
|
||||
logDir := env("USER_PROGRAM_IMPORT_LOG_DIR", defaultLogDir)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
|
||||
@@ -49,6 +49,8 @@ var (
|
||||
timeLayouts = []string{
|
||||
"2006-01-02 15:04:05.000",
|
||||
"2006-01-02 15:04:05",
|
||||
time.RFC3339,
|
||||
"2006-01-02T15:04:05.000Z07:00",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -63,6 +65,10 @@ func EnsureUserProgramReplica(ctx context.Context, conn *pgx.Conn, csvPath, sche
|
||||
logDir = "log"
|
||||
}
|
||||
|
||||
if err := ensureSchema(ctx, conn, schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createReplicaTable(ctx, conn, schema, ReplicaTable); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +149,14 @@ CREATE TABLE IF NOT EXISTS %s (
|
||||
return err
|
||||
}
|
||||
|
||||
func ensureSchema(ctx context.Context, conn *pgx.Conn, schema string) error {
|
||||
if schema == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := conn.Exec(ctx, fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s`, pgx.Identifier{schema}.Sanitize()))
|
||||
return err
|
||||
}
|
||||
|
||||
type importResult struct {
|
||||
rowsCopied int64
|
||||
rowsUpserted int64
|
||||
|
||||
@@ -69,11 +69,12 @@ func NewMySQLConfigFromEnv() (MySQLConfig, error) {
|
||||
}
|
||||
|
||||
func NewPathsFromEnv() (Paths, error) {
|
||||
schema := env("USER_PROGRAM_INFO_SCHEMA", env("POSTGRES_SCHEMA", DefaultSchema))
|
||||
paths := Paths{
|
||||
UpdateDir: env("USER_PROGRAM_UPDATE_DIR", DefaultUpdateDir),
|
||||
LogDir: env("USER_PROGRAM_IMPORT_LOG_DIR", DefaultLogDir),
|
||||
InitialCSV: env("USER_PROGRAM_INFO_CSV", DefaultInitialCSV),
|
||||
Schema: env("USER_PROGRAM_INFO_SCHEMA", DefaultSchema),
|
||||
Schema: schema,
|
||||
}
|
||||
|
||||
for _, dir := range []string{paths.UpdateDir, paths.LogDir} {
|
||||
|
||||
@@ -188,8 +188,8 @@ func scanRow(rows *sql.Rows) ([]string, error) {
|
||||
userCompany sql.NullString
|
||||
userDepartment sql.NullString
|
||||
userPosition sql.NullString
|
||||
userLoginTime sql.NullString
|
||||
createdAt sql.NullString
|
||||
userLoginTime sql.NullTime
|
||||
createdAt sql.NullTime
|
||||
userFamilyFlag sql.NullString
|
||||
)
|
||||
|
||||
@@ -225,8 +225,8 @@ func scanRow(rows *sql.Rows) ([]string, error) {
|
||||
nullToString(userCompany),
|
||||
nullToString(userDepartment),
|
||||
nullToString(userPosition),
|
||||
nullToString(userLoginTime),
|
||||
nullToString(createdAt),
|
||||
formatTimestamp(userLoginTime),
|
||||
formatTimestamp(createdAt),
|
||||
nullToString(userFamilyFlag),
|
||||
}, nil
|
||||
}
|
||||
@@ -241,3 +241,10 @@ func nullToString(v sql.NullString) string {
|
||||
func netAddr(host string, port int) string {
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
func formatTimestamp(t sql.NullTime) string {
|
||||
if !t.Valid {
|
||||
return ""
|
||||
}
|
||||
return t.Time.In(kst()).Format("2006-01-02 15:04:05.000")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user