Adding ability to print out responses as JSON.

This commit is contained in:
Renan DelValle 2018-11-09 15:58:26 -08:00
parent dcb27f64c2
commit bc28198c2d
No known key found for this signature in database
GPG key ID: C240AD6D6F443EC9
8 changed files with 210 additions and 130 deletions

View file

@ -1,9 +1,9 @@
package cmd package cmd
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
func init() { func init() {
@ -23,5 +23,5 @@ var createCmd = &cobra.Command{
} }
func createJob(cmd *cobra.Command, args []string) { func createJob(cmd *cobra.Command, args []string) {
fmt.Println("Not implemented yet.") log.Println("Not implemented yet.")
} }

View file

@ -2,10 +2,13 @@ package cmd
import ( import (
"fmt" "fmt"
"os" "github.com/spf13/pflag"
"github.com/paypal/gorealis" "github.com/paypal/gorealis"
"github.com/paypal/gorealis/gen-go/apache/aurora" "github.com/paypal/gorealis/gen-go/apache/aurora"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
func init() { func init() {
@ -20,30 +23,24 @@ func init() {
taskConfigCmd.Flags().StringVarP(name, "name", "n", "", "Aurora Name") taskConfigCmd.Flags().StringVarP(name, "name", "n", "", "Aurora Name")
/* Fetch Leader */ /* Fetch Leader */
leaderCmd.Flags().String("zkPath", "/aurora/scheduler", "Zookeeper node path where leader election happens") leaderCmd.Flags().String("zkPath", "/aurora/scheduler", "Zookeeper node path where leader election happens")
// Override usage template to hide global flags
leaderCmd.SetUsageTemplate(`Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`)
fetchCmd.AddCommand(leaderCmd) fetchCmd.AddCommand(leaderCmd)
// Hijack help function to hide unnecessary global flags
help := leaderCmd.HelpFunc()
leaderCmd.SetHelpFunc(func(cmd *cobra.Command, s []string){
if cmd.HasInheritedFlags(){
cmd.InheritedFlags().VisitAll(func(f *pflag.Flag){
if f.Name != "logLevel" {
f.Hidden = true
}
})
}
help(cmd, s)
})
// Fetch jobs // Fetch jobs
fetchJobsCmd.Flags().StringVarP(role, "role", "r", "", "Aurora Role") fetchJobsCmd.Flags().StringVarP(role, "role", "r", "", "Aurora Role")
fetchCmd.AddCommand(fetchJobsCmd) fetchCmd.AddCommand(fetchJobsCmd)
@ -66,8 +63,12 @@ var taskConfigCmd = &cobra.Command{
var leaderCmd = &cobra.Command{ var leaderCmd = &cobra.Command{
Use: "leader [zkNode0, zkNode1, ...zkNodeN]", Use: "leader [zkNode0, zkNode1, ...zkNodeN]",
PersistentPreRun: func(cmd *cobra.Command, args []string) {}, //We don't need a realis client for this cmd PersistentPreRun: func(cmd *cobra.Command, args []string) {
}, //We don't need a realis client for this cmd
PersistentPostRun: func(cmd *cobra.Command, args []string) {}, //We don't need a realis client for this cmd PersistentPostRun: func(cmd *cobra.Command, args []string) {}, //We don't need a realis client for this cmd
PreRun: setConfig,
Args: cobra.MinimumNArgs(1),
Short: "Fetch current Aurora leader given Zookeeper nodes. ", Short: "Fetch current Aurora leader given Zookeeper nodes. ",
Long: `Gets the current leading aurora scheduler instance using information from Zookeeper path. Long: `Gets the current leading aurora scheduler instance using information from Zookeeper path.
Pass Zookeeper nodes separated by a space as an argument to this command.`, Pass Zookeeper nodes separated by a space as an argument to this command.`,
@ -107,41 +108,45 @@ func fetchTasks(cmd *cobra.Command, args []string) {
tasks, err := client.GetTasksWithoutConfigs(taskQuery) tasks, err := client.GetTasksWithoutConfigs(taskQuery)
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
if toJson {
fmt.Println(toJSON(tasks))
} else {
for _, t := range tasks { for _, t := range tasks {
fmt.Println(t) fmt.Println(t)
} }
}
} }
func fetchStatus(cmd *cobra.Command, args []string) { func fetchStatus(cmd *cobra.Command, args []string) {
fmt.Printf("Fetching maintenance status for %v \n", args) log.Infof("Fetching maintenance status for %v \n", args)
_, result, err := client.MaintenanceStatus(args...) _, result, err := client.MaintenanceStatus(args...)
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
if toJson {
fmt.Println(toJSON(result.Statuses))
} else {
for k := range result.Statuses { for k := range result.Statuses {
fmt.Printf("Result: %s:%s\n", k.Host, k.Mode) fmt.Printf("Result: %s:%s\n", k.Host, k.Mode)
} }
}
} }
func fetchLeader(cmd *cobra.Command, args []string) { func fetchLeader(cmd *cobra.Command, args []string) {
fmt.Printf("Fetching leader from %v \n", args) log.Infof("Fetching leader from %v \n", args)
if len(args) < 1 { if len(args) < 1 {
fmt.Println("At least one Zookeper node address must be passed in.") log.Fatalln("At least one Zookeeper node address must be passed in.")
os.Exit(1)
} }
url, err := realis.LeaderFromZKOpts(realis.ZKEndpoints(args...), realis.ZKPath(cmd.Flag("zkPath").Value.String())) url, err := realis.LeaderFromZKOpts(realis.ZKEndpoints(args...), realis.ZKPath(cmd.Flag("zkPath").Value.String()))
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
fmt.Println(url) fmt.Println(url)
@ -149,27 +154,28 @@ func fetchLeader(cmd *cobra.Command, args []string) {
// TODO: Expand this to be able to filter by job name and environment. // TODO: Expand this to be able to filter by job name and environment.
func fetchJobs(cmd *cobra.Command, args []string) { func fetchJobs(cmd *cobra.Command, args []string) {
fmt.Printf("Fetching tasks under role: %s \n", *role) log.Infof("Fetching tasks under role: %s \n", *role)
if *role == "" { if *role == "" {
fmt.Println("Role must be specified.") log.Fatalln("Role must be specified.")
os.Exit(1)
} }
if *role == "*" { if *role == "*" {
fmt.Println("Warning: This is an expensive operation.") log.Warnln("This is an expensive operation.")
*role = "" *role = ""
} }
_, result, err := client.GetJobs(*role) _, result, err := client.GetJobs(*role)
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
for jobConfig, _ := range result.GetConfigs() { if toJson {
fmt.Println(toJSON(result.GetConfigs()))
} else {
for jobConfig := range result.GetConfigs() {
fmt.Println(jobConfig) fmt.Println(jobConfig)
} }
}
} }

View file

@ -2,10 +2,11 @@ package cmd
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
func init() { func init() {
@ -38,10 +39,9 @@ func snapshot(cmd *cobra.Command, args []string) {
fmt.Println("Forcing scheduler to write snapshot to Mesos replicated log") fmt.Println("Forcing scheduler to write snapshot to Mesos replicated log")
err := client.Snapshot() err := client.Snapshot()
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} else { } else {
fmt.Println("Snapshot started successfully") log.Println("Snapshot started successfully")
} }
} }
@ -57,10 +57,9 @@ func backup(cmd *cobra.Command, args []string) {
fmt.Println("Forcing scheduler to write a Backup of latest Snapshot to file system") fmt.Println("Forcing scheduler to write a Backup of latest Snapshot to file system")
err := client.PerformBackup() err := client.PerformBackup()
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} else { } else {
fmt.Println("Backup started successfully") log.Println("Backup started successfully")
} }
} }
@ -90,31 +89,29 @@ and the master responds with the latest state for each task, if possible.
func explicitRecon(cmd *cobra.Command, args []string) { func explicitRecon(cmd *cobra.Command, args []string) {
var batchSize *int32 var batchSize *int32
fmt.Println("Forcing scheduler to perform an explicit reconciliation with Mesos") log.Println("Forcing scheduler to perform an explicit reconciliation with Mesos")
switch len(args) { switch len(args) {
case 0: case 0:
fmt.Println("Using default batch size for explicit recon.") log.Infoln("Using default batch size for explicit recon.")
case 1: case 1:
fmt.Printf("Using %v as batch size for explicit recon.\n", args[0]) log.Infof("Using %v as batch size for explicit recon.\n", args[0])
// Get batch size from args and convert it to the right format // Get batch size from args and convert it to the right format
batchInt, err := strconv.Atoi(args[0]) batchInt, err := strconv.Atoi(args[0])
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
batchInt32 := int32(batchInt) batchInt32 := int32(batchInt)
batchSize = &batchInt32 batchSize = &batchInt32
default: default:
fmt.Println("Provide 0 arguments to use default batch size or one argument to use a custom batch size.") log.Fatalln("Provide 0 arguments to use default batch size or one argument to use a custom batch size.")
os.Exit(1)
} }
err := client.ForceExplicitTaskReconciliation(batchSize) err := client.ForceExplicitTaskReconciliation(batchSize)
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err.Error())
os.Exit(1)
} else { } else {
fmt.Println("Explicit reconciliation started successfully") fmt.Println("Explicit reconciliation started successfully")
} }
@ -129,11 +126,10 @@ var forceImplicitReconCmd = &cobra.Command{
func implicitRecon(cmd *cobra.Command, args []string) { func implicitRecon(cmd *cobra.Command, args []string) {
fmt.Println("Forcing scheduler to perform an implicit reconciliation with Mesos") log.Println("Forcing scheduler to perform an implicit reconciliation with Mesos")
err := client.PerformBackup() err := client.PerformBackup()
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} else { } else {
fmt.Println("Implicit reconciliation started successfully") fmt.Println("Implicit reconciliation started successfully")
} }

View file

@ -2,20 +2,18 @@ package cmd
import ( import (
"fmt" "fmt"
"log"
"os"
"github.com/paypal/gorealis" "github.com/paypal/gorealis"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
func init() { func init() {
rootCmd.AddCommand(killCmd) rootCmd.AddCommand(killCmd)
/* Sub-Commands */ /* Sub-Commands */
// Kill Job // Kill Job
killCmd.AddCommand(killJobCmd) killCmd.AddCommand(killJobCmd)
@ -28,8 +26,6 @@ func init() {
// Kill every task in the Aurora cluster // Kill every task in the Aurora cluster
killCmd.AddCommand(killEntireClusterCmd) killCmd.AddCommand(killEntireClusterCmd)
} }
var killCmd = &cobra.Command{ var killCmd = &cobra.Command{
@ -51,7 +47,7 @@ var killEntireClusterCmd = &cobra.Command{
} }
func killJob(cmd *cobra.Command, args []string) { func killJob(cmd *cobra.Command, args []string) {
log.Printf("Killing job [Env:%s Role:%s Name:%s]\n", *env, *role, *name) log.Infof("Killing job [Env:%s Role:%s Name:%s]\n", *env, *role, *name)
job := realis.NewJob(). job := realis.NewJob().
Environment(*env). Environment(*env).
@ -59,16 +55,18 @@ func killJob(cmd *cobra.Command, args []string) {
Name(*name) Name(*name)
resp, err := client.KillJob(job.JobKey()) resp, err := client.KillJob(job.JobKey())
if err != nil { if err != nil {
fmt.Println(err) log.Fatalln(err)
os.Exit(1)
} }
if ok, err := monitor.Instances(job.JobKey(), 0, 5, 50); !ok || err != nil { if ok, err := monitor.Instances(job.JobKey(), 0, 5, 50); !ok || err != nil {
log.Println("Unable to kill all instances of job") log.Fatalln("Unable to kill all instances of job")
os.Exit(1)
} }
fmt.Println(resp.String()) if toJson {
fmt.Println(toJSON(resp.GetResult_()))
} else {
fmt.Println(resp.GetResult_())
}
} }
func killEntireCluster(cmd *cobra.Command, args []string) { func killEntireCluster(cmd *cobra.Command, args []string) {

View file

@ -1,14 +1,14 @@
package cmd package cmd
import ( import (
"fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"os"
"strings" "strings"
"time" "time"
"github.com/paypal/gorealis" "github.com/paypal/gorealis"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
var username, password, zkAddr, schedAddr string var username, password, zkAddr, schedAddr string
@ -19,14 +19,15 @@ var skipCertVerification bool
var caCertsPath string var caCertsPath string
var clientKey, clientCert string var clientKey, clientCert string
var configFile string var configFile string
var toJson bool
var logLevel string
const australisVer = "v0.0.5" const australisVer = "v0.0.6"
var monitorInterval, monitorTimeout int var monitorInterval, monitorTimeout int
func init() { func init() {
rootCmd.SetVersionTemplate(`{{printf "%s\n" .Version}}`) rootCmd.SetVersionTemplate(`{{printf "%s\n" .Version}}`)
rootCmd.PersistentFlags().StringVarP(&zkAddr, "zookeeper", "z", "", "Zookeeper node(s) where Aurora stores information. (comma separated list)") rootCmd.PersistentFlags().StringVarP(&zkAddr, "zookeeper", "z", "", "Zookeeper node(s) where Aurora stores information. (comma separated list)")
@ -38,7 +39,8 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&caCertsPath, "caCertsPath", "a", "", "Path where CA certificates can be found.") rootCmd.PersistentFlags().StringVarP(&caCertsPath, "caCertsPath", "a", "", "Path where CA certificates can be found.")
rootCmd.PersistentFlags().BoolVarP(&skipCertVerification, "skipCertVerification", "i", false, "Skip CA certificate hostname verification.") rootCmd.PersistentFlags().BoolVarP(&skipCertVerification, "skipCertVerification", "i", false, "Skip CA certificate hostname verification.")
rootCmd.PersistentFlags().StringVar(&configFile, "config", "/etc/aurora/australis.yml", "Config file to use.") rootCmd.PersistentFlags().StringVar(&configFile, "config", "/etc/aurora/australis.yml", "Config file to use.")
rootCmd.PersistentFlags().BoolVar(&toJson, "jsonOutput", false, "Print output in JSON format.")
rootCmd.PersistentFlags().StringVarP(&logLevel, "logLevel", "l", "info", "Set logging level [" + getLoggingLevels() + "].")
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -57,9 +59,23 @@ func Execute() {
rootCmd.Execute() rootCmd.Execute()
} }
// TODO(rdelvalle): Move more from connect into this function
func setConfig(cmd *cobra.Command, args []string) {
lvl, err := log.ParseLevel(logLevel)
if err != nil {
log.Fatalf("Log level %v is not valid\n", logLevel)
}
log.SetLevel(lvl)
}
func connect(cmd *cobra.Command, args []string) { func connect(cmd *cobra.Command, args []string) {
var err error var err error
setConfig(cmd, args)
zkAddrSlice := strings.Split(zkAddr, ",") zkAddrSlice := strings.Split(zkAddr, ",")
viper.SetConfigFile(configFile) viper.SetConfigFile(configFile)
@ -113,8 +129,7 @@ func connect(cmd *cobra.Command, args []string) {
} else if schedAddr != "" { } else if schedAddr != "" {
realisOptions = append(realisOptions, realis.SchedulerUrl(schedAddr)) realisOptions = append(realisOptions, realis.SchedulerUrl(schedAddr))
} else { } else {
fmt.Println("Zookeeper address or Scheduler URL must be provided.") log.Fatalln("Zookeeper address or Scheduler URL must be provided.")
os.Exit(1)
} }
// Client certificate configuration if available // Client certificate configuration if available
@ -129,9 +144,7 @@ func connect(cmd *cobra.Command, args []string) {
client, err = realis.NewRealisClient(realisOptions...) client, err = realis.NewRealisClient(realisOptions...)
if err != nil { if err != nil {
fmt.Println(err) log.Fatal(err)
os.Exit(1)
} }
monitor = &realis.Monitor{Client: client} monitor = &realis.Monitor{Client: client}
} }

View file

@ -2,10 +2,10 @@ package cmd
import ( import (
"fmt" "fmt"
"os"
"github.com/paypal/gorealis/gen-go/apache/aurora" "github.com/paypal/gorealis/gen-go/apache/aurora"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
func init() { func init() {
@ -38,31 +38,46 @@ expects a space separated list of hosts to place into maintenance mode.`,
} }
func drain(cmd *cobra.Command, args []string) { func drain(cmd *cobra.Command, args []string) {
fmt.Println("Setting hosts to DRAINING") log.Infoln("Setting hosts to DRAINING")
fmt.Println(args) log.Infoln(args)
_, result, err := client.DrainHosts(args...) _, result, err := client.DrainHosts(args...)
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
log.Debugln(result)
// Monitor change to DRAINING and DRAINED mode // Monitor change to DRAINING and DRAINED mode
hostResult, err := monitor.HostMaintenance( hostResult, err := monitor.HostMaintenance(
args, args,
[]aurora.MaintenanceMode{aurora.MaintenanceMode_DRAINED}, []aurora.MaintenanceMode{aurora.MaintenanceMode_DRAINED},
monitorInterval, monitorInterval,
monitorTimeout) monitorTimeout)
transitioned := make([]string, 0,0)
if err != nil { if err != nil {
nonTransitioned := make([]string, 0,0)
for host, ok := range hostResult { for host, ok := range hostResult {
if !ok { if ok {
fmt.Printf("Host %s did not transtion into desired mode(s)\n", host) transitioned = append(transitioned, host)
} else {
nonTransitioned = append(nonTransitioned, host)
} }
} }
fmt.Printf("error: %+v\n", err.Error()) log.Printf("error: %+v\n", err)
return if toJson {
fmt.Println(toJSON(nonTransitioned))
} else {
fmt.Println("Did not enter DRAINED status: ", nonTransitioned)
}
} }
fmt.Println(result.String()) if toJson {
fmt.Println(toJSON(transitioned))
} else {
fmt.Println("Entered DRAINED status: ", transitioned)
}
} }

View file

@ -2,10 +2,10 @@ package cmd
import ( import (
"fmt" "fmt"
"os"
"github.com/paypal/gorealis/gen-go/apache/aurora" "github.com/paypal/gorealis/gen-go/apache/aurora"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
) )
func init() { func init() {
@ -45,42 +45,56 @@ var stopUpdateCmd = &cobra.Command{
} }
func endMaintenance(cmd *cobra.Command, args []string) { func endMaintenance(cmd *cobra.Command, args []string) {
fmt.Println("Setting hosts to NONE maintenance status.") log.Println("Setting hosts to NONE maintenance status.")
fmt.Println(args) log.Println(args)
_, result, err := client.EndMaintenance(args...) _, result, err := client.EndMaintenance(args...)
if err != nil { if err != nil {
fmt.Printf("error: %+v\n", err.Error()) log.Fatalf("error: %+v\n", err)
os.Exit(1)
} }
log.Debugln(result)
// Monitor change to NONE mode // Monitor change to NONE mode
hostResult, err := monitor.HostMaintenance( hostResult, err := monitor.HostMaintenance(
args, args,
[]aurora.MaintenanceMode{aurora.MaintenanceMode_NONE}, []aurora.MaintenanceMode{aurora.MaintenanceMode_NONE},
monitorInterval, monitorInterval,
monitorTimeout) monitorTimeout)
transitioned := make([]string, 0,0)
if err != nil { if err != nil {
nonTransitioned := make([]string, 0,0)
for host, ok := range hostResult { for host, ok := range hostResult {
if !ok { if ok {
fmt.Printf("Host %s did not transtion into desired mode(s)\n", host) transitioned = append(transitioned, host)
} else {
nonTransitioned = append(nonTransitioned, host)
} }
} }
fmt.Printf("error: %+v\n", err.Error()) log.Printf("error: %+v\n", err)
return if toJson {
fmt.Println(toJSON(nonTransitioned))
} else {
fmt.Println("Did not enter NONE status: ", nonTransitioned)
}
} }
fmt.Println(result.String()) if toJson {
fmt.Println(toJSON(transitioned))
} else {
fmt.Println("Entered NONE status: ", transitioned)
}
} }
func stopUpdate(cmd *cobra.Command, args []string) { func stopUpdate(cmd *cobra.Command, args []string) {
if len(args) != 1 { if len(args) != 1 {
fmt.Println("Only a single update ID must be provided.") log.Fatalln("Only a single update ID must be provided.")
os.Exit(1)
} }
fmt.Printf("Stopping (aborting) update [%s/%s/%s] %s\n", *env, *role, *name, args[0]) log.Infof("Stopping (aborting) update [%s/%s/%s] %s\n", *env, *role, *name, args[0])
resp, err := client.AbortJobUpdate(aurora.JobUpdateKey{ resp, err := client.AbortJobUpdate(aurora.JobUpdateKey{
Job: &aurora.JobKey{Environment: *env, Role: *role, Name: *name}, Job: &aurora.JobKey{Environment: *env, Role: *role, Name: *name},
@ -89,9 +103,12 @@ func stopUpdate(cmd *cobra.Command, args []string) {
"") "")
if err != nil { if err != nil {
fmt.Println(err) log.Fatalln(err)
os.Exit(1)
} }
fmt.Println(resp.String())
if toJson{
fmt.Println(toJSON(resp.GetResult_()))
} else {
fmt.Println(resp.GetDetails())
}
} }

35
cmd/util.go Normal file
View file

@ -0,0 +1,35 @@
package cmd
import (
"bytes"
"encoding/json"
log "github.com/sirupsen/logrus"
)
func toJSON(v interface{}) string {
output, err := json.Marshal(v)
if err != nil {
log.Fatalln("Unable to serialize statuses")
}
return string(output)
}
func getLoggingLevels() string {
var buffer bytes.Buffer
for _, level := range log.AllLevels {
buffer.WriteString(level.String())
buffer.WriteString(" ")
}
buffer.Truncate(buffer.Len()-1)
return buffer.String()
}