瀏覽代碼

started work on new arch

Torkel Ödegaard 11 年之前
父節點
當前提交
a4204880e8
共有 12 個文件被更改,包括 636 次插入47 次删除
  1. 14 0
      .bra.toml
  2. 41 0
      conf/grafana.ini
  3. 二進制
      grafana-pro
  4. 16 22
      grafana.go
  5. 8 3
      pkg/api/api.go
  6. 5 20
      pkg/api/api_logger.go
  7. 45 0
      pkg/cmd/web.go
  8. 73 0
      pkg/log/console.go
  9. 299 0
      pkg/log/log.go
  10. 7 0
      pkg/routes/routes.go
  11. 125 0
      pkg/setting/setting.go
  12. 3 2
      pkg/stores/rethinkdb.go

+ 14 - 0
.bra.toml

@@ -0,0 +1,14 @@
+[run]
+init_cmds = [["./grafana-pro", "web"]]
+watch_all = true
+watch_dirs = [
+	"$WORKDIR/pkg",
+	"$WORKDIR/views",
+]
+watch_exts = [".go", ".ini"]
+build_delay = 1500
+cmds = [
+	["go", "install"],
+	["go", "build"],
+	["./grafana-pro", "web"]
+]

+ 41 - 0
conf/grafana.ini

@@ -0,0 +1,41 @@
+app_name = Grafana Pro Server
+app_mode = dev
+
+[server]
+protocol = http
+domain = localhost
+root_url = %(protocol)s://%(domain)s:%(http_port)s/
+http_addr =
+http_port = 3000
+ssh_port = 22
+route_log = true
+
+[log]
+root_path =
+; Either "console", "file", "conn", "smtp" or "database", default is "console"
+; Use comma to separate multiple modes, e.g. "console, file"
+mode = console
+; Buffer length of channel, keep it as it is if you don't know what it is.
+buffer_len = 10000
+; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace"
+level = Trace
+
+; For "console" mode only
+[log.console]
+level =
+
+; For "file" mode only
+[log.file]
+level =
+; This enables automated log rotate(switch of following options), default is true
+log_rotate = true
+; Max line number of single file, default is 1000000
+max_lines = 1000000
+; Max size shift of single file, default is 28 means 1 << 28, 256MB
+max_lines_shift = 28
+; Segment log daily, default is true
+daily_rotate = true
+; Expired days of log file(delete after max days), default is 7
+max_days = 7
+
+

二進制
grafana-pro


+ 16 - 22
grafana.go

@@ -2,32 +2,26 @@ package main
 
 import (
 	"os"
-	"time"
+	"runtime"
 
-	log "github.com/alecthomas/log4go"
-	"github.com/torkelo/grafana-pro/pkg/configuration"
-	"github.com/torkelo/grafana-pro/pkg/server"
+	"github.com/codegangsta/cli"
+	"github.com/torkelo/grafana-pro/pkg/cmd"
 )
 
-func main() {
-	port := os.Getenv("PORT")
-	if port == "" {
-		port = "3838"
-	}
+const APP_VER = "0.1.0 Alpha"
 
-	log.Info("Starting Grafana-Pro v.1-alpha")
-
-	cfg := configuration.NewCfg(port)
-	server, err := server.NewServer(cfg)
-	if err != nil {
-		time.Sleep(time.Second)
-		panic(err)
-	}
+func init() {
+	runtime.GOMAXPROCS(runtime.NumCPU())
+}
 
-	err = server.ListenAndServe()
-	if err != nil {
-		log.Error("ListenAndServe failed: ", err)
+func main() {
+	app := cli.NewApp()
+	app.Name = "Grafana Pro"
+	app.Usage = "Grafana Pro Service"
+	app.Version = APP_VER
+	app.Commands = []cli.Command{
+		cmd.CmdWeb,
 	}
-
-	time.Sleep(time.Millisecond * 2000)
+	app.Flags = append(app.Flags, []cli.Flag{}...)
+	app.Run(os.Args)
 }

+ 8 - 3
pkg/api/api.go

@@ -1,14 +1,17 @@
 package api
 
 import (
+	"fmt"
 	"html/template"
 
-	log "github.com/alecthomas/log4go"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/sessions"
+
 	"github.com/torkelo/grafana-pro/pkg/components"
 	"github.com/torkelo/grafana-pro/pkg/configuration"
+	"github.com/torkelo/grafana-pro/pkg/log"
 	"github.com/torkelo/grafana-pro/pkg/models"
+	"github.com/torkelo/grafana-pro/pkg/setting"
 	"github.com/torkelo/grafana-pro/pkg/stores"
 )
 
@@ -34,9 +37,9 @@ func NewHttpServer(cfg *configuration.Cfg, store stores.Store) *HttpServer {
 }
 
 func (self *HttpServer) ListenAndServe() {
-	log.Info("Starting Http Listener on port %v", self.port)
 	defer func() { self.shutdown <- true }()
 
+	gin.SetMode(gin.ReleaseMode)
 	self.router = gin.New()
 	self.router.Use(gin.Recovery(), apiLogger(), CacheHeadersMiddleware())
 
@@ -60,7 +63,9 @@ func (self *HttpServer) ListenAndServe() {
 	self.router.GET("/admin/*_", self.auth(), self.index)
 	self.router.GET("/account/*_", self.auth(), self.index)
 
-	self.router.Run(":" + self.port)
+	listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
+	log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubUrl)
+	self.router.Run(listenAddr)
 }
 
 func (self *HttpServer) index(c *gin.Context) {

+ 5 - 20
pkg/api/api_logger.go

@@ -1,12 +1,12 @@
 package api
 
 import (
-	"log"
-	"os"
 	"strings"
 	"time"
 
 	"github.com/gin-gonic/gin"
+
+	"github.com/torkelo/grafana-pro/pkg/log"
 )
 
 var (
@@ -25,8 +25,6 @@ func ignoreLoggingRequest(code int, contentType string) bool {
 }
 
 func apiLogger() gin.HandlerFunc {
-	stdlogger := log.New(os.Stdout, "", 0)
-
 	return func(c *gin.Context) {
 		// Start timer
 		start := time.Now()
@@ -53,26 +51,13 @@ func apiLogger() gin.HandlerFunc {
 			requester = c.Request.RemoteAddr
 		}
 
-		var color string
-		switch {
-		case code >= 200 && code <= 299:
-			color = green
-		case code >= 300 && code <= 399:
-			color = white
-		case code >= 400 && code <= 499:
-			color = yellow
-		default:
-			color = red
-		}
-
 		end := time.Now()
 		latency := end.Sub(start)
-		stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s %4s %s\n%s",
-			end.Format("2006/01/02 - 15:04:05"),
-			color, code, reset,
+		log.Info("[http] %s %s %3d %12v %s %s",
+			c.Request.Method, c.Request.URL.Path,
+			code,
 			latency,
 			requester,
-			c.Request.Method, c.Request.URL.Path,
 			c.Errors.String(),
 		)
 	}

+ 45 - 0
pkg/cmd/web.go

@@ -0,0 +1,45 @@
+package cmd
+
+import (
+	"os"
+	"time"
+
+	"github.com/codegangsta/cli"
+	"github.com/siddontang/go-log/log"
+	"github.com/torkelo/grafana-pro/pkg/configuration"
+	"github.com/torkelo/grafana-pro/pkg/routes"
+	"github.com/torkelo/grafana-pro/pkg/server"
+)
+
+var CmdWeb = cli.Command{
+	Name:        "web",
+	Usage:       "Start Grafana Pro web server",
+	Description: `Start Grafana Pro server`,
+	Action:      runWeb,
+	Flags:       []cli.Flag{},
+}
+
+func runWeb(*cli.Context) {
+	routes.GlobalInit()
+	port := os.Getenv("PORT")
+	if port == "" {
+		port = "3838"
+	}
+
+	log.Info("Starting Grafana-Pro v.1-alpha")
+
+	cfg := configuration.NewCfg(port)
+	server, err := server.NewServer(cfg)
+	if err != nil {
+		time.Sleep(time.Second)
+		panic(err)
+	}
+
+	err = server.ListenAndServe()
+	if err != nil {
+		log.Error("ListenAndServe failed: ", err)
+	}
+
+	time.Sleep(time.Millisecond * 2000)
+
+}

+ 73 - 0
pkg/log/console.go

@@ -0,0 +1,73 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package log
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+	"runtime"
+)
+
+type Brush func(string) string
+
+func NewBrush(color string) Brush {
+	pre := "\033["
+	reset := "\033[0m"
+	return func(text string) string {
+		return pre + color + "m" + text + reset
+	}
+}
+
+var colors = []Brush{
+	NewBrush("1;36"), // Trace      cyan
+	NewBrush("1;34"), // Debug      blue
+	NewBrush("1;32"), // Info       green
+	NewBrush("1;33"), // Warn       yellow
+	NewBrush("1;31"), // Error      red
+	NewBrush("1;35"), // Critical   purple
+	NewBrush("1;31"), // Fatal      red
+}
+
+// ConsoleWriter implements LoggerInterface and writes messages to terminal.
+type ConsoleWriter struct {
+	lg    *log.Logger
+	Level int `json:"level"`
+}
+
+// create ConsoleWriter returning as LoggerInterface.
+func NewConsole() LoggerInterface {
+	return &ConsoleWriter{
+		lg:    log.New(os.Stdout, "", log.Ldate|log.Ltime),
+		Level: TRACE,
+	}
+}
+
+func (cw *ConsoleWriter) Init(config string) error {
+	return json.Unmarshal([]byte(config), cw)
+}
+
+func (cw *ConsoleWriter) WriteMsg(msg string, skip, level int) error {
+	if cw.Level > level {
+		return nil
+	}
+	if runtime.GOOS == "windows" {
+		cw.lg.Println(msg)
+	} else {
+		cw.lg.Println(colors[level](msg))
+	}
+	return nil
+}
+
+func (_ *ConsoleWriter) Flush() {
+
+}
+
+func (_ *ConsoleWriter) Destroy() {
+}
+
+func init() {
+	Register("console", NewConsole)
+}

+ 299 - 0
pkg/log/log.go

@@ -0,0 +1,299 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package log
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"sync"
+)
+
+var (
+	loggers []*Logger
+)
+
+func NewLogger(bufLen int64, mode, config string) {
+	logger := newLogger(bufLen)
+
+	isExist := false
+	for _, l := range loggers {
+		if l.adapter == mode {
+			isExist = true
+			l = logger
+		}
+	}
+	if !isExist {
+		loggers = append(loggers, logger)
+	}
+	if err := logger.SetLogger(mode, config); err != nil {
+		Fatal(1, "Fail to set logger(%s): %v", mode, err)
+	}
+}
+
+func Trace(format string, v ...interface{}) {
+	for _, logger := range loggers {
+		logger.Trace(format, v...)
+	}
+}
+
+func Debug(format string, v ...interface{}) {
+	for _, logger := range loggers {
+		logger.Debug(format, v...)
+	}
+}
+
+func Info(format string, v ...interface{}) {
+	for _, logger := range loggers {
+		logger.Info(format, v...)
+	}
+}
+
+func Warn(format string, v ...interface{}) {
+	for _, logger := range loggers {
+		logger.Warn(format, v...)
+	}
+}
+
+func Error(skip int, format string, v ...interface{}) {
+	for _, logger := range loggers {
+		logger.Error(skip, format, v...)
+	}
+}
+
+func Critical(skip int, format string, v ...interface{}) {
+	for _, logger := range loggers {
+		logger.Critical(skip, format, v...)
+	}
+}
+
+func Fatal(skip int, format string, v ...interface{}) {
+	Error(skip, format, v...)
+	for _, l := range loggers {
+		l.Close()
+	}
+	os.Exit(1)
+}
+
+func Close() {
+	for _, l := range loggers {
+		l.Close()
+	}
+}
+
+// .___        __                 _____
+// |   | _____/  |_  ____________/ ____\____    ____  ____
+// |   |/    \   __\/ __ \_  __ \   __\\__  \ _/ ___\/ __ \
+// |   |   |  \  | \  ___/|  | \/|  |   / __ \\  \__\  ___/
+// |___|___|  /__|  \___  >__|   |__|  (____  /\___  >___  >
+//          \/          \/                  \/     \/    \/
+
+type LogLevel int
+
+const (
+	TRACE = iota
+	DEBUG
+	INFO
+	WARN
+	ERROR
+	CRITICAL
+	FATAL
+)
+
+// LoggerInterface represents behaviors of a logger provider.
+type LoggerInterface interface {
+	Init(config string) error
+	WriteMsg(msg string, skip, level int) error
+	Destroy()
+	Flush()
+}
+
+type loggerType func() LoggerInterface
+
+var adapters = make(map[string]loggerType)
+
+// Register registers given logger provider to adapters.
+func Register(name string, log loggerType) {
+	if log == nil {
+		panic("log: register provider is nil")
+	}
+	if _, dup := adapters[name]; dup {
+		panic("log: register called twice for provider \"" + name + "\"")
+	}
+	adapters[name] = log
+}
+
+type logMsg struct {
+	skip, level int
+	msg         string
+}
+
+// Logger is default logger in beego application.
+// it can contain several providers and log message into all providers.
+type Logger struct {
+	adapter string
+	lock    sync.Mutex
+	level   int
+	msg     chan *logMsg
+	outputs map[string]LoggerInterface
+	quit    chan bool
+}
+
+// newLogger initializes and returns a new logger.
+func newLogger(buffer int64) *Logger {
+	l := &Logger{
+		msg:     make(chan *logMsg, buffer),
+		outputs: make(map[string]LoggerInterface),
+		quit:    make(chan bool),
+	}
+	go l.StartLogger()
+	return l
+}
+
+// SetLogger sets new logger instanse with given logger adapter and config.
+func (l *Logger) SetLogger(adapter string, config string) error {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+	if log, ok := adapters[adapter]; ok {
+		lg := log()
+		if err := lg.Init(config); err != nil {
+			return err
+		}
+		l.outputs[adapter] = lg
+		l.adapter = adapter
+	} else {
+		panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)")
+	}
+	return nil
+}
+
+// DelLogger removes a logger adapter instance.
+func (l *Logger) DelLogger(adapter string) error {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+	if lg, ok := l.outputs[adapter]; ok {
+		lg.Destroy()
+		delete(l.outputs, adapter)
+	} else {
+		panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)")
+	}
+	return nil
+}
+
+func (l *Logger) writerMsg(skip, level int, msg string) error {
+	if l.level > level {
+		return nil
+	}
+	lm := &logMsg{
+		skip:  skip,
+		level: level,
+	}
+
+	// Only error information needs locate position for debugging.
+	if lm.level >= ERROR {
+		pc, file, line, ok := runtime.Caller(skip)
+		if ok {
+			// Get caller function name.
+			fn := runtime.FuncForPC(pc)
+			var fnName string
+			if fn == nil {
+				fnName = "?()"
+			} else {
+				fnName = strings.TrimLeft(filepath.Ext(fn.Name()), ".") + "()"
+			}
+
+			lm.msg = fmt.Sprintf("[%s:%d %s] %s", filepath.Base(file), line, fnName, msg)
+		} else {
+			lm.msg = msg
+		}
+	} else {
+		lm.msg = msg
+	}
+	l.msg <- lm
+	return nil
+}
+
+// StartLogger starts logger chan reading.
+func (l *Logger) StartLogger() {
+	for {
+		select {
+		case bm := <-l.msg:
+			for _, l := range l.outputs {
+				if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil {
+					fmt.Println("ERROR, unable to WriteMsg:", err)
+				}
+			}
+		case <-l.quit:
+			return
+		}
+	}
+}
+
+// Flush flushs all chan data.
+func (l *Logger) Flush() {
+	for _, l := range l.outputs {
+		l.Flush()
+	}
+}
+
+// Close closes logger, flush all chan data and destroy all adapter instances.
+func (l *Logger) Close() {
+	l.quit <- true
+	for {
+		if len(l.msg) > 0 {
+			bm := <-l.msg
+			for _, l := range l.outputs {
+				if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil {
+					fmt.Println("ERROR, unable to WriteMsg:", err)
+				}
+			}
+		} else {
+			break
+		}
+	}
+	for _, l := range l.outputs {
+		l.Flush()
+		l.Destroy()
+	}
+}
+
+func (l *Logger) Trace(format string, v ...interface{}) {
+	msg := fmt.Sprintf("[T] "+format, v...)
+	l.writerMsg(0, TRACE, msg)
+}
+
+func (l *Logger) Debug(format string, v ...interface{}) {
+	msg := fmt.Sprintf("[D] "+format, v...)
+	l.writerMsg(0, DEBUG, msg)
+}
+
+func (l *Logger) Info(format string, v ...interface{}) {
+	msg := fmt.Sprintf("[I] "+format, v...)
+	l.writerMsg(0, INFO, msg)
+}
+
+func (l *Logger) Warn(format string, v ...interface{}) {
+	msg := fmt.Sprintf("[W] "+format, v...)
+	l.writerMsg(0, WARN, msg)
+}
+
+func (l *Logger) Error(skip int, format string, v ...interface{}) {
+	msg := fmt.Sprintf("[E] "+format, v...)
+	l.writerMsg(skip, ERROR, msg)
+}
+
+func (l *Logger) Critical(skip int, format string, v ...interface{}) {
+	msg := fmt.Sprintf("[C] "+format, v...)
+	l.writerMsg(skip, CRITICAL, msg)
+}
+
+func (l *Logger) Fatal(skip int, format string, v ...interface{}) {
+	msg := fmt.Sprintf("[F] "+format, v...)
+	l.writerMsg(skip, FATAL, msg)
+	l.Close()
+	os.Exit(1)
+}

+ 7 - 0
pkg/routes/routes.go

@@ -0,0 +1,7 @@
+package routes
+
+import "github.com/torkelo/grafana-pro/pkg/setting"
+
+func GlobalInit() {
+	setting.NewConfigContext()
+}

+ 125 - 0
pkg/setting/setting.go

@@ -0,0 +1,125 @@
+package setting
+
+import (
+	"net/url"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"github.com/Unknwon/com"
+	"github.com/Unknwon/goconfig"
+	"github.com/torkelo/grafana-pro/pkg/log"
+)
+
+type Scheme string
+
+const (
+	HTTP  Scheme = "http"
+	HTTPS Scheme = "https"
+)
+
+var (
+	// App settings.
+	AppVer    string
+	AppName   string
+	AppUrl    string
+	AppSubUrl string
+
+	// Log settings.
+	LogRootPath string
+	LogModes    []string
+	LogConfigs  []string
+
+	// Http server options
+	Protocol           Scheme
+	Domain             string
+	HttpAddr, HttpPort string
+	SshPort            int
+	CertFile, KeyFile  string
+	DisableRouterLog   bool
+
+	// Global setting objects.
+	Cfg          *goconfig.ConfigFile
+	ConfRootPath string
+	CustomPath   string // Custom directory path.
+	ProdMode     bool
+	RunUser      string
+	IsWindows    bool
+)
+
+func init() {
+	IsWindows = runtime.GOOS == "windows"
+	log.NewLogger(0, "console", `{"level": 0}`)
+}
+
+// WorkDir returns absolute path of work directory.
+func WorkDir() (string, error) {
+	execPath, err := ExecPath()
+	return path.Dir(strings.Replace(execPath, "\\", "/", -1)), err
+}
+
+func ExecPath() (string, error) {
+	file, err := exec.LookPath(os.Args[0])
+	if err != nil {
+		return "", err
+	}
+	p, err := filepath.Abs(file)
+	if err != nil {
+		return "", err
+	}
+	return p, nil
+}
+
+func NewConfigContext() {
+	workDir, err := WorkDir()
+	if err != nil {
+		log.Fatal(4, "Fail to get work directory: %v", err)
+	}
+	ConfRootPath = path.Join(workDir, "conf")
+
+	Cfg, err = goconfig.LoadConfigFile(path.Join(workDir, "conf/grafana.ini"))
+	if err != nil {
+		log.Fatal(4, "Fail to parse 'conf/grafana.ini': %v", err)
+	}
+
+	CustomPath = os.Getenv("GRAFANA_CONF")
+
+	if len(CustomPath) == 0 {
+		CustomPath = path.Join(workDir, "custom")
+	}
+
+	cfgPath := path.Join(CustomPath, "conf/grafana.ini")
+	if com.IsFile(cfgPath) {
+		if err = Cfg.AppendFiles(cfgPath); err != nil {
+			log.Fatal(4, "Fail to load custom 'conf/grafana.ini': %v", err)
+		}
+	} else {
+		log.Warn("No custom 'conf/grafana.ini'")
+	}
+
+	AppName = Cfg.MustValue("", "app_name", "Grafana Pro")
+	AppUrl = Cfg.MustValue("server", "root_url", "http://localhost:3000/")
+	if AppUrl[len(AppUrl)-1] != '/' {
+		AppUrl += "/"
+	}
+
+	// Check if has app suburl.
+	url, err := url.Parse(AppUrl)
+	if err != nil {
+		log.Fatal(4, "Invalid root_url(%s): %s", AppUrl, err)
+	}
+	AppSubUrl = strings.TrimSuffix(url.Path, "/")
+
+	Protocol = HTTP
+	if Cfg.MustValue("server", "protocol") == "https" {
+		Protocol = HTTPS
+		CertFile = Cfg.MustValue("server", "cert_file")
+		KeyFile = Cfg.MustValue("server", "key_file")
+	}
+	Domain = Cfg.MustValue("server", "domain", "localhost")
+	HttpAddr = Cfg.MustValue("server", "http_addr", "0.0.0.0")
+	HttpPort = Cfg.MustValue("server", "http_port", "3000")
+}

+ 3 - 2
pkg/stores/rethinkdb.go

@@ -3,8 +3,9 @@ package stores
 import (
 	"time"
 
-	log "github.com/alecthomas/log4go"
 	r "github.com/dancannon/gorethink"
+
+	"github.com/torkelo/grafana-pro/pkg/log"
 )
 
 type rethinkStore struct {
@@ -31,7 +32,7 @@ func NewRethinkStore(config *RethinkCfg) *rethinkStore {
 	})
 
 	if err != nil {
-		log.Crash("Failed to connect to rethink database %v", err)
+		log.Error(3, "Failed to connect to rethink database %v", err)
 	}
 
 	createRethinkDBTablesAndIndices(config, session)