diff --git a/client/client.go b/client/client.go index ee64c55..2f76e65 100755 --- a/client/client.go +++ b/client/client.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package client import ( @@ -82,7 +83,7 @@ func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { if err != nil { return resp, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() data, err := ioutil.ReadAll(resp.Body) if err != nil { diff --git a/client/client_test.go b/client/client_test.go index 80d485d..395e1f6 100755 --- a/client/client_test.go +++ b/client/client_test.go @@ -10,6 +10,8 @@ import ( "net/http" "net/url" "testing" + + "github.com/kondukto-io/kdt/klog" ) func TestNewRequestPath(t *testing.T) { @@ -85,7 +87,8 @@ func TestDo(t *testing.T) { } if resp.StatusCode != http.StatusOK { - t.Fatal("http response not ok") + klog.Fatalf("HTTP response status code: %d", resp.StatusCode) + t.Fatal("HTTP response not OK") } j, err := json.Marshal(&someone) diff --git a/client/projects.go b/client/projects.go index 6251003..fbd0cb8 100755 --- a/client/projects.go +++ b/client/projects.go @@ -2,12 +2,15 @@ Copyright © 2019 Kondukto */ + package client import ( "errors" "fmt" "net/http" + + "github.com/kondukto-io/kdt/klog" ) type Project struct { @@ -18,6 +21,8 @@ type Project struct { func (c *Client) ListProjects(arg string) ([]Project, error) { projects := make([]Project, 0) + klog.Debug("retrieving project list...") + req, err := c.newRequest("GET", "/api/v1/projects", nil) if err != nil { return projects, err @@ -30,6 +35,7 @@ func (c *Client) ListProjects(arg string) ([]Project, error) { type getProjectsResponse struct { Projects []Project `json:"data"` Total int `json:"total"` + Error string `json:"error"` } var ps getProjectsResponse @@ -39,7 +45,7 @@ func (c *Client) ListProjects(arg string) ([]Project, error) { } if resp.StatusCode != http.StatusOK { - return projects, errors.New("response not ok") + return projects, fmt.Errorf("HTTP response not OK : %s", ps.Error) } return ps.Projects, nil @@ -66,7 +72,7 @@ type ReleaseStatus struct { func (c *Client) ReleaseStatus(project string) (*ReleaseStatus, error) { if project == "" { - return nil, errors.New("invalid project id or name") + return nil, errors.New("missing project id or name") } path := fmt.Sprintf("/api/v1/projects/%s/release", project) @@ -84,7 +90,7 @@ func (c *Client) ReleaseStatus(project string) (*ReleaseStatus, error) { } if resp.StatusCode != http.StatusOK { - return nil, errors.New("response not ok") + return nil, fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } return rs, nil diff --git a/client/scans.go b/client/scans.go index b8ed827..280135b 100755 --- a/client/scans.go +++ b/client/scans.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package client import ( @@ -16,6 +17,8 @@ import ( "path/filepath" "time" + "github.com/kondukto-io/kdt/klog" + "github.com/google/go-querystring/query" "github.com/spf13/viper" ) @@ -69,6 +72,8 @@ func (c *Client) ListScans(project string, params *ScanSearchParams) ([]Scan, er // TODO: list scans call should be updated to take tool and metadata arguments scans := make([]Scan, 0) + klog.Debugf("retrieving scans of the project: %s", project) + path := fmt.Sprintf("/api/v1/projects/%s/scans", project) req, err := c.newRequest(http.MethodGet, path, nil) if err != nil { @@ -93,7 +98,7 @@ func (c *Client) ListScans(project string, params *ScanSearchParams) ([]Scan, er } if resp.StatusCode != http.StatusOK { - return scans, errors.New("response not ok") + return scans, fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } return ps.Scans, nil @@ -117,6 +122,7 @@ func (c *Client) FindScan(project string, params *ScanSearchParams) (*Scan, erro } func (c *Client) StartScanByScanId(id string) (string, error) { + klog.Debug("starting scan by scan_id") path := fmt.Sprintf("/api/v1/scans/%s/restart", id) req, err := c.newRequest(http.MethodGet, path, nil) if err != nil { @@ -134,7 +140,7 @@ func (c *Client) StartScanByScanId(id string) (string, error) { } if resp.StatusCode != http.StatusCreated { - return "", errors.New("response not ok") + return "", fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } if rsr.Event == "" { @@ -166,7 +172,7 @@ func (c *Client) StartScanByOption(id string, opt *ScanPROptions) (string, error } if resp.StatusCode != http.StatusCreated { - return "", errors.New("response not ok") + return "", fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } if rsr.Event == "" { @@ -183,17 +189,17 @@ func (c *Client) GetScanStatus(eventId string) (*Event, error) { return nil, err } - e := &Event{} + var e Event resp, err := c.do(req, &e) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { - return nil, errors.New("response not ok") + return nil, fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } - return e, nil + return &e, nil } func (c *Client) GetScanSummary(id string) (*Scan, error) { @@ -203,17 +209,17 @@ func (c *Client) GetScanSummary(id string) (*Scan, error) { return nil, err } - scan := &Scan{} + var scan Scan resp, err := c.do(req, &scan) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { - return nil, errors.New("response not ok") + return nil, fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } - return scan, nil + return &scan, nil } func (c *Client) GetLastResults(id string) (map[string]*ResultSet, error) { @@ -230,13 +236,16 @@ func (c *Client) GetLastResults(id string) (map[string]*ResultSet, error) { } if resp.StatusCode != http.StatusOK { - return nil, errors.New("response not ok") + return nil, fmt.Errorf("HTTP response not OK: %d", resp.StatusCode) } return m, err } func (c *Client) ImportScanResult(project, branch, tool string, file string) (string, error) { + + klog.Debugf("importing scan results using the file:%s", file) + path := "/api/v1/scans/import" rel := &url.URL{Path: path} u := c.BaseURL.ResolveReference(rel) @@ -285,7 +294,9 @@ func (c *Client) ImportScanResult(project, branch, tool string, file string) (st type importScanResultResponse struct { EventID string `json:"event_id"` Message string `json:"message"` + Error string `json:"error"` } + var importResponse importScanResultResponse resp, err := c.do(req, &importResponse) if err != nil { @@ -293,7 +304,7 @@ func (c *Client) ImportScanResult(project, branch, tool string, file string) (st } if resp.StatusCode != http.StatusOK { - return "", errors.New("failed to import scan results") + return "", fmt.Errorf("failed to import scan results: %s", importResponse.Error) } return importResponse.EventID, nil @@ -322,16 +333,17 @@ func (c *Client) ScanByImage(project, branch, tool, image string) (string, error type responseBody struct { EventID string `json:"event_id"` + Error string `json:"error"` } respBody := new(responseBody) resp, err := c.do(req, respBody) if err != nil { return "", fmt.Errorf("HTTP response failed: %w", err) - } + if resp.StatusCode != http.StatusCreated { - return "", fmt.Errorf("HTTP response not OK") + return "", fmt.Errorf("HTTP response not OK: %s", respBody.Error) } return respBody.EventID, nil diff --git a/cmd/import.go b/cmd/import.go index 8113a3d..77ab683 100755 --- a/cmd/import.go +++ b/cmd/import.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package cmd import ( diff --git a/cmd/list.go b/cmd/list.go index 0fe4f45..cbfd383 100755 --- a/cmd/list.go +++ b/cmd/list.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package cmd import ( @@ -12,11 +13,14 @@ import ( var listCmd = &cobra.Command{ Use: "list", Short: "base command for lists", - Run: listRootCommand, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + qwm(0, "") + } + }, } func init() { rootCmd.AddCommand(listCmd) } - -func listRootCommand(cmd *cobra.Command, args []string) {} diff --git a/cmd/listProjects.go b/cmd/listProjects.go index b301165..85c0c9b 100755 --- a/cmd/listProjects.go +++ b/cmd/listProjects.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package cmd import ( @@ -24,7 +25,7 @@ func init() { listCmd.AddCommand(listProjectsCmd) } -func projectsRootCommand(cmd *cobra.Command, args []string) { +func projectsRootCommand(_ *cobra.Command, args []string) { c, err := client.New() if err != nil { qwe(1, err, "could not initialize Kondukto client") @@ -45,11 +46,11 @@ func projectsRootCommand(cmd *cobra.Command, args []string) { } w := tabwriter.NewWriter(os.Stdout, 8, 8, 4, ' ', 0) - defer w.Flush() + defer func() { _ = w.Flush() }() - fmt.Fprintln(w, "NAME\tID") - fmt.Fprintln(w, "---\t---") + _, _ = fmt.Fprintln(w, "NAME\tID") + _, _ = fmt.Fprintln(w, "---\t---") for _, project := range projects { - fmt.Fprintf(w, "%s\t%s\n", project.Name, project.ID) + _, _ = fmt.Fprintf(w, "%s\t%s\n", project.Name, project.ID) } } diff --git a/cmd/listScanners.go b/cmd/listScanners.go new file mode 100644 index 0000000..2afa615 --- /dev/null +++ b/cmd/listScanners.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "fmt" + "os" + "text/tabwriter" + + "github.com/spf13/cobra" +) + +var listScannersCmd = &cobra.Command{ + Use: "scanners", + Short: "list supported scanners", + Run: func(cmd *cobra.Command, args []string) { + w := tabwriter.NewWriter(os.Stdout, 8, 8, 4, ' ', 0) + defer func() { _ = w.Flush() }() + + _, _ = fmt.Fprintf(w, "Tool Name\tScanner Type\n") + _, _ = fmt.Fprintf(w, "------\t------\n") + for k, v := range scanners { + _, _ = fmt.Fprintf(w, "%s\t%s\n", k, v) + } + }, +} + +func init() { + listCmd.AddCommand(listScannersCmd) +} diff --git a/cmd/listScans.go b/cmd/listScans.go index c7cf228..8a971a2 100755 --- a/cmd/listScans.go +++ b/cmd/listScans.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package cmd import ( @@ -27,7 +28,7 @@ func init() { _ = listScansCmd.MarkFlagRequired("project") } -func scanListRootCommand(cmd *cobra.Command, args []string) { +func scanListRootCommand(cmd *cobra.Command, _ []string) { c, err := client.New() if err != nil { qwe(1, err, "could not initialize Kondukto client") @@ -44,7 +45,7 @@ func scanListRootCommand(cmd *cobra.Command, args []string) { } w := tabwriter.NewWriter(os.Stdout, 8, 8, 4, ' ', 0) - defer w.Flush() + defer func() { _ = w.Flush() }() _, _ = fmt.Fprintf(w, "NAME\tID\tBRANCH\tMETA\tTOOL\tCRIT\tHIGH\tMED\tLOW\tSCORE\tDATE\n") _, _ = fmt.Fprintf(w, "---\t---\t---\t---\t---\t---\t---\t---\t---\t---\n") diff --git a/cmd/release.go b/cmd/release.go index 625f32f..4978cca 100755 --- a/cmd/release.go +++ b/cmd/release.go @@ -2,6 +2,7 @@ Copyright © 2020 Kondukto */ + package cmd import ( @@ -9,6 +10,8 @@ import ( "os" "text/tabwriter" + "github.com/kondukto-io/kdt/klog" + "github.com/kondukto-io/kdt/client" "github.com/spf13/cobra" @@ -31,7 +34,7 @@ func init() { _ = releaseCmd.MarkFlagRequired("project") } -func releaseRootCommand(cmd *cobra.Command, args []string) { +func releaseRootCommand(cmd *cobra.Command, _ []string) { c, err := client.New() if err != nil { qwe(1, err, "could not initialize Kondukto client") @@ -46,6 +49,7 @@ func releaseRootCommand(cmd *cobra.Command, args []string) { if err != nil { qwe(1, fmt.Errorf("failed to get release status: %w", err)) } + const statusUndefined = "undefined" const statusFail = "fail" @@ -53,16 +57,15 @@ func releaseRootCommand(cmd *cobra.Command, args []string) { qwm(0, "project has no release criteria") } - // Printing scan results w := tabwriter.NewWriter(os.Stdout, 8, 8, 4, ' ', 0) - fmt.Fprintf(w, "STATUS\tSAST\tDAST\tSCA\n") - fmt.Fprintf(w, "---\t---\t---\t---\n") - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n\n", rs.Status, rs.SAST.Status, rs.DAST.Status, rs.SCA.Status) - w.Flush() + _, _ = fmt.Fprintf(w, "STATUS\tSAST\tDAST\tSCA\n") + _, _ = fmt.Fprintf(w, "---\t---\t---\t---\n") + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n\n", rs.Status, rs.SAST.Status, rs.DAST.Status, rs.SCA.Status) + _ = w.Flush() sast, err := cmd.Flags().GetBool("sast") if err != nil { - qwm(1, "failed to parse sast flag") + klog.Fatalln("failed to parse sast flag") } dast, err := cmd.Flags().GetBool("dast") diff --git a/cmd/root.go b/cmd/root.go index 9c49884..29f64b4 100755 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,14 +2,16 @@ Copyright © 2019 Kondukto */ + package cmd import ( "fmt" - "github.com/spf13/cobra" + "github.com/kondukto-io/kdt/klog" "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -17,7 +19,13 @@ const ( repoURL = "https://github.com/kondukto-io/kdt" ) -var cfgFile string +var ( + cfgFile string + verbose bool + insecure bool + host string + token string +) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ @@ -26,7 +34,11 @@ var rootCmd = &cobra.Command{ Long: `KDT is the command line interface of Kondukto for starting scans and setting release criteria. It is made to ease integration of Kondukto to DevSecOps pipelines.`, // Uncomment the following line if your bare application // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) {}, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if verbose { + klog.DefaultLogger.Level = klog.LevelDebug + } + }, } // Execute adds all child commands to the root command and sets flags appropriately. @@ -40,9 +52,6 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - var insecure, verbose bool - var host, token string - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.kdt.yaml)") rootCmd.PersistentFlags().StringVar(&host, "host", "", "Kondukto server host") rootCmd.PersistentFlags().StringVar(&token, "token", "", "Kondukto API token") diff --git a/cmd/scan.go b/cmd/scan.go index f16cf3b..20e416a 100755 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -2,17 +2,19 @@ Copyright © 2019 Kondukto */ + package cmd import ( "errors" "fmt" - "log" "os" "path/filepath" "text/tabwriter" "time" + "github.com/kondukto-io/kdt/klog" + "github.com/kondukto-io/kdt/client" "github.com/spf13/cobra" ) @@ -31,30 +33,6 @@ const ( eventActive ) -const ( - toolCheckmarx = "checkmarx" - toolCxSca = "checkmarxsca" - toolOWASPZap = "owaspzap" - toolWebInspect = "webinspect" - toolNetSparker = "netsparker" - toolAppSpider = "appspider" - toolBandit = "bandit" - toolFindSecBugs = "findsecbugs" - toolDependencyCheck = "dependencycheck" - toolFortify = "fortify" - toolGoSec = "gosec" - toolBrakeman = "brakeman" - toolSCS = "securitycodescan" - toolTrivy = "trivy" - toolAppScan = "hclappscan" - toolZapless = "owaspzapheadless" - toolNancy = "nancy" - toolSemGrep = "semgrep" - toolVeracode = "veracode" - toolBurpSuite = "burpsuite" - toolBurpSuiteEnterprise = "burpsuiteenterprise" -) - const ( modeByFile = iota modeByScanID @@ -69,9 +47,15 @@ var scanCmd = &cobra.Command{ Use: "scan", Short: "base command for starting scans", Run: scanRootCommand, + PreRun: func(cmd *cobra.Command, args []string) { + t, _ := cmd.Flags().GetString("tool") + if !validTool(t) { + klog.Fatal("a valid tool must be given. Run `kdt list scanners` to see the supported scanner's list.") + } + }, } -func scanRootCommand(cmd *cobra.Command, args []string) { +func scanRootCommand(cmd *cobra.Command, _ []string) { // Initialize Kondukto client c, err := client.New() if err != nil { @@ -80,12 +64,12 @@ func scanRootCommand(cmd *cobra.Command, args []string) { eventID, err := startScan(cmd, c) if err != nil { - qwe(1, err, "scan failed") + klog.Fatalf("failed to start scan: %v", err) } async, err := cmd.Flags().GetBool("async") if err != nil { - qwe(1, err, "failed to parse async flag") + klog.Fatalf("failed to parse async flag: %v", err) } // Do not wait for scan to finish if async set to true @@ -231,7 +215,7 @@ func waitTillScanEnded(cmd *cobra.Command, c *client.Client, eventID string) { for { event, err := c.GetScanStatus(eventID) if err != nil { - qwe(1, err, "could not get scan status") + klog.Fatalf("failed to get scan status: %v", err) } switch event.Active { @@ -239,7 +223,7 @@ func waitTillScanEnded(cmd *cobra.Command, c *client.Client, eventID string) { qwm(1, "scan failed") case eventInactive: if event.Status == jobFinished { - log.Println("scan finished successfully") + klog.Println("scan finished successfully") scan, err := c.GetScanSummary(event.ScanId) if err != nil { qwe(1, err, "failed to fetch scan summary") @@ -260,9 +244,11 @@ func waitTillScanEnded(cmd *cobra.Command, c *client.Client, eventID string) { qwm(0, "scan duration exceeds timeout, it will continue running async in the background") } if event.Status != lastStatus { - log.Println(statusMsg(event.Status)) + klog.Println(statusMsg(event.Status)) lastStatus = event.Status // Get new scans scan id + } else { + klog.Debugf("event status [%s]", statusMsg(event.Status)) } time.Sleep(10 * time.Second) default: @@ -271,17 +257,6 @@ func waitTillScanEnded(cmd *cobra.Command, c *client.Client, eventID string) { } } -func validTool(tool string) bool { - switch tool { - case toolAppSpider, toolBandit, toolCheckmarx, toolFindSecBugs, toolNetSparker, toolOWASPZap, - toolFortify, toolGoSec, toolDependencyCheck, toolBrakeman, toolAppScan, toolSCS, toolTrivy, - toolNancy, toolCxSca, toolZapless, toolSemGrep, toolWebInspect, toolVeracode, toolBurpSuiteEnterprise, toolBurpSuite: - return true - default: - return false - } -} - func statusMsg(s int) string { switch s { case jobStarting: @@ -398,6 +373,7 @@ func scanByFile(cmd *cobra.Command, c *client.Client) (string, error) { return eventID, nil } +//goland:noinspection GoNilness func getScanIDByProjectTool(cmd *cobra.Command, c *client.Client) (string, error) { // Parse command line flags project, err := cmd.Flags().GetString("project") @@ -426,7 +402,7 @@ func getScanIDByProjectTool(cmd *cobra.Command, c *client.Client) (string, error scan, err := c.FindScan(project, params) if err != nil { - qwe(1, err, "could not get scans of the project") + klog.Fatal("no scans found for given project and tool configuration") } return scan.ID, nil @@ -466,6 +442,7 @@ func getScanIDByProjectToolAndMeta(cmd *cobra.Command, c *client.Client) (string scan, err := c.FindScan(project, params) if err != nil { + klog.Fatal("no scans found for given project, tool and metadata configuration") qwe(1, err, "could not get scans of the project") } @@ -516,6 +493,7 @@ func getScanIDByProjectToolAndPR(cmd *cobra.Command, c *client.Client) (string, scan, err := c.FindScan(project, params) if err != nil { + klog.Fatalf("no scans found for given project, tool and PR configuration") qwe(1, err, "could not get scans of the project") } if scan == nil { diff --git a/cmd/status.go b/cmd/status.go index ada1fa5..c741857 100755 --- a/cmd/status.go +++ b/cmd/status.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package cmd import ( @@ -31,7 +32,7 @@ func init() { statusCmd.Flags().Int("threshold-low", 0, "threshold for number of vulnerabilities with low severity") } -func statusRootCommand(cmd *cobra.Command, args []string) { +func statusRootCommand(cmd *cobra.Command, _ []string) { // Initialize Kondukto client c, err := client.New() if err != nil { @@ -67,7 +68,7 @@ func statusRootCommand(cmd *cobra.Command, args []string) { _, _ = fmt.Fprintf(w, "NAME\tID\tMETA\tTOOL\tCRIT\tHIGH\tMED\tLOW\tSCORE\tDATE\n") _, _ = fmt.Fprintf(w, "---\t---\t---\t---\t---\t---\t---\t---\t---\t---\n") _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%d\t%d\t%d\t%d\t%s\n\n", scan.Name, scan.ID, scan.MetaData, scan.Tool, scan.Summary.Critical, scan.Summary.High, scan.Summary.Medium, scan.Summary.Low, scan.Score, scan.Date) - w.Flush() + _ = w.Flush() if err := passTests(scan, cmd); err != nil { qwe(1, err, "scan could not pass security tests") } else { diff --git a/cmd/util.go b/cmd/util.go index 4d53027..40d81f0 100755 --- a/cmd/util.go +++ b/cmd/util.go @@ -2,25 +2,59 @@ Copyright © 2019 Kondukto */ + package cmd import ( "fmt" - "log" "os" + + "github.com/kondukto-io/kdt/klog" ) +var scanners = map[string]string{ + "checkmarx": "sast", + "checkmarxsca": "sca", + "owaspzap": "dast", + "webinspect": "dast", + "netsparker": "dast", + "appspider": "dast", + "bandit": "sast", + "findsecbugs": "sast", + "gosec": "sast", + "dependencycheck": "sca", + "fortify": "sast", + "securitycodescan": "sast", + "hclappscan": "dast", + "veracode": "sast", + "burpsuite": "dast", + "burpsuiteenterprise": "dast", + "nuclei": "dast", + "gitleaks": "sast", + "semgrep": "sast", + "semgrepconfig": "iac", + "kicks": "iac", + "trivy": "cs", +} + // qwe quits with error. If there are messages, wraps error with message func qwe(code int, err error, messages ...string) { for _, m := range messages { err = fmt.Errorf("%s: %w", m, err) } - log.Println(err) + klog.Fatalln(err) os.Exit(code) } // qwm quits with message func qwm(code int, message string) { - log.Println(message) + klog.Println(message) os.Exit(code) } + +func validTool(t string) bool { + if _, ok := scanners[t]; ok { + return true + } + return false +} diff --git a/cmd/version.go b/cmd/version.go index 71f1ed4..1587754 100755 --- a/cmd/version.go +++ b/cmd/version.go @@ -2,6 +2,7 @@ Copyright © 2019 Kondukto */ + package cmd import ( diff --git a/klog/klog.go b/klog/klog.go new file mode 100755 index 0000000..f7ae3e7 --- /dev/null +++ b/klog/klog.go @@ -0,0 +1,129 @@ +package klog + +import ( + "fmt" + "io" + "os" + "time" +) + +const ( + LevelInfo uint = iota + LevelError + LevelWarning + LevelDebug +) + +const ( + PrefixInfo = "INF: " + PrefixError = "ERR: " + PrefixWarning = "WRN: " + PrefixDebug = "DBG: " + PrefixFatal = "ERR: " + PrefixPanic = "PNC: " +) + +type Logger struct { + Level uint + Output io.Writer +} + +var DefaultLogger = &Logger{ + Level: LevelError, + Output: os.Stdout, +} + +func (l *Logger) out(s string) error { + s = fmt.Sprintf("%s %s", time.Now().Format(time.RFC3339), s) + if len(s) == 0 || s[len(s)-1] != '\n' { + s += "\n" + } + _, err := l.Output.Write([]byte(s)) + return err +} + +func Print(v ...interface{}) { + DefaultLogger.out(PrefixInfo + fmt.Sprint(v...)) +} + +func Printf(format string, v ...interface{}) { + DefaultLogger.out(PrefixInfo + fmt.Sprintf(format, v...)) +} + +func Println(v ...interface{}) { + DefaultLogger.out(PrefixInfo + fmt.Sprintln(v...)) +} + +func Fatal(v ...interface{}) { + DefaultLogger.out(PrefixFatal + fmt.Sprint(v...)) + os.Exit(1) +} + +func Fatalf(format string, v ...interface{}) { + DefaultLogger.out(PrefixFatal + fmt.Sprintf(format, v...)) + os.Exit(1) +} + +func Fatalln(v ...interface{}) { + DefaultLogger.out(PrefixFatal + fmt.Sprintln(v...)) + os.Exit(1) +} + +func Panic(v ...interface{}) { + s := fmt.Sprint(v...) + DefaultLogger.out(PrefixPanic + s) + panic(s) +} + +func Panicf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + DefaultLogger.out(PrefixPanic + s) + panic(s) +} + +func Panicln(v ...interface{}) { + s := fmt.Sprintln(v...) + DefaultLogger.out(PrefixPanic + s) + panic(s) +} + +func Debug(v ...interface{}) { + if DefaultLogger.Level < LevelDebug { + return + } + DefaultLogger.out(PrefixDebug + fmt.Sprint(v...)) +} + +func Debugf(format string, v ...interface{}) { + if DefaultLogger.Level < LevelDebug { + return + } + DefaultLogger.out(PrefixDebug + fmt.Sprintf(format, v...)) +} + +func Debugln(v ...interface{}) { + if DefaultLogger.Level < LevelDebug { + return + } + DefaultLogger.out(PrefixDebug + fmt.Sprintln(v...)) +} +func Warn(v ...interface{}) { + if DefaultLogger.Level < LevelWarning { + return + } + DefaultLogger.out(PrefixWarning + fmt.Sprint(v...)) +} + +func Warnf(format string, v ...interface{}) { + if DefaultLogger.Level < LevelWarning { + return + } + DefaultLogger.out(PrefixWarning + fmt.Sprintf(format, v...)) +} + +func Warnln(v ...interface{}) { + if DefaultLogger.Level < LevelWarning { + return + } + DefaultLogger.out(PrefixWarning + fmt.Sprintln(v...)) +}