oracle/cmd/logging/logging_main.go (202 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // contains a script to tail log files package main import ( "bufio" "context" "encoding/json" "flag" "fmt" "log" "os" "path/filepath" "strings" "sync" "time" "github.com/hpcloud/tail" "google.golang.org/grpc" dbdaemonlib "github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/pkg/agents/common" "github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/pkg/agents/consts" dbdpb "github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/pkg/agents/oracle" ) const ( logTypeAlert = "ALERT" logTypeListener = "LISTENER" alertLogPathQuery = `select value from v$diag_info where name = 'Diag Trace'` databaseNameQuery = `select name from v$database` listenerBaseVar = `ADR_BASE_SECURE` ) var ( logType = flag.String("logType", "", "the log file to stream. Currently supports: ALERT, LISTENER") debugLogger = flag.Bool("debugLogger", false, "enable to get debug logs from the logging sidecar") pollInterval = flag.Duration("pollInterval", 180*time.Second, "time interval to query for updates to log locations (total time to tail a new log might be 2x poll interval)") // If the listener directory becomes configurable then we will need to modify this listenerOraPath = filepath.Join(fmt.Sprintf(consts.ListenerDir, consts.DataMount), "SECURE/listener.ora") logger *log.Logger latestLogFilePath string latestLogFilePathLock sync.Mutex currentTail *tailRoutine ) func createDBDClient(ctx context.Context) (dbdpb.DatabaseDaemonClient, func() error, error) { conn, err := dbdaemonlib.DatabaseDaemonDialLocalhost(ctx, consts.DefaultDBDaemonPort, grpc.WithBlock()) if err != nil { return nil, func() error { return nil }, err } return dbdpb.NewDatabaseDaemonClient(conn), conn.Close, nil } func main() { flag.Parse() logger = tail.DiscardingLogger if *debugLogger { logger = tail.DefaultLogger } if *logType != logTypeAlert && *logType != logTypeListener { logger.Fatalf("unrecognized log type: %v", *logType) } logger.Print("logging main class starting up") go pollForPathUpdates(context.Background(), *logType) createTailRoutine() } type tailRoutine struct { filePath string t *tail.Tail } func (tr *tailRoutine) startTail() error { var err error tr.t, err = tail.TailFile(tr.filePath, tail.Config{Follow: true, ReOpen: true, Logger: logger}) if err != nil { return err } go func() { for line := range tr.t.Lines { fmt.Println(line.Text) } }() return nil } func (tr *tailRoutine) stopTail() error { if err := tr.t.Stop(); err != nil { return err } tr.t.Cleanup() return nil } func createTailRoutine() { tick := time.NewTicker(*pollInterval) defer tick.Stop() for { select { case <-tick.C: latestLogFilePathLock.Lock() latestPath := latestLogFilePath latestLogFilePathLock.Unlock() if latestPath != "" && (currentTail == nil || currentTail.t.Filename != latestPath) { logger.Printf("new path found: %v", latestPath) if !fileExists(latestPath) { logger.Printf("file does not currently exist, waiting: %v", latestPath) continue } if currentTail != nil { if err := currentTail.stopTail(); err != nil { logger.Fatalf("Unable to stop tail err=%v", err) } } currentTail = &tailRoutine{ filePath: latestPath, } if err := currentTail.startTail(); err != nil { logger.Fatalf("Unable to start tail err=%v", err) } } } } } func fileExists(path string) bool { _, err := os.Stat(path) if err == nil { return true } else if !os.IsNotExist(err) { logger.Fatalf("error checking file existence: %v", err) } return false } func getListenerLogBasePath(secureListenerPath string) (string, error) { f, err := os.Open(secureListenerPath) if err != nil { return "", err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() line = strings.TrimSpace(line) if strings.HasPrefix(line, listenerBaseVar) { split := strings.Split(line, "=") if len(split) != 2 { return "", fmt.Errorf("got len(split)=%d, expected 2", len(split)) } return strings.TrimSpace(split[1]), nil } } return "", fmt.Errorf("No %s line found", listenerBaseVar) } func pollForPathUpdates(ctx context.Context, logType string) { tick := time.NewTicker(*pollInterval) defer tick.Stop() for { select { case <-tick.C: var newLogFilePath string if logType == logTypeListener { listenerLogBase, err := getListenerLogBasePath(listenerOraPath) if err != nil { logger.Printf("unable to find base path for listener log: %v", err) continue } hostname, err := os.Hostname() if err != nil { logger.Fatalf("error getting hostname %v", err) } newLogFilePath = filepath.Join(listenerLogBase, "/diag/tnslsnr", hostname, "/secure/trace/secure.log") } else if logType == logTypeAlert { alertLogBase, err := queryDB(ctx, alertLogPathQuery) if err != nil || len(alertLogBase) == 0 { logger.Printf("Error querying alert log path err=%v, alertLogBase=%v", err, alertLogBase) continue } dbName, err := queryDB(ctx, databaseNameQuery) if err != nil || len(dbName) == 0 { logger.Printf("Error querying dbname err=%v, dbName=%v", err, dbName) continue } newLogFilePath = filepath.Join(alertLogBase, fmt.Sprintf("alert_%s.log", dbName)) } latestLogFilePathLock.Lock() latestLogFilePath = newLogFilePath latestLogFilePathLock.Unlock() } } } func queryDB(ctx context.Context, query string) (string, error) { dbdClient, closeConn, err := createDBDClient(ctx) if err != nil { return "", err } defer closeConn() resp, err := dbdClient.RunSQLPlusFormatted(ctx, &dbdpb.RunSQLPlusCMDRequest{Commands: []string{query}, Suppress: false, Quiet: true}) if err != nil { return "", err } row := make(map[string]string) if resp == nil || len(resp.GetMsg()) < 1 { return "", fmt.Errorf("query did not return any response, resp=%v", resp) } if err := json.Unmarshal([]byte(resp.GetMsg()[0]), &row); err != nil { return "", err } if len(row) > 1 { return "", fmt.Errorf("query returned more than one value, got=%d values", len(row)) } var queryVal string for _, value := range row { queryVal = value } return queryVal, nil }