Compare commits

...

27 Commits

Author SHA1 Message Date
Steve
149f9f992b Merge pull request #161 from notzippy/master
Patch for windows
2018-11-01 20:31:14 -07:00
NotZippy
dfb08d9bd2 Amended importPathFromPath to detect vendor folder from basePath, not just the word vendor in the path 2018-11-01 09:06:31 -07:00
NotZippy
98e771cd01 Patch for windows
Made interrupt call os.Kill for windows
Added check for process still running before killing it
2018-10-31 12:52:43 -07:00
NotZippy
5f558aca4e release v0.21.0 2018-10-30 06:23:52 -07:00
Steve
facfe0ecaf Merge pull request #159 from notzippy/develop
Patchset for 21
2018-10-29 16:29:03 -07:00
NotZippy
ee53d2f399 Patchset for 21
Added Version -u to update the checked out libaraies
Enhanced new skeleton toto support http https and git schemas when cloning the skeleton repo.
Enhanced version command to fetch server version from master branch
Enhanced version command to update local repository if a new version exists on the server
2018-10-29 15:26:59 -07:00
Steve
3c48e1f83e Merge pull request #158 from hwsoderlund/fix_compile_error
Fix compilation error visibility
2018-10-29 10:19:17 -07:00
Henrik Söderlund
19fb7d6776 Fix compilation error visibility 2018-10-29 16:55:59 +01:00
Steve
1e7b5322f5 Merge pull request #157 from HaraldNordgren/go_versions
Bump Go versions and use '.x' to always get latest patch versions
2018-10-28 11:08:56 -07:00
Harald Nordgren
205c652f07 Bump Go versions and use '.x' to always get latest patch versions 2018-10-28 18:42:35 +01:00
Steve
3de8b8c03c Merge pull request #156 from notzippy/develop
Modified run command to translate symlinks to absolute paths
2018-10-25 09:19:43 -07:00
NotZippy
4a877b2a8a Modified run command to translate symlinks to absolute paths 2018-10-24 21:29:07 -07:00
Steve
a0bafdc2a7 Merge pull request #155 from notzippy/develop
Added ability to readback output from command line
2018-10-24 10:26:16 -07:00
NotZippy
cdef0b75a8 Added ability to readback output from command line
Updated readme
2018-10-24 10:23:09 -07:00
Steve
e0d3f83ca8 Merge pull request #153 from notzippy/develop
Updated tool to give more meaningful messages
2018-10-19 09:12:48 -07:00
NotZippy
87c9e56322 Tool updates
Updated tool to give more meaningful errors
Added file system as an option to fetch the skeleton from
2018-10-19 06:45:01 -07:00
Steve
554e62574d Merge pull request #152 from notzippy/develop
Allow windows to fail on travis since to address a bug on current master
2018-10-18 08:05:51 -07:00
NotZippy
32a3f08dde Allow windows to fail on travis since to address a bug on current master 2018-10-18 08:05:15 -07:00
Steve
e6e1cad795 Merge pull request #148 from notzippy/develop
Shutdown / logger update
2018-10-12 21:20:05 -07:00
NotZippy
5e36cb1025 Updated travis to use checkout matching branch of Revel Framework for build. 2018-10-12 20:50:42 -07:00
NotZippy
8c21a56302 Revel tool enhancements
* run Command will choose CWD if no additional arguments are supplied
* Added Revel version check, compatible lists are in model/version
2018-10-12 20:40:48 -07:00
NotZippy
031fde6009 Update to logger 2018-10-12 20:40:48 -07:00
Steve
09ca80add8 Merge pull request #151 from lujiacn/master
Update build.go
2018-10-12 15:48:22 -07:00
Jia Lu
2d6c2eefa4 Update build.go
add missing c.Build.ImportPath, which is required to generate run.sh and run.bat
2018-10-12 22:53:01 +08:00
Steve
644d6e12bd Added check for copy dir
CopyDir should not fail if the source folder does not exist.
2018-10-10 11:40:29 -07:00
Steve
be7bebd962 Merge pull request #146 from notzippy/develop
Added missing environment variables to command, skipping the gopath
2018-10-02 09:27:54 -07:00
NotZippy
43c188c1eb Added missing environment variables to command, skipping the gopath 2018-10-02 08:48:12 -07:00
35 changed files with 1944 additions and 1428 deletions

View File

@@ -1,11 +1,13 @@
{
"GOLANG": {
"ABC":[25, 35, 50, 70],
"BLOCK_NESTING":[5, 6, 7, 8],
"CYCLO":[20, 30, 45, 60],
"TOO_MANY_IVARS": [15, 18, 20, 25],
"ABC":[25, 35, 50, 70],
"ARITY":[5,6,7,8],
"BLOCK_NESTING":[7, 9, 11, 13],
"CYCLO":[20, 30, 45, 60],
"TOO_MANY_IVARS": [20, 25, 40, 45],
"TOO_MANY_FUNCTIONS": [20, 30, 40, 50],
"TOTAL_COMPLEXITY": [150, 250, 400, 500],
"LOC": [50, 75, 90, 120]
"LOC": [100, 175, 250, 320],
"TOTAL_LOC": [300, 400, 500, 600]
}
}

View File

@@ -1,10 +1,10 @@
language: go
go:
- "1.8.7"
- "1.9"
- "1.10"
- "1.11"
- "1.8.x"
- "1.9.x"
- "1.10.x"
- "1.11.x"
- "tip"
os:
@@ -26,6 +26,8 @@ install:
- export REVEL_BRANCH="develop"
- 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi'
- 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"'
- git clone -b $REVEL_BRANCH git://github.com/revel/revel ../revel/
- git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/
- go get -t -v github.com/revel/cmd/revel
- go get -u github.com/golang/dep/cmd/dep
- echo $GOPATH
@@ -64,3 +66,4 @@ script:
matrix:
allow_failures:
- go: tip
- os: windows

View File

@@ -21,3 +21,31 @@ Create a new application
```commandline
revel new my/app
```
## Community
* [Gitter](https://gitter.im/revel/community)
* [StackOverflow](http://stackoverflow.com/questions/tagged/revel)
## Learn More
* [Manual, Samples, Godocs, etc](http://revel.github.io)
* [Apps using Revel](https://github.com/revel/revel/wiki/Apps-in-the-Wild)
* [Articles Featuring Revel](https://github.com/revel/revel/wiki/Articles)
## Contributing
* [Contributing Code Guidelines](https://github.com/revel/revel/blob/master/CONTRIBUTING.md)
* [Revel Contributors](https://github.com/revel/revel/graphs/contributors)
## Contributors
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/0)](https://sourcerer.io/fame/notzippy/revel/cmd/links/0)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/1)](https://sourcerer.io/fame/notzippy/revel/cmd/links/1)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/2)](https://sourcerer.io/fame/notzippy/revel/cmd/links/2)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/3)](https://sourcerer.io/fame/notzippy/revel/cmd/links/3)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/4)](https://sourcerer.io/fame/notzippy/revel/cmd/links/4)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/5)](https://sourcerer.io/fame/notzippy/revel/cmd/links/5)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/6)](https://sourcerer.io/fame/notzippy/revel/cmd/links/6)
[![](https://sourcerer.io/fame/notzippy/revel/cmd/images/7)](https://sourcerer.io/fame/notzippy/revel/cmd/links/7)

View File

@@ -15,7 +15,7 @@ import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"log"
"runtime"
)
// App contains the configuration for running a Revel app. (Not for the app itself)
@@ -61,7 +61,7 @@ func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelConta
// Start the app server, and wait until it is ready to serve requests.
func (cmd AppCmd) Start(c *model.CommandConfig) error {
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c}
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}}
cmd.Stdout = listeningWriter
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env)
utils.CmdInit(cmd.Cmd, c.AppPath)
@@ -71,8 +71,11 @@ func (cmd AppCmd) Start(c *model.CommandConfig) error {
select {
case exitState := <-cmd.waitChan():
println("Revel proxy is listening, point your browser to :", c.Run.Port)
return errors.New("revel/harness: app died reason: " + exitState)
fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port)
err := utils.NewError("","Revel Run Error", "starting your application there was an exception. See terminal output, " + exitState,"")
// TODO pretiffy command line output
// err.MetaError = listeningWriter.getLastOutput()
return err
case <-time.After(60 * time.Second):
println("Revel proxy is listening, point your browser to :", c.Run.Port)
@@ -89,7 +92,7 @@ func (cmd AppCmd) Start(c *model.CommandConfig) error {
// Run the app server inline. Never returns.
func (cmd AppCmd) Run() {
log.Println("Exec app:", "path", cmd.Path, "args", cmd.Args)
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
if err := cmd.Cmd.Run(); err != nil {
utils.Logger.Fatal("Error running:", "error", err)
}
@@ -97,12 +100,63 @@ func (cmd AppCmd) Run() {
// Kill terminates the app server if it's running.
func (cmd AppCmd) Kill() {
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
err := cmd.Process.Kill()
if err != nil {
utils.Logger.Fatal("Failed to kill revel server:", "error", err)
// Windows appears to send the kill to all threads, shutting down the
// server before this can, this check will ensure the process is still running
if _, err := os.FindProcess(int(cmd.Process.Pid));err!=nil {
// Server has already exited
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
return
}
// Send an interrupt signal to allow for a graceful shutdown
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
var err error
if runtime.GOOS == "windows" {
// os.Interrupt is not available on windows
err = cmd.Process.Signal(os.Kill)
} else {
err = cmd.Process.Signal(os.Interrupt)
}
if err != nil {
utils.Logger.Error(
"Revel app failed to kill process.",
"processid", cmd.Process.Pid,"error",err,
"killerror", cmd.Process.Kill())
return
}
// Wait for the shutdown
ch := make(chan bool, 1)
go func() {
s, err := cmd.Process.Wait()
defer func() {
ch <- true
}()
if err != nil {
utils.Logger.Info("Wait failed for process ", "error", err)
}
if s != nil {
utils.Logger.Info("Revel App exited", "state", s.String())
}
}()
// Use a timer to ensure that the process exits
utils.Logger.Info("Waiting to exit")
select {
case <-ch:
return
case <-time.After(60 * time.Second):
// Kill the process
utils.Logger.Error(
"Revel app failed to exit in 60 seconds - killing.",
"processid", cmd.Process.Pid,
"killerror", cmd.Process.Kill())
}
utils.Logger.Info("Done Waiting to exit")
}
}
@@ -129,8 +183,10 @@ type startupListeningWriter struct {
dest io.Writer
notifyReady chan bool
c *model.CommandConfig
buffer *bytes.Buffer
}
// Writes to this output stream
func (w *startupListeningWriter) Write(p []byte) (int, error) {
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
w.notifyReady <- true
@@ -142,5 +198,15 @@ func (w *startupListeningWriter) Write(p []byte) (int, error) {
w.notifyReady = nil
}
}
if w.notifyReady!=nil {
w.buffer.Write(p)
}
return w.dest.Write(p)
}
// Returns the cleaned output from the response
// TODO clean the response more
func (w *startupListeningWriter) getLastOutput() string {
return w.buffer.String()
}

View File

@@ -231,6 +231,9 @@ func (h *Harness) Refresh() (err *utils.Error) {
h.app.Port = h.port
if err2 := h.app.Cmd(h.runMode).Start(h.config); err2 != nil {
utils.Logger.Error("Could not start application", "error", err2)
if err,k :=err2.(*utils.Error);k {
return err
}
return &utils.Error{
Title: "App failed to start up",
Description: err2.Error(),

View File

@@ -0,0 +1,174 @@
package logger
import (
"github.com/mattn/go-colorable"
"gopkg.in/natefinch/lumberjack.v2"
"io"
"os"
)
type CompositeMultiHandler struct {
DebugHandler LogHandler
InfoHandler LogHandler
WarnHandler LogHandler
ErrorHandler LogHandler
CriticalHandler LogHandler
}
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{}
return cw, cw
}
func (h *CompositeMultiHandler) Log(r *Record) (err error) {
var handler LogHandler
switch r.Level {
case LvlInfo:
handler = h.InfoHandler
case LvlDebug:
handler = h.DebugHandler
case LvlWarn:
handler = h.WarnHandler
case LvlError:
handler = h.ErrorHandler
case LvlCrit:
handler = h.CriticalHandler
}
// Embed the caller function in the context
if handler != nil {
handler.Log(r)
}
return
}
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
if handler == nil {
// Ignore empty handler
return
}
source := &h.DebugHandler
switch level {
case LvlDebug:
source = &h.DebugHandler
case LvlInfo:
source = &h.InfoHandler
case LvlWarn:
source = &h.WarnHandler
case LvlError:
source = &h.ErrorHandler
case LvlCrit:
source = &h.CriticalHandler
}
if !replace && *source != nil {
// If we are not replacing the source make sure that the level handler is applied first
if _, isLevel := (*source).(*LevelFilterHandler); !isLevel {
*source = LevelHandler(level, *source)
}
// If this already was a list add a new logger to it
if ll, found := (*source).(*ListLogHandler); found {
ll.Add(handler)
} else {
*source = NewListLogHandler(*source, handler)
}
} else {
*source = handler
}
}
// For the multi handler set the handler, using the LogOptions defined
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 {
options.Levels = LvlAllList
}
// Set all levels
for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
}
}
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, JsonFormatEx(
options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true),
)))
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetJson(writer, options)
}
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
streamHandler := StreamHandler(
writer,
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
if os.Stdout == writer {
streamHandler = StreamHandler(
colorable.NewColorableStdout(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
} else if os.Stderr == writer {
streamHandler = StreamHandler(
colorable.NewColorableStderr(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
}
handler := CallerFileHandler(streamHandler)
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetTerminal(writer, options)
}
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
if len(levels) == 0 {
levels = LvlAllList
}
for _, level := range levels {
switch level {
case LvlDebug:
h.DebugHandler = nil
case LvlInfo:
h.InfoHandler = nil
case LvlWarn:
h.WarnHandler = nil
case LvlError:
h.ErrorHandler = nil
case LvlCrit:
h.CriticalHandler = nil
}
}
}

View File

@@ -3,8 +3,13 @@
These facilities all currently use the logging library called log15 at
https://github.com/inconshreveable/log15
Wrappers for the handlers are written here to provide a kind of isolation layer for Revel
in case sometime in the future we would like to switch to another source to implement logging
Defining handlers happens as follows
1) ALL handlers (log.all.output) replace any existing handlers
2) Output handlers (log.error.output) replace any existing handlers
3) Filter handlers (log.xxx.filter, log.xxx.nfilter) append to existing handlers,
note log.all.filter is treated as a filter handler, so it will NOT replace existing ones
*/
package logger

View File

@@ -1,82 +1,93 @@
package logger
import (
"fmt"
"io"
"os"
colorable "github.com/mattn/go-colorable"
"github.com/revel/log15"
"gopkg.in/natefinch/lumberjack.v2"
)
type LevelFilterHandler struct {
Level LogLevel
h LogHandler
}
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl == l15Lvl
}, h)
return &LevelFilterHandler{lvl, h}
}
// The implementation of the Log
func (h LevelFilterHandler) Log(r *Record) error {
if r.Level == h.Level {
return h.h.Log(r)
}
return nil
}
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl <= l15Lvl
return FilterHandler(func(r *Record) (pass bool) {
return r.Level <= lvl
}, h)
}
// Filters out records which match the level
// Uses the `log15.FilterHandler` to perform this task
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl != l15Lvl
return FilterHandler(func(r *Record) (pass bool) {
return r.Level != lvl
}, h)
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFileHandler` to perform this task
func CallerFileHandler(h LogHandler) LogHandler {
return log15.CallerFileHandler(h)
return FuncHandler(func(r *Record) error {
r.Context.Add("caller", fmt.Sprint(r.Call))
return h.Log(r)
})
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFuncHandler` to perform this task
func CallerFuncHandler(h LogHandler) LogHandler {
return log15.CallerFuncHandler(h)
return CallerFuncHandler(h)
}
// Filters out records which match the key value pair
// Uses the `log15.MatchFilterHandler` to perform this task
func MatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.MatchFilterHandler(key, value, h)
return MatchFilterHandler(key, value, h)
}
// MatchFilterHandler returns a Handler that only writes records
// to the wrapped Handler if the given key in the logged
// context matches the value. For example, to only log records
// from your ui package:
//
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
//
func MatchFilterHandler(key string, value interface{}, h LogHandler) LogHandler {
return FilterHandler(func(r *Record) (pass bool) {
return r.Context[key] == value
}, h)
}
// If match then A handler is called otherwise B handler is called
func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
if r.Ctx[i+1] == value {
if a != nil {
return a.Log(r)
}
return nil
}
}
}
if b != nil {
return FuncHandler(func(r *Record) error {
if r.Context[key] == value {
return a.Log(r)
} else if b != nil {
return b.Log(r)
}
return nil
})
}
// The nil handler is used if logging for a specific request needs to be turned off
func NilHandler() LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
return FuncHandler(func(r *Record) error {
return nil
})
}
@@ -93,80 +104,83 @@ func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandle
// Rather then chaining multiple filter handlers, process all here
func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
checkMap := map[string]bool{}
// Copy the map to a bool
for i := 0; i < len(r.Ctx); i += 2 {
if value, found := matchMap[r.Ctx[i].(string)]; found && value == r.Ctx[i+1] {
checkMap[r.Ctx[i].(string)] = true
return FuncHandler(func(r *Record) error {
matchCount := 0
for k, v := range matchMap {
value, found := r.Context[k]
if !found {
return nil
}
// Test for two failure cases
if value == v && inverse || value != v && !inverse {
return nil
} else {
matchCount++
}
}
if len(checkMap) == len(matchMap) {
if !inverse {
return a.Log(r)
}
} else if inverse {
return a.Log(r)
if matchCount != len(matchMap) {
return nil
}
return nil
return a.Log(r)
})
}
// Filters out records which do not match the key value pair
// Uses the `log15.FilterHandler` to perform this task
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
switch key {
case r.KeyNames.Lvl:
return r.Lvl != value
case r.KeyNames.Time:
return r.Time != value
case r.KeyNames.Msg:
return r.Msg != value
}
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
return r.Ctx[i+1] == value
}
}
return true
return FilterHandler(func(r *Record) (pass bool) {
return r.Context[key] != value
}, h)
}
func MultiHandler(hs ...LogHandler) LogHandler {
// Convert the log handlers to log15.Handlers
handlers := []log15.Handler{}
for _, h := range hs {
if h != nil {
handlers = append(handlers, h)
return FuncHandler(func(r *Record) error {
for _, h := range hs {
// what to do about failures?
h.Log(r)
}
}
return log15.MultiHandler(handlers...)
return nil
})
}
// Outputs the records to the passed in stream
// Uses the `log15.StreamHandler` to perform this task
// StreamHandler writes log records to an io.Writer
// with the given format. StreamHandler can be used
// to easily begin writing log records to other
// outputs.
//
// StreamHandler wraps itself with LazyHandler and SyncHandler
// to evaluate Lazy objects and perform safe concurrent writes.
func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
return log15.StreamHandler(wr, fmtr)
h := FuncHandler(func(r *Record) error {
_, err := wr.Write(fmtr.Format(r))
return err
})
return LazyHandler(SyncHandler(h))
}
// Filter handler, this is the only
// Uses the `log15.FilterHandler` to perform this task
func FilterHandler(fn func(r *log15.Record) bool, h LogHandler) LogHandler {
return log15.FilterHandler(fn, h)
// Filter handler
func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler {
return FuncHandler(func(r *Record) error {
if fn(r) {
return h.Log(r)
}
return nil
})
}
// List log handler handles a list of LogHandlers
type ListLogHandler struct {
handlers []LogHandler
}
// Create a new list of log handlers
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
return ll
}
func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
// Log the record
func (ll *ListLogHandler) Log(r *Record) (err error) {
for _, handler := range ll.handlers {
if err == nil {
err = handler.Log(r)
@@ -176,11 +190,15 @@ func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
}
return
}
// Add another log handler
func (ll *ListLogHandler) Add(h LogHandler) {
if h != nil {
ll.handlers = append(ll.handlers, h)
}
}
// Remove a log handler
func (ll *ListLogHandler) Del(h LogHandler) {
if h != nil {
for i, handler := range ll.handlers {
@@ -190,161 +208,3 @@ func (ll *ListLogHandler) Del(h LogHandler) {
}
}
}
type CompositeMultiHandler struct {
DebugHandler LogHandler
InfoHandler LogHandler
WarnHandler LogHandler
ErrorHandler LogHandler
CriticalHandler LogHandler
}
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{}
return cw, cw
}
func (h *CompositeMultiHandler) Log(r *log15.Record) (err error) {
var handler LogHandler
switch r.Lvl {
case log15.LvlInfo:
handler = h.InfoHandler
case log15.LvlDebug:
handler = h.DebugHandler
case log15.LvlWarn:
handler = h.WarnHandler
case log15.LvlError:
handler = h.ErrorHandler
case log15.LvlCrit:
handler = h.CriticalHandler
}
// Embed the caller function in the context
if handler != nil {
handler.Log(r)
}
return
}
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
if handler == nil {
// Ignore empty handler
return
}
source := &h.DebugHandler
switch level {
case LvlDebug:
source = &h.DebugHandler
case LvlInfo:
source = &h.InfoHandler
case LvlWarn:
source = &h.WarnHandler
case LvlError:
source = &h.ErrorHandler
case LvlCrit:
source = &h.CriticalHandler
}
if !replace && *source != nil {
// If this already was a list add a new logger to it
if ll, found := (*source).(*ListLogHandler); found {
ll.Add(handler)
} else {
*source = NewListLogHandler(*source, handler)
}
} else {
*source = handler
}
}
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 {
options.Levels = LvlAllList
}
// Set all levels
for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
}
}
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, log15.JsonFormatEx(
options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true),
)))
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetJson(writer, options)
}
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
streamHandler := StreamHandler(
writer,
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
if os.Stdout == writer {
streamHandler = StreamHandler(
colorable.NewColorableStdout(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
} else if os.Stderr == writer {
streamHandler = StreamHandler(
colorable.NewColorableStderr(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
}
handler := CallerFileHandler(streamHandler)
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetTerminal(writer, options)
}
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
if len(levels) == 0 {
levels = LvlAllList
}
for _, level := range levels {
switch level {
case LvlDebug:
h.DebugHandler = nil
case LvlInfo:
h.InfoHandler = nil
case LvlWarn:
h.WarnHandler = nil
case LvlError:
h.ErrorHandler = nil
case LvlCrit:
h.CriticalHandler = nil
}
}
}

189
logger/init.go Normal file
View File

@@ -0,0 +1,189 @@
package logger
// Get all handlers based on the Config (if available)
import (
"fmt"
"github.com/revel/config"
"log"
"os"
"path/filepath"
"strings"
)
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
// If running in test mode suppress anything that is not an error
if config != nil && config.BoolDefault(TEST_MODE_FLAG, false) {
// Preconfigure all the options
config.SetOption("log.info.output", "none")
config.SetOption("log.debug.output", "none")
config.SetOption("log.warn.output", "none")
config.SetOption("log.error.output", "stderr")
config.SetOption("log.crit.output", "stderr")
}
// If the configuration has an all option we can skip some
c, _ = NewCompositeMultiHandler()
// Filters are assigned first, non filtered items override filters
if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) {
initAllLog(c, basePath, config)
}
initLogLevels(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) {
initFilterLog(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initRequestLog(c, basePath, config)
}
return c
}
// Init the log.all configuration options
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
if output, found := config.String("log.all.output"); found {
// Set all output for the specified handler
if extraLogFlag {
log.Printf("Adding standard handler for levels to >%s< ", output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
}
}
}
// Init the filter options
// log.all.filter ....
// log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
for _, logFilter := range logFilterList {
// Init for all filters
for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
for _, option := range optionList {
splitOptions := strings.Split(option, ".")
keyMap := map[string]interface{}{}
for x := 3; x < len(splitOptions); x += 2 {
keyMap[splitOptions[x]] = splitOptions[x+1]
}
phandler := logFilter.parentHandler(keyMap)
if extraLogFlag {
log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
fmt.Printf("Adding key map handler %s %s output %s matching %#v\n", option, name, config.StringDefault(option, ""), keyMap)
}
if name == "all" {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
} else {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
}
}
}
}
}
}
// Init the log.error, log.warn etc configuration options
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
for _, name := range []string{"debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
if config != nil {
extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
output, found := config.String("log." + name + ".output")
if found {
if extraLogFlag {
log.Printf("Adding standard handler %s output %s", name, output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
// Gets the list of options with said prefix
} else {
initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
}
}
// Init the request log options
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
// Request logging to a separate output handler
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
// context with the word "section=requestlog" to that handler.
// Note if request logging is not enabled the MatchAbHandler will not be added and the
// request log messages will be sent out the INFO handler
outputRequest := "stdout"
if config != nil {
outputRequest = config.StringDefault("log.request.output", "")
}
oldInfo := c.InfoHandler
c.InfoHandler = nil
if outputRequest != "" {
initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
}
if c.InfoHandler != nil || oldInfo != nil {
if c.InfoHandler == nil {
c.InfoHandler = oldInfo
} else {
c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
}
}
}
// Returns a handler for the level using the output string
// Accept formats for output string are
// LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
if options.Ctx != nil {
options.SetExtendedOptions(
"noColor", !options.Ctx.BoolDefault("log.colorize", true),
"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
"maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
"maxAge", options.Ctx.IntDefault("log.maxage", 14),
"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
"compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
)
}
output = strings.TrimSpace(output)
if funcHandler, found := LogFunctionMap[output]; found {
funcHandler(c, options)
} else {
switch output {
case "":
fallthrough
case "off":
// No handler, discard data
default:
// Write to file specified
if !filepath.IsAbs(output) {
output = filepath.Join(basePath, output)
}
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
log.Panic(err)
}
if strings.HasSuffix(output, "json") {
c.SetJsonFile(output, options)
} else {
// Override defaults for a terminal file
options.SetExtendedOptions("noColor", true)
options.SetExtendedOptions("smallDate", false)
c.SetTerminalFile(output, options)
}
}
}
return
}

273
logger/init_test.go Normal file
View File

@@ -0,0 +1,273 @@
// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package logger_test
import (
"github.com/revel/config"
"github.com/revel/revel/logger"
"github.com/stretchr/testify/assert"
"os"
"strings"
"testing"
)
type (
// A counter for the tester
testCounter struct {
debug, info, warn, error, critical int
}
// The data to tes
testData struct {
config []string
result testResult
tc *testCounter
}
// The test result
testResult struct {
debug, info, warn, error, critical int
}
)
// Single test cases
var singleCases = []testData{
{config: []string{"log.crit.output"},
result: testResult{0, 0, 0, 0, 1}},
{config: []string{"log.error.output"},
result: testResult{0, 0, 0, 1, 1}},
{config: []string{"log.warn.output"},
result: testResult{0, 0, 1, 0, 0}},
{config: []string{"log.info.output"},
result: testResult{0, 1, 0, 0, 0}},
{config: []string{"log.debug.output"},
result: testResult{1, 0, 0, 0, 0}},
}
// Test singles
func TestSingleCases(t *testing.T) {
rootLog := logger.New()
for _, testCase := range singleCases {
testCase.logTest(rootLog, t)
testCase.validate(t)
}
}
// Filter test cases
var filterCases = []testData{
{config: []string{"log.crit.filter.module.app"},
result: testResult{0, 0, 0, 0, 1}},
{config: []string{"log.crit.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.error.filter.module.app"},
result: testResult{0, 0, 0, 1, 1}},
{config: []string{"log.error.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.warn.filter.module.app"},
result: testResult{0, 0, 1, 0, 0}},
{config: []string{"log.warn.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.info.filter.module.app"},
result: testResult{0, 1, 0, 0, 0}},
{config: []string{"log.info.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.debug.filter.module.app"},
result: testResult{1, 0, 0, 0, 0}},
{config: []string{"log.debug.filter.module.appa"},
result: testResult{0, 0, 0, 0, 0}},
}
// Filter test
func TestFilterCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range filterCases {
testCase.logTest(rootLog, t)
testCase.validate(t)
}
}
// Inverse test cases
var nfilterCases = []testData{
{config: []string{"log.crit.nfilter.module.appa"},
result: testResult{0, 0, 0, 0, 1}},
{config: []string{"log.crit.nfilter.modules.appa"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.crit.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.error.nfilter.module.appa"}, // Special case, when error is not nill critical inherits from error
result: testResult{0, 0, 0, 1, 1}},
{config: []string{"log.error.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.warn.nfilter.module.appa"},
result: testResult{0, 0, 1, 0, 0}},
{config: []string{"log.warn.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.info.nfilter.module.appa"},
result: testResult{0, 1, 0, 0, 0}},
{config: []string{"log.info.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0}},
{config: []string{"log.debug.nfilter.module.appa"},
result: testResult{1, 0, 0, 0, 0}},
{config: []string{"log.debug.nfilter.module.app"},
result: testResult{0, 0, 0, 0, 0}},
}
// Inverse test
func TestNotFilterCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range nfilterCases {
testCase.logTest(rootLog, t)
testCase.validate(t)
}
}
// off test cases
var offCases = []testData{
{config: []string{"log.all.output", "log.error.output=off"},
result: testResult{1, 1, 1, 0, 1}},
}
// Off test
func TestOffCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range offCases {
testCase.logTest(rootLog, t)
testCase.validate(t)
}
}
// Duplicate test cases
var duplicateCases = []testData{
{config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"},
result: testResult{1, 1, 1, 2, 1}},
}
// test duplicate cases
func TestDuplicateCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range duplicateCases {
testCase.logTest(rootLog, t)
testCase.validate(t)
}
}
// Contradicting cases
var contradictCases = []testData{
{config: []string{"log.all.output", "log.error.output=off", "log.all.output"},
result: testResult{1, 1, 1, 0, 1}},
{config: []string{"log.all.output", "log.error.output=off", "log.debug.filter.module.app"},
result: testResult{2, 1, 1, 0, 1}},
{config: []string{"log.all.filter.module.app", "log.info.output=off", "log.info.filter.module.app"},
result: testResult{1, 2, 1, 1, 1}},
{config: []string{"log.all.output", "log.info.output=off", "log.info.filter.module.app"},
result: testResult{1, 1, 1, 1, 1}},
}
// Contradiction test
func TestContradictCases(t *testing.T) {
rootLog := logger.New("module", "app")
for _, testCase := range contradictCases {
testCase.logTest(rootLog, t)
testCase.validate(t)
}
}
// All test cases
var allCases = []testData{
{config: []string{"log.all.filter.module.app"},
result: testResult{1, 1, 1, 1, 1}},
{config: []string{"log.all.output"},
result: testResult{2, 2, 2, 2, 2}},
}
// All tests
func TestAllCases(t *testing.T) {
rootLog := logger.New("module", "app")
for i, testCase := range allCases {
testCase.logTest(rootLog, t)
allCases[i] = testCase
}
rootLog = logger.New()
for i, testCase := range allCases {
testCase.logTest(rootLog, t)
allCases[i] = testCase
}
for _, testCase := range allCases {
testCase.validate(t)
}
}
func (c *testCounter) Log(r *logger.Record) error {
switch r.Level {
case logger.LvlDebug:
c.debug++
case logger.LvlInfo:
c.info++
case logger.LvlWarn:
c.warn++
case logger.LvlError:
c.error++
case logger.LvlCrit:
c.critical++
default:
panic("Unknown log level")
}
return nil
}
func (td *testData) logTest(rootLog logger.MultiLogger, t *testing.T) {
if td.tc == nil {
td.tc = &testCounter{}
counterInit(td.tc)
}
newContext := config.NewContext()
for _, i := range td.config {
iout := strings.Split(i, "=")
if len(iout) > 1 {
newContext.SetOption(iout[0], iout[1])
} else {
newContext.SetOption(i, "test")
}
}
newContext.SetOption("specialUseFlag", "true")
handler := logger.InitializeFromConfig("test", newContext)
rootLog.SetHandler(handler)
td.runLogTest(rootLog)
}
func (td *testData) runLogTest(log logger.MultiLogger) {
log.Debug("test")
log.Info("test")
log.Warn("test")
log.Error("test")
log.Crit("test")
}
func (td *testData) validate(t *testing.T) {
t.Logf("Test %#v expected %#v", td.tc, td.result)
assert.Equal(t, td.result.debug, td.tc.debug, "Debug failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.info, td.tc.info, "Info failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.warn, td.tc.warn, "Warn failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.error, td.tc.error, "Error failed "+strings.Join(td.config, " "))
assert.Equal(t, td.result.critical, td.tc.critical, "Critical failed "+strings.Join(td.config, " "))
}
// Add test to the function map
func counterInit(tc *testCounter) {
logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) {
// Output to the test log and the stdout
outHandler := logger.LogHandler(
logger.NewListLogHandler(tc,
logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true))),
)
if logOptions.HandlerWrap != nil {
outHandler = logOptions.HandlerWrap.SetChild(outHandler)
}
c.SetHandlers(outHandler, logOptions)
}
}

View File

@@ -1,656 +0,0 @@
package logger
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
import (
"os"
"fmt"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"sync"
"go.uber.org/zap/buffer"
"time"
"encoding/base64"
"unicode/utf8"
"encoding/json"
"math"
"io"
)
type (
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
// The encoders job is to encode the
SetHandler(h LogHandler)
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
Debugf(msg string, params ...interface{})
Info(msg string, ctx ...interface{})
Infof(msg string, params ...interface{})
Warn(msg string, ctx ...interface{})
Warnf(msg string, params ...interface{})
Error(msg string, ctx ...interface{})
Errorf(msg string, params ...interface{})
Crit(msg string, ctx ...interface{})
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
Fatal(msg string, ctx ...interface{})
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
Panic(msg string, ctx ...interface{})
Panicf(msg string, params ...interface{})
}
// The log han
LogHandler interface {
Encode(Record) ([]byte, error)
GetLevel() Level
GetWriter() io.Writer
}
// The Record
Record struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
Context []Field
}
// The fields passed in
Field interface {
GetKey() string
GetValueAsString() string
GetValue() interface{}
}
EntryCaller interface {
IsDefined() bool
GetPC() uintptr
GetFile() string
GetLine() int
}
// Called only if the logger needs
ResolveLaterLogger func() interface{}
FieldType int
Level int
)
type (
zapLogger struct {
logger *zap.SugaredLogger
coreList []*zapcore.Core
}
zapField struct {
Key string
Type FieldType
Integer int64
String string
Interface interface{}
}
zapEntryCaller struct {
Defined bool
PC uintptr
File string
Line int
}
zapEncoder struct {
lh LogHandler
}
)
func newLogger(addCaller bool) MultiLogger {
logger := zap.New(nil).WithOptions(zap.AddCaller())
l := &zapLogger{logger:logger.Sugar()}
return l
}
// It is up to the handler to determine the synchronization to the output
// streams
func (z *zapLogger) SetHandler(lh LogHandler) {
// Swap out the logger when a new handler is attached
encoder := &zapEncoder{lh}
levelHandler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.Level(lh.GetLevel())
})
logger := zap.New(zapcore.NewCore(encoder, nil, levelHandler)).WithOptions(zap.AddCaller())
Logger.With("foo","bar").Desugar().Core()
}
var Logger *zap.SugaredLogger
func InitLogger(logLevel zapcore.Level) {
config :=zap.NewDevelopmentEncoderConfig()
config.EncodeLevel = zapcore.CapitalColorLevelEncoder
consoleEncoder := NewConsoleEncoder(config)
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= logLevel
})
consoleDebugging := zapcore.Lock(os.Stdout)
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
)
logger := zap.New(core).WithOptions(zap.AddCaller())
Logger = logger.Sugar()
}
type LoggedError struct{ error }
func NewLoggedError(err error) *LoggedError {
return &LoggedError{err}
}
func Errorf(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(os.Stderr, format, args...)
panic(format) // Panic instead of os.Exit so that deferred will run.
}
// This is all for the Console logger - a little wordy but it works
var _sliceEncoderPool = sync.Pool{
New: func() interface{} {
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
},
}
func getSliceEncoder() *sliceArrayEncoder {
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
}
func putSliceEncoder(e *sliceArrayEncoder) {
e.elems = e.elems[:0]
_sliceEncoderPool.Put(e)
}
type consoleEncoder struct {
*zapcore.EncoderConfig
openNamespaces int
buf *buffer.Buffer
reflectBuf *buffer.Buffer
reflectEnc *json.Encoder
}
var (
_pool = buffer.NewPool()
// Get retrieves a buffer from the pool, creating one if necessary.
Get = _pool.Get
)
// NewConsoleEncoder creates an encoder whose output is designed for human -
// rather than machine - consumption. It serializes the core log entry data
// (message, level, timestamp, etc.) in a plain-text format and leaves the
// structured context as JSON.
//
// Note that although the console encoder doesn't use the keys specified in the
// encoder configuration, it will omit any element whose key is set to the empty
// string.
func NewConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder {
ec := &consoleEncoder{buf : Get(), reflectBuf: Get()}
ec.EncoderConfig = &cfg
return ec
}
func (c consoleEncoder) Clone() zapcore.Encoder {
return &consoleEncoder{buf : Get(), reflectBuf: Get()}
}
func (c consoleEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := Get()
var color = 0
switch ent.Level {
case zap.PanicLevel:
// Magenta
color = 35
case zap.ErrorLevel:
// Red
color = 31
case zap.WarnLevel:
// Yellow
color = 33
case zap.InfoLevel:
// Green
color = 32
case zap.DebugLevel:
// Cyan
color = 36
}
// We don't want the entry's metadata to be quoted and escaped (if it's
// encoded as strings), which means that we can't use the JSON encoder. The
// simplest option is to use the memory encoder and fmt.Fprint.
//
// If this ever becomes a performance bottleneck, we can implement
// ArrayEncoder for our plain-text format.
arr := getSliceEncoder()
if c.LevelKey != "" && c.EncodeLevel != nil {
arr.AppendString(fmt.Sprintf("\x1b[%dm%-5s\x1b[0m",color,ent.Level.CapitalString()))
}
if ent.LoggerName != "" && c.NameKey != "" {
nameEncoder := c.EncodeName
if nameEncoder == nil {
// Fall back to FullNameEncoder for backward compatibility.
nameEncoder = zapcore.FullNameEncoder
}
nameEncoder(ent.LoggerName, arr)
}
if c.TimeKey != "" && c.EncodeTime != nil {
arr.AppendString(ent.Time.Format("15:04:05"))
}
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil {
c.EncodeCaller(ent.Caller, arr)
}
for i := range arr.elems {
if i > 0 {
line.AppendByte(' ')
}
fmt.Fprint(line, arr.elems[i])
}
putSliceEncoder(arr)
// Add the message itself.
if c.MessageKey != "" {
c.addTabIfNecessary(line)
line.AppendString(ent.Message)
}
// Add any structured context.
c.writeContext(line, fields)
// If there's no stacktrace key, honor that; this allows users to force
// single-line output.
if ent.Stack != "" && c.StacktraceKey != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
if c.LineEnding != "" {
line.AppendString(c.LineEnding)
} else {
line.AppendString(zapcore.DefaultLineEnding)
}
return line, nil
}
func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []zapcore.Field) {
context := c.Clone().(*consoleEncoder)
defer context.buf.Free()
//
addFields(context, extra)
context.closeOpenNamespaces()
if context.buf.Len() == 0 {
return
}
//
line.Write(context.buf.Bytes())
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)
}
}
func (c consoleEncoder) addTabIfNecessary(line *buffer.Buffer) {
if line.Len() > 0 {
line.AppendByte('\t')
}
}
func (enc *consoleEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error {
enc.addKey(key)
return enc.AppendArray(arr)
}
func (enc *consoleEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
enc.addKey(key)
return enc.AppendObject(obj)
}
func (enc *consoleEncoder) AddBinary(key string, val []byte) {
enc.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (enc *consoleEncoder) AddByteString(key string, val []byte) {
enc.addKey(key)
enc.AppendByteString(val)
}
func (enc *consoleEncoder) AddBool(key string, val bool) {
enc.addKey(key)
enc.AppendBool(val)
}
func (enc *consoleEncoder) AddComplex128(key string, val complex128) {
enc.addKey(key)
enc.AppendComplex128(val)
}
func (enc *consoleEncoder) AddDuration(key string, val time.Duration) {
enc.addKey(key)
enc.AppendDuration(val)
}
func (enc *consoleEncoder) AddFloat64(key string, val float64) {
enc.addKey(key)
enc.AppendFloat64(val)
}
func (enc *consoleEncoder) AddInt64(key string, val int64) {
enc.addKey(key)
enc.AppendInt64(val)
}
func (enc *consoleEncoder) AddReflected(key string, obj interface{}) error {
enc.resetReflectBuf()
err := enc.reflectEnc.Encode(obj)
if err != nil {
return err
}
enc.reflectBuf.TrimNewline()
enc.addKey(key)
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
return err
}
func (enc *consoleEncoder) OpenNamespace(key string) {
enc.addKey(key)
enc.buf.AppendByte('{')
enc.openNamespaces++
}
func (enc *consoleEncoder) AddString(key, val string) {
enc.addKey(key)
enc.AppendString(val)
}
func (enc *consoleEncoder) AddTime(key string, val time.Time) {
enc.addKey(key)
enc.AppendTime(val)
}
func (enc *consoleEncoder) AddUint64(key string, val uint64) {
enc.addKey(key)
enc.AppendUint64(val)
}
func (enc *consoleEncoder) addKey(key string) {
// Print key in different color
enc.buf.AppendString(fmt.Sprintf(" \x1b[%dm%s\x1b[0m",36,key))
enc.buf.AppendByte('=')
}
func (enc *consoleEncoder) AppendArray(arr zapcore.ArrayMarshaler) error {
enc.buf.AppendByte('[')
err := arr.MarshalLogArray(enc)
enc.buf.AppendByte(']')
return err
}
func (enc *consoleEncoder) AppendObject(obj zapcore.ObjectMarshaler) error {
enc.buf.AppendByte('{')
err := obj.MarshalLogObject(enc)
enc.buf.AppendByte('}')
return err
}
func (enc *consoleEncoder) AppendBool(val bool) {
enc.buf.AppendBool(val)
}
func (enc *consoleEncoder) AppendByteString(val []byte) {
enc.buf.AppendByte('"')
enc.safeAddByteString(val)
enc.buf.AppendByte('"')
}
func (enc *consoleEncoder) AppendComplex128(val complex128) {
// Cast to a platform-independent, fixed-size type.
r, i := float64(real(val)), float64(imag(val))
enc.buf.AppendByte('"')
// Because we're always in a quoted string, we can use strconv without
// special-casing NaN and +/-Inf.
enc.buf.AppendFloat(r, 64)
enc.buf.AppendByte('+')
enc.buf.AppendFloat(i, 64)
enc.buf.AppendByte('i')
enc.buf.AppendByte('"')
}
func (enc *consoleEncoder) AppendDuration(val time.Duration) {
cur := enc.buf.Len()
enc.EncodeDuration(val, enc)
if cur == enc.buf.Len() {
// User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep
// JSON valid.
enc.AppendInt64(int64(val))
}
}
func (enc *consoleEncoder) AppendInt64(val int64) {
enc.buf.AppendInt(val)
}
func (enc *consoleEncoder) resetReflectBuf() {
if enc.reflectBuf == nil {
enc.reflectBuf = Get()
enc.reflectEnc = json.NewEncoder(enc.reflectBuf)
} else {
enc.reflectBuf.Reset()
}
}
func (enc *consoleEncoder) AppendReflected(val interface{}) error {
enc.resetReflectBuf()
err := enc.reflectEnc.Encode(val)
if err != nil {
return err
}
enc.reflectBuf.TrimNewline()
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
return err
}
func (enc *consoleEncoder) AppendString(val string) {
enc.safeAddString(val)
}
func (enc *consoleEncoder) AppendTime(val time.Time) {
cur := enc.buf.Len()
enc.EncodeTime(val, enc)
if cur == enc.buf.Len() {
// User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep
// output JSON valid.
enc.AppendInt64(val.UnixNano())
}
}
func (enc *consoleEncoder) AppendUint64(val uint64) {
enc.buf.AppendUint(val)
}
func (enc *consoleEncoder) appendFloat(val float64, bitSize int) {
switch {
case math.IsNaN(val):
enc.buf.AppendString(`"NaN"`)
case math.IsInf(val, 1):
enc.buf.AppendString(`"+Inf"`)
case math.IsInf(val, -1):
enc.buf.AppendString(`"-Inf"`)
default:
enc.buf.AppendFloat(val, bitSize)
}
}
// safeAddString JSON-escapes a string and appends it to the internal buffer.
// Unlike the standard library's encoder, it doesn't attempt to protect the
// user from browser vulnerabilities or JSONP-related problems.
func (enc *consoleEncoder) safeAddString(s string) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.AppendString(s[i : i+size])
i += size
}
}
// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte.
func (enc *consoleEncoder) safeAddByteString(s []byte) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRune(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.Write(s[i : i+size])
i += size
}
}
// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte.
func (enc *consoleEncoder) tryAddRuneSelf(b byte) bool {
if b >= utf8.RuneSelf {
return false
}
if 0x20 <= b && b != '\\' && b != '"' {
enc.buf.AppendByte(b)
return true
}
switch b {
case '\\', '"':
enc.buf.AppendByte('\\')
enc.buf.AppendByte(b)
case '\n':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('n')
case '\r':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('r')
case '\t':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('t')
default:
// Encode bytes < 0x20, except for the escape sequences above.
enc.buf.AppendString(`\u00`)
enc.buf.AppendByte(_hex[b>>4])
enc.buf.AppendByte(_hex[b&0xF])
}
return true
}
func (enc *consoleEncoder) closeOpenNamespaces() {
for i := 0; i < enc.openNamespaces; i++ {
enc.buf.AppendByte('}')
}
}
func (enc *consoleEncoder) tryAddRuneError(r rune, size int) bool {
if r == utf8.RuneError && size == 1 {
enc.buf.AppendString(`\ufffd`)
return true
}
return false
}
func (enc *consoleEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) }
func (enc *consoleEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) }
func (enc *consoleEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) }
func (enc *consoleEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) }
func (enc *consoleEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) }
func (enc *consoleEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) }
const _hex = "0123456789abcdef"
type sliceArrayEncoder struct {
elems []interface{}
}
func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {
enc := &sliceArrayEncoder{}
err := v.MarshalLogArray(enc)
s.elems = append(s.elems, enc.elems)
return err
}
func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {
m := zapcore.NewMapObjectEncoder()
err := v.MarshalLogObject(m)
s.elems = append(s.elems, m.Fields)
return err
}
func (s *sliceArrayEncoder) AppendReflected(v interface{}) error {
s.elems = append(s.elems, v)
return nil
}
func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) }

View File

@@ -0,0 +1,37 @@
package logger
import (
"os"
)
// The log function map can be added to, so that you can specify your own logging mechanism
// it has defaults for off, stdout, stderr
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
// Only drop the results if there is a parent handler defined
if logOptions.HandlerWrap != nil {
for _, l := range logOptions.Levels {
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
}
} else {
// Clear existing handler
c.SetHandlers(NilHandler(), logOptions)
}
},
// Do nothing - set the logger off
"": func(*CompositeMultiHandler, *LogOptions) {},
// Set the levels to stdout, replace existing
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
if logOptions.Ctx != nil {
logOptions.SetExtendedOptions(
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
}
c.SetTerminal(os.Stdout, logOptions)
},
// Set the levels to stderr output to terminal
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
c.SetTerminal(os.Stderr, logOptions)
},
}

View File

@@ -3,9 +3,7 @@ package logger
import (
"fmt"
"github.com/revel/config"
"github.com/revel/log15"
"log"
"os"
"time"
)
// The LogHandler defines the interface to handle the log records
@@ -13,51 +11,82 @@ type (
// The Multilogger reduces the number of exposed defined logging variables,
// and allows the output to be easily refined
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
//// SetHandler updates the logger to write records to the specified handler.
// SetHandler updates the logger to write records to the specified handler.
SetHandler(h LogHandler)
// Set the stack depth for the logger
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Debugf(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Info(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Infof(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Warn(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Warnf(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Error(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Errorf(msg string, params ...interface{})
// Log a message at the given level with context key/value pairs
Crit(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
// Log a message at the given level with context key/value pairs and exits
Fatal(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters and exits
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
// Log a message at the given level with context key/value pairs and panics
Panic(msg string, ctx ...interface{})
// Log a message at the given level formatting message with the parameters and panics
Panicf(msg string, params ...interface{})
}
// The log handler interface
LogHandler interface {
log15.Handler
Log(*Record) error
//log15.Handler
}
// The log stack handler interface
LogStackHandler interface {
LogHandler
GetStack() int
}
// The log handler interface which has child logs
ParentLogHandler interface {
SetChild(handler LogHandler) LogHandler
}
// The log format interface
LogFormat interface {
log15.Format
Format(r *Record) []byte
}
LogLevel log15.Lvl
RevelLogger struct {
log15.Logger
}
// The log level type
LogLevel int
// Used for the callback to LogFunctionMap
LogOptions struct {
@@ -67,125 +96,65 @@ type (
Levels []LogLevel
ExtendedOptions map[string]interface{}
}
// The log record
Record struct {
Message string // The message
Time time.Time // The time
Level LogLevel //The level
Call CallStack // The call stack if built
Context ContextMap // The context
}
// The lazy structure to implement a function to be invoked only if needed
Lazy struct {
Fn interface{} // the function
}
// Currently the only requirement for the callstack is to support the Formatter method
// which stack.Call does so we use that
CallStack interface {
fmt.Formatter // Requirement
}
)
// FormatFunc returns a new Format object which uses
// the given function to perform record formatting.
func FormatFunc(f func(*Record) []byte) LogFormat {
return formatFunc(f)
}
type formatFunc func(*Record) []byte
func (f formatFunc) Format(r *Record) []byte {
return f(r)
}
func NewRecord(message string, level LogLevel) *Record {
return &Record{Message: message, Context: ContextMap{}, Level: level}
}
const (
LvlDebug = LogLevel(log15.LvlDebug)
LvlInfo = LogLevel(log15.LvlInfo)
LvlWarn = LogLevel(log15.LvlWarn)
LvlError = LogLevel(log15.LvlError)
LvlCrit = LogLevel(log15.LvlCrit)
LvlCrit LogLevel = iota // Critical
LvlError // Error
LvlWarn // Warning
LvlInfo // Information
LvlDebug // Debug
)
// A list of all the log levels
var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
// The log function map can be added to, so that you can specify your own logging mechanism
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
// Only drop the results if there is a parent handler defined
if logOptions.HandlerWrap != nil {
for _, l := range logOptions.Levels {
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
}
}
},
// Do nothing - set the logger off
"": func(*CompositeMultiHandler, *LogOptions) {},
// Set the levels to stdout, replace existing
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
if logOptions.Ctx != nil {
logOptions.SetExtendedOptions(
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
}
c.SetTerminal(os.Stdout, logOptions)
},
// Set the levels to stderr output to terminal
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
c.SetTerminal(os.Stderr, logOptions)
},
}
// Set the systems default logger
// Default logs will be captured and handled by revel at level info
func SetDefaultLog(fromLog MultiLogger) {
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
// No need to show date and time, that will be logged with revel
log.SetFlags(0)
}
// Formatted debug call
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
rl.Debug(fmt.Sprintf(msg, param...))
}
// Formatted info call
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
rl.Info(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
rl.Warn(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
rl.Error(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
os.Exit(1)
}
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
panic(msg)
}
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
os.Exit(1)
}
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
panic(msg)
}
// Override log15 method
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
return old
}
// Set the stack level to check for the caller
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
rl.Logger.SetStackDepth(amount) // Ignore the logger returned
return rl
}
// Create a new logger
func New(ctx ...interface{}) MultiLogger {
r := &RevelLogger{Logger: log15.New(ctx...)}
r.SetStackDepth(1)
return r
}
// Set the handler in the Logger
func (rl *RevelLogger) SetHandler(h LogHandler) {
rl.Logger.SetHandler(h)
}
// Implements the ParentLogHandler
type parentLogHandler struct {
setChild func(handler LogHandler) LogHandler
}
// Create a new parent log handler
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
return &parentLogHandler{callBack}
}
// Sets the child of the log handler
func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
return p.setChild(child)
}
@@ -208,18 +177,24 @@ func (l *LogOptions) SetExtendedOptions(options ...interface{}) {
l.ExtendedOptions[options[x].(string)] = options[x+1]
}
}
// Gets a string option with default
func (l *LogOptions) GetStringDefault(option, value string) string {
if v, found := l.ExtendedOptions[option]; found {
return v.(string)
}
return value
}
// Gets an int option with default
func (l *LogOptions) GetIntDefault(option string, value int) int {
if v, found := l.ExtendedOptions[option]; found {
return v.(int)
}
return value
}
// Gets a boolean option with default
func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
if v, found := l.ExtendedOptions[option]; found {
return v.(bool)

142
logger/revel_logger.go Normal file
View File

@@ -0,0 +1,142 @@
package logger
import (
"fmt"
"github.com/revel/log15"
"log"
"os"
)
// This type implements the MultiLogger
type RevelLogger struct {
log15.Logger
}
// Set the systems default logger
// Default logs will be captured and handled by revel at level info
func SetDefaultLog(fromLog MultiLogger) {
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
// No need to show date and time, that will be logged with revel
log.SetFlags(0)
}
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
rl.Debug(fmt.Sprintf(msg, param...))
}
// Print a formatted info message
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
rl.Info(fmt.Sprintf(msg, param...))
}
// Print a formatted warn message
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
rl.Warn(fmt.Sprintf(msg, param...))
}
// Print a formatted error message
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
rl.Error(fmt.Sprintf(msg, param...))
}
// Print a formatted critical message
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
}
// Print a formatted fatal message
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
rl.Fatal(fmt.Sprintf(msg, param...))
}
// Print a formatted panic message
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
rl.Panic(fmt.Sprintf(msg, param...))
}
// Print a critical message and call os.Exit(1)
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
os.Exit(1)
}
// Print a critical message and panic
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
panic(msg)
}
// Override log15 method
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
return old
}
// Set the stack level to check for the caller
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
rl.Logger.SetStackDepth(amount) // Ignore the logger returned
return rl
}
// Create a new logger
func New(ctx ...interface{}) MultiLogger {
r := &RevelLogger{Logger: log15.New(ctx...)}
r.SetStackDepth(1)
return r
}
// Set the handler in the Logger
func (rl *RevelLogger) SetHandler(h LogHandler) {
rl.Logger.SetHandler(callHandler(h.Log))
}
// The function wrapper to implement the callback
type callHandler func(r *Record) error
// Log implementation, reads the record and extracts the details from the log record
// Hiding the implementation.
func (c callHandler) Log(log *log15.Record) error {
ctx := log.Ctx
var ctxMap ContextMap
if len(ctx) > 0 {
ctxMap = make(ContextMap, len(ctx)/2)
for i := 0; i < len(ctx); i += 2 {
v := ctx[i]
key, ok := v.(string)
if !ok {
key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v)
}
var value interface{}
if len(ctx) > i+1 {
value = ctx[i+1]
} else {
value = "LOGGER_VALUE_MISSING"
}
ctxMap[key] = value
}
} else {
ctxMap = make(ContextMap, 0)
}
r := &Record{Message: log.Msg, Context: ctxMap, Time: log.Time, Level: LogLevel(log.Lvl), Call: CallStack(log.Call)}
return c(r)
}
// Internally used contextMap, allows conversion of map to map[string]string
type ContextMap map[string]interface{}
// Convert the context map to be string only values, any non string values are ignored
func (m ContextMap) StringMap() (newMap map[string]string) {
if m != nil {
newMap = map[string]string{}
for key, value := range m {
if svalue, isstring := value.(string); isstring {
newMap[key] = svalue
}
}
}
return
}
func (m ContextMap) Add(key string, value interface{}) {
m[key] = value
}

View File

@@ -2,8 +2,8 @@ package logger
import (
"bytes"
"encoding/json"
"fmt"
"github.com/revel/log15"
"reflect"
"strconv"
"sync"
@@ -19,9 +19,8 @@ const (
)
var (
// Name the log level
toRevel = map[log15.Lvl]string{log15.LvlDebug: "DEBUG",
log15.LvlInfo: "INFO", log15.LvlWarn: "WARN", log15.LvlError: "ERROR", log15.LvlCrit: "CRIT"}
levelString = map[LogLevel]string{LvlDebug: "DEBUG",
LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT"}
)
// Outputs to the terminal in a format like below
@@ -31,53 +30,51 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
if smallDate {
dateFormat = termSmallTimeFormat
}
return log15.FormatFunc(func(r *log15.Record) []byte {
return FormatFunc(func(r *Record) []byte {
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
var color = 0
switch r.Lvl {
case log15.LvlCrit:
switch r.Level {
case LvlCrit:
// Magenta
color = 35
case log15.LvlError:
case LvlError:
// Red
color = 31
case log15.LvlWarn:
case LvlWarn:
// Yellow
color = 33
case log15.LvlInfo:
case LvlInfo:
// Green
color = 32
case log15.LvlDebug:
case LvlDebug:
// Cyan
color = 36
}
b := &bytes.Buffer{}
caller := findInContext("caller", r.Ctx)
module := findInContext("module", r.Ctx)
caller, _ := r.Context["caller"].(string)
module, _ := r.Context["module"].(string)
if noColor == false && color > 0 {
if len(module) > 0 {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
} else {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), caller, r.Msg)
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), caller, r.Message)
}
} else {
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
}
for i := 0; i < len(r.Ctx); i += 2 {
i := 0
for k, v := range r.Context {
if i != 0 {
b.WriteByte(' ')
}
k, ok := r.Ctx[i].(string)
if k == "caller" || k == "fn" || k == "module" {
i++
if k == "module" || k == "caller" {
continue
}
v := formatLogfmtValue(r.Ctx[i+1])
if !ok {
k, v = errorKey, formatLogfmtValue(k)
}
v := formatLogfmtValue(v)
// TODO: we should probably check that all of your key bytes aren't invalid
if noColor == false && color > 0 {
@@ -94,15 +91,6 @@ func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
return b.Bytes()
})
}
func findInContext(key string, ctx []interface{}) string {
for i := 0; i < len(ctx); i += 2 {
k := ctx[i].(string)
if key == k {
return formatLogfmtValue(ctx[i+1])
}
}
return ""
}
// formatValue formats a value for serialization
func formatLogfmtValue(value interface{}) string {
@@ -132,6 +120,8 @@ func formatLogfmtValue(value interface{}) string {
return escapeString(fmt.Sprintf("%+v", value))
}
}
// Format the value in json format
func formatShared(value interface{}) (result interface{}) {
defer func() {
if err := recover(); err != nil {
@@ -158,10 +148,12 @@ func formatShared(value interface{}) (result interface{}) {
}
}
// A reusuable buffer for outputting data
var stringBufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
// Escape the string when needed
func escapeString(s string) string {
needsQuotes := false
needsEscape := false
@@ -204,3 +196,50 @@ func escapeString(s string) string {
stringBufPool.Put(e)
return ret
}
// JsonFormatEx formats log records as JSON objects. If pretty is true,
// records will be pretty-printed. If lineSeparated is true, records
// will be logged with a new line between each record.
func JsonFormatEx(pretty, lineSeparated bool) LogFormat {
jsonMarshal := json.Marshal
if pretty {
jsonMarshal = func(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
}
return FormatFunc(func(r *Record) []byte {
props := make(map[string]interface{})
props["t"] = r.Time
props["lvl"] = levelString[r.Level]
props["msg"] = r.Message
for k, v := range r.Context {
props[k] = formatJsonValue(v)
}
b, err := jsonMarshal(props)
if err != nil {
b, _ = jsonMarshal(map[string]string{
errorKey: err.Error(),
})
return b
}
if lineSeparated {
b = append(b, '\n')
}
return b
})
}
func formatJsonValue(value interface{}) interface{} {
value = formatShared(value)
switch value.(type) {
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
return value
default:
return fmt.Sprintf("%+v", value)
}
}

View File

@@ -1,13 +1,9 @@
package logger
import (
"github.com/revel/config"
"github.com/revel/log15"
"gopkg.in/stack.v0"
"log"
"os"
"path/filepath"
"strings"
)
// Utility package to make existing logging backwards compatible
@@ -20,6 +16,14 @@ var (
}
)
const (
// The test mode flag overrides the default log level and shows only errors
TEST_MODE_FLAG = "testModeFlag"
// The special use flag enables showing messages when the logger is setup
SPECIAL_USE_FLAG = "specialUseFlag"
)
// Returns the logger for the name
func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
switch name {
case "trace": // TODO trace is deprecated, replaced by debug
@@ -40,198 +44,26 @@ func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
}
// Get all handlers based on the Config (if available)
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
// If running in test mode suppress anything that is not an error
if config != nil && config.BoolDefault("testModeFlag", false) {
config.SetOption("log.info.output", "off")
config.SetOption("log.debug.output", "off")
config.SetOption("log.warn.output", "off")
config.SetOption("log.error.output", "stderr")
config.SetOption("log.crit.output", "stderr")
}
// Used by the initFilterLog to handle the filters
var logFilterList = []struct {
LogPrefix, LogSuffix string
parentHandler func(map[string]interface{}) ParentLogHandler
}{{
"log.", ".filter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return MatchMapHandler(keyMap, child)
})
// If the configuration has an all option we can skip some
c, _ = NewCompositeMultiHandler()
// Filters are assigned first, non filtered items override filters
initAllLog(c, basePath, config)
initLogLevels(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initFilterLog(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initRequestLog(c, basePath, config)
return c
}
// Init the log.all configuration options
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
if output, found := config.String("log.all.output"); found {
// Set all output for the specified handler
if extraLogFlag {
log.Printf("Adding standard handler for levels to >%s< ", output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
}
}
}
// Init the filter options
// log.all.filter ....
// log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
// The commands to use
logFilterList := []struct {
LogPrefix, LogSuffix string
parentHandler func(map[string]interface{}) ParentLogHandler
}{{
"log.", ".filter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return MatchMapHandler(keyMap, child)
})
},
}, {
"log.", ".nfilter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return NotMatchMapHandler(keyMap, child)
})
},
}}
for _, logFilter := range logFilterList {
// Init for all filters
for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
for _, option := range optionList {
splitOptions := strings.Split(option, ".")
keyMap := map[string]interface{}{}
for x := 3; x < len(splitOptions); x += 2 {
keyMap[splitOptions[x]] = splitOptions[x+1]
}
phandler := logFilter.parentHandler(keyMap)
if extraLogFlag {
log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
}
if name == "all" {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
} else {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
}
}
}
}
}
}
// Init the log.error, log.warn etc configuration options
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
for _, name := range []string{"debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
output, found := config.String("log." + name + ".output")
if found {
if extraLogFlag {
log.Printf("Adding standard handler %s output %s", name, output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
// Gets the list of options with said prefix
} else {
initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
}
}
// Init the request log options
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
// Request logging to a separate output handler
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
// context with the word "section=requestlog" to that handler.
// Note if request logging is not enabled the MatchAbHandler will not be added and the
// request log messages will be sent out the INFO handler
outputRequest := "stdout"
if config != nil {
outputRequest = config.StringDefault("log.request.output", "")
}
oldInfo := c.InfoHandler
c.InfoHandler = nil
if outputRequest != "" {
initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
}
if c.InfoHandler != nil || oldInfo != nil {
if c.InfoHandler == nil {
c.InfoHandler = oldInfo
} else {
c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
}
}
}
// Returns a handler for the level using the output string
// Accept formats for output string are
// LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
if options.Ctx != nil {
options.SetExtendedOptions(
"noColor", !options.Ctx.BoolDefault("log.colorize", true),
"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
"maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
"maxAge", options.Ctx.IntDefault("log.maxage", 14),
"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
"compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
)
}
output = strings.TrimSpace(output)
if funcHandler, found := LogFunctionMap[output]; found {
funcHandler(c, options)
} else {
switch output {
case "":
fallthrough
case "off":
// No handler, discard data
default:
// Write to file specified
if !filepath.IsAbs(output) {
output = filepath.Join(basePath, output)
}
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
log.Panic(err)
}
if strings.HasSuffix(output, "json") {
c.SetJsonFile(output, options)
} else {
// Override defaults for a terminal file
options.SetExtendedOptions("noColor", true)
options.SetExtendedOptions("smallDate", false)
c.SetTerminalFile(output, options)
}
}
}
return
}
},
}, {
"log.", ".nfilter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return NotMatchMapHandler(keyMap, child)
})
},
}}
// This structure and method will handle the old output format and log it to the new format
type loggerRewrite struct {
@@ -240,8 +72,10 @@ type loggerRewrite struct {
hideDeprecated bool
}
// The message indicating that a logger is using a deprecated log mechanism
var log_deprecated = []byte("* LOG DEPRECATED * ")
// Implements the Write of the logger
func (lr loggerRewrite) Write(p []byte) (n int, err error) {
if !lr.hideDeprecated {
p = append(log_deprecated, p...)
@@ -270,7 +104,7 @@ func (lr loggerRewrite) Write(p []byte) (n int, err error) {
// For logging purposes the call stack can be used to record the stack trace of a bad error
// simply pass it as a context field in your log statement like
// `controller.Log.Critc("This should not occur","stack",revel.NewCallStack())`
// `controller.Log.Crit("This should not occur","stack",revel.NewCallStack())`
func NewCallStack() interface{} {
return stack.Trace()
}

98
logger/wrap_handlers.go Normal file
View File

@@ -0,0 +1,98 @@
package logger
// FuncHandler returns a Handler that logs records with the given
// function.
import (
"fmt"
"reflect"
"sync"
"time"
)
// Function handler wraps the declared function and returns the handler for it
func FuncHandler(fn func(r *Record) error) LogHandler {
return funcHandler(fn)
}
// The type decleration for the function
type funcHandler func(r *Record) error
// The implementation of the Log
func (h funcHandler) Log(r *Record) error {
return h(r)
}
// This function allows you to do a full declaration for the log,
// it is recommended you use FuncHandler instead
func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler {
return remoteHandler(log)
}
// The type used for the HandlerFunc
type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error
// The Log implementation
func (c remoteHandler) Log(record *Record) error {
return c(record.Message, record.Time, record.Level, record.Call, record.Context)
}
// SyncHandler can be wrapped around a handler to guarantee that
// only a single Log operation can proceed at a time. It's necessary
// for thread-safe concurrent writes.
func SyncHandler(h LogHandler) LogHandler {
var mu sync.Mutex
return FuncHandler(func(r *Record) error {
defer mu.Unlock()
mu.Lock()
return h.Log(r)
})
}
// LazyHandler writes all values to the wrapped handler after evaluating
// any lazy functions in the record's context. It is already wrapped
// around StreamHandler and SyslogHandler in this library, you'll only need
// it if you write your own Handler.
func LazyHandler(h LogHandler) LogHandler {
return FuncHandler(func(r *Record) error {
for k, v := range r.Context {
if lz, ok := v.(Lazy); ok {
value, err := evaluateLazy(lz)
if err != nil {
r.Context[errorKey] = "bad lazy " + k
} else {
v = value
}
}
}
return h.Log(r)
})
}
func evaluateLazy(lz Lazy) (interface{}, error) {
t := reflect.TypeOf(lz.Fn)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn)
}
if t.NumIn() > 0 {
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn)
}
if t.NumOut() == 0 {
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn)
}
value := reflect.ValueOf(lz.Fn)
results := value.Call([]reflect.Value{})
if len(results) == 1 {
return results[0].Interface(), nil
} else {
values := make([]interface{}, len(results))
for i, v := range results {
values[i] = v.Interface()
}
return values, nil
}
}

View File

@@ -1,17 +1,22 @@
package model
// The constants
import (
"fmt"
"github.com/revel/cmd"
"github.com/revel/cmd/logger"
"github.com/revel/cmd/utils"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
// The constants
const (
NEW COMMAND = iota + 1
RUN
@@ -28,17 +33,20 @@ type (
// The Command config for the line input
CommandConfig struct {
Index COMMAND // The index
Verbose []bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
ImportPath string // The import path (relative to a GOPATH)
GoPath string // The GoPath
GoCmd string // The full path to the go executable
SrcRoot string // The source root
AppPath string // The application path (absolute)
AppName string // The application name
PackageResolver func(pkgName string) error // a packge resolver for the config
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
Index COMMAND // The index
Verbose []bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
FrameworkVersion *Version // The framework version
CommandVersion *Version // The command version
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
ImportPath string // The import path (relative to a GOPATH)
GoPath string // The GoPath
GoCmd string // The full path to the go executable
SrcRoot string // The source root
AppPath string // The application path (absolute)
AppName string // The application name
Vendored bool // True if the application is vendored
PackageResolver func(pkgName string) error // a packge resolver for the config
BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"`
// The new command
New struct {
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
@@ -57,7 +65,7 @@ type (
Run struct {
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"`
Port int `short:"p" long:"port" default:"-1" description:"The port to listen" `
Port int `short:"p" long:"port" default:"-1" description:"The port to listen" `
NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"`
} `command:"run"`
// The package command
@@ -80,12 +88,13 @@ type (
// The version command
Version struct {
ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"`
Update bool `short:"u" long:"update" description:"Update the framework and modules" required:"false"`
} `command:"version"`
}
)
// Updates the import path depending on the command
func (c *CommandConfig) UpdateImportPath() bool {
func (c *CommandConfig) UpdateImportPath() error {
var importPath string
required := true
switch c.Index {
@@ -107,6 +116,7 @@ func (c *CommandConfig) UpdateImportPath() bool {
}
if len(importPath) == 0 || filepath.IsAbs(importPath) || importPath[0] == '.' {
utils.Logger.Info("Import path is absolute or not specified", "path", importPath)
// Try to determine the import path from the GO paths and the command line
currentPath, err := os.Getwd()
if len(importPath) > 0 {
@@ -116,7 +126,6 @@ func (c *CommandConfig) UpdateImportPath() bool {
}
// For an absolute path
currentPath, _ = filepath.Abs(importPath)
}
if err == nil {
@@ -128,6 +137,9 @@ func (c *CommandConfig) UpdateImportPath() bool {
if len(importPath) > 4 && strings.ToLower(importPath[0:4]) == "src/" {
importPath = importPath[4:]
} else if importPath == "src" {
if c.Index != VERSION {
return fmt.Errorf("Invlaid import path, working dir is in GOPATH root")
}
importPath = ""
}
utils.Logger.Info("Updated import path", "path", importPath)
@@ -138,23 +150,40 @@ func (c *CommandConfig) UpdateImportPath() bool {
c.ImportPath = importPath
utils.Logger.Info("Returned import path", "path", importPath, "buildpath", build.Default.GOPATH)
return (len(importPath) > 0 || !required)
if required && c.Index != NEW {
if err := c.SetVersions(); err != nil {
utils.Logger.Panic("Failed to fetch revel versions", "error", err)
}
if err:=c.FrameworkVersion.CompatibleFramework(c);err!=nil {
utils.Logger.Fatal("Compatibility Error", "message", err,
"Revel framework version", c.FrameworkVersion.String(), "Revel tool version", c.CommandVersion.String())
}
utils.Logger.Info("Revel versions", "revel-tool", c.CommandVersion.String(), "Revel Framework", c.FrameworkVersion.String())
}
if !required {
return nil
}
if len(importPath) == 0 {
return fmt.Errorf("Unable to determine import path from : %s", importPath)
}
return nil
}
// Used to initialize the package resolver
func (c *CommandConfig) InitPackageResolver() {
useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor"))
c.Vendored = utils.DirExists(filepath.Join(c.AppPath, "vendor"))
if c.Index == NEW && c.New.Vendored {
useVendor = true
c.Vendored = true
}
utils.Logger.Info("InitPackageResolver", "useVendor", useVendor, "path", c.AppPath)
utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath)
var (
depPath string
err error
)
if useVendor {
if c.Vendored {
utils.Logger.Info("Vendor folder detected, scanning for deps in path")
depPath, err = exec.LookPath("dep")
if err != nil {
@@ -170,8 +199,8 @@ func (c *CommandConfig) InitPackageResolver() {
//useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor"))
var getCmd *exec.Cmd
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", useVendor)
if useVendor {
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored)
if c.Vendored {
utils.Logger.Info("Using dependency manager to import package", "package", pkgName)
if depPath == "" {
@@ -182,10 +211,18 @@ func (c *CommandConfig) InitPackageResolver() {
utils.Logger.Error("Missing package", "package", pkgName)
return fmt.Errorf("Missing package %s", pkgName)
}
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
// Check to see if the package exists locally
_, err := build.Import(pkgName, c.AppPath, build.FindOnly)
if err != nil {
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
} else {
getCmd = exec.Command(depPath, "ensure", "-update", pkgName)
}
} else {
utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName)
getCmd = exec.Command(c.GoCmd, "get", pkgName)
getCmd = exec.Command(c.GoCmd, "get", "-u", pkgName)
}
utils.CmdInit(getCmd, c.AppPath)
@@ -201,6 +238,7 @@ func (c *CommandConfig) InitPackageResolver() {
// lookup and set Go related variables
func (c *CommandConfig) InitGoPaths() {
utils.Logger.Info("InitGoPaths")
// lookup go path
c.GoPath = build.Default.GOPATH
if c.GoPath == "" {
@@ -237,10 +275,10 @@ func (c *CommandConfig) InitGoPaths() {
}
}
if len(c.SrcRoot)==0 && len(bestpath) > 0 {
utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath, "bestpath",bestpath)
if len(c.SrcRoot) == 0 && len(bestpath) > 0 {
c.SrcRoot = bestpath
}
utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath)
// If source root is empty and this isn't a version then skip it
if len(c.SrcRoot) == 0 {
@@ -256,3 +294,48 @@ func (c *CommandConfig) InitGoPaths() {
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
utils.Logger.Info("Set application path", "path", c.AppPath)
}
// Sets the versions on the command config
func (c *CommandConfig) SetVersions() (err error) {
c.CommandVersion, _ = ParseVersion(cmd.Version)
_, revelPath, err := utils.FindSrcPaths(c.ImportPath, RevelImportPath, c.PackageResolver)
if err == nil {
utils.Logger.Info("Fullpath to revel", "dir", revelPath)
fset := token.NewFileSet() // positions are relative to fset
versionData, err := ioutil.ReadFile(filepath.Join(revelPath, RevelImportPath, "version.go"))
if err != nil {
utils.Logger.Error("Failed to find Revel version:", "error", err, "path", revelPath)
}
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", versionData, parser.ParseComments)
if err != nil {
return utils.NewBuildError("Failed to parse Revel version error:", "error", err)
}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
if spec.Names[0].Name == "Version" {
c.FrameworkVersion, err = ParseVersion(strings.Replace(r.Value, `"`, ``, -1))
if err != nil {
utils.Logger.Errorf("Failed to parse version")
} else {
utils.Logger.Info("Parsed revel version", "version", c.FrameworkVersion.String())
}
}
}
}
}
return
}

View File

@@ -10,15 +10,15 @@ import (
func TestEventHandler(t *testing.T) {
counter := 0
newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) {
if typeOf == revel.REVEL_FAILURE {
if typeOf == revel.ENGINE_SHUTDOWN_REQUEST {
counter++
}
return
}
// Attach the same handlder twice so we expect to see the response twice as well
// Attach the same handler twice so we expect to see the response twice as well
revel.AddInitEventHandler(newListener)
revel.AddInitEventHandler(newListener)
revel.RaiseEvent(revel.REVEL_AFTER_MODULES_LOADED, nil)
revel.RaiseEvent(revel.REVEL_FAILURE, nil)
revel.StopServer(1)
assert.Equal(t, counter, 2, "Expected event handler to have been called")
}

View File

@@ -92,6 +92,7 @@ func (w *WrappedRevelCallback) PackageResolver(pkgName string) error {
// RevelImportPath Revel framework import path
var RevelImportPath = "github.com/revel/revel"
var RevelModulesImportPath = "github.com/revel/modules"
// This function returns a container object describing the revel application
// eventually this type of function will replace the global variables.
@@ -120,8 +121,16 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp
rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath))
rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "vendor"))
rp.AppPath = filepath.Join(rp.BasePath, "app")
rp.ViewsPath = filepath.Join(rp.AppPath, "views")
// Sanity check , ensure app and conf paths exist
if !utils.DirExists(rp.AppPath) {
return rp, fmt.Errorf("No application found at path %s", rp.AppPath)
}
if !utils.DirExists(filepath.Join(rp.BasePath, "conf")) {
return rp, fmt.Errorf("No configuration found at path %s", filepath.Join(rp.BasePath, "conf"))
}
rp.ViewsPath = filepath.Join(rp.AppPath, "views")
rp.CodePaths = []string{rp.AppPath}
rp.TemplatePaths = []string{}
@@ -218,7 +227,7 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) {
modulePath, err := rp.ResolveImportPath(moduleImportPath)
if err != nil {
utils.Logger.Info("Missing module ", "module", moduleImportPath, "error",err)
utils.Logger.Info("Missing module ", "module_import_path", moduleImportPath, "error",err)
callback.PackageResolver(moduleImportPath)
modulePath, err = rp.ResolveImportPath(moduleImportPath)
if err != nil {

124
model/version.go Normal file
View File

@@ -0,0 +1,124 @@
package model
import (
"fmt"
"github.com/pkg/errors"
"regexp"
"strconv"
)
type Version struct {
Prefix string
Major int
Minor int
Maintenance int
Suffix string
BuildDate string
MinGoVersion string
}
// The compatibility list
var frameworkCompatibleRangeList = [][]string{
{"0.0.0", "0.20.0"}, // minimum Revel version to use with this version of the tool
{"0.19.99", "0.30.0"}, // Compatible with Framework V 0.19.99 - 0.30.0
}
// Parses a version like v1.2.3a or 1.2
var versionRegExp = regexp.MustCompile(`([^\d]*)?([0-9]*)\.([0-9]*)(\.([0-9]*))?(.*)`)
// Parse the version and return it as a Version object
func ParseVersion(version string) (v *Version, err error) {
v = &Version{}
return v, v.ParseVersion(version)
}
// Parse the version and return it as a Version object
func (v *Version)ParseVersion(version string) (err error) {
parsedResult := versionRegExp.FindAllStringSubmatch(version, -1)
if len(parsedResult) != 1 {
err = errors.Errorf("Invalid version %s", version)
return
}
if len(parsedResult[0]) != 7 {
err = errors.Errorf("Invalid version %s", version)
return
}
v.Prefix = parsedResult[0][1]
v.Major = v.intOrZero(parsedResult[0][2])
v.Minor = v.intOrZero(parsedResult[0][3])
v.Maintenance = v.intOrZero(parsedResult[0][5])
v.Suffix = parsedResult[0][6]
return
}
// Returns 0 or an int value for the string, errors are returned as 0
func (v *Version) intOrZero(input string) (value int) {
if input != "" {
value, _ = strconv.Atoi(input)
}
return value
}
// Returns true if this major revision is compatible
func (v *Version) CompatibleFramework(c *CommandConfig) error {
for i, rv := range frameworkCompatibleRangeList {
start, _ := ParseVersion(rv[0])
end, _ := ParseVersion(rv[1])
if !v.Newer(start) || v.Newer(end) {
continue
}
// Framework is older then 0.20, turn on historic mode
if i == 0 {
c.HistoricMode = true
}
return nil
}
return errors.New("Tool out of date - do a 'go get -u github.com/revel/cmd/revel'")
}
// Returns true if this major revision is newer then the passed in
func (v *Version) MajorNewer(o *Version) bool {
if v.Major != o.Major {
return v.Major > o.Major
}
return false
}
// Returns true if this major or major and minor revision is newer then the value passed in
func (v *Version) MinorNewer(o *Version) bool {
if v.Major != o.Major {
return v.Major > o.Major
}
if v.Minor != o.Minor {
return v.Minor > o.Minor
}
return false
}
// Returns true if the version is newer then the current on
func (v *Version) Newer(o *Version) bool {
if v.Major != o.Major {
return v.Major > o.Major
}
if v.Minor != o.Minor {
return v.Minor > o.Minor
}
if v.Maintenance != o.Maintenance {
return v.Maintenance > o.Maintenance
}
return false
}
// Convert the version to a string
func (v *Version) VersionString() string {
return fmt.Sprintf("%s%d.%d.%d%s", v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix)
}
// Convert the version build date and go version to a string
func (v *Version) String() string {
return fmt.Sprintf("Version: %s%d.%d.%d%s\nBuild Date: %s\n Minimium Go Version: %s",
v.Prefix, v.Major, v.Minor, v.Maintenance, v.Suffix, v.BuildDate, v.MinGoVersion)
}

33
model/version_test.go Normal file
View File

@@ -0,0 +1,33 @@
package model_test
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/revel/cmd/model"
)
var versionTests = [][]string{
{"v0.20.0-dev", "v0.20.0-dev"},
{"v0.20-dev", "v0.20.0-dev"},
{"v0.20.", "v0.20.0"},
{"2.0", "2.0.0"},
}
// Test that the event handler can be attached and it dispatches the event received
func TestVersion(t *testing.T) {
for _, v:= range versionTests {
p,e:=model.ParseVersion(v[0])
assert.Nil(t,e,"Should have parsed %s",v)
assert.Equal(t,p.String(),v[1], "Should be equal %s==%s",p.String(),v)
}
}
// test the ranges
func TestVersionRange(t *testing.T) {
a,_ := model.ParseVersion("0.1.2")
b,_ := model.ParseVersion("0.2.1")
c,_ := model.ParseVersion("1.0.1")
assert.True(t, b.MinorNewer(a), "B is newer then A")
assert.False(t, b.MajorNewer(a), "B is not major newer then A")
assert.False(t, b.MajorNewer(c), "B is not major newer then A")
assert.True(t, c.MajorNewer(b), "C is major newer then b")
}

View File

@@ -134,7 +134,7 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat
var importPath string
typeExpr := model.NewTypeExprFromAst(pkgName, field.Type)
if !typeExpr.Valid {
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
utils.Logger.Warnf("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
return // We didn't understand one of the args. Ignore this action.
}
// Local object

View File

@@ -60,9 +60,10 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
// Returns a valid import string from the path
// using the build.Defaul.GOPATH to determine the root
func importPathFromPath(root string) string {
if vendorIdx := strings.Index(root, "/vendor/"); vendorIdx != -1 {
return filepath.ToSlash(root[vendorIdx+8:])
func importPathFromPath(root, basePath string) string {
vendorTest := filepath.Join(basePath, "vendor")
if len(root) > len(vendorTest) && root[:len(vendorTest)] == vendorTest {
return filepath.ToSlash(root[len(vendorTest)+1:])
}
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
srcPath := filepath.Join(gopath, "src")

View File

@@ -37,7 +37,7 @@ type methodMap map[string][]*model.MethodSpec
func ProcessSource(paths *model.RevelContainer) (_ *model.SourceInfo, compileError error) {
pc := &processContainer{paths: paths}
for _, root := range paths.CodePaths {
rootImportPath := importPathFromPath(root)
rootImportPath := importPathFromPath(root, paths.BasePath)
if rootImportPath == "" {
utils.Logger.Info("Skipping empty code path", "path", root)
continue
@@ -46,6 +46,9 @@ func ProcessSource(paths *model.RevelContainer) (_ *model.SourceInfo, compileErr
// Start walking the directory tree.
compileError = utils.Walk(root, pc.processPath)
if compileError != nil {
return
}
}
return pc.srcInfo, compileError

View File

@@ -62,6 +62,7 @@ func buildApp(c *model.CommandConfig) (err error) {
// Convert target to absolute path
c.Build.TargetPath, _ = filepath.Abs(destPath)
c.Build.Mode = mode
c.Build.ImportPath = appImportPath
revel_paths, err := model.NewRevelPaths(mode, appImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver))
if err != nil {

View File

@@ -65,7 +65,7 @@ func newApp(name string, command model.COMMAND, precall func(c *model.CommandCon
if precall != nil {
precall(c)
}
if !c.UpdateImportPath() {
if c.UpdateImportPath()!=nil {
a.Fail("Unable to update import path")
}
c.InitGoPaths()

View File

@@ -65,6 +65,13 @@ func newApp(c *model.CommandConfig) (err error) {
if err == nil || !utils.Empty(c.AppPath) {
return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath)
}
// checking and setting skeleton
if err=setSkeletonPath(c);err!=nil {
return
}
// Create application path
if err := os.MkdirAll(c.AppPath, os.ModePerm); err != nil {
return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath)
}
@@ -107,7 +114,8 @@ func newApp(c *model.CommandConfig) (err error) {
getCmd := exec.Command("dep", "ensure", "-v")
utils.CmdInit(getCmd, c.AppPath)
utils.Logger.Info("Exec:", "args", getCmd.Args)
utils.Logger.Info("Exec:", "args", getCmd.Args, "env", getCmd.Env, "workingdir",getCmd.Dir)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
return utils.NewBuildIfError(err, string(getOutput))
@@ -115,15 +123,11 @@ func newApp(c *model.CommandConfig) (err error) {
}
// checking and setting application
if err = setApplicationPath(c); err != nil {
return err
}
// checking and setting skeleton
if err=setSkeletonPath(c);err!=nil {
return
}
// At this point the versions can be set
c.SetVersions()
// copy files to new app directory
if err = copyNewAppFiles(c);err != nil {
@@ -189,40 +193,46 @@ func setApplicationPath(c *model.CommandConfig) (err error) {
c.AppName = filepath.Base(c.AppPath)
//if c.BasePath == "." {
// // we need to remove the a single '.' when
// // the app is in the $GOROOT/src directory
// c.BasePath = ""
//} else {
// // we need to append a '/' when the app is
// // is a subdirectory such as $GOROOT/src/path/to/revelapp
// c.BasePath += "/"
//}
return nil
}
// Set the skeleton path
func setSkeletonPath(c *model.CommandConfig) (err error) {
if len(c.New.SkeletonPath) == 0 {
c.New.SkeletonPath = "git://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
}
// First check to see the protocol of the string
if sp, err := url.Parse(c.New.SkeletonPath); err == nil {
sp, err := url.Parse(c.New.SkeletonPath)
if err == nil {
utils.Logger.Info("Detected skeleton path", "path", sp)
switch strings.ToLower(sp.Scheme) {
// TODO Add support for https, http, ftp, file
// TODO Add support for ftp, sftp, scp ??
case "" :
sp.Scheme="file"
fallthrough
case "file" :
fullpath := sp.String()[7:]
if !filepath.IsAbs(fullpath) {
fullpath, err = filepath.Abs(fullpath)
if err!=nil {
return
}
}
c.New.SkeletonPath = fullpath
utils.Logger.Info("Set skeleton path to ", fullpath)
if !utils.DirExists(fullpath) {
return fmt.Errorf("Failed to find skeleton in filepath %s %s", fullpath, sp.String())
}
case "git":
fallthrough
case "http":
fallthrough
case "https":
if err := newLoadFromGit(c, sp); err != nil {
return err
}
//case "":
//if err := newLoadFromGo(c, sp); err != nil {
// return err
//}
default:
utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath)
@@ -245,7 +255,7 @@ func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
utils.Logger.Info("Exec:", "args", getCmd.Args)
getOutput, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Fatalf("Abort: could not clone the Skeleton source code: \n%s\n%s\n", getOutput, c.New.SkeletonPath)
utils.Logger.Fatal("Abort: could not clone the Skeleton source code: ","output", string(getOutput), "path", c.New.SkeletonPath)
}
outputPath := targetPath
if len(pathpart) > 1 {
@@ -307,20 +317,27 @@ const (
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
required = ["github.com/revel/cmd/revel"]
required = ["github.com/revel/revel", "github.com/revel/modules"]
# Note to use a specific version changes this to
#
# [[override]]
# version = "0.20.1"
# name = "github.com/revel/modules"
[[override]]
branch = "master"
name = "github.com/revel/modules"
# Note to use a specific version changes this to
#
# [[override]]
# version = "0.20.0"
# name = "github.com/revel/revel"
[[override]]
branch = "master"
name = "github.com/revel/revel"
[[override]]
branch = "master"
name = "github.com/revel/cmd"
[[override]]
branch = "master"
name = "github.com/revel/log15"

View File

@@ -20,6 +20,7 @@ import (
"github.com/revel/cmd/logger"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"bytes"
)
const (
@@ -70,14 +71,17 @@ func main() {
wd, _ := os.Getwd()
utils.InitLogger(wd, logger.LvlError)
parser := flags.NewParser(c, flags.HelpFlag|flags.PassDoubleDash)
if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
fmt.Fprint(os.Stderr, err.Error())
if len(os.Args)<2 {
parser.WriteHelp(os.Stdout)
os.Exit(1)
}
if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
fmt.Fprint(os.Stderr, err.Error() +"\n")
os.Exit(1)
}
// Switch based on the verbose flag
if len(c.Verbose)>1 {
utils.InitLogger(wd, logger.LvlDebug)
@@ -87,8 +91,10 @@ func main() {
utils.InitLogger(wd, logger.LvlWarn)
}
if !c.UpdateImportPath() {
utils.Logger.Fatal("Unable to determine application path")
if err := c.UpdateImportPath();err!=nil {
utils.Logger.Error(err.Error())
parser.WriteHelp(os.Stdout)
os.Exit(1)
}
command := Commands[c.Index]
@@ -136,12 +142,12 @@ func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err
}
}
if c.Index == 0 {
err = fmt.Errorf("Unknown command %v", extraArgs)
} else if len(extraArgs) > 0 {
if len(extraArgs) > 0 {
utils.Logger.Info("Found additional arguements, setting them")
if !Commands[c.Index].UpdateConfig(c, extraArgs) {
err = fmt.Errorf("Invalid command line arguements %v", extraArgs)
buffer := &bytes.Buffer{}
parser.WriteHelp(buffer)
err = fmt.Errorf("Invalid command line arguements %v\n%s", extraArgs, buffer.String())
}
}

View File

@@ -12,6 +12,8 @@ import (
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/build"
"os"
"path/filepath"
)
var cmdRun = &Command{
@@ -71,7 +73,7 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
// 3. revel run [run-mode] [port]
// Check to see if the import path evaluates out to something that may be on a gopath
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
if runIsImportPath(args[0]) {
// 1st arg is the import path
c.Run.ImportPath = args[0]
@@ -92,12 +94,7 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
// 1. revel run [import-path]
// 2. revel run [port]
// 3. revel run [run-mode]
_, err := build.Import(args[0], "", build.FindOnly)
if err != nil {
utils.Logger.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
}
utils.Logger.Info("Trying to build with", args[0], err)
if err == nil {
if runIsImportPath(args[0]) {
// 1st arg is the import path
c.Run.ImportPath = args[0]
} else if _, err := strconv.Atoi(args[0]); err == nil {
@@ -108,12 +105,21 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool {
c.Run.Mode = args[0]
}
case 0:
return false
// Attempt to set the import path to the current working director.
c.Run.ImportPath,_ = os.Getwd()
}
c.Index = model.RUN
return true
}
// Returns true if this is an absolute path or a relative gopath
func runIsImportPath(pathToCheck string) bool {
if _, err := build.Import(pathToCheck, "", build.FindOnly);err==nil {
return true
}
return filepath.IsAbs(pathToCheck)
}
// Called to run the app
func runApp(c *model.CommandConfig) (err error) {
if c.Run.Mode == "" {

View File

@@ -10,19 +10,30 @@ package main
import (
"fmt"
"runtime"
"github.com/revel/cmd"
"github.com/revel/cmd/model"
"github.com/revel/cmd/utils"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"bytes"
)
type (
// The version container
VersionCommand struct {
Command *model.CommandConfig // The command
revelVersion *model.Version // The Revel framework version
modulesVersion *model.Version // The Revel modules version
cmdVersion *model.Version // The tool version
}
)
var cmdVersion = &Command{
@@ -38,12 +49,13 @@ For example:
}
func init() {
cmdVersion.RunWith = versionApp
cmdVersion.UpdateConfig = updateVersionConfig
v := &VersionCommand{}
cmdVersion.UpdateConfig = v.UpdateConfig
cmdVersion.RunWith = v.RunWith
}
// Update the version
func updateVersionConfig(c *model.CommandConfig, args []string) bool {
func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool {
if len(args) > 0 {
c.Version.ImportPath = args[0]
}
@@ -51,69 +63,199 @@ func updateVersionConfig(c *model.CommandConfig, args []string) bool {
}
// Displays the version of go and Revel
func versionApp(c *model.CommandConfig) (err error) {
func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) {
utils.Logger.Info("Requesting version information", "config", c)
v.Command = c
var revelPath, appPath string
// Update the versions with the local values
v.updateLocalVersions()
appPath, revelPath, err = utils.FindSrcPaths(c.ImportPath, model.RevelImportPath, c.PackageResolver)
if err != nil {
return utils.NewBuildError("Failed to import "+c.ImportPath+" with error:", "error", err)
needsUpdates := true
versionInfo := ""
for x := 0; x < 2 && needsUpdates; x++ {
needsUpdates = false
versionInfo, needsUpdates = v.doRepoCheck(x==0)
}
revelPath = revelPath + model.RevelImportPath
fmt.Println("\nRevel Framework",revelPath, appPath )
if err != nil {
utils.Logger.Info("Failed to find Revel in GOPATH with error:", "error", err, "gopath", build.Default.GOPATH)
fmt.Println("Information not available (not on GOPATH)")
fmt.Println(versionInfo)
cmd := exec.Command(c.GoCmd, "version")
cmd.Stdout = os.Stdout
if e := cmd.Start(); e != nil {
fmt.Println("Go command error ", e)
} else {
utils.Logger.Info("Fullpath to revel", "dir", revelPath)
fset := token.NewFileSet() // positions are relative to fset
version, err := ioutil.ReadFile(filepath.Join(revelPath, "version.go"))
if err != nil {
utils.Logger.Error("Failed to find Revel version:", "error", err, "path", revelPath)
}
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", version, parser.ParseComments)
if err != nil {
return utils.NewBuildError("Failed to parse Revel version error:", "error", err)
}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
fmt.Printf("Revel %s = %s\n", spec.Names[0].Name, r.Value)
}
}
}
fmt.Println("\nRevel Command Utility Tool")
fmt.Println("Version", cmd.Version)
fmt.Println("Build Date", cmd.BuildDate)
fmt.Println("Minimum Go Version", cmd.MinimumGoVersion)
fmt.Printf("Compiled By %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
// Extract the goversion detected
if len(c.GoCmd) > 0 {
cmd := exec.Command(c.GoCmd, "version")
cmd.Stdout = os.Stdout
if e := cmd.Start(); e != nil {
fmt.Println("Go command error ", e)
} else {
cmd.Wait()
}
} else {
fmt.Println("Go command not found ")
cmd.Wait()
}
return
}
// Checks the Revel repos for the latest version
func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) {
var (
title string
localVersion *model.Version
)
for _, repo := range []string{"revel", "cmd", "modules"} {
versonFromRepo, err := v.versionFromRepo(repo, "", "version.go")
if err != nil {
utils.Logger.Info("Failed to get version from repo", "repo", repo, "error", err)
}
switch repo {
case "revel":
title, repo, localVersion = "Revel Framework", "github.com/revel/revel", v.revelVersion
case "cmd":
title, repo, localVersion = "Revel Cmd", "github.com/revel/cmd/revel", v.cmdVersion
case "modules":
title, repo, localVersion = "Revel Modules", "github.com/revel/modules", v.modulesVersion
}
// Only do an update on the first loop, and if specified to update
shouldUpdate := updateLibs && v.Command.Version.Update
if v.Command.Version.Update {
if localVersion == nil || (versonFromRepo != nil && versonFromRepo.Newer(localVersion)) {
needsUpdate = true
if shouldUpdate {
v.doUpdate(title, repo, localVersion, versonFromRepo)
v.updateLocalVersions()
}
}
}
versionInfo = versionInfo + v.outputVersion(title, repo, localVersion, versonFromRepo)
}
return
}
// Checks for updates if needed
func (v *VersionCommand) doUpdate(title, repo string, local, remote *model.Version) {
utils.Logger.Info("Updating package", "package", title, "repo",repo)
fmt.Println("Attempting to update package", title)
if err := v.Command.PackageResolver(repo); err != nil {
utils.Logger.Error("Unable to update repo", "repo", repo, "error", err)
} else if repo == "github.com/revel/cmd/revel" {
// One extra step required here to run the install for the command
utils.Logger.Fatal("Revel command tool was updated, you must manually run the following command before continuing\ngo install github.com/revel/cmd/revel")
}
return
}
// Prints out the local and remote versions, calls update if needed
func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.Version) (output string) {
buffer := &bytes.Buffer{}
remoteVersion := "Unknown"
if remote != nil {
remoteVersion = remote.VersionString()
}
localVersion := "Unknown"
if local != nil {
localVersion = local.VersionString()
}
fmt.Fprintf(buffer, "%s\t:\t%s\t(%s remote master branch)\n", title, localVersion, remoteVersion)
return buffer.String()
}
// Returns the version from the repository
func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string) (version *model.Version, err error) {
if branchName == "" {
branchName = "master"
}
// Try to download the version of file from the repo, just use an http connection to retrieve the source
// Assuming that the repo is github
fullurl := "https://raw.githubusercontent.com/revel/" + repoName + "/" + branchName + "/" + fileName
resp, err := http.Get(fullurl)
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
utils.Logger.Info("Got version file", "from", fullurl, "content", string(body))
return v.versionFromBytes(body)
}
// Returns version information from a file called version on the gopath
func (v *VersionCommand) compareAndUpdateVersion(remoteVersion *model.Version, localVersion *model.Version) (err error) {
return
}
func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) {
utils.Logger.Info("Fullpath to revel", "dir", sourcePath)
sourceStream, err := ioutil.ReadFile(filepath.Join(sourcePath, "version.go"))
if err != nil {
return
}
return v.versionFromBytes(sourceStream)
}
// Returns version information from a file called version on the gopath
func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.Version, err error) {
fset := token.NewFileSet() // positions are relative to fset
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", sourceStream, parser.ParseComments)
if err != nil {
err = utils.NewBuildError("Failed to parse Revel version error:", "error", err)
return
}
version = &model.Version{}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
switch spec.Names[0].Name {
case "Version":
version.ParseVersion(strings.Replace(r.Value, `"`, "", -1))
case "BuildDate":
version.BuildDate = r.Value
case "MinimumGoVersion":
version.MinGoVersion = r.Value
}
}
}
return
}
// Fetch the local version of revel from the file system
func (v *VersionCommand) updateLocalVersions() {
v.cmdVersion = &model.Version{}
v.cmdVersion.ParseVersion(cmd.Version)
v.cmdVersion.BuildDate = cmd.BuildDate
v.cmdVersion.MinGoVersion = cmd.MinimumGoVersion
var modulePath, revelPath string
_, revelPath, err := utils.FindSrcPaths(v.Command.ImportPath, model.RevelImportPath, v.Command.PackageResolver)
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel library", "error,err")
return
}
revelPath = revelPath + model.RevelImportPath
utils.Logger.Info("Fullpath to revel", "dir", revelPath)
v.revelVersion, err = v.versionFromFilepath(revelPath)
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel", "error,err")
}
_, modulePath, err = utils.FindSrcPaths(v.Command.ImportPath, model.RevelModulesImportPath, v.Command.PackageResolver)
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel library", "error,err")
return
}
modulePath = modulePath + model.RevelModulesImportPath
v.modulesVersion, err = v.versionFromFilepath(modulePath)
if err != nil {
utils.Logger.Warn("Unable to extract version information from Revel Modules", "error", err)
}
return

View File

@@ -5,23 +5,30 @@ import (
"os"
"os/exec"
"strings"
"bytes"
"path/filepath"
)
// Initialize the command based on the GO environment
func CmdInit(c *exec.Cmd, basePath string) {
c.Dir = basePath
// Go 1.8 fails if we do not include the GOROOT
c.Env = []string{"GOPATH=" + build.Default.GOPATH, "PATH=" + GetEnv("PATH"), "GOROOT="+ GetEnv("GOROOT")}
}
// Returns an environment variable
func GetEnv(name string) string {
for _, v := range os.Environ() {
split := strings.Split(v, "=")
if split[0] == name {
return strings.Join(split[1:], "")
// Dep does not like paths that are not real, convert all paths in go to real paths
realPath := &bytes.Buffer{}
for _, p := range filepath.SplitList(build.Default.GOPATH) {
rp,_ := filepath.EvalSymlinks(p)
if realPath.Len() > 0 {
realPath.WriteString(string(filepath.ListSeparator))
}
realPath.WriteString(rp)
}
return ""
}
// Go 1.8 fails if we do not include the GOROOT
c.Env = []string{"GOPATH=" + realPath.String(), "GOROOT="+ os.Getenv("GOROOT")}
// Fetch the rest of the env variables
for _, e := range os.Environ() {
pair := strings.Split(e, "=")
if pair[0]=="GOPATH" || pair[0]=="GOROOT" {
continue
}
c.Env = append(c.Env,e)
}
}

View File

@@ -23,6 +23,15 @@ type (
IsError bool
}
)
// Return a new error object
func NewError(source, title,path,description string) *Error {
return &Error {
SourceType:source,
Title:title,
Path:path,
Description:description,
}
}
// Creates a link based on the configuration setting "errors.link"
func (e *Error) SetLink(errorLink string) {

View File

@@ -151,7 +151,7 @@ func MustChmod(filename string, mode os.FileMode) {
// Called if panic
func PanicOnError(err error, msg string) {
if revErr, ok := err.(*Error); (ok && revErr != nil) || (!ok && err != nil) {
Logger.Panicf("Abort: %s: %s %s\n", msg, revErr, err)
Logger.Panicf("Abort: %s: %s %s", msg, revErr, err)
}
}
@@ -160,6 +160,9 @@ func PanicOnError(err error, msg string) {
// Additionally, the trailing ".template" is stripped from the file name.
// Also, dot files and dot directories are skipped.
func CopyDir(destDir, srcDir string, data map[string]interface{}) error {
if !DirExists(srcDir) {
return nil
}
return fsWalk(srcDir, srcDir, func(srcPath string, info os.FileInfo, err error) error {
// Get the relative path from the source base, and the corresponding path in
// the dest directory.

View File

@@ -1,15 +1,15 @@
// Copyright (c) 2012-2017 The Revel Framework Authors, All rights reserved.
// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package cmd
const (
// Version current Revel Command version
Version = "0.20.1"
// Version current Revel version
Version = "0.21.1"
// BuildDate latest commit/release date
BuildDate = "2018-09-30"
BuildDate = "2018-10-30"
// MinimumGoVersion minimum required Go version for Revel
MinimumGoVersion = ">= go1.8"