- Updated logger methods to respect the new `config.Silent` flag, allowing suppression of console output in development mode. - Refactored `buildTurnResponse` to utilize a shared `buildTrace` function for improved tool call association. - Introduced JSON output capabilities in `OutputWriter` for detailed trace and duration reporting. - Enhanced reporting mechanisms to include trace details in JSON and Markdown formats for better diagnostics. - Added support for a new `Trace` structure to capture detailed execution information, including tool calls and completion data.
290 lines
6.3 KiB
Go
290 lines
6.3 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/caarlos0/env/v6"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/joho/godotenv"
|
|
"github.com/yaoapp/kun/exception"
|
|
"github.com/yaoapp/kun/log"
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
)
|
|
|
|
// Conf 配置参数
|
|
var Conf Config
|
|
|
|
// LogOutput 日志输出
|
|
var LogOutput io.WriteCloser // 日志文件
|
|
|
|
// DSLExtensions the dsl file Extensions
|
|
var DSLExtensions = []string{"*.yao", "*.json", "*.jsonc"}
|
|
|
|
func init() {
|
|
Init()
|
|
}
|
|
|
|
// Init setting
|
|
func Init() {
|
|
// Determine app root: YAO_ROOT env > find app.yao > current directory
|
|
root := os.Getenv("YAO_ROOT")
|
|
if root == "" {
|
|
root = findAppRoot()
|
|
}
|
|
if root == "" {
|
|
root = "."
|
|
}
|
|
|
|
filename := filepath.Join(root, ".env")
|
|
if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
|
|
Conf = LoadWithRoot(root)
|
|
ApplyMode()
|
|
return
|
|
}
|
|
|
|
// Load .env then override root if auto-detected
|
|
Conf = LoadFromWithRoot(filename, root)
|
|
ApplyMode()
|
|
}
|
|
|
|
// ApplyMode applies production or development mode based on Conf.Mode
|
|
func ApplyMode() {
|
|
switch Conf.Mode {
|
|
case "production":
|
|
Production()
|
|
case "development":
|
|
Development()
|
|
}
|
|
}
|
|
|
|
// findAppRoot finds the Yao application root directory by looking for app.yao
|
|
// It traverses up from the current directory until it finds app.yao or reaches the filesystem root
|
|
func findAppRoot() string {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
for {
|
|
// Check for app.yao, app.json, or app.jsonc
|
|
for _, appFile := range []string{"app.yao", "app.json", "app.jsonc"} {
|
|
appFilePath := filepath.Join(dir, appFile)
|
|
if _, err := os.Stat(appFilePath); err == nil {
|
|
return dir
|
|
}
|
|
}
|
|
|
|
// Move to parent directory
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
// Reached root, no app.yao found
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// LoadFrom 从配置项中加载
|
|
func LoadFrom(envfile string) Config {
|
|
return LoadFromWithRoot(envfile, "")
|
|
}
|
|
|
|
// LoadFromWithRoot loads config from env file with optional root override
|
|
func LoadFromWithRoot(envfile string, root string) Config {
|
|
file, err := filepath.Abs(envfile)
|
|
if err != nil {
|
|
cfg := LoadWithRoot(root)
|
|
ReloadLog()
|
|
return cfg
|
|
}
|
|
|
|
// load from env
|
|
godotenv.Overload(file)
|
|
cfg := LoadWithRoot(root)
|
|
ReloadLog()
|
|
return cfg
|
|
}
|
|
|
|
// Load the config
|
|
func Load() Config {
|
|
return LoadWithRoot("")
|
|
}
|
|
|
|
// LoadWithRoot loads config with an optional root override
|
|
// If root is empty, uses YAO_ROOT env or current directory
|
|
func LoadWithRoot(root string) Config {
|
|
cfg := Config{}
|
|
if err := env.Parse(&cfg); err != nil {
|
|
exception.New("Can't read config %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
// Root path: use provided root > env YAO_ROOT > default "."
|
|
if root != "" {
|
|
cfg.Root, _ = filepath.Abs(root)
|
|
} else {
|
|
cfg.Root, _ = filepath.Abs(cfg.Root)
|
|
}
|
|
|
|
// App Root
|
|
if cfg.AppSource == "" {
|
|
cfg.AppSource = cfg.Root
|
|
}
|
|
|
|
// DataRoot
|
|
if cfg.DataRoot == "" {
|
|
cfg.DataRoot = filepath.Join(cfg.Root, "data")
|
|
}
|
|
if !filepath.IsAbs(cfg.DataRoot) {
|
|
cfg.DataRoot = filepath.Join(cfg.Root, cfg.DataRoot)
|
|
}
|
|
|
|
// Resolve DB relative paths based on Root
|
|
for i, dsn := range cfg.DB.Primary {
|
|
if !filepath.IsAbs(dsn) && (cfg.DB.Driver == "sqlite3" || cfg.DB.Driver == "") {
|
|
cfg.DB.Primary[i] = filepath.Join(cfg.Root, dsn)
|
|
}
|
|
}
|
|
for i, dsn := range cfg.DB.Secondary {
|
|
if !filepath.IsAbs(dsn) && (cfg.DB.Driver == "sqlite3" || cfg.DB.Driver == "") {
|
|
cfg.DB.Secondary[i] = filepath.Join(cfg.Root, dsn)
|
|
}
|
|
}
|
|
|
|
// Trace Driver - default based on mode
|
|
if cfg.Trace.Driver == "" {
|
|
if cfg.Mode == "development" {
|
|
cfg.Trace.Driver = "local"
|
|
} else {
|
|
cfg.Trace.Driver = "store"
|
|
}
|
|
}
|
|
|
|
// Trace Path - default to same directory as log file when using local driver
|
|
if cfg.Trace.Driver == "local" {
|
|
if cfg.Trace.Path == "" {
|
|
// Use the log file directory
|
|
logDir := cfg.GetLogDir()
|
|
cfg.Trace.Path = filepath.Join(logDir, "traces")
|
|
}
|
|
|
|
if !filepath.IsAbs(cfg.Trace.Path) {
|
|
cfg.Trace.Path = filepath.Join(cfg.Root, cfg.Trace.Path)
|
|
}
|
|
}
|
|
|
|
// Trace Prefix - default prefix for store driver
|
|
if cfg.Trace.Driver == "store" && cfg.Trace.Prefix == "" {
|
|
cfg.Trace.Prefix = "trace:"
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
// GetLogDir returns the directory of the log file
|
|
func (cfg *Config) GetLogDir() string {
|
|
logPath := cfg.Log
|
|
if logPath == "" {
|
|
logPath = filepath.Join(cfg.Root, "logs", "application.log")
|
|
}
|
|
|
|
if !filepath.IsAbs(logPath) {
|
|
logPath = filepath.Join(cfg.Root, logPath)
|
|
}
|
|
|
|
return filepath.Dir(logPath)
|
|
}
|
|
|
|
// Production 设定为生产环境
|
|
func Production() {
|
|
os.Setenv("YAO_MODE", "production")
|
|
Conf.Mode = "production"
|
|
log.SetLevel(log.InfoLevel)
|
|
log.SetFormatter(log.TEXT)
|
|
if Conf.LogMode == "JSON" {
|
|
log.SetFormatter(log.JSON)
|
|
}
|
|
gin.SetMode(gin.ReleaseMode)
|
|
ReloadLog()
|
|
}
|
|
|
|
// Development 设定为开发环境
|
|
func Development() {
|
|
os.Setenv("YAO_MODE", "development")
|
|
Conf.Mode = "development"
|
|
log.SetLevel(log.TraceLevel)
|
|
log.SetFormatter(log.TEXT)
|
|
if Conf.LogMode == "JSON" {
|
|
log.SetFormatter(log.JSON)
|
|
}
|
|
gin.SetMode(gin.DebugMode)
|
|
ReloadLog()
|
|
}
|
|
|
|
// ReloadLog 重新打开日志
|
|
func ReloadLog() {
|
|
CloseLog()
|
|
OpenLog()
|
|
}
|
|
|
|
// OpenLog 打开日志
|
|
func OpenLog() {
|
|
|
|
if Conf.Log == "" {
|
|
Conf.Log = filepath.Join(Conf.Root, "logs", "application.log")
|
|
}
|
|
|
|
if !filepath.IsAbs(Conf.Log) {
|
|
Conf.Log = filepath.Join(Conf.Root, Conf.Log)
|
|
}
|
|
|
|
logfile, err := filepath.Abs(Conf.Log)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
logpath := filepath.Dir(logfile)
|
|
|
|
// Check if the log path exists
|
|
if _, err := os.Stat(logpath); errors.Is(err, os.ErrNotExist) {
|
|
LogOutput, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0666)
|
|
log.SetOutput(LogOutput)
|
|
gin.DefaultWriter = io.MultiWriter(LogOutput)
|
|
return
|
|
}
|
|
|
|
LogOutput = &lumberjack.Logger{
|
|
Filename: logfile,
|
|
MaxSize: Conf.LogMaxSize, // megabytes
|
|
MaxBackups: Conf.LogMaxBackups,
|
|
MaxAge: Conf.LogMaxAage, //days
|
|
LocalTime: Conf.LogLocalTime,
|
|
}
|
|
|
|
log.SetOutput(LogOutput)
|
|
gin.DefaultWriter = io.MultiWriter(LogOutput)
|
|
}
|
|
|
|
// CloseLog 关闭日志
|
|
func CloseLog() {
|
|
if LogOutput != nil {
|
|
err := LogOutput.Close()
|
|
if err != nil {
|
|
log.Error("Failed to close log output: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// IsDevelopment returns true if the current mode is development
|
|
func IsDevelopment() bool {
|
|
return Conf.Mode == "development"
|
|
}
|
|
|
|
// Silent indicates whether stdout output should be suppressed
|
|
// (set by `yao run -s/--silent`).
|
|
var Silent bool
|