diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..4ac8687 --- /dev/null +++ b/build.bat @@ -0,0 +1 @@ +go build -o OracleSync2MySQL -ldflags "-X main.Version=0.1.2" \ No newline at end of file diff --git a/cmd/app.go b/cmd/app.go index 138768c..116e711 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -22,6 +22,7 @@ import ( var srcDb *sql.DB var destDb *sql.DB var oracleConnStr godror.ConnectionParams +var srcSchema string func getConn() (connStr *connect.DbConnStr) { connStr = new(connect.DbConnStr) @@ -30,6 +31,7 @@ func getConn() (connStr *connect.DbConnStr) { connStr.SrcPassword = viper.GetString("src.password") connStr.SrcDatabase = viper.GetString("src.database") connStr.SrcPort = viper.GetInt("src.port") + connStr.SrcSchema = viper.GetString("src.schema") connStr.DestHost = viper.GetString("dest.host") connStr.DestPort = viper.GetInt("dest.port") connStr.DestUserName = viper.GetString("dest.username") @@ -46,6 +48,7 @@ func PrepareSrc(connStr *connect.DbConnStr) { srcPassword := connStr.SrcPassword srcDatabase := connStr.SrcDatabase srcPort := connStr.SrcPort + srcSchema = connStr.SrcSchema //srcConn := fmt.Sprintf("oracle://%s:%s@%s:%d/%s?LOB FETCH=POST", srcUserName, srcPassword, srcHost, srcPort, srcDatabase) //fmt.Println(srcConn) var err error diff --git a/cmd/compare.go b/cmd/compare.go index bdbe8fc..32f5eda 100644 --- a/cmd/compare.go +++ b/cmd/compare.go @@ -5,9 +5,6 @@ import ( "github.com/liushuochen/gotable" "github.com/spf13/cobra" "github.com/spf13/viper" - "io" - "os" - "path/filepath" "strconv" "time" ) @@ -30,7 +27,7 @@ var compareDbCmd = &cobra.Command{ // 每页的分页记录数,仅全库迁移时有效 pageSize := viper.GetInt("pageSize") // 从配置文件中获取需要排除的表 - excludeTab := viper.GetStringSlice("exclude") + excludeTab = viper.GetStringSlice("exclude") PrepareSrc(connStr) PrepareDest(connStr) var tableMap map[string][]string @@ -40,20 +37,6 @@ var compareDbCmd = &cobra.Command{ } else { // 不指定-s选项,查询源库所有表名 tableMap = fetchTableMap(pageSize, excludeTab) } - // 创建运行日志目录 - logDir, _ := filepath.Abs(CreateDateDir("")) - f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := f.Close(); err != nil { - log.Fatal(err) // 或设置到函数返回值中 - } - }() - // log信息重定向到平面文件 - multiWriter := io.MultiWriter(os.Stdout, f) - log.SetOutput(multiWriter) // 以下开始调用比对表行数的方法 start := time.Now() // 用于控制协程goroutine运行时候的并发数,例如3个一批,3个一批的goroutine并发运行 diff --git a/cmd/create.go b/cmd/create.go index d3b73ac..e3c4db6 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -4,9 +4,6 @@ import ( "fmt" "github.com/liushuochen/gotable" "github.com/spf13/viper" - "io" - "os" - "path/filepath" "strconv" "strings" "time" @@ -35,7 +32,7 @@ var createTableCmd = &cobra.Command{ // 每页的分页记录数,仅全库迁移时有效 pageSize := viper.GetInt("pageSize") // 从配置文件中获取需要排除的表 - excludeTab := viper.GetStringSlice("exclude") + excludeTab = viper.GetStringSlice("exclude") PrepareSrc(connStr) PrepareDest(connStr) var tableMap map[string][]string @@ -45,20 +42,6 @@ var createTableCmd = &cobra.Command{ } else { // 不指定-s选项,查询源库所有表名 tableMap = fetchTableMap(pageSize, excludeTab) } - // 创建运行日志目录 - logDir, _ := filepath.Abs(CreateDateDir("")) - f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := f.Close(); err != nil { - log.Fatal(err) // 或设置到函数返回值中 - } - }() - // log信息重定向到平面文件 - multiWriter := io.MultiWriter(os.Stdout, f) - log.SetOutput(multiWriter) // 实例初始化,调用接口中创建目标表的方法 var db Database start := time.Now() @@ -89,27 +72,11 @@ var onlyDataCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { // 获取配置文件中的数据库连接字符串 connStr := getConn() - // 创建运行日志目录 - logDir, _ := filepath.Abs(CreateDateDir("")) - // 输出调用文件以及方法位置 - log.SetReportCaller(true) - f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := f.Close(); err != nil { - log.Fatal(err) - } - }() - // log信息重定向到平面文件 - multiWriter := io.MultiWriter(os.Stdout, f) - log.SetOutput(multiWriter) start := time.Now() // map结构,表名以及该表用来迁移查询源库的语句 var tableMap map[string][]string // 从配置文件中获取需要排除的表 - excludeTab := viper.GetStringSlice("exclude") + excludeTab = viper.GetStringSlice("exclude") log.Info("running SourceDB check connect") // 生成源库数据库连接 PrepareSrc(connStr) diff --git a/cmd/root.go b/cmd/root.go index a16f628..f6f9d62 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,22 +1,21 @@ package cmd import ( + "OracleSync2MySQL/pkg/sirupsen/logrus" "bytes" "database/sql" "fmt" "github.com/mitchellh/go-homedir" - "io" "math" "os" "os/signal" - "path/filepath" "strconv" "strings" "sync" "syscall" "time" - "github.com/sirupsen/logrus" + //"github.com/sirupsen/logrus" "github.com/spf13/cobra" "OracleSync2MySQL/connect" @@ -24,10 +23,11 @@ import ( "github.com/spf13/viper" ) -var log = logrus.New() +var log, logDir = logrus.New() var cfgFile string var selFromYml bool var metaData bool +var excludeTab []string var wg sync.WaitGroup var wg2 sync.WaitGroup @@ -49,27 +49,11 @@ func startDataTransfer(connStr *connect.DbConnStr) { exitChan := make(chan os.Signal) signal.Notify(exitChan, os.Interrupt, os.Kill, syscall.SIGTERM) go exitHandle(exitChan) - // 创建运行日志目录 - logDir, _ := filepath.Abs(CreateDateDir("")) - // 输出调用文件以及方法位置 - log.SetReportCaller(true) - f, err := os.OpenFile(logDir+"/"+"run.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := f.Close(); err != nil { - log.Fatal(err) - } - }() - // log信息重定向到平面文件 - multiWriter := io.MultiWriter(os.Stdout, f) - log.SetOutput(multiWriter) start := time.Now() // map结构,表名以及该表用来迁移查询源库的语句 var tableMap map[string][]string // 从配置文件中获取需要排除的表 - excludeTab := viper.GetStringSlice("exclude") + excludeTab = viper.GetStringSlice("exclude") log.Info("running SourceDB check connect") // 生成源库数据库连接 PrepareSrc(connStr) @@ -317,13 +301,17 @@ func prepareSqlStr(tableName string, pageSize int) (sqlList []string) { return } // 根据当前表总数以及每页的页记录大小pageSize,自动计算需要多少页记录数,即总共循环多少次,如果表没有数据,后面判断下切片长度再做处理 - sql2 := "/* goapp */" + "select ceil(count(*)/" + strconv.Itoa(pageSize) + ") as total_page_num from " + "\"" + tableName + "\"" + sql2 := "/* goapp count */" + "select ceil(count(*)/" + strconv.Itoa(pageSize) + ") as total_page_num from " + "\"" + tableName + "\"" //以下是直接使用QueryRow err = srcDb.QueryRow(sql2).Scan(&totalPageNum) if err != nil { log.Fatal(sql2, " exec failed ", err) return } + if totalPageNum == 1 { + sqlList = append(sqlList, fmt.Sprintf("SELECT %s FROM \"%s\"", colNameFull, tableName)) + return sqlList + } // 以下生成分页查询语句 for i := 0; i < totalPageNum; i++ { // 使用小于而不是小于等于,否则会多生成一条分页查询边界外的sql,即此sql查询源表没有数据,也会导致后面迁移数据有多个无用的goroutine curStartPage := i + 1 @@ -345,7 +333,7 @@ func runMigration(logDir string, startPage int, tableName string, sqlStr string, log.Info(fmt.Sprintf("%v Taskid[%d] Processing TableData %v ", time.Now().Format("2006-01-02 15:04:05.000000"), startPage, tableName)) start := time.Now() // 直接查询,即查询全表或者分页查询(SELECT t.* FROM (SELECT id FROM test ORDER BY id LIMIT ?, ?) temp LEFT JOIN test t ON temp.id = t.id;) - sqlStr = "/* goapp */" + sqlStr + sqlStr = "/* goapp query */" + sqlStr // 查询源库的sql rows, err := srcDb.Query(sqlStr) //传入参数之后执行 defer rows.Close() diff --git a/cmd/tablemeta.go b/cmd/tablemeta.go index 8c5a962..bde806f 100644 --- a/cmd/tablemeta.go +++ b/cmd/tablemeta.go @@ -1,6 +1,7 @@ package cmd import ( + "bytes" "database/sql" "fmt" "regexp" @@ -204,7 +205,7 @@ func (tb *Table) IdxCreate(logDir string, tableName string, ch chan struct{}, id log.Error(err) } LogOutput(logDir, "createSql", destIdxSql) - destIdxSql = "/* goapp */" + destIdxSql + destIdxSql = "/* goapp idx */" + destIdxSql // 创建目标索引,主键、其余约束 if !metaData { if _, err = destDb.Exec(destIdxSql); err != nil { @@ -224,7 +225,20 @@ func (tb *Table) SeqCreate(logDir string) (ret []string) { startTime := time.Now() failedCount = 0 var dbRet, tableName string - rows, err := srcDb.Query("select table_name,trigger_body from user_triggers where upper(trigger_type) ='BEFORE EACH ROW'") + srcTableSql := "select table_name,trigger_body from user_triggers where upper(trigger_type) ='BEFORE EACH ROW' %s union select table_name,trigger_body from dba_triggers where upper(trigger_type) ='BEFORE EACH ROW' %s" + buffer := bytes.NewBufferString(" ") + if len(excludeTab) > 0 { + buffer.WriteString(" and table_name not in ( ") + for index, tabName := range excludeTab { + if index < len(excludeTab)-1 { + buffer.WriteString("'" + tabName + "'" + ",") + } else { + buffer.WriteString("'" + tabName + "'" + ")") + } + } + } + srcTableSql = fmt.Sprintf(srcTableSql, buffer.String(), buffer.String()) + rows, err := srcDb.Query(srcTableSql) if err != nil { log.Error(err) } @@ -251,7 +265,7 @@ func (tb *Table) SeqCreate(logDir string) (ret []string) { if len(match) == 2 { autoColName := match[1] // 创建目标数据库该表表的自增列索引 - sqlAutoColIdx := "/* goapp */" + "create index ids_" + tableName + "_" + autoColName + "_" + strconv.Itoa(idx) + " on " + tableName + "(" + autoColName + ")" + sqlAutoColIdx := "/* goapp seq idx */" + "create index ids_" + tableName + "_" + autoColName + "_" + strconv.Itoa(idx) + " on " + tableName + "(" + autoColName + ")" log.Info("[", idx, "] create auto_increment for table ", tableName) LogOutput(logDir, "createSql", sqlAutoColIdx+";") if !metaData { @@ -263,7 +277,7 @@ func (tb *Table) SeqCreate(logDir string) (ret []string) { } // 更改目标数据库该表的列属性为自增列 - sqlModifyAuto := "/* goapp */" + "alter table " + tableName + " modify " + autoColName + " bigint auto_increment" + sqlModifyAuto := "/* goapp seq auto */" + "alter table " + tableName + " modify " + autoColName + " bigint auto_increment" LogOutput(logDir, "createSql", sqlModifyAuto+";") if !metaData { if _, err = destDb.Exec(sqlModifyAuto); err != nil { @@ -286,7 +300,22 @@ func (tb *Table) FkCreate(logDir string) (ret []string) { startTime := time.Now() failedCount = 0 var tableName, sqlStr string - rows, err := srcDb.Query("SELECT B.TABLE_NAME,'ALTER TABLE ' || B.TABLE_NAME || ' ADD CONSTRAINT ' ||\n B.CONSTRAINT_NAME || ' FOREIGN KEY (' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM USER_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME) || ') REFERENCES ' ||\n (SELECT B1.table_name FROM USER_CONSTRAINTS B1\n WHERE B1.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || '(' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM USER_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || ');'\nFROM USER_CONSTRAINTS B\nWHERE B.CONSTRAINT_TYPE = 'R' ") + srcTableSql := "SELECT B.TABLE_NAME,'ALTER TABLE ' || B.TABLE_NAME || ' ADD CONSTRAINT ' ||\n B.CONSTRAINT_NAME || ' FOREIGN KEY (' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM USER_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME) || ') REFERENCES ' ||\n (SELECT B1.table_name FROM USER_CONSTRAINTS B1\n WHERE B1.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || '(' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM USER_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || ');'\nFROM USER_CONSTRAINTS B\nWHERE B.CONSTRAINT_TYPE = 'R' %s" + srcTableSql = srcTableSql + "union SELECT B.TABLE_NAME,'ALTER TABLE ' || B.TABLE_NAME || ' ADD CONSTRAINT ' ||\n B.CONSTRAINT_NAME || ' FOREIGN KEY (' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM DBA_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME) || ') REFERENCES ' ||\n (SELECT B1.table_name FROM DBA_CONSTRAINTS B1\n WHERE B1.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || '(' ||\n (SELECT listagg(A.COLUMN_NAME,',') within group(order by a.position)\n FROM DBA_CONS_COLUMNS A\n WHERE A.CONSTRAINT_NAME = B.R_CONSTRAINT_NAME) || ');'\nFROM DBA_CONSTRAINTS B\nWHERE B.CONSTRAINT_TYPE = 'R' %s" + buffer := bytes.NewBufferString(" ") + if len(excludeTab) > 0 { + buffer.WriteString(" and B.TABLE_NAME not in ( ") + for index, tabName := range excludeTab { + if index < len(excludeTab)-1 { + buffer.WriteString("'" + tabName + "'" + ",") + } else { + buffer.WriteString("'" + tabName + "'" + ")") + } + } + } + srcTableSql = fmt.Sprintf(srcTableSql, buffer.String(), buffer.String()) + rows, err := srcDb.Query(srcTableSql) + if err != nil { log.Error(err) } @@ -299,7 +328,7 @@ func (tb *Table) FkCreate(logDir string) (ret []string) { log.Error(err) } log.Info("[", idx, "] create foreign key for table ", tableName) - sqlStr = "/* goapp */" + sqlStr + sqlStr = "/* goapp fk */" + sqlStr LogOutput(logDir, "createSql", sqlStr) if !metaData { if _, err = destDb.Exec(sqlStr); err != nil { @@ -343,7 +372,7 @@ func (tb *Table) NormalIdx(logDir string) (ret []string) { } log.Info("[", idx, "] create normal index for table ", tableName) LogOutput(logDir, "createSql", createSql+";") - createSql = "/* goapp */" + createSql + createSql = "/* goapp normal idx */" + createSql if !metaData { if _, err = destDb.Exec(createSql); err != nil { log.Error(createSql, " create normal index failed ", err) diff --git a/connect/connect.go b/connect/connect.go index 53e3edb..282d8dc 100644 --- a/connect/connect.go +++ b/connect/connect.go @@ -7,6 +7,7 @@ type DbConnStr struct { SrcPassword string SrcDatabase string SrcPort int + SrcSchema string DestHost string DestPort int DestUserName string diff --git a/example.yml b/example.yml index 3df2b58..0208e08 100644 --- a/example.yml +++ b/example.yml @@ -4,6 +4,7 @@ src: database: orcl username: admin password: oracle +# schema: test dest: host: 192.168.1.37 port: 3306 diff --git a/go.mod b/go.mod index 0327760..390f059 100644 --- a/go.mod +++ b/go.mod @@ -1,36 +1,56 @@ module OracleSync2MySQL -go 1.20 +go 1.21 + +toolchain go1.22.3 + +require ( + github.com/fatih/color v1.17.0 + github.com/go-sql-driver/mysql v1.8.1 + github.com/godror/godror v0.44.0 + github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092 + github.com/mitchellh/go-homedir v1.1.0 + //github.com/sirupsen/logrus v1.9.3 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.19.0 +) + +require ( + github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible + go.uber.org/zap v1.27.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 +) require ( - github.com/fatih/color v1.15.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/godror/godror v0.37.0 // indirect - github.com/godror/knownpb v0.1.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/godror/knownpb v0.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092 // indirect + github.com/lestrrat-go/strftime v1.0.6 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sijms/go-ora/v2 v2.7.9 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/cobra v1.7.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.16.0 // indirect - github.com/subosito/gotenv v1.4.2 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 86b831a..51109e1 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -47,6 +49,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -57,21 +60,35 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/godror/godror v0.37.0 h1:3wR3/1msywDE49PzuXh9UUiwWOBNri0RVQQcu3HU4UY= github.com/godror/godror v0.37.0/go.mod h1:jW1+pN+z/V0h28p9XZXVNtEvfZP/2EBfaSjKJLp3E4g= +github.com/godror/godror v0.44.0 h1:tW0oDotJDoC5GTl5saOAwxmlozDy15ImjsbC7UVKEVA= +github.com/godror/godror v0.44.0/go.mod h1:oRlxogABC1Tr5u/zYF3EjHE1fYvAeNBS9MJ8bq1hVkU= github.com/godror/knownpb v0.1.0 h1:dJPK8s/I3PQzGGaGcUStL2zIaaICNzKKAK8BzP1uLio= github.com/godror/knownpb v0.1.0/go.mod h1:4nRFbQo1dDuwKnblRXDxrfCFYeT4hjg3GjMqef58eRE= +github.com/godror/knownpb v0.1.1 h1:A4J7jdx7jWBhJm18NntafzSC//iZDHkDi1+juwQ5pTI= +github.com/godror/knownpb v0.1.1/go.mod h1:4nRFbQo1dDuwKnblRXDxrfCFYeT4hjg3GjMqef58eRE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -143,6 +160,11 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= +github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092 h1:u9I3sJ+uTakxnRrvuYJGsEi4SvEMN+yB47WWGDHHxIk= github.com/liushuochen/gotable v0.0.0-20221119160816-1113793e7092/go.mod h1:CxUy8nDvutaC1pOfaG9TRoYwdHHqoNstSPPKhomC9k8= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -152,46 +174,75 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sijms/go-ora/v2 v2.7.9 h1:FvPwsyNtAOywDKlgjrgCpGkL0s49ZA/ShTBgEAfYKE0= github.com/sijms/go-ora/v2 v2.7.9/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -202,6 +253,12 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -219,6 +276,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 h1:Mi0bCswbz+9cXmwFAdxoo5GPFMKONUpua6iUdtQS7lk= +golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= +golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -335,6 +398,10 @@ golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -346,6 +413,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -491,11 +562,17 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..1ed7552 --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,283 @@ +package logger + +import ( + "encoding/json" + "fmt" + rotatelogs "github.com/lestrrat-go/file-rotatelogs" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" + "gopkg.in/yaml.v3" + "io" + "os" + "time" +) + +const ( + TimeDivision = "time" + SizeDivision = "size" + + _defaultEncoding = "console" + _defaultDivision = "size" + _defaultUnit = Hour +) + +var ( + _encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) zapcore.Encoder{ + "console": func(encoderConfig zapcore.EncoderConfig) zapcore.Encoder { + return zapcore.NewConsoleEncoder(encoderConfig) + }, + "json": func(encoderConfig zapcore.EncoderConfig) zapcore.Encoder { + return zapcore.NewJSONEncoder(encoderConfig) + }, + } +) + +type ZapLogger struct { + zl *zap.Logger +} + +type LogOptions struct { + // Encoding sets the logger's encoding. Valid values are "json" and + // "console", as well as any third-party encodings registered via + // RegisterEncoder. + Encoding string `json:"encoding" yaml:"encoding" toml:"encoding"` + InfoFilename string `json:"info_filename" yaml:"info_filename" toml:"info_filename"` + ErrorFilename string `json:"error_filename" yaml:"error_filename" toml:"error_filename"` + MaxSize int `json:"max_size" yaml:"max_size" toml:"max_size"` + MaxBackups int `json:"max_backups" yaml:"max_backups" toml:"max_backups"` + MaxAge int `json:"max_age" yaml:"max_age" toml:"max_age"` + Compress bool `json:"compress" yaml:"compress" toml:"compress"` + Division string `json:"division" yaml:"division" toml:"division"` + LevelSeparate bool `json:"level_separate" yaml:"level_separate" toml:"level_separate"` + TimeUnit TimeUnit `json:"time_unit" yaml:"time_unit" toml:"time_unit"` + Stacktrace bool `json:"stacktrace" yaml:"stacktrace" toml:"stacktrace"` + EncodeTime string `json:"encode_time" yaml:"encode_time" toml:"encode_time"` + closeDisplay int + caller bool +} + +func infoLevel() zap.LevelEnablerFunc { + return zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl < zapcore.WarnLevel + }) +} + +func warnLevel() zap.LevelEnablerFunc { + return zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapcore.WarnLevel + }) +} + +func New() *LogOptions { + return &LogOptions{ + Division: _defaultDivision, + LevelSeparate: false, + TimeUnit: _defaultUnit, + Encoding: _defaultEncoding, + caller: false, + } +} + +//func NewFromToml(confPath string) *LogOptions { +// var c *LogOptions +// if _, err := toml.DecodeFile(confPath, &c); err != nil { +// panic(err) +// } +// return c +//} + +func NewFromYaml(confPath string) *LogOptions { + var c *LogOptions + file, err := os.ReadFile(confPath) + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(file, &c) + if err != nil { + fmt.Printf("error: %v", err) + } + return c +} + +func NewFromJson(confPath string) *LogOptions { + var c *LogOptions + file, err := os.ReadFile(confPath) + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + } + err = json.Unmarshal(file, &c) + if err != nil { + fmt.Printf("error: %v", err) + } + return c +} + +func (c *LogOptions) SetDivision(division string) { + c.Division = division +} + +func (c *LogOptions) SetEncodeTime(format string) { + c.EncodeTime = format +} + +func (c *LogOptions) CloseConsoleDisplay() { + c.closeDisplay = 1 +} + +func (c *LogOptions) SetCaller(b bool) { + c.caller = b +} + +func (c *LogOptions) SetTimeUnit(t TimeUnit) { + c.TimeUnit = t +} + +func (c *LogOptions) SetErrorFile(path string) { + c.LevelSeparate = true + c.ErrorFilename = path +} + +func (c *LogOptions) SetInfoFile(path string) { + c.InfoFilename = path +} + +func (c *LogOptions) SetEncoding(encoding string) { + c.Encoding = encoding +} + +// isOutput whether set output file +func (c *LogOptions) isOutput() bool { + return c.InfoFilename != "" +} + +func (c *LogOptions) InitLogger() *ZapLogger { + var ( + logger *zap.Logger + infoHook, warnHook io.Writer + wsInfo []zapcore.WriteSyncer + wsWarn []zapcore.WriteSyncer + ) + + if c.Encoding == "" { + c.Encoding = _defaultEncoding + } + if c.EncodeTime == "" { + c.EncodeTime = RFC3339 + } + encoder := _encoderNameToConstructor[c.Encoding] + + encoderConfig := zapcore.EncoderConfig{ + TimeKey: "time", + LevelKey: "level", + NameKey: "logger", + CallerKey: "file", + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.TimeEncoderOfLayout(c.EncodeTime), + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.FullCallerEncoder, + } + + if c.closeDisplay == 0 { + wsInfo = append(wsInfo, zapcore.AddSync(os.Stdout)) + wsWarn = append(wsWarn, zapcore.AddSync(os.Stdout)) + } + + // zapcore WriteSyncer setting + if c.isOutput() { + switch c.Division { + case TimeDivision: + infoHook = c.timeDivisionWriter(c.InfoFilename) + if c.LevelSeparate { + warnHook = c.timeDivisionWriter(c.ErrorFilename) + } + case SizeDivision: + infoHook = c.sizeDivisionWriter(c.InfoFilename) + if c.LevelSeparate { + warnHook = c.sizeDivisionWriter(c.ErrorFilename) + } + } + wsInfo = append(wsInfo, zapcore.AddSync(infoHook)) + } + + if c.ErrorFilename != "" { + wsWarn = append(wsWarn, zapcore.AddSync(warnHook)) + } + + opts := make([]zap.Option, 0) + cos := make([]zapcore.Core, 0) + + if c.LevelSeparate { + cos = append( + cos, + zapcore.NewCore(encoder(encoderConfig), zapcore.NewMultiWriteSyncer(wsInfo...), infoLevel()), + zapcore.NewCore(encoder(encoderConfig), zapcore.NewMultiWriteSyncer(wsWarn...), warnLevel()), + ) + } else { + cos = append( + cos, + zapcore.NewCore(encoder(encoderConfig), zapcore.NewMultiWriteSyncer(wsInfo...), zap.InfoLevel), + ) + } + + opts = append(opts, zap.Development()) + + if c.Stacktrace { + opts = append(opts, zap.AddStacktrace(zapcore.WarnLevel)) + } + + if c.caller { + opts = append(opts, zap.AddCaller()) + } + + logger = zap.New(zapcore.NewTee(cos...), opts...) + zlog := &ZapLogger{zl: logger} + return zlog +} + +func (c *LogOptions) sizeDivisionWriter(filename string) io.Writer { + hook := &lumberjack.Logger{ + Filename: filename, + MaxSize: c.MaxSize, + MaxBackups: c.MaxBackups, + MaxAge: c.MaxSize, + Compress: c.Compress, + } + return hook +} + +func (c *LogOptions) timeDivisionWriter(filename string) io.Writer { + hook, err := rotatelogs.New( + filename+c.TimeUnit.Format(), + rotatelogs.WithMaxAge(time.Duration(int64(24*time.Hour)*int64(c.MaxAge))), + rotatelogs.WithRotationTime(c.TimeUnit.RotationGap()), + ) + + if err != nil { + panic(err) + } + return hook +} + +func (l *ZapLogger) Error(args ...interface{}) { + l.zl.Error(fmt.Sprint(args...)) +} + +func (l *ZapLogger) Fatal(args ...interface{}) { + l.zl.Fatal(fmt.Sprint(args...)) +} + +func (l *ZapLogger) Info(args ...interface{}) { + l.zl.Info(fmt.Sprint(args...)) +} + +func (l *ZapLogger) Debug(args ...interface{}) { + l.zl.Debug(fmt.Sprint(args...)) +} + +func (l *ZapLogger) Warn(args ...interface{}) { + l.zl.Warn(fmt.Sprint(args...)) +} diff --git a/pkg/logger/unit.go b/pkg/logger/unit.go new file mode 100644 index 0000000..ffa91d3 --- /dev/null +++ b/pkg/logger/unit.go @@ -0,0 +1,50 @@ +package logger + +import ( + "time" +) + +type TimeUnit string + +const ( + Minute = "minute" + Hour = "hour" + Day = "day" + Month = "month" + Year = "year" + RFC3339 = "2006-01-02T15:04:05Z07:00" +) + +func (t TimeUnit) Format() string { + switch t { + case Minute: + return ".%Y%m%d%H%M" + case Hour: + return ".%Y%m%d%H" + case Day: + return ".%Y%m%d" + case Month: + return ".%Y%m" + case Year: + return ".%Y" + default: + return ".%Y%m%d" + } +} + +func (t TimeUnit) RotationGap() time.Duration { + switch t { + case Minute: + return time.Minute + case Hour: + return time.Hour + case Day: + return time.Hour * 24 + case Month: + return time.Hour * 24 * 30 + case Year: + return time.Hour * 24 * 365 + default: + return time.Hour * 24 + } +} diff --git a/pkg/sirupsen/logrus/logrus.go b/pkg/sirupsen/logrus/logrus.go new file mode 100644 index 0000000..992aaaf --- /dev/null +++ b/pkg/sirupsen/logrus/logrus.go @@ -0,0 +1,42 @@ +package logrus + +import ( + "OracleSync2MySQL/pkg/logger" + "fmt" + "os" + "path/filepath" + "time" +) + +func New() (*logger.ZapLogger, string) { + c := logger.New() + logDir, _ := filepath.Abs(CreateDateDir("")) + c.SetDivision("time") // 设置归档方式,"time"时间归档 "size" 文件大小归档,文件大小等可以在配置文件配置 + c.SetTimeUnit(logger.Day) // 时间归档 可以设置切割单位 + c.SetEncoding("console") // 输出格式 "json" 或者 "console" + c.Stacktrace = true // 添加 Stacktrace, 默认false + c.SetInfoFile(logDir + "/" + "run.log") // 设置info级别日志 + c.SetErrorFile(logDir + "/" + "run_err.log") // 设置warn级别日志 + c.SetEncodeTime("2006-01-02 15:04:05") // 设置时间格式 + return c.InitLogger(), logDir // 初始化 +} + +// CreateDateDir 根据当前日期来创建文件夹 +func CreateDateDir(basePath string) string { + folderName := "log/" + time.Now().Format("2006_01_02_15_04_05") + folderPath := filepath.Join(basePath, folderName) + if _, err := os.Stat(folderPath); os.IsNotExist(err) { + // 必须分成两步 + // 先创建文件夹 + err := os.MkdirAll(folderPath, 0777) //级联创建目录 + if err != nil { + fmt.Println("create directory log failed ", err) + } + // 再修改权限 + err = os.Chmod(folderPath, 0777) + if err != nil { + fmt.Println("chmod directory log failed ", err) + } + } + return folderPath +} diff --git a/test/_cmd/logruslyHook_test.go_ b/test/_cmd/logruslyHook_test.go_ new file mode 100644 index 0000000..4b16228 --- /dev/null +++ b/test/_cmd/logruslyHook_test.go_ @@ -0,0 +1,156 @@ +package _cmd + +import ( + "fmt" + rotatelogs "github.com/lestrrat-go/file-rotatelogs" + "github.com/rifflock/lfshook" + "github.com/sirupsen/logrus" + "math/rand" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +var log *logrus.Logger + +// 测试日志文件是否按大小进行切割 +func TestLogRotationBySize(t *testing.T) { + logDir := newLogger() + fmt.Println(logDir) + log.Info("running SourceDB check connect") + + // 生成足够的日志以触发文件切割 + for i := 0; i < 100000; i++ { + g := generateRandomString(33) + fmt.Printf("Test os.Stdout log entry %d,%s", i, g) + log.Infof("Test info log entry %d,%s", i, g) + log.Errorf("Test error log entry %d,%s", i, g) + } + + // 检查是否创建了多个日志文件 + //files, err := os.ReadDir(logDir) + //if err != nil { + // t.Fatalf("Failed to list log directory: %v", err) + //} + + // 至少应该有两个日志文件 + //if len(files) < 2 { + // t.Errorf("Expected multiple log files due to rotation, but found %d", len(files)) + //} + + //清理测试日志文件 + //cleanupTestLogs(logDir) +} + +// 清理测试生成的日志文件 +func cleanupTestLogs(dir string) { + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if strings.Contains(path, "run") { + os.Remove(path) + } + return nil + }) +} + +// CreateDateDir 根据当前日期来创建文件夹 +func CreateDateDir(basePath string) string { + folderName := "log/" + time.Now().Format("2006_01_02_15_04_05") + folderPath := filepath.Join(basePath, folderName) + if _, err := os.Stat(folderPath); os.IsNotExist(err) { + // 必须分成两步 + // 先创建文件夹 + err := os.MkdirAll(folderPath, 0777) //级联创建目录 + if err != nil { + fmt.Println("create directory log failed ", err) + } + // 再修改权限 + err = os.Chmod(folderPath, 0777) + if err != nil { + fmt.Println("chmod directory log failed ", err) + } + } + return folderPath +} + +func newLogger() string { + // 创建运行日志目录 + logDir, _ := filepath.Abs(CreateDateDir("")) + if log != nil { + return logDir + } + + logFilePath := logDir + "/" + "run.log" + // 创建rotatelogs的Logger实例 + rl, err := rotatelogs.New( + logFilePath+".%Y%m%d%H%M", + rotatelogs.WithLinkName(logFilePath), // 生成软链接指向最新日志文件 + rotatelogs.WithMaxAge(24*time.Hour), // 日志文件最大保留时间 + rotatelogs.WithRotationTime(time.Hour), // 日志切割时间间隔 + rotatelogs.WithRotationSize(1024*1024), // 日志文件最大大小(例如50MB) + ) + if err != nil { + fmt.Println("Failed to create rotatelogs logger:", err) + return logDir + } + + // 为 Error 级别日志设置 rotatelogs + errorLogPath := filepath.Join(logDir, "run_error.log") + errorRotator, err := rotatelogs.New( + errorLogPath+".%Y%m%d%H%M", + rotatelogs.WithLinkName(errorLogPath), + rotatelogs.WithMaxAge(7*24*time.Hour), + rotatelogs.WithRotationTime(24*time.Hour), + //rotatelogs.WithRotationSize(10*1024*1024), + ) + if err != nil { + fmt.Printf("failed to create rotatelogs for error: %v", err) + } + + // lfshook 决定哪些日志级别可用日志分割 + writeMap := lfshook.WriterMap{ + //logrus.PanicLevel: rl, + //logrus.FatalLevel: rl, + logrus.ErrorLevel: errorRotator, + //logrus.WarnLevel: rl, + logrus.InfoLevel: rl, + //logrus.DebugLevel: rl, + } + + //logrus.AddHook(lfshook.NewHook( + // lfshook.WriterMap{ + // logrus.InfoLevel: rl, + // logrus.ErrorLevel: rl, + // }, + // &logrus.TextFormatter{}, + //)) + + // 配置 lfshook + hook := lfshook.NewHook(writeMap, &logrus.TextFormatter{ + // 设置日期格式 + TimestampFormat: "2006.01.02 - 15:04:05", + }) + + log = logrus.New() + log.SetReportCaller(true) + //multiWriter := io.MultiWriter(os.Stdout, rl) + log.SetOutput(os.Stdout) + fmt.Println(logFilePath) + log.AddHook(hook) + + return logDir +} + +// generateRandomString 生成长度为 n 的随机字符串 +func generateRandomString(n int) string { + charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + + b := make([]byte, n) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +}