From 686c2fbab7607d42183ae685a27ea3d7dce8c3f6 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 27 Apr 2018 12:49:45 -0700 Subject: Structured logging (#8673) * Implementing structured logging * Changes to en.json to allow refactor to run. * Fixing global logger * Structured logger initalization. * Add caller. * Do some log redirection. * Auto refactor * Cleaning up l4g reference and removing dependancy. * Removing junk. * Copyright headers. * Fixing tests * Revert "Changes to en.json to allow refactor to run." This reverts commit fd8249e99bcad0231e6ea65cd77c32aae9a54026. * Fixing some auto refactor strangeness and typo. * Making keys more human readable. --- Gopkg.lock | 40 +- Gopkg.toml | 11 +- api/api.go | 4 +- api/api_test.go | 15 +- api/apitestlib.go | 15 +- api/channel.go | 12 +- api/context.go | 18 +- api/user.go | 13 +- api/websocket.go | 6 +- api4/api.go | 5 +- api4/api_test.go | 15 +- api4/apitestlib.go | 32 +- api4/channel.go | 5 +- api4/context.go | 18 +- api4/oauth.go | 6 +- api4/plugin.go | 4 +- api4/system.go | 7 +- api4/system_test.go | 4 +- api4/user.go | 4 +- api4/webhook.go | 7 +- api4/websocket.go | 6 +- app/admin.go | 8 +- app/analytics.go | 6 +- app/app.go | 51 +- app/app_test.go | 14 +- app/apptestlib.go | 31 +- app/authorization.go | 7 +- app/auto_responder.go | 5 +- app/auto_users.go | 7 +- app/channel.go | 44 +- app/cluster_discovery.go | 16 +- app/command.go | 6 +- app/command_echo.go | 5 +- app/command_groupmsg.go | 4 +- app/command_invite.go | 7 +- app/command_invite_people.go | 4 +- app/command_loadtest.go | 8 +- app/command_msg.go | 8 +- app/command_mute_test.go | 5 +- app/command_remove.go | 4 +- app/config.go | 8 +- app/diagnostics.go | 8 +- app/email.go | 8 +- app/email_batching.go | 22 +- app/emoji.go | 11 +- app/file.go | 40 +- app/import.go | 19 +- app/ldap.go | 9 +- app/license.go | 11 +- app/notification.go | 32 +- app/oauth.go | 11 +- app/plugin.go | 49 +- app/plugin/ldapextras/plugin.go | 4 +- app/post.go | 30 +- app/ratelimit.go | 7 +- app/role.go | 3 +- app/security_update_check.go | 15 +- app/server.go | 34 +- app/session.go | 15 +- app/slackimport.go | 105 ++-- app/status.go | 9 +- app/team.go | 10 +- app/user.go | 44 +- app/web_conn.go | 29 +- app/web_hub.go | 24 +- app/webhook.go | 7 +- app/websocket_router.go | 6 +- cmd/commands/command.go | 1 + cmd/commands/config_flag_test.go | 1 + cmd/commands/jobserver.go | 9 +- cmd/commands/server.go | 27 +- cmd/commands/user.go | 3 +- cmd/init.go | 2 - config/default.json | 3 +- einterfaces/brand.go | 3 +- einterfaces/oauthproviders.go | 3 +- jobs/jobs.go | 7 +- jobs/jobs_watcher.go | 13 +- jobs/schedulers.go | 26 +- jobs/workers.go | 6 +- manualtesting/manual_testing.go | 13 +- manualtesting/test_autolink.go | 5 +- mlog/global.go | 42 ++ mlog/log.go | 143 ++++++ model/client.go | 6 +- model/config.go | 11 +- store/layered_store.go | 4 +- store/redis_supplier.go | 4 +- store/redis_supplier_reactions.go | 11 +- store/redis_supplier_roles.go | 17 +- store/sqlstore/channel_member_history_store.go | 5 +- store/sqlstore/channel_store.go | 4 +- store/sqlstore/command_webhook_store.go | 7 +- store/sqlstore/post_store.go | 14 +- store/sqlstore/preference_store.go | 4 +- store/sqlstore/session_store.go | 9 +- store/sqlstore/store_test.go | 10 + store/sqlstore/supplier.go | 66 +-- store/sqlstore/supplier_reactions.go | 7 +- store/sqlstore/tokens_store.go | 7 +- store/sqlstore/upgrade.go | 23 +- store/store.go | 4 +- store/storetest/docker.go | 11 +- utils/config.go | 93 +--- utils/config_test.go | 3 +- utils/file_backend_local.go | 5 +- utils/file_backend_s3.go | 8 +- utils/file_backend_test.go | 10 + utils/html.go | 15 +- utils/i18n.go | 6 +- utils/license.go | 22 +- utils/mail.go | 15 +- utils/redirect_std_log.go | 65 --- utils/redirect_std_log_test.go | 24 - vendor/github.com/alecthomas/log4go/.gitignore | 2 - vendor/github.com/alecthomas/log4go/LICENSE | 13 - vendor/github.com/alecthomas/log4go/README | 14 - vendor/github.com/alecthomas/log4go/config.go | 288 ----------- vendor/github.com/alecthomas/log4go/filelog.go | 305 ------------ vendor/github.com/alecthomas/log4go/log4go.go | 484 ------------------ vendor/github.com/alecthomas/log4go/pattlog.go | 130 ----- vendor/github.com/alecthomas/log4go/socklog.go | 57 --- vendor/github.com/alecthomas/log4go/termlog.go | 49 -- vendor/github.com/alecthomas/log4go/wrapper.go | 278 ----------- vendor/go.uber.org/atomic/.codecov.yml | 15 + vendor/go.uber.org/atomic/.gitignore | 11 + vendor/go.uber.org/atomic/.travis.yml | 23 + vendor/go.uber.org/atomic/LICENSE.txt | 19 + vendor/go.uber.org/atomic/Makefile | 64 +++ vendor/go.uber.org/atomic/README.md | 36 ++ vendor/go.uber.org/atomic/atomic.go | 309 ++++++++++++ vendor/go.uber.org/atomic/glide.lock | 17 + vendor/go.uber.org/atomic/glide.yaml | 6 + vendor/go.uber.org/atomic/string.go | 49 ++ vendor/go.uber.org/multierr/.codecov.yml | 15 + vendor/go.uber.org/multierr/.gitignore | 1 + vendor/go.uber.org/multierr/.travis.yml | 33 ++ vendor/go.uber.org/multierr/CHANGELOG.md | 28 ++ vendor/go.uber.org/multierr/LICENSE.txt | 19 + vendor/go.uber.org/multierr/Makefile | 74 +++ vendor/go.uber.org/multierr/README.md | 23 + vendor/go.uber.org/multierr/error.go | 401 +++++++++++++++ vendor/go.uber.org/multierr/glide.lock | 19 + vendor/go.uber.org/multierr/glide.yaml | 8 + vendor/go.uber.org/zap/.codecov.yml | 17 + vendor/go.uber.org/zap/.gitignore | 28 ++ vendor/go.uber.org/zap/.readme.tmpl | 108 ++++ vendor/go.uber.org/zap/.travis.yml | 21 + vendor/go.uber.org/zap/CHANGELOG.md | 286 +++++++++++ vendor/go.uber.org/zap/CODE_OF_CONDUCT.md | 75 +++ vendor/go.uber.org/zap/CONTRIBUTING.md | 81 +++ vendor/go.uber.org/zap/FAQ.md | 154 ++++++ vendor/go.uber.org/zap/LICENSE.txt | 19 + vendor/go.uber.org/zap/Makefile | 76 +++ vendor/go.uber.org/zap/README.md | 136 ++++++ vendor/go.uber.org/zap/array.go | 320 ++++++++++++ vendor/go.uber.org/zap/buffer/buffer.go | 106 ++++ vendor/go.uber.org/zap/buffer/pool.go | 49 ++ vendor/go.uber.org/zap/check_license.sh | 17 + vendor/go.uber.org/zap/config.go | 243 +++++++++ vendor/go.uber.org/zap/doc.go | 113 +++++ vendor/go.uber.org/zap/encoder.go | 75 +++ vendor/go.uber.org/zap/error.go | 80 +++ vendor/go.uber.org/zap/field.go | 310 ++++++++++++ vendor/go.uber.org/zap/flag.go | 39 ++ vendor/go.uber.org/zap/glide.lock | 76 +++ vendor/go.uber.org/zap/glide.yaml | 35 ++ vendor/go.uber.org/zap/global.go | 169 +++++++ vendor/go.uber.org/zap/http_handler.go | 81 +++ .../zap/internal/bufferpool/bufferpool.go | 31 ++ vendor/go.uber.org/zap/internal/color/color.go | 44 ++ vendor/go.uber.org/zap/internal/exit/exit.go | 64 +++ vendor/go.uber.org/zap/level.go | 132 +++++ vendor/go.uber.org/zap/logger.go | 305 ++++++++++++ vendor/go.uber.org/zap/options.go | 109 +++++ vendor/go.uber.org/zap/stacktrace.go | 126 +++++ vendor/go.uber.org/zap/sugar.go | 304 ++++++++++++ vendor/go.uber.org/zap/time.go | 27 + vendor/go.uber.org/zap/writer.go | 96 ++++ vendor/go.uber.org/zap/zapcore/console_encoder.go | 147 ++++++ vendor/go.uber.org/zap/zapcore/core.go | 113 +++++ vendor/go.uber.org/zap/zapcore/doc.go | 24 + vendor/go.uber.org/zap/zapcore/encoder.go | 348 +++++++++++++ vendor/go.uber.org/zap/zapcore/entry.go | 257 ++++++++++ vendor/go.uber.org/zap/zapcore/error.go | 120 +++++ vendor/go.uber.org/zap/zapcore/field.go | 201 ++++++++ vendor/go.uber.org/zap/zapcore/hook.go | 68 +++ vendor/go.uber.org/zap/zapcore/json_encoder.go | 480 ++++++++++++++++++ vendor/go.uber.org/zap/zapcore/level.go | 175 +++++++ vendor/go.uber.org/zap/zapcore/level_strings.go | 46 ++ vendor/go.uber.org/zap/zapcore/marshaler.go | 53 ++ vendor/go.uber.org/zap/zapcore/memory_encoder.go | 179 +++++++ vendor/go.uber.org/zap/zapcore/sampler.go | 134 +++++ vendor/go.uber.org/zap/zapcore/tee.go | 81 +++ vendor/go.uber.org/zap/zapcore/write_syncer.go | 123 +++++ vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore | 23 + vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE | 21 + vendor/gopkg.in/natefinch/lumberjack.v2/README.md | 174 +++++++ vendor/gopkg.in/natefinch/lumberjack.v2/chown.go | 11 + .../natefinch/lumberjack.v2/chown_linux.go | 19 + .../gopkg.in/natefinch/lumberjack.v2/lumberjack.go | 541 +++++++++++++++++++++ web/web.go | 7 +- web/web_test.go | 10 + wsapi/status.go | 5 +- wsapi/websocket_handler.go | 10 +- 205 files changed, 9479 insertions(+), 2487 deletions(-) create mode 100644 mlog/global.go create mode 100644 mlog/log.go delete mode 100644 utils/redirect_std_log.go delete mode 100644 utils/redirect_std_log_test.go delete mode 100644 vendor/github.com/alecthomas/log4go/.gitignore delete mode 100644 vendor/github.com/alecthomas/log4go/LICENSE delete mode 100644 vendor/github.com/alecthomas/log4go/README delete mode 100644 vendor/github.com/alecthomas/log4go/config.go delete mode 100644 vendor/github.com/alecthomas/log4go/filelog.go delete mode 100644 vendor/github.com/alecthomas/log4go/log4go.go delete mode 100644 vendor/github.com/alecthomas/log4go/pattlog.go delete mode 100644 vendor/github.com/alecthomas/log4go/socklog.go delete mode 100644 vendor/github.com/alecthomas/log4go/termlog.go delete mode 100644 vendor/github.com/alecthomas/log4go/wrapper.go create mode 100644 vendor/go.uber.org/atomic/.codecov.yml create mode 100644 vendor/go.uber.org/atomic/.gitignore create mode 100644 vendor/go.uber.org/atomic/.travis.yml create mode 100644 vendor/go.uber.org/atomic/LICENSE.txt create mode 100644 vendor/go.uber.org/atomic/Makefile create mode 100644 vendor/go.uber.org/atomic/README.md create mode 100644 vendor/go.uber.org/atomic/atomic.go create mode 100644 vendor/go.uber.org/atomic/glide.lock create mode 100644 vendor/go.uber.org/atomic/glide.yaml create mode 100644 vendor/go.uber.org/atomic/string.go create mode 100644 vendor/go.uber.org/multierr/.codecov.yml create mode 100644 vendor/go.uber.org/multierr/.gitignore create mode 100644 vendor/go.uber.org/multierr/.travis.yml create mode 100644 vendor/go.uber.org/multierr/CHANGELOG.md create mode 100644 vendor/go.uber.org/multierr/LICENSE.txt create mode 100644 vendor/go.uber.org/multierr/Makefile create mode 100644 vendor/go.uber.org/multierr/README.md create mode 100644 vendor/go.uber.org/multierr/error.go create mode 100644 vendor/go.uber.org/multierr/glide.lock create mode 100644 vendor/go.uber.org/multierr/glide.yaml create mode 100644 vendor/go.uber.org/zap/.codecov.yml create mode 100644 vendor/go.uber.org/zap/.gitignore create mode 100644 vendor/go.uber.org/zap/.readme.tmpl create mode 100644 vendor/go.uber.org/zap/.travis.yml create mode 100644 vendor/go.uber.org/zap/CHANGELOG.md create mode 100644 vendor/go.uber.org/zap/CODE_OF_CONDUCT.md create mode 100644 vendor/go.uber.org/zap/CONTRIBUTING.md create mode 100644 vendor/go.uber.org/zap/FAQ.md create mode 100644 vendor/go.uber.org/zap/LICENSE.txt create mode 100644 vendor/go.uber.org/zap/Makefile create mode 100644 vendor/go.uber.org/zap/README.md create mode 100644 vendor/go.uber.org/zap/array.go create mode 100644 vendor/go.uber.org/zap/buffer/buffer.go create mode 100644 vendor/go.uber.org/zap/buffer/pool.go create mode 100755 vendor/go.uber.org/zap/check_license.sh create mode 100644 vendor/go.uber.org/zap/config.go create mode 100644 vendor/go.uber.org/zap/doc.go create mode 100644 vendor/go.uber.org/zap/encoder.go create mode 100644 vendor/go.uber.org/zap/error.go create mode 100644 vendor/go.uber.org/zap/field.go create mode 100644 vendor/go.uber.org/zap/flag.go create mode 100644 vendor/go.uber.org/zap/glide.lock create mode 100644 vendor/go.uber.org/zap/glide.yaml create mode 100644 vendor/go.uber.org/zap/global.go create mode 100644 vendor/go.uber.org/zap/http_handler.go create mode 100644 vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go create mode 100644 vendor/go.uber.org/zap/internal/color/color.go create mode 100644 vendor/go.uber.org/zap/internal/exit/exit.go create mode 100644 vendor/go.uber.org/zap/level.go create mode 100644 vendor/go.uber.org/zap/logger.go create mode 100644 vendor/go.uber.org/zap/options.go create mode 100644 vendor/go.uber.org/zap/stacktrace.go create mode 100644 vendor/go.uber.org/zap/sugar.go create mode 100644 vendor/go.uber.org/zap/time.go create mode 100644 vendor/go.uber.org/zap/writer.go create mode 100644 vendor/go.uber.org/zap/zapcore/console_encoder.go create mode 100644 vendor/go.uber.org/zap/zapcore/core.go create mode 100644 vendor/go.uber.org/zap/zapcore/doc.go create mode 100644 vendor/go.uber.org/zap/zapcore/encoder.go create mode 100644 vendor/go.uber.org/zap/zapcore/entry.go create mode 100644 vendor/go.uber.org/zap/zapcore/error.go create mode 100644 vendor/go.uber.org/zap/zapcore/field.go create mode 100644 vendor/go.uber.org/zap/zapcore/hook.go create mode 100644 vendor/go.uber.org/zap/zapcore/json_encoder.go create mode 100644 vendor/go.uber.org/zap/zapcore/level.go create mode 100644 vendor/go.uber.org/zap/zapcore/level_strings.go create mode 100644 vendor/go.uber.org/zap/zapcore/marshaler.go create mode 100644 vendor/go.uber.org/zap/zapcore/memory_encoder.go create mode 100644 vendor/go.uber.org/zap/zapcore/sampler.go create mode 100644 vendor/go.uber.org/zap/zapcore/tee.go create mode 100644 vendor/go.uber.org/zap/zapcore/write_syncer.go create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/README.md create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/chown.go create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go diff --git a/Gopkg.lock b/Gopkg.lock index a8679b889..80eb97aa1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,13 +7,6 @@ revision = "2600fb119af974220d3916a5916d6e31176aac1b" version = "v1.0.1" -[[projects]] - branch = "master" - name = "github.com/alecthomas/log4go" - packages = ["."] - revision = "9c17fbb2767ccbdda78584f28d545c44a4b29c4f" - source = "https://github.com/mattermost/log4go.git" - [[projects]] branch = "master" name = "github.com/armon/go-metrics" @@ -511,6 +504,31 @@ packages = ["."] revision = "a0b114877d4caeffbd7f87e3757c17fce570fea7" +[[projects]] + name = "go.uber.org/atomic" + packages = ["."] + revision = "8474b86a5a6f79c443ce4b2992817ff32cf208b8" + version = "v1.3.1" + +[[projects]] + name = "go.uber.org/multierr" + packages = ["."] + revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" + version = "v1.1.0" + +[[projects]] + name = "go.uber.org/zap" + packages = [ + ".", + "buffer", + "internal/bufferpool", + "internal/color", + "internal/exit", + "zapcore" + ] + revision = "eeedf312bc6c57391d84767a4cd413f02a917974" + version = "v1.8.0" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -607,6 +625,12 @@ revision = "41f3572897373c5538c50a2402db15db079fa4fd" version = "2.0.0" +[[projects]] + name = "gopkg.in/natefinch/lumberjack.v2" + packages = ["."] + revision = "a96e63847dc3c67d17befa69c303767e2f84e54f" + version = "v2.1" + [[projects]] name = "gopkg.in/olivere/elastic.v5" packages = [ @@ -635,6 +659,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a6a107c250033694b6d11085333da149e3e1171da3c23ce5bc9362148adef141" + inputs-digest = "4f92b22eac4e8efa0aa098a6b1f1599ab087160296bd5fb74a9fad5366bbe1bc" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index d0753ad64..7eb44b15f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,13 +24,6 @@ # go-tests = true # unused-packages = true - -# To use our own fork -[[constraint]] - name = "github.com/alecthomas/log4go" - branch = "master" - source = "https://github.com/mattermost/log4go.git" - # To keep us on latest since maintainer stopped releasing versions [[constraint]] name = "github.com/go-sql-driver/mysql" @@ -71,3 +64,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "gopkg.in/natefinch/lumberjack.v2" + version = "2.1.0" diff --git a/api/api.go b/api/api.go index 70f36db85..2b226bbeb 100644 --- a/api/api.go +++ b/api/api.go @@ -6,9 +6,9 @@ package api import ( "net/http" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" _ "github.com/nicksnyder/go-i18n/i18n" @@ -114,7 +114,7 @@ func Init(a *app.App, root *mux.Router) *API { a.InitEmailBatching() if *a.Config().ServiceSettings.EnableAPIv3 { - l4g.Info("API version 3 is scheduled for deprecation. Please see https://api.mattermost.com for details.") + mlog.Info("API version 3 is scheduled for deprecation. Please see https://api.mattermost.com for details.") } return api diff --git a/api/api_test.go b/api/api_test.go index d447fc9bd..a4ddf6a37 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -8,20 +8,29 @@ import ( "os" "testing" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" ) func TestMain(m *testing.M) { flag.Parse() + + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + utils.TranslationsPreInit() // In the case where a dev just wants to run a single test, it's faster to just use the default // store. if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - l4g.Info("-test.run used, not creating temporary containers") + mlog.Info("-test.run used, not creating temporary containers") os.Exit(m.Run()) } diff --git a/api/apitestlib.go b/api/apitestlib.go index 699b0eb90..20dbc4073 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -14,14 +14,13 @@ import ( "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/wsapi" - - l4g "github.com/alecthomas/log4go" ) type TestHelper struct { @@ -233,8 +232,8 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { err := me.App.JoinUserToTeam(team, user, "") if err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -248,8 +247,8 @@ func (me *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID + " " + model.TEAM_ADMIN_ROLE_ID} if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { utils.EnableDebugLogForTest() - l4g.Error(tmr.Err.Error()) - l4g.Close() + mlog.Error(tmr.Err.Error()) + time.Sleep(time.Second) panic(tmr.Err) } @@ -262,8 +261,8 @@ func (me *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Tea tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID} if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { utils.EnableDebugLogForTest() - l4g.Error(tmr.Err.Error()) - l4g.Close() + mlog.Error(tmr.Err.Error()) + time.Sleep(time.Second) panic(tmr.Err) } diff --git a/api/channel.go b/api/channel.go index 976007725..9c465412c 100644 --- a/api/channel.go +++ b/api/channel.go @@ -4,13 +4,13 @@ package api import ( + "fmt" "net/http" "strconv" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitChannel() { @@ -203,7 +203,7 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { } else { if oldChannelDisplayName != channel.DisplayName { if err := c.App.PostUpdateChannelDisplayNameMessage(c.Session.UserId, channel, oldChannelDisplayName, channel.DisplayName); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } c.LogAudit("name=" + channel.Name) @@ -251,7 +251,7 @@ func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) { return } else { if err := c.App.PostUpdateChannelHeaderMessage(c.Session.UserId, channel, oldChannelHeader, channelHeader); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } c.LogAudit("name=" + channel.Name) w.Write([]byte(channel.ToJson())) @@ -297,7 +297,7 @@ func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) { return } else { if err := c.App.PostUpdateChannelPurposeMessage(c.Session.UserId, channel, oldChannelPurpose, channelPurpose); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } c.LogAudit("name=" + channel.Name) w.Write([]byte(channel.ToJson())) @@ -318,7 +318,7 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) { if _, err := c.App.GetUser(c.Session.UserId); err != nil { c.Err = err c.RemoveSessionCookie(w, r) - l4g.Error(utils.T("api.channel.get_channels.error"), c.Session.UserId) + mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", c.Session.UserId), mlog.String("user_id", c.Session.UserId)) return } } diff --git a/api/context.go b/api/context.go index 1eb1e3f4f..8ebb5f73b 100644 --- a/api/context.go +++ b/api/context.go @@ -11,11 +11,11 @@ import ( "sync/atomic" "time" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" goi18n "github.com/nicksnyder/go-i18n/i18n" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -101,7 +101,7 @@ type handler struct { func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { now := time.Now() - l4g.Debug("%v", r.URL.Path) + mlog.Debug(fmt.Sprintf("%v", r.URL.Path)) c := &Context{} c.App = h.app @@ -146,7 +146,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { session, err := c.App.GetSession(token) if err != nil { - l4g.Error(utils.T("api.context.invalid_session.error"), err.Error()) + mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error())) c.RemoveSessionCookie(w, r) if h.requireUser || h.requireSystemAdmin { c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) @@ -268,14 +268,14 @@ func (c *Context) LogError(err *model.AppError) { if c.Path == "/api/v3/users/websocket" && err.StatusCode == 401 || err.Id == "web.check_browser_compatibility.app_error" { c.LogDebug(err) } else if err.Id != "api.post.create_post.town_square_read_only" { - l4g.Error(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) + mlog.Error(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, + c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) } } func (c *Context) LogDebug(err *model.AppError) { - l4g.Debug(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) + mlog.Debug(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, + c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) } func (c *Context) UserRequired() { @@ -387,7 +387,7 @@ func (c *Context) GetTeamURL() string { if !c.teamURLValid { c.SetTeamURLFromSession() if !c.teamURLValid { - l4g.Debug(utils.T("api.context.invalid_team_url.debug")) + mlog.Debug("Team URL accessed when not valid. Team URL should not be used in API functions or those that are team independent") } } return c.teamURL @@ -424,7 +424,7 @@ func IsApiCall(r *http.Request) bool { func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) { err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) - l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) + mlog.Debug(fmt.Sprintf("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))) if IsApiCall(r) { w.WriteHeader(err.StatusCode) diff --git a/api/user.go b/api/user.go index 35a3687b9..15fd4c7ea 100644 --- a/api/user.go +++ b/api/user.go @@ -11,12 +11,11 @@ import ( "strings" "time" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitUser() { @@ -245,7 +244,7 @@ func getMe(c *Context, w http.ResponseWriter, r *http.Request) { if user, err := c.App.GetUser(c.Session.UserId); err != nil { c.Err = err c.RemoveSessionCookie(w, r) - l4g.Error(utils.T("api.user.get_me.getting.error"), c.Session.UserId) + mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", c.Session.UserId), mlog.String("user_id", c.Session.UserId)) return } else if c.HandleEtag(user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress), "Get Me", w, r) { return @@ -1042,12 +1041,12 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) { var user *model.User var err *model.AppError if user, err = c.App.GetUser(c.Session.UserId); err != nil { - l4g.Warn(err.Error()) + mlog.Warn(err.Error()) return } if err := c.App.SendMfaChangeEmail(user.Email, activate, user.Locale, c.App.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) @@ -1171,7 +1170,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { if len(teamId) > 0 { c.App.Go(func() { if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } else { c.App.AddDirectChannels(teamId, user) } @@ -1185,7 +1184,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") c.App.Go(func() { if err := c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) } diff --git a/api/websocket.go b/api/websocket.go index 0da18d896..7f2c9c0db 100644 --- a/api/websocket.go +++ b/api/websocket.go @@ -4,12 +4,12 @@ package api import ( + "fmt" "net/http" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/websocket" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitWebSocket() { @@ -25,7 +25,7 @@ func connect(c *Context, w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - l4g.Error(utils.T("api.web_socket.connect.error"), err) + mlog.Error(fmt.Sprintf("websocket connect err: %v", err)) c.Err = model.NewAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "", http.StatusInternalServerError) return } diff --git a/api4/api.go b/api4/api.go index 88526e4d3..d36c3e3ee 100644 --- a/api4/api.go +++ b/api4/api.go @@ -4,11 +4,12 @@ package api4 import ( + "fmt" "net/http" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" @@ -243,7 +244,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { func Handle404(w http.ResponseWriter, r *http.Request) { err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) - l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) + mlog.Debug(fmt.Sprintf("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))) w.WriteHeader(err.StatusCode) err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'." diff --git a/api4/api_test.go b/api4/api_test.go index fd804b70d..2efd21f22 100644 --- a/api4/api_test.go +++ b/api4/api_test.go @@ -8,20 +8,29 @@ import ( "os" "testing" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" ) func TestMain(m *testing.M) { flag.Parse() + + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + utils.TranslationsPreInit() // In the case where a dev just wants to run a single test, it's faster to just use the default // store. if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - l4g.Info("-test.run used, not creating temporary containers") + mlog.Info("-test.run used, not creating temporary containers") os.Exit(m.Run()) } diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 4620c5f4e..48765687a 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -19,8 +19,8 @@ import ( "testing" "time" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" @@ -156,13 +156,13 @@ func (me *TestHelper) TearDown() { options := map[string]bool{} options[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true if result := <-me.App.Srv.Store.User().Search("", "fakeuser", options); result.Err != nil { - l4g.Error("Error tearing down test users") + mlog.Error("Error tearing down test users") } else { users := result.Data.([]*model.User) for _, u := range users { if err := me.App.PermanentDeleteUser(u); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } } @@ -171,13 +171,13 @@ func (me *TestHelper) TearDown() { go func() { defer wg.Done() if result := <-me.App.Srv.Store.Team().SearchByName("faketeam"); result.Err != nil { - l4g.Error("Error tearing down test teams") + mlog.Error("Error tearing down test teams") } else { teams := result.Data.([]*model.Team) for _, t := range teams { if err := me.App.PermanentDeleteTeam(t); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } } @@ -186,7 +186,7 @@ func (me *TestHelper) TearDown() { go func() { defer wg.Done() if result := <-me.App.Srv.Store.OAuth().GetApps(0, 1000); result.Err != nil { - l4g.Error("Error tearing down test oauth apps") + mlog.Error("Error tearing down test oauth apps") } else { apps := result.Data.([]*model.OAuthApp) @@ -450,8 +450,8 @@ func (me *TestHelper) UpdateActiveUser(user *model.User, active bool) { _, err := me.App.UpdateActive(user, active) if err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -464,8 +464,8 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { err := me.App.JoinUserToTeam(team, user, "") if err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -478,8 +478,8 @@ func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) member, err := me.App.AddUserToChannel(user, channel) if err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -784,8 +784,8 @@ func (me *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID + " " + model.TEAM_ADMIN_ROLE_ID} if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { utils.EnableDebugLogForTest() - l4g.Error(tmr.Err.Error()) - l4g.Close() + mlog.Error(tmr.Err.Error()) + time.Sleep(time.Second) panic(tmr.Err) } @@ -798,8 +798,8 @@ func (me *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Tea tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID} if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { utils.EnableDebugLogForTest() - l4g.Error(tmr.Err.Error()) - l4g.Close() + mlog.Error(tmr.Err.Error()) + time.Sleep(time.Second) panic(tmr.Err) } diff --git a/api4/channel.go b/api4/channel.go index 685c188bc..83fa8eb18 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -6,8 +6,7 @@ package api4 import ( "net/http" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -139,7 +138,7 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { } else { if oldChannelDisplayName != channel.DisplayName { if err := c.App.PostUpdateChannelDisplayNameMessage(c.Session.UserId, channel, oldChannelDisplayName, channel.DisplayName); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } diff --git a/api4/context.go b/api4/context.go index 9f3822633..c965e1d80 100644 --- a/api4/context.go +++ b/api4/context.go @@ -10,10 +10,10 @@ import ( "strings" "time" - l4g "github.com/alecthomas/log4go" goi18n "github.com/nicksnyder/go-i18n/i18n" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -90,7 +90,7 @@ type handler struct { func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { now := time.Now() - l4g.Debug("%v - %v", r.Method, r.URL.Path) + mlog.Debug(fmt.Sprintf("%v - %v", r.Method, r.URL.Path)) c := &Context{} c.App = h.app @@ -124,7 +124,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { session, err := c.App.GetSession(token) if err != nil { - l4g.Info(utils.T("api.context.invalid_session.error"), err.Error()) + mlog.Info(fmt.Sprintf("Invalid session err=%v", err.Error())) if err.StatusCode == http.StatusInternalServerError { c.Err = err } else if h.requireSession { @@ -220,19 +220,19 @@ func (c *Context) LogError(err *model.AppError) { err.Id == "web.check_browser_compatibility.app_error" { c.LogDebug(err) } else { - l4g.Error(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) + mlog.Error(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, + c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) } } func (c *Context) LogInfo(err *model.AppError) { - l4g.Info(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) + mlog.Info(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, + c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) } func (c *Context) LogDebug(err *model.AppError) { - l4g.Debug(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) + mlog.Debug(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, + c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) } func (c *Context) IsSystemAdmin() bool { diff --git a/api4/oauth.go b/api4/oauth.go index a173159b6..fa120ebbf 100644 --- a/api4/oauth.go +++ b/api4/oauth.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -463,7 +463,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { if err != nil { err.Translate(c.T) - l4g.Error(err.Error()) + mlog.Error(err.Error()) if action == model.OAUTH_ACTION_MOBILE { w.Write([]byte(err.ToJson())) } else { @@ -475,7 +475,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { user, err := c.App.CompleteOAuth(service, body, teamId, props) if err != nil { err.Translate(c.T) - l4g.Error(err.Error()) + mlog.Error(err.Error()) if action == model.OAUTH_ACTION_MOBILE { w.Write([]byte(err.ToJson())) } else { diff --git a/api4/plugin.go b/api4/plugin.go index ee7121ffb..37fbf12cd 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -8,7 +8,7 @@ package api4 import ( "net/http" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -17,7 +17,7 @@ const ( ) func (api *API) InitPlugin() { - l4g.Debug("EXPERIMENTAL: Initializing plugin api") + mlog.Debug("EXPERIMENTAL: Initializing plugin api") api.BaseRoutes.Plugins.Handle("", api.ApiSessionRequired(uploadPlugin)).Methods("POST") api.BaseRoutes.Plugins.Handle("", api.ApiSessionRequired(getPlugins)).Methods("GET") diff --git a/api4/system.go b/api4/system.go index c307a39b7..acb02bc3e 100644 --- a/api4/system.go +++ b/api4/system.go @@ -5,11 +5,12 @@ package api4 import ( "bytes" + "fmt" "io" "net/http" "runtime" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -61,7 +62,7 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) { rdata := map[string]string{} rdata["status"] = "unhealthy" - l4g.Warn(utils.T("api.system.go_routines"), actualGoroutines, *c.App.Config().ServiceSettings.GoroutineHealthThreshold) + mlog.Warn(fmt.Sprintf("The number of running goroutines is over the health threshold %v of %v", actualGoroutines, *c.App.Config().ServiceSettings.GoroutineHealthThreshold)) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(model.MapToJson(rdata))) @@ -229,7 +230,7 @@ func postLog(c *Context, w http.ResponseWriter, r *http.Request) { err.Where = "client" c.LogError(err) } else { - l4g.Debug(msg) + mlog.Debug(fmt.Sprint(msg)) } m["message"] = msg diff --git a/api4/system_test.go b/api4/system_test.go index b12421e62..c0fde6c39 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/stretchr/testify/assert" ) @@ -392,7 +392,7 @@ func TestGetLogs(t *testing.T) { Client := th.Client for i := 0; i < 20; i++ { - l4g.Info(i) + mlog.Info(fmt.Sprint(i)) } logs, resp := th.SystemAdminClient.GetLogs(0, 10) diff --git a/api4/user.go b/api4/user.go index e13bf9448..897c49ad1 100644 --- a/api4/user.go +++ b/api4/user.go @@ -9,8 +9,8 @@ import ( "strconv" "time" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -1177,7 +1177,7 @@ func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) { err = c.App.SendEmailVerification(user) if err != nil { // Don't want to leak whether the email is valid or not - l4g.Error(err.Error()) + mlog.Error(err.Error()) ReturnStatusOK(w) return } diff --git a/api4/webhook.go b/api4/webhook.go index a0e7b5785..fadc3fbf3 100644 --- a/api4/webhook.go +++ b/api4/webhook.go @@ -4,16 +4,15 @@ package api4 import ( + "fmt" "io" "net/http" "strings" - l4g "github.com/alecthomas/log4go" - "github.com/gorilla/mux" "github.com/gorilla/schema" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitWebhook() { @@ -492,7 +491,7 @@ func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) { } if c.App.Config().LogSettings.EnableWebhookDebugging { - l4g.Debug(utils.T("api.webhook.incoming.debug"), incomingWebhookPayload.ToJson()) + mlog.Debug(fmt.Sprint("Incoming webhook received. Content=", incomingWebhookPayload.ToJson())) } err = c.App.HandleIncomingWebhook(id, incomingWebhookPayload) diff --git a/api4/websocket.go b/api4/websocket.go index 7ea19224b..68125621a 100644 --- a/api4/websocket.go +++ b/api4/websocket.go @@ -4,12 +4,12 @@ package api4 import ( + "fmt" "net/http" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/websocket" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitWebSocket() { @@ -25,7 +25,7 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - l4g.Error(utils.T("api.web_socket.connect.error"), err) + mlog.Error(fmt.Sprintf("websocket connect err: %v", err)) c.Err = model.NewAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "", http.StatusInternalServerError) return } diff --git a/app/admin.go b/app/admin.go index 60b71505a..892e2d16b 100644 --- a/app/admin.go +++ b/app/admin.go @@ -13,7 +13,7 @@ import ( "net/http" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -137,7 +137,7 @@ func (a *App) InvalidateAllCaches() *model.AppError { } func (a *App) InvalidateAllCachesSkipSend() { - l4g.Info(utils.T("api.context.invalidate_all_caches")) + mlog.Info("Purging all caches") a.sessionCache.Purge() ClearStatusCache() a.Srv.Store.Channel().ClearCaches() @@ -209,7 +209,7 @@ func (a *App) SaveConfig(cfg *model.Config, sendConfigChangeClusterMessage bool) func (a *App) RecycleDatabaseConnection() { oldStore := a.Srv.Store - l4g.Warn(utils.T("api.admin.recycle_db_start.warn")) + mlog.Warn("Attempting to recycle the database connection.") a.Srv.Store = a.newStore() a.Jobs.Store = a.Srv.Store @@ -218,7 +218,7 @@ func (a *App) RecycleDatabaseConnection() { oldStore.Close() } - l4g.Warn(utils.T("api.admin.recycle_db_end.warn")) + mlog.Warn("Finished recycling the database connection.") } func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError { diff --git a/app/analytics.go b/app/analytics.go index f4e6fec95..7a32e78c1 100644 --- a/app/analytics.go +++ b/app/analytics.go @@ -4,7 +4,9 @@ package app import ( - l4g "github.com/alecthomas/log4go" + "fmt" + + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -22,7 +24,7 @@ func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *mo } else { systemUserCount = r.Data.(int64) if systemUserCount > int64(*a.Config().AnalyticsSettings.MaxUsersForStatistics) { - l4g.Debug("More than %v users on the system, intensive queries skipped", *a.Config().AnalyticsSettings.MaxUsersForStatistics) + mlog.Debug(fmt.Sprintf("More than %v users on the system, intensive queries skipped", *a.Config().AnalyticsSettings.MaxUsersForStatistics)) skipIntensiveQueries = true } } diff --git a/app/app.go b/app/app.go index 51ee37cbd..b31f67d6b 100644 --- a/app/app.go +++ b/app/app.go @@ -5,6 +5,7 @@ package app import ( "crypto/ecdsa" + "fmt" "html/template" "net" "net/http" @@ -13,13 +14,13 @@ import ( "sync" "sync/atomic" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/mattermost/mattermost-server/einterfaces" ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs" "github.com/mattermost/mattermost-server/jobs" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/plugin/pluginenv" "github.com/mattermost/mattermost-server/store" @@ -35,6 +36,8 @@ type App struct { Srv *Server + Log *mlog.Logger + PluginEnv *pluginenv.Environment PluginConfigListenerId string @@ -77,6 +80,7 @@ type App struct { sessionCache *utils.Cache configListenerId string licenseListenerId string + logListenerId string disableConfigWatch bool configWatcher *utils.ConfigWatcher asymmetricSigningKey *ecdsa.PrivateKey @@ -127,15 +131,23 @@ func New(options ...Option) (outApp *App, outErr error) { } model.AppErrorInit(utils.T) - // The first time we load config, clear any existing filters to allow the configuration - // changes to take effect. This is safe only because no one else is logging at this point. - l4g.Close() - if err := app.LoadConfig(app.configFile); err != nil { - // Re-initialize the default logger as we bail out. - l4g.Global = l4g.NewDefaultLogger(l4g.DEBUG) return nil, err } + + // Initalize logging + app.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&app.Config().LogSettings)) + + // Redirect default golang logger to this logger + mlog.RedirectStdLog(app.Log) + + // Use this app logger as the global logger (eventually remove all instances of global logging) + mlog.InitGlobalLogger(app.Log) + + app.logListenerId = app.AddConfigListener(func(_, after *model.Config) { + app.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings)) + }) + app.EnableConfigWatch() app.LoadTimezones() @@ -166,7 +178,7 @@ func New(options ...Option) (outApp *App, outErr error) { }) app.regenerateClientConfig() - l4g.Info(utils.T("api.server.new_server.init.info")) + mlog.Info("Server is initializing...") app.initEnterprise() @@ -177,7 +189,7 @@ func New(options ...Option) (outApp *App, outErr error) { } if htmlTemplateWatcher, err := utils.NewHTMLTemplateWatcher("templates"); err != nil { - l4g.Error(utils.T("api.api.init.parsing_templates.error"), err) + mlog.Error(fmt.Sprintf("Failed to parse server templates %v", err)) } else { app.htmlTemplateWatcher = htmlTemplateWatcher } @@ -210,7 +222,7 @@ func (a *App) configOrLicenseListener() { func (a *App) Shutdown() { appCount-- - l4g.Info(utils.T("api.server.stop_server.stopping.info")) + mlog.Info("Stopping Server...") a.StopServer() a.HubStop() @@ -229,7 +241,8 @@ func (a *App) Shutdown() { a.RemoveConfigListener(a.configListenerId) a.RemoveLicenseListener(a.licenseListenerId) - l4g.Info(utils.T("api.server.stop_server.stopped.info")) + a.RemoveConfigListener(a.logListenerId) + mlog.Info("Server stopped") a.DisableConfigWatch() } @@ -499,7 +512,7 @@ func (a *App) HTTPClient(trustURLs bool) *http.Client { func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) - l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) + mlog.Debug(fmt.Sprintf("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))) utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) } @@ -511,7 +524,7 @@ func (a *App) DoAdvancedPermissionsMigration() { return } - l4g.Info("Migrating roles to database.") + mlog.Info("Migrating roles to database.") roles := model.MakeDefaultRoles() roles = utils.SetRolePermissionsFromConfig(roles, a.Config(), a.License() != nil) @@ -521,8 +534,8 @@ func (a *App) DoAdvancedPermissionsMigration() { if result := <-a.Srv.Store.Role().Save(role); result.Err != nil { // If this failed for reasons other than the role already existing, don't mark the migration as done. if result2 := <-a.Srv.Store.Role().GetByName(role.Name); result2.Err != nil { - l4g.Critical("Failed to migrate role to database.") - l4g.Critical(result.Err) + mlog.Critical("Failed to migrate role to database.") + mlog.Critical(fmt.Sprint(result.Err)) allSucceeded = false } else { // If the role already existed, check it is the same and update if not. @@ -534,8 +547,8 @@ func (a *App) DoAdvancedPermissionsMigration() { role.Id = fetchedRole.Id if result := <-a.Srv.Store.Role().Save(role); result.Err != nil { // Role is not the same, but failed to update. - l4g.Critical("Failed to migrate role to database.") - l4g.Critical(result.Err) + mlog.Critical("Failed to migrate role to database.") + mlog.Critical(fmt.Sprint(result.Err)) allSucceeded = false } } @@ -553,7 +566,7 @@ func (a *App) DoAdvancedPermissionsMigration() { } if result := <-a.Srv.Store.System().Save(&system); result.Err != nil { - l4g.Critical("Failed to mark advanced permissions migration as completed.") - l4g.Critical(result.Err) + mlog.Critical("Failed to mark advanced permissions migration as completed.") + mlog.Critical(fmt.Sprint(result.Err)) } } diff --git a/app/app_test.go b/app/app_test.go index a726fc2b5..ccf7faeeb 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -9,10 +9,10 @@ import ( "os" "testing" - l4g "github.com/alecthomas/log4go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" @@ -20,12 +20,22 @@ import ( func TestMain(m *testing.M) { flag.Parse() + + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + utils.TranslationsPreInit() // In the case where a dev just wants to run a single test, it's faster to just use the default // store. if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - l4g.Info("-test.run used, not creating temporary containers") + mlog.Info("-test.run used, not creating temporary containers") os.Exit(m.Run()) } diff --git a/app/apptestlib.go b/app/apptestlib.go index a5c2db91c..626e932e8 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -11,9 +11,8 @@ import ( "path/filepath" "time" - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/plugin/pluginenv" @@ -169,8 +168,8 @@ func (me *TestHelper) CreateTeam() *model.Team { utils.DisableDebugLogForTest() var err *model.AppError if team, err = me.App.CreateTeam(team); err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -192,8 +191,8 @@ func (me *TestHelper) CreateUser() *model.User { utils.DisableDebugLogForTest() var err *model.AppError if user, err = me.App.CreateUser(user); err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -219,8 +218,8 @@ func (me *TestHelper) createChannel(team *model.Team, channelType string) *model utils.DisableDebugLogForTest() var err *model.AppError if channel, err = me.App.CreateChannel(channel, true); err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -233,8 +232,8 @@ func (me *TestHelper) CreateDmChannel(user *model.User) *model.Channel { var err *model.AppError var channel *model.Channel if channel, err = me.App.CreateDirectChannel(me.BasicUser.Id, user.Id); err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -255,8 +254,8 @@ func (me *TestHelper) CreatePost(channel *model.Channel) *model.Post { utils.DisableDebugLogForTest() var err *model.AppError if post, err = me.App.CreatePost(post, channel, false); err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -269,8 +268,8 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { err := me.App.JoinUserToTeam(team, user, "") if err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } @@ -283,8 +282,8 @@ func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) member, err := me.App.AddUserToChannel(user, channel) if err != nil { - l4g.Error(err.Error()) - l4g.Close() + mlog.Error(err.Error()) + time.Sleep(time.Second) panic(err) } diff --git a/app/authorization.go b/app/authorization.go index 2187472f7..f281b3e65 100644 --- a/app/authorization.go +++ b/app/authorization.go @@ -4,10 +4,11 @@ package app import ( + "fmt" "net/http" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -193,8 +194,8 @@ func (a *App) RolesGrantPermission(roleNames []string, permissionId string) bool if err != nil { // This should only happen if something is very broken. We can't realistically // recover the situation, so deny permission and log an error. - l4g.Error("Failed to get roles from database with role names: " + strings.Join(roleNames, ",")) - l4g.Error(err) + mlog.Error("Failed to get roles from database with role names: " + strings.Join(roleNames, ",")) + mlog.Error(fmt.Sprint(err)) return false } diff --git a/app/auto_responder.go b/app/auto_responder.go index 23402ecd4..aa7f243c4 100644 --- a/app/auto_responder.go +++ b/app/auto_responder.go @@ -4,8 +4,7 @@ package app import ( - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -28,7 +27,7 @@ func (a *App) SendAutoResponse(channel *model.Channel, receiver *model.User, roo } if _, err := a.CreatePost(autoResponderPost, channel, false); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } } diff --git a/app/auto_users.go b/app/auto_users.go index 8ed6767ad..b11f9c572 100644 --- a/app/auto_users.go +++ b/app/auto_users.go @@ -4,11 +4,10 @@ package app import ( + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" - - l4g "github.com/alecthomas/log4go" ) type AutoUserCreator struct { @@ -75,7 +74,7 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { result, err := cfg.client.CreateUserWithInvite(user, "", "", cfg.team.InviteId) if err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) return nil, false } @@ -83,7 +82,7 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { status := &model.Status{UserId: ruser.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} if result := <-cfg.app.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) return nil, false } diff --git a/app/channel.go b/app/channel.go index e7cd747c6..26e3d771c 100644 --- a/app/channel.go +++ b/app/channel.go @@ -9,7 +9,7 @@ import ( "strings" "time" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -60,17 +60,17 @@ func (a *App) JoinDefaultChannels(teamId string, user *model.User, channelRole s err = cmResult.Err } if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(user.Id, townSquare.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages { if requestor == nil { if err := a.postJoinTeamMessage(user, townSquare); err != nil { - l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) + mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) } } else { if err := a.postAddToTeamMessage(requestor, user, townSquare, ""); err != nil { - l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) + mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) } } } @@ -93,16 +93,16 @@ func (a *App) JoinDefaultChannels(teamId string, user *model.User, channelRole s err = cmResult.Err } if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(user.Id, offTopic.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } if requestor == nil { if err := a.postJoinChannelMessage(user, offTopic); err != nil { - l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) + mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) } } else { if err := a.PostAddToChannelMessage(requestor, user, offTopic, ""); err != nil { - l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) + mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) } } @@ -174,7 +174,7 @@ func (a *App) CreateChannel(channel *model.Channel, addMember bool) (*model.Chan return nil, cmresult.Err } if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(channel.CreatorId, sc.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } a.InvalidateCacheForUser(channel.CreatorId) @@ -227,10 +227,10 @@ func (a *App) createDirectChannel(userId string, otherUserId string) (*model.Cha channel := result.Data.(*model.Channel) if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId, channel.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(otherUserId, channel.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } return channel, nil @@ -258,7 +258,7 @@ func (a *App) WaitForChannelMembership(channelId string, userId string) { } } - l4g.Error("WaitForChannelMembership giving up channelId=%v userId=%v", channelId, userId) + mlog.Error(fmt.Sprintf("WaitForChannelMembership giving up channelId=%v userId=%v", channelId, userId), mlog.String("user_id", userId)) } } @@ -329,7 +329,7 @@ func (a *App) createGroupChannel(userIds []string, creatorId string) (*model.Cha return nil, result.Err } if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } } @@ -413,19 +413,19 @@ func (a *App) PatchChannel(channel *model.Channel, patch *model.ChannelPatch, us if oldChannelDisplayName != channel.DisplayName { if err := a.PostUpdateChannelDisplayNameMessage(userId, channel, oldChannelDisplayName, channel.DisplayName); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } if channel.Header != oldChannelHeader { if err := a.PostUpdateChannelHeaderMessage(userId, channel, oldChannelHeader, channel.Header); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } if channel.Purpose != oldChannelPurpose { if err := a.PostUpdateChannelPurposeMessage(userId, channel, oldChannelPurpose, channel.Purpose); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } @@ -536,21 +536,21 @@ func (a *App) DeleteChannel(channel *model.Channel, userId string) *model.AppErr } if _, err := a.CreatePost(post, channel, false); err != nil { - l4g.Error(utils.T("api.channel.delete_channel.failed_post.error"), err) + mlog.Error(fmt.Sprintf("Failed to post archive message %v", err)) } } now := model.GetMillis() for _, hook := range incomingHooks { if result := <-a.Srv.Store.Webhook().DeleteIncoming(hook.Id, now); result.Err != nil { - l4g.Error(utils.T("api.channel.delete_channel.incoming_webhook.error"), hook.Id) + mlog.Error(fmt.Sprintf("Encountered error deleting incoming webhook, id=%v", hook.Id)) } a.InvalidateCacheForWebhook(hook.Id) } for _, hook := range outgoingHooks { if result := <-a.Srv.Store.Webhook().DeleteOutgoing(hook.Id, now); result.Err != nil { - l4g.Error(utils.T("api.channel.delete_channel.outgoing_webhook.error"), hook.Id) + mlog.Error(fmt.Sprintf("Encountered error deleting outgoing webhook, id=%v", hook.Id)) } } @@ -594,13 +594,13 @@ func (a *App) addUserToChannel(user *model.User, channel *model.Channel, teamMem Roles: model.CHANNEL_USER_ROLE_ID, } if result := <-a.Srv.Store.Channel().SaveMember(newMember); result.Err != nil { - l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", user.Id, channel.Id, result.Err) + mlog.Error(fmt.Sprintf("Failed to add member user_id=%v channel_id=%v err=%v", user.Id, channel.Id, result.Err), mlog.String("user_id", user.Id)) return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil, "", http.StatusInternalServerError) } a.WaitForChannelMembership(channel.Id, user.Id) if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } a.InvalidateCacheForUser(user.Id) @@ -1449,10 +1449,10 @@ func (a *App) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model. channel := result.Data.(*model.Channel) if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId1, channel.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId2, channel.Id, model.GetMillis()); result.Err != nil { - l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err) + mlog.Warn(fmt.Sprintf("Failed to update ChannelMemberHistory table %v", result.Err)) } return channel, nil diff --git a/app/cluster_discovery.go b/app/cluster_discovery.go index 2682425f5..f7443680c 100644 --- a/app/cluster_discovery.go +++ b/app/cluster_discovery.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -36,36 +36,36 @@ func (me *ClusterDiscoveryService) Start() { <-me.app.Srv.Store.ClusterDiscovery().Cleanup() if cresult := <-me.app.Srv.Store.ClusterDiscovery().Exists(&me.ClusterDiscovery); cresult.Err != nil { - l4g.Error(fmt.Sprintf("ClusterDiscoveryService failed to check if row exists for %v with err=%v", me.ClusterDiscovery.ToJson(), cresult.Err)) + mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to check if row exists for %v with err=%v", me.ClusterDiscovery.ToJson(), cresult.Err)) } else { if cresult.Data.(bool) { if u := <-me.app.Srv.Store.ClusterDiscovery().Delete(&me.ClusterDiscovery); u.Err != nil { - l4g.Error(fmt.Sprintf("ClusterDiscoveryService failed to start clean for %v with err=%v", me.ClusterDiscovery.ToJson(), u.Err)) + mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to start clean for %v with err=%v", me.ClusterDiscovery.ToJson(), u.Err)) } } } if result := <-me.app.Srv.Store.ClusterDiscovery().Save(&me.ClusterDiscovery); result.Err != nil { - l4g.Error(fmt.Sprintf("ClusterDiscoveryService failed to save for %v with err=%v", me.ClusterDiscovery.ToJson(), result.Err)) + mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to save for %v with err=%v", me.ClusterDiscovery.ToJson(), result.Err)) return } go func() { - l4g.Debug(fmt.Sprintf("ClusterDiscoveryService ping writer started for %v", me.ClusterDiscovery.ToJson())) + mlog.Debug(fmt.Sprintf("ClusterDiscoveryService ping writer started for %v", me.ClusterDiscovery.ToJson())) ticker := time.NewTicker(DISCOVERY_SERVICE_WRITE_PING) defer func() { ticker.Stop() if u := <-me.app.Srv.Store.ClusterDiscovery().Delete(&me.ClusterDiscovery); u.Err != nil { - l4g.Error(fmt.Sprintf("ClusterDiscoveryService failed to cleanup for %v with err=%v", me.ClusterDiscovery.ToJson(), u.Err)) + mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to cleanup for %v with err=%v", me.ClusterDiscovery.ToJson(), u.Err)) } - l4g.Debug(fmt.Sprintf("ClusterDiscoveryService ping writer stopped for %v", me.ClusterDiscovery.ToJson())) + mlog.Debug(fmt.Sprintf("ClusterDiscoveryService ping writer stopped for %v", me.ClusterDiscovery.ToJson())) }() for { select { case <-ticker.C: if u := <-me.app.Srv.Store.ClusterDiscovery().SetLastPingAt(&me.ClusterDiscovery); u.Err != nil { - l4g.Error(fmt.Sprintf("ClusterDiscoveryService failed to write ping for %v with err=%v", me.ClusterDiscovery.ToJson(), u.Err)) + mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to write ping for %v with err=%v", me.ClusterDiscovery.ToJson(), u.Err)) } case <-me.stop: return diff --git a/app/command.go b/app/command.go index 039952cf0..796d656a7 100644 --- a/app/command.go +++ b/app/command.go @@ -10,7 +10,7 @@ import ( "net/url" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" goi18n "github.com/nicksnyder/go-i18n/i18n" @@ -207,7 +207,7 @@ func (a *App) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, * teamCmds := result.Data.([]*model.Command) for _, cmd := range teamCmds { if trigger == cmd.Trigger { - l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, args.UserId)) + mlog.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, args.UserId)) p := url.Values{} p.Set("token", cmd.Token) @@ -308,7 +308,7 @@ func (a *App) HandleCommandResponse(command *model.Command, args *model.CommandA response.Attachments = a.ProcessSlackAttachments(response.Attachments) if _, err := a.CreateCommandPost(post, args.TeamId, response); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } return response, nil diff --git a/app/command_echo.go b/app/command_echo.go index 9fef8a0a9..f0851964b 100644 --- a/app/command_echo.go +++ b/app/command_echo.go @@ -4,11 +4,12 @@ package app import ( + "fmt" "strconv" "strings" "time" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" goi18n "github.com/nicksnyder/go-i18n/i18n" ) @@ -89,7 +90,7 @@ func (me *EchoProvider) DoCommand(a *App, args *model.CommandArgs, message strin time.Sleep(time.Duration(delay) * time.Second) if _, err := a.CreatePostMissingChannel(post, true); err != nil { - l4g.Error(args.T("api.command_echo.create.app_error"), err) + mlog.Error(fmt.Sprintf("Unable to create /echo post, err=%v", err)) } }) diff --git a/app/command_groupmsg.go b/app/command_groupmsg.go index 32ad431ff..0e783e1a8 100644 --- a/app/command_groupmsg.go +++ b/app/command_groupmsg.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" goi18n "github.com/nicksnyder/go-i18n/i18n" ) @@ -95,7 +95,7 @@ func (me *groupmsgProvider) DoCommand(a *App, args *model.CommandArgs, message s groupChannel, channelErr := a.CreateGroupChannel(targetUsersSlice, args.UserId) if channelErr != nil { - l4g.Error(channelErr.Error()) + mlog.Error(channelErr.Error()) return &model.CommandResponse{Text: args.T("api.command_groupmsg.group_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } diff --git a/app/command_invite.go b/app/command_invite.go index ce443bf3d..9045365ad 100644 --- a/app/command_invite.go +++ b/app/command_invite.go @@ -4,9 +4,10 @@ package app import ( + "fmt" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" goi18n "github.com/nicksnyder/go-i18n/i18n" ) @@ -41,7 +42,7 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str return &model.CommandResponse{Text: args.T("api.command_invite.missing_message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } - l4g.Debug(message) + mlog.Debug(fmt.Sprint(message)) splitMessage := strings.SplitN(message, " ", 2) targetUsername := splitMessage[0] @@ -49,7 +50,7 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str var userProfile *model.User if result := <-a.Srv.Store.User().GetByUsername(targetUsername); result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) return &model.CommandResponse{Text: args.T("api.command_invite.missing_user.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } else { userProfile = result.Data.(*model.User) diff --git a/app/command_invite_people.go b/app/command_invite_people.go index e463bd37c..e5ff5a316 100644 --- a/app/command_invite_people.go +++ b/app/command_invite_people.go @@ -6,7 +6,7 @@ package app import ( "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" goi18n "github.com/nicksnyder/go-i18n/i18n" ) @@ -63,7 +63,7 @@ func (me *InvitePeopleProvider) DoCommand(a *App, args *model.CommandArgs, messa } if err := a.InviteNewUsersToTeam(emailList, args.TeamId, args.UserId); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.fail")} } diff --git a/app/command_loadtest.go b/app/command_loadtest.go index e43a7113f..a4a8f003a 100644 --- a/app/command_loadtest.go +++ b/app/command_loadtest.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" goi18n "github.com/nicksnyder/go-i18n/i18n" @@ -177,10 +177,10 @@ func (me *LoadTestProvider) SetupCommand(a *App, args *model.CommandArgs, messag if !err { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } else { - l4g.Info("Testing environment created") + mlog.Info("Testing environment created") for i := 0; i < len(environment.Teams); i++ { - l4g.Info("Team Created: " + environment.Teams[i].Name) - l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) + mlog.Info("Team Created: " + environment.Teams[i].Name) + mlog.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) } } } else { diff --git a/app/command_msg.go b/app/command_msg.go index cf0e90c74..6877c10d6 100644 --- a/app/command_msg.go +++ b/app/command_msg.go @@ -6,7 +6,7 @@ package app import ( "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" goi18n "github.com/nicksnyder/go-i18n/i18n" ) @@ -50,7 +50,7 @@ func (me *msgProvider) DoCommand(a *App, args *model.CommandArgs, message string var userProfile *model.User if result := <-a.Srv.Store.User().GetByUsername(targetUsername); result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } else { userProfile = result.Data.(*model.User) @@ -67,13 +67,13 @@ func (me *msgProvider) DoCommand(a *App, args *model.CommandArgs, message string if channel := <-a.Srv.Store.Channel().GetByName(args.TeamId, channelName, true); channel.Err != nil { if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { if directChannel, err := a.CreateDirectChannel(args.UserId, userProfile.Id); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) return &model.CommandResponse{Text: args.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } else { targetChannelId = directChannel.Id } } else { - l4g.Error(channel.Err.Error()) + mlog.Error(channel.Err.Error()) return &model.CommandResponse{Text: args.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } } else { diff --git a/app/command_mute_test.go b/app/command_mute_test.go index 9f82c48cc..18ced3ddf 100644 --- a/app/command_mute_test.go +++ b/app/command_mute_test.go @@ -4,11 +4,12 @@ package app import ( + "testing" + "time" + "github.com/mattermost/mattermost-server/model" "github.com/nicksnyder/go-i18n/i18n" "github.com/stretchr/testify/assert" - "testing" - "time" ) func TestMuteCommandNoChannel(t *testing.T) { diff --git a/app/command_remove.go b/app/command_remove.go index a9c21e2d5..3671a2063 100644 --- a/app/command_remove.go +++ b/app/command_remove.go @@ -6,9 +6,9 @@ package app import ( "strings" - l4g "github.com/alecthomas/log4go" goi18n "github.com/nicksnyder/go-i18n/i18n" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -93,7 +93,7 @@ func doCommand(a *App, args *model.CommandArgs, message string) *model.CommandRe var userProfile *model.User if result := <-a.Srv.Store.User().GetByUsername(targetUsername); result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) return &model.CommandResponse{Text: args.T("api.command_remove.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } else { userProfile = result.Data.(*model.User) diff --git a/app/config.go b/app/config.go index 75d38e24a..b4fbfe725 100644 --- a/app/config.go +++ b/app/config.go @@ -17,8 +17,7 @@ import ( "strconv" "strings" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -60,9 +59,6 @@ func (a *App) LoadConfig(configFile string) *model.AppError { a.configFile = configPath - utils.ConfigureLog(&cfg.LogSettings) - l4g.Info("Using config file at %s", configPath) - a.config.Store(cfg) a.envConfig = envConfig @@ -101,7 +97,7 @@ func (a *App) EnableConfigWatch() { a.ReloadConfig() }) if err != nil { - l4g.Error(err) + mlog.Error(fmt.Sprint(err)) } a.configWatcher = configWatcher } diff --git a/app/diagnostics.go b/app/diagnostics.go index 6e9adbf3c..cda0814ca 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -5,11 +5,10 @@ package app import ( "encoding/json" - "log" - "os" "runtime" "sync/atomic" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/segmentio/analytics-go" ) @@ -69,12 +68,12 @@ func (a *App) SendDailyDiagnostics() { func (a *App) initDiagnostics(endpoint string) { if client == nil { client = analytics.New(SEGMENT_KEY) + client.Logger = a.Log.StdLog(mlog.String("source", "segment")) // For testing if endpoint != "" { client.Endpoint = endpoint client.Verbose = true client.Size = 1 - client.Logger = log.New(os.Stdout, "segment ", log.LstdFlags) } client.Identify(&analytics.Identify{ UserId: a.DiagnosticId(), @@ -299,10 +298,11 @@ func (a *App) trackConfig() { a.SendDiagnostic(TRACK_CONFIG_LOG, map[string]interface{}{ "enable_console": cfg.LogSettings.EnableConsole, "console_level": cfg.LogSettings.ConsoleLevel, + "console_json": *cfg.LogSettings.ConsoleJson, "enable_file": cfg.LogSettings.EnableFile, "file_level": cfg.LogSettings.FileLevel, + "file_json": cfg.LogSettings.FileJson, "enable_webhook_debugging": cfg.LogSettings.EnableWebhookDebugging, - "isdefault_file_format": isDefault(cfg.LogSettings.FileFormat, ""), "isdefault_file_location": isDefault(cfg.LogSettings.FileLocation, ""), }) diff --git a/app/email.go b/app/email.go index 7a50fd82a..aa05cefdb 100644 --- a/app/email.go +++ b/app/email.go @@ -9,9 +9,9 @@ import ( "net/http" - l4g "github.com/alecthomas/log4go" "github.com/nicksnyder/go-i18n/i18n" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -282,17 +282,17 @@ func (a *App) SendInviteEmails(team *model.Team, senderName string, invites []st data := model.MapToJson(props) if result := <-a.Srv.Store.Token().Save(token); result.Err != nil { - l4g.Error(utils.T("api.team.invite_members.send.error"), result.Err) + mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", result.Err)) continue } bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(data), url.QueryEscape(token.Token)) if !a.Config().EmailSettings.SendEmailNotifications { - l4g.Info(utils.T("api.team.invite_members.sending.info"), invite, bodyPage.Props["Link"]) + mlog.Info(fmt.Sprintf("sending invitation to %v %v", invite, bodyPage.Props["Link"])) } if err := a.SendMail(invite, subject, bodyPage.Render()); err != nil { - l4g.Error(utils.T("api.team.invite_members.send.error"), err) + mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", err)) } } } diff --git a/app/email_batching.go b/app/email_batching.go index 07adda674..e1ea7abb5 100644 --- a/app/email_batching.go +++ b/app/email_batching.go @@ -10,12 +10,12 @@ import ( "sync" "time" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" "net/http" - l4g "github.com/alecthomas/log4go" "github.com/nicksnyder/go-i18n/i18n" ) @@ -41,7 +41,7 @@ func (a *App) AddNotificationEmailToBatch(user *model.User, post *model.Post, te } if !a.EmailBatching.Add(user, post, team) { - l4g.Error(utils.T("api.email_batching.add_notification_email_to_batch.channel_full.app_error")) + mlog.Error("Email batching job's receiving channel was full. Please increase the EmailBatchingBufferSize.") return model.NewAppError("AddNotificationEmailToBatch", "api.email_batching.add_notification_email_to_batch.channel_full.app_error", nil, "", http.StatusInternalServerError) } @@ -71,7 +71,7 @@ func NewEmailBatchingJob(a *App, bufferSize int) *EmailBatchingJob { } func (job *EmailBatchingJob) Start() { - l4g.Debug(utils.T("api.email_batching.start.starting"), *job.app.Config().EmailSettings.EmailBatchingInterval) + mlog.Debug(fmt.Sprintf("Email batching job starting. Checking for pending emails every %v seconds.", *job.app.Config().EmailSettings.EmailBatchingInterval)) newTask := model.CreateRecurringTask(EMAIL_BATCHING_TASK_NAME, job.CheckPendingEmails, time.Duration(*job.app.Config().EmailSettings.EmailBatchingInterval)*time.Second) job.taskMutex.Lock() @@ -107,7 +107,7 @@ func (job *EmailBatchingJob) CheckPendingEmails() { // without actually sending emails job.checkPendingNotifications(time.Now(), job.app.sendBatchedEmailNotification) - l4g.Debug(utils.T("api.email_batching.check_pending_emails.finished_running"), len(job.pendingNotifications)) + mlog.Debug(fmt.Sprintf("Email batching job ran. %v user(s) still have notifications pending.", len(job.pendingNotifications))) } func (job *EmailBatchingJob) handleNewNotifications() { @@ -141,7 +141,7 @@ func (job *EmailBatchingJob) checkPendingNotifications(now time.Time, handler fu } tchan := job.app.Srv.Store.Team().GetByName(notifications[0].teamName) if result := <-tchan; result.Err != nil { - l4g.Error("Unable to find Team id for notification", result.Err) + mlog.Error(fmt.Sprint("Unable to find Team id for notification", result.Err)) continue } else if team, ok := result.Data.(*model.Team); ok { inspectedTeamNames[notification.teamName] = team.Id @@ -151,12 +151,12 @@ func (job *EmailBatchingJob) checkPendingNotifications(now time.Time, handler fu // all queued notifications mchan := job.app.Srv.Store.Channel().GetMembersForUser(inspectedTeamNames[notification.teamName], userId) if result := <-mchan; result.Err != nil { - l4g.Error("Unable to find ChannelMembers for user", result.Err) + mlog.Error(fmt.Sprint("Unable to find ChannelMembers for user", result.Err)) continue } else if channelMembers, ok := result.Data.(*model.ChannelMembers); ok { for _, channelMember := range *channelMembers { if channelMember.LastViewedAt >= batchStartTime { - l4g.Debug("Deleted notifications for user %s", userId) + mlog.Debug(fmt.Sprintf("Deleted notifications for user %s", userId), mlog.String("user_id", userId)) delete(job.pendingNotifications, userId) break } @@ -198,7 +198,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch var user *model.User if result := <-uchan; result.Err != nil { - l4g.Warn("api.email_batching.send_batched_email_notification.user.app_error") + mlog.Warn("api.email_batching.send_batched_email_notification.user.app_error") return } else { user = result.Data.(*model.User) @@ -212,7 +212,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch var sender *model.User schan := a.Srv.Store.User().Get(notification.post.UserId) if result := <-schan; result.Err != nil { - l4g.Warn(utils.T("api.email_batching.render_batched_post.sender.app_error")) + mlog.Warn("Unable to find sender of post for batched email notification") continue } else { sender = result.Data.(*model.User) @@ -221,7 +221,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch var channel *model.Channel cchan := a.Srv.Store.Channel().Get(notification.post.ChannelId, true) if result := <-cchan; result.Err != nil { - l4g.Warn(utils.T("api.email_batching.render_batched_post.channel.app_error")) + mlog.Warn("Unable to find channel of post for batched email notification") continue } else { channel = result.Data.(*model.Channel) @@ -250,7 +250,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch body.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications)) if err := a.SendMail(user.Email, subject, body.Render()); err != nil { - l4g.Warn(utils.T("api.email_batchings.send_batched_email_notification.send.app_error"), user.Email, err) + mlog.Warn(fmt.Sprint("api.email_batchings.send_batched_email_notification.send.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", user.Email, err)) } } diff --git a/app/emoji.go b/app/emoji.go index eebe59ccf..f48501cf9 100644 --- a/app/emoji.go +++ b/app/emoji.go @@ -5,6 +5,7 @@ package app import ( "bytes" + "fmt" "image" "image/draw" "image/gif" @@ -14,13 +15,11 @@ import ( "mime/multipart" "net/http" - l4g "github.com/alecthomas/log4go" - "image/color/palette" "github.com/disintegration/imaging" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) const ( @@ -242,13 +241,13 @@ func imageToPaletted(img image.Image) *image.Paletted { func (a *App) deleteEmojiImage(id string) { if err := a.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil { - l4g.Error("Failed to rename image when deleting emoji %v", id) + mlog.Error(fmt.Sprintf("Failed to rename image when deleting emoji %v", id)) } } func (a *App) deleteReactionsForEmoji(emojiName string) { if result := <-a.Srv.Store.Reaction().DeleteAllWithEmojiName(emojiName); result.Err != nil { - l4g.Warn(utils.T("api.emoji.delete.delete_reactions.app_error"), emojiName) - l4g.Warn(result.Err) + mlog.Warn(fmt.Sprintf("Unable to delete reactions when deleting emoji with emoji name %v", emojiName)) + mlog.Warn(fmt.Sprint(result.Err)) } } diff --git a/app/file.go b/app/file.go index 06ee61c92..a1addd7ac 100644 --- a/app/file.go +++ b/app/file.go @@ -22,11 +22,11 @@ import ( "sync" "time" - l4g "github.com/alecthomas/log4go" "github.com/disintegration/imaging" "github.com/rwcarlsen/goexif/exif" _ "golang.org/x/image/bmp" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -98,7 +98,7 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension} split := strings.SplitN(filename, "/", 5) if len(split) < 5 { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.unexpected_filename.error"), post.Id, filename) + mlog.Error(fmt.Sprintf("Unable to decipher filename when migrating post to use FileInfos, post_id=%v, filename=%v", post.Id, filename), mlog.String("post_id", post.Id)) return nil } @@ -108,7 +108,7 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin name, _ := url.QueryUnescape(split[4]) if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") { - l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.mismatched_filename.warn"), post.Id, post.ChannelId, post.UserId, filename) + mlog.Warn(fmt.Sprintf("Found an unusual filename when migrating post to use FileInfos, post_id=%v, channel_id=%v, user_id=%v, filename=%v", post.Id, post.ChannelId, post.UserId, filename), mlog.String("post_id", post.Id)) } pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId) @@ -117,13 +117,13 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin // Open the file and populate the fields of the FileInfo var info *model.FileInfo if data, err := a.ReadFile(path); err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.file_not_found.error"), post.Id, filename, path, err) + mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.file_not_found.error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, filename, path, err), mlog.String("post_id", post.Id)) return nil } else { var err *model.AppError info, err = model.GetInfoForBytes(name, data) if err != nil { - l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.info.app_error"), post.Id, filename, err) + mlog.Warn(fmt.Sprintf("Unable to fully decode file info when migrating post to use FileInfos, post_id=%v, filename=%v, err=%v", post.Id, filename, err), mlog.String("post_id", post.Id)) } } @@ -151,7 +151,7 @@ func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string { // This post is in a direct channel so we need to figure out what team the files are stored under. if result := <-a.Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.teams.app_error"), post.Id, result.Err) + mlog.Error(fmt.Sprintf("Unable to get teams when migrating post to use FileInfos, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) } else if teams := result.Data.([]*model.Team); len(teams) == 1 { // The user has only one team so the post must've been sent from it return teams[0].Id @@ -173,7 +173,7 @@ var fileMigrationLock sync.Mutex // Creates and stores FileInfos for a post created before the FileInfos table existed. func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { if len(post.Filenames) == 0 { - l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.no_filenames.warn"), post.Id) + mlog.Warn(fmt.Sprintf("Unable to migrate post to use FileInfos with an empty Filenames field, post_id=%v", post.Id), mlog.String("post_id", post.Id)) return []*model.FileInfo{} } @@ -184,7 +184,7 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { var channel *model.Channel if result := <-cchan; result.Err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.channel.app_error"), post.Id, post.ChannelId, result.Err) + mlog.Error(fmt.Sprintf("Unable to get channel when migrating post to use FileInfos, post_id=%v, channel_id=%v, err=%v", post.Id, post.ChannelId, result.Err), mlog.String("post_id", post.Id)) return []*model.FileInfo{} } else { channel = result.Data.(*model.Channel) @@ -202,7 +202,7 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { // Create FileInfo objects for this post infos := make([]*model.FileInfo, 0, len(filenames)) if teamId == "" { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.team_id.error"), post.Id, filenames) + mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.team_id.error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, filenames), mlog.String("post_id", post.Id)) } else { for _, filename := range filenames { info := a.GetInfoForFilename(post, teamId, filename) @@ -219,26 +219,26 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { defer fileMigrationLock.Unlock() if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_again.app_error"), post.Id, result.Err) + mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.get_post_again.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, result.Err), mlog.String("post_id", post.Id)) return []*model.FileInfo{} } else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) { // Another thread has already created FileInfos for this post, so just return those if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, false); result.Err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error"), post.Id, result.Err) + mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, result.Err), mlog.String("post_id", post.Id)) return []*model.FileInfo{} } else { - l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.not_migrating_post.debug"), post.Id) + mlog.Debug(fmt.Sprintf("Post already migrated to use FileInfos, post_id=%v", post.Id), mlog.String("post_id", post.Id)) return result.Data.([]*model.FileInfo) } } - l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.migrating_post.debug"), post.Id) + mlog.Debug(fmt.Sprintf("Migrating post to use FileInfos, post_id=%v", post.Id), mlog.String("post_id", post.Id)) savedInfos := make([]*model.FileInfo, 0, len(infos)) fileIds := make([]string, 0, len(filenames)) for _, info := range infos { if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_file_info.app_error"), post.Id, info.Id, info.Path, result.Err) + mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.save_file_info.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, info.Id, info.Path, result.Err), mlog.String("post_id", post.Id)) continue } @@ -255,7 +255,7 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { // Update Posts to clear Filenames and set FileIds if result := <-a.Srv.Store.Post().Update(newPost, post); result.Err != nil { - l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_post.app_error"), post.Id, newPost.FileIds, post.Filenames, result.Err) + mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.save_post.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, newPost.FileIds, post.Filenames, result.Err), mlog.String("post_id", post.Id)) return []*model.FileInfo{} } else { return savedInfos @@ -415,7 +415,7 @@ func prepareImage(fileData []byte) (*image.Image, int, int) { // Decode image bytes into Image object img, imgType, err := image.Decode(bytes.NewReader(fileData)) if err != nil { - l4g.Error(utils.T("api.file.handle_images_forget.decode.error"), err) + mlog.Error(fmt.Sprintf("Unable to decode image err=%v", err)) return nil, 0, 0 } @@ -492,12 +492,12 @@ func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, widt buf := new(bytes.Buffer) if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil { - l4g.Error(utils.T("api.file.handle_images_forget.encode_jpeg.error"), thumbnailPath, err) + mlog.Error(fmt.Sprintf("Unable to encode image as jpeg path=%v err=%v", thumbnailPath, err)) return } if err := a.WriteFile(buf.Bytes(), thumbnailPath); err != nil { - l4g.Error(utils.T("api.file.handle_images_forget.upload_thumb.error"), thumbnailPath, err) + mlog.Error(fmt.Sprintf("Unable to upload thumbnail path=%v err=%v", thumbnailPath, err)) return } } @@ -514,12 +514,12 @@ func (a *App) generatePreviewImage(img image.Image, previewPath string, width in buf := new(bytes.Buffer) if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil { - l4g.Error(utils.T("api.file.handle_images_forget.encode_preview.error"), previewPath, err) + mlog.Error(fmt.Sprintf("Unable to encode image as preview jpg path=%v err=%v", previewPath, err)) return } if err := a.WriteFile(buf.Bytes(), previewPath); err != nil { - l4g.Error(utils.T("api.file.handle_images_forget.upload_preview.error"), previewPath, err) + mlog.Error(fmt.Sprintf("Unable to upload preview path=%v err=%v", previewPath, err)) return } } diff --git a/app/import.go b/app/import.go index 23a315be7..fc0bfafa6 100644 --- a/app/import.go +++ b/app/import.go @@ -7,6 +7,7 @@ import ( "bufio" "bytes" "encoding/json" + "fmt" "io" "net/http" "os" @@ -16,11 +17,9 @@ import ( "time" "unicode/utf8" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" ) // Import Data Models @@ -715,10 +714,10 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { if data.ProfileImage != nil { file, err := os.Open(*data.ProfileImage) if err != nil { - l4g.Error(utils.T("api.import.import_user.profile_image.error"), err) + mlog.Error(fmt.Sprint("api.import.import_user.profile_image.error FIXME: NOT FOUND IN TRANSLATIONS FILE", err)) } if err := a.SetProfileImageFromFile(savedUser.Id, file); err != nil { - l4g.Error(utils.T("api.import.import_user.profile_image.error"), err) + mlog.Error(fmt.Sprint("api.import.import_user.profile_image.error FIXME: NOT FOUND IN TRANSLATIONS FILE", err)) } } @@ -1654,12 +1653,12 @@ func (a *App) OldImportPost(post *model.Post) { post.Hashtags, _ = model.ParseHashtags(post.Message) if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { - l4g.Debug(utils.T("api.import.import_post.saving.debug"), post.UserId, post.Message) + mlog.Debug(fmt.Sprintf("Error saving post. user=%v, message=%v", post.UserId, post.Message)) } for _, fileId := range post.FileIds { if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { - l4g.Error(utils.T("api.import.import_post.attach_files.error"), post.Id, post.FileIds, result.Err) + mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, result.Err), mlog.String("post_id", post.Id)) } } @@ -1675,17 +1674,17 @@ func (a *App) OldImportUser(team *model.Team, user *model.User) *model.User { user.Roles = model.SYSTEM_USER_ROLE_ID if result := <-a.Srv.Store.User().Save(user); result.Err != nil { - l4g.Error(utils.T("api.import.import_user.saving.error"), result.Err) + mlog.Error(fmt.Sprintf("Error saving user. err=%v", result.Err)) return nil } else { ruser := result.Data.(*model.User) if cresult := <-a.Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { - l4g.Error(utils.T("api.import.import_user.set_email.error"), cresult.Err) + mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", cresult.Err)) } if err := a.JoinUserToTeam(team, user, ""); err != nil { - l4g.Error(utils.T("api.import.import_user.join_team.error"), err) + mlog.Error(fmt.Sprintf("Failed to join team when importing err=%v", err)) } return ruser diff --git a/app/ldap.go b/app/ldap.go index ff7a5ed21..22c3b746b 100644 --- a/app/ldap.go +++ b/app/ldap.go @@ -4,9 +4,10 @@ package app import ( + "fmt" "net/http" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -18,7 +19,7 @@ func (a *App) SyncLdap() { if ldapI := a.Ldap; ldapI != nil { ldapI.StartSynchronizeJob(false) } else { - l4g.Error("%v", model.NewAppError("SyncLdap", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented).Error()) + mlog.Error(fmt.Sprintf("%v", model.NewAppError("SyncLdap", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented).Error())) } } }) @@ -68,7 +69,7 @@ func (a *App) SwitchEmailToLdap(email, password, code, ldapId, ldapPassword stri a.Go(func() { if err := a.SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) @@ -114,7 +115,7 @@ func (a *App) SwitchLdapToEmail(ldapPassword, code, email, newPassword string) ( a.Go(func() { if err := a.SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) diff --git a/app/license.go b/app/license.go index 148b10317..310a61fdb 100644 --- a/app/license.go +++ b/app/license.go @@ -9,8 +9,7 @@ import ( "net/http" "strings" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -30,7 +29,7 @@ func (a *App) LoadLicense() { if license != nil { if _, err := a.SaveLicense(licenseBytes); err != nil { - l4g.Info("Failed to save license key loaded from disk err=%v", err.Error()) + mlog.Info(fmt.Sprintf("Failed to save license key loaded from disk err=%v", err.Error())) } else { licenseId = license.Id } @@ -40,9 +39,9 @@ func (a *App) LoadLicense() { if result := <-a.Srv.Store.License().Get(licenseId); result.Err == nil { record := result.Data.(*model.LicenseRecord) a.ValidateAndSetLicenseBytes([]byte(record.Bytes)) - l4g.Info("License key valid unlocking enterprise features.") + mlog.Info("License key valid unlocking enterprise features.") } else { - l4g.Info(utils.T("mattermost.load_license.find.warn")) + mlog.Info("License key from https://mattermost.com required to unlock enterprise features.") } } @@ -140,7 +139,7 @@ func (a *App) ValidateAndSetLicenseBytes(b []byte) { return } - l4g.Warn(utils.T("utils.license.load_license.invalid.warn")) + mlog.Warn("No valid enterprise license found") } func (a *App) SetClientLicense(m map[string]string) { diff --git a/app/notification.go b/app/notification.go index d1f6225b3..7198de764 100644 --- a/app/notification.go +++ b/app/notification.go @@ -15,7 +15,7 @@ import ( "time" "unicode" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -193,14 +193,14 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod // Remove the user as recipient when the user has muted the channel. if channelMuted, ok := channelMemberNotifyPropsMap[id][model.MARK_UNREAD_NOTIFY_PROP]; ok { if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { - l4g.Debug("Channel muted for user_id %v, channel_mute %v", id, channelMuted) + mlog.Debug(fmt.Sprintf("Channel muted for user_id %v, channel_mute %v", id, channelMuted)) userAllowsEmails = false } } //If email verification is required and user email is not verified don't send email. if a.Config().EmailSettings.RequireEmailVerification && !profileMap[id].EmailVerified { - l4g.Error("Skipped sending notification email to %v, address not verified. [details: user_id=%v]", profileMap[id].Email, id) + mlog.Error(fmt.Sprintf("Skipped sending notification email to %v, address not verified. [details: user_id=%v]", profileMap[id].Email, id)) continue } @@ -266,7 +266,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod // MUST be completed before push notifications send for _, uchan := range updateMentionChans { if result := <-uchan; result.Err != nil { - l4g.Warn(utils.T("api.post.update_mention_count_and_forget.update_error"), post.Id, post.ChannelId, result.Err) + mlog.Warn(fmt.Sprintf("Failed to update mention count, post_id=%v channel_id=%v err=%v", post.Id, post.ChannelId, result.Err), mlog.String("post_id", post.Id)) } } @@ -274,7 +274,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod if *a.Config().EmailSettings.SendPushNotifications { pushServer := *a.Config().EmailSettings.PushNotificationServer if license := a.License(); pushServer == model.MHPNS && (license == nil || !*license.Features.MHPNS) { - l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn")) + mlog.Warn("api.post.send_notifications_and_forget.push_notification.mhpnsWarn FIXME: NOT FOUND IN TRANSLATIONS FILE") sendPushNotifications = false } else { sendPushNotifications = true @@ -330,7 +330,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod var infos []*model.FileInfo if result := <-fchan; result.Err != nil { - l4g.Warn(utils.T("api.post.send_notifications.files.error"), post.Id, result.Err) + mlog.Warn(fmt.Sprint("api.post.send_notifications.files.error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, result.Err), mlog.String("post_id", post.Id)) } else { infos = result.Data.([]*model.FileInfo) } @@ -415,7 +415,7 @@ func (a *App) sendNotificationEmail(post *model.Post, user *model.User, channel a.Go(func() { if err := a.SendMail(user.Email, html.UnescapeString(subjectText), bodyText); err != nil { - l4g.Error(utils.T("api.post.send_notifications_and_forget.send.error"), user.Email, err) + mlog.Error(fmt.Sprint("api.post.send_notifications_and_forget.send.error FIXME: NOT FOUND IN TRANSLATIONS FILE", user.Email, err)) } }) @@ -577,7 +577,7 @@ func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.Tra // extract the filenames from their paths and determine what type of files are attached var infos []*model.FileInfo if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, true); result.Err != nil { - l4g.Warn(utils.T("api.post.get_message_for_notification.get_files.error"), post.Id, result.Err) + mlog.Warn(fmt.Sprintf("Encountered error when getting files for notification message, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) } else { infos = result.Data.([]*model.FileInfo) } @@ -617,7 +617,7 @@ func (a *App) sendPushNotification(post *model.Post, user *model.User, channel * msg := model.PushNotification{} if badge := <-a.Srv.Store.User().GetUnreadCount(user.Id); badge.Err != nil { msg.Badge = 1 - l4g.Error(utils.T("store.sql_user.get_unread_count.app_error"), user.Id, badge.Err) + mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, badge.Err), mlog.String("user_id", user.Id)) } else { msg.Badge = int(badge.Data.(int64)) } @@ -651,7 +651,7 @@ func (a *App) sendPushNotification(post *model.Post, user *model.User, channel * tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) - l4g.Debug("Sending push notification to device %v for user %v with msg of '%v'", tmpMessage.DeviceId, user.Id, msg.Message) + mlog.Debug(fmt.Sprintf("Sending push notification to device %v for user %v with msg of '%v'", tmpMessage.DeviceId, user.Id, msg.Message), mlog.String("user_id", user.Id)) a.Go(func(session *model.Session) func() { return func() { @@ -729,7 +729,7 @@ func (a *App) ClearPushNotification(userId string, channelId string) { sessions, err := a.getMobileAppSessions(userId) if err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) return } @@ -739,12 +739,12 @@ func (a *App) ClearPushNotification(userId string, channelId string) { msg.ContentAvailable = 0 if badge := <-a.Srv.Store.User().GetUnreadCount(userId); badge.Err != nil { msg.Badge = 0 - l4g.Error(utils.T("store.sql_user.get_unread_count.app_error"), userId, badge.Err) + mlog.Error(fmt.Sprint("We could not get the unread message count for the user", userId, badge.Err), mlog.String("user_id", userId)) } else { msg.Badge = int(badge.Data.(int64)) } - l4g.Debug(utils.T("api.post.send_notifications_and_forget.clear_push_notification.debug"), msg.DeviceId, msg.ChannelId) + mlog.Debug(fmt.Sprintf("Clearing push notification to %v with channel_id %v", msg.DeviceId, msg.ChannelId)) for _, session := range sessions { tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) @@ -762,7 +762,7 @@ func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session request, _ := http.NewRequest("POST", strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson())) if resp, err := a.HTTPClient(true).Do(request); err != nil { - l4g.Error("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, err.Error()) + mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, err.Error()), mlog.String("user_id", session.UserId)) } else { pushResponse := model.PushResponseFromJson(resp.Body) if resp.Body != nil { @@ -770,13 +770,13 @@ func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session } if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_REMOVE { - l4g.Info("Device was reported as removed for UserId=%v SessionId=%v removing push for this session", session.UserId, session.Id) + mlog.Info(fmt.Sprintf("Device was reported as removed for UserId=%v SessionId=%v removing push for this session", session.UserId, session.Id), mlog.String("user_id", session.UserId)) a.AttachDeviceId(session.Id, "", session.ExpiresAt) a.ClearSessionCacheForUser(session.UserId) } if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_FAIL { - l4g.Error("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, pushResponse[model.PUSH_STATUS_ERROR_MSG]) + mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, pushResponse[model.PUSH_STATUS_ERROR_MSG]), mlog.String("user_id", session.UserId)) } } } diff --git a/app/oauth.go b/app/oauth.go index 630fd3e2d..13fbd5a73 100644 --- a/app/oauth.go +++ b/app/oauth.go @@ -6,14 +6,15 @@ package app import ( "bytes" b64 "encoding/base64" + "fmt" "io" "net/http" "net/url" "strings" "time" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -226,7 +227,7 @@ func (a *App) GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret accessData = &model.AccessData{ClientId: clientId, UserId: user.Id, Token: session.Token, RefreshToken: model.NewId(), RedirectUri: redirectUri, ExpiresAt: session.ExpiresAt, Scope: authData.Scope} if result := <-a.Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil { - l4g.Error(result.Err) + mlog.Error(fmt.Sprint(result.Err)) return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) } @@ -295,7 +296,7 @@ func (a *App) newSessionUpdateToken(appName string, accessData *model.AccessData accessData.RefreshToken = model.NewId() accessData.ExpiresAt = session.ExpiresAt if result := <-a.Srv.Store.OAuth().UpdateAccessData(accessData); result.Err != nil { - l4g.Error(result.Err) + mlog.Error(fmt.Sprint(result.Err)) return nil, model.NewAppError("newSessionUpdateToken", "web.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) } accessRsp := &model.AccessResponse{ @@ -528,7 +529,7 @@ func (a *App) CompleteSwitchWithOAuth(service string, userData io.ReadCloser, em a.Go(func() { if err := a.SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) @@ -774,7 +775,7 @@ func (a *App) SwitchOAuthToEmail(email, password, requesterId string) (string, * a.Go(func() { if err := a.SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) diff --git a/app/plugin.go b/app/plugin.go index 903f4b767..3da9cea40 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -17,9 +17,8 @@ import ( "strings" "unicode/utf8" - l4g "github.com/alecthomas/log4go" - "github.com/gorilla/mux" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" @@ -48,7 +47,7 @@ func (a *App) initBuiltInPlugins() { "ldapextras": &ldapextras.Plugin{}, } for id, p := range plugins { - l4g.Debug("Initializing built-in plugin: " + id) + mlog.Debug("Initializing built-in plugin: " + id) api := &BuiltInPluginAPI{ id: id, router: a.Srv.Router.PathPrefix("/plugins/" + id).Subrouter(), @@ -70,13 +69,13 @@ func (a *App) initBuiltInPlugins() { // and deactivate all other plugins. func (a *App) ActivatePlugins() { if a.PluginEnv == nil { - l4g.Error("plugin env not initialized") + mlog.Error("plugin env not initialized") return } plugins, err := a.PluginEnv.Plugins() if err != nil { - l4g.Error("failed to activate plugins: " + err.Error()) + mlog.Error("failed to activate plugins: " + err.Error()) return } @@ -92,13 +91,13 @@ func (a *App) ActivatePlugins() { if pluginState.Enable && !active { if err := a.activatePlugin(plugin.Manifest); err != nil { - l4g.Error("%v plugin enabled in config.json but failing to activate err=%v", plugin.Manifest.Id, err.DetailedError) + mlog.Error(fmt.Sprintf("%v plugin enabled in config.json but failing to activate err=%v", plugin.Manifest.Id, err.DetailedError)) continue } } else if !pluginState.Enable && active { if err := a.deactivatePlugin(plugin.Manifest); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } } @@ -115,7 +114,7 @@ func (a *App) activatePlugin(manifest *model.Manifest) *model.AppError { a.Publish(message) } - l4g.Info("Activated %v plugin", manifest.Id) + mlog.Info(fmt.Sprintf("Activated %v plugin", manifest.Id)) return nil } @@ -132,7 +131,7 @@ func (a *App) deactivatePlugin(manifest *model.Manifest) *model.AppError { a.Publish(message) } - l4g.Info("Deactivated %v plugin", manifest.Id) + mlog.Info(fmt.Sprintf("Deactivated %v plugin", manifest.Id)) return nil } @@ -370,15 +369,15 @@ func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride plug return } - l4g.Info("Starting up plugins") + mlog.Info("Starting up plugins") if err := os.Mkdir(pluginPath, 0744); err != nil && !os.IsExist(err) { - l4g.Error("failed to start up plugins: " + err.Error()) + mlog.Error("failed to start up plugins: " + err.Error()) return } if err := os.Mkdir(webappPath, 0744); err != nil && !os.IsExist(err) { - l4g.Error("failed to start up plugins: " + err.Error()) + mlog.Error("failed to start up plugins: " + err.Error()) return } @@ -400,15 +399,15 @@ func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride plug if supervisorOverride != nil { options = append(options, pluginenv.SupervisorProvider(supervisorOverride)) } else if err := sandbox.CheckSupport(); err != nil { - l4g.Warn(err.Error()) - l4g.Warn("plugin sandboxing is not supported. plugins will run with the same access level as the server. See documentation to learn more: https://developers.mattermost.com/extend/plugins/security/") + mlog.Warn(err.Error()) + mlog.Warn("plugin sandboxing is not supported. plugins will run with the same access level as the server. See documentation to learn more: https://developers.mattermost.com/extend/plugins/security/") options = append(options, pluginenv.SupervisorProvider(rpcplugin.SupervisorProvider)) } else { options = append(options, pluginenv.SupervisorProvider(sandbox.SupervisorProvider)) } if env, err := pluginenv.New(options...); err != nil { - l4g.Error("failed to start up plugins: " + err.Error()) + mlog.Error("failed to start up plugins: " + err.Error()) return } else { a.PluginEnv = env @@ -416,15 +415,15 @@ func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride plug for id, asset := range prepackagedPlugins { if tarball, err := asset("plugin.tar.gz"); err != nil { - l4g.Error("failed to install prepackaged plugin: " + err.Error()) + mlog.Error("failed to install prepackaged plugin: " + err.Error()) } else if tarball != nil { a.removePlugin(id, true) if _, err := a.installPlugin(bytes.NewReader(tarball), true); err != nil { - l4g.Error("failed to install prepackaged plugin: " + err.Error()) + mlog.Error("failed to install prepackaged plugin: " + err.Error()) } if _, ok := a.Config().PluginSettings.PluginStates[id]; !ok && id != "zoom" { if err := a.EnablePlugin(id); err != nil { - l4g.Error("failed to enable prepackaged plugin: " + err.Error()) + mlog.Error("failed to enable prepackaged plugin: " + err.Error()) } } } @@ -441,7 +440,7 @@ func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride plug } for _, err := range a.PluginEnv.Hooks().OnConfigurationChange() { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) @@ -451,7 +450,7 @@ func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride plug func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) { if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) - l4g.Error(err.Error()) + mlog.Error(err.Error()) w.WriteHeader(err.StatusCode) w.Header().Set("Content-Type", "application/json") w.Write([]byte(err.ToJson())) @@ -507,10 +506,10 @@ func (a *App) ShutDownPlugins() { return } - l4g.Info("Shutting down plugins") + mlog.Info("Shutting down plugins") for _, err := range a.PluginEnv.Shutdown() { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } a.RemoveConfigListener(a.PluginConfigListenerId) a.PluginConfigListenerId = "" @@ -533,7 +532,7 @@ func (a *App) SetPluginKey(pluginId string, key string, value []byte) *model.App result := <-a.Srv.Store.Plugin().SaveOrUpdate(kv) if result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) } return result.Err @@ -546,7 +545,7 @@ func (a *App) GetPluginKey(pluginId string, key string) ([]byte, *model.AppError if result.Err.StatusCode == http.StatusNotFound { return nil, nil } - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) return nil, result.Err } @@ -559,7 +558,7 @@ func (a *App) DeletePluginKey(pluginId string, key string) *model.AppError { result := <-a.Srv.Store.Plugin().Delete(pluginId, getKeyHash(key)) if result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) } return result.Err diff --git a/app/plugin/ldapextras/plugin.go b/app/plugin/ldapextras/plugin.go index 3af55c2ac..e0645b68e 100644 --- a/app/plugin/ldapextras/plugin.go +++ b/app/plugin/ldapextras/plugin.go @@ -8,10 +8,10 @@ import ( "net/http" "sync/atomic" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/app/plugin" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -34,7 +34,7 @@ func (p *Plugin) config() *Configuration { func (p *Plugin) OnConfigurationChange() { var configuration Configuration if err := p.api.LoadPluginConfiguration(&configuration); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } p.configuration.Store(&configuration) } diff --git a/app/post.go b/app/post.go index 4ce009369..41139da63 100644 --- a/app/post.go +++ b/app/post.go @@ -14,8 +14,8 @@ import ( "regexp" "strings" - l4g "github.com/alecthomas/log4go" "github.com/dyatlov/go-opengraph/opengraph" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -78,7 +78,7 @@ func (a *App) CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError) // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) if _, ok := post.Props["from_webhook"]; !ok { if result := <-a.Srv.Store.Channel().UpdateLastViewedAt([]string{post.ChannelId}, post.UserId); result.Err != nil { - l4g.Error(utils.T("api.post.create_post.last_viewed.error"), post.ChannelId, post.UserId, result.Err) + mlog.Error(fmt.Sprintf("Encountered error updating last viewed, channel_id=%s, user_id=%s, err=%v", post.ChannelId, post.UserId, result.Err)) } if *a.Config().ServiceSettings.EnableChannelViewedMessages { @@ -182,7 +182,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo for _, fileId := range post.FileIds { if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { - l4g.Error(utils.T("api.post.create_post.attach_files.error"), post.Id, post.FileIds, post.UserId, result.Err) + mlog.Error(fmt.Sprintf("Encountered error attaching files to post, post_id=%s, user_id=%s, file_ids=%v, err=%v", post.Id, post.FileIds, post.UserId, result.Err), mlog.String("post_id", post.Id)) } } @@ -266,7 +266,7 @@ func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *mode if triggerWebhooks { a.Go(func() { if err := a.handleWebhookEvents(post, team, channel, user); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) } @@ -378,7 +378,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { a.Go(func() { if rchannel := <-a.Srv.Store.Channel().GetForPost(rpost.Id); rchannel.Err != nil { - l4g.Error("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id) + mlog.Error(fmt.Sprintf("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id)) } else { esInterface.IndexPost(rpost, rchannel.Data.(*model.Channel).TeamId) } @@ -576,7 +576,7 @@ func (a *App) DeletePost(postId string) (*model.Post, *model.AppError) { func (a *App) DeleteFlaggedPosts(postId string) { if result := <-a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil { - l4g.Warn(utils.T("api.post.delete_flagged_post.app_error.warn"), result.Err) + mlog.Warn(fmt.Sprintf("Unable to delete flagged post preference when deleting post, err=%v", result.Err)) return } } @@ -587,7 +587,7 @@ func (a *App) DeletePostFiles(post *model.Post) { } if result := <-a.Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil { - l4g.Warn(utils.T("api.post.delete_post_files.app_error.warn"), post.Id, result.Err) + mlog.Warn(fmt.Sprintf("Encountered error when deleting files for post, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) } } @@ -605,7 +605,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr // Convert channel names to channel IDs for idx, channelName := range params.InChannels { if channel, err := a.GetChannelByName(channelName, teamId); err != nil { - l4g.Error(err) + mlog.Error(fmt.Sprint(err)) } else { params.InChannels[idx] = channel.Id } @@ -614,7 +614,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr // Convert usernames to user IDs for idx, username := range params.FromUsers { if user, err := a.GetUserByUsername(username); err != nil { - l4g.Error(err) + mlog.Error(fmt.Sprint(err)) } else { params.FromUsers[idx] = user.Id } @@ -632,7 +632,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr // We only allow the user to search in channels they are a member of. userChannels, err := a.GetChannelsForUser(teamId, userId) if err != nil { - l4g.Error(err) + mlog.Error(fmt.Sprint(err)) return nil, err } @@ -721,13 +721,13 @@ func (a *App) GetOpenGraphMetadata(requestURL string) *opengraph.OpenGraph { res, err := a.HTTPClient(false).Get(requestURL) if err != nil { - l4g.Error("GetOpenGraphMetadata request failed for url=%v with err=%v", requestURL, err.Error()) + mlog.Error(fmt.Sprintf("GetOpenGraphMetadata request failed for url=%v with err=%v", requestURL, err.Error())) return og } defer consumeAndClose(res) if err := og.ProcessHTML(res.Body); err != nil { - l4g.Error("GetOpenGraphMetadata processing failed for url=%v with err=%v", requestURL, err.Error()) + mlog.Error(fmt.Sprintf("GetOpenGraphMetadata processing failed for url=%v with err=%v", requestURL, err.Error())) } makeOpenGraphURLsAbsolute(og, requestURL) @@ -738,7 +738,7 @@ func (a *App) GetOpenGraphMetadata(requestURL string) *opengraph.OpenGraph { func makeOpenGraphURLsAbsolute(og *opengraph.OpenGraph, requestURL string) { parsedRequestURL, err := url.Parse(requestURL) if err != nil { - l4g.Warn("makeOpenGraphURLsAbsolute failed to parse url=%v", requestURL) + mlog.Warn(fmt.Sprintf("makeOpenGraphURLsAbsolute failed to parse url=%v", requestURL)) return } @@ -749,7 +749,7 @@ func makeOpenGraphURLsAbsolute(og *opengraph.OpenGraph, requestURL string) { parsedResultURL, err := url.Parse(resultURL) if err != nil { - l4g.Warn("makeOpenGraphURLsAbsolute failed to parse result url=%v", resultURL) + mlog.Warn(fmt.Sprintf("makeOpenGraphURLsAbsolute failed to parse result url=%v", resultURL)) return resultURL } @@ -962,7 +962,7 @@ func (a *App) ImageProxyRemover() (f func(string) string) { func (a *App) MaxPostSize() int { maxPostSize := model.POST_MESSAGE_MAX_RUNES_V1 if result := <-a.Srv.Store.Post().GetMaxPostSize(); result.Err != nil { - l4g.Error(result.Err) + mlog.Error(fmt.Sprint(result.Err)) } else { maxPostSize = result.Data.(int) } diff --git a/app/ratelimit.go b/app/ratelimit.go index 13508f36f..77d15caa2 100644 --- a/app/ratelimit.go +++ b/app/ratelimit.go @@ -4,12 +4,13 @@ package app import ( + "fmt" "math" "net/http" "strconv" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" "github.com/pkg/errors" @@ -74,14 +75,14 @@ func (rl *RateLimiter) GenerateKey(r *http.Request) string { func (rl *RateLimiter) RateLimitWriter(key string, w http.ResponseWriter) bool { limited, context, err := rl.throttledRateLimiter.RateLimit(key, 1) if err != nil { - l4g.Critical("Internal server error when rate limiting. Rate Limiting broken. Error:" + err.Error()) + mlog.Critical("Internal server error when rate limiting. Rate Limiting broken. Error:" + err.Error()) return false } setRateLimitHeaders(w, context) if limited { - l4g.Error("Denied due to throttling settings code=429 key=%v", key) + mlog.Error(fmt.Sprintf("Denied due to throttling settings code=429 key=%v", key)) http.Error(w, "limit exceeded", 429) } diff --git a/app/role.go b/app/role.go index c99d8365b..c9278e0bd 100644 --- a/app/role.go +++ b/app/role.go @@ -6,8 +6,9 @@ package app import ( "reflect" - "github.com/mattermost/mattermost-server/model" "net/http" + + "github.com/mattermost/mattermost-server/model" ) func (a *App) GetRole(id string) (*model.Role, *model.AppError) { diff --git a/app/security_update_check.go b/app/security_update_check.go index 1f1759fef..15bb81e5a 100644 --- a/app/security_update_check.go +++ b/app/security_update_check.go @@ -4,13 +4,14 @@ package app import ( + "fmt" "io/ioutil" "net/http" "net/url" "runtime" "strconv" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -38,7 +39,7 @@ func (a *App) DoSecurityUpdateCheck() { currentTime := model.GetMillis() if (currentTime - lastSecurityTime) > SECURITY_UPDATE_PERIOD { - l4g.Debug(utils.T("mattermost.security_checks.debug")) + mlog.Debug("Checking for security update from Mattermost") v := url.Values{} @@ -75,7 +76,7 @@ func (a *App) DoSecurityUpdateCheck() { res, err := http.Get(SECURITY_URL + "/security?" + v.Encode()) if err != nil { - l4g.Error(utils.T("mattermost.security_info.error")) + mlog.Error("Failed to get security update information from Mattermost.") return } @@ -86,26 +87,26 @@ func (a *App) DoSecurityUpdateCheck() { if bulletin.AppliesToVersion == model.CurrentVersion { if props["SecurityBulletin_"+bulletin.Id] == "" { if results := <-a.Srv.Store.User().GetSystemAdminProfiles(); results.Err != nil { - l4g.Error(utils.T("mattermost.system_admins.error")) + mlog.Error("Failed to get system admins for security update information from Mattermost.") return } else { users := results.Data.(map[string]*model.User) resBody, err := http.Get(SECURITY_URL + "/bulletins/" + bulletin.Id) if err != nil { - l4g.Error(utils.T("mattermost.security_bulletin.error")) + mlog.Error("Failed to get security bulletin details") return } body, err := ioutil.ReadAll(resBody.Body) res.Body.Close() if err != nil || resBody.StatusCode != 200 { - l4g.Error(utils.T("mattermost.security_bulletin_read.error")) + mlog.Error("Failed to read security bulletin details") return } for _, user := range users { - l4g.Info(utils.T("mattermost.send_bulletin.info"), bulletin.Id, user.Email) + mlog.Info(fmt.Sprintf("Sending security bulletin for %v to %v", bulletin.Id, user.Email)) a.SendMail(user.Email, utils.T("mattermost.bulletin.subject"), string(body)) } } diff --git a/app/server.go b/app/server.go index e89041ebe..7d229201d 100644 --- a/app/server.go +++ b/app/server.go @@ -15,12 +15,12 @@ import ( "strings" "time" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/pkg/errors" "golang.org/x/crypto/acme/autocert" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -50,8 +50,8 @@ type RecoveryLogger struct { } func (rl *RecoveryLogger) Println(i ...interface{}) { - l4g.Error("Please check the std error output for the stack trace") - l4g.Error(i) + mlog.Error("Please check the std error output for the stack trace") + mlog.Error(fmt.Sprint(i)) } type CorsWrapper struct { @@ -97,12 +97,12 @@ func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) { } func (a *App) StartServer() error { - l4g.Info(utils.T("api.server.start_server.starting.info")) + mlog.Info("Starting Server...") var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router} if *a.Config().RateLimitSettings.Enable { - l4g.Info(utils.T("api.server.start_server.rate.info")) + mlog.Info("RateLimiter is enabled") rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings) if err != nil { @@ -117,6 +117,7 @@ func (a *App) StartServer() error { Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler), ReadTimeout: time.Duration(*a.Config().ServiceSettings.ReadTimeout) * time.Second, WriteTimeout: time.Duration(*a.Config().ServiceSettings.WriteTimeout) * time.Second, + ErrorLog: a.Log.StdLog(mlog.String("source", "httpserver")), } addr := *a.Config().ServiceSettings.ListenAddress @@ -135,7 +136,7 @@ func (a *App) StartServer() error { } a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr) - l4g.Info(utils.T("api.server.start_server.listening.info"), listener.Addr().String()) + mlog.Info(fmt.Sprintf("Server is listening on %v", listener.Addr().String())) // Migration from old let's encrypt library if *a.Config().ServiceSettings.UseLetsEncrypt { @@ -151,24 +152,33 @@ func (a *App) StartServer() error { if *a.Config().ServiceSettings.Forward80To443 { if host, port, err := net.SplitHostPort(addr); err != nil { - l4g.Error("Unable to setup forwarding: " + err.Error()) + mlog.Error("Unable to setup forwarding: " + err.Error()) } else if port != "443" { return fmt.Errorf(utils.T("api.server.start_server.forward80to443.enabled_but_listening_on_wrong_port"), port) } else { httpListenAddress := net.JoinHostPort(host, "http") if *a.Config().ServiceSettings.UseLetsEncrypt { - go http.ListenAndServe(httpListenAddress, m.HTTPHandler(nil)) + server := &http.Server{ + Addr: httpListenAddress, + Handler: m.HTTPHandler(nil), + ErrorLog: a.Log.StdLog(mlog.String("source", "le_forwarder_server")), + } + go server.ListenAndServe() } else { go func() { redirectListener, err := net.Listen("tcp", httpListenAddress) if err != nil { - l4g.Error("Unable to setup forwarding: " + err.Error()) + mlog.Error("Unable to setup forwarding: " + err.Error()) return } defer redirectListener.Close() - http.Serve(redirectListener, http.HandlerFunc(redirectHTTPToHTTPS)) + server := &http.Server{ + Handler: handler, + ErrorLog: a.Log.StdLog(mlog.String("source", "forwarder_server")), + } + server.Serve(redirectListener) }() } } @@ -197,7 +207,7 @@ func (a *App) StartServer() error { err = a.Srv.Server.Serve(listener) } if err != nil && err != http.ErrServerClosed { - l4g.Critical(utils.T("api.server.start_server.starting.critical"), err) + mlog.Critical(fmt.Sprintf("Error starting server, err:%v", err)) time.Sleep(time.Second) } close(a.Srv.didFinishListen) @@ -213,7 +223,7 @@ func (a *App) StopServer() { didShutdown := false for a.Srv.didFinishListen != nil && !didShutdown { if err := a.Srv.Server.Shutdown(ctx); err != nil { - l4g.Warn(err.Error()) + mlog.Warn(err.Error()) } timer := time.NewTimer(time.Millisecond * 50) select { diff --git a/app/session.go b/app/session.go index 03754398d..53170cec0 100644 --- a/app/session.go +++ b/app/session.go @@ -4,12 +4,11 @@ package app import ( + "fmt" "net/http" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" - - l4g "github.com/alecthomas/log4go" ) func (a *App) CreateSession(session *model.Session) (*model.Session, *model.AppError) { @@ -171,10 +170,10 @@ func (a *App) RevokeSessionsForDeviceId(userId string, deviceId string, currentS sessions := result.Data.([]*model.Session) for _, session := range sessions { if session.DeviceId == deviceId && session.Id != currentSessionId { - l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, userId) + mlog.Debug(fmt.Sprintf("Revoking sessionId=%v for userId=%v re-login with same device Id", session.Id, userId), mlog.String("user_id", userId)) if err := a.RevokeSession(session); err != nil { // Soft error so we still remove the other sessions - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } } @@ -233,7 +232,7 @@ func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) { } if result := <-a.Srv.Store.Session().UpdateLastActivityAt(session.Id, now); result.Err != nil { - l4g.Error(utils.T("api.status.last_activity.error"), session.UserId, session.Id, result.Err) + mlog.Error(fmt.Sprintf("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", session.UserId, session.Id, result.Err), mlog.String("user_id", session.UserId)) } session.LastActivityAt = now @@ -256,11 +255,11 @@ func (a *App) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserAc } if result := <-uchan; result.Err != nil { - l4g.Error(result.Err.Error()) + mlog.Error(result.Err.Error()) } else { user := result.Data.(*model.User) if err := a.SendUserAccessTokenAddedEmail(user.Email, user.Locale); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } diff --git a/app/slackimport.go b/app/slackimport.go index d5e7fe2b2..3333af604 100644 --- a/app/slackimport.go +++ b/app/slackimport.go @@ -7,6 +7,7 @@ import ( "archive/zip" "bytes" "encoding/json" + "fmt" "io" "mime/multipart" "path/filepath" @@ -17,7 +18,7 @@ import ( "net/http" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -81,7 +82,7 @@ func SlackConvertTimeStamp(ts string) int64 { timeStamp, err := strconv.ParseInt(timeString, 10, 64) if err != nil { - l4g.Warn(utils.T("api.slackimport.slack_convert_timestamp.bad.warn")) + mlog.Warn("Slack Import: Bad timestamp detected.") return 1 } return timeStamp * 1000 // Convert to milliseconds @@ -105,7 +106,7 @@ func SlackParseChannels(data io.Reader) ([]SlackChannel, error) { var channels []SlackChannel if err := decoder.Decode(&channels); err != nil { - l4g.Warn(utils.T("api.slackimport.slack_parse_channels.error")) + mlog.Warn("Slack Import: Error occurred when parsing some Slack channels. Import may work anyway.") return channels, err } return channels, nil @@ -127,23 +128,23 @@ func SlackParsePosts(data io.Reader) ([]SlackPost, error) { var posts []SlackPost if err := decoder.Decode(&posts); err != nil { - l4g.Warn(utils.T("api.slackimport.slack_parse_posts.error")) + mlog.Warn("Slack Import: Error occurred when parsing some Slack posts. Import may work anyway.") return posts, err } return posts, nil } -func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map[string]*model.User { +func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, importerLog *bytes.Buffer) map[string]*model.User { // Log header - log.WriteString(utils.T("api.slackimport.slack_add_users.created")) - log.WriteString("===============\r\n\r\n") + importerLog.WriteString(utils.T("api.slackimport.slack_add_users.created")) + importerLog.WriteString("===============\r\n\r\n") addedUsers := make(map[string]*model.User) // Need the team var team *model.Team if result := <-a.Srv.Store.Team().Get(teamId); result.Err != nil { - log.WriteString(utils.T("api.slackimport.slack_import.team_fail")) + importerLog.WriteString(utils.T("api.slackimport.slack_import.team_fail")) return addedUsers } else { team = result.Data.(*model.Team) @@ -155,8 +156,8 @@ func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Bu email := sUser.Profile.Email if email == "" { email = sUser.Username + "@example.com" - log.WriteString(utils.T("api.slackimport.slack_add_users.missing_email_address", map[string]interface{}{"Email": email, "Username": sUser.Username})) - l4g.Warn(utils.T("api.slackimport.slack_add_users.missing_email_address.warn", map[string]interface{}{"Email": email, "Username": sUser.Username})) + importerLog.WriteString(utils.T("api.slackimport.slack_add_users.missing_email_address", map[string]interface{}{"Email": email, "Username": sUser.Username})) + mlog.Warn("Slack Import: User {{.Username}} does not have an email address in the Slack export. Used {{.Email}} as a placeholder. The user should update their email address once logged in to the system.") } password := model.NewId() @@ -166,9 +167,9 @@ func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Bu existingUser := result.Data.(*model.User) addedUsers[sUser.Id] = existingUser if err := a.JoinUserToTeam(team, addedUsers[sUser.Id], ""); err != nil { - log.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing_failed", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) + importerLog.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing_failed", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) } else { - log.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) + importerLog.WriteString(utils.T("api.slackimport.slack_add_users.merge_existing", map[string]interface{}{"Email": existingUser.Email, "Username": existingUser.Username})) } continue } @@ -183,9 +184,9 @@ func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Bu if mUser := a.OldImportUser(team, &newUser); mUser != nil { addedUsers[sUser.Id] = mUser - log.WriteString(utils.T("api.slackimport.slack_add_users.email_pwd", map[string]interface{}{"Email": newUser.Email, "Password": password})) + importerLog.WriteString(utils.T("api.slackimport.slack_add_users.email_pwd", map[string]interface{}{"Email": newUser.Email, "Password": password})) } else { - log.WriteString(utils.T("api.slackimport.slack_add_users.unable_import", map[string]interface{}{"Username": sUser.Username})) + importerLog.WriteString(utils.T("api.slackimport.slack_add_users.unable_import", map[string]interface{}{"Username": sUser.Username})) } } @@ -227,10 +228,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack switch { case sPost.Type == "message" && (sPost.SubType == "" || sPost.SubType == "file_share"): if sPost.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.without_user.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } newPost := model.Post{ @@ -248,19 +249,19 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportPost(&newPost) for _, fileId := range newPost.FileIds { if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, newPost.Id); result.Err != nil { - l4g.Error(utils.T("api.slackimport.slack_add_posts.attach_files.error"), newPost.Id, newPost.FileIds, result.Err) + mlog.Error(fmt.Sprintf("Slack Import: An error occurred when attaching files to a message, post_id=%s, file_ids=%v, err=%v.", newPost.Id, newPost.FileIds, result.Err)) } } case sPost.Type == "message" && sPost.SubType == "file_comment": if sPost.Comment == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_comment.debug")) + mlog.Debug("Slack Import: Unable to import the message as it has no comments.") continue } else if sPost.Comment.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.Comment.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } newPost := model.Post{ @@ -272,10 +273,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportPost(&newPost) case sPost.Type == "message" && sPost.SubType == "bot_message": if botUser == nil { - l4g.Warn(utils.T("api.slackimport.slack_add_posts.bot_user_no_exists.warn")) + mlog.Warn("Slack Import: Unable to import the bot message as the bot user does not exist.") continue } else if sPost.BotId == "" { - l4g.Warn(utils.T("api.slackimport.slack_add_posts.no_bot_id.warn")) + mlog.Warn("Slack Import: Unable to import bot message as the BotId field is missing.") continue } @@ -296,10 +297,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportIncomingWebhookPost(post, props) case sPost.Type == "message" && (sPost.SubType == "channel_join" || sPost.SubType == "channel_leave"): if sPost.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } @@ -323,10 +324,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportPost(&newPost) case sPost.Type == "message" && sPost.SubType == "me_message": if sPost.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.without_user.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } newPost := model.Post{ @@ -338,10 +339,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportPost(&newPost) case sPost.Type == "message" && sPost.SubType == "channel_topic": if sPost.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } newPost := model.Post{ @@ -354,10 +355,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportPost(&newPost) case sPost.Type == "message" && sPost.SubType == "channel_purpose": if sPost.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } newPost := model.Post{ @@ -370,10 +371,10 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack a.OldImportPost(&newPost) case sPost.Type == "message" && sPost.SubType == "channel_name": if sPost.User == "" { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug")) + mlog.Debug("Slack Import: Unable to import the message as the user field is missing.") continue } else if users[sPost.User] == nil { - l4g.Debug(utils.T("api.slackimport.slack_add_posts.user_no_exists.debug"), sPost.User) + mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) continue } newPost := model.Post{ @@ -385,7 +386,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack } a.OldImportPost(&newPost) default: - l4g.Warn(utils.T("api.slackimport.slack_add_posts.unsupported.warn"), sPost.Type, sPost.SubType) + mlog.Warn(fmt.Sprintf("Slack Import: Unable to import the message as its type is not supported: post_type=%v, post_subtype=%v.", sPost.Type, sPost.SubType)) } } } @@ -395,7 +396,7 @@ func (a *App) SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, tea if file, ok := uploads[sPost.File.Id]; ok { openFile, err := file.Open() if err != nil { - l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_open_failed.warn", map[string]interface{}{"FileId": sPost.File.Id, "Error": err.Error()})) + mlog.Warn("Slack Import: Unable to open the file {{.FileId}} from the Slack export: {{.Error}}.") return nil, false } defer openFile.Close() @@ -403,17 +404,17 @@ func (a *App) SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, tea timestamp := utils.TimeFromMillis(SlackConvertTimeStamp(sPost.TimeStamp)) uploadedFile, err := a.OldImportFile(timestamp, openFile, teamId, channelId, userId, filepath.Base(file.Name)) if err != nil { - l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_upload_failed.warn", map[string]interface{}{"FileId": sPost.File.Id, "Error": err.Error()})) + mlog.Warn("Slack Import: An error occurred when uploading file {{.FileId}}: {{.Error}}.") return nil, false } return uploadedFile, true } else { - l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_not_found.warn", map[string]interface{}{"FileId": sPost.File.Id})) + mlog.Warn("Slack Import: Unable to import file {{.FileId}} as the file is missing from the Slack export zip file.") return nil, false } } else { - l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_not_in_json.warn")) + mlog.Warn("Slack Import: Unable to attach the file to the post as the latter has no file section present in Slack export.") return nil, false } } @@ -421,7 +422,7 @@ func (a *App) SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, tea func (a *App) deactivateSlackBotUser(user *model.User) { _, err := a.UpdateActive(user, false) if err != nil { - l4g.Warn(utils.T("api.slackimport.slack_deactivate_bot_user.failed_to_deactivate", err)) + mlog.Warn("Slack Import: Unable to deactivate the user account used for the bot.") } } @@ -439,32 +440,32 @@ func (a *App) addSlackUsersToChannel(members []string, users map[string]*model.U func SlackSanitiseChannelProperties(channel model.Channel) model.Channel { if utf8.RuneCountInString(channel.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES { - l4g.Warn("api.slackimport.slack_sanitise_channel_properties.display_name_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) + mlog.Warn(fmt.Sprint("api.slackimport.slack_sanitise_channel_properties.display_name_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName})) channel.DisplayName = truncateRunes(channel.DisplayName, model.CHANNEL_DISPLAY_NAME_MAX_RUNES) } if len(channel.Name) > model.CHANNEL_NAME_MAX_LENGTH { - l4g.Warn("api.slackimport.slack_sanitise_channel_properties.name_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) + mlog.Warn(fmt.Sprint("api.slackimport.slack_sanitise_channel_properties.name_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName})) channel.Name = channel.Name[0:model.CHANNEL_NAME_MAX_LENGTH] } if utf8.RuneCountInString(channel.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES { - l4g.Warn("api.slackimport.slack_sanitise_channel_properties.purpose_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) + mlog.Warn(fmt.Sprint("api.slackimport.slack_sanitise_channel_properties.purpose_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName})) channel.Purpose = truncateRunes(channel.Purpose, model.CHANNEL_PURPOSE_MAX_RUNES) } if utf8.RuneCountInString(channel.Header) > model.CHANNEL_HEADER_MAX_RUNES { - l4g.Warn("api.slackimport.slack_sanitise_channel_properties.header_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName}) + mlog.Warn(fmt.Sprint("api.slackimport.slack_sanitise_channel_properties.header_too_long.warn", map[string]interface{}{"ChannelName": channel.DisplayName})) channel.Header = truncateRunes(channel.Header, model.CHANNEL_HEADER_MAX_RUNES) } return channel } -func (a *App) SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[string][]SlackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User, log *bytes.Buffer) map[string]*model.Channel { +func (a *App) SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[string][]SlackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User, importerLog *bytes.Buffer) map[string]*model.Channel { // Write Header - log.WriteString(utils.T("api.slackimport.slack_add_channels.added")) - log.WriteString("=================\r\n\r\n") + importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.added")) + importerLog.WriteString("=================\r\n\r\n") addedChannels := make(map[string]*model.Channel) for _, sChannel := range slackchannels { @@ -482,7 +483,7 @@ func (a *App) SlackAddChannels(teamId string, slackchannels []SlackChannel, post if result := <-a.Srv.Store.Channel().GetByName(teamId, sChannel.Name, true); result.Err == nil { // The channel already exists as an active channel. Merge with the existing one. mChannel = result.Data.(*model.Channel) - log.WriteString(utils.T("api.slackimport.slack_add_channels.merge", map[string]interface{}{"DisplayName": newChannel.DisplayName})) + importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.merge", map[string]interface{}{"DisplayName": newChannel.DisplayName})) } else if result := <-a.Srv.Store.Channel().GetDeletedByName(teamId, sChannel.Name); result.Err == nil { // The channel already exists but has been deleted. Generate a random string for the handle instead. newChannel.Name = model.NewId() @@ -493,14 +494,14 @@ func (a *App) SlackAddChannels(teamId string, slackchannels []SlackChannel, post // Haven't found an existing channel to merge with. Try importing it as a new one. mChannel = a.OldImportChannel(&newChannel) if mChannel == nil { - l4g.Warn(utils.T("api.slackimport.slack_add_channels.import_failed.warn"), newChannel.DisplayName) - log.WriteString(utils.T("api.slackimport.slack_add_channels.import_failed", map[string]interface{}{"DisplayName": newChannel.DisplayName})) + mlog.Warn(fmt.Sprintf("Slack Import: Unable to import Slack channel: %s.", newChannel.DisplayName)) + importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.import_failed", map[string]interface{}{"DisplayName": newChannel.DisplayName})) continue } } - a.addSlackUsersToChannel(sChannel.Members, users, mChannel, log) - log.WriteString(newChannel.DisplayName + "\r\n") + a.addSlackUsersToChannel(sChannel.Members, users, mChannel, importerLog) + importerLog.WriteString(newChannel.DisplayName + "\r\n") addedChannels[sChannel.Id] = mChannel a.SlackAddPosts(teamId, mChannel, posts[sChannel.Name], users, uploads, botUser) } @@ -513,7 +514,7 @@ func SlackConvertUserMentions(users []SlackUser, posts map[string][]SlackPost) m for _, user := range users { r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>") if err != nil { - l4g.Warn(utils.T("api.slackimport.slack_convert_user_mentions.compile_regexp_failed.warn"), user.Id, user.Username) + mlog.Warn(fmt.Sprint("Slack Import: Unable to compile the @mention, matching regular expression for the Slack user {{.Username}} (id={{.UserID}}).", user.Id, user.Username), mlog.String("user_id", user.Id)) continue } regexes["@"+user.Username] = r @@ -541,7 +542,7 @@ func SlackConvertChannelMentions(channels []SlackChannel, posts map[string][]Sla for _, channel := range channels { r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>") if err != nil { - l4g.Warn(utils.T("api.slackimport.slack_convert_channel_mentions.compile_regexp_failed.warn"), channel.Id, channel.Name) + mlog.Warn(fmt.Sprint("Slack Import: Unable to compile the !channel, matching regular expression for the Slack channel {{.ChannelName}} (id={{.ChannelID}}).", channel.Id, channel.Name)) continue } regexes["~"+channel.Name] = r diff --git a/app/status.go b/app/status.go index 64d5379ef..d8e93e2f1 100644 --- a/app/status.go +++ b/app/status.go @@ -4,8 +4,9 @@ package app import ( - l4g "github.com/alecthomas/log4go" + "fmt" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -208,7 +209,7 @@ func (a *App) SetStatusOnline(userId string, sessionId string, manual bool) { } if result := <-schan; result.Err != nil { - l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err) + mlog.Error(fmt.Sprintf("Failed to save status for user_id=%v, err=%v", userId, result.Err), mlog.String("user_id", userId)) } } @@ -292,7 +293,7 @@ func (a *App) SaveAndBroadcastStatus(status *model.Status) *model.AppError { a.AddStatusCache(status) if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { - l4g.Error(utils.T("api.status.save_status.error"), status.UserId, result.Err) + mlog.Error(fmt.Sprintf("Failed to save status for user_id=%v, err=%v", status.UserId, result.Err)) } event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) @@ -320,7 +321,7 @@ func (a *App) SetStatusOutOfOffice(userId string) { a.AddStatusCache(status) if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { - l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err) + mlog.Error(fmt.Sprintf("Failed to save status for user_id=%v, err=%v", userId, result.Err), mlog.String("user_id", userId)) } event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) diff --git a/app/team.go b/app/team.go index 47e28f2ed..78d932870 100644 --- a/app/team.go +++ b/app/team.go @@ -13,9 +13,9 @@ import ( "net/url" "strings" - l4g "github.com/alecthomas/log4go" "github.com/disintegration/imaging" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -350,7 +350,7 @@ func (a *App) JoinUserToTeam(team *model.Team, user *model.User, userRequestorId // Soft error if there is an issue joining the default channels if err := a.JoinDefaultChannels(team.Id, user, channelRole, userRequestorId); err != nil { - l4g.Error(utils.T("api.user.create_user.joining.error"), user.Id, team.Id, err) + mlog.Error(fmt.Sprintf("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", user.Id, team.Id, err), mlog.String("user_id", user.Id)) } a.ClearSessionCacheForUser(user.Id) @@ -638,11 +638,11 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string) if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages { if requestorId == user.Id { if err := a.postLeaveTeamMessage(user, channel); err != nil { - l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) + mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) } } else { if err := a.postRemoveFromTeamMessage(user, channel); err != nil { - l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) + mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) } } } @@ -908,7 +908,7 @@ func (a *App) GetTeamIdFromQuery(query url.Values) (string, *model.AppError) { } else if len(inviteId) > 0 { if result := <-a.Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { // soft fail, so we still create user but don't auto-join team - l4g.Error("%v", result.Err) + mlog.Error(fmt.Sprintf("%v", result.Err)) } else { return result.Data.(*model.Team).Id, nil } diff --git a/app/user.go b/app/user.go index 34076555e..fd8b6b377 100644 --- a/app/user.go +++ b/app/user.go @@ -22,10 +22,10 @@ import ( "strconv" "strings" - l4g "github.com/alecthomas/log4go" "github.com/disintegration/imaging" "github.com/golang/freetype" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -118,7 +118,7 @@ func (a *App) CreateUserWithInviteId(user *model.User, inviteId string) (*model. a.AddDirectChannels(team.Id, ruser) if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } return ruser, nil @@ -131,7 +131,7 @@ func (a *App) CreateUserAsAdmin(user *model.User) (*model.User, *model.AppError) } if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } return ruser, nil @@ -155,7 +155,7 @@ func (a *App) CreateUserFromSignup(user *model.User) (*model.User, *model.AppErr } if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } return ruser, nil @@ -172,7 +172,7 @@ func (a *App) IsUserSignUpAllowed() *model.AppError { func (a *App) IsFirstUserAccount() bool { if a.SessionCacheLength() == 0 { if cr := <-a.Srv.Store.User().GetTotalUsersCount(); cr.Err != nil { - l4g.Error(cr.Err) + mlog.Error(fmt.Sprint(cr.Err)) return false } else { count := cr.Data.(int64) @@ -227,20 +227,20 @@ func (a *App) createUser(user *model.User) (*model.User, *model.AppError) { } if result := <-a.Srv.Store.User().Save(user); result.Err != nil { - l4g.Error(utils.T("api.user.create_user.save.error"), result.Err) + mlog.Error(fmt.Sprintf("Couldn't save the user err=%v", result.Err)) return nil, result.Err } else { ruser := result.Data.(*model.User) if user.EmailVerified { if err := a.VerifyUserEmail(ruser.Id); err != nil { - l4g.Error(utils.T("api.user.create_user.verified.error"), err) + mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", err)) } } pref := model.Preference{UserId: ruser.Id, Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, Name: ruser.Id, Value: "0"} if presult := <-a.Srv.Store.Preference().Save(&model.Preferences{pref}); presult.Err != nil { - l4g.Error(utils.T("api.user.create_user.tutorial.error"), presult.Err.Message) + mlog.Error(fmt.Sprintf("Encountered error saving tutorial preference, err=%v", presult.Err.Message)) } ruser.Sanitize(map[string]bool{}) @@ -306,7 +306,7 @@ func (a *App) CreateOAuthUser(service string, userData io.Reader, teamId string) err = a.AddDirectChannels(teamId, user) if err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } @@ -851,7 +851,7 @@ func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model a.InvalidateCacheForUser(userId) if user, err := a.GetUser(userId); err != nil { - l4g.Error(utils.T("api.user.get_me.getting.error"), userId) + mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", userId), mlog.String("user_id", userId)) } else { options := a.Config().GetSanitizeOptions() user.SanitizeProfile(options) @@ -1056,14 +1056,14 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, if rusers[0].Email != rusers[1].Email { a.Go(func() { if err := a.SendEmailChangeEmail(rusers[1].Email, rusers[0].Email, rusers[0].Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) if a.Config().EmailSettings.RequireEmailVerification { a.Go(func() { if err := a.SendEmailVerification(rusers[0]); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) } @@ -1072,7 +1072,7 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, if rusers[0].Username != rusers[1].Username { a.Go(func() { if err := a.SendChangeUsernameEmail(rusers[1].Username, rusers[0].Username, rusers[0].Email, rusers[0].Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) } @@ -1117,12 +1117,12 @@ func (a *App) UpdateMfa(activate bool, userId, token string) *model.AppError { var err *model.AppError if user, err = a.GetUser(userId); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) return } if err := a.SendMfaChangeEmail(user.Email, activate, user.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) @@ -1160,7 +1160,7 @@ func (a *App) UpdatePasswordSendEmail(user *model.User, newPassword, method stri a.Go(func() { if err := a.SendPasswordChangeEmail(user.Email, method, user.Locale, a.GetSiteURL()); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) @@ -1194,7 +1194,7 @@ func (a *App) ResetPasswordFromToken(userSuppliedTokenString, newPassword string } if err := a.DeleteToken(token); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } return nil @@ -1278,7 +1278,7 @@ func (a *App) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent if result := <-schan; result.Err != nil { // soft error since the user roles were still updated - l4g.Error(result.Err) + mlog.Error(fmt.Sprint(result.Err)) } a.ClearSessionCacheForUser(user.Id) @@ -1294,9 +1294,9 @@ func (a *App) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent } func (a *App) PermanentDeleteUser(user *model.User) *model.AppError { - l4g.Warn(utils.T("api.user.permanent_delete_user.attempting.warn"), user.Email, user.Id) + mlog.Warn(fmt.Sprintf("Attempting to permanently delete account %v id=%v", user.Email, user.Id), mlog.String("user_id", user.Id)) if user.IsInRole(model.SYSTEM_ADMIN_ROLE_ID) { - l4g.Warn(utils.T("api.user.permanent_delete_user.system_admin.warn"), user.Email) + mlog.Warn(fmt.Sprintf("You are deleting %v that is a system administrator. You may need to set another account as the system administrator using the command line tools.", user.Email)) } if _, err := a.UpdateActive(user, false); err != nil { @@ -1351,7 +1351,7 @@ func (a *App) PermanentDeleteUser(user *model.User) *model.AppError { return result.Err } - l4g.Warn(utils.T("api.user.permanent_delete_user.deleted.warn"), user.Email, user.Id) + mlog.Warn(fmt.Sprintf("Permanently deleted account %v id=%v", user.Email, user.Id), mlog.String("user_id", user.Id)) return nil } @@ -1395,7 +1395,7 @@ func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppErr return err } if err := a.DeleteToken(token); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } diff --git a/app/web_conn.go b/app/web_conn.go index 9ae5505b2..9d8134f34 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -8,10 +8,9 @@ import ( "sync/atomic" "time" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" - l4g "github.com/alecthomas/log4go" "github.com/gorilla/websocket" goi18n "github.com/nicksnyder/go-i18n/i18n" ) @@ -138,9 +137,9 @@ func (c *WebConn) readPump() { if err := c.WebSocket.ReadJSON(&req); err != nil { // browsers will appear as CloseNoStatusReceived if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - l4g.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId)) + mlog.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId)) } else { - l4g.Debug(fmt.Sprintf("websocket.read: closing websocket for userId=%v error=%v", c.UserId, err.Error())) + mlog.Debug(fmt.Sprintf("websocket.read: closing websocket for userId=%v error=%v", c.UserId, err.Error())) } return @@ -177,7 +176,7 @@ func (c *WebConn) writePump() { if msg.EventType() == model.WEBSOCKET_EVENT_TYPING || msg.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE || msg.EventType() == model.WEBSOCKET_EVENT_CHANNEL_VIEWED { - l4g.Info(fmt.Sprintf("websocket.slow: dropping message userId=%v type=%v channelId=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId)) + mlog.Info(fmt.Sprintf("websocket.slow: dropping message userId=%v type=%v channelId=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId)) skipSend = true } } @@ -196,9 +195,9 @@ func (c *WebConn) writePump() { if len(c.Send) >= SEND_DEADLOCK_WARN { if evtOk { - l4g.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v channelId=%v size=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId, len(msg.ToJson()))) + mlog.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v channelId=%v size=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId, len(msg.ToJson()))) } else { - l4g.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v size=%v", c.UserId, msg.EventType(), len(msg.ToJson()))) + mlog.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v size=%v", c.UserId, msg.EventType(), len(msg.ToJson()))) } } @@ -206,9 +205,9 @@ func (c *WebConn) writePump() { if err := c.WebSocket.WriteMessage(websocket.TextMessage, msgBytes); err != nil { // browsers will appear as CloseNoStatusReceived if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - l4g.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId)) + mlog.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId)) } else { - l4g.Debug(fmt.Sprintf("websocket.send: closing websocket for userId=%v, error=%v", c.UserId, err.Error())) + mlog.Debug(fmt.Sprintf("websocket.send: closing websocket for userId=%v, error=%v", c.UserId, err.Error())) } return @@ -226,9 +225,9 @@ func (c *WebConn) writePump() { if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil { // browsers will appear as CloseNoStatusReceived if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - l4g.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId)) + mlog.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId)) } else { - l4g.Debug(fmt.Sprintf("websocket.ticker: closing websocket for userId=%v error=%v", c.UserId, err.Error())) + mlog.Debug(fmt.Sprintf("websocket.ticker: closing websocket for userId=%v error=%v", c.UserId, err.Error())) } return @@ -237,7 +236,7 @@ func (c *WebConn) writePump() { return case <-authTicker.C: if c.GetSessionToken() == "" { - l4g.Debug(fmt.Sprintf("websocket.authTicker: did not authenticate ip=%v", c.WebSocket.RemoteAddr())) + mlog.Debug(fmt.Sprintf("websocket.authTicker: did not authenticate ip=%v", c.WebSocket.RemoteAddr())) return } authTicker.Stop() @@ -261,7 +260,7 @@ func (webCon *WebConn) IsAuthenticated() bool { session, err := webCon.App.GetSession(webCon.GetSessionToken()) if err != nil { - l4g.Error(utils.T("api.websocket.invalid_session.error"), err.Error()) + mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error())) webCon.SetSessionToken("") webCon.SetSession(nil) webCon.SetSessionExpiresAt(0) @@ -334,7 +333,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool { if webCon.AllChannelMembers == nil { if result := <-webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true); result.Err != nil { - l4g.Error("webhub.shouldSendEvent: " + result.Err.Error()) + mlog.Error("webhub.shouldSendEvent: " + result.Err.Error()) return false } else { webCon.AllChannelMembers = result.Data.(map[string]string) @@ -365,7 +364,7 @@ func (webCon *WebConn) IsMemberOfTeam(teamId string) bool { if currentSession == nil || len(currentSession.Token) == 0 { session, err := webCon.App.GetSession(webCon.GetSessionToken()) if err != nil { - l4g.Error(utils.T("api.websocket.invalid_session.error"), err.Error()) + mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error())) return false } else { webCon.SetSession(session) diff --git a/app/web_hub.go b/app/web_hub.go index 971b03481..18eb97c8e 100644 --- a/app/web_hub.go +++ b/app/web_hub.go @@ -13,10 +13,8 @@ import ( "sync/atomic" "time" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) const ( @@ -66,7 +64,7 @@ func (a *App) TotalWebsocketConnections() int { func (a *App) HubStart() { // Total number of hubs is twice the number of CPUs. numberOfHubs := runtime.NumCPU() * 2 - l4g.Info(utils.T("api.web_hub.start.starting.debug"), numberOfHubs) + mlog.Info(fmt.Sprintf("Starting %v websocket hubs", numberOfHubs)) a.Hubs = make([]*Hub, numberOfHubs) a.HubsStopCheckingForDeadlock = make(chan bool, 1) @@ -89,7 +87,7 @@ func (a *App) HubStart() { case <-ticker.C: for _, hub := range a.Hubs { if len(hub.broadcast) >= DEADLOCK_WARN { - l4g.Error("Hub processing might be deadlock on hub %v goroutine %v with %v events in the buffer", hub.connectionIndex, hub.goroutineId, len(hub.broadcast)) + mlog.Error(fmt.Sprintf("Hub processing might be deadlock on hub %v goroutine %v with %v events in the buffer", hub.connectionIndex, hub.goroutineId, len(hub.broadcast))) buf := make([]byte, 1<<16) runtime.Stack(buf, true) output := fmt.Sprintf("%s", buf) @@ -97,7 +95,7 @@ func (a *App) HubStart() { for _, part := range splits { if strings.Contains(part, fmt.Sprintf("%v", hub.goroutineId)) { - l4g.Error("Trace for possible deadlock goroutine %v", part) + mlog.Error(fmt.Sprintf("Trace for possible deadlock goroutine %v", part)) } } } @@ -111,12 +109,12 @@ func (a *App) HubStart() { } func (a *App) HubStop() { - l4g.Info(utils.T("api.web_hub.start.stopping.debug")) + mlog.Info("stopping websocket hub connections") select { case a.HubsStopCheckingForDeadlock <- true: default: - l4g.Warn("We appear to have already sent the stop checking for deadlocks command") + mlog.Warn("We appear to have already sent the stop checking for deadlocks command") } for _, hub := range a.Hubs { @@ -367,7 +365,7 @@ func (h *Hub) Start() { doStart = func() { h.goroutineId = getGoroutineId() - l4g.Debug("Hub for index %v is starting with goroutine %v", h.connectionIndex, h.goroutineId) + mlog.Debug(fmt.Sprintf("Hub for index %v is starting with goroutine %v", h.connectionIndex, h.goroutineId)) connections := newHubConnectionIndex() @@ -404,7 +402,7 @@ func (h *Hub) Start() { select { case webCon.Send <- msg: default: - l4g.Error(fmt.Sprintf("webhub.broadcast: cannot send, closing websocket for userId=%v", webCon.UserId)) + mlog.Error(fmt.Sprintf("webhub.broadcast: cannot send, closing websocket for userId=%v", webCon.UserId)) close(webCon.Send) connections.Remove(webCon) } @@ -438,12 +436,12 @@ func (h *Hub) Start() { doRecover = func() { if !h.ExplicitStop { if r := recover(); r != nil { - l4g.Error(fmt.Sprintf("Recovering from Hub panic. Panic was: %v", r)) + mlog.Error(fmt.Sprintf("Recovering from Hub panic. Panic was: %v", r)) } else { - l4g.Error("Webhub stopped unexpectedly. Recovering.") + mlog.Error("Webhub stopped unexpectedly. Recovering.") } - l4g.Error(string(debug.Stack())) + mlog.Error(string(debug.Stack())) go doRecoverableStart() } diff --git a/app/webhook.go b/app/webhook.go index 5c3e963ce..a5ab28952 100644 --- a/app/webhook.go +++ b/app/webhook.go @@ -4,13 +4,14 @@ package app import ( + "fmt" "io" "net/http" "regexp" "strings" "unicode/utf8" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -107,7 +108,7 @@ func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model. req.Header.Set("Content-Type", contentType) req.Header.Set("Accept", "application/json") if resp, err := a.HTTPClient(false).Do(req); err != nil { - l4g.Error(utils.T("api.post.handle_webhook_events_and_forget.event_post.error"), err.Error()) + mlog.Error(fmt.Sprintf("Event POST failed, err=%s", err.Error())) } else { defer consumeAndClose(resp) @@ -134,7 +135,7 @@ func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model. } if _, err := a.CreateWebhookPost(hook.CreatorId, channel, text, webhookResp.Username, webhookResp.IconURL, webhookResp.Props, webhookResp.Type, postRootId); err != nil { - l4g.Error(utils.T("api.post.handle_webhook_events_and_forget.create_post.error"), err) + mlog.Error(fmt.Sprintf("Failed to create response post, err=%v", err)) } } } diff --git a/app/websocket_router.go b/app/websocket_router.go index 6bc3a6ff7..6c5e142a1 100644 --- a/app/websocket_router.go +++ b/app/websocket_router.go @@ -4,10 +4,10 @@ package app import ( - l4g "github.com/alecthomas/log4go" - + "fmt" "net/http" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -91,7 +91,7 @@ func (wr *WebSocketRouter) ServeWebSocket(conn *WebConn, r *model.WebSocketReque } func ReturnWebSocketError(conn *WebConn, r *model.WebSocketRequest, err *model.AppError) { - l4g.Error(utils.T("api.web_socket_router.log.error"), r.Seq, conn.UserId, err.SystemMessage(utils.T), err.DetailedError) + mlog.Error(fmt.Sprintf("websocket routing error: seq=%v uid=%v %v [details: %v]", r.Seq, conn.UserId, err.SystemMessage(utils.T), err.DetailedError)) err.DetailedError = "" errorResp := model.NewWebSocketError(r.Seq, err) diff --git a/cmd/commands/command.go b/cmd/commands/command.go index 380816644..e7b7e0a0d 100644 --- a/cmd/commands/command.go +++ b/cmd/commands/command.go @@ -5,6 +5,7 @@ package commands import ( "errors" + "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" diff --git a/cmd/commands/config_flag_test.go b/cmd/commands/config_flag_test.go index f31c989d8..59178b620 100644 --- a/cmd/commands/config_flag_test.go +++ b/cmd/commands/config_flag_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "encoding/json" + "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/utils" ) diff --git a/cmd/commands/jobserver.go b/cmd/commands/jobserver.go index b96984b41..a7671e190 100644 --- a/cmd/commands/jobserver.go +++ b/cmd/commands/jobserver.go @@ -8,8 +8,8 @@ import ( "os/signal" "syscall" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/cmd" + "github.com/mattermost/mattermost-server/mlog" "github.com/spf13/cobra" ) @@ -36,13 +36,12 @@ func jobserverCmdF(command *cobra.Command, args []string) { if err != nil { panic(err.Error()) } - defer l4g.Close() defer a.Shutdown() a.LoadLicense() // Run jobs - l4g.Info("Starting Mattermost job server") + mlog.Info("Starting Mattermost job server") if !noJobs { a.Jobs.StartWorkers() } @@ -55,10 +54,10 @@ func jobserverCmdF(command *cobra.Command, args []string) { <-signalChan // Cleanup anything that isn't handled by a defer statement - l4g.Info("Stopping Mattermost job server") + mlog.Info("Stopping Mattermost job server") a.Jobs.StopSchedulers() a.Jobs.StopWorkers() - l4g.Info("Stopped Mattermost job server") + mlog.Info("Stopped Mattermost job server") } diff --git a/cmd/commands/server.go b/cmd/commands/server.go index 9048696ca..77f195f4b 100644 --- a/cmd/commands/server.go +++ b/cmd/commands/server.go @@ -4,18 +4,19 @@ package commands import ( + "fmt" "net" "os" "os/signal" "syscall" "time" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/manualtesting" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/web" @@ -61,7 +62,7 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan a, err := app.New(options...) if err != nil { - l4g.Critical(err.Error()) + mlog.Critical(err.Error()) return err } defer a.Shutdown() @@ -69,17 +70,17 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan utils.TestConnection(a.Config()) pwd, _ := os.Getwd() - l4g.Info(utils.T("mattermost.current_version"), model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise) - l4g.Info(utils.T("mattermost.entreprise_enabled"), model.BuildEnterpriseReady) - l4g.Info(utils.T("mattermost.working_dir"), pwd) - l4g.Info(utils.T("mattermost.config_file"), utils.FindConfigFile(configFileLocation)) + mlog.Info(fmt.Sprintf("Current version is %v (%v/%v/%v/%v)", model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise)) + mlog.Info(fmt.Sprintf("Enterprise Enabled: %v", model.BuildEnterpriseReady)) + mlog.Info(fmt.Sprintf("Current working directory is %v", pwd)) + mlog.Info(fmt.Sprintf("Loaded config file from %v", utils.FindConfigFile(configFileLocation))) backend, appErr := a.FileBackend() if appErr == nil { appErr = backend.TestConnection() } if appErr != nil { - l4g.Error("Problem with file storage settings: " + appErr.Error()) + mlog.Error("Problem with file storage settings: " + appErr.Error()) } if model.BuildEnterpriseReady == "true" { @@ -99,7 +100,7 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan serverErr := a.StartServer() if serverErr != nil { - l4g.Critical(serverErr.Error()) + mlog.Critical(serverErr.Error()) return serverErr } @@ -111,7 +112,7 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan license := a.License() if license == nil && len(a.Config().SqlSettings.DataSourceReplicas) > 1 { - l4g.Warn(utils.T("store.sql.read_replicas_not_licensed.critical")) + mlog.Warn("More than 1 read replica functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license.") a.UpdateConfig(func(cfg *model.Config) { cfg.SqlSettings.DataSourceReplicas = cfg.SqlSettings.DataSourceReplicas[:1] }) @@ -171,7 +172,7 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan if a.Elasticsearch != nil { a.Go(func() { if err := a.Elasticsearch.Start(); err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } }) } @@ -241,7 +242,7 @@ func runSessionCleanupJob(a *app.App) { func resetStatuses(a *app.App) { if result := <-a.Srv.Store.Status().ResetAll(); result.Err != nil { - l4g.Error(utils.T("mattermost.reset_status.error"), result.Err.Error()) + mlog.Error(fmt.Sprint("mattermost.reset_status.error FIXME: NOT FOUND IN TRANSLATIONS FILE", result.Err.Error())) } } @@ -260,11 +261,11 @@ func notifyReady() { // notify systemd that the server is ready. systemdSocket := os.Getenv("NOTIFY_SOCKET") if systemdSocket != "" { - l4g.Info("Sending systemd READY notification.") + mlog.Info("Sending systemd READY notification.") err := sendSystemdReadyNotification(systemdSocket) if err != nil { - l4g.Error(err.Error()) + mlog.Error(err.Error()) } } } diff --git a/cmd/commands/user.go b/cmd/commands/user.go index 96e8698e5..da3fb454b 100644 --- a/cmd/commands/user.go +++ b/cmd/commands/user.go @@ -9,7 +9,6 @@ import ( "fmt" "io/ioutil" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" @@ -644,7 +643,7 @@ func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error { if err := migrate.MigrateToSaml(fromAuth, matches, autoFlag, dryRunFlag); err != nil { return errors.New("Error while migrating users: " + err.Error()) } - l4g.Close() + cmd.CommandPrettyPrintln("Successfully migrated accounts.") } diff --git a/cmd/init.go b/cmd/init.go index c85bd5362..e3b4e97e1 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -33,8 +33,6 @@ func InitDBCommandContext(configFileLocation string) (*app.App, error) { } model.AppErrorInit(utils.T) - utils.ConfigureCmdLineLog() - a, err := app.New(app.ConfigFile(configFileLocation)) if err != nil { return nil, err diff --git a/config/default.json b/config/default.json index 584380453..8bf06dc8b 100644 --- a/config/default.json +++ b/config/default.json @@ -118,9 +118,10 @@ "LogSettings": { "EnableConsole": true, "ConsoleLevel": "DEBUG", + "ConsoleJson": true, "EnableFile": true, "FileLevel": "INFO", - "FileFormat": "", + "FileJson": true, "FileLocation": "", "EnableWebhookDebugging": true, "EnableDiagnostics": true diff --git a/einterfaces/brand.go b/einterfaces/brand.go index fc584a91c..11e7d4e42 100644 --- a/einterfaces/brand.go +++ b/einterfaces/brand.go @@ -4,8 +4,9 @@ package einterfaces import ( - "github.com/mattermost/mattermost-server/model" "mime/multipart" + + "github.com/mattermost/mattermost-server/model" ) type BrandInterface interface { diff --git a/einterfaces/oauthproviders.go b/einterfaces/oauthproviders.go index 7e24d2a70..ed54a204a 100644 --- a/einterfaces/oauthproviders.go +++ b/einterfaces/oauthproviders.go @@ -4,8 +4,9 @@ package einterfaces import ( - "github.com/mattermost/mattermost-server/model" "io" + + "github.com/mattermost/mattermost-server/model" ) type OauthProvider interface { diff --git a/jobs/jobs.go b/jobs/jobs.go index b367730b5..850491403 100644 --- a/jobs/jobs.go +++ b/jobs/jobs.go @@ -5,11 +5,12 @@ package jobs import ( "context" + "fmt" "time" "net/http" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -125,10 +126,10 @@ func (srv *JobServer) CancellationWatcher(ctx context.Context, jobId string, can for { select { case <-ctx.Done(): - l4g.Debug("CancellationWatcher for Job: %v Aborting as job has finished.", jobId) + mlog.Debug(fmt.Sprintf("CancellationWatcher for Job: %v Aborting as job has finished.", jobId)) return case <-time.After(CANCEL_WATCHER_POLLING_INTERVAL * time.Millisecond): - l4g.Debug("CancellationWatcher for Job: %v polling.", jobId) + mlog.Debug(fmt.Sprintf("CancellationWatcher for Job: %v polling.", jobId)) if result := <-srv.Store.Job().Get(jobId); result.Err == nil { jobStatus := result.Data.(*model.Job) if jobStatus.Status == model.JOB_STATUS_CANCEL_REQUESTED { diff --git a/jobs/jobs_watcher.go b/jobs/jobs_watcher.go index 645715db4..07979442d 100644 --- a/jobs/jobs_watcher.go +++ b/jobs/jobs_watcher.go @@ -4,10 +4,11 @@ package jobs import ( + "fmt" "math/rand" "time" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -35,7 +36,7 @@ func (srv *JobServer) MakeWatcher(workers *Workers, pollingInterval int) *Watche } func (watcher *Watcher) Start() { - l4g.Debug("Watcher Started") + mlog.Debug("Watcher Started") // Delay for some random number of milliseconds before starting to ensure that multiple // instances of the jobserver don't poll at a time too close to each other. @@ -43,14 +44,14 @@ func (watcher *Watcher) Start() { <-time.After(time.Duration(rand.Intn(watcher.pollingInterval)) * time.Millisecond) defer func() { - l4g.Debug("Watcher Finished") + mlog.Debug("Watcher Finished") watcher.stopped <- true }() for { select { case <-watcher.stop: - l4g.Debug("Watcher: Received stop signal") + mlog.Debug("Watcher: Received stop signal") return case <-time.After(time.Duration(watcher.pollingInterval) * time.Millisecond): watcher.PollAndNotify() @@ -59,14 +60,14 @@ func (watcher *Watcher) Start() { } func (watcher *Watcher) Stop() { - l4g.Debug("Watcher Stopping") + mlog.Debug("Watcher Stopping") watcher.stop <- true <-watcher.stopped } func (watcher *Watcher) PollAndNotify() { if result := <-watcher.srv.Store.Job().GetAllByStatus(model.JOB_STATUS_PENDING); result.Err != nil { - l4g.Error("Error occurred getting all pending statuses: %v", result.Err.Error()) + mlog.Error(fmt.Sprintf("Error occurred getting all pending statuses: %v", result.Err.Error())) } else { jobs := result.Data.([]*model.Job) diff --git a/jobs/schedulers.go b/jobs/schedulers.go index 839cbbae8..2823036df 100644 --- a/jobs/schedulers.go +++ b/jobs/schedulers.go @@ -4,11 +4,11 @@ package jobs import ( + "fmt" "sync" "time" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -25,7 +25,7 @@ type Schedulers struct { } func (srv *JobServer) InitSchedulers() *Schedulers { - l4g.Debug("Initialising schedulers.") + mlog.Debug("Initialising schedulers.") schedulers := &Schedulers{ stop: make(chan bool), @@ -59,10 +59,10 @@ func (schedulers *Schedulers) Start() *Schedulers { go func() { schedulers.startOnce.Do(func() { - l4g.Info("Starting schedulers.") + mlog.Info("Starting schedulers.") defer func() { - l4g.Info("Schedulers stopped.") + mlog.Info("Schedulers stopped.") close(schedulers.stopped) }() @@ -78,7 +78,7 @@ func (schedulers *Schedulers) Start() *Schedulers { for { select { case <-schedulers.stop: - l4g.Debug("Schedulers received stop signal.") + mlog.Debug("Schedulers received stop signal.") return case now = <-time.After(1 * time.Minute): cfg := schedulers.jobs.Config() @@ -93,8 +93,8 @@ func (schedulers *Schedulers) Start() *Schedulers { if scheduler != nil { if scheduler.Enabled(cfg) { if _, err := schedulers.scheduleJob(cfg, scheduler); err != nil { - l4g.Warn("Failed to schedule job with scheduler: %v", scheduler.Name()) - l4g.Error(err) + mlog.Warn(fmt.Sprintf("Failed to schedule job with scheduler: %v", scheduler.Name())) + mlog.Error(fmt.Sprint(err)) } else { schedulers.setNextRunTime(cfg, idx, now, true) } @@ -119,7 +119,7 @@ func (schedulers *Schedulers) Start() *Schedulers { } func (schedulers *Schedulers) Stop() *Schedulers { - l4g.Info("Stopping schedulers.") + mlog.Info("Stopping schedulers.") close(schedulers.stop) <-schedulers.stopped return schedulers @@ -130,7 +130,7 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim if !pendingJobs { if pj, err := schedulers.jobs.CheckForPendingJobsByType(scheduler.JobType()); err != nil { - l4g.Error("Failed to set next job run time: " + err.Error()) + mlog.Error("Failed to set next job run time: " + err.Error()) schedulers.nextRunTimes[idx] = nil return } else { @@ -140,13 +140,13 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim lastSuccessfulJob, err := schedulers.jobs.GetLastSuccessfulJobByType(scheduler.JobType()) if err != nil { - l4g.Error("Failed to set next job run time: " + err.Error()) + mlog.Error("Failed to set next job run time: " + err.Error()) schedulers.nextRunTimes[idx] = nil return } schedulers.nextRunTimes[idx] = scheduler.NextScheduleTime(cfg, now, pendingJobs, lastSuccessfulJob) - l4g.Debug("Next run time for scheduler %v: %v", scheduler.Name(), schedulers.nextRunTimes[idx]) + mlog.Debug(fmt.Sprintf("Next run time for scheduler %v: %v", scheduler.Name(), schedulers.nextRunTimes[idx])) } func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Scheduler) (*model.Job, *model.AppError) { @@ -164,6 +164,6 @@ func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Sch } func (schedulers *Schedulers) handleConfigChange(oldConfig *model.Config, newConfig *model.Config) { - l4g.Debug("Schedulers received config change.") + mlog.Debug("Schedulers received config change.") schedulers.configChanged <- newConfig } diff --git a/jobs/workers.go b/jobs/workers.go index ca34855e5..57a255013 100644 --- a/jobs/workers.go +++ b/jobs/workers.go @@ -6,7 +6,7 @@ package jobs import ( "sync" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -54,7 +54,7 @@ func (srv *JobServer) InitWorkers() *Workers { } func (workers *Workers) Start() *Workers { - l4g.Info("Starting workers") + mlog.Info("Starting workers") workers.startOnce.Do(func() { if workers.DataRetention != nil && (*workers.ConfigService.Config().DataRetentionSettings.EnableMessageDeletion || *workers.ConfigService.Config().DataRetentionSettings.EnableFileDeletion) { @@ -152,7 +152,7 @@ func (workers *Workers) Stop() *Workers { workers.LdapSync.Stop() } - l4g.Info("Stopped workers") + mlog.Info("Stopped workers") return workers } diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index 9743df006..7b78fd312 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -4,6 +4,7 @@ package manualtesting import ( + "fmt" "hash/fnv" "math/rand" "net/http" @@ -11,9 +12,9 @@ import ( "strconv" "time" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -34,7 +35,7 @@ func Init(api3 *api.API) { func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { // Let the world know - l4g.Info(utils.T("manaultesting.manual_test.setup.info")) + mlog.Info("Setting up for manual test...") // URL Parameters params, err := url.ParseQuery(r.URL.RawQuery) @@ -51,7 +52,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { hash := hasher.Sum32() rand.Seed(int64(hash)) } else { - l4g.Debug(utils.T("manaultesting.manual_test.uid.debug")) + mlog.Debug("No uid in URL") } // Create a client for tests to use @@ -63,7 +64,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { var teamID string var userID string if ok1 && ok2 { - l4g.Info(utils.T("manaultesting.manual_test.create.info")) + mlog.Info("Creating user and team") // Create team for testing team := &model.Team{ DisplayName: teamDisplayName[0], @@ -155,7 +156,7 @@ func getChannelID(a *app.App, channelname string, teamid string, userid string) // Grab all the channels result := <-a.Srv.Store.Channel().GetChannels(teamid, userid) if result.Err != nil { - l4g.Debug(utils.T("manaultesting.get_channel_id.unable.debug")) + mlog.Debug("Unable to get channels") return "", false } @@ -166,6 +167,6 @@ func getChannelID(a *app.App, channelname string, teamid string, userid string) return channel.Id, true } } - l4g.Debug(utils.T("manaultesting.get_channel_id.no_found.debug"), channelname, strconv.Itoa(len(data))) + mlog.Debug(fmt.Sprintf("Could not find channel: %v, %v possibilities searched", channelname, strconv.Itoa(len(data)))) return "", false } diff --git a/manualtesting/test_autolink.go b/manualtesting/test_autolink.go index 434e50c95..3fe589241 100644 --- a/manualtesting/test_autolink.go +++ b/manualtesting/test_autolink.go @@ -6,9 +6,8 @@ package manualtesting import ( "net/http" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) const LINK_POST_TEXT = ` @@ -23,7 +22,7 @@ https://medium.com/@slackhq/11-useful-tips-for-getting-the-most-of-slack-5dfb3d1 ` func testAutoLink(env TestEnvironment) *model.AppError { - l4g.Info(utils.T("manaultesting.test_autolink.info")) + mlog.Info("Manual Auto Link Test") channelID, err := getChannelID(env.Context.App, model.DEFAULT_CHANNEL, env.CreatedTeamId, env.CreatedUserId) if !err { return model.NewAppError("/manualtest", "manaultesting.test_autolink.unable.app_error", nil, "", http.StatusInternalServerError) diff --git a/mlog/global.go b/mlog/global.go new file mode 100644 index 000000000..91d5858a4 --- /dev/null +++ b/mlog/global.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mlog + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var globalLogger *Logger + +func InitGlobalLogger(logger *Logger) { + globalLogger = logger + Debug = globalLogger.Debug + Info = globalLogger.Info + Warn = globalLogger.Warn + Error = globalLogger.Error + Critical = globalLogger.Critical +} + +func RedirectStdLog(logger *Logger) { + zap.RedirectStdLogAt(logger.zap.With(zap.String("source", "stdlog")), zapcore.ErrorLevel) +} + +type LogFunc func(string, ...Field) + +// DON'T USE THIS Modify the level on the app logger +func GloballyDisableDebugLogForTest() { + globalLogger.consoleLevel.SetLevel(zapcore.ErrorLevel) +} + +// DON'T USE THIS Modify the level on the app logger +func GloballyEnableDebugLogForTest() { + globalLogger.consoleLevel.SetLevel(zapcore.DebugLevel) +} + +var Debug LogFunc +var Info LogFunc +var Warn LogFunc +var Error LogFunc +var Critical LogFunc diff --git a/mlog/log.go b/mlog/log.go new file mode 100644 index 000000000..801030c6c --- /dev/null +++ b/mlog/log.go @@ -0,0 +1,143 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mlog + +import ( + "log" + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +const ( + // Very verbose messages for debugging specific issues + LevelDebug = "debug" + // Default log level, informational + LevelInfo = "info" + // Warnings are messages about possible issues + LevelWarn = "warn" + // Errors are messages about things we know are problems + LevelError = "error" +) + +// Type and function aliases from zap to limit the libraries scope into MM code +type Field = zapcore.Field + +var Int64 = zap.Int64 +var Int = zap.Int +var String = zap.String + +type LoggerConfiguration struct { + EnableConsole bool + ConsoleJson bool + ConsoleLevel string + EnableFile bool + FileJson bool + FileLevel string + FileLocation string +} + +type Logger struct { + zap *zap.Logger + consoleLevel zap.AtomicLevel + fileLevel zap.AtomicLevel +} + +func getZapLevel(level string) zapcore.Level { + switch level { + case LevelInfo: + return zapcore.InfoLevel + case LevelWarn: + return zapcore.WarnLevel + case LevelDebug: + return zapcore.DebugLevel + case LevelError: + return zapcore.ErrorLevel + default: + return zapcore.InfoLevel + } +} + +func NewLogger(config *LoggerConfiguration) *Logger { + cores := []zapcore.Core{} + logger := &Logger{ + consoleLevel: zap.NewAtomicLevelAt(getZapLevel(config.ConsoleLevel)), + fileLevel: zap.NewAtomicLevelAt(getZapLevel(config.FileLevel)), + } + + encoderConfig := zap.NewProductionEncoderConfig() + var encoder zapcore.Encoder + if config.ConsoleJson { + encoder = zapcore.NewJSONEncoder(encoderConfig) + } else { + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoder = zapcore.NewConsoleEncoder(encoderConfig) + } + + if config.EnableConsole { + writer := zapcore.Lock(os.Stdout) + core := zapcore.NewCore(encoder, writer, logger.consoleLevel) + cores = append(cores, core) + } + + if config.EnableFile { + writer := zapcore.AddSync(&lumberjack.Logger{ + Filename: config.FileLocation, + MaxSize: 100, + Compress: true, + }) + core := zapcore.NewCore(encoder, writer, logger.fileLevel) + cores = append(cores, core) + } + + combinedCore := zapcore.NewTee(cores...) + + logger.zap = zap.New(combinedCore, + zap.AddCallerSkip(2), + zap.AddCaller(), + ) + + return logger +} + +func (l *Logger) ChangeLevels(config *LoggerConfiguration) { + l.consoleLevel.SetLevel(getZapLevel(config.ConsoleLevel)) + l.fileLevel.SetLevel(getZapLevel(config.FileLevel)) +} + +func (l *Logger) SetConsoleLevel(level string) { + l.consoleLevel.SetLevel(getZapLevel(level)) +} + +func (l *Logger) With(fields ...Field) *Logger { + newlogger := *l + newlogger.zap = newlogger.zap.With(fields...) + return &newlogger +} + +func (l *Logger) StdLog(fields ...Field) *log.Logger { + return zap.NewStdLog(l.With(fields...).zap) +} + +func (l *Logger) Debug(message string, fields ...Field) { + l.zap.Debug(message, fields...) +} + +func (l *Logger) Info(message string, fields ...Field) { + l.zap.Info(message, fields...) +} + +func (l *Logger) Warn(message string, fields ...Field) { + l.zap.Warn(message, fields...) +} + +func (l *Logger) Error(message string, fields ...Field) { + l.zap.Error(message, fields...) +} + +func (l *Logger) Critical(message string, fields ...Field) { + l.zap.Error(message, fields...) +} diff --git a/model/client.go b/model/client.go index 317374d36..e648ca279 100644 --- a/model/client.go +++ b/model/client.go @@ -14,8 +14,6 @@ import ( "strconv" "strings" "time" - - l4g "github.com/alecthomas/log4go" ) var UsedApiV3 *int32 = new(int32) @@ -210,7 +208,7 @@ func getCookie(name string, resp *http.Response) *http.Cookie { // Must is a convenience function used for testing. func (c *Client) Must(result *Result, err *AppError) *Result { if err != nil { - l4g.Close() + time.Sleep(time.Second) panic(err) } @@ -221,7 +219,7 @@ func (c *Client) Must(result *Result, err *AppError) *Result { // MustGeneric is a convenience function used for testing. func (c *Client) MustGeneric(result interface{}, err *AppError) interface{} { if err != nil { - l4g.Close() + time.Sleep(time.Second) panic(err) } diff --git a/model/config.go b/model/config.go index fb43dea42..5074b7637 100644 --- a/model/config.go +++ b/model/config.go @@ -585,9 +585,10 @@ func (s *SqlSettings) SetDefaults() { type LogSettings struct { EnableConsole bool ConsoleLevel string + ConsoleJson *bool EnableFile bool FileLevel string - FileFormat string + FileJson *bool FileLocation string EnableWebhookDebugging bool EnableDiagnostics *bool @@ -597,6 +598,14 @@ func (s *LogSettings) SetDefaults() { if s.EnableDiagnostics == nil { s.EnableDiagnostics = NewBool(true) } + + if s.ConsoleJson == nil { + s.ConsoleJson = NewBool(true) + } + + if s.FileJson == nil { + s.FileJson = NewBool(true) + } } type PasswordSettings struct { diff --git a/store/layered_store.go b/store/layered_store.go index 5ef907260..a0a31fb39 100644 --- a/store/layered_store.go +++ b/store/layered_store.go @@ -6,8 +6,8 @@ package store import ( "context" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -42,7 +42,7 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn // Setup the chain if ENABLE_EXPERIMENTAL_REDIS { - l4g.Debug("Experimental redis enabled.") + mlog.Debug("Experimental redis enabled.") store.RedisLayer = NewRedisSupplier() store.RedisLayer.SetChainNext(store.DatabaseLayer) store.LayerChainHead = store.RedisLayer diff --git a/store/redis_supplier.go b/store/redis_supplier.go index 751227be9..ce8cb0f0d 100644 --- a/store/redis_supplier.go +++ b/store/redis_supplier.go @@ -9,8 +9,8 @@ import ( "time" - l4g "github.com/alecthomas/log4go" "github.com/go-redis/redis" + "github.com/mattermost/mattermost-server/mlog" ) const REDIS_EXPIRY_TIME = 30 * time.Minute @@ -45,7 +45,7 @@ func NewRedisSupplier() *RedisSupplier { }) if _, err := supplier.client.Ping().Result(); err != nil { - l4g.Error("Unable to ping redis server: " + err.Error()) + mlog.Error("Unable to ping redis server: " + err.Error()) return nil } diff --git a/store/redis_supplier_reactions.go b/store/redis_supplier_reactions.go index cece8113d..ec9a4b4e0 100644 --- a/store/redis_supplier_reactions.go +++ b/store/redis_supplier_reactions.go @@ -6,21 +6,20 @@ package store import ( "context" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) func (s *RedisSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil { - l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) + mlog.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) } return s.Next().ReactionSave(ctx, reaction, hints...) } func (s *RedisSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil { - l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) + mlog.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) } return s.Next().ReactionDelete(ctx, reaction, hints...) } @@ -34,13 +33,13 @@ func (s *RedisSupplier) ReactionGetForPost(ctx context.Context, postId string, h return result } if err != nil { - l4g.Error("Redis encountered an error on read: " + err.Error()) + mlog.Error("Redis encountered an error on read: " + err.Error()) } result := s.Next().ReactionGetForPost(ctx, postId, hints...) if err := s.save("reactions:"+postId, result.Data, REDIS_EXPIRY_TIME); err != nil { - l4g.Error("Redis encountered and error on write: " + err.Error()) + mlog.Error("Redis encountered and error on write: " + err.Error()) } return result diff --git a/store/redis_supplier_roles.go b/store/redis_supplier_roles.go index 232a8c040..c53614113 100644 --- a/store/redis_supplier_roles.go +++ b/store/redis_supplier_roles.go @@ -7,8 +7,7 @@ import ( "context" "fmt" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -16,7 +15,7 @@ func (s *RedisSupplier) RoleSave(ctx context.Context, role *model.Role, hints .. key := buildRedisKeyForRoleName(role.Name) if err := s.client.Del(key).Err(); err != nil { - l4g.Error("Redis failed to remove key " + key + " Error: " + err.Error()) + mlog.Error("Redis failed to remove key " + key + " Error: " + err.Error()) } return s.Next().RoleSave(ctx, role, hints...) @@ -34,7 +33,7 @@ func (s *RedisSupplier) RoleGetByName(ctx context.Context, name string, hints .. var role *model.Role found, err := s.load(key, &role) if err != nil { - l4g.Error("Redis encountered an error on read: " + err.Error()) + mlog.Error("Redis encountered an error on read: " + err.Error()) } else if found { result := NewSupplierResult() result.Data = role @@ -45,7 +44,7 @@ func (s *RedisSupplier) RoleGetByName(ctx context.Context, name string, hints .. if result.Err == nil { if err := s.save(key, result.Data, REDIS_EXPIRY_TIME); err != nil { - l4g.Error("Redis encountered and error on write: " + err.Error()) + mlog.Error("Redis encountered and error on write: " + err.Error()) } } @@ -64,7 +63,7 @@ func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, } else { rolesToQuery = append(rolesToQuery, roleName) if err != nil { - l4g.Error("Redis encountered an error on read: " + err.Error()) + mlog.Error("Redis encountered an error on read: " + err.Error()) } } } @@ -75,7 +74,7 @@ func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, rolesFound := result.Data.([]*model.Role) for _, role := range rolesFound { if err := s.save(buildRedisKeyForRoleName(role.Name), role, REDIS_EXPIRY_TIME); err != nil { - l4g.Error("Redis encountered and error on write: " + err.Error()) + mlog.Error("Redis encountered and error on write: " + err.Error()) } } result.Data = append(foundRoles, result.Data.([]*model.Role)...) @@ -87,10 +86,10 @@ func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, func (s *RedisSupplier) RolePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { defer func() { if keys, err := s.client.Keys("roles:*").Result(); err != nil { - l4g.Error("Redis encountered an error on read: " + err.Error()) + mlog.Error("Redis encountered an error on read: " + err.Error()) } else { if err := s.client.Del(keys...).Err(); err != nil { - l4g.Error("Redis encountered an error on delete: " + err.Error()) + mlog.Error("Redis encountered an error on delete: " + err.Error()) } } }() diff --git a/store/sqlstore/channel_member_history_store.go b/store/sqlstore/channel_member_history_store.go index 6fc78b514..70ad16467 100644 --- a/store/sqlstore/channel_member_history_store.go +++ b/store/sqlstore/channel_member_history_store.go @@ -4,11 +4,12 @@ package sqlstore import ( + "fmt" "net/http" "database/sql" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -60,7 +61,7 @@ func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId str result.Err = model.NewAppError("SqlChannelMemberHistoryStore.LogLeaveEvent", "store.sql_channel_member_history.log_leave_event.update_error", params, err.Error(), http.StatusInternalServerError) } else if rows, err := sqlResult.RowsAffected(); err == nil && rows != 1 { // there was no join event to update - this is best effort, so no need to raise an error - l4g.Warn("Channel join event for user %v and channel %v not found", userId, channelId) + mlog.Warn(fmt.Sprintf("Channel join event for user %v and channel %v not found", userId, channelId), mlog.String("user_id", userId)) } }) } diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 21785c461..3bd87961a 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -11,9 +11,9 @@ import ( "strconv" "strings" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/gorp" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -852,7 +852,7 @@ func (s SqlChannelStore) IsUserInChannelUseCache(userId string, channelId string } if result := <-s.GetAllChannelMembersForUser(userId, true); result.Err != nil { - l4g.Error("SqlChannelStore.IsUserInChannelUseCache: " + result.Err.Error()) + mlog.Error("SqlChannelStore.IsUserInChannelUseCache: " + result.Err.Error()) return false } else { ids := result.Data.(map[string]string) diff --git a/store/sqlstore/command_webhook_store.go b/store/sqlstore/command_webhook_store.go index 40fa8577c..1ea4f7328 100644 --- a/store/sqlstore/command_webhook_store.go +++ b/store/sqlstore/command_webhook_store.go @@ -7,8 +7,7 @@ import ( "database/sql" "net/http" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -86,9 +85,9 @@ func (s SqlCommandWebhookStore) TryUse(id string, limit int) store.StoreChannel } func (s SqlCommandWebhookStore) Cleanup() { - l4g.Debug("Cleaning up command webhook store.") + mlog.Debug("Cleaning up command webhook store.") exptime := model.GetMillis() - model.COMMAND_WEBHOOK_LIFETIME if _, err := s.GetMaster().Exec("DELETE FROM CommandWebhooks WHERE CreateAt < :ExpTime", map[string]interface{}{"ExpTime": exptime}); err != nil { - l4g.Error("Unable to cleanup command webhook store.") + mlog.Error("Unable to cleanup command webhook store.") } } diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index dc7248057..75154791c 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -12,8 +12,8 @@ import ( "strings" "sync" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -947,7 +947,7 @@ func (s *SqlPostStore) Search(teamId string, userId string, params *model.Search _, err := s.GetSearchReplica().Select(&posts, searchQuery, queryParams) if err != nil { - l4g.Warn(utils.T("store.sql_post.search.warn"), err.Error()) + mlog.Warn(fmt.Sprintf("Query error searching posts: %v", err.Error())) // Don't return the error to the caller as it is of no use to the user. Instead return an empty set of search results. } else { for _, p := range posts { @@ -1147,7 +1147,7 @@ func (s *SqlPostStore) GetPostsByIds(postIds []string) store.StoreChannel { _, err := s.GetReplica().Select(&posts, query, params) if err != nil { - l4g.Error(err) + mlog.Error(fmt.Sprint(err)) result.Err = model.NewAppError("SqlPostStore.GetPostsByIds", "store.sql_post.get_posts_by_ids.app_error", nil, "", http.StatusInternalServerError) } else { result.Data = posts @@ -1247,7 +1247,7 @@ func (s *SqlPostStore) determineMaxPostSize() int { table_name = 'posts' AND column_name = 'message' `); err != nil { - l4g.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error()) + mlog.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error()) } } else if s.DriverName() == model.DATABASE_DRIVER_MYSQL { // The Post.Message column in MySQL has historically been TEXT, with a maximum @@ -1263,10 +1263,10 @@ func (s *SqlPostStore) determineMaxPostSize() int { AND column_name = 'Message' LIMIT 0, 1 `); err != nil { - l4g.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error()) + mlog.Error(utils.T("store.sql_post.query_max_post_size.error") + err.Error()) } } else { - l4g.Warn(utils.T("store.sql_post.query_max_post_size.unrecognized_driver")) + mlog.Warn("No implementation found to determine the maximum supported post size") } // Assume a worst-case representation of four bytes per rune. @@ -1279,7 +1279,7 @@ func (s *SqlPostStore) determineMaxPostSize() int { maxPostSize = model.POST_MESSAGE_MAX_RUNES_V1 } - l4g.Info(utils.T("store.sql_post.query_max_post_size.max_post_size_bytes"), maxPostSize, maxPostSizeBytes) + mlog.Info(fmt.Sprintf("Post.Message supports at most %d characters (%d bytes)", maxPostSize, maxPostSizeBytes)) return maxPostSize } diff --git a/store/sqlstore/preference_store.go b/store/sqlstore/preference_store.go index 791b51d2f..bfe5f6613 100644 --- a/store/sqlstore/preference_store.go +++ b/store/sqlstore/preference_store.go @@ -6,9 +6,9 @@ package sqlstore import ( "net/http" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/gorp" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -38,7 +38,7 @@ func (s SqlPreferenceStore) CreateIndexesIfNotExists() { } func (s SqlPreferenceStore) DeleteUnusedFeatures() { - l4g.Debug("Deleting any unused pre-release features") + mlog.Debug("Deleting any unused pre-release features") sql := `DELETE FROM Preferences diff --git a/store/sqlstore/session_store.go b/store/sqlstore/session_store.go index 221603865..c63abefbb 100644 --- a/store/sqlstore/session_store.go +++ b/store/sqlstore/session_store.go @@ -4,10 +4,11 @@ package sqlstore import ( + "fmt" "net/http" "time" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -223,7 +224,7 @@ func (me SqlSessionStore) AnalyticsSessionCount() store.StoreChannel { } func (me SqlSessionStore) Cleanup(expiryTime int64, batchSize int64) { - l4g.Debug("Cleaning up session store.") + mlog.Debug("Cleaning up session store.") var query string if me.DriverName() == model.DATABASE_DRIVER_POSTGRES { @@ -236,13 +237,13 @@ func (me SqlSessionStore) Cleanup(expiryTime int64, batchSize int64) { for rowsAffected > 0 { if sqlResult, err := me.GetMaster().Exec(query, map[string]interface{}{"ExpiresAt": expiryTime, "Limit": batchSize}); err != nil { - l4g.Error("Unable to cleanup session store. err=%v", err.Error()) + mlog.Error(fmt.Sprintf("Unable to cleanup session store. err=%v", err.Error())) return } else { var rowErr error rowsAffected, rowErr = sqlResult.RowsAffected() if rowErr != nil { - l4g.Error("Unable to cleanup session store. err=%v", err.Error()) + mlog.Error(fmt.Sprintf("Unable to cleanup session store. err=%v", err.Error())) return } } diff --git a/store/sqlstore/store_test.go b/store/sqlstore/store_test.go index 41d6a9b7e..58065d65d 100644 --- a/store/sqlstore/store_test.go +++ b/store/sqlstore/store_test.go @@ -8,6 +8,7 @@ import ( "sync" "testing" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/storetest" @@ -98,6 +99,15 @@ func tearDownStores() { } func TestMain(m *testing.M) { + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + utils.TranslationsPreInit() status := 0 diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 8f37db0cd..69b30be59 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -15,11 +15,11 @@ import ( "sync/atomic" "time" - l4g "github.com/alecthomas/log4go" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/mattermost/gorp" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" @@ -144,7 +144,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter err := supplier.GetMaster().CreateTablesIfNotExists() if err != nil { - l4g.Critical(utils.T("store.sql.creating_tables.critical"), err) + mlog.Critical(fmt.Sprintf("Error creating database tables: %v", err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_TABLE) } @@ -189,13 +189,13 @@ func (s *SqlSupplier) Next() store.LayeredStoreSupplier { func setupConnection(con_type string, dataSource string, settings *model.SqlSettings) *gorp.DbMap { db, err := dbsql.Open(*settings.DriverName, dataSource) if err != nil { - l4g.Critical("Failed to open SQL connection to err:%v", err.Error()) + mlog.Critical(fmt.Sprintf("Failed to open SQL connection to err:%v", err.Error())) time.Sleep(time.Second) os.Exit(EXIT_DB_OPEN) } for i := 0; i < DB_PING_ATTEMPTS; i++ { - l4g.Info("Pinging SQL %v database", con_type) + mlog.Info(fmt.Sprintf("Pinging SQL %v database", con_type)) ctx, cancel := context.WithTimeout(context.Background(), DB_PING_TIMEOUT_SECS*time.Second) defer cancel() err = db.PingContext(ctx) @@ -203,11 +203,11 @@ func setupConnection(con_type string, dataSource string, settings *model.SqlSett break } else { if i == DB_PING_ATTEMPTS-1 { - l4g.Critical("Failed to ping DB, server will exit err=%v", err) + mlog.Critical(fmt.Sprintf("Failed to ping DB, server will exit err=%v", err)) time.Sleep(time.Second) os.Exit(EXIT_PING) } else { - l4g.Error("Failed to ping DB retrying in %v seconds err=%v", DB_PING_TIMEOUT_SECS, err) + mlog.Error(fmt.Sprintf("Failed to ping DB retrying in %v seconds err=%v", DB_PING_TIMEOUT_SECS, err)) time.Sleep(DB_PING_TIMEOUT_SECS * time.Second) } } @@ -228,7 +228,7 @@ func setupConnection(con_type string, dataSource string, settings *model.SqlSett } else if *settings.DriverName == model.DATABASE_DRIVER_POSTGRES { dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}, QueryTimeout: connectionTimeout} } else { - l4g.Critical(utils.T("store.sql.dialect_driver.critical")) + mlog.Critical("Failed to create dialect specific driver") time.Sleep(time.Second) os.Exit(EXIT_NO_DRIVER) } @@ -338,7 +338,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool { ) if err != nil { - l4g.Critical(utils.T("store.sql.table_exists.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err)) time.Sleep(time.Second) os.Exit(EXIT_TABLE_EXISTS) } @@ -360,7 +360,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool { ) if err != nil { - l4g.Critical(utils.T("store.sql.table_exists.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err)) time.Sleep(time.Second) os.Exit(EXIT_TABLE_EXISTS_MYSQL) } @@ -382,7 +382,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool { return count > 0 } else { - l4g.Critical(utils.T("store.sql.column_exists_missing_driver.critical")) + mlog.Critical("Failed to check if column exists because of missing driver") time.Sleep(time.Second) os.Exit(EXIT_COLUMN_EXISTS) return false @@ -406,7 +406,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool return false } - l4g.Critical(utils.T("store.sql.column_exists.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err)) time.Sleep(time.Second) os.Exit(EXIT_DOES_COLUMN_EXISTS_POSTGRES) } @@ -429,7 +429,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool ) if err != nil { - l4g.Critical(utils.T("store.sql.column_exists.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err)) time.Sleep(time.Second) os.Exit(EXIT_DOES_COLUMN_EXISTS_MYSQL) } @@ -452,7 +452,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool return count > 0 } else { - l4g.Critical(utils.T("store.sql.column_exists_missing_driver.critical")) + mlog.Critical("Failed to check if column exists because of missing driver") time.Sleep(time.Second) os.Exit(EXIT_DOES_COLUMN_EXISTS_MISSING) return false @@ -468,7 +468,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'") if err != nil { - l4g.Critical(utils.T("store.sql.create_column.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to create column %v", err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_POSTGRES) } @@ -478,7 +478,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'") if err != nil { - l4g.Critical(utils.T("store.sql.create_column.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to create column %v", err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_MYSQL) } @@ -486,7 +486,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri return true } else { - l4g.Critical(utils.T("store.sql.create_column_missing_driver.critical")) + mlog.Critical("Failed to create column because of missing driver") time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_MISSING) return false @@ -501,7 +501,7 @@ func (ss *SqlSupplier) RemoveColumnIfExists(tableName string, columnName string) _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " DROP COLUMN " + columnName) if err != nil { - l4g.Critical("Failed to drop column %v", err) + mlog.Critical(fmt.Sprintf("Failed to drop column %v", err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_COLUMN) } @@ -516,7 +516,7 @@ func (ss *SqlSupplier) RemoveTableIfExists(tableName string) bool { _, err := ss.GetMaster().ExecNoTimeout("DROP TABLE " + tableName) if err != nil { - l4g.Critical("Failed to drop table %v", err) + mlog.Critical(fmt.Sprintf("Failed to drop table %v", err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_TABLE) } @@ -537,7 +537,7 @@ func (ss *SqlSupplier) RenameColumnIfExists(tableName string, oldColumnName stri } if err != nil { - l4g.Critical(utils.T("store.sql.rename_column.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to rename column %v", err)) time.Sleep(time.Second) os.Exit(EXIT_RENAME_COLUMN) } @@ -559,7 +559,7 @@ func (ss *SqlSupplier) GetMaxLengthOfColumnIfExists(tableName string, columnName } if err != nil { - l4g.Critical(utils.T("store.sql.maxlength_column.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to get max length of column %v", err)) time.Sleep(time.Second) os.Exit(EXIT_MAX_COLUMN) } @@ -580,7 +580,7 @@ func (ss *SqlSupplier) AlterColumnTypeIfExists(tableName string, columnName stri } if err != nil { - l4g.Critical(utils.T("store.sql.alter_column_type.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to alter column type %v", err)) time.Sleep(time.Second) os.Exit(EXIT_ALTER_COLUMN) } @@ -621,7 +621,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string query := "" if indexType == INDEX_TYPE_FULL_TEXT { if len(columnNames) != 1 { - l4g.Critical("Unable to create multi column full text index") + mlog.Critical("Unable to create multi column full text index") os.Exit(EXIT_CREATE_INDEX_POSTGRES) } columnName := columnNames[0] @@ -633,7 +633,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string _, err := ss.GetMaster().ExecNoTimeout(query) if err != nil { - l4g.Critical("Failed to create index %v, %v", errExists, err) + mlog.Critical(fmt.Sprintf("Failed to create index %v, %v", errExists, err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_POSTGRES) } @@ -641,7 +641,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) if err != nil { - l4g.Critical(utils.T("store.sql.check_index.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to check index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_MYSQL) } @@ -657,19 +657,19 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string _, err = ss.GetMaster().ExecNoTimeout("CREATE " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")") if err != nil { - l4g.Critical("Failed to create index %v", err) + mlog.Critical(fmt.Sprintf("Failed to create index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_FULL_MYSQL) } } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { _, err := ss.GetMaster().ExecNoTimeout("CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")") if err != nil { - l4g.Critical("Failed to create index %v", err) + mlog.Critical(fmt.Sprintf("Failed to create index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_SQLITE) } } else { - l4g.Critical(utils.T("store.sql.create_index_missing_driver.critical")) + mlog.Critical("Failed to create index because of missing driver") time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_MISSING) } @@ -688,7 +688,7 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b _, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName) if err != nil { - l4g.Critical(utils.T("store.sql.remove_index.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to remove index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_POSTGRES) } @@ -698,7 +698,7 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) if err != nil { - l4g.Critical(utils.T("store.sql.check_index.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to check index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_MYSQL) } @@ -709,19 +709,19 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b _, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName + " ON " + tableName) if err != nil { - l4g.Critical(utils.T("store.sql.remove_index.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to remove index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_MYSQL) } } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { _, err := ss.GetMaster().ExecNoTimeout("DROP INDEX IF EXISTS " + indexName) if err != nil { - l4g.Critical(utils.T("store.sql.remove_index.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to remove index %v", err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_SQLITE) } } else { - l4g.Critical(utils.T("store.sql.create_index_missing_driver.critical")) + mlog.Critical("Failed to create index because of missing driver") time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_MISSING) } @@ -758,7 +758,7 @@ func (ss *SqlSupplier) GetAllConns() []*gorp.DbMap { } func (ss *SqlSupplier) Close() { - l4g.Info(utils.T("store.sql.closing.info")) + mlog.Info("Closing SqlStore") ss.master.Db.Close() for _, replica := range ss.replicas { replica.Db.Close() diff --git a/store/sqlstore/supplier_reactions.go b/store/sqlstore/supplier_reactions.go index aa3b078ea..5a9c9302a 100644 --- a/store/sqlstore/supplier_reactions.go +++ b/store/sqlstore/supplier_reactions.go @@ -5,14 +5,13 @@ package sqlstore import ( "context" + "fmt" "net/http" - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/gorp" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" ) func initSqlSupplierReactions(sqlStore SqlStore) { @@ -136,7 +135,7 @@ func (s *SqlSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiN for _, reaction := range reactions { if _, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, map[string]interface{}{"PostId": reaction.PostId, "UpdateAt": model.GetMillis()}); err != nil { - l4g.Warn(utils.T("store.sql_reaction.delete_all_with_emoji_name.update_post.warn"), reaction.PostId, err.Error()) + mlog.Warn(fmt.Sprintf("Unable to update Post.HasReactions while removing reactions post_id=%v, error=%v", reaction.PostId, err.Error())) } } diff --git a/store/sqlstore/tokens_store.go b/store/sqlstore/tokens_store.go index ccb58cef1..6dde1bfbf 100644 --- a/store/sqlstore/tokens_store.go +++ b/store/sqlstore/tokens_store.go @@ -7,8 +7,7 @@ import ( "database/sql" "net/http" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" ) @@ -70,9 +69,9 @@ func (s SqlTokenStore) GetByToken(tokenString string) store.StoreChannel { } func (s SqlTokenStore) Cleanup() { - l4g.Debug("Cleaning up token store.") + mlog.Debug("Cleaning up token store.") deltime := model.GetMillis() - model.MAX_TOKEN_EXIPRY_TIME if _, err := s.GetMaster().Exec("DELETE FROM Tokens WHERE CreateAt < :DelTime", map[string]interface{}{"DelTime": deltime}); err != nil { - l4g.Error("Unable to cleanup token store.") + mlog.Error("Unable to cleanup token store.") } } diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 059d1a866..ec2b1a1c0 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -5,14 +5,13 @@ package sqlstore import ( "encoding/json" + "fmt" "os" "strings" "time" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) const ( @@ -82,17 +81,17 @@ func UpgradeDatabase(sqlStore SqlStore) { // so lets set it to the current version. if sqlStore.GetCurrentSchemaVersion() == "" { if result := <-sqlStore.System().SaveOrUpdate(&model.System{Name: "Version", Value: model.CurrentVersion}); result.Err != nil { - l4g.Critical(result.Err.Error()) + mlog.Critical(result.Err.Error()) time.Sleep(time.Second) os.Exit(EXIT_VERSION_SAVE_MISSING) } - l4g.Info("The database schema has been set to version %v", model.CurrentVersion) + mlog.Info(fmt.Sprintf("The database schema has been set to version %v", model.CurrentVersion)) } // If we're not on the current version then it's too old to be upgraded if sqlStore.GetCurrentSchemaVersion() != model.CurrentVersion { - l4g.Critical(utils.T("store.sql.schema_version.critical"), sqlStore.GetCurrentSchemaVersion(), OLDEST_SUPPORTED_VERSION, model.CurrentVersion, OLDEST_SUPPORTED_VERSION) + mlog.Critical(fmt.Sprintf("Database schema version %v is no longer supported. This Mattermost server supports automatic upgrades from schema version %v through schema version %v. Downgrades are not supported. Please manually upgrade to at least version %v before continuing", sqlStore.GetCurrentSchemaVersion(), OLDEST_SUPPORTED_VERSION, model.CurrentVersion, OLDEST_SUPPORTED_VERSION)) time.Sleep(time.Second) os.Exit(EXIT_TOO_OLD) } @@ -100,18 +99,18 @@ func UpgradeDatabase(sqlStore SqlStore) { func saveSchemaVersion(sqlStore SqlStore, version string) { if result := <-sqlStore.System().Update(&model.System{Name: "Version", Value: version}); result.Err != nil { - l4g.Critical(result.Err.Error()) + mlog.Critical(result.Err.Error()) time.Sleep(time.Second) os.Exit(EXIT_VERSION_SAVE) } - l4g.Warn(utils.T("store.sql.upgraded.warn"), version) + mlog.Warn(fmt.Sprintf("The database schema has been upgraded to version %v", version)) } func shouldPerformUpgrade(sqlStore SqlStore, currentSchemaVersion string, expectedSchemaVersion string) bool { if sqlStore.GetCurrentSchemaVersion() == currentSchemaVersion { - l4g.Warn(utils.T("store.sql.schema_out_of_date.warn"), currentSchemaVersion) - l4g.Warn(utils.T("store.sql.schema_upgrade_attempt.warn"), expectedSchemaVersion) + mlog.Warn(fmt.Sprintf("The database schema version of %v appears to be out of date", currentSchemaVersion)) + mlog.Warn(fmt.Sprintf("Attempting to upgrade the database schema version to %v", expectedSchemaVersion)) return true } @@ -135,7 +134,7 @@ func UpgradeDatabaseToVersion32(sqlStore SqlStore) { } func themeMigrationFailed(err error) { - l4g.Critical(utils.T("store.sql_user.migrate_theme.critical"), err) + mlog.Critical(fmt.Sprintf("Failed to migrate User.ThemeProps to Preferences table %v", err)) time.Sleep(time.Second) os.Exit(EXIT_THEME_MIGRATION) } @@ -403,7 +402,7 @@ func UpgradeDatabaseToVersion49(sqlStore SqlStore) { defaultTimezone := model.DefaultUserTimezone() defaultTimezoneValue, err := json.Marshal(defaultTimezone) if err != nil { - l4g.Critical(err) + mlog.Critical(fmt.Sprint(err)) } sqlStore.CreateColumnIfNotExists("Users", "Timezone", "varchar(256)", "varchar(256)", string(defaultTimezoneValue)) sqlStore.RemoveIndexIfExists("idx_channels_displayname", "Channels") diff --git a/store/store.go b/store/store.go index e64089068..0b5c9df5f 100644 --- a/store/store.go +++ b/store/store.go @@ -6,8 +6,6 @@ package store import ( "time" - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/mattermost-server/model" ) @@ -32,7 +30,7 @@ func Do(f func(result *StoreResult)) StoreChannel { func Must(sc StoreChannel) interface{} { r := <-sc if r.Err != nil { - l4g.Close() + time.Sleep(time.Second) panic(r.Err) } diff --git a/store/storetest/docker.go b/store/storetest/docker.go index cd2a3075a..f3830a6fe 100644 --- a/store/storetest/docker.go +++ b/store/storetest/docker.go @@ -12,8 +12,7 @@ import ( "strings" "time" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -31,7 +30,7 @@ type RunningContainer struct { } func (c *RunningContainer) Stop() error { - l4g.Info("Removing container: %v", c.Id) + mlog.Info(fmt.Sprintf("Removing container: %v", c.Id)) return exec.Command("docker", "rm", "-f", c.Id).Run() } @@ -47,7 +46,7 @@ func NewMySQLContainer() (*RunningContainer, *model.SqlSettings, error) { if err != nil { return nil, nil, err } - l4g.Info("Waiting for mysql connectivity") + mlog.Info("Waiting for mysql connectivity") port := container.NetworkSettings.Ports["3306/tcp"][0].HostPort if err := waitForPort(port); err != nil { container.Stop() @@ -66,7 +65,7 @@ func NewPostgreSQLContainer() (*RunningContainer, *model.SqlSettings, error) { if err != nil { return nil, nil, err } - l4g.Info("Waiting for postgres connectivity") + mlog.Info("Waiting for postgres connectivity") port := container.NetworkSettings.Ports["5432/tcp"][0].HostPort if err := waitForPort(port); err != nil { container.Stop() @@ -111,7 +110,7 @@ func runContainer(args []string) (*RunningContainer, error) { exec.Command("docker", "rm", "-f", id).Run() return nil, err } - l4g.Info("Running container: %v", id) + mlog.Info(fmt.Sprintf("Running container: %v", id)) return &RunningContainer{containers[0]}, nil } diff --git a/utils/config.go b/utils/config.go index 51b7ea003..34cd0ed9f 100644 --- a/utils/config.go +++ b/utils/config.go @@ -15,7 +15,6 @@ import ( "strconv" "strings" - l4g "github.com/alecthomas/log4go" "github.com/fsnotify/fsnotify" "github.com/pkg/errors" "github.com/spf13/viper" @@ -23,6 +22,7 @@ import ( "net/http" "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils/jsonutils" ) @@ -32,8 +32,6 @@ const ( LOG_FILENAME = "mattermost.log" ) -var originalDisableDebugLvl l4g.Level = l4g.DEBUG - // FindConfigFile attempts to find an existing configuration file. fileName can be an absolute or // relative path or name such as "/opt/mattermost/config.json" or simply "config.json". An empty // string is returned if no configuration is found. @@ -66,71 +64,26 @@ func FindDir(dir string) (string, bool) { return "./", false } -func DisableDebugLogForTest() { - if l4g.Global["stdout"] != nil { - originalDisableDebugLvl = l4g.Global["stdout"].Level - l4g.Global["stdout"].Level = l4g.ERROR +func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfiguration { + return &mlog.LoggerConfiguration{ + EnableConsole: s.EnableConsole, + ConsoleJson: *s.ConsoleJson, + ConsoleLevel: strings.ToLower(s.ConsoleLevel), + EnableFile: s.EnableFile, + FileJson: *s.FileJson, + FileLevel: strings.ToLower(s.FileLevel), + FileLocation: GetLogFileLocation(s.FileLocation), } } -func EnableDebugLogForTest() { - if l4g.Global["stdout"] != nil { - l4g.Global["stdout"].Level = originalDisableDebugLvl - } -} - -func ConfigureCmdLineLog() { - ls := model.LogSettings{} - ls.EnableConsole = true - ls.ConsoleLevel = "WARN" - ConfigureLog(&ls) +// DON'T USE THIS Modify the level on the app logger +func DisableDebugLogForTest() { + mlog.GloballyDisableDebugLogForTest() } -// ConfigureLog enables and configures logging. -// -// Note that it is not currently possible to disable filters nor to modify previously enabled -// filters, given the lack of concurrency guarantees from the underlying l4g library. -// -// TODO: this code initializes console and file logging. It will eventually be replaced by JSON logging in logger/logger.go -// See PLT-3893 for more information -func ConfigureLog(s *model.LogSettings) { - if _, alreadySet := l4g.Global["stdout"]; !alreadySet && s.EnableConsole { - level := l4g.DEBUG - if s.ConsoleLevel == "INFO" { - level = l4g.INFO - } else if s.ConsoleLevel == "WARN" { - level = l4g.WARNING - } else if s.ConsoleLevel == "ERROR" { - level = l4g.ERROR - } - - lw := l4g.NewConsoleLogWriter() - lw.SetFormat("[%D %T] [%L] %M") - l4g.AddFilter("stdout", level, lw) - } - - if _, alreadySet := l4g.Global["file"]; !alreadySet && s.EnableFile { - var fileFormat = s.FileFormat - - if fileFormat == "" { - fileFormat = "[%D %T] [%L] %M" - } - - level := l4g.DEBUG - if s.FileLevel == "INFO" { - level = l4g.INFO - } else if s.FileLevel == "WARN" { - level = l4g.WARNING - } else if s.FileLevel == "ERROR" { - level = l4g.ERROR - } - - flw := l4g.NewFileLogWriter(GetLogFileLocation(s.FileLocation), false) - flw.SetFormat(fileFormat) - flw.SetRotate(true) - flw.SetRotateLines(LOG_ROTATE_SIZE) - l4g.AddFilter("file", level, flw) - } +// DON'T USE THIS Modify the level on the app logger +func EnableDebugLogForTest() { + mlog.GloballyEnableDebugLogForTest() } func GetLogFileLocation(fileLocation string) string { @@ -189,17 +142,17 @@ func NewConfigWatcher(cfgFileName string, f func()) (*ConfigWatcher, error) { // we only care about the config file if filepath.Clean(event.Name) == configFile { if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { - l4g.Info(fmt.Sprintf("Config file watcher detected a change reloading %v", cfgFileName)) + mlog.Info(fmt.Sprintf("Config file watcher detected a change reloading %v", cfgFileName)) if _, _, configReadErr := ReadConfigFile(cfgFileName, true); configReadErr == nil { f() } else { - l4g.Error(fmt.Sprintf("Failed to read while watching config file at %v with err=%v", cfgFileName, configReadErr.Error())) + mlog.Error(fmt.Sprintf("Failed to read while watching config file at %v with err=%v", cfgFileName, configReadErr.Error())) } } } case err := <-watcher.Errors: - l4g.Error(fmt.Sprintf("Failed while watching config file at %v with err=%v", cfgFileName, err.Error())) + mlog.Error(fmt.Sprintf("Failed while watching config file at %v with err=%v", cfgFileName, err.Error())) case <-ret.close: return } @@ -278,7 +231,7 @@ func newViper(allowEnvironmentOverrides bool) *viper.Viper { func structToMap(t reflect.Type) (out map[string]interface{}) { defer func() { if r := recover(); r != nil { - l4g.Error("Panicked in structToMap. This should never happen. %v", r) + mlog.Error(fmt.Sprintf("Panicked in structToMap. This should never happen. %v", r)) } }() @@ -345,7 +298,7 @@ func flattenStructToMap(in map[string]interface{}) map[string]interface{} { func fixEnvSettingsCase(in map[string]interface{}) (out map[string]interface{}, err error) { defer func() { if r := recover(); r != nil { - l4g.Error("Panicked in fixEnvSettingsCase. This should never happen. %v", r) + mlog.Error(fmt.Sprintf("Panicked in fixEnvSettingsCase. This should never happen. %v", r)) out = in } }() @@ -450,13 +403,13 @@ func LoadConfig(fileName string) (*model.Config, string, map[string]interface{}, if needSave { if err := SaveConfig(configPath, config); err != nil { - l4g.Warn(err.Error()) + mlog.Warn(err.Error()) } } if err := ValidateLocales(config); err != nil { if err := SaveConfig(configPath, config); err != nil { - l4g.Warn(err.Error()) + mlog.Warn(err.Error()) } } diff --git a/utils/config_test.go b/utils/config_test.go index 11b110367..ec66a30f0 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -19,9 +19,8 @@ import ( func TestConfig(t *testing.T) { TranslationsPreInit() - cfg, _, _, err := LoadConfig("config.json") + _, _, _, err := LoadConfig("config.json") require.Nil(t, err) - InitTranslations(cfg.LocalizationSettings) } func TestReadConfig(t *testing.T) { diff --git a/utils/file_backend_local.go b/utils/file_backend_local.go index 1367ccc1e..f85ace55a 100644 --- a/utils/file_backend_local.go +++ b/utils/file_backend_local.go @@ -9,8 +9,7 @@ import ( "os" "path/filepath" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -28,7 +27,7 @@ func (b *LocalFileBackend) TestConnection() *model.AppError { return model.NewAppError("TestFileConnection", "Don't have permissions to write to local path specified or other error.", nil, err.Error(), http.StatusInternalServerError) } os.Remove(filepath.Join(b.directory, TEST_FILE_PATH)) - l4g.Info("Able to write files to local storage.") + mlog.Info("Able to write files to local storage.") return nil } diff --git a/utils/file_backend_s3.go b/utils/file_backend_s3.go index 75282897f..2f644f602 100644 --- a/utils/file_backend_s3.go +++ b/utils/file_backend_s3.go @@ -11,10 +11,10 @@ import ( "path/filepath" "strings" - l4g "github.com/alecthomas/log4go" s3 "github.com/minio/minio-go" "github.com/minio/minio-go/pkg/credentials" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -70,14 +70,14 @@ func (b *S3FileBackend) TestConnection() *model.AppError { } if !exists { - l4g.Warn("Bucket specified does not exist. Attempting to create...") + mlog.Warn("Bucket specified does not exist. Attempting to create...") err := s3Clnt.MakeBucket(b.bucket, b.region) if err != nil { - l4g.Error("Unable to create bucket.") + mlog.Error("Unable to create bucket.") return model.NewAppError("TestFileConnection", "Unable to create bucket", nil, err.Error(), http.StatusInternalServerError) } } - l4g.Info("Connection to S3 or minio is good. Bucket exists.") + mlog.Info("Connection to S3 or minio is good. Bucket exists.") return nil } diff --git a/utils/file_backend_test.go b/utils/file_backend_test.go index 2b8e2a527..047e9df62 100644 --- a/utils/file_backend_test.go +++ b/utils/file_backend_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -23,6 +24,15 @@ type FileBackendTestSuite struct { } func TestLocalFileBackendTestSuite(t *testing.T) { + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + dir, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(dir) diff --git a/utils/html.go b/utils/html.go index f9a7abe5b..0de33435d 100644 --- a/utils/html.go +++ b/utils/html.go @@ -6,14 +6,15 @@ package utils import ( "bytes" "errors" + "fmt" "html/template" "io" "path/filepath" "reflect" "sync/atomic" - l4g "github.com/alecthomas/log4go" "github.com/fsnotify/fsnotify" + "github.com/mattermost/mattermost-server/mlog" "github.com/nicksnyder/go-i18n/i18n" ) @@ -25,7 +26,7 @@ type HTMLTemplateWatcher struct { func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { templatesDir, _ := FindDir(directory) - l4g.Debug("Parsing server templates at %v", templatesDir) + mlog.Debug(fmt.Sprintf("Parsing server templates at %v", templatesDir)) ret := &HTMLTemplateWatcher{ stop: make(chan struct{}), @@ -57,15 +58,15 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { return case event := <-watcher.Events: if event.Op&fsnotify.Write == fsnotify.Write { - l4g.Info("Re-parsing templates because of modified file %v", event.Name) + mlog.Info(fmt.Sprintf("Re-parsing templates because of modified file %v", event.Name)) if htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html")); err != nil { - l4g.Error("Failed to parse templates %v", err) + mlog.Error(fmt.Sprintf("Failed to parse templates %v", err)) } else { ret.templates.Store(htmlTemplates) } } case err := <-watcher.Errors: - l4g.Error("Failed in directory watcher %s", err) + mlog.Error(fmt.Sprintf("Failed in directory watcher %s", err)) } } }() @@ -110,7 +111,7 @@ func (t *HTMLTemplate) RenderToWriter(w io.Writer) error { } if err := t.Templates.ExecuteTemplate(w, t.TemplateName, t); err != nil { - l4g.Error(T("api.api.render.error"), t.TemplateName, err) + mlog.Error(fmt.Sprintf("Error rendering template %v err=%v", t.TemplateName, err)) return err } @@ -134,7 +135,7 @@ func escapeForHtml(arg interface{}) interface{} { } return safeArg default: - l4g.Warn("Unable to escape value for HTML template %v of type %v", arg, reflect.ValueOf(arg).Type()) + mlog.Warn(fmt.Sprintf("Unable to escape value for HTML template %v of type %v", arg, reflect.ValueOf(arg).Type())) return "" } } diff --git a/utils/i18n.go b/utils/i18n.go index 72704c241..d7c55e4e6 100644 --- a/utils/i18n.go +++ b/utils/i18n.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/nicksnyder/go-i18n/i18n" ) @@ -67,7 +67,7 @@ func InitTranslationsWithDir(dir string) error { func GetTranslationsBySystemLocale() (i18n.TranslateFunc, error) { locale := *settings.DefaultServerLocale if _, ok := locales[locale]; !ok { - l4g.Error("Failed to load system translations for '%v' attempting to fall back to '%v'", locale, model.DEFAULT_LOCALE) + mlog.Error(fmt.Sprintf("Failed to load system translations for '%v' attempting to fall back to '%v'", locale, model.DEFAULT_LOCALE)) locale = model.DEFAULT_LOCALE } @@ -80,7 +80,7 @@ func GetTranslationsBySystemLocale() (i18n.TranslateFunc, error) { return nil, fmt.Errorf("Failed to load system translations") } - l4g.Info(translations("utils.i18n.loaded"), locale, locales[locale]) + mlog.Info(fmt.Sprintf("Loaded system translations for '%v' from '%v'", locale, locales[locale])) return translations, nil } diff --git a/utils/license.go b/utils/license.go index cf874b62b..aa89026ea 100644 --- a/utils/license.go +++ b/utils/license.go @@ -10,14 +10,14 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" + "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -36,12 +36,12 @@ func ValidateLicense(signed []byte) (bool, string) { _, err := base64.StdEncoding.Decode(decoded, signed) if err != nil { - l4g.Error(T("utils.license.validate_license.decode.error"), err.Error()) + mlog.Error(fmt.Sprintf("Encountered error decoding license, err=%v", err.Error())) return false, "" } if len(decoded) <= 256 { - l4g.Error(T("utils.license.validate_license.not_long.error")) + mlog.Error("Signed license not long enough") return false, "" } @@ -57,7 +57,7 @@ func ValidateLicense(signed []byte) (bool, string) { public, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - l4g.Error(T("utils.license.validate_license.signing.error"), err.Error()) + mlog.Error(fmt.Sprintf("Encountered error signing license, err=%v", err.Error())) return false, "" } @@ -69,7 +69,7 @@ func ValidateLicense(signed []byte) (bool, string) { err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, d, signature) if err != nil { - l4g.Error(T("utils.license.validate_license.invalid.error"), err.Error()) + mlog.Error(fmt.Sprintf("Invalid signature, err=%v", err.Error())) return false, "" } @@ -80,15 +80,15 @@ func GetAndValidateLicenseFileFromDisk(location string) (*model.License, []byte) fileName := GetLicenseFileLocation(location) if _, err := os.Stat(fileName); err != nil { - l4g.Debug("We could not find the license key in the database or on disk at %v", fileName) + mlog.Debug(fmt.Sprintf("We could not find the license key in the database or on disk at %v", fileName)) return nil, nil } - l4g.Info("License key has not been uploaded. Loading license key from disk at %v", fileName) + mlog.Info(fmt.Sprintf("License key has not been uploaded. Loading license key from disk at %v", fileName)) licenseBytes := GetLicenseFileFromDisk(fileName) if success, licenseStr := ValidateLicense(licenseBytes); !success { - l4g.Error("Found license key at %v but it appears to be invalid.", fileName) + mlog.Error(fmt.Sprintf("Found license key at %v but it appears to be invalid.", fileName)) return nil, nil } else { return model.LicenseFromJson(strings.NewReader(licenseStr)), licenseBytes @@ -98,14 +98,14 @@ func GetAndValidateLicenseFileFromDisk(location string) (*model.License, []byte) func GetLicenseFileFromDisk(fileName string) []byte { file, err := os.Open(fileName) if err != nil { - l4g.Error("Failed to open license key from disk at %v err=%v", fileName, err.Error()) + mlog.Error(fmt.Sprintf("Failed to open license key from disk at %v err=%v", fileName, err.Error())) return nil } defer file.Close() licenseBytes, err := ioutil.ReadAll(file) if err != nil { - l4g.Error("Failed to read license key from disk at %v err=%v", fileName, err.Error()) + mlog.Error(fmt.Sprintf("Failed to read license key from disk at %v err=%v", fileName, err.Error())) return nil } diff --git a/utils/mail.go b/utils/mail.go index ee5a8dd6f..119ca0674 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -6,6 +6,7 @@ package utils import ( "crypto/tls" "errors" + "fmt" "io" "mime" "net" @@ -17,8 +18,8 @@ import ( "net/http" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/html2text" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -128,14 +129,14 @@ func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) { c, err := smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort) if err != nil { - l4g.Error(T("utils.mail.new_client.open.error"), err) + mlog.Error(fmt.Sprintf("Failed to open a connection to SMTP server %v", err)) return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) } if hostname != "" { err := c.Hello(hostname) if err != nil { - l4g.Error(T("utils.mail.new_client.helo.error"), err) + mlog.Error(fmt.Sprintf("Failed to to set the HELO to SMTP server %v", err)) return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError) } } @@ -180,14 +181,14 @@ func TestConnection(config *model.Config) { conn, err1 := ConnectToSMTPServer(config) if err1 != nil { - l4g.Error(T("utils.mail.test.configured.error"), T(err1.Message), err1.DetailedError) + mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", T(err1.Message), err1.DetailedError)) return } defer conn.Close() c, err2 := NewSMTPClient(conn, config) if err2 != nil { - l4g.Error(T("utils.mail.test.configured.error"), T(err2.Message), err2.DetailedError) + mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", T(err2.Message), err2.DetailedError)) return } defer c.Quit() @@ -228,13 +229,13 @@ func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subje } func SendMail(c *smtp.Client, mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend FileBackend, date time.Time) *model.AppError { - l4g.Debug(T("utils.mail.send_mail.sending.debug"), mimeTo, subject) + mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", mimeTo, subject)) htmlMessage := "\r\n" + htmlBody + "" txtBody, err := html2text.FromString(htmlBody) if err != nil { - l4g.Warn(err) + mlog.Warn(fmt.Sprint(err)) txtBody = "" } diff --git a/utils/redirect_std_log.go b/utils/redirect_std_log.go deleted file mode 100644 index 4fbfcf8ec..000000000 --- a/utils/redirect_std_log.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package utils - -import ( - "bufio" - "log" - "os" - "strings" - - l4g "github.com/alecthomas/log4go" -) - -type RedirectStdLog struct { - reader *os.File - writer *os.File - system string - ignoreDebug bool -} - -func NewRedirectStdLog(system string, ignoreDebug bool) *log.Logger { - r, w, _ := os.Pipe() - logger := &RedirectStdLog{ - reader: r, - writer: w, - system: system, - ignoreDebug: ignoreDebug, - } - - go func(l *RedirectStdLog) { - scanner := bufio.NewScanner(l.reader) - for scanner.Scan() { - line := scanner.Text() - - if strings.Index(line, "[DEBUG]") == 0 { - if !ignoreDebug { - l4g.Debug("%v%v", system, line[7:]) - } - } else if strings.Index(line, "[DEBG]") == 0 { - if !ignoreDebug { - l4g.Debug("%v%v", system, line[6:]) - } - } else if strings.Index(line, "[WARN]") == 0 { - l4g.Info("%v%v", system, line[6:]) - } else if strings.Index(line, "[ERROR]") == 0 { - l4g.Error("%v%v", system, line[7:]) - } else if strings.Index(line, "[EROR]") == 0 { - l4g.Error("%v%v", system, line[6:]) - } else if strings.Index(line, "[ERR]") == 0 { - l4g.Error("%v%v", system, line[5:]) - } else if strings.Index(line, "[INFO]") == 0 { - l4g.Info("%v%v", system, line[6:]) - } else { - l4g.Info("%v %v", system, line) - } - } - }(logger) - - return log.New(logger.writer, "", 0) -} - -func (l *RedirectStdLog) Write(p []byte) (n int, err error) { - return l.writer.Write(p) -} diff --git a/utils/redirect_std_log_test.go b/utils/redirect_std_log_test.go deleted file mode 100644 index cbe55c921..000000000 --- a/utils/redirect_std_log_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package utils - -import ( - "testing" - "time" -) - -func TestRedirectStdLog(t *testing.T) { - log := NewRedirectStdLog("test", false) - - log.Println("[DEBUG] this is a message") - log.Println("[DEBG] this is a message") - log.Println("[WARN] this is a message") - log.Println("[ERROR] this is a message") - log.Println("[EROR] this is a message") - log.Println("[ERR] this is a message") - log.Println("[INFO] this is a message") - log.Println("this is a message") - - time.Sleep(time.Second * 1) -} diff --git a/vendor/github.com/alecthomas/log4go/.gitignore b/vendor/github.com/alecthomas/log4go/.gitignore deleted file mode 100644 index f6207cd8a..000000000 --- a/vendor/github.com/alecthomas/log4go/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.sw[op] -.DS_Store diff --git a/vendor/github.com/alecthomas/log4go/LICENSE b/vendor/github.com/alecthomas/log4go/LICENSE deleted file mode 100644 index 7093402bf..000000000 --- a/vendor/github.com/alecthomas/log4go/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2010, Kyle Lemons . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/alecthomas/log4go/README b/vendor/github.com/alecthomas/log4go/README deleted file mode 100644 index 3361567f7..000000000 --- a/vendor/github.com/alecthomas/log4go/README +++ /dev/null @@ -1,14 +0,0 @@ -# This is an unmaintained fork, left only so it doesn't break imports. - -Please see http://log4go.googlecode.com/ - -Installation: -- Run `goinstall log4go.googlecode.com/hg` - -Usage: -- Add the following import: -import l4g "log4go.googlecode.com/hg" - -Acknowledgements: -- pomack - For providing awesome patches to bring log4go up to the latest Go spec diff --git a/vendor/github.com/alecthomas/log4go/config.go b/vendor/github.com/alecthomas/log4go/config.go deleted file mode 100644 index 577c3eb2f..000000000 --- a/vendor/github.com/alecthomas/log4go/config.go +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "encoding/xml" - "fmt" - "io/ioutil" - "os" - "strconv" - "strings" -) - -type xmlProperty struct { - Name string `xml:"name,attr"` - Value string `xml:",chardata"` -} - -type xmlFilter struct { - Enabled string `xml:"enabled,attr"` - Tag string `xml:"tag"` - Level string `xml:"level"` - Type string `xml:"type"` - Property []xmlProperty `xml:"property"` -} - -type xmlLoggerConfig struct { - Filter []xmlFilter `xml:"filter"` -} - -// Load XML configuration; see examples/example.xml for documentation -func (log Logger) LoadConfiguration(filename string) { - log.Close() - - // Open the configuration file - fd, err := os.Open(filename) - if err != nil { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err) - os.Exit(1) - } - - contents, err := ioutil.ReadAll(fd) - if err != nil { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err) - os.Exit(1) - } - - xc := new(xmlLoggerConfig) - if err := xml.Unmarshal(contents, xc); err != nil { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err) - os.Exit(1) - } - - for _, xmlfilt := range xc.Filter { - var filt LogWriter - var lvl Level - bad, good, enabled := false, true, false - - // Check required children - if len(xmlfilt.Enabled) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename) - bad = true - } else { - enabled = xmlfilt.Enabled != "false" - } - if len(xmlfilt.Tag) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename) - bad = true - } - if len(xmlfilt.Type) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename) - bad = true - } - if len(xmlfilt.Level) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename) - bad = true - } - - switch xmlfilt.Level { - case "FINEST": - lvl = FINEST - case "FINE": - lvl = FINE - case "DEBUG": - lvl = DEBUG - case "TRACE": - lvl = TRACE - case "INFO": - lvl = INFO - case "WARNING": - lvl = WARNING - case "ERROR": - lvl = ERROR - case "CRITICAL": - lvl = CRITICAL - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level) - bad = true - } - - // Just so all of the required attributes are errored at the same time if missing - if bad { - os.Exit(1) - } - - switch xmlfilt.Type { - case "console": - filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled) - case "file": - filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled) - case "xml": - filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled) - case "socket": - filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled) - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type) - os.Exit(1) - } - - // Just so all of the required params are errored at the same time if wrong - if !good { - os.Exit(1) - } - - // If we're disabled (syntax and correctness checks only), don't add to logger - if !enabled { - continue - } - - log[xmlfilt.Tag] = &Filter{lvl, filt} - } -} - -func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) { - // Parse properties - for _, prop := range props { - switch prop.Name { - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename) - } - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - return NewConsoleLogWriter(), true -} - -// Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024) -func strToNumSuffix(str string, mult int) int { - num := 1 - if len(str) > 1 { - switch str[len(str)-1] { - case 'G', 'g': - num *= mult - fallthrough - case 'M', 'm': - num *= mult - fallthrough - case 'K', 'k': - num *= mult - str = str[0 : len(str)-1] - } - } - parsed, _ := strconv.Atoi(str) - return parsed * num -} -func xmlToFileLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) { - file := "" - format := "[%D %T] [%L] (%S) %M" - maxlines := 0 - maxsize := 0 - daily := false - rotate := false - - // Parse properties - for _, prop := range props { - switch prop.Name { - case "filename": - file = strings.Trim(prop.Value, " \r\n") - case "format": - format = strings.Trim(prop.Value, " \r\n") - case "maxlines": - maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) - case "maxsize": - maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) - case "daily": - daily = strings.Trim(prop.Value, " \r\n") != "false" - case "rotate": - rotate = strings.Trim(prop.Value, " \r\n") != "false" - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) - } - } - - // Check properties - if len(file) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename) - return nil, false - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - flw := NewFileLogWriter(file, rotate) - flw.SetFormat(format) - flw.SetRotateLines(maxlines) - flw.SetRotateSize(maxsize) - flw.SetRotateDaily(daily) - return flw, true -} - -func xmlToXMLLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) { - file := "" - maxrecords := 0 - maxsize := 0 - daily := false - rotate := false - - // Parse properties - for _, prop := range props { - switch prop.Name { - case "filename": - file = strings.Trim(prop.Value, " \r\n") - case "maxrecords": - maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) - case "maxsize": - maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) - case "daily": - daily = strings.Trim(prop.Value, " \r\n") != "false" - case "rotate": - rotate = strings.Trim(prop.Value, " \r\n") != "false" - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename) - } - } - - // Check properties - if len(file) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename) - return nil, false - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - xlw := NewXMLLogWriter(file, rotate) - xlw.SetRotateLines(maxrecords) - xlw.SetRotateSize(maxsize) - xlw.SetRotateDaily(daily) - return xlw, true -} - -func xmlToSocketLogWriter(filename string, props []xmlProperty, enabled bool) (SocketLogWriter, bool) { - endpoint := "" - protocol := "udp" - - // Parse properties - for _, prop := range props { - switch prop.Name { - case "endpoint": - endpoint = strings.Trim(prop.Value, " \r\n") - case "protocol": - protocol = strings.Trim(prop.Value, " \r\n") - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) - } - } - - // Check properties - if len(endpoint) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename) - return nil, false - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - return NewSocketLogWriter(protocol, endpoint), true -} diff --git a/vendor/github.com/alecthomas/log4go/filelog.go b/vendor/github.com/alecthomas/log4go/filelog.go deleted file mode 100644 index a5ae87809..000000000 --- a/vendor/github.com/alecthomas/log4go/filelog.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "bytes" - "fmt" - "io" - "os" - "time" -) - -// This log writer sends output to a file -type FileLogWriter struct { - rec chan *LogRecord - rot chan bool - done chan bool - - // The opened file - filename string - file *os.File - - // The logging format - format string - - // File header/trailer - header, trailer string - - // Rotate at linecount - maxlines int - maxlines_curlines int - - // Rotate at size - maxsize int - maxsize_cursize int - - // Rotate daily - daily bool - daily_opendate int - - // Keep old logfiles (.001, .002, etc) - rotate bool - maxbackup int -} - -// This is the FileLogWriter's output method -func (w *FileLogWriter) LogWrite(rec *LogRecord) { - w.rec <- rec -} - -func (w *FileLogWriter) Close() { - close(w.rec) - <-w.done -} - -// NewFileLogWriter creates a new LogWriter which writes to the given file and -// has rotation enabled if rotate is true. -// -// If rotate is true, any time a new log file is opened, the old one is renamed -// with a .### extension to preserve it. The various Set* methods can be used -// to configure log rotation based on lines, size, and daily. -// -// The standard log-line format is: -// [%D %T] [%L] (%S) %M -func NewFileLogWriter(fname string, rotate bool) *FileLogWriter { - w := &FileLogWriter{ - rec: make(chan *LogRecord, LogBufferLength), - rot: make(chan bool), - done: make(chan bool), - filename: fname, - format: "[%D %T] [%L] (%S) %M", - rotate: rotate, - maxbackup: 999, - } - - // open the file for the first time - if err := w.intRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return nil - } - - go func() { - defer func() { - if w.file != nil { - fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) - w.file.Sync() - w.file.Close() - } - close(w.done) - }() - - for { - select { - case <-w.rot: - if err := w.intRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return - } - case rec, ok := <-w.rec: - if !ok { - return - } - now := time.Now() - if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) || - (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) || - (w.daily && now.Day() != w.daily_opendate) { - if err := w.intRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return - } - } - - // Perform the write - n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec)) - if err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return - } - - // Update the counts - w.maxlines_curlines++ - w.maxsize_cursize += n - } - } - }() - - return w -} - -// Request that the logs rotate -func (w *FileLogWriter) Rotate() { - w.rot <- true -} - -// If this is called in a threaded context, it MUST be synchronized -func (w *FileLogWriter) intRotate() error { - // Close any log file that may be open - if w.file != nil { - fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) - w.file.Close() - } - - // If we are keeping log files, move it to the next available number - if w.rotate { - _, err := os.Lstat(w.filename) - if err == nil { // file exists - // Find the next available number - num := 1 - fname := "" - if w.daily && time.Now().Day() != w.daily_opendate { - yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02") - - for ; err == nil && num <= 999; num++ { - fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num) - _, err = os.Lstat(fname) - } - // return error if the last file checked still existed - if err == nil { - return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename) - } - } else { - num = w.maxbackup - 1 - for ; num >= 1; num-- { - fname = w.filename + fmt.Sprintf(".%d", num) - nfname := w.filename + fmt.Sprintf(".%d", num+1) - _, err = os.Lstat(fname) - if err == nil { - os.Rename(fname, nfname) - } - } - } - - w.file.Close() - // Rename the file to its newfound home - err = os.Rename(w.filename, fname) - if err != nil { - return fmt.Errorf("Rotate: %s\n", err) - } - } - } - - lineCount := 0 - byteCount := 0 - - // If the file exists, open for reading and set our line/byte counts - // On failure, just assume the file doesn't exist - if fd, err := os.OpenFile(w.filename, os.O_RDONLY, 0440); err == nil { - lineCount, _ = lineCounter(fd) - - fi, err := fd.Stat() - if err == nil { - byteCount = int(fi.Size()) - } - - fd.Close() - } - - fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) - if err != nil { - return err - } - w.file = fd - - now := time.Now() - fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now})) - - // Set the daily open date to the current date - w.daily_opendate = now.Day() - - // initialize rotation values - w.maxlines_curlines = lineCount - w.maxsize_cursize = byteCount - - return nil -} - -// Taken from https://stackoverflow.com/a/24563853 -func lineCounter(r io.Reader) (int, error) { - buf := make([]byte, 32*1024) - count := 0 - lineSep := []byte{'\n'} - - for { - c, err := r.Read(buf) - count += bytes.Count(buf[:c], lineSep) - - switch { - case err == io.EOF: - return count, nil - - case err != nil: - return count, err - } - } -} - -// Set the logging format (chainable). Must be called before the first log -// message is written. -func (w *FileLogWriter) SetFormat(format string) *FileLogWriter { - w.format = format - return w -} - -// Set the logfile header and footer (chainable). Must be called before the first log -// message is written. These are formatted similar to the FormatLogRecord (e.g. -// you can use %D and %T in your header/footer for date and time). -func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter { - w.header, w.trailer = head, foot - if w.maxlines_curlines == 0 { - fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()})) - } - return w -} - -// Set rotate at linecount (chainable). Must be called before the first log -// message is written. -func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines) - w.maxlines = maxlines - return w -} - -// Set rotate at size (chainable). Must be called before the first log message -// is written. -func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize) - w.maxsize = maxsize - return w -} - -// Set rotate daily (chainable). Must be called before the first log message is -// written. -func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily) - w.daily = daily - return w -} - -// Set max backup files. Must be called before the first log message -// is written. -func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter { - w.maxbackup = maxbackup - return w -} - -// SetRotate changes whether or not the old logs are kept. (chainable) Must be -// called before the first log message is written. If rotate is false, the -// files are overwritten; otherwise, they are rotated to another file before the -// new log is opened. -func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate) - w.rotate = rotate - return w -} - -// NewXMLLogWriter is a utility method for creating a FileLogWriter set up to -// output XML record log messages instead of line-based ones. -func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter { - return NewFileLogWriter(fname, rotate).SetFormat( - ` - %D %T - %S - %M - `).SetHeadFoot("", "") -} diff --git a/vendor/github.com/alecthomas/log4go/log4go.go b/vendor/github.com/alecthomas/log4go/log4go.go deleted file mode 100644 index 822e890cc..000000000 --- a/vendor/github.com/alecthomas/log4go/log4go.go +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -// Package log4go provides level-based and highly configurable logging. -// -// Enhanced Logging -// -// This is inspired by the logging functionality in Java. Essentially, you create a Logger -// object and create output filters for it. You can send whatever you want to the Logger, -// and it will filter that based on your settings and send it to the outputs. This way, you -// can put as much debug code in your program as you want, and when you're done you can filter -// out the mundane messages so only the important ones show up. -// -// Utility functions are provided to make life easier. Here is some example code to get started: -// -// log := log4go.NewLogger() -// log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter()) -// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) -// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) -// -// The first two lines can be combined with the utility NewDefaultLogger: -// -// log := log4go.NewDefaultLogger(log4go.DEBUG) -// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) -// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) -// -// Usage notes: -// - The ConsoleLogWriter does not display the source of the message to standard -// output, but the FileLogWriter does. -// - The utility functions (Info, Debug, Warn, etc) derive their source from the -// calling function, and this incurs extra overhead. -// -// Changes from 2.0: -// - The external interface has remained mostly stable, but a lot of the -// internals have been changed, so if you depended on any of this or created -// your own LogWriter, then you will probably have to update your code. In -// particular, Logger is now a map and ConsoleLogWriter is now a channel -// behind-the-scenes, and the LogWrite method no longer has return values. -// -// Future work: (please let me know if you think I should work on any of these particularly) -// - Log file rotation -// - Logging configuration files ala log4j -// - Have the ability to remove filters? -// - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows -// for another method of logging -// - Add an XML filter type -package log4go - -import ( - "errors" - "fmt" - "os" - "runtime" - "strings" - "time" -) - -// Version information -const ( - L4G_VERSION = "log4go-v3.0.1" - L4G_MAJOR = 3 - L4G_MINOR = 0 - L4G_BUILD = 1 -) - -/****** Constants ******/ - -// These are the integer logging levels used by the logger -type Level int - -const ( - FINEST Level = iota - FINE - DEBUG - TRACE - INFO - WARNING - ERROR - CRITICAL -) - -// Logging level strings -var ( - levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} -) - -func (l Level) String() string { - if l < 0 || int(l) > len(levelStrings) { - return "UNKNOWN" - } - return levelStrings[int(l)] -} - -/****** Variables ******/ -var ( - // LogBufferLength specifies how many log messages a particular log4go - // logger can buffer at a time before writing them. - LogBufferLength = 32 -) - -/****** LogRecord ******/ - -// A LogRecord contains all of the pertinent information for each message -type LogRecord struct { - Level Level // The log level - Created time.Time // The time at which the log message was created (nanoseconds) - Source string // The message source - Message string // The log message -} - -/****** LogWriter ******/ - -// This is an interface for anything that should be able to write logs -type LogWriter interface { - // This will be called to log a LogRecord message. - LogWrite(rec *LogRecord) - - // This should clean up anything lingering about the LogWriter, as it is called before - // the LogWriter is removed. LogWrite should not be called after Close. - Close() -} - -/****** Logger ******/ - -// A Filter represents the log level below which no log records are written to -// the associated LogWriter. -type Filter struct { - Level Level - LogWriter -} - -// A Logger represents a collection of Filters through which log messages are -// written. -type Logger map[string]*Filter - -// Create a new logger. -// -// DEPRECATED: Use make(Logger) instead. -func NewLogger() Logger { - os.Stderr.WriteString("warning: use of deprecated NewLogger\n") - return make(Logger) -} - -// Create a new logger with a "stdout" filter configured to send log messages at -// or above lvl to standard output. -// -// DEPRECATED: use NewDefaultLogger instead. -func NewConsoleLogger(lvl Level) Logger { - os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n") - return Logger{ - "stdout": &Filter{lvl, NewConsoleLogWriter()}, - } -} - -// Create a new logger with a "stdout" filter configured to send log messages at -// or above lvl to standard output. -func NewDefaultLogger(lvl Level) Logger { - return Logger{ - "stdout": &Filter{lvl, NewConsoleLogWriter()}, - } -} - -// Closes all log writers in preparation for exiting the program or a -// reconfiguration of logging. Calling this is not really imperative, unless -// you want to guarantee that all log messages are written. Close removes -// all filters (and thus all LogWriters) from the logger. -func (log Logger) Close() { - // Close all open loggers - for name, filt := range log { - filt.Close() - delete(log, name) - } -} - -// Add a new LogWriter to the Logger which will only log messages at lvl or -// higher. This function should not be called from multiple goroutines. -// Returns the logger for chaining. -func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger { - log[name] = &Filter{lvl, writer} - return log -} - -/******* Logging *******/ -// Send a formatted log message internally -func (log Logger) intLogf(lvl Level, format string, args ...interface{}) { - skip := true - - // Determine if any logging will be done - for _, filt := range log { - if lvl >= filt.Level { - skip = false - break - } - } - if skip { - return - } - - // Determine caller func - pc, _, lineno, ok := runtime.Caller(2) - src := "" - if ok { - src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) - } - - msg := format - if len(args) > 0 { - msg = fmt.Sprintf(format, args...) - } - - // Make the log record - rec := &LogRecord{ - Level: lvl, - Created: time.Now(), - Source: src, - Message: msg, - } - - // Dispatch the logs - for _, filt := range log { - if lvl < filt.Level { - continue - } - filt.LogWrite(rec) - } -} - -// Send a closure log message internally -func (log Logger) intLogc(lvl Level, closure func() string) { - skip := true - - // Determine if any logging will be done - for _, filt := range log { - if lvl >= filt.Level { - skip = false - break - } - } - if skip { - return - } - - // Determine caller func - pc, _, lineno, ok := runtime.Caller(2) - src := "" - if ok { - src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) - } - - // Make the log record - rec := &LogRecord{ - Level: lvl, - Created: time.Now(), - Source: src, - Message: closure(), - } - - // Dispatch the logs - for _, filt := range log { - if lvl < filt.Level { - continue - } - filt.LogWrite(rec) - } -} - -// Send a log message with manual level, source, and message. -func (log Logger) Log(lvl Level, source, message string) { - skip := true - - // Determine if any logging will be done - for _, filt := range log { - if lvl >= filt.Level { - skip = false - break - } - } - if skip { - return - } - - // Make the log record - rec := &LogRecord{ - Level: lvl, - Created: time.Now(), - Source: source, - Message: message, - } - - // Dispatch the logs - for _, filt := range log { - if lvl < filt.Level { - continue - } - filt.LogWrite(rec) - } -} - -// Logf logs a formatted log message at the given log level, using the caller as -// its source. -func (log Logger) Logf(lvl Level, format string, args ...interface{}) { - log.intLogf(lvl, format, args...) -} - -// Logc logs a string returned by the closure at the given log level, using the caller as -// its source. If no log message would be written, the closure is never called. -func (log Logger) Logc(lvl Level, closure func() string) { - log.intLogc(lvl, closure) -} - -// Finest logs a message at the finest log level. -// See Debug for an explanation of the arguments. -func (log Logger) Finest(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINEST - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Fine logs a message at the fine log level. -// See Debug for an explanation of the arguments. -func (log Logger) Fine(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Debug is a utility method for debug log messages. -// The behavior of Debug depends on the first argument: -// - arg0 is a string -// When given a string as the first argument, this behaves like Logf but with -// the DEBUG log level: the first argument is interpreted as a format for the -// latter arguments. -// - arg0 is a func()string -// When given a closure of type func()string, this logs the string returned by -// the closure iff it will be logged. The closure runs at most one time. -// - arg0 is interface{} -// When given anything else, the log message will be each of the arguments -// formatted with %v and separated by spaces (ala Sprint). -func (log Logger) Debug(arg0 interface{}, args ...interface{}) { - const ( - lvl = DEBUG - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Trace logs a message at the trace log level. -// See Debug for an explanation of the arguments. -func (log Logger) Trace(arg0 interface{}, args ...interface{}) { - const ( - lvl = TRACE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Info logs a message at the info log level. -// See Debug for an explanation of the arguments. -func (log Logger) Info(arg0 interface{}, args ...interface{}) { - const ( - lvl = INFO - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Warn logs a message at the warning log level and returns the formatted error. -// At the warning level and higher, there is no performance benefit if the -// message is not actually logged, because all formats are processed and all -// closures are executed to format the error message. -// See Debug for further explanation of the arguments. -func (log Logger) Warn(arg0 interface{}, args ...interface{}) error { - const ( - lvl = WARNING - ) - var msg string - switch first := arg0.(type) { - case string: - // Use the string as a format string - msg = fmt.Sprintf(first, args...) - case func() string: - // Log the closure (no other arguments used) - msg = first() - default: - // Build a format string so that it will be similar to Sprint - msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - } - log.intLogf(lvl, msg) - return errors.New(msg) -} - -// Error logs a message at the error log level and returns the formatted error, -// See Warn for an explanation of the performance and Debug for an explanation -// of the parameters. -func (log Logger) Error(arg0 interface{}, args ...interface{}) error { - const ( - lvl = ERROR - ) - var msg string - switch first := arg0.(type) { - case string: - // Use the string as a format string - msg = fmt.Sprintf(first, args...) - case func() string: - // Log the closure (no other arguments used) - msg = first() - default: - // Build a format string so that it will be similar to Sprint - msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - } - log.intLogf(lvl, msg) - return errors.New(msg) -} - -// Critical logs a message at the critical log level and returns the formatted error, -// See Warn for an explanation of the performance and Debug for an explanation -// of the parameters. -func (log Logger) Critical(arg0 interface{}, args ...interface{}) error { - const ( - lvl = CRITICAL - ) - var msg string - switch first := arg0.(type) { - case string: - // Use the string as a format string - msg = fmt.Sprintf(first, args...) - case func() string: - // Log the closure (no other arguments used) - msg = first() - default: - // Build a format string so that it will be similar to Sprint - msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - } - log.intLogf(lvl, msg) - return errors.New(msg) -} diff --git a/vendor/github.com/alecthomas/log4go/pattlog.go b/vendor/github.com/alecthomas/log4go/pattlog.go deleted file mode 100644 index 98632e4da..000000000 --- a/vendor/github.com/alecthomas/log4go/pattlog.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" -) - -const ( - FORMAT_DEFAULT = "[%D %T] [%L] (%S) %M" - FORMAT_SHORT = "[%t %d] [%L] %M" - FORMAT_ABBREV = "[%L] %M" -) - -type formatCacheType struct { - LastUpdateSeconds int64 - shortTime, shortDate string - longTime, longDate string -} - -var formatCache = &formatCacheType{} -var mutex sync.Mutex - -// Known format codes: -// %T - Time (15:04:05 MST) -// %t - Time (15:04) -// %D - Date (2006/01/02) -// %d - Date (01/02/06) -// %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT) -// %S - Source -// %M - Message -// Ignores unknown formats -// Recommended: "[%D %T] [%L] (%S) %M" -func FormatLogRecord(format string, rec *LogRecord) string { - if rec == nil { - return "" - } - if len(format) == 0 { - return "" - } - - out := bytes.NewBuffer(make([]byte, 0, 64)) - secs := rec.Created.UnixNano() / 1e9 - - mutex.Lock() - cache := *formatCache - if cache.LastUpdateSeconds != secs { - month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year() - hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second() - zone, _ := rec.Created.Zone() - updated := &formatCacheType{ - LastUpdateSeconds: secs, - shortTime: fmt.Sprintf("%02d:%02d", hour, minute), - shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100), - longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone), - longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day), - } - cache = *updated - formatCache = updated - } - mutex.Unlock() - - // Split the string into pieces by % signs - pieces := bytes.Split([]byte(format), []byte{'%'}) - - // Iterate over the pieces, replacing known formats - for i, piece := range pieces { - if i > 0 && len(piece) > 0 { - switch piece[0] { - case 'T': - out.WriteString(cache.longTime) - case 't': - out.WriteString(cache.shortTime) - case 'D': - out.WriteString(cache.longDate) - case 'd': - out.WriteString(cache.shortDate) - case 'L': - out.WriteString(levelStrings[rec.Level]) - case 'S': - out.WriteString(rec.Source) - case 's': - slice := strings.Split(rec.Source, "/") - out.WriteString(slice[len(slice)-1]) - case 'M': - out.WriteString(rec.Message) - } - if len(piece) > 1 { - out.Write(piece[1:]) - } - } else if len(piece) > 0 { - out.Write(piece) - } - } - out.WriteByte('\n') - - return out.String() -} - -// This is the standard writer that prints to standard output. -type FormatLogWriter chan *LogRecord - -// This creates a new FormatLogWriter -func NewFormatLogWriter(out io.Writer, format string) FormatLogWriter { - records := make(FormatLogWriter, LogBufferLength) - go records.run(out, format) - return records -} - -func (w FormatLogWriter) run(out io.Writer, format string) { - for rec := range w { - fmt.Fprint(out, FormatLogRecord(format, rec)) - } -} - -// This is the FormatLogWriter's output method. This will block if the output -// buffer is full. -func (w FormatLogWriter) LogWrite(rec *LogRecord) { - w <- rec -} - -// Close stops the logger from sending messages to standard output. Attempts to -// send log messages to this logger after a Close have undefined behavior. -func (w FormatLogWriter) Close() { - close(w) -} diff --git a/vendor/github.com/alecthomas/log4go/socklog.go b/vendor/github.com/alecthomas/log4go/socklog.go deleted file mode 100644 index 1d224a99d..000000000 --- a/vendor/github.com/alecthomas/log4go/socklog.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "encoding/json" - "fmt" - "net" - "os" -) - -// This log writer sends output to a socket -type SocketLogWriter chan *LogRecord - -// This is the SocketLogWriter's output method -func (w SocketLogWriter) LogWrite(rec *LogRecord) { - w <- rec -} - -func (w SocketLogWriter) Close() { - close(w) -} - -func NewSocketLogWriter(proto, hostport string) SocketLogWriter { - sock, err := net.Dial(proto, hostport) - if err != nil { - fmt.Fprintf(os.Stderr, "NewSocketLogWriter(%q): %s\n", hostport, err) - return nil - } - - w := SocketLogWriter(make(chan *LogRecord, LogBufferLength)) - - go func() { - defer func() { - if sock != nil && proto == "tcp" { - sock.Close() - } - }() - - for rec := range w { - // Marshall into JSON - js, err := json.Marshal(rec) - if err != nil { - fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err) - return - } - - _, err = sock.Write(js) - if err != nil { - fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err) - return - } - } - }() - - return w -} diff --git a/vendor/github.com/alecthomas/log4go/termlog.go b/vendor/github.com/alecthomas/log4go/termlog.go deleted file mode 100644 index 8a941e269..000000000 --- a/vendor/github.com/alecthomas/log4go/termlog.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "fmt" - "io" - "os" - "time" -) - -var stdout io.Writer = os.Stdout - -// This is the standard writer that prints to standard output. -type ConsoleLogWriter struct { - format string - w chan *LogRecord -} - -// This creates a new ConsoleLogWriter -func NewConsoleLogWriter() *ConsoleLogWriter { - consoleWriter := &ConsoleLogWriter{ - format: "[%T %D] [%L] (%S) %M", - w: make(chan *LogRecord, LogBufferLength), - } - go consoleWriter.run(stdout) - return consoleWriter -} -func (c *ConsoleLogWriter) SetFormat(format string) { - c.format = format -} -func (c *ConsoleLogWriter) run(out io.Writer) { - for rec := range c.w { - fmt.Fprint(out, FormatLogRecord(c.format, rec)) - } -} - -// This is the ConsoleLogWriter's output method. This will block if the output -// buffer is full. -func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) { - c.w <- rec -} - -// Close stops the logger from sending messages to standard output. Attempts to -// send log messages to this logger after a Close have undefined behavior. -func (c *ConsoleLogWriter) Close() { - close(c.w) - time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete -} diff --git a/vendor/github.com/alecthomas/log4go/wrapper.go b/vendor/github.com/alecthomas/log4go/wrapper.go deleted file mode 100644 index 2ae222b0c..000000000 --- a/vendor/github.com/alecthomas/log4go/wrapper.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "errors" - "fmt" - "os" - "strings" -) - -var ( - Global Logger -) - -func init() { - Global = NewDefaultLogger(DEBUG) -} - -// Wrapper for (*Logger).LoadConfiguration -func LoadConfiguration(filename string) { - Global.LoadConfiguration(filename) -} - -// Wrapper for (*Logger).AddFilter -func AddFilter(name string, lvl Level, writer LogWriter) { - Global.AddFilter(name, lvl, writer) -} - -// Wrapper for (*Logger).Close (closes and removes all logwriters) -func Close() { - Global.Close() -} - -func Crash(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(CRITICAL, strings.Repeat(" %v", len(args))[1:], args...) - } - panic(args) -} - -// Logs the given message and crashes the program -func Crashf(format string, args ...interface{}) { - Global.intLogf(CRITICAL, format, args...) - Global.Close() // so that hopefully the messages get logged - panic(fmt.Sprintf(format, args...)) -} - -// Compatibility with `log` -func Exit(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...) - } - Global.Close() // so that hopefully the messages get logged - os.Exit(0) -} - -// Compatibility with `log` -func Exitf(format string, args ...interface{}) { - Global.intLogf(ERROR, format, args...) - Global.Close() // so that hopefully the messages get logged - os.Exit(0) -} - -// Compatibility with `log` -func Stderr(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...) - } -} - -// Compatibility with `log` -func Stderrf(format string, args ...interface{}) { - Global.intLogf(ERROR, format, args...) -} - -// Compatibility with `log` -func Stdout(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(INFO, strings.Repeat(" %v", len(args))[1:], args...) - } -} - -// Compatibility with `log` -func Stdoutf(format string, args ...interface{}) { - Global.intLogf(INFO, format, args...) -} - -// Send a log message manually -// Wrapper for (*Logger).Log -func Log(lvl Level, source, message string) { - Global.Log(lvl, source, message) -} - -// Send a formatted log message easily -// Wrapper for (*Logger).Logf -func Logf(lvl Level, format string, args ...interface{}) { - Global.intLogf(lvl, format, args...) -} - -// Send a closure log message -// Wrapper for (*Logger).Logc -func Logc(lvl Level, closure func() string) { - Global.intLogc(lvl, closure) -} - -// Utility for finest log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Finest -func Finest(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINEST - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for fine log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Fine -func Fine(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for debug log messages -// When given a string as the first argument, this behaves like Logf but with the DEBUG log level (e.g. the first argument is interpreted as a format for the latter arguments) -// When given a closure of type func()string, this logs the string returned by the closure iff it will be logged. The closure runs at most one time. -// When given anything else, the log message will be each of the arguments formatted with %v and separated by spaces (ala Sprint). -// Wrapper for (*Logger).Debug -func Debug(arg0 interface{}, args ...interface{}) { - const ( - lvl = DEBUG - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for trace log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Trace -func Trace(arg0 interface{}, args ...interface{}) { - const ( - lvl = TRACE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for info log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Info -func Info(arg0 interface{}, args ...interface{}) { - const ( - lvl = INFO - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for warn log messages (returns an error for easy function returns) (see Debug() for parameter explanation) -// These functions will execute a closure exactly once, to build the error message for the return -// Wrapper for (*Logger).Warn -func Warn(arg0 interface{}, args ...interface{}) error { - const ( - lvl = WARNING - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - return errors.New(fmt.Sprintf(first, args...)) - case func() string: - // Log the closure (no other arguments used) - str := first() - Global.intLogf(lvl, "%s", str) - return errors.New(str) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) - } - return nil -} - -// Utility for error log messages (returns an error for easy function returns) (see Debug() for parameter explanation) -// These functions will execute a closure exactly once, to build the error message for the return -// Wrapper for (*Logger).Error -func Error(arg0 interface{}, args ...interface{}) error { - const ( - lvl = ERROR - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - return errors.New(fmt.Sprintf(first, args...)) - case func() string: - // Log the closure (no other arguments used) - str := first() - Global.intLogf(lvl, "%s", str) - return errors.New(str) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) - } - return nil -} - -// Utility for critical log messages (returns an error for easy function returns) (see Debug() for parameter explanation) -// These functions will execute a closure exactly once, to build the error message for the return -// Wrapper for (*Logger).Critical -func Critical(arg0 interface{}, args ...interface{}) error { - const ( - lvl = CRITICAL - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - return errors.New(fmt.Sprintf(first, args...)) - case func() string: - // Log the closure (no other arguments used) - str := first() - Global.intLogf(lvl, "%s", str) - return errors.New(str) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) - } - return nil -} diff --git a/vendor/go.uber.org/atomic/.codecov.yml b/vendor/go.uber.org/atomic/.codecov.yml new file mode 100644 index 000000000..6d4d1be7b --- /dev/null +++ b/vendor/go.uber.org/atomic/.codecov.yml @@ -0,0 +1,15 @@ +coverage: + range: 80..100 + round: down + precision: 2 + + status: + project: # measuring the overall project coverage + default: # context, you can create multiple ones with custom titles + enabled: yes # must be yes|true to enable this status + target: 100 # specify the target coverage for each commit status + # option: "auto" (must increase from parent commit or pull request base) + # option: "X%" a static target percentage to hit + if_not_found: success # if parent is not found report status as success, error, or failure + if_ci_failed: error # if ci fails report status as success, error, or failure + diff --git a/vendor/go.uber.org/atomic/.gitignore b/vendor/go.uber.org/atomic/.gitignore new file mode 100644 index 000000000..0a4504f11 --- /dev/null +++ b/vendor/go.uber.org/atomic/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +/vendor +/cover +cover.out +lint.log + +# Binaries +*.test + +# Profiling output +*.prof diff --git a/vendor/go.uber.org/atomic/.travis.yml b/vendor/go.uber.org/atomic/.travis.yml new file mode 100644 index 000000000..58957222a --- /dev/null +++ b/vendor/go.uber.org/atomic/.travis.yml @@ -0,0 +1,23 @@ +sudo: false +language: go +go_import_path: go.uber.org/atomic + +go: + - 1.7 + - 1.8 + - 1.9 + +cache: + directories: + - vendor + +install: + - make install_ci + +script: + - make test_ci + - scripts/test-ubergo.sh + - make lint + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/go.uber.org/atomic/LICENSE.txt b/vendor/go.uber.org/atomic/LICENSE.txt new file mode 100644 index 000000000..8765c9fbc --- /dev/null +++ b/vendor/go.uber.org/atomic/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/go.uber.org/atomic/Makefile b/vendor/go.uber.org/atomic/Makefile new file mode 100644 index 000000000..dfc63d9db --- /dev/null +++ b/vendor/go.uber.org/atomic/Makefile @@ -0,0 +1,64 @@ +PACKAGES := $(shell glide nv) +# Many Go tools take file globs or directories as arguments instead of packages. +PACKAGE_FILES ?= *.go + + +# The linting tools evolve with each Go version, so run them only on the latest +# stable release. +GO_VERSION := $(shell go version | cut -d " " -f 3) +GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION))) +LINTABLE_MINOR_VERSIONS := 7 8 +ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),) +SHOULD_LINT := true +endif + + +export GO15VENDOREXPERIMENT=1 + + +.PHONY: build +build: + go build -i $(PACKAGES) + + +.PHONY: install +install: + glide --version || go get github.com/Masterminds/glide + glide install + + +.PHONY: test +test: + go test -cover -race $(PACKAGES) + + +.PHONY: install_ci +install_ci: install + go get github.com/wadey/gocovmerge + go get github.com/mattn/goveralls + go get golang.org/x/tools/cmd/cover +ifdef SHOULD_LINT + go get github.com/golang/lint/golint +endif + +.PHONY: lint +lint: +ifdef SHOULD_LINT + @rm -rf lint.log + @echo "Checking formatting..." + @gofmt -d -s $(PACKAGE_FILES) 2>&1 | tee lint.log + @echo "Checking vet..." + @$(foreach dir,$(PACKAGE_FILES),go tool vet $(dir) 2>&1 | tee -a lint.log;) + @echo "Checking lint..." + @$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;) + @echo "Checking for unresolved FIXMEs..." + @git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log + @[ ! -s lint.log ] +else + @echo "Skipping linters on" $(GO_VERSION) +endif + + +.PHONY: test_ci +test_ci: install_ci build + ./scripts/cover.sh $(shell go list $(PACKAGES)) diff --git a/vendor/go.uber.org/atomic/README.md b/vendor/go.uber.org/atomic/README.md new file mode 100644 index 000000000..6505abf65 --- /dev/null +++ b/vendor/go.uber.org/atomic/README.md @@ -0,0 +1,36 @@ +# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard] + +Simple wrappers for primitive types to enforce atomic access. + +## Installation +`go get -u go.uber.org/atomic` + +## Usage +The standard library's `sync/atomic` is powerful, but it's easy to forget which +variables must be accessed atomically. `go.uber.org/atomic` preserves all the +functionality of the standard library, but wraps the primitive types to +provide a safer, more convenient API. + +```go +var atom atomic.Uint32 +atom.Store(42) +atom.Sub(2) +atom.CAS(40, 11) +``` + +See the [documentation][doc] for a complete API specification. + +## Development Status +Stable. + +
+Released under the [MIT License](LICENSE.txt). + +[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg +[doc]: https://godoc.org/go.uber.org/atomic +[ci-img]: https://travis-ci.org/uber-go/atomic.svg?branch=master +[ci]: https://travis-ci.org/uber-go/atomic +[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/atomic +[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic +[reportcard]: https://goreportcard.com/report/go.uber.org/atomic diff --git a/vendor/go.uber.org/atomic/atomic.go b/vendor/go.uber.org/atomic/atomic.go new file mode 100644 index 000000000..1ca50dc34 --- /dev/null +++ b/vendor/go.uber.org/atomic/atomic.go @@ -0,0 +1,309 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package atomic provides simple wrappers around numerics to enforce atomic +// access. +package atomic + +import ( + "math" + "sync/atomic" +) + +// Int32 is an atomic wrapper around an int32. +type Int32 struct{ v int32 } + +// NewInt32 creates an Int32. +func NewInt32(i int32) *Int32 { + return &Int32{i} +} + +// Load atomically loads the wrapped value. +func (i *Int32) Load() int32 { + return atomic.LoadInt32(&i.v) +} + +// Add atomically adds to the wrapped int32 and returns the new value. +func (i *Int32) Add(n int32) int32 { + return atomic.AddInt32(&i.v, n) +} + +// Sub atomically subtracts from the wrapped int32 and returns the new value. +func (i *Int32) Sub(n int32) int32 { + return atomic.AddInt32(&i.v, -n) +} + +// Inc atomically increments the wrapped int32 and returns the new value. +func (i *Int32) Inc() int32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int32 and returns the new value. +func (i *Int32) Dec() int32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Int32) CAS(old, new int32) bool { + return atomic.CompareAndSwapInt32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int32) Store(n int32) { + atomic.StoreInt32(&i.v, n) +} + +// Swap atomically swaps the wrapped int32 and returns the old value. +func (i *Int32) Swap(n int32) int32 { + return atomic.SwapInt32(&i.v, n) +} + +// Int64 is an atomic wrapper around an int64. +type Int64 struct{ v int64 } + +// NewInt64 creates an Int64. +func NewInt64(i int64) *Int64 { + return &Int64{i} +} + +// Load atomically loads the wrapped value. +func (i *Int64) Load() int64 { + return atomic.LoadInt64(&i.v) +} + +// Add atomically adds to the wrapped int64 and returns the new value. +func (i *Int64) Add(n int64) int64 { + return atomic.AddInt64(&i.v, n) +} + +// Sub atomically subtracts from the wrapped int64 and returns the new value. +func (i *Int64) Sub(n int64) int64 { + return atomic.AddInt64(&i.v, -n) +} + +// Inc atomically increments the wrapped int64 and returns the new value. +func (i *Int64) Inc() int64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int64 and returns the new value. +func (i *Int64) Dec() int64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Int64) CAS(old, new int64) bool { + return atomic.CompareAndSwapInt64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int64) Store(n int64) { + atomic.StoreInt64(&i.v, n) +} + +// Swap atomically swaps the wrapped int64 and returns the old value. +func (i *Int64) Swap(n int64) int64 { + return atomic.SwapInt64(&i.v, n) +} + +// Uint32 is an atomic wrapper around an uint32. +type Uint32 struct{ v uint32 } + +// NewUint32 creates a Uint32. +func NewUint32(i uint32) *Uint32 { + return &Uint32{i} +} + +// Load atomically loads the wrapped value. +func (i *Uint32) Load() uint32 { + return atomic.LoadUint32(&i.v) +} + +// Add atomically adds to the wrapped uint32 and returns the new value. +func (i *Uint32) Add(n uint32) uint32 { + return atomic.AddUint32(&i.v, n) +} + +// Sub atomically subtracts from the wrapped uint32 and returns the new value. +func (i *Uint32) Sub(n uint32) uint32 { + return atomic.AddUint32(&i.v, ^(n - 1)) +} + +// Inc atomically increments the wrapped uint32 and returns the new value. +func (i *Uint32) Inc() uint32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int32 and returns the new value. +func (i *Uint32) Dec() uint32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uint32) CAS(old, new uint32) bool { + return atomic.CompareAndSwapUint32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint32) Store(n uint32) { + atomic.StoreUint32(&i.v, n) +} + +// Swap atomically swaps the wrapped uint32 and returns the old value. +func (i *Uint32) Swap(n uint32) uint32 { + return atomic.SwapUint32(&i.v, n) +} + +// Uint64 is an atomic wrapper around a uint64. +type Uint64 struct{ v uint64 } + +// NewUint64 creates a Uint64. +func NewUint64(i uint64) *Uint64 { + return &Uint64{i} +} + +// Load atomically loads the wrapped value. +func (i *Uint64) Load() uint64 { + return atomic.LoadUint64(&i.v) +} + +// Add atomically adds to the wrapped uint64 and returns the new value. +func (i *Uint64) Add(n uint64) uint64 { + return atomic.AddUint64(&i.v, n) +} + +// Sub atomically subtracts from the wrapped uint64 and returns the new value. +func (i *Uint64) Sub(n uint64) uint64 { + return atomic.AddUint64(&i.v, ^(n - 1)) +} + +// Inc atomically increments the wrapped uint64 and returns the new value. +func (i *Uint64) Inc() uint64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint64 and returns the new value. +func (i *Uint64) Dec() uint64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uint64) CAS(old, new uint64) bool { + return atomic.CompareAndSwapUint64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint64) Store(n uint64) { + atomic.StoreUint64(&i.v, n) +} + +// Swap atomically swaps the wrapped uint64 and returns the old value. +func (i *Uint64) Swap(n uint64) uint64 { + return atomic.SwapUint64(&i.v, n) +} + +// Bool is an atomic Boolean. +type Bool struct{ v uint32 } + +// NewBool creates a Bool. +func NewBool(initial bool) *Bool { + return &Bool{boolToInt(initial)} +} + +// Load atomically loads the Boolean. +func (b *Bool) Load() bool { + return truthy(atomic.LoadUint32(&b.v)) +} + +// CAS is an atomic compare-and-swap. +func (b *Bool) CAS(old, new bool) bool { + return atomic.CompareAndSwapUint32(&b.v, boolToInt(old), boolToInt(new)) +} + +// Store atomically stores the passed value. +func (b *Bool) Store(new bool) { + atomic.StoreUint32(&b.v, boolToInt(new)) +} + +// Swap sets the given value and returns the previous value. +func (b *Bool) Swap(new bool) bool { + return truthy(atomic.SwapUint32(&b.v, boolToInt(new))) +} + +// Toggle atomically negates the Boolean and returns the previous value. +func (b *Bool) Toggle() bool { + return truthy(atomic.AddUint32(&b.v, 1) - 1) +} + +func truthy(n uint32) bool { + return n&1 == 1 +} + +func boolToInt(b bool) uint32 { + if b { + return 1 + } + return 0 +} + +// Float64 is an atomic wrapper around float64. +type Float64 struct { + v uint64 +} + +// NewFloat64 creates a Float64. +func NewFloat64(f float64) *Float64 { + return &Float64{math.Float64bits(f)} +} + +// Load atomically loads the wrapped value. +func (f *Float64) Load() float64 { + return math.Float64frombits(atomic.LoadUint64(&f.v)) +} + +// Store atomically stores the passed value. +func (f *Float64) Store(s float64) { + atomic.StoreUint64(&f.v, math.Float64bits(s)) +} + +// Add atomically adds to the wrapped float64 and returns the new value. +func (f *Float64) Add(s float64) float64 { + for { + old := f.Load() + new := old + s + if f.CAS(old, new) { + return new + } + } +} + +// Sub atomically subtracts from the wrapped float64 and returns the new value. +func (f *Float64) Sub(s float64) float64 { + return f.Add(-s) +} + +// CAS is an atomic compare-and-swap. +func (f *Float64) CAS(old, new float64) bool { + return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new)) +} + +// Value shadows the type of the same name from sync/atomic +// https://godoc.org/sync/atomic#Value +type Value struct{ atomic.Value } diff --git a/vendor/go.uber.org/atomic/glide.lock b/vendor/go.uber.org/atomic/glide.lock new file mode 100644 index 000000000..3c72c5997 --- /dev/null +++ b/vendor/go.uber.org/atomic/glide.lock @@ -0,0 +1,17 @@ +hash: f14d51408e3e0e4f73b34e4039484c78059cd7fc5f4996fdd73db20dc8d24f53 +updated: 2016-10-27T00:10:51.16960137-07:00 +imports: [] +testImports: +- name: github.com/davecgh/go-spew + version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: d77da356e56a7428ad25149ca77381849a6a5232 + subpackages: + - assert + - require diff --git a/vendor/go.uber.org/atomic/glide.yaml b/vendor/go.uber.org/atomic/glide.yaml new file mode 100644 index 000000000..4cf608ec0 --- /dev/null +++ b/vendor/go.uber.org/atomic/glide.yaml @@ -0,0 +1,6 @@ +package: go.uber.org/atomic +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert + - require diff --git a/vendor/go.uber.org/atomic/string.go b/vendor/go.uber.org/atomic/string.go new file mode 100644 index 000000000..ede8136fa --- /dev/null +++ b/vendor/go.uber.org/atomic/string.go @@ -0,0 +1,49 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// String is an atomic type-safe wrapper around Value for strings. +type String struct{ v Value } + +// NewString creates a String. +func NewString(str string) *String { + s := &String{} + if str != "" { + s.Store(str) + } + return s +} + +// Load atomically loads the wrapped string. +func (s *String) Load() string { + v := s.v.Load() + if v == nil { + return "" + } + return v.(string) +} + +// Store atomically stores the passed string. +// Note: Converting the string to an interface{} to store in the Value +// requires an allocation. +func (s *String) Store(str string) { + s.v.Store(str) +} diff --git a/vendor/go.uber.org/multierr/.codecov.yml b/vendor/go.uber.org/multierr/.codecov.yml new file mode 100644 index 000000000..6d4d1be7b --- /dev/null +++ b/vendor/go.uber.org/multierr/.codecov.yml @@ -0,0 +1,15 @@ +coverage: + range: 80..100 + round: down + precision: 2 + + status: + project: # measuring the overall project coverage + default: # context, you can create multiple ones with custom titles + enabled: yes # must be yes|true to enable this status + target: 100 # specify the target coverage for each commit status + # option: "auto" (must increase from parent commit or pull request base) + # option: "X%" a static target percentage to hit + if_not_found: success # if parent is not found report status as success, error, or failure + if_ci_failed: error # if ci fails report status as success, error, or failure + diff --git a/vendor/go.uber.org/multierr/.gitignore b/vendor/go.uber.org/multierr/.gitignore new file mode 100644 index 000000000..61ead8666 --- /dev/null +++ b/vendor/go.uber.org/multierr/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/vendor/go.uber.org/multierr/.travis.yml b/vendor/go.uber.org/multierr/.travis.yml new file mode 100644 index 000000000..5ffa8fed4 --- /dev/null +++ b/vendor/go.uber.org/multierr/.travis.yml @@ -0,0 +1,33 @@ +sudo: false +language: go +go_import_path: go.uber.org/multierr + +env: + global: + - GO15VENDOREXPERIMENT=1 + +go: + - 1.7 + - 1.8 + - tip + +cache: + directories: + - vendor + +before_install: +- go version + +install: +- | + set -e + make install_ci + +script: +- | + set -e + make lint + make test_ci + +after_success: +- bash <(curl -s https://codecov.io/bash) diff --git a/vendor/go.uber.org/multierr/CHANGELOG.md b/vendor/go.uber.org/multierr/CHANGELOG.md new file mode 100644 index 000000000..898445d06 --- /dev/null +++ b/vendor/go.uber.org/multierr/CHANGELOG.md @@ -0,0 +1,28 @@ +Releases +======== + +v1.1.0 (2017-06-30) +=================== + +- Added an `Errors(error) []error` function to extract the underlying list of + errors for a multierr error. + + +v1.0.0 (2017-05-31) +=================== + +No changes since v0.2.0. This release is committing to making no breaking +changes to the current API in the 1.X series. + + +v0.2.0 (2017-04-11) +=================== + +- Repeatedly appending to the same error is now faster due to fewer + allocations. + + +v0.1.0 (2017-31-03) +=================== + +- Initial release diff --git a/vendor/go.uber.org/multierr/LICENSE.txt b/vendor/go.uber.org/multierr/LICENSE.txt new file mode 100644 index 000000000..858e02475 --- /dev/null +++ b/vendor/go.uber.org/multierr/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/go.uber.org/multierr/Makefile b/vendor/go.uber.org/multierr/Makefile new file mode 100644 index 000000000..a7437d061 --- /dev/null +++ b/vendor/go.uber.org/multierr/Makefile @@ -0,0 +1,74 @@ +export GO15VENDOREXPERIMENT=1 + +PACKAGES := $(shell glide nv) + +GO_FILES := $(shell \ + find . '(' -path '*/.*' -o -path './vendor' ')' -prune \ + -o -name '*.go' -print | cut -b3-) + +.PHONY: install +install: + glide --version || go get github.com/Masterminds/glide + glide install + +.PHONY: build +build: + go build -i $(PACKAGES) + +.PHONY: test +test: + go test -cover -race $(PACKAGES) + +.PHONY: gofmt +gofmt: + $(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX)) + @gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true + @[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" | cat - $(FMT_LOG) && false) + +.PHONY: govet +govet: + $(eval VET_LOG := $(shell mktemp -t govet.XXXXX)) + @go vet $(PACKAGES) 2>&1 \ + | grep -v '^exit status' > $(VET_LOG) || true + @[ ! -s "$(VET_LOG)" ] || (echo "govet failed:" | cat - $(VET_LOG) && false) + +.PHONY: golint +golint: + @go get github.com/golang/lint/golint + $(eval LINT_LOG := $(shell mktemp -t golint.XXXXX)) + @cat /dev/null > $(LINT_LOG) + @$(foreach pkg, $(PACKAGES), golint $(pkg) >> $(LINT_LOG) || true;) + @[ ! -s "$(LINT_LOG)" ] || (echo "golint failed:" | cat - $(LINT_LOG) && false) + +.PHONY: staticcheck +staticcheck: + @go get honnef.co/go/tools/cmd/staticcheck + $(eval STATICCHECK_LOG := $(shell mktemp -t staticcheck.XXXXX)) + @staticcheck $(PACKAGES) 2>&1 > $(STATICCHECK_LOG) || true + @[ ! -s "$(STATICCHECK_LOG)" ] || (echo "staticcheck failed:" | cat - $(STATICCHECK_LOG) && false) + +.PHONY: lint +lint: gofmt govet golint staticcheck + +.PHONY: cover +cover: + ./scripts/cover.sh $(shell go list $(PACKAGES)) + go tool cover -html=cover.out -o cover.html + +update-license: + @go get go.uber.org/tools/update-license + @update-license \ + $(shell go list -json $(PACKAGES) | \ + jq -r '.Dir + "/" + (.GoFiles | .[])') + +############################################################################## + +.PHONY: install_ci +install_ci: install + go get github.com/wadey/gocovmerge + go get github.com/mattn/goveralls + go get golang.org/x/tools/cmd/cover + +.PHONY: test_ci +test_ci: install_ci + ./scripts/cover.sh $(shell go list $(PACKAGES)) diff --git a/vendor/go.uber.org/multierr/README.md b/vendor/go.uber.org/multierr/README.md new file mode 100644 index 000000000..065088f64 --- /dev/null +++ b/vendor/go.uber.org/multierr/README.md @@ -0,0 +1,23 @@ +# multierr [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +`multierr` allows combining one or more Go `error`s together. + +## Installation + + go get -u go.uber.org/multierr + +## Status + +Stable: No breaking changes will be made before 2.0. + +------------------------------------------------------------------------------- + +Released under the [MIT License]. + +[MIT License]: LICENSE.txt +[doc-img]: https://godoc.org/go.uber.org/multierr?status.svg +[doc]: https://godoc.org/go.uber.org/multierr +[ci-img]: https://travis-ci.org/uber-go/multierr.svg?branch=master +[cov-img]: https://codecov.io/gh/uber-go/multierr/branch/master/graph/badge.svg +[ci]: https://travis-ci.org/uber-go/multierr +[cov]: https://codecov.io/gh/uber-go/multierr diff --git a/vendor/go.uber.org/multierr/error.go b/vendor/go.uber.org/multierr/error.go new file mode 100644 index 000000000..de6ce4736 --- /dev/null +++ b/vendor/go.uber.org/multierr/error.go @@ -0,0 +1,401 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package multierr allows combining one or more errors together. +// +// Overview +// +// Errors can be combined with the use of the Combine function. +// +// multierr.Combine( +// reader.Close(), +// writer.Close(), +// conn.Close(), +// ) +// +// If only two errors are being combined, the Append function may be used +// instead. +// +// err = multierr.Combine(reader.Close(), writer.Close()) +// +// This makes it possible to record resource cleanup failures from deferred +// blocks with the help of named return values. +// +// func sendRequest(req Request) (err error) { +// conn, err := openConnection() +// if err != nil { +// return err +// } +// defer func() { +// err = multierr.Append(err, conn.Close()) +// }() +// // ... +// } +// +// The underlying list of errors for a returned error object may be retrieved +// with the Errors function. +// +// errors := multierr.Errors(err) +// if len(errors) > 0 { +// fmt.Println("The following errors occurred:") +// } +// +// Advanced Usage +// +// Errors returned by Combine and Append MAY implement the following +// interface. +// +// type errorGroup interface { +// // Returns a slice containing the underlying list of errors. +// // +// // This slice MUST NOT be modified by the caller. +// Errors() []error +// } +// +// Note that if you need access to list of errors behind a multierr error, you +// should prefer using the Errors function. That said, if you need cheap +// read-only access to the underlying errors slice, you can attempt to cast +// the error to this interface. You MUST handle the failure case gracefully +// because errors returned by Combine and Append are not guaranteed to +// implement this interface. +// +// var errors []error +// group, ok := err.(errorGroup) +// if ok { +// errors = group.Errors() +// } else { +// errors = []error{err} +// } +package multierr // import "go.uber.org/multierr" + +import ( + "bytes" + "fmt" + "io" + "strings" + "sync" + + "go.uber.org/atomic" +) + +var ( + // Separator for single-line error messages. + _singlelineSeparator = []byte("; ") + + _newline = []byte("\n") + + // Prefix for multi-line messages + _multilinePrefix = []byte("the following errors occurred:") + + // Prefix for the first and following lines of an item in a list of + // multi-line error messages. + // + // For example, if a single item is: + // + // foo + // bar + // + // It will become, + // + // - foo + // bar + _multilineSeparator = []byte("\n - ") + _multilineIndent = []byte(" ") +) + +// _bufferPool is a pool of bytes.Buffers. +var _bufferPool = sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, +} + +type errorGroup interface { + Errors() []error +} + +// Errors returns a slice containing zero or more errors that the supplied +// error is composed of. If the error is nil, the returned slice is empty. +// +// err := multierr.Append(r.Close(), w.Close()) +// errors := multierr.Errors(err) +// +// If the error is not composed of other errors, the returned slice contains +// just the error that was passed in. +// +// Callers of this function are free to modify the returned slice. +func Errors(err error) []error { + if err == nil { + return nil + } + + // Note that we're casting to multiError, not errorGroup. Our contract is + // that returned errors MAY implement errorGroup. Errors, however, only + // has special behavior for multierr-specific error objects. + // + // This behavior can be expanded in the future but I think it's prudent to + // start with as little as possible in terms of contract and possibility + // of misuse. + eg, ok := err.(*multiError) + if !ok { + return []error{err} + } + + errors := eg.Errors() + result := make([]error, len(errors)) + copy(result, errors) + return result +} + +// multiError is an error that holds one or more errors. +// +// An instance of this is guaranteed to be non-empty and flattened. That is, +// none of the errors inside multiError are other multiErrors. +// +// multiError formats to a semi-colon delimited list of error messages with +// %v and with a more readable multi-line format with %+v. +type multiError struct { + copyNeeded atomic.Bool + errors []error +} + +var _ errorGroup = (*multiError)(nil) + +// Errors returns the list of underlying errors. +// +// This slice MUST NOT be modified. +func (merr *multiError) Errors() []error { + if merr == nil { + return nil + } + return merr.errors +} + +func (merr *multiError) Error() string { + if merr == nil { + return "" + } + + buff := _bufferPool.Get().(*bytes.Buffer) + buff.Reset() + + merr.writeSingleline(buff) + + result := buff.String() + _bufferPool.Put(buff) + return result +} + +func (merr *multiError) Format(f fmt.State, c rune) { + if c == 'v' && f.Flag('+') { + merr.writeMultiline(f) + } else { + merr.writeSingleline(f) + } +} + +func (merr *multiError) writeSingleline(w io.Writer) { + first := true + for _, item := range merr.errors { + if first { + first = false + } else { + w.Write(_singlelineSeparator) + } + io.WriteString(w, item.Error()) + } +} + +func (merr *multiError) writeMultiline(w io.Writer) { + w.Write(_multilinePrefix) + for _, item := range merr.errors { + w.Write(_multilineSeparator) + writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item)) + } +} + +// Writes s to the writer with the given prefix added before each line after +// the first. +func writePrefixLine(w io.Writer, prefix []byte, s string) { + first := true + for len(s) > 0 { + if first { + first = false + } else { + w.Write(prefix) + } + + idx := strings.IndexByte(s, '\n') + if idx < 0 { + idx = len(s) - 1 + } + + io.WriteString(w, s[:idx+1]) + s = s[idx+1:] + } +} + +type inspectResult struct { + // Number of top-level non-nil errors + Count int + + // Total number of errors including multiErrors + Capacity int + + // Index of the first non-nil error in the list. Value is meaningless if + // Count is zero. + FirstErrorIdx int + + // Whether the list contains at least one multiError + ContainsMultiError bool +} + +// Inspects the given slice of errors so that we can efficiently allocate +// space for it. +func inspect(errors []error) (res inspectResult) { + first := true + for i, err := range errors { + if err == nil { + continue + } + + res.Count++ + if first { + first = false + res.FirstErrorIdx = i + } + + if merr, ok := err.(*multiError); ok { + res.Capacity += len(merr.errors) + res.ContainsMultiError = true + } else { + res.Capacity++ + } + } + return +} + +// fromSlice converts the given list of errors into a single error. +func fromSlice(errors []error) error { + res := inspect(errors) + switch res.Count { + case 0: + return nil + case 1: + // only one non-nil entry + return errors[res.FirstErrorIdx] + case len(errors): + if !res.ContainsMultiError { + // already flat + return &multiError{errors: errors} + } + } + + nonNilErrs := make([]error, 0, res.Capacity) + for _, err := range errors[res.FirstErrorIdx:] { + if err == nil { + continue + } + + if nested, ok := err.(*multiError); ok { + nonNilErrs = append(nonNilErrs, nested.errors...) + } else { + nonNilErrs = append(nonNilErrs, err) + } + } + + return &multiError{errors: nonNilErrs} +} + +// Combine combines the passed errors into a single error. +// +// If zero arguments were passed or if all items are nil, a nil error is +// returned. +// +// Combine(nil, nil) // == nil +// +// If only a single error was passed, it is returned as-is. +// +// Combine(err) // == err +// +// Combine skips over nil arguments so this function may be used to combine +// together errors from operations that fail independently of each other. +// +// multierr.Combine( +// reader.Close(), +// writer.Close(), +// pipe.Close(), +// ) +// +// If any of the passed errors is a multierr error, it will be flattened along +// with the other errors. +// +// multierr.Combine(multierr.Combine(err1, err2), err3) +// // is the same as +// multierr.Combine(err1, err2, err3) +// +// The returned error formats into a readable multi-line error message if +// formatted with %+v. +// +// fmt.Sprintf("%+v", multierr.Combine(err1, err2)) +func Combine(errors ...error) error { + return fromSlice(errors) +} + +// Append appends the given errors together. Either value may be nil. +// +// This function is a specialization of Combine for the common case where +// there are only two errors. +// +// err = multierr.Append(reader.Close(), writer.Close()) +// +// The following pattern may also be used to record failure of deferred +// operations without losing information about the original error. +// +// func doSomething(..) (err error) { +// f := acquireResource() +// defer func() { +// err = multierr.Append(err, f.Close()) +// }() +func Append(left error, right error) error { + switch { + case left == nil: + return right + case right == nil: + return left + } + + if _, ok := right.(*multiError); !ok { + if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { + // Common case where the error on the left is constantly being + // appended to. + errs := append(l.errors, right) + return &multiError{errors: errs} + } else if !ok { + // Both errors are single errors. + return &multiError{errors: []error{left, right}} + } + } + + // Either right or both, left and right, are multiErrors. Rely on usual + // expensive logic. + errors := [2]error{left, right} + return fromSlice(errors[0:]) +} diff --git a/vendor/go.uber.org/multierr/glide.lock b/vendor/go.uber.org/multierr/glide.lock new file mode 100644 index 000000000..f9ea94c33 --- /dev/null +++ b/vendor/go.uber.org/multierr/glide.lock @@ -0,0 +1,19 @@ +hash: b53b5e9a84b9cb3cc4b2d0499e23da2feca1eec318ce9bb717ecf35bf24bf221 +updated: 2017-04-10T13:34:45.671678062-07:00 +imports: +- name: go.uber.org/atomic + version: 3b8db5e93c4c02efbc313e17b2e796b0914a01fb +testImports: +- name: github.com/davecgh/go-spew + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + subpackages: + - assert + - require diff --git a/vendor/go.uber.org/multierr/glide.yaml b/vendor/go.uber.org/multierr/glide.yaml new file mode 100644 index 000000000..6ef084ec2 --- /dev/null +++ b/vendor/go.uber.org/multierr/glide.yaml @@ -0,0 +1,8 @@ +package: go.uber.org/multierr +import: +- package: go.uber.org/atomic + version: ^1 +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert diff --git a/vendor/go.uber.org/zap/.codecov.yml b/vendor/go.uber.org/zap/.codecov.yml new file mode 100644 index 000000000..8e5ca7d3e --- /dev/null +++ b/vendor/go.uber.org/zap/.codecov.yml @@ -0,0 +1,17 @@ +coverage: + range: 80..100 + round: down + precision: 2 + + status: + project: # measuring the overall project coverage + default: # context, you can create multiple ones with custom titles + enabled: yes # must be yes|true to enable this status + target: 95% # specify the target coverage for each commit status + # option: "auto" (must increase from parent commit or pull request base) + # option: "X%" a static target percentage to hit + if_not_found: success # if parent is not found report status as success, error, or failure + if_ci_failed: error # if ci fails report status as success, error, or failure +ignore: + - internal/readme/readme.go + diff --git a/vendor/go.uber.org/zap/.gitignore b/vendor/go.uber.org/zap/.gitignore new file mode 100644 index 000000000..08fbde6ce --- /dev/null +++ b/vendor/go.uber.org/zap/.gitignore @@ -0,0 +1,28 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +vendor + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.pprof +*.out +*.log diff --git a/vendor/go.uber.org/zap/.readme.tmpl b/vendor/go.uber.org/zap/.readme.tmpl new file mode 100644 index 000000000..550dcda12 --- /dev/null +++ b/vendor/go.uber.org/zap/.readme.tmpl @@ -0,0 +1,108 @@ +# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +Blazing fast, structured, leveled logging in Go. + +## Installation + +`go get -u go.uber.org/zap` + +Note that zap only supports the two most recent minor versions of Go. + +## Quick Start + +In contexts where performance is nice, but not critical, use the +`SugaredLogger`. It's 4-10x faster than than other structured logging +packages and includes both structured and `printf`-style APIs. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() // flushes buffer, if any +sugar := logger.Sugar() +sugar.Infow("failed to fetch URL", + // Structured context as loosely typed key-value pairs. + "url", url, + "attempt", 3, + "backoff", time.Second, +) +sugar.Infof("Failed to fetch URL: %s", url) +``` + +When performance and type safety are critical, use the `Logger`. It's even +faster than the `SugaredLogger` and allocates far less, but it only supports +structured logging. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() +logger.Info("failed to fetch URL", + // Structured context as strongly typed Field values. + zap.String("url", url), + zap.Int("attempt", 3), + zap.Duration("backoff", time.Second), +) +``` + +See the [documentation][doc] and [FAQ](FAQ.md) for more details. + +## Performance + +For applications that log in the hot path, reflection-based serialization and +string formatting are prohibitively expensive — they're CPU-intensive +and make many small allocations. Put differently, using `encoding/json` and +`fmt.Fprintf` to log tons of `interface{}`s makes your application slow. + +Zap takes a different approach. It includes a reflection-free, zero-allocation +JSON encoder, and the base `Logger` strives to avoid serialization overhead +and allocations wherever possible. By building the high-level `SugaredLogger` +on that foundation, zap lets users *choose* when they need to count every +allocation and when they'd prefer a more familiar, loosely typed API. + +As measured by its own [benchmarking suite][], not only is zap more performant +than comparable structured logging packages — it's also faster than the +standard library. Like all benchmarks, take these with a grain of salt.[1](#footnote-versions) + +Log a message and 10 fields: + +{{.BenchmarkAddingFields}} + +Log a message with a logger that already has 10 fields of context: + +{{.BenchmarkAccumulatedContext}} + +Log a static string, without any context or `printf`-style templating: + +{{.BenchmarkWithoutFields}} + +## Development Status: Stable + +All APIs are finalized, and no breaking changes will be made in the 1.x series +of releases. Users of semver-aware dependency management systems should pin +zap to `^1`. + +## Contributing + +We encourage and support an active, healthy community of contributors — +including you! Details are in the [contribution guide](CONTRIBUTING.md) and +the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on +issues and pull requests, but you can also report any negative conduct to +oss-conduct@uber.com. That email list is a private, safe space; even the zap +maintainers don't have access, so don't hesitate to hold us to a high +standard. + +
+ +Released under the [MIT License](LICENSE.txt). + +1 In particular, keep in mind that we may be +benchmarking against slightly older versions of other packages. Versions are +pinned in zap's [glide.lock][] file. [↩](#anchor-versions) + +[doc-img]: https://godoc.org/go.uber.org/zap?status.svg +[doc]: https://godoc.org/go.uber.org/zap +[ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master +[ci]: https://travis-ci.org/uber-go/zap +[cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/zap +[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks +[glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock diff --git a/vendor/go.uber.org/zap/.travis.yml b/vendor/go.uber.org/zap/.travis.yml new file mode 100644 index 000000000..da1100b7d --- /dev/null +++ b/vendor/go.uber.org/zap/.travis.yml @@ -0,0 +1,21 @@ +language: go +sudo: false +go: + - "1.9" + - "1.10" +go_import_path: go.uber.org/zap +env: + global: + - TEST_TIMEOUT_SCALE=10 +cache: + directories: + - vendor +install: + - make dependencies +script: + - make lint + - make test + - make bench +after_success: + - make cover + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/go.uber.org/zap/CHANGELOG.md b/vendor/go.uber.org/zap/CHANGELOG.md new file mode 100644 index 000000000..be28291d5 --- /dev/null +++ b/vendor/go.uber.org/zap/CHANGELOG.md @@ -0,0 +1,286 @@ +# Changelog + +## v1.8.0 (13 Apr 2018) + +Enhancements: +* [#508][]: Make log level configurable when redirecting the standard + library's logger. +* [#518][]: Add a logger that writes to a `*testing.TB`. +* [#577][]: Add a top-level alias for `zapcore.Field` to clean up GoDoc. + +Bugfixes: +* [#574][]: Add a missing import comment to `go.uber.org/zap/buffer`. + +Thanks to @DiSiqueira and @djui for their contributions to this release. + +## v1.7.1 (25 Sep 2017) + +Bugfixes: +* [#504][]: Store strings when using AddByteString with the map encoder. + +## v1.7.0 (21 Sep 2017) + +Enhancements: + +* [#487][]: Add `NewStdLogAt`, which extends `NewStdLog` by allowing the user + to specify the level of the logged messages. + +## v1.6.0 (30 Aug 2017) + +Enhancements: + +* [#491][]: Omit zap stack frames from stacktraces. +* [#490][]: Add a `ContextMap` method to observer logs for simpler + field validation in tests. + +## v1.5.0 (22 Jul 2017) + +Enhancements: + +* [#460][] and [#470][]: Support errors produced by `go.uber.org/multierr`. +* [#465][]: Support user-supplied encoders for logger names. + +Bugfixes: + +* [#477][]: Fix a bug that incorrectly truncated deep stacktraces. + +Thanks to @richard-tunein and @pavius for their contributions to this release. + +## v1.4.1 (08 Jun 2017) + +This release fixes two bugs. + +Bugfixes: + +* [#435][]: Support a variety of case conventions when unmarshaling levels. +* [#444][]: Fix a panic in the observer. + +## v1.4.0 (12 May 2017) + +This release adds a few small features and is fully backward-compatible. + +Enhancements: + +* [#424][]: Add a `LineEnding` field to `EncoderConfig`, allowing users to + override the Unix-style default. +* [#425][]: Preserve time zones when logging times. +* [#431][]: Make `zap.AtomicLevel` implement `fmt.Stringer`, which makes a + variety of operations a bit simpler. + +## v1.3.0 (25 Apr 2017) + +This release adds an enhancement to zap's testing helpers as well as the +ability to marshal an AtomicLevel. It is fully backward-compatible. + +Enhancements: + +* [#415][]: Add a substring-filtering helper to zap's observer. This is + particularly useful when testing the `SugaredLogger`. +* [#416][]: Make `AtomicLevel` implement `encoding.TextMarshaler`. + +## v1.2.0 (13 Apr 2017) + +This release adds a gRPC compatibility wrapper. It is fully backward-compatible. + +Enhancements: + +* [#402][]: Add a `zapgrpc` package that wraps zap's Logger and implements + `grpclog.Logger`. + +## v1.1.0 (31 Mar 2017) + +This release fixes two bugs and adds some enhancements to zap's testing helpers. +It is fully backward-compatible. + +Bugfixes: + +* [#385][]: Fix caller path trimming on Windows. +* [#396][]: Fix a panic when attempting to use non-existent directories with + zap's configuration struct. + +Enhancements: + +* [#386][]: Add filtering helpers to zaptest's observing logger. + +Thanks to @moitias for contributing to this release. + +## v1.0.0 (14 Mar 2017) + +This is zap's first stable release. All exported APIs are now final, and no +further breaking changes will be made in the 1.x release series. Anyone using a +semver-aware dependency manager should now pin to `^1`. + +Breaking changes: + +* [#366][]: Add byte-oriented APIs to encoders to log UTF-8 encoded text without + casting from `[]byte` to `string`. +* [#364][]: To support buffering outputs, add `Sync` methods to `zapcore.Core`, + `zap.Logger`, and `zap.SugaredLogger`. +* [#371][]: Rename the `testutils` package to `zaptest`, which is less likely to + clash with other testing helpers. + +Bugfixes: + +* [#362][]: Make the ISO8601 time formatters fixed-width, which is friendlier + for tab-separated console output. +* [#369][]: Remove the automatic locks in `zapcore.NewCore`, which allows zap to + work with concurrency-safe `WriteSyncer` implementations. +* [#347][]: Stop reporting errors when trying to `fsync` standard out on Linux + systems. +* [#373][]: Report the correct caller from zap's standard library + interoperability wrappers. + +Enhancements: + +* [#348][]: Add a registry allowing third-party encodings to work with zap's + built-in `Config`. +* [#327][]: Make the representation of logger callers configurable (like times, + levels, and durations). +* [#376][]: Allow third-party encoders to use their own buffer pools, which + removes the last performance advantage that zap's encoders have over plugins. +* [#346][]: Add `CombineWriteSyncers`, a convenience function to tee multiple + `WriteSyncer`s and lock the result. +* [#365][]: Make zap's stacktraces compatible with mid-stack inlining (coming in + Go 1.9). +* [#372][]: Export zap's observing logger as `zaptest/observer`. This makes it + easier for particularly punctilious users to unit test their application's + logging. + +Thanks to @suyash, @htrendev, @flisky, @Ulexus, and @skipor for their +contributions to this release. + +## v1.0.0-rc.3 (7 Mar 2017) + +This is the third release candidate for zap's stable release. There are no +breaking changes. + +Bugfixes: + +* [#339][]: Byte slices passed to `zap.Any` are now correctly treated as binary blobs + rather than `[]uint8`. + +Enhancements: + +* [#307][]: Users can opt into colored output for log levels. +* [#353][]: In addition to hijacking the output of the standard library's + package-global logging functions, users can now construct a zap-backed + `log.Logger` instance. +* [#311][]: Frames from common runtime functions and some of zap's internal + machinery are now omitted from stacktraces. + +Thanks to @ansel1 and @suyash for their contributions to this release. + +## v1.0.0-rc.2 (21 Feb 2017) + +This is the second release candidate for zap's stable release. It includes two +breaking changes. + +Breaking changes: + +* [#316][]: Zap's global loggers are now fully concurrency-safe + (previously, users had to ensure that `ReplaceGlobals` was called before the + loggers were in use). However, they must now be accessed via the `L()` and + `S()` functions. Users can update their projects with + + ``` + gofmt -r "zap.L -> zap.L()" -w . + gofmt -r "zap.S -> zap.S()" -w . + ``` +* [#309][] and [#317][]: RC1 was mistakenly shipped with invalid + JSON and YAML struct tags on all config structs. This release fixes the tags + and adds static analysis to prevent similar bugs in the future. + +Bugfixes: + +* [#321][]: Redirecting the standard library's `log` output now + correctly reports the logger's caller. + +Enhancements: + +* [#325][] and [#333][]: Zap now transparently supports non-standard, rich + errors like those produced by `github.com/pkg/errors`. +* [#326][]: Though `New(nil)` continues to return a no-op logger, `NewNop()` is + now preferred. Users can update their projects with `gofmt -r 'zap.New(nil) -> + zap.NewNop()' -w .`. +* [#300][]: Incorrectly importing zap as `github.com/uber-go/zap` now returns a + more informative error. + +Thanks to @skipor and @chapsuk for their contributions to this release. + +## v1.0.0-rc.1 (14 Feb 2017) + +This is the first release candidate for zap's stable release. There are multiple +breaking changes and improvements from the pre-release version. Most notably: + +* **Zap's import path is now "go.uber.org/zap"** — all users will + need to update their code. +* User-facing types and functions remain in the `zap` package. Code relevant + largely to extension authors is now in the `zapcore` package. +* The `zapcore.Core` type makes it easy for third-party packages to use zap's + internals but provide a different user-facing API. +* `Logger` is now a concrete type instead of an interface. +* A less verbose (though slower) logging API is included by default. +* Package-global loggers `L` and `S` are included. +* A human-friendly console encoder is included. +* A declarative config struct allows common logger configurations to be managed + as configuration instead of code. +* Sampling is more accurate, and doesn't depend on the standard library's shared + timer heap. + +## v0.1.0-beta.1 (6 Feb 2017) + +This is a minor version, tagged to allow users to pin to the pre-1.0 APIs and +upgrade at their leisure. Since this is the first tagged release, there are no +backward compatibility concerns and all functionality is new. + +Early zap adopters should pin to the 0.1.x minor version until they're ready to +upgrade to the upcoming stable release. + +[#316]: https://github.com/uber-go/zap/pull/316 +[#309]: https://github.com/uber-go/zap/pull/309 +[#317]: https://github.com/uber-go/zap/pull/317 +[#321]: https://github.com/uber-go/zap/pull/321 +[#325]: https://github.com/uber-go/zap/pull/325 +[#333]: https://github.com/uber-go/zap/pull/333 +[#326]: https://github.com/uber-go/zap/pull/326 +[#300]: https://github.com/uber-go/zap/pull/300 +[#339]: https://github.com/uber-go/zap/pull/339 +[#307]: https://github.com/uber-go/zap/pull/307 +[#353]: https://github.com/uber-go/zap/pull/353 +[#311]: https://github.com/uber-go/zap/pull/311 +[#366]: https://github.com/uber-go/zap/pull/366 +[#364]: https://github.com/uber-go/zap/pull/364 +[#371]: https://github.com/uber-go/zap/pull/371 +[#362]: https://github.com/uber-go/zap/pull/362 +[#369]: https://github.com/uber-go/zap/pull/369 +[#347]: https://github.com/uber-go/zap/pull/347 +[#373]: https://github.com/uber-go/zap/pull/373 +[#348]: https://github.com/uber-go/zap/pull/348 +[#327]: https://github.com/uber-go/zap/pull/327 +[#376]: https://github.com/uber-go/zap/pull/376 +[#346]: https://github.com/uber-go/zap/pull/346 +[#365]: https://github.com/uber-go/zap/pull/365 +[#372]: https://github.com/uber-go/zap/pull/372 +[#385]: https://github.com/uber-go/zap/pull/385 +[#396]: https://github.com/uber-go/zap/pull/396 +[#386]: https://github.com/uber-go/zap/pull/386 +[#402]: https://github.com/uber-go/zap/pull/402 +[#415]: https://github.com/uber-go/zap/pull/415 +[#416]: https://github.com/uber-go/zap/pull/416 +[#424]: https://github.com/uber-go/zap/pull/424 +[#425]: https://github.com/uber-go/zap/pull/425 +[#431]: https://github.com/uber-go/zap/pull/431 +[#435]: https://github.com/uber-go/zap/pull/435 +[#444]: https://github.com/uber-go/zap/pull/444 +[#477]: https://github.com/uber-go/zap/pull/477 +[#465]: https://github.com/uber-go/zap/pull/465 +[#460]: https://github.com/uber-go/zap/pull/460 +[#470]: https://github.com/uber-go/zap/pull/470 +[#487]: https://github.com/uber-go/zap/pull/487 +[#490]: https://github.com/uber-go/zap/pull/490 +[#491]: https://github.com/uber-go/zap/pull/491 +[#504]: https://github.com/uber-go/zap/pull/504 +[#508]: https://github.com/uber-go/zap/pull/508 +[#518]: https://github.com/uber-go/zap/pull/518 +[#577]: https://github.com/uber-go/zap/pull/577 +[#574]: https://github.com/uber-go/zap/pull/574 diff --git a/vendor/go.uber.org/zap/CODE_OF_CONDUCT.md b/vendor/go.uber.org/zap/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..e327d9aa5 --- /dev/null +++ b/vendor/go.uber.org/zap/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual +identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. Representation of a +project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at oss-conduct@uber.com. The project +team will review and investigate all complaints, and will respond in a way +that it deems appropriate to the circumstances. The project team is obligated +to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +[http://contributor-covenant.org/version/1/4][version]. + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/go.uber.org/zap/CONTRIBUTING.md b/vendor/go.uber.org/zap/CONTRIBUTING.md new file mode 100644 index 000000000..9454bbaf0 --- /dev/null +++ b/vendor/go.uber.org/zap/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing + +We'd love your help making zap the very best structured logging library in Go! + +If you'd like to add new exported APIs, please [open an issue][open-issue] +describing your proposal — discussing API changes ahead of time makes +pull request review much smoother. In your issue, pull request, and any other +communications, please remember to treat your fellow contributors with +respect! We take our [code of conduct](CODE_OF_CONDUCT.md) seriously. + +Note that you'll need to sign [Uber's Contributor License Agreement][cla] +before we can accept any of your contributions. If necessary, a bot will remind +you to accept the CLA when you open your pull request. + +## Setup + +[Fork][fork], then clone the repository: + +``` +mkdir -p $GOPATH/src/go.uber.org +cd $GOPATH/src/go.uber.org +git clone git@github.com:your_github_username/zap.git +cd zap +git remote add upstream https://github.com/uber-go/zap.git +git fetch upstream +``` + +Install zap's dependencies: + +``` +make dependencies +``` + +Make sure that the tests and the linters pass: + +``` +make test +make lint +``` + +If you're not using the minor version of Go specified in the Makefile's +`LINTABLE_MINOR_VERSIONS` variable, `make lint` doesn't do anything. This is +fine, but it means that you'll only discover lint failures after you open your +pull request. + +## Making Changes + +Start by creating a new branch for your changes: + +``` +cd $GOPATH/src/go.uber.org/zap +git checkout master +git fetch upstream +git rebase upstream/master +git checkout -b cool_new_feature +``` + +Make your changes, then ensure that `make lint` and `make test` still pass. If +you're satisfied with your changes, push them to your fork. + +``` +git push origin cool_new_feature +``` + +Then use the GitHub UI to open a pull request. + +At this point, you're waiting on us to review your changes. We *try* to respond +to issues and pull requests within a few business days, and we may suggest some +improvements or alternatives. Once your changes are approved, one of the +project maintainers will merge them. + +We're much more likely to approve your changes if you: + +* Add tests for new functionality. +* Write a [good commit message][commit-message]. +* Maintain backward compatibility. + +[fork]: https://github.com/uber-go/zap/fork +[open-issue]: https://github.com/uber-go/zap/issues/new +[cla]: https://cla-assistant.io/uber-go/zap +[commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html diff --git a/vendor/go.uber.org/zap/FAQ.md b/vendor/go.uber.org/zap/FAQ.md new file mode 100644 index 000000000..90ddfe17a --- /dev/null +++ b/vendor/go.uber.org/zap/FAQ.md @@ -0,0 +1,154 @@ +# Frequently Asked Questions + +## Design + +### Why spend so much effort on logger performance? + +Of course, most applications won't notice the impact of a slow logger: they +already take tens or hundreds of milliseconds for each operation, so an extra +millisecond doesn't matter. + +On the other hand, why *not* make structured logging fast? The `SugaredLogger` +isn't any harder to use than other logging packages, and the `Logger` makes +structured logging possible in performance-sensitive contexts. Across a fleet +of Go microservices, making each application even slightly more efficient adds +up quickly. + +### Why aren't `Logger` and `SugaredLogger` interfaces? + +Unlike the familiar `io.Writer` and `http.Handler`, `Logger` and +`SugaredLogger` interfaces would include *many* methods. As [Rob Pike points +out][go-proverbs], "The bigger the interface, the weaker the abstraction." +Interfaces are also rigid — *any* change requires releasing a new major +version, since it breaks all third-party implementations. + +Making the `Logger` and `SugaredLogger` concrete types doesn't sacrifice much +abstraction, and it lets us add methods without introducing breaking changes. +Your applications should define and depend upon an interface that includes +just the methods you use. + +### Why sample application logs? + +Applications often experience runs of errors, either because of a bug or +because of a misbehaving user. Logging errors is usually a good idea, but it +can easily make this bad situation worse: not only is your application coping +with a flood of errors, it's also spending extra CPU cycles and I/O logging +those errors. Since writes are typically serialized, logging limits throughput +when you need it most. + +Sampling fixes this problem by dropping repetitive log entries. Under normal +conditions, your application writes out every entry. When similar entries are +logged hundreds or thousands of times each second, though, zap begins dropping +duplicates to preserve throughput. + +### Why do the structured logging APIs take a message in addition to fields? + +Subjectively, we find it helpful to accompany structured context with a brief +description. This isn't critical during development, but it makes debugging +and operating unfamiliar systems much easier. + +More concretely, zap's sampling algorithm uses the message to identify +duplicate entries. In our experience, this is a practical middle ground +between random sampling (which often drops the exact entry that you need while +debugging) and hashing the complete entry (which is prohibitively expensive). + +### Why include package-global loggers? + +Since so many other logging packages include a global logger, many +applications aren't designed to accept loggers as explicit parameters. +Changing function signatures is often a breaking change, so zap includes +global loggers to simplify migration. + +Avoid them where possible. + +### Why include dedicated Panic and Fatal log levels? + +In general, application code should handle errors gracefully instead of using +`panic` or `os.Exit`. However, every rule has exceptions, and it's common to +crash when an error is truly unrecoverable. To avoid losing any information +— especially the reason for the crash — the logger must flush any +buffered entries before the process exits. + +Zap makes this easy by offering `Panic` and `Fatal` logging methods that +automatically flush before exiting. Of course, this doesn't guarantee that +logs will never be lost, but it eliminates a common error. + +See the discussion in uber-go/zap#207 for more details. + +### What's `DPanic`? + +`DPanic` stands for "panic in development." In development, it logs at +`PanicLevel`; otherwise, it logs at `ErrorLevel`. `DPanic` makes it easier to +catch errors that are theoretically possible, but shouldn't actually happen, +*without* crashing in production. + +If you've ever written code like this, you need `DPanic`: + +```go +if err != nil { + panic(fmt.Sprintf("shouldn't ever get here: %v", err)) +} +``` + +## Installation + +### What does the error `expects import "go.uber.org/zap"` mean? + +Either zap was installed incorrectly or you're referencing the wrong package +name in your code. + +Zap's source code happens to be hosted on GitHub, but the [import +path][import-path] is `go.uber.org/zap`. This gives us, the project +maintainers, the freedom to move the source code if necessary. However, it +means that you need to take a little care when installing and using the +package. + +If you follow two simple rules, everything should work: install zap with `go +get -u go.uber.org/zap`, and always import it in your code with `import +"go.uber.org/zap"`. Your code shouldn't contain *any* references to +`github.com/uber-go/zap`. + +## Usage + +### Does zap support log rotation? + +Zap doesn't natively support rotating log files, since we prefer to leave this +to an external program like `logrotate`. + +However, it's easy to integrate a log rotation package like +[`gopkg.in/natefinch/lumberjack.v2`][lumberjack] as a `zapcore.WriteSyncer`. + +```go +// lumberjack.Logger is already safe for concurrent use, so we don't need to +// lock it. +w := zapcore.AddSync(&lumberjack.Logger{ + Filename: "/var/log/myapp/foo.log", + MaxSize: 500, // megabytes + MaxBackups: 3, + MaxAge: 28, // days +}) +core := zapcore.NewCore( + zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), + w, + zap.InfoLevel, +) +logger := zap.New(core) +``` + +## Extensions + +We'd love to support every logging need within zap itself, but we're only +familiar with a handful of log ingestion systems, flag-parsing packages, and +the like. Rather than merging code that we can't effectively debug and +support, we'd rather grow an ecosystem of zap extensions. + +We're aware of the following extensions, but haven't used them ourselves: + +| Package | Integration | +| --- | --- | +| `github.com/tchap/zapext` | Sentry, syslog | +| `github.com/fgrosse/zaptest` | Ginkgo | + +[go-proverbs]: https://go-proverbs.github.io/ +[import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths +[lumberjack]: https://godoc.org/gopkg.in/natefinch/lumberjack.v2 diff --git a/vendor/go.uber.org/zap/LICENSE.txt b/vendor/go.uber.org/zap/LICENSE.txt new file mode 100644 index 000000000..6652bed45 --- /dev/null +++ b/vendor/go.uber.org/zap/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/go.uber.org/zap/Makefile b/vendor/go.uber.org/zap/Makefile new file mode 100644 index 000000000..ef7893b3b --- /dev/null +++ b/vendor/go.uber.org/zap/Makefile @@ -0,0 +1,76 @@ +export GO15VENDOREXPERIMENT=1 + +BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem +PKGS ?= $(shell glide novendor) +# Many Go tools take file globs or directories as arguments instead of packages. +PKG_FILES ?= *.go zapcore benchmarks buffer zapgrpc zaptest zaptest/observer internal/bufferpool internal/exit internal/color internal/ztest + +# The linting tools evolve with each Go version, so run them only on the latest +# stable release. +GO_VERSION := $(shell go version | cut -d " " -f 3) +GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION))) +LINTABLE_MINOR_VERSIONS := 10 +ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),) +SHOULD_LINT := true +endif + + +.PHONY: all +all: lint test + +.PHONY: dependencies +dependencies: + @echo "Installing Glide and locked dependencies..." + glide --version || go get -u -f github.com/Masterminds/glide + glide install + @echo "Installing test dependencies..." + go install ./vendor/github.com/axw/gocov/gocov + go install ./vendor/github.com/mattn/goveralls +ifdef SHOULD_LINT + @echo "Installing golint..." + go install ./vendor/github.com/golang/lint/golint +else + @echo "Not installing golint, since we don't expect to lint on" $(GO_VERSION) +endif + +# Disable printf-like invocation checking due to testify.assert.Error() +VET_RULES := -printf=false + +.PHONY: lint +lint: +ifdef SHOULD_LINT + @rm -rf lint.log + @echo "Checking formatting..." + @gofmt -d -s $(PKG_FILES) 2>&1 | tee lint.log + @echo "Installing test dependencies for vet..." + @go test -i $(PKGS) + @echo "Checking vet..." + @$(foreach dir,$(PKG_FILES),go tool vet $(VET_RULES) $(dir) 2>&1 | tee -a lint.log;) + @echo "Checking lint..." + @$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;) + @echo "Checking for unresolved FIXMEs..." + @git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log + @echo "Checking for license headers..." + @./check_license.sh | tee -a lint.log + @[ ! -s lint.log ] +else + @echo "Skipping linters on" $(GO_VERSION) +endif + +.PHONY: test +test: + go test -race $(PKGS) + +.PHONY: cover +cover: + ./scripts/cover.sh $(PKGS) + +.PHONY: bench +BENCH ?= . +bench: + @$(foreach pkg,$(PKGS),go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) $(pkg);) + +.PHONY: updatereadme +updatereadme: + rm -f README.md + cat .readme.tmpl | go run internal/readme/readme.go > README.md diff --git a/vendor/go.uber.org/zap/README.md b/vendor/go.uber.org/zap/README.md new file mode 100644 index 000000000..4b2bb9d8b --- /dev/null +++ b/vendor/go.uber.org/zap/README.md @@ -0,0 +1,136 @@ +# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +Blazing fast, structured, leveled logging in Go. + +## Installation + +`go get -u go.uber.org/zap` + +Note that zap only supports the two most recent minor versions of Go. + +## Quick Start + +In contexts where performance is nice, but not critical, use the +`SugaredLogger`. It's 4-10x faster than than other structured logging +packages and includes both structured and `printf`-style APIs. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() // flushes buffer, if any +sugar := logger.Sugar() +sugar.Infow("failed to fetch URL", + // Structured context as loosely typed key-value pairs. + "url", url, + "attempt", 3, + "backoff", time.Second, +) +sugar.Infof("Failed to fetch URL: %s", url) +``` + +When performance and type safety are critical, use the `Logger`. It's even +faster than the `SugaredLogger` and allocates far less, but it only supports +structured logging. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() +logger.Info("failed to fetch URL", + // Structured context as strongly typed Field values. + zap.String("url", url), + zap.Int("attempt", 3), + zap.Duration("backoff", time.Second), +) +``` + +See the [documentation][doc] and [FAQ](FAQ.md) for more details. + +## Performance + +For applications that log in the hot path, reflection-based serialization and +string formatting are prohibitively expensive — they're CPU-intensive +and make many small allocations. Put differently, using `encoding/json` and +`fmt.Fprintf` to log tons of `interface{}`s makes your application slow. + +Zap takes a different approach. It includes a reflection-free, zero-allocation +JSON encoder, and the base `Logger` strives to avoid serialization overhead +and allocations wherever possible. By building the high-level `SugaredLogger` +on that foundation, zap lets users *choose* when they need to count every +allocation and when they'd prefer a more familiar, loosely typed API. + +As measured by its own [benchmarking suite][], not only is zap more performant +than comparable structured logging packages — it's also faster than the +standard library. Like all benchmarks, take these with a grain of salt.[1](#footnote-versions) + +Log a message and 10 fields: + +| Package | Time | Objects Allocated | +| :--- | :---: | :---: | +| :zap: zap | 3131 ns/op | 5 allocs/op | +| :zap: zap (sugared) | 4173 ns/op | 21 allocs/op | +| zerolog | 16154 ns/op | 90 allocs/op | +| lion | 16341 ns/op | 111 allocs/op | +| go-kit | 17049 ns/op | 126 allocs/op | +| logrus | 23662 ns/op | 142 allocs/op | +| log15 | 36351 ns/op | 149 allocs/op | +| apex/log | 42530 ns/op | 126 allocs/op | + +Log a message with a logger that already has 10 fields of context: + +| Package | Time | Objects Allocated | +| :--- | :---: | :---: | +| :zap: zap | 380 ns/op | 0 allocs/op | +| :zap: zap (sugared) | 564 ns/op | 2 allocs/op | +| zerolog | 321 ns/op | 0 allocs/op | +| lion | 7092 ns/op | 39 allocs/op | +| go-kit | 20226 ns/op | 115 allocs/op | +| logrus | 22312 ns/op | 130 allocs/op | +| log15 | 28788 ns/op | 79 allocs/op | +| apex/log | 42063 ns/op | 115 allocs/op | + +Log a static string, without any context or `printf`-style templating: + +| Package | Time | Objects Allocated | +| :--- | :---: | :---: | +| :zap: zap | 361 ns/op | 0 allocs/op | +| :zap: zap (sugared) | 534 ns/op | 2 allocs/op | +| zerolog | 323 ns/op | 0 allocs/op | +| standard library | 575 ns/op | 2 allocs/op | +| go-kit | 922 ns/op | 13 allocs/op | +| lion | 1413 ns/op | 10 allocs/op | +| logrus | 2291 ns/op | 27 allocs/op | +| apex/log | 3690 ns/op | 11 allocs/op | +| log15 | 5954 ns/op | 26 allocs/op | + +## Development Status: Stable + +All APIs are finalized, and no breaking changes will be made in the 1.x series +of releases. Users of semver-aware dependency management systems should pin +zap to `^1`. + +## Contributing + +We encourage and support an active, healthy community of contributors — +including you! Details are in the [contribution guide](CONTRIBUTING.md) and +the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on +issues and pull requests, but you can also report any negative conduct to +oss-conduct@uber.com. That email list is a private, safe space; even the zap +maintainers don't have access, so don't hesitate to hold us to a high +standard. + +
+ +Released under the [MIT License](LICENSE.txt). + +1 In particular, keep in mind that we may be +benchmarking against slightly older versions of other packages. Versions are +pinned in zap's [glide.lock][] file. [↩](#anchor-versions) + +[doc-img]: https://godoc.org/go.uber.org/zap?status.svg +[doc]: https://godoc.org/go.uber.org/zap +[ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master +[ci]: https://travis-ci.org/uber-go/zap +[cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/zap +[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks +[glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock diff --git a/vendor/go.uber.org/zap/array.go b/vendor/go.uber.org/zap/array.go new file mode 100644 index 000000000..5be3704a3 --- /dev/null +++ b/vendor/go.uber.org/zap/array.go @@ -0,0 +1,320 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "time" + + "go.uber.org/zap/zapcore" +) + +// Array constructs a field with the given key and ArrayMarshaler. It provides +// a flexible, but still type-safe and efficient, way to add array-like types +// to the logging context. The struct's MarshalLogArray method is called lazily. +func Array(key string, val zapcore.ArrayMarshaler) Field { + return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val} +} + +// Bools constructs a field that carries a slice of bools. +func Bools(key string, bs []bool) Field { + return Array(key, bools(bs)) +} + +// ByteStrings constructs a field that carries a slice of []byte, each of which +// must be UTF-8 encoded text. +func ByteStrings(key string, bss [][]byte) Field { + return Array(key, byteStringsArray(bss)) +} + +// Complex128s constructs a field that carries a slice of complex numbers. +func Complex128s(key string, nums []complex128) Field { + return Array(key, complex128s(nums)) +} + +// Complex64s constructs a field that carries a slice of complex numbers. +func Complex64s(key string, nums []complex64) Field { + return Array(key, complex64s(nums)) +} + +// Durations constructs a field that carries a slice of time.Durations. +func Durations(key string, ds []time.Duration) Field { + return Array(key, durations(ds)) +} + +// Float64s constructs a field that carries a slice of floats. +func Float64s(key string, nums []float64) Field { + return Array(key, float64s(nums)) +} + +// Float32s constructs a field that carries a slice of floats. +func Float32s(key string, nums []float32) Field { + return Array(key, float32s(nums)) +} + +// Ints constructs a field that carries a slice of integers. +func Ints(key string, nums []int) Field { + return Array(key, ints(nums)) +} + +// Int64s constructs a field that carries a slice of integers. +func Int64s(key string, nums []int64) Field { + return Array(key, int64s(nums)) +} + +// Int32s constructs a field that carries a slice of integers. +func Int32s(key string, nums []int32) Field { + return Array(key, int32s(nums)) +} + +// Int16s constructs a field that carries a slice of integers. +func Int16s(key string, nums []int16) Field { + return Array(key, int16s(nums)) +} + +// Int8s constructs a field that carries a slice of integers. +func Int8s(key string, nums []int8) Field { + return Array(key, int8s(nums)) +} + +// Strings constructs a field that carries a slice of strings. +func Strings(key string, ss []string) Field { + return Array(key, stringArray(ss)) +} + +// Times constructs a field that carries a slice of time.Times. +func Times(key string, ts []time.Time) Field { + return Array(key, times(ts)) +} + +// Uints constructs a field that carries a slice of unsigned integers. +func Uints(key string, nums []uint) Field { + return Array(key, uints(nums)) +} + +// Uint64s constructs a field that carries a slice of unsigned integers. +func Uint64s(key string, nums []uint64) Field { + return Array(key, uint64s(nums)) +} + +// Uint32s constructs a field that carries a slice of unsigned integers. +func Uint32s(key string, nums []uint32) Field { + return Array(key, uint32s(nums)) +} + +// Uint16s constructs a field that carries a slice of unsigned integers. +func Uint16s(key string, nums []uint16) Field { + return Array(key, uint16s(nums)) +} + +// Uint8s constructs a field that carries a slice of unsigned integers. +func Uint8s(key string, nums []uint8) Field { + return Array(key, uint8s(nums)) +} + +// Uintptrs constructs a field that carries a slice of pointer addresses. +func Uintptrs(key string, us []uintptr) Field { + return Array(key, uintptrs(us)) +} + +// Errors constructs a field that carries a slice of errors. +func Errors(key string, errs []error) Field { + return Array(key, errArray(errs)) +} + +type bools []bool + +func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range bs { + arr.AppendBool(bs[i]) + } + return nil +} + +type byteStringsArray [][]byte + +func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range bss { + arr.AppendByteString(bss[i]) + } + return nil +} + +type complex128s []complex128 + +func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendComplex128(nums[i]) + } + return nil +} + +type complex64s []complex64 + +func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendComplex64(nums[i]) + } + return nil +} + +type durations []time.Duration + +func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ds { + arr.AppendDuration(ds[i]) + } + return nil +} + +type float64s []float64 + +func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendFloat64(nums[i]) + } + return nil +} + +type float32s []float32 + +func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendFloat32(nums[i]) + } + return nil +} + +type ints []int + +func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt(nums[i]) + } + return nil +} + +type int64s []int64 + +func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt64(nums[i]) + } + return nil +} + +type int32s []int32 + +func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt32(nums[i]) + } + return nil +} + +type int16s []int16 + +func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt16(nums[i]) + } + return nil +} + +type int8s []int8 + +func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt8(nums[i]) + } + return nil +} + +type stringArray []string + +func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ss { + arr.AppendString(ss[i]) + } + return nil +} + +type times []time.Time + +func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ts { + arr.AppendTime(ts[i]) + } + return nil +} + +type uints []uint + +func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint(nums[i]) + } + return nil +} + +type uint64s []uint64 + +func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint64(nums[i]) + } + return nil +} + +type uint32s []uint32 + +func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint32(nums[i]) + } + return nil +} + +type uint16s []uint16 + +func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint16(nums[i]) + } + return nil +} + +type uint8s []uint8 + +func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint8(nums[i]) + } + return nil +} + +type uintptrs []uintptr + +func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUintptr(nums[i]) + } + return nil +} diff --git a/vendor/go.uber.org/zap/buffer/buffer.go b/vendor/go.uber.org/zap/buffer/buffer.go new file mode 100644 index 000000000..d15f7fdb3 --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/buffer.go @@ -0,0 +1,106 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package buffer provides a thin wrapper around a byte slice. Unlike the +// standard library's bytes.Buffer, it supports a portion of the strconv +// package's zero-allocation formatters. +package buffer // import "go.uber.org/zap/buffer" + +import "strconv" + +const _size = 1024 // by default, create 1 KiB buffers + +// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so +// the only way to construct one is via a Pool. +type Buffer struct { + bs []byte + pool Pool +} + +// AppendByte writes a single byte to the Buffer. +func (b *Buffer) AppendByte(v byte) { + b.bs = append(b.bs, v) +} + +// AppendString writes a string to the Buffer. +func (b *Buffer) AppendString(s string) { + b.bs = append(b.bs, s...) +} + +// AppendInt appends an integer to the underlying buffer (assuming base 10). +func (b *Buffer) AppendInt(i int64) { + b.bs = strconv.AppendInt(b.bs, i, 10) +} + +// AppendUint appends an unsigned integer to the underlying buffer (assuming +// base 10). +func (b *Buffer) AppendUint(i uint64) { + b.bs = strconv.AppendUint(b.bs, i, 10) +} + +// AppendBool appends a bool to the underlying buffer. +func (b *Buffer) AppendBool(v bool) { + b.bs = strconv.AppendBool(b.bs, v) +} + +// AppendFloat appends a float to the underlying buffer. It doesn't quote NaN +// or +/- Inf. +func (b *Buffer) AppendFloat(f float64, bitSize int) { + b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize) +} + +// Len returns the length of the underlying byte slice. +func (b *Buffer) Len() int { + return len(b.bs) +} + +// Cap returns the capacity of the underlying byte slice. +func (b *Buffer) Cap() int { + return cap(b.bs) +} + +// Bytes returns a mutable reference to the underlying byte slice. +func (b *Buffer) Bytes() []byte { + return b.bs +} + +// String returns a string copy of the underlying byte slice. +func (b *Buffer) String() string { + return string(b.bs) +} + +// Reset resets the underlying byte slice. Subsequent writes re-use the slice's +// backing array. +func (b *Buffer) Reset() { + b.bs = b.bs[:0] +} + +// Write implements io.Writer. +func (b *Buffer) Write(bs []byte) (int, error) { + b.bs = append(b.bs, bs...) + return len(bs), nil +} + +// Free returns the Buffer to its Pool. +// +// Callers must not retain references to the Buffer after calling Free. +func (b *Buffer) Free() { + b.pool.put(b) +} diff --git a/vendor/go.uber.org/zap/buffer/pool.go b/vendor/go.uber.org/zap/buffer/pool.go new file mode 100644 index 000000000..8fb3e202c --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/pool.go @@ -0,0 +1,49 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import "sync" + +// A Pool is a type-safe wrapper around a sync.Pool. +type Pool struct { + p *sync.Pool +} + +// NewPool constructs a new Pool. +func NewPool() Pool { + return Pool{p: &sync.Pool{ + New: func() interface{} { + return &Buffer{bs: make([]byte, 0, _size)} + }, + }} +} + +// Get retrieves a Buffer from the pool, creating one if necessary. +func (p Pool) Get() *Buffer { + buf := p.p.Get().(*Buffer) + buf.Reset() + buf.pool = p + return buf +} + +func (p Pool) put(buf *Buffer) { + p.p.Put(buf) +} diff --git a/vendor/go.uber.org/zap/check_license.sh b/vendor/go.uber.org/zap/check_license.sh new file mode 100755 index 000000000..345ac8b89 --- /dev/null +++ b/vendor/go.uber.org/zap/check_license.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e + +ERROR_COUNT=0 +while read -r file +do + case "$(head -1 "${file}")" in + *"Copyright (c) "*" Uber Technologies, Inc.") + # everything's cool + ;; + *) + echo "$file is missing license header." + (( ERROR_COUNT++ )) + ;; + esac +done < <(git ls-files "*\.go") + +exit $ERROR_COUNT diff --git a/vendor/go.uber.org/zap/config.go b/vendor/go.uber.org/zap/config.go new file mode 100644 index 000000000..dae130303 --- /dev/null +++ b/vendor/go.uber.org/zap/config.go @@ -0,0 +1,243 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sort" + "time" + + "go.uber.org/zap/zapcore" +) + +// SamplingConfig sets a sampling strategy for the logger. Sampling caps the +// global CPU and I/O load that logging puts on your process while attempting +// to preserve a representative subset of your logs. +// +// Values configured here are per-second. See zapcore.NewSampler for details. +type SamplingConfig struct { + Initial int `json:"initial" yaml:"initial"` + Thereafter int `json:"thereafter" yaml:"thereafter"` +} + +// Config offers a declarative way to construct a logger. It doesn't do +// anything that can't be done with New, Options, and the various +// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to +// toggle common options. +// +// Note that Config intentionally supports only the most common options. More +// unusual logging setups (logging to network connections or message queues, +// splitting output between multiple files, etc.) are possible, but require +// direct use of the zapcore package. For sample code, see the package-level +// BasicConfiguration and AdvancedConfiguration examples. +// +// For an example showing runtime log level changes, see the documentation for +// AtomicLevel. +type Config struct { + // Level is the minimum enabled logging level. Note that this is a dynamic + // level, so calling Config.Level.SetLevel will atomically change the log + // level of all loggers descended from this config. + Level AtomicLevel `json:"level" yaml:"level"` + // Development puts the logger in development mode, which changes the + // behavior of DPanicLevel and takes stacktraces more liberally. + Development bool `json:"development" yaml:"development"` + // DisableCaller stops annotating logs with the calling function's file + // name and line number. By default, all logs are annotated. + DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` + // DisableStacktrace completely disables automatic stacktrace capturing. By + // default, stacktraces are captured for WarnLevel and above logs in + // development and ErrorLevel and above in production. + DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` + // Sampling sets a sampling policy. A nil SamplingConfig disables sampling. + Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` + // Encoding sets the logger's encoding. Valid values are "json" and + // "console", as well as any third-party encodings registered via + // RegisterEncoder. + Encoding string `json:"encoding" yaml:"encoding"` + // EncoderConfig sets options for the chosen encoder. See + // zapcore.EncoderConfig for details. + EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` + // OutputPaths is a list of paths to write logging output to. See Open for + // details. + OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` + // ErrorOutputPaths is a list of paths to write internal logger errors to. + // The default is standard error. + // + // Note that this setting only affects internal errors; for sample code that + // sends error-level logs to a different location from info- and debug-level + // logs, see the package-level AdvancedConfiguration example. + ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` + // InitialFields is a collection of fields to add to the root logger. + InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` +} + +// NewProductionEncoderConfig returns an opinionated EncoderConfig for +// production environments. +func NewProductionEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.EpochTimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// NewProductionConfig is a reasonable production logging configuration. +// Logging is enabled at InfoLevel and above. +// +// It uses a JSON encoder, writes to standard error, and enables sampling. +// Stacktraces are automatically included on logs of ErrorLevel and above. +func NewProductionConfig() Config { + return Config{ + Level: NewAtomicLevelAt(InfoLevel), + Development: false, + Sampling: &SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: NewProductionEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for +// development environments. +func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + // Keys can be anything except the empty string. + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// NewDevelopmentConfig is a reasonable development logging configuration. +// Logging is enabled at DebugLevel and above. +// +// It enables development mode (which makes DPanicLevel logs panic), uses a +// console encoder, writes to standard error, and disables sampling. +// Stacktraces are automatically included on logs of WarnLevel and above. +func NewDevelopmentConfig() Config { + return Config{ + Level: NewAtomicLevelAt(DebugLevel), + Development: true, + Encoding: "console", + EncoderConfig: NewDevelopmentEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// Build constructs a logger from the Config and Options. +func (cfg Config) Build(opts ...Option) (*Logger, error) { + enc, err := cfg.buildEncoder() + if err != nil { + return nil, err + } + + sink, errSink, err := cfg.openSinks() + if err != nil { + return nil, err + } + + log := New( + zapcore.NewCore(enc, sink, cfg.Level), + cfg.buildOptions(errSink)..., + ) + if len(opts) > 0 { + log = log.WithOptions(opts...) + } + return log, nil +} + +func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { + opts := []Option{ErrorOutput(errSink)} + + if cfg.Development { + opts = append(opts, Development()) + } + + if !cfg.DisableCaller { + opts = append(opts, AddCaller()) + } + + stackLevel := ErrorLevel + if cfg.Development { + stackLevel = WarnLevel + } + if !cfg.DisableStacktrace { + opts = append(opts, AddStacktrace(stackLevel)) + } + + if cfg.Sampling != nil { + opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter)) + })) + } + + if len(cfg.InitialFields) > 0 { + fs := make([]Field, 0, len(cfg.InitialFields)) + keys := make([]string, 0, len(cfg.InitialFields)) + for k := range cfg.InitialFields { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fs = append(fs, Any(k, cfg.InitialFields[k])) + } + opts = append(opts, Fields(fs...)) + } + + return opts +} + +func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { + sink, closeOut, err := Open(cfg.OutputPaths...) + if err != nil { + return nil, nil, err + } + errSink, _, err := Open(cfg.ErrorOutputPaths...) + if err != nil { + closeOut() + return nil, nil, err + } + return sink, errSink, nil +} + +func (cfg Config) buildEncoder() (zapcore.Encoder, error) { + return newEncoder(cfg.Encoding, cfg.EncoderConfig) +} diff --git a/vendor/go.uber.org/zap/doc.go b/vendor/go.uber.org/zap/doc.go new file mode 100644 index 000000000..3f16a8d45 --- /dev/null +++ b/vendor/go.uber.org/zap/doc.go @@ -0,0 +1,113 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zap provides fast, structured, leveled logging. +// +// For applications that log in the hot path, reflection-based serialization +// and string formatting are prohibitively expensive - they're CPU-intensive +// and make many small allocations. Put differently, using json.Marshal and +// fmt.Fprintf to log tons of interface{} makes your application slow. +// +// Zap takes a different approach. It includes a reflection-free, +// zero-allocation JSON encoder, and the base Logger strives to avoid +// serialization overhead and allocations wherever possible. By building the +// high-level SugaredLogger on that foundation, zap lets users choose when +// they need to count every allocation and when they'd prefer a more familiar, +// loosely typed API. +// +// Choosing a Logger +// +// In contexts where performance is nice, but not critical, use the +// SugaredLogger. It's 4-10x faster than other structured logging packages and +// supports both structured and printf-style logging. Like log15 and go-kit, +// the SugaredLogger's structured logging APIs are loosely typed and accept a +// variadic number of key-value pairs. (For more advanced use cases, they also +// accept strongly typed fields - see the SugaredLogger.With documentation for +// details.) +// sugar := zap.NewExample().Sugar() +// defer sugar.Sync() +// sugar.Infow("failed to fetch URL", +// "url", "http://example.com", +// "attempt", 3, +// "backoff", time.Second, +// ) +// sugar.Printf("failed to fetch URL: %s", "http://example.com") +// +// By default, loggers are unbuffered. However, since zap's low-level APIs +// allow buffering, calling Sync before letting your process exit is a good +// habit. +// +// In the rare contexts where every microsecond and every allocation matter, +// use the Logger. It's even faster than the SugaredLogger and allocates far +// less, but it only supports strongly-typed, structured logging. +// logger := zap.NewExample() +// defer logger.Sync() +// logger.Info("failed to fetch URL", +// zap.String("url", "http://example.com"), +// zap.Int("attempt", 3), +// zap.Duration("backoff", time.Second), +// ) +// +// Choosing between the Logger and SugaredLogger doesn't need to be an +// application-wide decision: converting between the two is simple and +// inexpensive. +// logger := zap.NewExample() +// defer logger.Sync() +// sugar := logger.Sugar() +// plain := sugar.Desugar() +// +// Configuring Zap +// +// The simplest way to build a Logger is to use zap's opinionated presets: +// NewExample, NewProduction, and NewDevelopment. These presets build a logger +// with a single function call: +// logger, err := zap.NewProduction() +// if err != nil { +// log.Fatalf("can't initialize zap logger: %v", err) +// } +// defer logger.Sync() +// +// Presets are fine for small projects, but larger projects and organizations +// naturally require a bit more customization. For most users, zap's Config +// struct strikes the right balance between flexibility and convenience. See +// the package-level BasicConfiguration example for sample code. +// +// More unusual configurations (splitting output between files, sending logs +// to a message queue, etc.) are possible, but require direct use of +// go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration +// example for sample code. +// +// Extending Zap +// +// The zap package itself is a relatively thin wrapper around the interfaces +// in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g., +// BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an +// exception aggregation service, like Sentry or Rollbar) typically requires +// implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core +// interfaces. See the zapcore documentation for details. +// +// Similarly, package authors can use the high-performance Encoder and Core +// implementations in the zapcore package to build their own loggers. +// +// Frequently Asked Questions +// +// An FAQ covering everything from installation errors to design decisions is +// available at https://github.com/uber-go/zap/blob/master/FAQ.md. +package zap // import "go.uber.org/zap" diff --git a/vendor/go.uber.org/zap/encoder.go b/vendor/go.uber.org/zap/encoder.go new file mode 100644 index 000000000..2e9d3c341 --- /dev/null +++ b/vendor/go.uber.org/zap/encoder.go @@ -0,0 +1,75 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "sync" + + "go.uber.org/zap/zapcore" +) + +var ( + errNoEncoderNameSpecified = errors.New("no encoder name specified") + + _encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){ + "console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewConsoleEncoder(encoderConfig), nil + }, + "json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewJSONEncoder(encoderConfig), nil + }, + } + _encoderMutex sync.RWMutex +) + +// RegisterEncoder registers an encoder constructor, which the Config struct +// can then reference. By default, the "json" and "console" encoders are +// registered. +// +// Attempting to register an encoder whose name is already taken returns an +// error. +func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error { + _encoderMutex.Lock() + defer _encoderMutex.Unlock() + if name == "" { + return errNoEncoderNameSpecified + } + if _, ok := _encoderNameToConstructor[name]; ok { + return fmt.Errorf("encoder already registered for name %q", name) + } + _encoderNameToConstructor[name] = constructor + return nil +} + +func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + _encoderMutex.RLock() + defer _encoderMutex.RUnlock() + if name == "" { + return nil, errNoEncoderNameSpecified + } + constructor, ok := _encoderNameToConstructor[name] + if !ok { + return nil, fmt.Errorf("no encoder registered for name %q", name) + } + return constructor(encoderConfig) +} diff --git a/vendor/go.uber.org/zap/error.go b/vendor/go.uber.org/zap/error.go new file mode 100644 index 000000000..65982a51e --- /dev/null +++ b/vendor/go.uber.org/zap/error.go @@ -0,0 +1,80 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync" + + "go.uber.org/zap/zapcore" +) + +var _errArrayElemPool = sync.Pool{New: func() interface{} { + return &errArrayElem{} +}} + +// Error is shorthand for the common idiom NamedError("error", err). +func Error(err error) Field { + return NamedError("error", err) +} + +// NamedError constructs a field that lazily stores err.Error() under the +// provided key. Errors which also implement fmt.Formatter (like those produced +// by github.com/pkg/errors) will also have their verbose representation stored +// under key+"Verbose". If passed a nil error, the field is a no-op. +// +// For the common case in which the key is simply "error", the Error function +// is shorter and less repetitive. +func NamedError(key string, err error) Field { + if err == nil { + return Skip() + } + return Field{Key: key, Type: zapcore.ErrorType, Interface: err} +} + +type errArray []error + +func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + // To represent each error as an object with an "error" attribute and + // potentially an "errorVerbose" attribute, we need to wrap it in a + // type that implements LogObjectMarshaler. To prevent this from + // allocating, pool the wrapper type. + elem := _errArrayElemPool.Get().(*errArrayElem) + elem.error = errs[i] + arr.AppendObject(elem) + elem.error = nil + _errArrayElemPool.Put(elem) + } + return nil +} + +type errArrayElem struct { + error +} + +func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error { + // Re-use the error field's logic, which supports non-standard error types. + Error(e.error).AddTo(enc) + return nil +} diff --git a/vendor/go.uber.org/zap/field.go b/vendor/go.uber.org/zap/field.go new file mode 100644 index 000000000..5130e1347 --- /dev/null +++ b/vendor/go.uber.org/zap/field.go @@ -0,0 +1,310 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "math" + "time" + + "go.uber.org/zap/zapcore" +) + +// Field is an alias for Field. Aliasing this type dramatically +// improves the navigability of this package's API documentation. +type Field = zapcore.Field + +// Skip constructs a no-op field, which is often useful when handling invalid +// inputs in other Field constructors. +func Skip() Field { + return Field{Type: zapcore.SkipType} +} + +// Binary constructs a field that carries an opaque binary blob. +// +// Binary data is serialized in an encoding-appropriate format. For example, +// zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text, +// use ByteString. +func Binary(key string, val []byte) Field { + return Field{Key: key, Type: zapcore.BinaryType, Interface: val} +} + +// Bool constructs a field that carries a bool. +func Bool(key string, val bool) Field { + var ival int64 + if val { + ival = 1 + } + return Field{Key: key, Type: zapcore.BoolType, Integer: ival} +} + +// ByteString constructs a field that carries UTF-8 encoded text as a []byte. +// To log opaque binary blobs (which aren't necessarily valid UTF-8), use +// Binary. +func ByteString(key string, val []byte) Field { + return Field{Key: key, Type: zapcore.ByteStringType, Interface: val} +} + +// Complex128 constructs a field that carries a complex number. Unlike most +// numeric fields, this costs an allocation (to convert the complex128 to +// interface{}). +func Complex128(key string, val complex128) Field { + return Field{Key: key, Type: zapcore.Complex128Type, Interface: val} +} + +// Complex64 constructs a field that carries a complex number. Unlike most +// numeric fields, this costs an allocation (to convert the complex64 to +// interface{}). +func Complex64(key string, val complex64) Field { + return Field{Key: key, Type: zapcore.Complex64Type, Interface: val} +} + +// Float64 constructs a field that carries a float64. The way the +// floating-point value is represented is encoder-dependent, so marshaling is +// necessarily lazy. +func Float64(key string, val float64) Field { + return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))} +} + +// Float32 constructs a field that carries a float32. The way the +// floating-point value is represented is encoder-dependent, so marshaling is +// necessarily lazy. +func Float32(key string, val float32) Field { + return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))} +} + +// Int constructs a field with the given key and value. +func Int(key string, val int) Field { + return Int64(key, int64(val)) +} + +// Int64 constructs a field with the given key and value. +func Int64(key string, val int64) Field { + return Field{Key: key, Type: zapcore.Int64Type, Integer: val} +} + +// Int32 constructs a field with the given key and value. +func Int32(key string, val int32) Field { + return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)} +} + +// Int16 constructs a field with the given key and value. +func Int16(key string, val int16) Field { + return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)} +} + +// Int8 constructs a field with the given key and value. +func Int8(key string, val int8) Field { + return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)} +} + +// String constructs a field with the given key and value. +func String(key string, val string) Field { + return Field{Key: key, Type: zapcore.StringType, String: val} +} + +// Uint constructs a field with the given key and value. +func Uint(key string, val uint) Field { + return Uint64(key, uint64(val)) +} + +// Uint64 constructs a field with the given key and value. +func Uint64(key string, val uint64) Field { + return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)} +} + +// Uint32 constructs a field with the given key and value. +func Uint32(key string, val uint32) Field { + return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)} +} + +// Uint16 constructs a field with the given key and value. +func Uint16(key string, val uint16) Field { + return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)} +} + +// Uint8 constructs a field with the given key and value. +func Uint8(key string, val uint8) Field { + return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)} +} + +// Uintptr constructs a field with the given key and value. +func Uintptr(key string, val uintptr) Field { + return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)} +} + +// Reflect constructs a field with the given key and an arbitrary object. It uses +// an encoding-appropriate, reflection-based function to lazily serialize nearly +// any object into the logging context, but it's relatively slow and +// allocation-heavy. Outside tests, Any is always a better choice. +// +// If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect +// includes the error message in the final log output. +func Reflect(key string, val interface{}) Field { + return Field{Key: key, Type: zapcore.ReflectType, Interface: val} +} + +// Namespace creates a named, isolated scope within the logger's context. All +// subsequent fields will be added to the new namespace. +// +// This helps prevent key collisions when injecting loggers into sub-components +// or third-party libraries. +func Namespace(key string) Field { + return Field{Key: key, Type: zapcore.NamespaceType} +} + +// Stringer constructs a field with the given key and the output of the value's +// String method. The Stringer's String method is called lazily. +func Stringer(key string, val fmt.Stringer) Field { + return Field{Key: key, Type: zapcore.StringerType, Interface: val} +} + +// Time constructs a Field with the given key and value. The encoder +// controls how the time is serialized. +func Time(key string, val time.Time) Field { + return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()} +} + +// Stack constructs a field that stores a stacktrace of the current goroutine +// under provided key. Keep in mind that taking a stacktrace is eager and +// expensive (relatively speaking); this function both makes an allocation and +// takes about two microseconds. +func Stack(key string) Field { + // Returning the stacktrace as a string costs an allocation, but saves us + // from expanding the zapcore.Field union struct to include a byte slice. Since + // taking a stacktrace is already so expensive (~10us), the extra allocation + // is okay. + return String(key, takeStacktrace()) +} + +// Duration constructs a field with the given key and value. The encoder +// controls how the duration is serialized. +func Duration(key string, val time.Duration) Field { + return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)} +} + +// Object constructs a field with the given key and ObjectMarshaler. It +// provides a flexible, but still type-safe and efficient, way to add map- or +// struct-like user-defined types to the logging context. The struct's +// MarshalLogObject method is called lazily. +func Object(key string, val zapcore.ObjectMarshaler) Field { + return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val} +} + +// Any takes a key and an arbitrary value and chooses the best way to represent +// them as a field, falling back to a reflection-based approach only if +// necessary. +// +// Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between +// them. To minimize surprises, []byte values are treated as binary blobs, byte +// values are treated as uint8, and runes are always treated as integers. +func Any(key string, value interface{}) Field { + switch val := value.(type) { + case zapcore.ObjectMarshaler: + return Object(key, val) + case zapcore.ArrayMarshaler: + return Array(key, val) + case bool: + return Bool(key, val) + case []bool: + return Bools(key, val) + case complex128: + return Complex128(key, val) + case []complex128: + return Complex128s(key, val) + case complex64: + return Complex64(key, val) + case []complex64: + return Complex64s(key, val) + case float64: + return Float64(key, val) + case []float64: + return Float64s(key, val) + case float32: + return Float32(key, val) + case []float32: + return Float32s(key, val) + case int: + return Int(key, val) + case []int: + return Ints(key, val) + case int64: + return Int64(key, val) + case []int64: + return Int64s(key, val) + case int32: + return Int32(key, val) + case []int32: + return Int32s(key, val) + case int16: + return Int16(key, val) + case []int16: + return Int16s(key, val) + case int8: + return Int8(key, val) + case []int8: + return Int8s(key, val) + case string: + return String(key, val) + case []string: + return Strings(key, val) + case uint: + return Uint(key, val) + case []uint: + return Uints(key, val) + case uint64: + return Uint64(key, val) + case []uint64: + return Uint64s(key, val) + case uint32: + return Uint32(key, val) + case []uint32: + return Uint32s(key, val) + case uint16: + return Uint16(key, val) + case []uint16: + return Uint16s(key, val) + case uint8: + return Uint8(key, val) + case []byte: + return Binary(key, val) + case uintptr: + return Uintptr(key, val) + case []uintptr: + return Uintptrs(key, val) + case time.Time: + return Time(key, val) + case []time.Time: + return Times(key, val) + case time.Duration: + return Duration(key, val) + case []time.Duration: + return Durations(key, val) + case error: + return NamedError(key, val) + case []error: + return Errors(key, val) + case fmt.Stringer: + return Stringer(key, val) + default: + return Reflect(key, val) + } +} diff --git a/vendor/go.uber.org/zap/flag.go b/vendor/go.uber.org/zap/flag.go new file mode 100644 index 000000000..131287507 --- /dev/null +++ b/vendor/go.uber.org/zap/flag.go @@ -0,0 +1,39 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "flag" + + "go.uber.org/zap/zapcore" +) + +// LevelFlag uses the standard library's flag.Var to declare a global flag +// with the specified name, default, and usage guidance. The returned value is +// a pointer to the value of the flag. +// +// If you don't want to use the flag package's global state, you can use any +// non-nil *Level as a flag.Value with your own *flag.FlagSet. +func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level { + lvl := defaultLevel + flag.Var(&lvl, name, usage) + return &lvl +} diff --git a/vendor/go.uber.org/zap/glide.lock b/vendor/go.uber.org/zap/glide.lock new file mode 100644 index 000000000..881b462c0 --- /dev/null +++ b/vendor/go.uber.org/zap/glide.lock @@ -0,0 +1,76 @@ +hash: f073ba522c06c88ea3075bde32a8aaf0969a840a66cab6318a0897d141ffee92 +updated: 2017-07-22T18:06:49.598185334-07:00 +imports: +- name: go.uber.org/atomic + version: 4e336646b2ef9fc6e47be8e21594178f98e5ebcf +- name: go.uber.org/multierr + version: 3c4937480c32f4c13a875a1829af76c98ca3d40a +testImports: +- name: github.com/apex/log + version: d9b960447bfa720077b2da653cc79e533455b499 + subpackages: + - handlers/json +- name: github.com/axw/gocov + version: 3a69a0d2a4ef1f263e2d92b041a69593d6964fe8 + subpackages: + - gocov +- name: github.com/davecgh/go-spew + version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + subpackages: + - spew +- name: github.com/fatih/color + version: 62e9147c64a1ed519147b62a56a14e83e2be02c1 +- name: github.com/go-kit/kit + version: e10f5bf035be9af21fd5b2fb4469d5716c6ab07d + subpackages: + - log +- name: github.com/go-logfmt/logfmt + version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 +- name: github.com/go-stack/stack + version: 54be5f394ed2c3e19dac9134a40a95ba5a017f7b +- name: github.com/golang/lint + version: c5fb716d6688a859aae56d26d3e6070808df29f7 + subpackages: + - golint +- name: github.com/kr/logfmt + version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 +- name: github.com/mattn/go-colorable + version: 3fa8c76f9daed4067e4a806fb7e4dc86455c6d6a +- name: github.com/mattn/go-isatty + version: fc9e8d8ef48496124e79ae0df75490096eccf6fe +- name: github.com/mattn/goveralls + version: 6efce81852ad1b7567c17ad71b03aeccc9dd9ae0 +- name: github.com/pborman/uuid + version: e790cca94e6cc75c7064b1332e63811d4aae1a53 +- name: github.com/pkg/errors + version: 645ef00459ed84a119197bfb8d8205042c6df63d +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/rs/zerolog + version: eed4c2b94d945e0b2456ad6aa518a443986b5f22 +- name: github.com/satori/go.uuid + version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b +- name: github.com/sirupsen/logrus + version: 7dd06bf38e1e13df288d471a57d5adbac106be9e +- name: github.com/stretchr/testify + version: f6abca593680b2315d2075e0f5e2a9751e3f431a + subpackages: + - assert + - require +- name: go.pedge.io/lion + version: 87958e8713f1fa138d993087133b97e976642159 +- name: golang.org/x/sys + version: c4489faa6e5ab84c0ef40d6ee878f7a030281f0f + subpackages: + - unix +- name: golang.org/x/tools + version: 496819729719f9d07692195e0a94d6edd2251389 + subpackages: + - cover +- name: gopkg.in/inconshreveable/log15.v2 + version: b105bd37f74e5d9dc7b6ad7806715c7a2b83fd3f + subpackages: + - stack + - term diff --git a/vendor/go.uber.org/zap/glide.yaml b/vendor/go.uber.org/zap/glide.yaml new file mode 100644 index 000000000..94412594c --- /dev/null +++ b/vendor/go.uber.org/zap/glide.yaml @@ -0,0 +1,35 @@ +package: go.uber.org/zap +license: MIT +import: +- package: go.uber.org/atomic + version: ^1 +- package: go.uber.org/multierr + version: ^1 +testImport: +- package: github.com/satori/go.uuid +- package: github.com/sirupsen/logrus +- package: github.com/apex/log + subpackages: + - handlers/json +- package: github.com/go-kit/kit + subpackages: + - log +- package: github.com/stretchr/testify + subpackages: + - assert + - require +- package: gopkg.in/inconshreveable/log15.v2 +- package: github.com/mattn/goveralls +- package: github.com/pborman/uuid +- package: github.com/pkg/errors +- package: go.pedge.io/lion +- package: github.com/rs/zerolog +- package: golang.org/x/tools + subpackages: + - cover +- package: github.com/golang/lint + subpackages: + - golint +- package: github.com/axw/gocov + subpackages: + - gocov diff --git a/vendor/go.uber.org/zap/global.go b/vendor/go.uber.org/zap/global.go new file mode 100644 index 000000000..d02232e39 --- /dev/null +++ b/vendor/go.uber.org/zap/global.go @@ -0,0 +1,169 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "fmt" + "log" + "os" + "sync" + + "go.uber.org/zap/zapcore" +) + +const ( + _stdLogDefaultDepth = 2 + _loggerWriterDepth = 2 + _programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + + "https://github.com/uber-go/zap/issues/new and reference this error: %v" +) + +var ( + _globalMu sync.RWMutex + _globalL = NewNop() + _globalS = _globalL.Sugar() +) + +// L returns the global Logger, which can be reconfigured with ReplaceGlobals. +// It's safe for concurrent use. +func L() *Logger { + _globalMu.RLock() + l := _globalL + _globalMu.RUnlock() + return l +} + +// S returns the global SugaredLogger, which can be reconfigured with +// ReplaceGlobals. It's safe for concurrent use. +func S() *SugaredLogger { + _globalMu.RLock() + s := _globalS + _globalMu.RUnlock() + return s +} + +// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a +// function to restore the original values. It's safe for concurrent use. +func ReplaceGlobals(logger *Logger) func() { + _globalMu.Lock() + prev := _globalL + _globalL = logger + _globalS = logger.Sugar() + _globalMu.Unlock() + return func() { ReplaceGlobals(prev) } +} + +// NewStdLog returns a *log.Logger which writes to the supplied zap Logger at +// InfoLevel. To redirect the standard library's package-global logging +// functions, use RedirectStdLog instead. +func NewStdLog(l *Logger) *log.Logger { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + f := logger.Info + return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */) +} + +// NewStdLogAt returns *log.Logger which writes to supplied zap logger at +// required level. +func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + logFunc, err := levelToFunc(logger, level) + if err != nil { + return nil, err + } + return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil +} + +// RedirectStdLog redirects output from the standard library's package-global +// logger to the supplied logger at InfoLevel. Since zap already handles caller +// annotations, timestamps, etc., it automatically disables the standard +// library's annotations and prefixing. +// +// It returns a function to restore the original prefix and flags and reset the +// standard library's output to os.Stderr. +func RedirectStdLog(l *Logger) func() { + f, err := redirectStdLogAt(l, InfoLevel) + if err != nil { + // Can't get here, since passing InfoLevel to redirectStdLogAt always + // works. + panic(fmt.Sprintf(_programmerErrorTemplate, err)) + } + return f +} + +// RedirectStdLogAt redirects output from the standard library's package-global +// logger to the supplied logger at the specified level. Since zap already +// handles caller annotations, timestamps, etc., it automatically disables the +// standard library's annotations and prefixing. +// +// It returns a function to restore the original prefix and flags and reset the +// standard library's output to os.Stderr. +func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { + return redirectStdLogAt(l, level) +} + +func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { + flags := log.Flags() + prefix := log.Prefix() + log.SetFlags(0) + log.SetPrefix("") + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + logFunc, err := levelToFunc(logger, level) + if err != nil { + return nil, err + } + log.SetOutput(&loggerWriter{logFunc}) + return func() { + log.SetFlags(flags) + log.SetPrefix(prefix) + log.SetOutput(os.Stderr) + }, nil +} + +func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) { + switch lvl { + case DebugLevel: + return logger.Debug, nil + case InfoLevel: + return logger.Info, nil + case WarnLevel: + return logger.Warn, nil + case ErrorLevel: + return logger.Error, nil + case DPanicLevel: + return logger.DPanic, nil + case PanicLevel: + return logger.Panic, nil + case FatalLevel: + return logger.Fatal, nil + } + return nil, fmt.Errorf("unrecognized level: %q", lvl) +} + +type loggerWriter struct { + logFunc func(msg string, fields ...Field) +} + +func (l *loggerWriter) Write(p []byte) (int, error) { + p = bytes.TrimSpace(p) + l.logFunc(string(p)) + return len(p), nil +} diff --git a/vendor/go.uber.org/zap/http_handler.go b/vendor/go.uber.org/zap/http_handler.go new file mode 100644 index 000000000..f171c3847 --- /dev/null +++ b/vendor/go.uber.org/zap/http_handler.go @@ -0,0 +1,81 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "encoding/json" + "fmt" + "net/http" + + "go.uber.org/zap/zapcore" +) + +// ServeHTTP is a simple JSON endpoint that can report on or change the current +// logging level. +// +// GET requests return a JSON description of the current logging level. PUT +// requests change the logging level and expect a payload like: +// {"level":"info"} +// +// It's perfectly safe to change the logging level while a program is running. +func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { + type errorResponse struct { + Error string `json:"error"` + } + type payload struct { + Level *zapcore.Level `json:"level"` + } + + enc := json.NewEncoder(w) + + switch r.Method { + + case "GET": + current := lvl.Level() + enc.Encode(payload{Level: ¤t}) + + case "PUT": + var req payload + + if errmess := func() string { + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return fmt.Sprintf("Request body must be well-formed JSON: %v", err) + } + if req.Level == nil { + return "Must specify a logging level." + } + return "" + }(); errmess != "" { + w.WriteHeader(http.StatusBadRequest) + enc.Encode(errorResponse{Error: errmess}) + return + } + + lvl.SetLevel(*req.Level) + enc.Encode(req) + + default: + w.WriteHeader(http.StatusMethodNotAllowed) + enc.Encode(errorResponse{ + Error: "Only GET and PUT are supported.", + }) + } +} diff --git a/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go b/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go new file mode 100644 index 000000000..dad583aaa --- /dev/null +++ b/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go @@ -0,0 +1,31 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package bufferpool houses zap's shared internal buffer pool. Third-party +// packages can recreate the same functionality with buffers.NewPool. +package bufferpool + +import "go.uber.org/zap/buffer" + +var ( + _pool = buffer.NewPool() + // Get retrieves a buffer from the pool, creating one if necessary. + Get = _pool.Get +) diff --git a/vendor/go.uber.org/zap/internal/color/color.go b/vendor/go.uber.org/zap/internal/color/color.go new file mode 100644 index 000000000..c4d5d02ab --- /dev/null +++ b/vendor/go.uber.org/zap/internal/color/color.go @@ -0,0 +1,44 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package color adds coloring functionality for TTY output. +package color + +import "fmt" + +// Foreground colors. +const ( + Black Color = iota + 30 + Red + Green + Yellow + Blue + Magenta + Cyan + White +) + +// Color represents a text color. +type Color uint8 + +// Add adds the coloring to the given string. +func (c Color) Add(s string) string { + return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s) +} diff --git a/vendor/go.uber.org/zap/internal/exit/exit.go b/vendor/go.uber.org/zap/internal/exit/exit.go new file mode 100644 index 000000000..dfc5b05fe --- /dev/null +++ b/vendor/go.uber.org/zap/internal/exit/exit.go @@ -0,0 +1,64 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package exit provides stubs so that unit tests can exercise code that calls +// os.Exit(1). +package exit + +import "os" + +var real = func() { os.Exit(1) } + +// Exit normally terminates the process by calling os.Exit(1). If the package +// is stubbed, it instead records a call in the testing spy. +func Exit() { + real() +} + +// A StubbedExit is a testing fake for os.Exit. +type StubbedExit struct { + Exited bool + prev func() +} + +// Stub substitutes a fake for the call to os.Exit(1). +func Stub() *StubbedExit { + s := &StubbedExit{prev: real} + real = s.exit + return s +} + +// WithStub runs the supplied function with Exit stubbed. It returns the stub +// used, so that users can test whether the process would have crashed. +func WithStub(f func()) *StubbedExit { + s := Stub() + defer s.Unstub() + f() + return s +} + +// Unstub restores the previous exit function. +func (se *StubbedExit) Unstub() { + real = se.prev +} + +func (se *StubbedExit) exit() { + se.Exited = true +} diff --git a/vendor/go.uber.org/zap/level.go b/vendor/go.uber.org/zap/level.go new file mode 100644 index 000000000..3567a9a1e --- /dev/null +++ b/vendor/go.uber.org/zap/level.go @@ -0,0 +1,132 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "go.uber.org/atomic" + "go.uber.org/zap/zapcore" +) + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel = zapcore.DebugLevel + // InfoLevel is the default logging priority. + InfoLevel = zapcore.InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel = zapcore.WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel = zapcore.ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel = zapcore.DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel = zapcore.PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel = zapcore.FatalLevel +) + +// LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with +// an anonymous function. +// +// It's particularly useful when splitting log output between different +// outputs (e.g., standard error and standard out). For sample code, see the +// package-level AdvancedConfiguration example. +type LevelEnablerFunc func(zapcore.Level) bool + +// Enabled calls the wrapped function. +func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { return f(lvl) } + +// An AtomicLevel is an atomically changeable, dynamic logging level. It lets +// you safely change the log level of a tree of loggers (the root logger and +// any children created by adding context) at runtime. +// +// The AtomicLevel itself is an http.Handler that serves a JSON endpoint to +// alter its level. +// +// AtomicLevels must be created with the NewAtomicLevel constructor to allocate +// their internal atomic pointer. +type AtomicLevel struct { + l *atomic.Int32 +} + +// NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging +// enabled. +func NewAtomicLevel() AtomicLevel { + return AtomicLevel{ + l: atomic.NewInt32(int32(InfoLevel)), + } +} + +// NewAtomicLevelAt is a convenience function that creates an AtomicLevel +// and then calls SetLevel with the given level. +func NewAtomicLevelAt(l zapcore.Level) AtomicLevel { + a := NewAtomicLevel() + a.SetLevel(l) + return a +} + +// Enabled implements the zapcore.LevelEnabler interface, which allows the +// AtomicLevel to be used in place of traditional static levels. +func (lvl AtomicLevel) Enabled(l zapcore.Level) bool { + return lvl.Level().Enabled(l) +} + +// Level returns the minimum enabled log level. +func (lvl AtomicLevel) Level() zapcore.Level { + return zapcore.Level(int8(lvl.l.Load())) +} + +// SetLevel alters the logging level. +func (lvl AtomicLevel) SetLevel(l zapcore.Level) { + lvl.l.Store(int32(l)) +} + +// String returns the string representation of the underlying Level. +func (lvl AtomicLevel) String() string { + return lvl.Level().String() +} + +// UnmarshalText unmarshals the text to an AtomicLevel. It uses the same text +// representations as the static zapcore.Levels ("debug", "info", "warn", +// "error", "dpanic", "panic", and "fatal"). +func (lvl *AtomicLevel) UnmarshalText(text []byte) error { + if lvl.l == nil { + lvl.l = &atomic.Int32{} + } + + var l zapcore.Level + if err := l.UnmarshalText(text); err != nil { + return err + } + + lvl.SetLevel(l) + return nil +} + +// MarshalText marshals the AtomicLevel to a byte slice. It uses the same +// text representation as the static zapcore.Levels ("debug", "info", "warn", +// "error", "dpanic", "panic", and "fatal"). +func (lvl AtomicLevel) MarshalText() (text []byte, err error) { + return lvl.Level().MarshalText() +} diff --git a/vendor/go.uber.org/zap/logger.go b/vendor/go.uber.org/zap/logger.go new file mode 100644 index 000000000..dc8f6e3a4 --- /dev/null +++ b/vendor/go.uber.org/zap/logger.go @@ -0,0 +1,305 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "io/ioutil" + "os" + "runtime" + "strings" + "time" + + "go.uber.org/zap/zapcore" +) + +// A Logger provides fast, leveled, structured logging. All methods are safe +// for concurrent use. +// +// The Logger is designed for contexts in which every microsecond and every +// allocation matters, so its API intentionally favors performance and type +// safety over brevity. For most applications, the SugaredLogger strikes a +// better balance between performance and ergonomics. +type Logger struct { + core zapcore.Core + + development bool + name string + errorOutput zapcore.WriteSyncer + + addCaller bool + addStack zapcore.LevelEnabler + + callerSkip int +} + +// New constructs a new Logger from the provided zapcore.Core and Options. If +// the passed zapcore.Core is nil, it falls back to using a no-op +// implementation. +// +// This is the most flexible way to construct a Logger, but also the most +// verbose. For typical use cases, the highly-opinionated presets +// (NewProduction, NewDevelopment, and NewExample) or the Config struct are +// more convenient. +// +// For sample code, see the package-level AdvancedConfiguration example. +func New(core zapcore.Core, options ...Option) *Logger { + if core == nil { + return NewNop() + } + log := &Logger{ + core: core, + errorOutput: zapcore.Lock(os.Stderr), + addStack: zapcore.FatalLevel + 1, + } + return log.WithOptions(options...) +} + +// NewNop returns a no-op Logger. It never writes out logs or internal errors, +// and it never runs user-defined hooks. +// +// Using WithOptions to replace the Core or error output of a no-op Logger can +// re-enable logging. +func NewNop() *Logger { + return &Logger{ + core: zapcore.NewNopCore(), + errorOutput: zapcore.AddSync(ioutil.Discard), + addStack: zapcore.FatalLevel + 1, + } +} + +// NewProduction builds a sensible production Logger that writes InfoLevel and +// above logs to standard error as JSON. +// +// It's a shortcut for NewProductionConfig().Build(...Option). +func NewProduction(options ...Option) (*Logger, error) { + return NewProductionConfig().Build(options...) +} + +// NewDevelopment builds a development Logger that writes DebugLevel and above +// logs to standard error in a human-friendly format. +// +// It's a shortcut for NewDevelopmentConfig().Build(...Option). +func NewDevelopment(options ...Option) (*Logger, error) { + return NewDevelopmentConfig().Build(options...) +} + +// NewExample builds a Logger that's designed for use in zap's testable +// examples. It writes DebugLevel and above logs to standard out as JSON, but +// omits the timestamp and calling function to keep example output +// short and deterministic. +func NewExample(options ...Option) *Logger { + encoderCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "logger", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + } + core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel) + return New(core).WithOptions(options...) +} + +// Sugar wraps the Logger to provide a more ergonomic, but slightly slower, +// API. Sugaring a Logger is quite inexpensive, so it's reasonable for a +// single application to use both Loggers and SugaredLoggers, converting +// between them on the boundaries of performance-sensitive code. +func (log *Logger) Sugar() *SugaredLogger { + core := log.clone() + core.callerSkip += 2 + return &SugaredLogger{core} +} + +// Named adds a new path segment to the logger's name. Segments are joined by +// periods. By default, Loggers are unnamed. +func (log *Logger) Named(s string) *Logger { + if s == "" { + return log + } + l := log.clone() + if log.name == "" { + l.name = s + } else { + l.name = strings.Join([]string{l.name, s}, ".") + } + return l +} + +// WithOptions clones the current Logger, applies the supplied Options, and +// returns the resulting Logger. It's safe to use concurrently. +func (log *Logger) WithOptions(opts ...Option) *Logger { + c := log.clone() + for _, opt := range opts { + opt.apply(c) + } + return c +} + +// With creates a child logger and adds structured context to it. Fields added +// to the child don't affect the parent, and vice versa. +func (log *Logger) With(fields ...Field) *Logger { + if len(fields) == 0 { + return log + } + l := log.clone() + l.core = l.core.With(fields) + return l +} + +// Check returns a CheckedEntry if logging a message at the specified level +// is enabled. It's a completely optional optimization; in high-performance +// applications, Check can help avoid allocating a slice to hold fields. +func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + return log.check(lvl, msg) +} + +// Debug logs a message at DebugLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Debug(msg string, fields ...Field) { + if ce := log.check(DebugLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Info logs a message at InfoLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Info(msg string, fields ...Field) { + if ce := log.check(InfoLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Warn logs a message at WarnLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Warn(msg string, fields ...Field) { + if ce := log.check(WarnLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Error logs a message at ErrorLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Error(msg string, fields ...Field) { + if ce := log.check(ErrorLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// DPanic logs a message at DPanicLevel. The message includes any fields +// passed at the log site, as well as any fields accumulated on the logger. +// +// If the logger is in development mode, it then panics (DPanic means +// "development panic"). This is useful for catching errors that are +// recoverable, but shouldn't ever happen. +func (log *Logger) DPanic(msg string, fields ...Field) { + if ce := log.check(DPanicLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Panic logs a message at PanicLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then panics, even if logging at PanicLevel is disabled. +func (log *Logger) Panic(msg string, fields ...Field) { + if ce := log.check(PanicLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Fatal logs a message at FatalLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then calls os.Exit(1), even if logging at FatalLevel is +// disabled. +func (log *Logger) Fatal(msg string, fields ...Field) { + if ce := log.check(FatalLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Sync calls the underlying Core's Sync method, flushing any buffered log +// entries. Applications should take care to call Sync before exiting. +func (log *Logger) Sync() error { + return log.core.Sync() +} + +// Core returns the Logger's underlying zapcore.Core. +func (log *Logger) Core() zapcore.Core { + return log.core +} + +func (log *Logger) clone() *Logger { + copy := *log + return © +} + +func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + // check must always be called directly by a method in the Logger interface + // (e.g., Check, Info, Fatal). + const callerSkipOffset = 2 + + // Create basic checked entry thru the core; this will be non-nil if the + // log message will actually be written somewhere. + ent := zapcore.Entry{ + LoggerName: log.name, + Time: time.Now(), + Level: lvl, + Message: msg, + } + ce := log.core.Check(ent, nil) + willWrite := ce != nil + + // Set up any required terminal behavior. + switch ent.Level { + case zapcore.PanicLevel: + ce = ce.Should(ent, zapcore.WriteThenPanic) + case zapcore.FatalLevel: + ce = ce.Should(ent, zapcore.WriteThenFatal) + case zapcore.DPanicLevel: + if log.development { + ce = ce.Should(ent, zapcore.WriteThenPanic) + } + } + + // Only do further annotation if we're going to write this message; checked + // entries that exist only for terminal behavior don't benefit from + // annotation. + if !willWrite { + return ce + } + + // Thread the error output through to the CheckedEntry. + ce.ErrorOutput = log.errorOutput + if log.addCaller { + ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset)) + if !ce.Entry.Caller.Defined { + fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC()) + log.errorOutput.Sync() + } + } + if log.addStack.Enabled(ce.Entry.Level) { + ce.Entry.Stack = Stack("").String + } + + return ce +} diff --git a/vendor/go.uber.org/zap/options.go b/vendor/go.uber.org/zap/options.go new file mode 100644 index 000000000..7a6b0fca1 --- /dev/null +++ b/vendor/go.uber.org/zap/options.go @@ -0,0 +1,109 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import "go.uber.org/zap/zapcore" + +// An Option configures a Logger. +type Option interface { + apply(*Logger) +} + +// optionFunc wraps a func so it satisfies the Option interface. +type optionFunc func(*Logger) + +func (f optionFunc) apply(log *Logger) { + f(log) +} + +// WrapCore wraps or replaces the Logger's underlying zapcore.Core. +func WrapCore(f func(zapcore.Core) zapcore.Core) Option { + return optionFunc(func(log *Logger) { + log.core = f(log.core) + }) +} + +// Hooks registers functions which will be called each time the Logger writes +// out an Entry. Repeated use of Hooks is additive. +// +// Hooks are useful for simple side effects, like capturing metrics for the +// number of emitted logs. More complex side effects, including anything that +// requires access to the Entry's structured fields, should be implemented as +// a zapcore.Core instead. See zapcore.RegisterHooks for details. +func Hooks(hooks ...func(zapcore.Entry) error) Option { + return optionFunc(func(log *Logger) { + log.core = zapcore.RegisterHooks(log.core, hooks...) + }) +} + +// Fields adds fields to the Logger. +func Fields(fs ...Field) Option { + return optionFunc(func(log *Logger) { + log.core = log.core.With(fs) + }) +} + +// ErrorOutput sets the destination for errors generated by the Logger. Note +// that this option only affects internal errors; for sample code that sends +// error-level logs to a different location from info- and debug-level logs, +// see the package-level AdvancedConfiguration example. +// +// The supplied WriteSyncer must be safe for concurrent use. The Open and +// zapcore.Lock functions are the simplest ways to protect files with a mutex. +func ErrorOutput(w zapcore.WriteSyncer) Option { + return optionFunc(func(log *Logger) { + log.errorOutput = w + }) +} + +// Development puts the logger in development mode, which makes DPanic-level +// logs panic instead of simply logging an error. +func Development() Option { + return optionFunc(func(log *Logger) { + log.development = true + }) +} + +// AddCaller configures the Logger to annotate each message with the filename +// and line number of zap's caller. +func AddCaller() Option { + return optionFunc(func(log *Logger) { + log.addCaller = true + }) +} + +// AddCallerSkip increases the number of callers skipped by caller annotation +// (as enabled by the AddCaller option). When building wrappers around the +// Logger and SugaredLogger, supplying this Option prevents zap from always +// reporting the wrapper code as the caller. +func AddCallerSkip(skip int) Option { + return optionFunc(func(log *Logger) { + log.callerSkip += skip + }) +} + +// AddStacktrace configures the Logger to record a stack trace for all messages at +// or above a given level. +func AddStacktrace(lvl zapcore.LevelEnabler) Option { + return optionFunc(func(log *Logger) { + log.addStack = lvl + }) +} diff --git a/vendor/go.uber.org/zap/stacktrace.go b/vendor/go.uber.org/zap/stacktrace.go new file mode 100644 index 000000000..100fac216 --- /dev/null +++ b/vendor/go.uber.org/zap/stacktrace.go @@ -0,0 +1,126 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "runtime" + "strings" + "sync" + + "go.uber.org/zap/internal/bufferpool" +) + +const _zapPackage = "go.uber.org/zap" + +var ( + _stacktracePool = sync.Pool{ + New: func() interface{} { + return newProgramCounters(64) + }, + } + + // We add "." and "/" suffixes to the package name to ensure we only match + // the exact package and not any package with the same prefix. + _zapStacktracePrefixes = addPrefix(_zapPackage, ".", "/") + _zapStacktraceVendorContains = addPrefix("/vendor/", _zapStacktracePrefixes...) +) + +func takeStacktrace() string { + buffer := bufferpool.Get() + defer buffer.Free() + programCounters := _stacktracePool.Get().(*programCounters) + defer _stacktracePool.Put(programCounters) + + var numFrames int + for { + // Skip the call to runtime.Counters and takeStacktrace so that the + // program counters start at the caller of takeStacktrace. + numFrames = runtime.Callers(2, programCounters.pcs) + if numFrames < len(programCounters.pcs) { + break + } + // Don't put the too-short counter slice back into the pool; this lets + // the pool adjust if we consistently take deep stacktraces. + programCounters = newProgramCounters(len(programCounters.pcs) * 2) + } + + i := 0 + skipZapFrames := true // skip all consecutive zap frames at the beginning. + frames := runtime.CallersFrames(programCounters.pcs[:numFrames]) + + // Note: On the last iteration, frames.Next() returns false, with a valid + // frame, but we ignore this frame. The last frame is a a runtime frame which + // adds noise, since it's only either runtime.main or runtime.goexit. + for frame, more := frames.Next(); more; frame, more = frames.Next() { + if skipZapFrames && isZapFrame(frame.Function) { + continue + } else { + skipZapFrames = false + } + + if i != 0 { + buffer.AppendByte('\n') + } + i++ + buffer.AppendString(frame.Function) + buffer.AppendByte('\n') + buffer.AppendByte('\t') + buffer.AppendString(frame.File) + buffer.AppendByte(':') + buffer.AppendInt(int64(frame.Line)) + } + + return buffer.String() +} + +func isZapFrame(function string) bool { + for _, prefix := range _zapStacktracePrefixes { + if strings.HasPrefix(function, prefix) { + return true + } + } + + // We can't use a prefix match here since the location of the vendor + // directory affects the prefix. Instead we do a contains match. + for _, contains := range _zapStacktraceVendorContains { + if strings.Contains(function, contains) { + return true + } + } + + return false +} + +type programCounters struct { + pcs []uintptr +} + +func newProgramCounters(size int) *programCounters { + return &programCounters{make([]uintptr, size)} +} + +func addPrefix(prefix string, ss ...string) []string { + withPrefix := make([]string, len(ss)) + for i, s := range ss { + withPrefix[i] = prefix + s + } + return withPrefix +} diff --git a/vendor/go.uber.org/zap/sugar.go b/vendor/go.uber.org/zap/sugar.go new file mode 100644 index 000000000..77ca227f4 --- /dev/null +++ b/vendor/go.uber.org/zap/sugar.go @@ -0,0 +1,304 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +const ( + _oddNumberErrMsg = "Ignored key without a value." + _nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." +) + +// A SugaredLogger wraps the base Logger functionality in a slower, but less +// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar +// method. +// +// Unlike the Logger, the SugaredLogger doesn't insist on structured logging. +// For each log level, it exposes three methods: one for loosely-typed +// structured logging, one for println-style formatting, and one for +// printf-style formatting. For example, SugaredLoggers can produce InfoLevel +// output with Infow ("info with" structured context), Info, or Infof. +type SugaredLogger struct { + base *Logger +} + +// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring +// is quite inexpensive, so it's reasonable for a single application to use +// both Loggers and SugaredLoggers, converting between them on the boundaries +// of performance-sensitive code. +func (s *SugaredLogger) Desugar() *Logger { + base := s.base.clone() + base.callerSkip -= 2 + return base +} + +// Named adds a sub-scope to the logger's name. See Logger.Named for details. +func (s *SugaredLogger) Named(name string) *SugaredLogger { + return &SugaredLogger{base: s.base.Named(name)} +} + +// With adds a variadic number of fields to the logging context. It accepts a +// mix of strongly-typed Field objects and loosely-typed key-value pairs. When +// processing pairs, the first element of the pair is used as the field key +// and the second as the field value. +// +// For example, +// sugaredLogger.With( +// "hello", "world", +// "failure", errors.New("oh no"), +// Stack(), +// "count", 42, +// "user", User{Name: "alice"}, +// ) +// is the equivalent of +// unsugared.With( +// String("hello", "world"), +// String("failure", "oh no"), +// Stack(), +// Int("count", 42), +// Object("user", User{Name: "alice"}), +// ) +// +// Note that the keys in key-value pairs should be strings. In development, +// passing a non-string key panics. In production, the logger is more +// forgiving: a separate error is logged, but the key-value pair is skipped +// and execution continues. Passing an orphaned key triggers similar behavior: +// panics in development and errors in production. +func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger { + return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} +} + +// Debug uses fmt.Sprint to construct and log a message. +func (s *SugaredLogger) Debug(args ...interface{}) { + s.log(DebugLevel, "", args, nil) +} + +// Info uses fmt.Sprint to construct and log a message. +func (s *SugaredLogger) Info(args ...interface{}) { + s.log(InfoLevel, "", args, nil) +} + +// Warn uses fmt.Sprint to construct and log a message. +func (s *SugaredLogger) Warn(args ...interface{}) { + s.log(WarnLevel, "", args, nil) +} + +// Error uses fmt.Sprint to construct and log a message. +func (s *SugaredLogger) Error(args ...interface{}) { + s.log(ErrorLevel, "", args, nil) +} + +// DPanic uses fmt.Sprint to construct and log a message. In development, the +// logger then panics. (See DPanicLevel for details.) +func (s *SugaredLogger) DPanic(args ...interface{}) { + s.log(DPanicLevel, "", args, nil) +} + +// Panic uses fmt.Sprint to construct and log a message, then panics. +func (s *SugaredLogger) Panic(args ...interface{}) { + s.log(PanicLevel, "", args, nil) +} + +// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit. +func (s *SugaredLogger) Fatal(args ...interface{}) { + s.log(FatalLevel, "", args, nil) +} + +// Debugf uses fmt.Sprintf to log a templated message. +func (s *SugaredLogger) Debugf(template string, args ...interface{}) { + s.log(DebugLevel, template, args, nil) +} + +// Infof uses fmt.Sprintf to log a templated message. +func (s *SugaredLogger) Infof(template string, args ...interface{}) { + s.log(InfoLevel, template, args, nil) +} + +// Warnf uses fmt.Sprintf to log a templated message. +func (s *SugaredLogger) Warnf(template string, args ...interface{}) { + s.log(WarnLevel, template, args, nil) +} + +// Errorf uses fmt.Sprintf to log a templated message. +func (s *SugaredLogger) Errorf(template string, args ...interface{}) { + s.log(ErrorLevel, template, args, nil) +} + +// DPanicf uses fmt.Sprintf to log a templated message. In development, the +// logger then panics. (See DPanicLevel for details.) +func (s *SugaredLogger) DPanicf(template string, args ...interface{}) { + s.log(DPanicLevel, template, args, nil) +} + +// Panicf uses fmt.Sprintf to log a templated message, then panics. +func (s *SugaredLogger) Panicf(template string, args ...interface{}) { + s.log(PanicLevel, template, args, nil) +} + +// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit. +func (s *SugaredLogger) Fatalf(template string, args ...interface{}) { + s.log(FatalLevel, template, args, nil) +} + +// Debugw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +// +// When debug-level logging is disabled, this is much faster than +// s.With(keysAndValues).Debug(msg) +func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { + s.log(DebugLevel, msg, nil, keysAndValues) +} + +// Infow logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) { + s.log(InfoLevel, msg, nil, keysAndValues) +} + +// Warnw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) { + s.log(WarnLevel, msg, nil, keysAndValues) +} + +// Errorw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { + s.log(ErrorLevel, msg, nil, keysAndValues) +} + +// DPanicw logs a message with some additional context. In development, the +// logger then panics. (See DPanicLevel for details.) The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) { + s.log(DPanicLevel, msg, nil, keysAndValues) +} + +// Panicw logs a message with some additional context, then panics. The +// variadic key-value pairs are treated as they are in With. +func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) { + s.log(PanicLevel, msg, nil, keysAndValues) +} + +// Fatalw logs a message with some additional context, then calls os.Exit. The +// variadic key-value pairs are treated as they are in With. +func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) { + s.log(FatalLevel, msg, nil, keysAndValues) +} + +// Sync flushes any buffered log entries. +func (s *SugaredLogger) Sync() error { + return s.base.Sync() +} + +func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { + // If logging at this level is completely disabled, skip the overhead of + // string formatting. + if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { + return + } + + // Format with Sprint, Sprintf, or neither. + msg := template + if msg == "" && len(fmtArgs) > 0 { + msg = fmt.Sprint(fmtArgs...) + } else if msg != "" && len(fmtArgs) > 0 { + msg = fmt.Sprintf(template, fmtArgs...) + } + + if ce := s.base.Check(lvl, msg); ce != nil { + ce.Write(s.sweetenFields(context)...) + } +} + +func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { + if len(args) == 0 { + return nil + } + + // Allocate enough space for the worst case; if users pass only structured + // fields, we shouldn't penalize them with extra allocations. + fields := make([]Field, 0, len(args)) + var invalid invalidPairs + + for i := 0; i < len(args); { + // This is a strongly-typed field. Consume it and move on. + if f, ok := args[i].(Field); ok { + fields = append(fields, f) + i++ + continue + } + + // Make sure this element isn't a dangling key. + if i == len(args)-1 { + s.base.DPanic(_oddNumberErrMsg, Any("ignored", args[i])) + break + } + + // Consume this value and the next, treating them as a key-value pair. If the + // key isn't a string, add this pair to the slice of invalid pairs. + key, val := args[i], args[i+1] + if keyStr, ok := key.(string); !ok { + // Subsequent errors are likely, so allocate once up front. + if cap(invalid) == 0 { + invalid = make(invalidPairs, 0, len(args)/2) + } + invalid = append(invalid, invalidPair{i, key, val}) + } else { + fields = append(fields, Any(keyStr, val)) + } + i += 2 + } + + // If we encountered any invalid key-value pairs, log an error. + if len(invalid) > 0 { + s.base.DPanic(_nonStringKeyErrMsg, Array("invalid", invalid)) + } + return fields +} + +type invalidPair struct { + position int + key, value interface{} +} + +func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddInt64("position", int64(p.position)) + Any("key", p.key).AddTo(enc) + Any("value", p.value).AddTo(enc) + return nil +} + +type invalidPairs []invalidPair + +func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error { + var err error + for i := range ps { + err = multierr.Append(err, enc.AppendObject(ps[i])) + } + return err +} diff --git a/vendor/go.uber.org/zap/time.go b/vendor/go.uber.org/zap/time.go new file mode 100644 index 000000000..c5a1f1622 --- /dev/null +++ b/vendor/go.uber.org/zap/time.go @@ -0,0 +1,27 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import "time" + +func timeToMillis(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} diff --git a/vendor/go.uber.org/zap/writer.go b/vendor/go.uber.org/zap/writer.go new file mode 100644 index 000000000..16f55ce48 --- /dev/null +++ b/vendor/go.uber.org/zap/writer.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "io/ioutil" + "os" + + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +// Open is a high-level wrapper that takes a variadic number of paths, opens or +// creates each of the specified files, and combines them into a locked +// WriteSyncer. It also returns any error encountered and a function to close +// any opened files. +// +// Passing no paths returns a no-op WriteSyncer. The special paths "stdout" and +// "stderr" are interpreted as os.Stdout and os.Stderr, respectively. +func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { + writers, close, err := open(paths) + if err != nil { + return nil, nil, err + } + + writer := CombineWriteSyncers(writers...) + return writer, close, nil +} + +func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { + var openErr error + writers := make([]zapcore.WriteSyncer, 0, len(paths)) + files := make([]*os.File, 0, len(paths)) + close := func() { + for _, f := range files { + f.Close() + } + } + for _, path := range paths { + switch path { + case "stdout": + writers = append(writers, os.Stdout) + // Don't close standard out. + continue + case "stderr": + writers = append(writers, os.Stderr) + // Don't close standard error. + continue + } + f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + openErr = multierr.Append(openErr, err) + if err == nil { + writers = append(writers, f) + files = append(files, f) + } + } + + if openErr != nil { + close() + return writers, nil, openErr + } + + return writers, close, nil +} + +// CombineWriteSyncers is a utility that combines multiple WriteSyncers into a +// single, locked WriteSyncer. If no inputs are supplied, it returns a no-op +// WriteSyncer. +// +// It's provided purely as a convenience; the result is no different from +// using zapcore.NewMultiWriteSyncer and zapcore.Lock individually. +func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer { + if len(writers) == 0 { + return zapcore.AddSync(ioutil.Discard) + } + return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)) +} diff --git a/vendor/go.uber.org/zap/zapcore/console_encoder.go b/vendor/go.uber.org/zap/zapcore/console_encoder.go new file mode 100644 index 000000000..b7875966f --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/console_encoder.go @@ -0,0 +1,147 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "sync" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" +) + +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 { + *jsonEncoder +} + +// 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 EncoderConfig) Encoder { + return consoleEncoder{newJSONEncoder(cfg, true)} +} + +func (c consoleEncoder) Clone() Encoder { + return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)} +} + +func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + line := bufferpool.Get() + + // 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.TimeKey != "" && c.EncodeTime != nil { + c.EncodeTime(ent.Time, arr) + } + if c.LevelKey != "" && c.EncodeLevel != nil { + c.EncodeLevel(ent.Level, arr) + } + if ent.LoggerName != "" && c.NameKey != "" { + nameEncoder := c.EncodeName + + if nameEncoder == nil { + // Fall back to FullNameEncoder for backward compatibility. + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, arr) + } + if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil { + c.EncodeCaller(ent.Caller, arr) + } + for i := range arr.elems { + if i > 0 { + line.AppendByte('\t') + } + 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(DefaultLineEnding) + } + return line, nil +} + +func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) { + context := c.jsonEncoder.Clone().(*jsonEncoder) + defer context.buf.Free() + + addFields(context, extra) + context.closeOpenNamespaces() + if context.buf.Len() == 0 { + return + } + + c.addTabIfNecessary(line) + line.AppendByte('{') + line.Write(context.buf.Bytes()) + line.AppendByte('}') +} + +func (c consoleEncoder) addTabIfNecessary(line *buffer.Buffer) { + if line.Len() > 0 { + line.AppendByte('\t') + } +} diff --git a/vendor/go.uber.org/zap/zapcore/core.go b/vendor/go.uber.org/zap/zapcore/core.go new file mode 100644 index 000000000..a1ef8b034 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/core.go @@ -0,0 +1,113 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// Core is a minimal, fast logger interface. It's designed for library authors +// to wrap in a more user-friendly API. +type Core interface { + LevelEnabler + + // With adds structured context to the Core. + With([]Field) Core + // Check determines whether the supplied Entry should be logged (using the + // embedded LevelEnabler and possibly some extra logic). If the entry + // should be logged, the Core adds itself to the CheckedEntry and returns + // the result. + // + // Callers must use Check before calling Write. + Check(Entry, *CheckedEntry) *CheckedEntry + // Write serializes the Entry and any Fields supplied at the log site and + // writes them to their destination. + // + // If called, Write should always log the Entry and Fields; it should not + // replicate the logic of Check. + Write(Entry, []Field) error + // Sync flushes buffered logs (if any). + Sync() error +} + +type nopCore struct{} + +// NewNopCore returns a no-op Core. +func NewNopCore() Core { return nopCore{} } +func (nopCore) Enabled(Level) bool { return false } +func (n nopCore) With([]Field) Core { return n } +func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce } +func (nopCore) Write(Entry, []Field) error { return nil } +func (nopCore) Sync() error { return nil } + +// NewCore creates a Core that writes logs to a WriteSyncer. +func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { + return &ioCore{ + LevelEnabler: enab, + enc: enc, + out: ws, + } +} + +type ioCore struct { + LevelEnabler + enc Encoder + out WriteSyncer +} + +func (c *ioCore) With(fields []Field) Core { + clone := c.clone() + addFields(clone.enc, fields) + return clone +} + +func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if c.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} + +func (c *ioCore) Write(ent Entry, fields []Field) error { + buf, err := c.enc.EncodeEntry(ent, fields) + if err != nil { + return err + } + _, err = c.out.Write(buf.Bytes()) + buf.Free() + if err != nil { + return err + } + if ent.Level > ErrorLevel { + // Since we may be crashing the program, sync the output. Ignore Sync + // errors, pending a clean solution to issue #370. + c.Sync() + } + return nil +} + +func (c *ioCore) Sync() error { + return c.out.Sync() +} + +func (c *ioCore) clone() *ioCore { + return &ioCore{ + LevelEnabler: c.LevelEnabler, + enc: c.enc.Clone(), + out: c.out, + } +} diff --git a/vendor/go.uber.org/zap/zapcore/doc.go b/vendor/go.uber.org/zap/zapcore/doc.go new file mode 100644 index 000000000..31000e91f --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapcore defines and implements the low-level interfaces upon which +// zap is built. By providing alternate implementations of these interfaces, +// external packages can extend zap's capabilities. +package zapcore // import "go.uber.org/zap/zapcore" diff --git a/vendor/go.uber.org/zap/zapcore/encoder.go b/vendor/go.uber.org/zap/zapcore/encoder.go new file mode 100644 index 000000000..f0509522b --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/encoder.go @@ -0,0 +1,348 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "time" + + "go.uber.org/zap/buffer" +) + +// DefaultLineEnding defines the default line ending when writing logs. +// Alternate line endings specified in EncoderConfig can override this +// behavior. +const DefaultLineEnding = "\n" + +// A LevelEncoder serializes a Level to a primitive type. +type LevelEncoder func(Level, PrimitiveArrayEncoder) + +// LowercaseLevelEncoder serializes a Level to a lowercase string. For example, +// InfoLevel is serialized to "info". +func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.String()) +} + +// LowercaseColorLevelEncoder serializes a Level to a lowercase string and adds coloring. +// For example, InfoLevel is serialized to "info" and colored blue. +func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToLowercaseColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.String()) + } + enc.AppendString(s) +} + +// CapitalLevelEncoder serializes a Level to an all-caps string. For example, +// InfoLevel is serialized to "INFO". +func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.CapitalString()) +} + +// CapitalColorLevelEncoder serializes a Level to an all-caps string and adds color. +// For example, InfoLevel is serialized to "INFO" and colored blue. +func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToCapitalColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.CapitalString()) + } + enc.AppendString(s) +} + +// UnmarshalText unmarshals text to a LevelEncoder. "capital" is unmarshaled to +// CapitalLevelEncoder, "coloredCapital" is unmarshaled to CapitalColorLevelEncoder, +// "colored" is unmarshaled to LowercaseColorLevelEncoder, and anything else +// is unmarshaled to LowercaseLevelEncoder. +func (e *LevelEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "capital": + *e = CapitalLevelEncoder + case "capitalColor": + *e = CapitalColorLevelEncoder + case "color": + *e = LowercaseColorLevelEncoder + default: + *e = LowercaseLevelEncoder + } + return nil +} + +// A TimeEncoder serializes a time.Time to a primitive type. +type TimeEncoder func(time.Time, PrimitiveArrayEncoder) + +// EpochTimeEncoder serializes a time.Time to a floating-point number of seconds +// since the Unix epoch. +func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + sec := float64(nanos) / float64(time.Second) + enc.AppendFloat64(sec) +} + +// EpochMillisTimeEncoder serializes a time.Time to a floating-point number of +// milliseconds since the Unix epoch. +func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + millis := float64(nanos) / float64(time.Millisecond) + enc.AppendFloat64(millis) +} + +// EpochNanosTimeEncoder serializes a time.Time to an integer number of +// nanoseconds since the Unix epoch. +func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + enc.AppendInt64(t.UnixNano()) +} + +// ISO8601TimeEncoder serializes a time.Time to an ISO8601-formatted string +// with millisecond precision. +func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02T15:04:05.000Z0700")) +} + +// UnmarshalText unmarshals text to a TimeEncoder. "iso8601" and "ISO8601" are +// unmarshaled to ISO8601TimeEncoder, "millis" is unmarshaled to +// EpochMillisTimeEncoder, and anything else is unmarshaled to EpochTimeEncoder. +func (e *TimeEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "iso8601", "ISO8601": + *e = ISO8601TimeEncoder + case "millis": + *e = EpochMillisTimeEncoder + case "nanos": + *e = EpochNanosTimeEncoder + default: + *e = EpochTimeEncoder + } + return nil +} + +// A DurationEncoder serializes a time.Duration to a primitive type. +type DurationEncoder func(time.Duration, PrimitiveArrayEncoder) + +// SecondsDurationEncoder serializes a time.Duration to a floating-point number of seconds elapsed. +func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendFloat64(float64(d) / float64(time.Second)) +} + +// NanosDurationEncoder serializes a time.Duration to an integer number of +// nanoseconds elapsed. +func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(int64(d)) +} + +// StringDurationEncoder serializes a time.Duration using its built-in String +// method. +func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendString(d.String()) +} + +// UnmarshalText unmarshals text to a DurationEncoder. "string" is unmarshaled +// to StringDurationEncoder, and anything else is unmarshaled to +// NanosDurationEncoder. +func (e *DurationEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "string": + *e = StringDurationEncoder + case "nanos": + *e = NanosDurationEncoder + default: + *e = SecondsDurationEncoder + } + return nil +} + +// A CallerEncoder serializes an EntryCaller to a primitive type. +type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder) + +// FullCallerEncoder serializes a caller in /full/path/to/package/file:line +// format. +func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.String()) +} + +// ShortCallerEncoder serializes a caller in package/file:line format, trimming +// all but the final directory from the full path. +func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.TrimmedPath()) +} + +// UnmarshalText unmarshals text to a CallerEncoder. "full" is unmarshaled to +// FullCallerEncoder and anything else is unmarshaled to ShortCallerEncoder. +func (e *CallerEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullCallerEncoder + default: + *e = ShortCallerEncoder + } + return nil +} + +// A NameEncoder serializes a period-separated logger name to a primitive +// type. +type NameEncoder func(string, PrimitiveArrayEncoder) + +// FullNameEncoder serializes the logger name as-is. +func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(loggerName) +} + +// UnmarshalText unmarshals text to a NameEncoder. Currently, everything is +// unmarshaled to FullNameEncoder. +func (e *NameEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullNameEncoder + default: + *e = FullNameEncoder + } + return nil +} + +// An EncoderConfig allows users to configure the concrete encoders supplied by +// zapcore. +type EncoderConfig struct { + // Set the keys used for each log entry. If any key is empty, that portion + // of the entry is omitted. + MessageKey string `json:"messageKey" yaml:"messageKey"` + LevelKey string `json:"levelKey" yaml:"levelKey"` + TimeKey string `json:"timeKey" yaml:"timeKey"` + NameKey string `json:"nameKey" yaml:"nameKey"` + CallerKey string `json:"callerKey" yaml:"callerKey"` + StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` + LineEnding string `json:"lineEnding" yaml:"lineEnding"` + // Configure the primitive representations of common complex types. For + // example, some users may want all time.Times serialized as floating-point + // seconds since epoch, while others may prefer ISO8601 strings. + EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` + EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` + EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` + EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` + // Unlike the other primitive type encoders, EncodeName is optional. The + // zero value falls back to FullNameEncoder. + EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` +} + +// ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a +// map- or struct-like object to the logging context. Like maps, ObjectEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ObjectEncoder interface { + // Logging-specific marshalers. + AddArray(key string, marshaler ArrayMarshaler) error + AddObject(key string, marshaler ObjectMarshaler) error + + // Built-in types. + AddBinary(key string, value []byte) // for arbitrary bytes + AddByteString(key string, value []byte) // for UTF-8 encoded bytes + AddBool(key string, value bool) + AddComplex128(key string, value complex128) + AddComplex64(key string, value complex64) + AddDuration(key string, value time.Duration) + AddFloat64(key string, value float64) + AddFloat32(key string, value float32) + AddInt(key string, value int) + AddInt64(key string, value int64) + AddInt32(key string, value int32) + AddInt16(key string, value int16) + AddInt8(key string, value int8) + AddString(key, value string) + AddTime(key string, value time.Time) + AddUint(key string, value uint) + AddUint64(key string, value uint64) + AddUint32(key string, value uint32) + AddUint16(key string, value uint16) + AddUint8(key string, value uint8) + AddUintptr(key string, value uintptr) + + // AddReflected uses reflection to serialize arbitrary objects, so it's slow + // and allocation-heavy. + AddReflected(key string, value interface{}) error + // OpenNamespace opens an isolated namespace where all subsequent fields will + // be added. Applications can use namespaces to prevent key collisions when + // injecting loggers into sub-components or third-party libraries. + OpenNamespace(key string) +} + +// ArrayEncoder is a strongly-typed, encoding-agnostic interface for adding +// array-like objects to the logging context. Of note, it supports mixed-type +// arrays even though they aren't typical in Go. Like slices, ArrayEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ArrayEncoder interface { + // Built-in types. + PrimitiveArrayEncoder + + // Time-related types. + AppendDuration(time.Duration) + AppendTime(time.Time) + + // Logging-specific marshalers. + AppendArray(ArrayMarshaler) error + AppendObject(ObjectMarshaler) error + + // AppendReflected uses reflection to serialize arbitrary objects, so it's + // slow and allocation-heavy. + AppendReflected(value interface{}) error +} + +// PrimitiveArrayEncoder is the subset of the ArrayEncoder interface that deals +// only in Go's built-in types. It's included only so that Duration- and +// TimeEncoders cannot trigger infinite recursion. +type PrimitiveArrayEncoder interface { + // Built-in types. + AppendBool(bool) + AppendByteString([]byte) // for UTF-8 encoded bytes + AppendComplex128(complex128) + AppendComplex64(complex64) + AppendFloat64(float64) + AppendFloat32(float32) + AppendInt(int) + AppendInt64(int64) + AppendInt32(int32) + AppendInt16(int16) + AppendInt8(int8) + AppendString(string) + AppendUint(uint) + AppendUint64(uint64) + AppendUint32(uint32) + AppendUint16(uint16) + AppendUint8(uint8) + AppendUintptr(uintptr) +} + +// Encoder is a format-agnostic interface for all log entry marshalers. Since +// log encoders don't need to support the same wide range of use cases as +// general-purpose marshalers, it's possible to make them faster and +// lower-allocation. +// +// Implementations of the ObjectEncoder interface's methods can, of course, +// freely modify the receiver. However, the Clone and EncodeEntry methods will +// be called concurrently and shouldn't modify the receiver. +type Encoder interface { + ObjectEncoder + + // Clone copies the encoder, ensuring that adding fields to the copy doesn't + // affect the original. + Clone() Encoder + + // EncodeEntry encodes an entry and fields, along with any accumulated + // context, into a byte buffer and returns it. + EncodeEntry(Entry, []Field) (*buffer.Buffer, error) +} diff --git a/vendor/go.uber.org/zap/zapcore/entry.go b/vendor/go.uber.org/zap/zapcore/entry.go new file mode 100644 index 000000000..7d9893f33 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/entry.go @@ -0,0 +1,257 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "strings" + "sync" + "time" + + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/exit" + + "go.uber.org/multierr" +) + +var ( + _cePool = sync.Pool{New: func() interface{} { + // Pre-allocate some space for cores. + return &CheckedEntry{ + cores: make([]Core, 4), + } + }} +) + +func getCheckedEntry() *CheckedEntry { + ce := _cePool.Get().(*CheckedEntry) + ce.reset() + return ce +} + +func putCheckedEntry(ce *CheckedEntry) { + if ce == nil { + return + } + _cePool.Put(ce) +} + +// NewEntryCaller makes an EntryCaller from the return signature of +// runtime.Caller. +func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller { + if !ok { + return EntryCaller{} + } + return EntryCaller{ + PC: pc, + File: file, + Line: line, + Defined: true, + } +} + +// EntryCaller represents the caller of a logging function. +type EntryCaller struct { + Defined bool + PC uintptr + File string + Line int +} + +// String returns the full path and line number of the caller. +func (ec EntryCaller) String() string { + return ec.FullPath() +} + +// FullPath returns a /full/path/to/package/file:line description of the +// caller. +func (ec EntryCaller) FullPath() string { + if !ec.Defined { + return "undefined" + } + buf := bufferpool.Get() + buf.AppendString(ec.File) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// TrimmedPath returns a package/file:line description of the caller, +// preserving only the leaf directory name and file name. +func (ec EntryCaller) TrimmedPath() string { + if !ec.Defined { + return "undefined" + } + // nb. To make sure we trim the path correctly on Windows too, we + // counter-intuitively need to use '/' and *not* os.PathSeparator here, + // because the path given originates from Go stdlib, specifically + // runtime.Caller() which (as of Mar/17) returns forward slashes even on + // Windows. + // + // See https://github.com/golang/go/issues/3335 + // and https://github.com/golang/go/issues/18151 + // + // for discussion on the issue on Go side. + // + // Find the last separator. + // + idx := strings.LastIndexByte(ec.File, '/') + if idx == -1 { + return ec.FullPath() + } + // Find the penultimate separator. + idx = strings.LastIndexByte(ec.File[:idx], '/') + if idx == -1 { + return ec.FullPath() + } + buf := bufferpool.Get() + // Keep everything after the penultimate separator. + buf.AppendString(ec.File[idx+1:]) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// An Entry represents a complete log message. The entry's structured context +// is already serialized, but the log level, time, message, and call site +// information are available for inspection and modification. +// +// Entries are pooled, so any functions that accept them MUST be careful not to +// retain references to them. +type Entry struct { + Level Level + Time time.Time + LoggerName string + Message string + Caller EntryCaller + Stack string +} + +// CheckWriteAction indicates what action to take after a log entry is +// processed. Actions are ordered in increasing severity. +type CheckWriteAction uint8 + +const ( + // WriteThenNoop indicates that nothing special needs to be done. It's the + // default behavior. + WriteThenNoop CheckWriteAction = iota + // WriteThenPanic causes a panic after Write. + WriteThenPanic + // WriteThenFatal causes a fatal os.Exit after Write. + WriteThenFatal +) + +// CheckedEntry is an Entry together with a collection of Cores that have +// already agreed to log it. +// +// CheckedEntry references should be created by calling AddCore or Should on a +// nil *CheckedEntry. References are returned to a pool after Write, and MUST +// NOT be retained after calling their Write method. +type CheckedEntry struct { + Entry + ErrorOutput WriteSyncer + dirty bool // best-effort detection of pool misuse + should CheckWriteAction + cores []Core +} + +func (ce *CheckedEntry) reset() { + ce.Entry = Entry{} + ce.ErrorOutput = nil + ce.dirty = false + ce.should = WriteThenNoop + for i := range ce.cores { + // don't keep references to cores + ce.cores[i] = nil + } + ce.cores = ce.cores[:0] +} + +// Write writes the entry to the stored Cores, returns any errors, and returns +// the CheckedEntry reference to a pool for immediate re-use. Finally, it +// executes any required CheckWriteAction. +func (ce *CheckedEntry) Write(fields ...Field) { + if ce == nil { + return + } + + if ce.dirty { + if ce.ErrorOutput != nil { + // Make a best effort to detect unsafe re-use of this CheckedEntry. + // If the entry is dirty, log an internal error; because the + // CheckedEntry is being used after it was returned to the pool, + // the message may be an amalgamation from multiple call sites. + fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", time.Now(), ce.Entry) + ce.ErrorOutput.Sync() + } + return + } + ce.dirty = true + + var err error + for i := range ce.cores { + err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) + } + if ce.ErrorOutput != nil { + if err != nil { + fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", time.Now(), err) + ce.ErrorOutput.Sync() + } + } + + should, msg := ce.should, ce.Message + putCheckedEntry(ce) + + switch should { + case WriteThenPanic: + panic(msg) + case WriteThenFatal: + exit.Exit() + } +} + +// AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be +// used by Core.Check implementations, and is safe to call on nil CheckedEntry +// references. +func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.cores = append(ce.cores, core) + return ce +} + +// Should sets this CheckedEntry's CheckWriteAction, which controls whether a +// Core will panic or fatal after writing this log entry. Like AddCore, it's +// safe to call on nil CheckedEntry references. +func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.should = should + return ce +} diff --git a/vendor/go.uber.org/zap/zapcore/error.go b/vendor/go.uber.org/zap/zapcore/error.go new file mode 100644 index 000000000..a67c7bacc --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/error.go @@ -0,0 +1,120 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "sync" +) + +// Encodes the given error into fields of an object. A field with the given +// name is added for the error message. +// +// If the error implements fmt.Formatter, a field with the name ${key}Verbose +// is also added with the full verbose error message. +// +// Finally, if the error implements errorGroup (from go.uber.org/multierr) or +// causer (from github.com/pkg/errors), a ${key}Causes field is added with an +// array of objects containing the errors this error was comprised of. +// +// { +// "error": err.Error(), +// "errorVerbose": fmt.Sprintf("%+v", err), +// "errorCauses": [ +// ... +// ], +// } +func encodeError(key string, err error, enc ObjectEncoder) error { + basic := err.Error() + enc.AddString(key, basic) + + switch e := err.(type) { + case errorGroup: + return enc.AddArray(key+"Causes", errArray(e.Errors())) + case fmt.Formatter: + verbose := fmt.Sprintf("%+v", e) + if verbose != basic { + // This is a rich error type, like those produced by + // github.com/pkg/errors. + enc.AddString(key+"Verbose", verbose) + } + } + return nil +} + +type errorGroup interface { + // Provides read-only access to the underlying list of errors, preferably + // without causing any allocs. + Errors() []error +} + +type causer interface { + // Provides access to the error that caused this error. + Cause() error +} + +// Note that errArry and errArrayElem are very similar to the version +// implemented in the top-level error.go file. We can't re-use this because +// that would require exporting errArray as part of the zapcore API. + +// Encodes a list of errors using the standard error encoding logic. +type errArray []error + +func (errs errArray) MarshalLogArray(arr ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + + el := newErrArrayElem(errs[i]) + arr.AppendObject(el) + el.Free() + } + return nil +} + +var _errArrayElemPool = sync.Pool{New: func() interface{} { + return &errArrayElem{} +}} + +// Encodes any error into a {"error": ...} re-using the same errors logic. +// +// May be passed in place of an array to build a single-element array. +type errArrayElem struct{ err error } + +func newErrArrayElem(err error) *errArrayElem { + e := _errArrayElemPool.Get().(*errArrayElem) + e.err = err + return e +} + +func (e *errArrayElem) MarshalLogArray(arr ArrayEncoder) error { + return arr.AppendObject(e) +} + +func (e *errArrayElem) MarshalLogObject(enc ObjectEncoder) error { + return encodeError("error", e.err, enc) +} + +func (e *errArrayElem) Free() { + e.err = nil + _errArrayElemPool.Put(e) +} diff --git a/vendor/go.uber.org/zap/zapcore/field.go b/vendor/go.uber.org/zap/zapcore/field.go new file mode 100644 index 000000000..6a5e33e2f --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/field.go @@ -0,0 +1,201 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "fmt" + "math" + "reflect" + "time" +) + +// A FieldType indicates which member of the Field union struct should be used +// and how it should be serialized. +type FieldType uint8 + +const ( + // UnknownType is the default field type. Attempting to add it to an encoder will panic. + UnknownType FieldType = iota + // ArrayMarshalerType indicates that the field carries an ArrayMarshaler. + ArrayMarshalerType + // ObjectMarshalerType indicates that the field carries an ObjectMarshaler. + ObjectMarshalerType + // BinaryType indicates that the field carries an opaque binary blob. + BinaryType + // BoolType indicates that the field carries a bool. + BoolType + // ByteStringType indicates that the field carries UTF-8 encoded bytes. + ByteStringType + // Complex128Type indicates that the field carries a complex128. + Complex128Type + // Complex64Type indicates that the field carries a complex128. + Complex64Type + // DurationType indicates that the field carries a time.Duration. + DurationType + // Float64Type indicates that the field carries a float64. + Float64Type + // Float32Type indicates that the field carries a float32. + Float32Type + // Int64Type indicates that the field carries an int64. + Int64Type + // Int32Type indicates that the field carries an int32. + Int32Type + // Int16Type indicates that the field carries an int16. + Int16Type + // Int8Type indicates that the field carries an int8. + Int8Type + // StringType indicates that the field carries a string. + StringType + // TimeType indicates that the field carries a time.Time. + TimeType + // Uint64Type indicates that the field carries a uint64. + Uint64Type + // Uint32Type indicates that the field carries a uint32. + Uint32Type + // Uint16Type indicates that the field carries a uint16. + Uint16Type + // Uint8Type indicates that the field carries a uint8. + Uint8Type + // UintptrType indicates that the field carries a uintptr. + UintptrType + // ReflectType indicates that the field carries an interface{}, which should + // be serialized using reflection. + ReflectType + // NamespaceType signals the beginning of an isolated namespace. All + // subsequent fields should be added to the new namespace. + NamespaceType + // StringerType indicates that the field carries a fmt.Stringer. + StringerType + // ErrorType indicates that the field carries an error. + ErrorType + // SkipType indicates that the field is a no-op. + SkipType +) + +// A Field is a marshaling operation used to add a key-value pair to a logger's +// context. Most fields are lazily marshaled, so it's inexpensive to add fields +// to disabled debug-level log statements. +type Field struct { + Key string + Type FieldType + Integer int64 + String string + Interface interface{} +} + +// AddTo exports a field through the ObjectEncoder interface. It's primarily +// useful to library authors, and shouldn't be necessary in most applications. +func (f Field) AddTo(enc ObjectEncoder) { + var err error + + switch f.Type { + case ArrayMarshalerType: + err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler)) + case ObjectMarshalerType: + err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler)) + case BinaryType: + enc.AddBinary(f.Key, f.Interface.([]byte)) + case BoolType: + enc.AddBool(f.Key, f.Integer == 1) + case ByteStringType: + enc.AddByteString(f.Key, f.Interface.([]byte)) + case Complex128Type: + enc.AddComplex128(f.Key, f.Interface.(complex128)) + case Complex64Type: + enc.AddComplex64(f.Key, f.Interface.(complex64)) + case DurationType: + enc.AddDuration(f.Key, time.Duration(f.Integer)) + case Float64Type: + enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer))) + case Float32Type: + enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer))) + case Int64Type: + enc.AddInt64(f.Key, f.Integer) + case Int32Type: + enc.AddInt32(f.Key, int32(f.Integer)) + case Int16Type: + enc.AddInt16(f.Key, int16(f.Integer)) + case Int8Type: + enc.AddInt8(f.Key, int8(f.Integer)) + case StringType: + enc.AddString(f.Key, f.String) + case TimeType: + if f.Interface != nil { + enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location))) + } else { + // Fall back to UTC if location is nil. + enc.AddTime(f.Key, time.Unix(0, f.Integer)) + } + case Uint64Type: + enc.AddUint64(f.Key, uint64(f.Integer)) + case Uint32Type: + enc.AddUint32(f.Key, uint32(f.Integer)) + case Uint16Type: + enc.AddUint16(f.Key, uint16(f.Integer)) + case Uint8Type: + enc.AddUint8(f.Key, uint8(f.Integer)) + case UintptrType: + enc.AddUintptr(f.Key, uintptr(f.Integer)) + case ReflectType: + err = enc.AddReflected(f.Key, f.Interface) + case NamespaceType: + enc.OpenNamespace(f.Key) + case StringerType: + enc.AddString(f.Key, f.Interface.(fmt.Stringer).String()) + case ErrorType: + encodeError(f.Key, f.Interface.(error), enc) + case SkipType: + break + default: + panic(fmt.Sprintf("unknown field type: %v", f)) + } + + if err != nil { + enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error()) + } +} + +// Equals returns whether two fields are equal. For non-primitive types such as +// errors, marshalers, or reflect types, it uses reflect.DeepEqual. +func (f Field) Equals(other Field) bool { + if f.Type != other.Type { + return false + } + if f.Key != other.Key { + return false + } + + switch f.Type { + case BinaryType, ByteStringType: + return bytes.Equal(f.Interface.([]byte), other.Interface.([]byte)) + case ArrayMarshalerType, ObjectMarshalerType, ErrorType, ReflectType: + return reflect.DeepEqual(f.Interface, other.Interface) + default: + return f == other + } +} + +func addFields(enc ObjectEncoder, fields []Field) { + for i := range fields { + fields[i].AddTo(enc) + } +} diff --git a/vendor/go.uber.org/zap/zapcore/hook.go b/vendor/go.uber.org/zap/zapcore/hook.go new file mode 100644 index 000000000..5db4afb30 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/hook.go @@ -0,0 +1,68 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type hooked struct { + Core + funcs []func(Entry) error +} + +// RegisterHooks wraps a Core and runs a collection of user-defined callback +// hooks each time a message is logged. Execution of the callbacks is blocking. +// +// This offers users an easy way to register simple callbacks (e.g., metrics +// collection) without implementing the full Core interface. +func RegisterHooks(core Core, hooks ...func(Entry) error) Core { + funcs := append([]func(Entry) error{}, hooks...) + return &hooked{ + Core: core, + funcs: funcs, + } +} + +func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + // Let the wrapped Core decide whether to log this message or not. This + // also gives the downstream a chance to register itself directly with the + // CheckedEntry. + if downstream := h.Core.Check(ent, ce); downstream != nil { + return downstream.AddCore(ent, h) + } + return ce +} + +func (h *hooked) With(fields []Field) Core { + return &hooked{ + Core: h.Core.With(fields), + funcs: h.funcs, + } +} + +func (h *hooked) Write(ent Entry, _ []Field) error { + // Since our downstream had a chance to register itself directly with the + // CheckedMessage, we don't need to call it here. + var err error + for i := range h.funcs { + err = multierr.Append(err, h.funcs[i](ent)) + } + return err +} diff --git a/vendor/go.uber.org/zap/zapcore/json_encoder.go b/vendor/go.uber.org/zap/zapcore/json_encoder.go new file mode 100644 index 000000000..1006ba2b1 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/json_encoder.go @@ -0,0 +1,480 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/base64" + "encoding/json" + "math" + "sync" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" +) + +// For JSON-escaping; see jsonEncoder.safeAddString below. +const _hex = "0123456789abcdef" + +var _jsonPool = sync.Pool{New: func() interface{} { + return &jsonEncoder{} +}} + +func getJSONEncoder() *jsonEncoder { + return _jsonPool.Get().(*jsonEncoder) +} + +func putJSONEncoder(enc *jsonEncoder) { + enc.EncoderConfig = nil + enc.buf = nil + enc.spaced = false + enc.openNamespaces = 0 + _jsonPool.Put(enc) +} + +type jsonEncoder struct { + *EncoderConfig + buf *buffer.Buffer + spaced bool // include spaces after colons and commas + openNamespaces int +} + +// NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder +// appropriately escapes all field keys and values. +// +// Note that the encoder doesn't deduplicate keys, so it's possible to produce +// a message like +// {"foo":"bar","foo":"baz"} +// This is permitted by the JSON specification, but not encouraged. Many +// libraries will ignore duplicate key-value pairs (typically keeping the last +// pair) when unmarshaling, but users should attempt to avoid adding duplicate +// keys. +func NewJSONEncoder(cfg EncoderConfig) Encoder { + return newJSONEncoder(cfg, false) +} + +func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { + return &jsonEncoder{ + EncoderConfig: &cfg, + buf: bufferpool.Get(), + spaced: spaced, + } +} + +func (enc *jsonEncoder) AddArray(key string, arr ArrayMarshaler) error { + enc.addKey(key) + return enc.AppendArray(arr) +} + +func (enc *jsonEncoder) AddObject(key string, obj ObjectMarshaler) error { + enc.addKey(key) + return enc.AppendObject(obj) +} + +func (enc *jsonEncoder) AddBinary(key string, val []byte) { + enc.AddString(key, base64.StdEncoding.EncodeToString(val)) +} + +func (enc *jsonEncoder) AddByteString(key string, val []byte) { + enc.addKey(key) + enc.AppendByteString(val) +} + +func (enc *jsonEncoder) AddBool(key string, val bool) { + enc.addKey(key) + enc.AppendBool(val) +} + +func (enc *jsonEncoder) AddComplex128(key string, val complex128) { + enc.addKey(key) + enc.AppendComplex128(val) +} + +func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { + enc.addKey(key) + enc.AppendDuration(val) +} + +func (enc *jsonEncoder) AddFloat64(key string, val float64) { + enc.addKey(key) + enc.AppendFloat64(val) +} + +func (enc *jsonEncoder) AddInt64(key string, val int64) { + enc.addKey(key) + enc.AppendInt64(val) +} + +func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { + marshaled, err := json.Marshal(obj) + if err != nil { + return err + } + enc.addKey(key) + _, err = enc.buf.Write(marshaled) + return err +} + +func (enc *jsonEncoder) OpenNamespace(key string) { + enc.addKey(key) + enc.buf.AppendByte('{') + enc.openNamespaces++ +} + +func (enc *jsonEncoder) AddString(key, val string) { + enc.addKey(key) + enc.AppendString(val) +} + +func (enc *jsonEncoder) AddTime(key string, val time.Time) { + enc.addKey(key) + enc.AppendTime(val) +} + +func (enc *jsonEncoder) AddUint64(key string, val uint64) { + enc.addKey(key) + enc.AppendUint64(val) +} + +func (enc *jsonEncoder) AppendArray(arr ArrayMarshaler) error { + enc.addElementSeparator() + enc.buf.AppendByte('[') + err := arr.MarshalLogArray(enc) + enc.buf.AppendByte(']') + return err +} + +func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { + enc.addElementSeparator() + enc.buf.AppendByte('{') + err := obj.MarshalLogObject(enc) + enc.buf.AppendByte('}') + return err +} + +func (enc *jsonEncoder) AppendBool(val bool) { + enc.addElementSeparator() + enc.buf.AppendBool(val) +} + +func (enc *jsonEncoder) AppendByteString(val []byte) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddByteString(val) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendComplex128(val complex128) { + enc.addElementSeparator() + // 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 *jsonEncoder) 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 *jsonEncoder) AppendInt64(val int64) { + enc.addElementSeparator() + enc.buf.AppendInt(val) +} + +func (enc *jsonEncoder) AppendReflected(val interface{}) error { + marshaled, err := json.Marshal(val) + if err != nil { + return err + } + enc.addElementSeparator() + _, err = enc.buf.Write(marshaled) + return err +} + +func (enc *jsonEncoder) AppendString(val string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(val) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) 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 *jsonEncoder) AppendUint64(val uint64) { + enc.addElementSeparator() + enc.buf.AppendUint(val) +} + +func (enc *jsonEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) } +func (enc *jsonEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) } +func (enc *jsonEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) } +func (enc *jsonEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) } +func (enc *jsonEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) } +func (enc *jsonEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) } + +func (enc *jsonEncoder) Clone() Encoder { + clone := enc.clone() + clone.buf.Write(enc.buf.Bytes()) + return clone +} + +func (enc *jsonEncoder) clone() *jsonEncoder { + clone := getJSONEncoder() + clone.EncoderConfig = enc.EncoderConfig + clone.spaced = enc.spaced + clone.openNamespaces = enc.openNamespaces + clone.buf = bufferpool.Get() + return clone +} + +func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + final := enc.clone() + final.buf.AppendByte('{') + + if final.LevelKey != "" { + final.addKey(final.LevelKey) + cur := final.buf.Len() + final.EncodeLevel(ent.Level, final) + if cur == final.buf.Len() { + // User-supplied EncodeLevel was a no-op. Fall back to strings to keep + // output JSON valid. + final.AppendString(ent.Level.String()) + } + } + if final.TimeKey != "" { + final.AddTime(final.TimeKey, ent.Time) + } + if ent.LoggerName != "" && final.NameKey != "" { + final.addKey(final.NameKey) + cur := final.buf.Len() + nameEncoder := final.EncodeName + + // if no name encoder provided, fall back to FullNameEncoder for backwards + // compatibility + if nameEncoder == nil { + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, final) + if cur == final.buf.Len() { + // User-supplied EncodeName was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.LoggerName) + } + } + if ent.Caller.Defined && final.CallerKey != "" { + final.addKey(final.CallerKey) + cur := final.buf.Len() + final.EncodeCaller(ent.Caller, final) + if cur == final.buf.Len() { + // User-supplied EncodeCaller was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.Caller.String()) + } + } + if final.MessageKey != "" { + final.addKey(enc.MessageKey) + final.AppendString(ent.Message) + } + if enc.buf.Len() > 0 { + final.addElementSeparator() + final.buf.Write(enc.buf.Bytes()) + } + addFields(final, fields) + final.closeOpenNamespaces() + if ent.Stack != "" && final.StacktraceKey != "" { + final.AddString(final.StacktraceKey, ent.Stack) + } + final.buf.AppendByte('}') + if final.LineEnding != "" { + final.buf.AppendString(final.LineEnding) + } else { + final.buf.AppendString(DefaultLineEnding) + } + + ret := final.buf + putJSONEncoder(final) + return ret, nil +} + +func (enc *jsonEncoder) truncate() { + enc.buf.Reset() +} + +func (enc *jsonEncoder) closeOpenNamespaces() { + for i := 0; i < enc.openNamespaces; i++ { + enc.buf.AppendByte('}') + } +} + +func (enc *jsonEncoder) addKey(key string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(key) + enc.buf.AppendByte('"') + enc.buf.AppendByte(':') + if enc.spaced { + enc.buf.AppendByte(' ') + } +} + +func (enc *jsonEncoder) addElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + switch enc.buf.Bytes()[last] { + case '{', '[', ':', ',', ' ': + return + default: + enc.buf.AppendByte(',') + if enc.spaced { + enc.buf.AppendByte(' ') + } + } +} + +func (enc *jsonEncoder) appendFloat(val float64, bitSize int) { + enc.addElementSeparator() + 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 *jsonEncoder) 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 *jsonEncoder) 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 *jsonEncoder) 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 *jsonEncoder) tryAddRuneError(r rune, size int) bool { + if r == utf8.RuneError && size == 1 { + enc.buf.AppendString(`\ufffd`) + return true + } + return false +} diff --git a/vendor/go.uber.org/zap/zapcore/level.go b/vendor/go.uber.org/zap/zapcore/level.go new file mode 100644 index 000000000..e575c9f43 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/level.go @@ -0,0 +1,175 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "fmt" +) + +var errUnmarshalNilLevel = errors.New("can't unmarshal a nil *Level") + +// A Level is a logging priority. Higher levels are more important. +type Level int8 + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel Level = iota - 1 + // InfoLevel is the default logging priority. + InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel + + _minLevel = DebugLevel + _maxLevel = FatalLevel +) + +// String returns a lower-case ASCII representation of the log level. +func (l Level) String() string { + switch l { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warn" + case ErrorLevel: + return "error" + case DPanicLevel: + return "dpanic" + case PanicLevel: + return "panic" + case FatalLevel: + return "fatal" + default: + return fmt.Sprintf("Level(%d)", l) + } +} + +// CapitalString returns an all-caps ASCII representation of the log level. +func (l Level) CapitalString() string { + // Printing levels in all-caps is common enough that we should export this + // functionality. + switch l { + case DebugLevel: + return "DEBUG" + case InfoLevel: + return "INFO" + case WarnLevel: + return "WARN" + case ErrorLevel: + return "ERROR" + case DPanicLevel: + return "DPANIC" + case PanicLevel: + return "PANIC" + case FatalLevel: + return "FATAL" + default: + return fmt.Sprintf("LEVEL(%d)", l) + } +} + +// MarshalText marshals the Level to text. Note that the text representation +// drops the -Level suffix (see example). +func (l Level) MarshalText() ([]byte, error) { + return []byte(l.String()), nil +} + +// UnmarshalText unmarshals text to a level. Like MarshalText, UnmarshalText +// expects the text representation of a Level to drop the -Level suffix (see +// example). +// +// In particular, this makes it easy to configure logging levels using YAML, +// TOML, or JSON files. +func (l *Level) UnmarshalText(text []byte) error { + if l == nil { + return errUnmarshalNilLevel + } + if !l.unmarshalText(text) && !l.unmarshalText(bytes.ToLower(text)) { + return fmt.Errorf("unrecognized level: %q", text) + } + return nil +} + +func (l *Level) unmarshalText(text []byte) bool { + switch string(text) { + case "debug", "DEBUG": + *l = DebugLevel + case "info", "INFO", "": // make the zero value useful + *l = InfoLevel + case "warn", "WARN": + *l = WarnLevel + case "error", "ERROR": + *l = ErrorLevel + case "dpanic", "DPANIC": + *l = DPanicLevel + case "panic", "PANIC": + *l = PanicLevel + case "fatal", "FATAL": + *l = FatalLevel + default: + return false + } + return true +} + +// Set sets the level for the flag.Value interface. +func (l *Level) Set(s string) error { + return l.UnmarshalText([]byte(s)) +} + +// Get gets the level for the flag.Getter interface. +func (l *Level) Get() interface{} { + return *l +} + +// Enabled returns true if the given level is at or above this level. +func (l Level) Enabled(lvl Level) bool { + return lvl >= l +} + +// LevelEnabler decides whether a given logging level is enabled when logging a +// message. +// +// Enablers are intended to be used to implement deterministic filters; +// concerns like sampling are better implemented as a Core. +// +// Each concrete Level value implements a static LevelEnabler which returns +// true for itself and all higher logging levels. For example WarnLevel.Enabled() +// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and +// FatalLevel, but return false for InfoLevel and DebugLevel. +type LevelEnabler interface { + Enabled(Level) bool +} diff --git a/vendor/go.uber.org/zap/zapcore/level_strings.go b/vendor/go.uber.org/zap/zapcore/level_strings.go new file mode 100644 index 000000000..7af8dadcb --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/level_strings.go @@ -0,0 +1,46 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/zap/internal/color" + +var ( + _levelToColor = map[Level]color.Color{ + DebugLevel: color.Magenta, + InfoLevel: color.Blue, + WarnLevel: color.Yellow, + ErrorLevel: color.Red, + DPanicLevel: color.Red, + PanicLevel: color.Red, + FatalLevel: color.Red, + } + _unknownLevelColor = color.Red + + _levelToLowercaseColorString = make(map[Level]string, len(_levelToColor)) + _levelToCapitalColorString = make(map[Level]string, len(_levelToColor)) +) + +func init() { + for level, color := range _levelToColor { + _levelToLowercaseColorString[level] = color.Add(level.String()) + _levelToCapitalColorString[level] = color.Add(level.CapitalString()) + } +} diff --git a/vendor/go.uber.org/zap/zapcore/marshaler.go b/vendor/go.uber.org/zap/zapcore/marshaler.go new file mode 100644 index 000000000..2627a653d --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/marshaler.go @@ -0,0 +1,53 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// ObjectMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +type ObjectMarshaler interface { + MarshalLogObject(ObjectEncoder) error +} + +// ObjectMarshalerFunc is a type adapter that turns a function into an +// ObjectMarshaler. +type ObjectMarshalerFunc func(ObjectEncoder) error + +// MarshalLogObject calls the underlying function. +func (f ObjectMarshalerFunc) MarshalLogObject(enc ObjectEncoder) error { + return f(enc) +} + +// ArrayMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +type ArrayMarshaler interface { + MarshalLogArray(ArrayEncoder) error +} + +// ArrayMarshalerFunc is a type adapter that turns a function into an +// ArrayMarshaler. +type ArrayMarshalerFunc func(ArrayEncoder) error + +// MarshalLogArray calls the underlying function. +func (f ArrayMarshalerFunc) MarshalLogArray(enc ArrayEncoder) error { + return f(enc) +} diff --git a/vendor/go.uber.org/zap/zapcore/memory_encoder.go b/vendor/go.uber.org/zap/zapcore/memory_encoder.go new file mode 100644 index 000000000..5c46bc13d --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/memory_encoder.go @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// MapObjectEncoder is an ObjectEncoder backed by a simple +// map[string]interface{}. It's not fast enough for production use, but it's +// helpful in tests. +type MapObjectEncoder struct { + // Fields contains the entire encoded log context. + Fields map[string]interface{} + // cur is a pointer to the namespace we're currently writing to. + cur map[string]interface{} +} + +// NewMapObjectEncoder creates a new map-backed ObjectEncoder. +func NewMapObjectEncoder() *MapObjectEncoder { + m := make(map[string]interface{}) + return &MapObjectEncoder{ + Fields: m, + cur: m, + } +} + +// AddArray implements ObjectEncoder. +func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { + arr := &sliceArrayEncoder{} + err := v.MarshalLogArray(arr) + m.cur[key] = arr.elems + return err +} + +// AddObject implements ObjectEncoder. +func (m *MapObjectEncoder) AddObject(k string, v ObjectMarshaler) error { + newMap := NewMapObjectEncoder() + m.cur[k] = newMap.Fields + return v.MarshalLogObject(newMap) +} + +// AddBinary implements ObjectEncoder. +func (m *MapObjectEncoder) AddBinary(k string, v []byte) { m.cur[k] = v } + +// AddByteString implements ObjectEncoder. +func (m *MapObjectEncoder) AddByteString(k string, v []byte) { m.cur[k] = string(v) } + +// AddBool implements ObjectEncoder. +func (m *MapObjectEncoder) AddBool(k string, v bool) { m.cur[k] = v } + +// AddDuration implements ObjectEncoder. +func (m MapObjectEncoder) AddDuration(k string, v time.Duration) { m.cur[k] = v } + +// AddComplex128 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex128(k string, v complex128) { m.cur[k] = v } + +// AddComplex64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex64(k string, v complex64) { m.cur[k] = v } + +// AddFloat64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat64(k string, v float64) { m.cur[k] = v } + +// AddFloat32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat32(k string, v float32) { m.cur[k] = v } + +// AddInt implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt(k string, v int) { m.cur[k] = v } + +// AddInt64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt64(k string, v int64) { m.cur[k] = v } + +// AddInt32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt32(k string, v int32) { m.cur[k] = v } + +// AddInt16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt16(k string, v int16) { m.cur[k] = v } + +// AddInt8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt8(k string, v int8) { m.cur[k] = v } + +// AddString implements ObjectEncoder. +func (m *MapObjectEncoder) AddString(k string, v string) { m.cur[k] = v } + +// AddTime implements ObjectEncoder. +func (m MapObjectEncoder) AddTime(k string, v time.Time) { m.cur[k] = v } + +// AddUint implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint(k string, v uint) { m.cur[k] = v } + +// AddUint64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint64(k string, v uint64) { m.cur[k] = v } + +// AddUint32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint32(k string, v uint32) { m.cur[k] = v } + +// AddUint16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint16(k string, v uint16) { m.cur[k] = v } + +// AddUint8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint8(k string, v uint8) { m.cur[k] = v } + +// AddUintptr implements ObjectEncoder. +func (m *MapObjectEncoder) AddUintptr(k string, v uintptr) { m.cur[k] = v } + +// AddReflected implements ObjectEncoder. +func (m *MapObjectEncoder) AddReflected(k string, v interface{}) error { + m.cur[k] = v + return nil +} + +// OpenNamespace implements ObjectEncoder. +func (m *MapObjectEncoder) OpenNamespace(k string) { + ns := make(map[string]interface{}) + m.cur[k] = ns + m.cur = ns +} + +// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like +// the MapObjectEncoder, it's not designed for production use. +type sliceArrayEncoder struct { + elems []interface{} +} + +func (s *sliceArrayEncoder) AppendArray(v ArrayMarshaler) error { + enc := &sliceArrayEncoder{} + err := v.MarshalLogArray(enc) + s.elems = append(s.elems, enc.elems) + return err +} + +func (s *sliceArrayEncoder) AppendObject(v ObjectMarshaler) error { + m := 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) } diff --git a/vendor/go.uber.org/zap/zapcore/sampler.go b/vendor/go.uber.org/zap/zapcore/sampler.go new file mode 100644 index 000000000..e31641863 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/sampler.go @@ -0,0 +1,134 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "time" + + "go.uber.org/atomic" +) + +const ( + _numLevels = _maxLevel - _minLevel + 1 + _countersPerLevel = 4096 +) + +type counter struct { + resetAt atomic.Int64 + counter atomic.Uint64 +} + +type counters [_numLevels][_countersPerLevel]counter + +func newCounters() *counters { + return &counters{} +} + +func (cs *counters) get(lvl Level, key string) *counter { + i := lvl - _minLevel + j := fnv32a(key) % _countersPerLevel + return &cs[i][j] +} + +// fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc +func fnv32a(s string) uint32 { + const ( + offset32 = 2166136261 + prime32 = 16777619 + ) + hash := uint32(offset32) + for i := 0; i < len(s); i++ { + hash ^= uint32(s[i]) + hash *= prime32 + } + return hash +} + +func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { + tn := t.UnixNano() + resetAfter := c.resetAt.Load() + if resetAfter > tn { + return c.counter.Inc() + } + + c.counter.Store(1) + + newResetAfter := tn + tick.Nanoseconds() + if !c.resetAt.CAS(resetAfter, newResetAfter) { + // We raced with another goroutine trying to reset, and it also reset + // the counter to 1, so we need to reincrement the counter. + return c.counter.Inc() + } + + return 1 +} + +type sampler struct { + Core + + counts *counters + tick time.Duration + first, thereafter uint64 +} + +// NewSampler creates a Core that samples incoming entries, which caps the CPU +// and I/O load of logging while attempting to preserve a representative subset +// of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// Keep in mind that zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { + return &sampler{ + Core: core, + tick: tick, + counts: newCounters(), + first: uint64(first), + thereafter: uint64(thereafter), + } +} + +func (s *sampler) With(fields []Field) Core { + return &sampler{ + Core: s.Core.With(fields), + tick: s.tick, + counts: s.counts, + first: s.first, + thereafter: s.thereafter, + } +} + +func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !s.Enabled(ent.Level) { + return ce + } + + counter := s.counts.get(ent.Level, ent.Message) + n := counter.IncCheckReset(ent.Time, s.tick) + if n > s.first && (n-s.first)%s.thereafter != 0 { + return ce + } + return s.Core.Check(ent, ce) +} diff --git a/vendor/go.uber.org/zap/zapcore/tee.go b/vendor/go.uber.org/zap/zapcore/tee.go new file mode 100644 index 000000000..07a32eef9 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/tee.go @@ -0,0 +1,81 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type multiCore []Core + +// NewTee creates a Core that duplicates log entries into two or more +// underlying Cores. +// +// Calling it with a single Core returns the input unchanged, and calling +// it with no input returns a no-op Core. +func NewTee(cores ...Core) Core { + switch len(cores) { + case 0: + return NewNopCore() + case 1: + return cores[0] + default: + return multiCore(cores) + } +} + +func (mc multiCore) With(fields []Field) Core { + clone := make(multiCore, len(mc)) + for i := range mc { + clone[i] = mc[i].With(fields) + } + return clone +} + +func (mc multiCore) Enabled(lvl Level) bool { + for i := range mc { + if mc[i].Enabled(lvl) { + return true + } + } + return false +} + +func (mc multiCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + for i := range mc { + ce = mc[i].Check(ent, ce) + } + return ce +} + +func (mc multiCore) Write(ent Entry, fields []Field) error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Write(ent, fields)) + } + return err +} + +func (mc multiCore) Sync() error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Sync()) + } + return err +} diff --git a/vendor/go.uber.org/zap/zapcore/write_syncer.go b/vendor/go.uber.org/zap/zapcore/write_syncer.go new file mode 100644 index 000000000..209e25fe2 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/write_syncer.go @@ -0,0 +1,123 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "io" + "sync" + + "go.uber.org/multierr" +) + +// A WriteSyncer is an io.Writer that can also flush any buffered data. Note +// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer. +type WriteSyncer interface { + io.Writer + Sync() error +} + +// AddSync converts an io.Writer to a WriteSyncer. It attempts to be +// intelligent: if the concrete type of the io.Writer implements WriteSyncer, +// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync. +func AddSync(w io.Writer) WriteSyncer { + switch w := w.(type) { + case WriteSyncer: + return w + default: + return writerWrapper{w} + } +} + +type lockedWriteSyncer struct { + sync.Mutex + ws WriteSyncer +} + +// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In +// particular, *os.Files must be locked before use. +func Lock(ws WriteSyncer) WriteSyncer { + if _, ok := ws.(*lockedWriteSyncer); ok { + // no need to layer on another lock + return ws + } + return &lockedWriteSyncer{ws: ws} +} + +func (s *lockedWriteSyncer) Write(bs []byte) (int, error) { + s.Lock() + n, err := s.ws.Write(bs) + s.Unlock() + return n, err +} + +func (s *lockedWriteSyncer) Sync() error { + s.Lock() + err := s.ws.Sync() + s.Unlock() + return err +} + +type writerWrapper struct { + io.Writer +} + +func (w writerWrapper) Sync() error { + return nil +} + +type multiWriteSyncer []WriteSyncer + +// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes +// and sync calls, much like io.MultiWriter. +func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { + if len(ws) == 1 { + return ws[0] + } + // Copy to protect against https://github.com/golang/go/issues/7809 + return multiWriteSyncer(append([]WriteSyncer(nil), ws...)) +} + +// See https://golang.org/src/io/multi.go +// When not all underlying syncers write the same number of bytes, +// the smallest number is returned even though Write() is called on +// all of them. +func (ws multiWriteSyncer) Write(p []byte) (int, error) { + var writeErr error + nWritten := 0 + for _, w := range ws { + n, err := w.Write(p) + writeErr = multierr.Append(writeErr, err) + if nWritten == 0 && n != 0 { + nWritten = n + } else if n < nWritten { + nWritten = n + } + } + return nWritten, writeErr +} + +func (ws multiWriteSyncer) Sync() error { + var err error + for _, w := range ws { + err = multierr.Append(err, w.Sync()) + } + return err +} diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore b/vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore new file mode 100644 index 000000000..836562412 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE b/vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE new file mode 100644 index 000000000..c3d4cc307 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Nate Finch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/README.md b/vendor/gopkg.in/natefinch/lumberjack.v2/README.md new file mode 100644 index 000000000..9e9715453 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/README.md @@ -0,0 +1,174 @@ +# lumberjack [![GoDoc](https://godoc.org/gopkg.in/natefinch/lumberjack.v2?status.png)](https://godoc.org/gopkg.in/natefinch/lumberjack.v2) [![Build Status](https://drone.io/github.com/natefinch/lumberjack/status.png)](https://drone.io/github.com/natefinch/lumberjack/latest) [![Build status](https://ci.appveyor.com/api/projects/status/00gchpxtg4gkrt5d)](https://ci.appveyor.com/project/natefinch/lumberjack) [![Coverage Status](https://coveralls.io/repos/natefinch/lumberjack/badge.svg?branch=v2.0)](https://coveralls.io/r/natefinch/lumberjack?branch=v2.0) + +### Lumberjack is a Go package for writing logs to rolling files. + +Package lumberjack provides a rolling logger. + +Note that this is v2.0 of lumberjack, and should be imported using gopkg.in +thusly: + + import "gopkg.in/natefinch/lumberjack.v2" + +The package name remains simply lumberjack, and the code resides at +https://github.com/natefinch/lumberjack under the v2.0 branch. + +Lumberjack is intended to be one part of a logging infrastructure. +It is not an all-in-one solution, but instead is a pluggable +component at the bottom of the logging stack that simply controls the files +to which logs are written. + +Lumberjack plays well with any logging package that can write to an +io.Writer, including the standard library's log package. + +Lumberjack assumes that only one process is writing to the output files. +Using the same lumberjack configuration from multiple processes on the same +machine will result in improper behavior. + + +**Example** + +To use lumberjack with the standard library's log package, just pass it into the SetOutput function when your application starts. + +Code: + +```go +log.SetOutput(&lumberjack.Logger{ + Filename: "/var/log/myapp/foo.log", + MaxSize: 500, // megabytes + MaxBackups: 3, + MaxAge: 28, //days +}) +``` + + + +## type Logger +``` go +type Logger struct { + // Filename is the file to write logs to. Backup log files will be retained + // in the same directory. It uses -lumberjack.log in + // os.TempDir() if empty. + Filename string `json:"filename" yaml:"filename"` + + // MaxSize is the maximum size in megabytes of the log file before it gets + // rotated. It defaults to 100 megabytes. + MaxSize int `json:"maxsize" yaml:"maxsize"` + + // MaxAge is the maximum number of days to retain old log files based on the + // timestamp encoded in their filename. Note that a day is defined as 24 + // hours and may not exactly correspond to calendar days due to daylight + // savings, leap seconds, etc. The default is not to remove old log files + // based on age. + MaxAge int `json:"maxage" yaml:"maxage"` + + // MaxBackups is the maximum number of old log files to retain. The default + // is to retain all old log files (though MaxAge may still cause them to get + // deleted.) + MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + + // LocalTime determines if the time used for formatting the timestamps in + // backup files is the computer's local time. The default is to use UTC + // time. + LocalTime bool `json:"localtime" yaml:"localtime"` + // contains filtered or unexported fields +} +``` +Logger is an io.WriteCloser that writes to the specified filename. + +Logger opens or creates the logfile on first Write. If the file exists and +is less than MaxSize megabytes, lumberjack will open and append to that file. +If the file exists and its size is >= MaxSize megabytes, the file is renamed +by putting the current time in a timestamp in the name immediately before the +file's extension (or the end of the filename if there's no extension). A new +log file is then created using original filename. + +Whenever a write would cause the current log file exceed MaxSize megabytes, +the current file is closed, renamed, and a new log file created with the +original name. Thus, the filename you give Logger is always the "current" log +file. + +Backups use the log file name given to Logger, in the form `name-timestamp.ext` +where name is the filename without the extension, timestamp is the time at which +the log was rotated formatted with the time.Time format of +`2006-01-02T15-04-05.000` and the extension is the original extension. For +example, if your Logger.Filename is `/var/log/foo/server.log`, a backup created +at 6:30pm on Nov 11 2016 would use the filename +`/var/log/foo/server-2016-11-04T18-30-00.000.log` + +### Cleaning Up Old Log Files +Whenever a new logfile gets created, old log files may be deleted. The most +recent files according to the encoded timestamp will be retained, up to a +number equal to MaxBackups (or all of them if MaxBackups is 0). Any files +with an encoded timestamp older than MaxAge days are deleted, regardless of +MaxBackups. Note that the time encoded in the timestamp is the rotation +time, which may differ from the last time that file was written to. + +If MaxBackups and MaxAge are both 0, no old log files will be deleted. + + + + + + + + + + + +### func (\*Logger) Close +``` go +func (l *Logger) Close() error +``` +Close implements io.Closer, and closes the current logfile. + + + +### func (\*Logger) Rotate +``` go +func (l *Logger) Rotate() error +``` +Rotate causes Logger to close the existing log file and immediately create a +new one. This is a helper function for applications that want to initiate +rotations outside of the normal rotation rules, such as in response to +SIGHUP. After rotating, this initiates a cleanup of old log files according +to the normal rules. + +**Example** + +Example of how to rotate in response to SIGHUP. + +Code: + +```go +l := &lumberjack.Logger{} +log.SetOutput(l) +c := make(chan os.Signal, 1) +signal.Notify(c, syscall.SIGHUP) + +go func() { + for { + <-c + l.Rotate() + } +}() +``` + +### func (\*Logger) Write +``` go +func (l *Logger) Write(p []byte) (n int, err error) +``` +Write implements io.Writer. If a write would cause the log file to be larger +than MaxSize, the file is closed, renamed to include a timestamp of the +current time, and a new log file is created using the original log file name. +If the length of the write is greater than MaxSize, an error is returned. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/chown.go b/vendor/gopkg.in/natefinch/lumberjack.v2/chown.go new file mode 100644 index 000000000..11d066972 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/chown.go @@ -0,0 +1,11 @@ +// +build !linux + +package lumberjack + +import ( + "os" +) + +func chown(_ string, _ os.FileInfo) error { + return nil +} diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go b/vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go new file mode 100644 index 000000000..2758ec9ce --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go @@ -0,0 +1,19 @@ +package lumberjack + +import ( + "os" + "syscall" +) + +// os_Chown is a var so we can mock it out during tests. +var os_Chown = os.Chown + +func chown(name string, info os.FileInfo) error { + f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + f.Close() + stat := info.Sys().(*syscall.Stat_t) + return os_Chown(name, int(stat.Uid), int(stat.Gid)) +} diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go b/vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go new file mode 100644 index 000000000..ca19da440 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go @@ -0,0 +1,541 @@ +// Package lumberjack provides a rolling logger. +// +// Note that this is v2.0 of lumberjack, and should be imported using gopkg.in +// thusly: +// +// import "gopkg.in/natefinch/lumberjack.v2" +// +// The package name remains simply lumberjack, and the code resides at +// https://github.com/natefinch/lumberjack under the v2.0 branch. +// +// Lumberjack is intended to be one part of a logging infrastructure. +// It is not an all-in-one solution, but instead is a pluggable +// component at the bottom of the logging stack that simply controls the files +// to which logs are written. +// +// Lumberjack plays well with any logging package that can write to an +// io.Writer, including the standard library's log package. +// +// Lumberjack assumes that only one process is writing to the output files. +// Using the same lumberjack configuration from multiple processes on the same +// machine will result in improper behavior. +package lumberjack + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" +) + +const ( + backupTimeFormat = "2006-01-02T15-04-05.000" + compressSuffix = ".gz" + defaultMaxSize = 100 +) + +// ensure we always implement io.WriteCloser +var _ io.WriteCloser = (*Logger)(nil) + +// Logger is an io.WriteCloser that writes to the specified filename. +// +// Logger opens or creates the logfile on first Write. If the file exists and +// is less than MaxSize megabytes, lumberjack will open and append to that file. +// If the file exists and its size is >= MaxSize megabytes, the file is renamed +// by putting the current time in a timestamp in the name immediately before the +// file's extension (or the end of the filename if there's no extension). A new +// log file is then created using original filename. +// +// Whenever a write would cause the current log file exceed MaxSize megabytes, +// the current file is closed, renamed, and a new log file created with the +// original name. Thus, the filename you give Logger is always the "current" log +// file. +// +// Backups use the log file name given to Logger, in the form +// `name-timestamp.ext` where name is the filename without the extension, +// timestamp is the time at which the log was rotated formatted with the +// time.Time format of `2006-01-02T15-04-05.000` and the extension is the +// original extension. For example, if your Logger.Filename is +// `/var/log/foo/server.log`, a backup created at 6:30pm on Nov 11 2016 would +// use the filename `/var/log/foo/server-2016-11-04T18-30-00.000.log` +// +// Cleaning Up Old Log Files +// +// Whenever a new logfile gets created, old log files may be deleted. The most +// recent files according to the encoded timestamp will be retained, up to a +// number equal to MaxBackups (or all of them if MaxBackups is 0). Any files +// with an encoded timestamp older than MaxAge days are deleted, regardless of +// MaxBackups. Note that the time encoded in the timestamp is the rotation +// time, which may differ from the last time that file was written to. +// +// If MaxBackups and MaxAge are both 0, no old log files will be deleted. +type Logger struct { + // Filename is the file to write logs to. Backup log files will be retained + // in the same directory. It uses -lumberjack.log in + // os.TempDir() if empty. + Filename string `json:"filename" yaml:"filename"` + + // MaxSize is the maximum size in megabytes of the log file before it gets + // rotated. It defaults to 100 megabytes. + MaxSize int `json:"maxsize" yaml:"maxsize"` + + // MaxAge is the maximum number of days to retain old log files based on the + // timestamp encoded in their filename. Note that a day is defined as 24 + // hours and may not exactly correspond to calendar days due to daylight + // savings, leap seconds, etc. The default is not to remove old log files + // based on age. + MaxAge int `json:"maxage" yaml:"maxage"` + + // MaxBackups is the maximum number of old log files to retain. The default + // is to retain all old log files (though MaxAge may still cause them to get + // deleted.) + MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + + // LocalTime determines if the time used for formatting the timestamps in + // backup files is the computer's local time. The default is to use UTC + // time. + LocalTime bool `json:"localtime" yaml:"localtime"` + + // Compress determines if the rotated log files should be compressed + // using gzip. + Compress bool `json:"compress" yaml:"compress"` + + size int64 + file *os.File + mu sync.Mutex + + millCh chan bool + startMill sync.Once +} + +var ( + // currentTime exists so it can be mocked out by tests. + currentTime = time.Now + + // os_Stat exists so it can be mocked out by tests. + os_Stat = os.Stat + + // megabyte is the conversion factor between MaxSize and bytes. It is a + // variable so tests can mock it out and not need to write megabytes of data + // to disk. + megabyte = 1024 * 1024 +) + +// Write implements io.Writer. If a write would cause the log file to be larger +// than MaxSize, the file is closed, renamed to include a timestamp of the +// current time, and a new log file is created using the original log file name. +// If the length of the write is greater than MaxSize, an error is returned. +func (l *Logger) Write(p []byte) (n int, err error) { + l.mu.Lock() + defer l.mu.Unlock() + + writeLen := int64(len(p)) + if writeLen > l.max() { + return 0, fmt.Errorf( + "write length %d exceeds maximum file size %d", writeLen, l.max(), + ) + } + + if l.file == nil { + if err = l.openExistingOrNew(len(p)); err != nil { + return 0, err + } + } + + if l.size+writeLen > l.max() { + if err := l.rotate(); err != nil { + return 0, err + } + } + + n, err = l.file.Write(p) + l.size += int64(n) + + return n, err +} + +// Close implements io.Closer, and closes the current logfile. +func (l *Logger) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + return l.close() +} + +// close closes the file if it is open. +func (l *Logger) close() error { + if l.file == nil { + return nil + } + err := l.file.Close() + l.file = nil + return err +} + +// Rotate causes Logger to close the existing log file and immediately create a +// new one. This is a helper function for applications that want to initiate +// rotations outside of the normal rotation rules, such as in response to +// SIGHUP. After rotating, this initiates compression and removal of old log +// files according to the configuration. +func (l *Logger) Rotate() error { + l.mu.Lock() + defer l.mu.Unlock() + return l.rotate() +} + +// rotate closes the current file, moves it aside with a timestamp in the name, +// (if it exists), opens a new file with the original filename, and then runs +// post-rotation processing and removal. +func (l *Logger) rotate() error { + if err := l.close(); err != nil { + return err + } + if err := l.openNew(); err != nil { + return err + } + l.mill() + return nil +} + +// openNew opens a new log file for writing, moving any old log file out of the +// way. This methods assumes the file has already been closed. +func (l *Logger) openNew() error { + err := os.MkdirAll(l.dir(), 0744) + if err != nil { + return fmt.Errorf("can't make directories for new logfile: %s", err) + } + + name := l.filename() + mode := os.FileMode(0644) + info, err := os_Stat(name) + if err == nil { + // Copy the mode off the old logfile. + mode = info.Mode() + // move the existing file + newname := backupName(name, l.LocalTime) + if err := os.Rename(name, newname); err != nil { + return fmt.Errorf("can't rename log file: %s", err) + } + + // this is a no-op anywhere but linux + if err := chown(name, info); err != nil { + return err + } + } + + // we use truncate here because this should only get called when we've moved + // the file ourselves. if someone else creates the file in the meantime, + // just wipe out the contents. + f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) + if err != nil { + return fmt.Errorf("can't open new logfile: %s", err) + } + l.file = f + l.size = 0 + return nil +} + +// backupName creates a new filename from the given name, inserting a timestamp +// between the filename and the extension, using the local time if requested +// (otherwise UTC). +func backupName(name string, local bool) string { + dir := filepath.Dir(name) + filename := filepath.Base(name) + ext := filepath.Ext(filename) + prefix := filename[:len(filename)-len(ext)] + t := currentTime() + if !local { + t = t.UTC() + } + + timestamp := t.Format(backupTimeFormat) + return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext)) +} + +// openExistingOrNew opens the logfile if it exists and if the current write +// would not put it over MaxSize. If there is no such file or the write would +// put it over the MaxSize, a new file is created. +func (l *Logger) openExistingOrNew(writeLen int) error { + l.mill() + + filename := l.filename() + info, err := os_Stat(filename) + if os.IsNotExist(err) { + return l.openNew() + } + if err != nil { + return fmt.Errorf("error getting log file info: %s", err) + } + + if info.Size()+int64(writeLen) >= l.max() { + return l.rotate() + } + + file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + // if we fail to open the old log file for some reason, just ignore + // it and open a new log file. + return l.openNew() + } + l.file = file + l.size = info.Size() + return nil +} + +// genFilename generates the name of the logfile from the current time. +func (l *Logger) filename() string { + if l.Filename != "" { + return l.Filename + } + name := filepath.Base(os.Args[0]) + "-lumberjack.log" + return filepath.Join(os.TempDir(), name) +} + +// millRunOnce performs compression and removal of stale log files. +// Log files are compressed if enabled via configuration and old log +// files are removed, keeping at most l.MaxBackups files, as long as +// none of them are older than MaxAge. +func (l *Logger) millRunOnce() error { + if l.MaxBackups == 0 && l.MaxAge == 0 && !l.Compress { + return nil + } + + files, err := l.oldLogFiles() + if err != nil { + return err + } + + var compress, remove []logInfo + + if l.MaxBackups > 0 && l.MaxBackups < len(files) { + preserved := make(map[string]bool) + var remaining []logInfo + for _, f := range files { + // Only count the uncompressed log file or the + // compressed log file, not both. + fn := f.Name() + if strings.HasSuffix(fn, compressSuffix) { + fn = fn[:len(fn)-len(compressSuffix)] + } + preserved[fn] = true + + if len(preserved) > l.MaxBackups { + remove = append(remove, f) + } else { + remaining = append(remaining, f) + } + } + files = remaining + } + if l.MaxAge > 0 { + diff := time.Duration(int64(24*time.Hour) * int64(l.MaxAge)) + cutoff := currentTime().Add(-1 * diff) + + var remaining []logInfo + for _, f := range files { + if f.timestamp.Before(cutoff) { + remove = append(remove, f) + } else { + remaining = append(remaining, f) + } + } + files = remaining + } + + if l.Compress { + for _, f := range files { + if !strings.HasSuffix(f.Name(), compressSuffix) { + compress = append(compress, f) + } + } + } + + for _, f := range remove { + errRemove := os.Remove(filepath.Join(l.dir(), f.Name())) + if err == nil && errRemove != nil { + err = errRemove + } + } + for _, f := range compress { + fn := filepath.Join(l.dir(), f.Name()) + errCompress := compressLogFile(fn, fn+compressSuffix) + if err == nil && errCompress != nil { + err = errCompress + } + } + + return err +} + +// millRun runs in a goroutine to manage post-rotation compression and removal +// of old log files. +func (l *Logger) millRun() { + for _ = range l.millCh { + // what am I going to do, log this? + _ = l.millRunOnce() + } +} + +// mill performs post-rotation compression and removal of stale log files, +// starting the mill goroutine if necessary. +func (l *Logger) mill() { + l.startMill.Do(func() { + l.millCh = make(chan bool, 1) + go l.millRun() + }) + select { + case l.millCh <- true: + default: + } +} + +// oldLogFiles returns the list of backup log files stored in the same +// directory as the current log file, sorted by ModTime +func (l *Logger) oldLogFiles() ([]logInfo, error) { + files, err := ioutil.ReadDir(l.dir()) + if err != nil { + return nil, fmt.Errorf("can't read log file directory: %s", err) + } + logFiles := []logInfo{} + + prefix, ext := l.prefixAndExt() + + for _, f := range files { + if f.IsDir() { + continue + } + if t, err := l.timeFromName(f.Name(), prefix, ext); err == nil { + logFiles = append(logFiles, logInfo{t, f}) + continue + } + if t, err := l.timeFromName(f.Name(), prefix, ext+compressSuffix); err == nil { + logFiles = append(logFiles, logInfo{t, f}) + continue + } + // error parsing means that the suffix at the end was not generated + // by lumberjack, and therefore it's not a backup file. + } + + sort.Sort(byFormatTime(logFiles)) + + return logFiles, nil +} + +// timeFromName extracts the formatted time from the filename by stripping off +// the filename's prefix and extension. This prevents someone's filename from +// confusing time.parse. +func (l *Logger) timeFromName(filename, prefix, ext string) (time.Time, error) { + if !strings.HasPrefix(filename, prefix) { + return time.Time{}, errors.New("mismatched prefix") + } + if !strings.HasSuffix(filename, ext) { + return time.Time{}, errors.New("mismatched extension") + } + ts := filename[len(prefix) : len(filename)-len(ext)] + return time.Parse(backupTimeFormat, ts) +} + +// max returns the maximum size in bytes of log files before rolling. +func (l *Logger) max() int64 { + if l.MaxSize == 0 { + return int64(defaultMaxSize * megabyte) + } + return int64(l.MaxSize) * int64(megabyte) +} + +// dir returns the directory for the current filename. +func (l *Logger) dir() string { + return filepath.Dir(l.filename()) +} + +// prefixAndExt returns the filename part and extension part from the Logger's +// filename. +func (l *Logger) prefixAndExt() (prefix, ext string) { + filename := filepath.Base(l.filename()) + ext = filepath.Ext(filename) + prefix = filename[:len(filename)-len(ext)] + "-" + return prefix, ext +} + +// compressLogFile compresses the given log file, removing the +// uncompressed log file if successful. +func compressLogFile(src, dst string) (err error) { + f, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open log file: %v", err) + } + defer f.Close() + + fi, err := os_Stat(src) + if err != nil { + return fmt.Errorf("failed to stat log file: %v", err) + } + + if err := chown(dst, fi); err != nil { + return fmt.Errorf("failed to chown compressed log file: %v", err) + } + + // If this file already exists, we presume it was created by + // a previous attempt to compress the log file. + gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode()) + if err != nil { + return fmt.Errorf("failed to open compressed log file: %v", err) + } + defer gzf.Close() + + gz := gzip.NewWriter(gzf) + + defer func() { + if err != nil { + os.Remove(dst) + err = fmt.Errorf("failed to compress log file: %v", err) + } + }() + + if _, err := io.Copy(gz, f); err != nil { + return err + } + if err := gz.Close(); err != nil { + return err + } + if err := gzf.Close(); err != nil { + return err + } + + if err := f.Close(); err != nil { + return err + } + if err := os.Remove(src); err != nil { + return err + } + + return nil +} + +// logInfo is a convenience struct to return the filename and its embedded +// timestamp. +type logInfo struct { + timestamp time.Time + os.FileInfo +} + +// byFormatTime sorts by newest time formatted in the name. +type byFormatTime []logInfo + +func (b byFormatTime) Less(i, j int) bool { + return b[i].timestamp.After(b[j].timestamp) +} + +func (b byFormatTime) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b byFormatTime) Len() int { + return len(b) +} diff --git a/web/web.go b/web/web.go index c9d318397..56a5ab6ac 100644 --- a/web/web.go +++ b/web/web.go @@ -4,6 +4,7 @@ package web import ( + "fmt" "net/http" "path/filepath" "strings" @@ -11,20 +12,20 @@ import ( "github.com/NYTimes/gziphandler" "github.com/avct/uasurfer" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) func Init(api3 *api.API) { - l4g.Debug(utils.T("web.init.debug")) + mlog.Debug("Initializing web routes") mainrouter := api3.BaseRoutes.Root if *api3.App.Config().ServiceSettings.WebserverMode != "disabled" { staticDir, _ := utils.FindDir(model.CLIENT_DIR) - l4g.Debug("Using client directory at %v", staticDir) + mlog.Debug(fmt.Sprintf("Using client directory at %v", staticDir)) staticHandler := staticHandler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) pluginHandler := pluginHandler(api3.App.Config, http.StripPrefix("/static/plugins/", http.FileServer(http.Dir(*api3.App.Config().PluginSettings.ClientDirectory)))) diff --git a/web/web_test.go b/web/web_test.go index 09460b3b0..12099709e 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -11,6 +11,7 @@ import ( "github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" @@ -150,6 +151,15 @@ func TestIncomingWebhook(t *testing.T) { } func TestMain(m *testing.M) { + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + utils.TranslationsPreInit() status := 0 diff --git a/wsapi/status.go b/wsapi/status.go index c3d323053..113bb7ed5 100644 --- a/wsapi/status.go +++ b/wsapi/status.go @@ -4,8 +4,7 @@ package wsapi import ( - l4g "github.com/alecthomas/log4go" - + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -22,7 +21,7 @@ func (api *API) getStatuses(req *model.WebSocketRequest) (map[string]interface{} func (api *API) getStatusesByIds(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) { var userIds []string if userIds = model.ArrayFromInterface(req.Data["user_ids"]); len(userIds) == 0 { - l4g.Error(model.StringInterfaceToJson(req.Data)) + mlog.Error(model.StringInterfaceToJson(req.Data)) return nil, NewInvalidWebSocketParamError(req.Action, "user_ids") } diff --git a/wsapi/websocket_handler.go b/wsapi/websocket_handler.go index 0208de140..dc6bd06d9 100644 --- a/wsapi/websocket_handler.go +++ b/wsapi/websocket_handler.go @@ -4,11 +4,11 @@ package wsapi import ( - l4g "github.com/alecthomas/log4go" - + "fmt" "net/http" "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) @@ -23,11 +23,11 @@ type webSocketHandler struct { } func (wh webSocketHandler) ServeWebSocket(conn *app.WebConn, r *model.WebSocketRequest) { - l4g.Debug("websocket: %s", r.Action) + mlog.Debug(fmt.Sprintf("websocket: %s", r.Action)) session, sessionErr := wh.app.GetSession(conn.GetSessionToken()) if sessionErr != nil { - l4g.Error(utils.T("api.web_socket_handler.log.error"), "websocket", r.Action, r.Seq, conn.UserId, sessionErr.SystemMessage(utils.T), sessionErr.Error()) + mlog.Error(fmt.Sprintf("%v:%v seq=%v uid=%v %v [details: %v]", "websocket", r.Action, r.Seq, conn.UserId, sessionErr.SystemMessage(utils.T), sessionErr.Error())) sessionErr.DetailedError = "" errResp := model.NewWebSocketError(r.Seq, sessionErr) @@ -43,7 +43,7 @@ func (wh webSocketHandler) ServeWebSocket(conn *app.WebConn, r *model.WebSocketR var err *model.AppError if data, err = wh.handlerFunc(r); err != nil { - l4g.Error(utils.T("api.web_socket_handler.log.error"), "websocket", r.Action, r.Seq, r.Session.UserId, err.SystemMessage(utils.T), err.DetailedError) + mlog.Error(fmt.Sprintf("%v:%v seq=%v uid=%v %v [details: %v]", "websocket", r.Action, r.Seq, r.Session.UserId, err.SystemMessage(utils.T), err.DetailedError)) err.DetailedError = "" errResp := model.NewWebSocketError(r.Seq, err) -- cgit v1.2.3-1-g7c22