diff --git a/_examples/README.md b/_examples/README.md index 2e16a848..7754fdbf 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -60,7 +60,9 @@ * [Multi](routing/subdomains/multi/main.go) * [Wildcard](routing/subdomains/wildcard/main.go) * [WWW](routing/subdomains/www/main.go) + * [WWW Method](routing/subdomains/www/www-method/main.go) * [Redirection](routing/subdomains/redirect/main.go) + * [Multi Instances](routing/subdomains/redirect/multi-instances/main.go) * [HTTP Errors View](routing/subdomains/http-errors-view/main.go) * [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go) * [API Versioning](routing/versioning/main.go) diff --git a/_examples/i18n/main.go b/_examples/i18n/main.go index b084c3a6..eecf93c0 100644 --- a/_examples/i18n/main.go +++ b/_examples/i18n/main.go @@ -14,7 +14,7 @@ func newApp() *iris.Application { if err != nil { panic(err) } - app.I18n.Subdomain + // app.I18n.Subdomain = false to disable resolve lang code from subdomain. // app.I18n.LoadAssets for go-bindata. // Default values: diff --git a/_examples/routing/subdomains/http-errors-view/main.go b/_examples/routing/subdomains/http-errors-view/main.go index a7a04a3a..3b8ec6fc 100644 --- a/_examples/routing/subdomains/http-errors-view/main.go +++ b/_examples/routing/subdomains/http-errors-view/main.go @@ -17,9 +17,10 @@ func newApp() *iris.Application { // Optionally, to minify the HTML5 error response. // Note that minification might be slower, caching is advised. - test.UseError(iris.Minify) + // test.UseError(iris.Minify) + // or pass it to OnErrorCode: // Register error code 404 handler. - test.OnErrorCode(iris.StatusNotFound, handleNotFoundTestSubdomain) + test.OnErrorCode(iris.StatusNotFound, iris.Minify, handleNotFoundTestSubdomain) test.Get("/", testIndex) diff --git a/_examples/routing/subdomains/redirect/multi-instances/main.go b/_examples/routing/subdomains/redirect/multi-instances/main.go new file mode 100644 index 00000000..35f4a4ee --- /dev/null +++ b/_examples/routing/subdomains/redirect/multi-instances/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "net/http" + + "github.com/kataras/iris/v12" +) + +func main() { + app := iris.New() + + hosts := map[string]*iris.Application{ + "mydomain.com": createRoot("www.mydomain.com"), // redirects to www. + "www.mydomain.com": createWWW(), + "test.mydomain.com": createTest(), + } + for _, r := range hosts { + r.Build() + } + + app.Downgrade(func(w http.ResponseWriter, r *http.Request) { + host := r.Host + if host == "" { + host = r.URL.Host + } + + if router, ok := hosts[host]; ok { + router.ServeHTTP(w, r) + return + } + + http.NotFound(w, r) + }) + + app.Listen(":80") +} + +func createRoot(redirectTo string) *iris.Application { + app := iris.New() + app.Downgrade(func(w http.ResponseWriter, r *http.Request) { + fullScheme := "http://" + if r.TLS != nil { + fullScheme = "https://" + } + + http.Redirect(w, r, fullScheme+redirectTo, iris.StatusMovedPermanently) + }) + + return app +} + +func createWWW() *iris.Application { + app := iris.New() + app.Get("/", index) + + users := app.Party("/users") + users.Get("/", usersIndex) + users.Get("/login", getLogin) + + return app +} + +func createTest() *iris.Application { + app := iris.New() + app.Get("/", func(ctx iris.Context) { + ctx.WriteString("Test Index") + }) + + return app +} + +func index(ctx iris.Context) { + ctx.Writef("This is the www.mydomain.com endpoint.") +} + +func usersIndex(ctx iris.Context) { + ctx.Writef("This is the www.mydomain.com/users endpoint.") +} + +func getLogin(ctx iris.Context) { + ctx.Writef("This is the www.mydomain.com/users/login endpoint.") +} diff --git a/_examples/routing/subdomains/www/main.go b/_examples/routing/subdomains/www/main.go index 280179a6..2f095585 100644 --- a/_examples/routing/subdomains/www/main.go +++ b/_examples/routing/subdomains/www/main.go @@ -1,8 +1,6 @@ package main -import ( - "github.com/kataras/iris/v12" -) +import "github.com/kataras/iris/v12" func newApp() *iris.Application { app := iris.New() @@ -44,9 +42,9 @@ func newApp() *iris.Application { ctx.Writef("hi from www.mydomain.com") }) } - // See also the "subdomains/redirect" to register redirect router wrappers between subdomains, + // See "subdomains/redirect" to register redirect router wrappers between subdomains, // i.e mydomain.com to www.mydomain.com (like facebook does for SEO reasons(;)). - + // And ./www-method example. return app } @@ -64,9 +62,7 @@ func main() { // http://www.mydomain.com/contact // http://www.mydomain.com/api/users // http://www.mydomain.com/api/users/42 - if err := app.Listen("mydomain.com:80"); err != nil { - panic(err) - } + app.Listen("mydomain.com:80") } func info(ctx iris.Context) { diff --git a/_examples/routing/subdomains/www/www-method/main.go b/_examples/routing/subdomains/www/www-method/main.go new file mode 100644 index 00000000..b2fd4b65 --- /dev/null +++ b/_examples/routing/subdomains/www/www-method/main.go @@ -0,0 +1,51 @@ +package main + +import "github.com/kataras/iris/v12" + +func newApp() *iris.Application { + app := iris.New() + // This will create a new "www" subdomain + // and redirect root-level domain requests + // to that one: + www := app.WWW() + www.Get("/", info) + www.Get("/about", info) + www.Get("/contact", info) + + www.PartyFunc("/api/users", func(r iris.Party) { + r.Get("/", info) + r.Get("/{id:uint64}", info) + + r.Post("/", info) + + r.Put("/{id:uint64}", info) + }) + + return app +} + +func main() { + app := newApp() + // http://mydomain.com + // http://mydomain.com/about + // http://imydomain.com/contact + // http://mydomain.com/api/users + // http://mydomain.com/api/users/42 + + // http://www.mydomain.com + // http://www.mydomain.com/hi + // http://www.mydomain.com/about + // http://www.mydomain.com/contact + // http://www.mydomain.com/api/users + // http://www.mydomain.com/api/users/42 + app.Listen("mydomain.com:80") +} + +func info(ctx iris.Context) { + method := ctx.Method() + subdomain := ctx.Subdomain() + path := ctx.Path() + + ctx.Writef("\nInfo\n\n") + ctx.Writef("Method: %s\nSubdomain: %s\nPath: %s", method, subdomain, path) +} diff --git a/aliases.go b/aliases.go index b609b024..77270d9b 100644 --- a/aliases.go +++ b/aliases.go @@ -244,23 +244,6 @@ var ( ctx.Next() } - // Minify is a middleware which minifies the responses - // based on the response content type. - // Note that minification might be slower, caching is advised. - // Customize the minifier through `Application.Minifier()`. - Minify = func(ctx Context) { - w := ctx.Application().Minifier().ResponseWriter(ctx.ResponseWriter().Naive(), ctx.Request()) - // Note(@kataras): - // We don't use defer w.Close() - // because this response writer holds a sync.WaitGroup under the hoods - // and we MUST be sure that its wg.Wait is called on request cancelation - // and not in the end of handlers chain execution - // (which if running a time-consuming task it will delay its resource release). - ctx.OnCloseErr(w.Close) - ctx.ResponseWriter().SetWriter(w) - ctx.Next() - } - // MatchImagesAssets is a simple regex expression // that can be passed to the DirOptions.Cache.CompressIgnore field // in order to skip compression on already-compressed file types diff --git a/context/application.go b/context/application.go index b0d89e85..ee0fbe4f 100644 --- a/context/application.go +++ b/context/application.go @@ -4,6 +4,7 @@ import ( stdContext "context" "io" "net/http" + "sync" "github.com/kataras/golog" "github.com/tdewolff/minify/v2" @@ -84,3 +85,51 @@ type Application interface { // Order may change. FindClosestPaths(subdomain, searchPath string, n int) []string } + +var ( + registeredApps []Application + mu sync.RWMutex +) + +// RegisterApplication registers an application to the global shared storage. +func RegisterApplication(app Application) { + if app == nil { + return + } + + mu.Lock() + registeredApps = append(registeredApps, app) + mu.Unlock() +} + +// LastApplication returns the last registered Application. +// Handlers has access to the current Application, +// use `Context.Application()` instead. +func LastApplication() Application { + mu.RLock() + if n := len(registeredApps); n > 0 { + if app := registeredApps[n-1]; app != nil { + mu.RUnlock() + return app + } + } + mu.RUnlock() + return nil +} + +// DefaultLogger returns a Logger instance for an Iris module. +// If the program contains at least one registered Iris Application +// before this call then it will return a child of that Application's Logger +// otherwise a fresh child of the `golog.Default` will be returned instead. +// +// It should be used when a module has no access to the Application or its Logger. +func DefaultLogger(prefix string) (logger *golog.Logger) { + if app := LastApplication(); app != nil { + logger = app.Logger() + } else { + logger = golog.Default + } + + logger = logger.Child(prefix) + return +} diff --git a/core/router/api_builder.go b/core/router/api_builder.go index ba25fe12..53d72d4b 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -15,6 +15,8 @@ import ( "github.com/kataras/iris/v12/hero" "github.com/kataras/iris/v12/macro" macroHandler "github.com/kataras/iris/v12/macro/handler" + + "github.com/kataras/golog" ) // MethodNone is a Virtual method @@ -153,6 +155,8 @@ func overlapRoute(r *Route, next *Route) { // APIBuilder the visible API for constructing the router // and child routers. type APIBuilder struct { + // the application logger. + logger *golog.Logger // parent is the creator of this Party. // It is nil on Root. parent *APIBuilder // currently it's used only on UseRouter feature. @@ -227,8 +231,9 @@ var ( // NewAPIBuilder creates & returns a new builder // which is responsible to build the API and the router handler. -func NewAPIBuilder() *APIBuilder { +func NewAPIBuilder(logger *golog.Logger) *APIBuilder { return &APIBuilder{ + logger: logger, parent: nil, macros: macro.Defaults, errors: errgroup.New("API Builder"), @@ -240,6 +245,11 @@ func NewAPIBuilder() *APIBuilder { } } +// Logger returns the Application Logger. +func (api *APIBuilder) Logger() *golog.Logger { + return api.logger +} + // IsRoot reports whether this Party is the root Application's one. // It will return false on all children Parties, no exception. func (api *APIBuilder) IsRoot() bool { diff --git a/core/router/api_builder_benchmark_test.go b/core/router/api_builder_benchmark_test.go index e709b266..b88b587d 100644 --- a/core/router/api_builder_benchmark_test.go +++ b/core/router/api_builder_benchmark_test.go @@ -8,6 +8,8 @@ import ( "time" "github.com/kataras/iris/v12/context" + + "github.com/kataras/golog" ) // @@ -83,7 +85,7 @@ func BenchmarkAPIBuilder(b *testing.B) { // i.e /gzhyweumidvelqewrvoyqmzopvuxli/{name:string}/bibrkratnrrhvsjwsxygfwmqwhcstc/{age:int}/end paths := genPaths(routesLength, 15, 42) - api := NewAPIBuilder() + api := NewAPIBuilder(golog.Default) requestHandler := NewDefaultHandler(nil, nil) b.ReportAllocs() diff --git a/core/router/handler.go b/core/router/handler.go index 2d038631..8532b2dc 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -246,7 +246,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { // logger.Debugf("API: %d registered %s (", len(registeredRoutes), tr) // with: pio.WriteRich(logger.Printer, debugLevel.Title, debugLevel.ColorCode, debugLevel.Style...) - fmt.Fprintf(logger.Printer, " %s API: %d registered %s (", time.Now().Format(logger.TimeFormat), len(registeredRoutes), tr) + fmt.Fprintf(logger.Printer, " %s %sAPI: %d registered %s (", time.Now().Format(logger.TimeFormat), logger.Prefix, len(registeredRoutes), tr) // logger.NewLine = bckpNewLine diff --git a/core/router/party.go b/core/router/party.go index 204de86e..be0d1812 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -6,6 +6,8 @@ import ( "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/core/errgroup" "github.com/kataras/iris/v12/macro" + + "github.com/kataras/golog" ) // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. @@ -13,6 +15,9 @@ import ( // // Look the `APIBuilder` structure for its implementation. type Party interface { + // Logger returns the Application Logger. + Logger() *golog.Logger + // IsRoot reports whether this Party is the root Application's one. // It will return false on all children Parties, no exception. IsRoot() bool diff --git a/go.mod b/go.mod index fb951704..218ec038 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/iris-contrib/schema v0.0.2 github.com/json-iterator/go v1.1.10 github.com/kataras/blocks v0.0.2 - github.com/kataras/golog v0.1.0 + github.com/kataras/golog v0.1.2 github.com/kataras/neffos v0.0.16 github.com/kataras/pio v0.0.10 github.com/kataras/sitemap v0.0.5 diff --git a/iris.go b/iris.go index 036a96b2..a73e765e 100644 --- a/iris.go +++ b/iris.go @@ -99,20 +99,21 @@ type Application struct { // New creates and returns a fresh empty iris *Application instance. func New() *Application { config := DefaultConfiguration() - app := &Application{ - config: &config, - logger: golog.Default, - minifier: newMinifier(), - I18n: i18n.New(), - APIBuilder: router.NewAPIBuilder(), - Router: router.NewRouter(), + config: &config, + Router: router.NewRouter(), + I18n: i18n.New(), + minifier: newMinifier(), } + logger := newLogger(app) + app.logger = logger + app.APIBuilder = router.NewAPIBuilder(logger) app.ContextPool = context.New(func() interface{} { return context.NewContext(app) }) + context.RegisterApplication(app) return app } @@ -161,6 +162,8 @@ func (app *Application) WWW() router.Party { // Example: https://github.com/kataras/iris/tree/master/_examples/routing/subdomains/redirect func (app *Application) SubdomainRedirect(from, to router.Party) router.Party { sd := router.NewSubdomainRedirectWrapper(app.ConfigurationReadOnly().GetVHost, from.GetRelPath(), to.GetRelPath()) + // TODO: add a debug message here or wait for a response from the issuer + // so we can force these to run on build state (last registered, first executed). app.Router.WrapRouter(sd) return to } @@ -186,6 +189,22 @@ func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly { return app.config } +// Maybe, if it's requested: +// func (app *Application) SetName(appName string) *iris.Application { +// app.config.name = appName +// app.logger.SetChildPrefix(appName) +// return app +// } + +func newLogger(app *Application) *golog.Logger { + logger := golog.Default.Child(app) + if prefix := os.Getenv("IRIS_APP_NAME"); prefix != "" { + logger.SetChildPrefix(prefix) + } + + return logger +} + // Logger returns the golog logger instance(pointer) that is being used inside the "app". // // Available levels: @@ -200,7 +219,7 @@ func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly { // Defaults to "info" level. // // Callers can use the application's logger which is -// the same `golog.Default` logger, +// the same `golog.Default.LastChild()` logger, // to print custom logs too. // Usage: // app.Logger().Error/Errorf("...") @@ -273,6 +292,25 @@ func newMinifier() *minify.M { return m } +// Minify is a middleware which minifies the responses +// based on the response content type. +// Note that minification might be slower, caching is advised. +// Customize the minifier through `Application.Minifier()`. +// Usage: +// app.Use(iris.Minify) +func Minify(ctx Context) { + w := ctx.Application().Minifier().ResponseWriter(ctx.ResponseWriter().Naive(), ctx.Request()) + // Note(@kataras): + // We don't use defer w.Close() + // because this response writer holds a sync.WaitGroup under the hoods + // and we MUST be sure that its wg.Wait is called on request cancelation + // and not in the end of handlers chain execution + // (which if running a time-consuming task it will delay its resource release). + ctx.OnCloseErr(w.Close) + ctx.ResponseWriter().SetWriter(w) + ctx.Next() +} + // Minifier returns the minifier instance. // By default it can minifies: // - text/html diff --git a/mvc/mvc.go b/mvc/mvc.go index 7309934d..049bf20c 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -247,8 +247,8 @@ var _ websocket.ConnHandler = (*Application)(nil) // It returns a collection of namespace and events that // were registered through `HandleWebsocket` controllers. func (app *Application) GetNamespaces() websocket.Namespaces { - if golog.Default.Level == golog.DebugLevel { - websocket.EnableDebug(golog.Default) + if logger := app.Router.Logger(); logger.Level == golog.DebugLevel { + websocket.EnableDebug(logger) } return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces() diff --git a/sessions/config.go b/sessions/config.go index e673d50e..ce7026d8 100644 --- a/sessions/config.go +++ b/sessions/config.go @@ -6,6 +6,7 @@ import ( "github.com/kataras/iris/v12/context" "github.com/google/uuid" + "github.com/kataras/golog" ) const ( @@ -16,6 +17,9 @@ const ( type ( // Config is the configuration for sessions. Please read it before using sessions. Config struct { + // Logger instance for sessions usage, e.g. { Logger: app.Logger() }. + // Defauls to a child of "sessions" of the latest Iris Application's main Logger. + Logger *golog.Logger // Cookie string, the session's client cookie name, for example: "mysessionid" // // Defaults to "irissessionid". @@ -65,6 +69,10 @@ type ( // Validate corrects missing fields configuration fields and returns the right configuration func (c Config) Validate() Config { + if c.Logger == nil { + c.Logger = context.DefaultLogger("sessions") + } + if c.Cookie == "" { c.Cookie = DefaultCookieName } diff --git a/sessions/database.go b/sessions/database.go index a32f0fbd..0f590062 100644 --- a/sessions/database.go +++ b/sessions/database.go @@ -6,6 +6,8 @@ import ( "time" "github.com/kataras/iris/v12/core/memstore" + + "github.com/kataras/golog" ) // ErrNotImplemented is returned when a particular feature is not yet implemented yet. @@ -22,6 +24,8 @@ var ErrNotImplemented = errors.New("not implemented yet") // // Look the `sessiondb` folder for databases implementations. type Database interface { + // SetLogger should inject a logger to this Database. + SetLogger(*golog.Logger) // Acquire receives a session's lifetime from the database, // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. Acquire(sid string, expires time.Duration) LifeTime @@ -36,7 +40,7 @@ type Database interface { OnUpdateExpiration(sid string, newExpires time.Duration) error // Set sets a key value of a specific session. // The "immutable" input argument depends on the store, it may not implement it at all. - Set(sid string, lifetime LifeTime, key string, value interface{}, immutable bool) + Set(sid string, lifetime *LifeTime, key string, value interface{}, immutable bool) // Get retrieves a session value based on the key. Get(sid string, key string) interface{} // Visit loops through all session keys and values. @@ -61,6 +65,8 @@ var _ Database = (*mem)(nil) func newMemDB() Database { return &mem{values: make(map[string]*memstore.Store)} } +func (s *mem) SetLogger(*golog.Logger) {} + func (s *mem) Acquire(sid string, expires time.Duration) LifeTime { s.mu.Lock() s.values[sid] = new(memstore.Store) @@ -72,7 +78,7 @@ func (s *mem) Acquire(sid string, expires time.Duration) LifeTime { func (s *mem) OnUpdateExpiration(string, time.Duration) error { return nil } // immutable depends on the store, it may not implement it at all. -func (s *mem) Set(sid string, lifetime LifeTime, key string, value interface{}, immutable bool) { +func (s *mem) Set(sid string, lifetime *LifeTime, key string, value interface{}, immutable bool) { s.mu.RLock() s.values[sid].Save(key, value, immutable) s.mu.RUnlock() diff --git a/sessions/provider.go b/sessions/provider.go index 022ddc21..35e724c1 100644 --- a/sessions/provider.go +++ b/sessions/provider.go @@ -70,7 +70,7 @@ func (p *provider) newSession(man *Sessions, sid string, expires time.Duration) lifetime.Begin(expires, onExpire) } - sess.Lifetime = lifetime + sess.Lifetime = &lifetime return sess } diff --git a/sessions/session.go b/sessions/session.go index f4110ec0..204d2261 100644 --- a/sessions/session.go +++ b/sessions/session.go @@ -22,7 +22,7 @@ type ( mu sync.RWMutex // for flashes. // Lifetime it contains the expiration data, use it for read-only information. // See `Sessions.UpdateExpiration` too. - Lifetime LifeTime + Lifetime *LifeTime // Man is the sessions manager that this session created of. Man *Sessions diff --git a/sessions/sessiondb/badger/database.go b/sessions/sessiondb/badger/database.go index c7accb7c..512fc462 100644 --- a/sessions/sessiondb/badger/database.go +++ b/sessions/sessiondb/badger/database.go @@ -8,6 +8,7 @@ import ( "sync/atomic" "time" + "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/sessions" "github.com/dgraph-io/badger/v2" @@ -26,6 +27,7 @@ type Database struct { // it's initialized at `New` or `NewFromDB`. // Can be used to get stats. Service *badger.DB + logger *golog.Logger closed uint32 // if 1 is closed. } @@ -53,11 +55,12 @@ func New(directoryPath string) (*Database, error) { } opts := badger.DefaultOptions(directoryPath) - opts.Logger = golog.Default.Child("[sessionsdb.badger]").DisableNewLine() + badgerLogger := context.DefaultLogger("sessionsdb.badger").DisableNewLine() + opts.Logger = badgerLogger service, err := badger.Open(opts) if err != nil { - golog.Errorf("unable to initialize the badger-based session database: %v", err) + badgerLogger.Errorf("unable to initialize the badger-based session database: %v\n", err) return nil, err } @@ -72,6 +75,12 @@ func NewFromDB(service *badger.DB) *Database { return db } +// SetLogger sets the logger once before server ran. +// By default the Iris one is injected. +func (db *Database) SetLogger(logger *golog.Logger) { + db.logger = logger +} + // Acquire receives a session's lifetime from the database, // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime { @@ -94,7 +103,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime } if err != nil { - golog.Error(err) + db.logger.Error(err) } return sessions.LifeTime{} // session manager will handle the rest. @@ -118,10 +127,10 @@ func makeKey(sid, key string) []byte { // Set sets a key value of a specific session. // Ignore the "immutable". -func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, value interface{}, immutable bool) { +func (db *Database) Set(sid string, lifetime *sessions.LifeTime, key string, value interface{}, immutable bool) { valueBytes, err := sessions.DefaultTranscoder.Marshal(value) if err != nil { - golog.Error(err) + db.logger.Error(err) return } @@ -131,7 +140,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu }) if err != nil { - golog.Error(err) + db.logger.Error(err) } } @@ -149,7 +158,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) { }) if err != nil && err != badger.ErrKeyNotFound { - golog.Error(err) + db.logger.Error(err) return nil } @@ -189,7 +198,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { return sessions.DefaultTranscoder.Unmarshal(valueBytes, &value) }) if err != nil { - golog.Errorf("[sessionsdb.badger.Visit] %v", err) + db.logger.Errorf("[sessionsdb.badger.Visit] %v", err) continue } @@ -231,7 +240,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) { txn := db.Service.NewTransaction(true) err := txn.Delete(makeKey(sid, key)) if err != nil { - golog.Error(err) + db.logger.Error(err) return false } return txn.Commit() == nil @@ -250,7 +259,7 @@ func (db *Database) Clear(sid string) { for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() { key := iter.Item().Key() if err := txn.Delete(key); err != nil { - golog.Warnf("Database.Clear: %s: %v", key, err) + db.logger.Warnf("Database.Clear: %s: %v", key, err) continue } } @@ -264,10 +273,10 @@ func (db *Database) Release(sid string) { // and remove the $sid. txn := db.Service.NewTransaction(true) if err := txn.Delete([]byte(sid)); err != nil { - golog.Warnf("Database.Release.Delete: %s: %v", sid, err) + db.logger.Warnf("Database.Release.Delete: %s: %v", sid, err) } if err := txn.Commit(); err != nil { - golog.Debugf("Database.Release.Commit: %s: %v", sid, err) + db.logger.Debugf("Database.Release.Commit: %s: %v", sid, err) } } @@ -282,7 +291,7 @@ func closeDB(db *Database) error { } err := db.Service.Close() if err != nil { - golog.Warnf("closing the badger connection: %v", err) + db.logger.Warnf("closing the badger connection: %v", err) } else { atomic.StoreUint32(&db.closed, 1) } diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index 90e884b8..9899fb62 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -27,6 +27,7 @@ type Database struct { // it's initialized at `New` or `NewFromDB`. // Can be used to get stats. Service *bolt.DB + logger *golog.Logger } var errPathMissing = errors.New("path is required") @@ -91,7 +92,7 @@ func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket { if b == nil { // session does not exist, it shouldn't happen, session bucket creation happens once at `Acquire`, // no need to accept the `bolt.bucket.CreateBucketIfNotExists`'s performance cost. - golog.Debugf("unreachable session access for '%s'", sid) + db.logger.Debugf("unreachable session access for '%s'", sid) } return b @@ -122,20 +123,20 @@ func (db *Database) cleanup() error { if bExp := b.Bucket(expirationName); bExp != nil { // has expiration. _, expValue := bExp.Cursor().First() // the expiration bucket contains only one key(we don't care, see `Acquire`) value(time.Time) pair. if expValue == nil { - golog.Debugf("cleanup: expiration is there but its value is empty '%s'", v) // should never happen. + db.logger.Debugf("cleanup: expiration is there but its value is empty '%s'", v) // should never happen. continue } var expirationTime time.Time if err := sessions.DefaultTranscoder.Unmarshal(expValue, &expirationTime); err != nil { - golog.Debugf("cleanup: unable to retrieve expiration value for '%s'", v) + db.logger.Debugf("cleanup: unable to retrieve expiration value for '%s'", v) continue } if expirationTime.Before(time.Now()) { // expired, delete the expiration bucket. if err := b.DeleteBucket(expirationName); err != nil { - golog.Debugf("cleanup: unable to destroy a session '%s'", bsid) + db.logger.Debugf("cleanup: unable to destroy a session '%s'", bsid) return err } @@ -149,6 +150,12 @@ func (db *Database) cleanup() error { }) } +// SetLogger sets the logger once before server ran. +// By default the Iris one is injected. +func (db *Database) SetLogger(logger *golog.Logger) { + db.logger = logger +} + var expirationKey = []byte("exp") // it can be random. // Acquire receives a session's lifetime from the database, @@ -166,14 +173,14 @@ func (db *Database) Acquire(sid string, expires time.Duration) (lifetime session // don't return a lifetime, let it empty, session manager will do its job. b, err = root.CreateBucket(name) if err != nil { - golog.Debugf("unable to create a session bucket for '%s': %v", sid, err) + db.logger.Debugf("unable to create a session bucket for '%s': %v", sid, err) return err } expirationTime := time.Now().Add(expires) timeBytes, err := sessions.DefaultTranscoder.Marshal(expirationTime) if err != nil { - golog.Debugf("unable to set an expiration value on session expiration bucket for '%s': %v", sid, err) + db.logger.Debugf("unable to set an expiration value on session expiration bucket for '%s': %v", sid, err) return err } @@ -194,7 +201,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) (lifetime session var expirationTime time.Time if err = sessions.DefaultTranscoder.Unmarshal(expValue, &expirationTime); err != nil { - golog.Debugf("acquire: unable to retrieve expiration value for '%s', value was: '%s': %v", sid, expValue, err) + db.logger.Debugf("acquire: unable to retrieve expiration value for '%s', value was: '%s': %v", sid, expValue, err) return } @@ -207,7 +214,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) (lifetime session return }) if err != nil { - golog.Debugf("unable to acquire session '%s': %v", sid, err) + db.logger.Debugf("unable to acquire session '%s': %v", sid, err) return sessions.LifeTime{} } @@ -227,7 +234,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err root := db.getBucket(tx) b := root.Bucket(expirationName) if b == nil { - // golog.Debugf("tried to reset the expiration value for '%s' while its configured lifetime is unlimited or the session is already expired and not found now", sid) + // db.logger.Debugf("tried to reset the expiration value for '%s' while its configured lifetime is unlimited or the session is already expired and not found now", sid) return sessions.ErrNotFound } @@ -235,7 +242,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err }) if err != nil { - golog.Debugf("unable to reset the expiration value for '%s': %v", sid, err) + db.logger.Debugf("unable to reset the expiration value for '%s': %v", sid, err) } return err @@ -247,10 +254,10 @@ func makeKey(key string) []byte { // Set sets a key value of a specific session. // Ignore the "immutable". -func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, value interface{}, immutable bool) { +func (db *Database) Set(sid string, _ *sessions.LifeTime, key string, value interface{}, immutable bool) { valueBytes, err := sessions.DefaultTranscoder.Marshal(value) if err != nil { - golog.Debug(err) + db.logger.Debug(err) return } @@ -268,7 +275,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu }) if err != nil { - golog.Debug(err) + db.logger.Debug(err) } } @@ -288,7 +295,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) { return sessions.DefaultTranscoder.Unmarshal(valueBytes, &value) }) if err != nil { - golog.Debugf("session '%s' key '%s' cannot be retrieved: %v", sid, key, err) + db.logger.Debugf("session '%s' key '%s' cannot be retrieved: %v", sid, key, err) } return @@ -305,7 +312,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { return b.ForEach(func(k []byte, v []byte) error { var value interface{} if err := sessions.DefaultTranscoder.Unmarshal(v, &value); err != nil { - golog.Debugf("unable to retrieve value of key '%s' of '%s': %v", k, sid, err) + db.logger.Debugf("unable to retrieve value of key '%s' of '%s': %v", k, sid, err) return err } @@ -315,7 +322,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { }) if err != nil { - golog.Debugf("Database.Visit: %s: %v", sid, err) + db.logger.Debugf("Database.Visit: %s: %v", sid, err) } } @@ -332,7 +339,7 @@ func (db *Database) Len(sid string) (n int) { }) if err != nil { - golog.Debugf("Database.Len: %s: %v", sid, err) + db.logger.Debugf("Database.Len: %s: %v", sid, err) } return @@ -366,7 +373,7 @@ func (db *Database) Clear(sid string) { }) if err != nil { - golog.Debugf("Database.Clear: %s: %v", sid, err) + db.logger.Debugf("Database.Clear: %s: %v", sid, err) } } @@ -384,7 +391,7 @@ func (db *Database) Release(sid string) { }) if err != nil { - golog.Debugf("Database.Release: %s: %v", sid, err) + db.logger.Debugf("Database.Release: %s: %v", sid, err) } } @@ -396,7 +403,7 @@ func (db *Database) Close() error { func closeDB(db *Database) error { err := db.Service.Close() if err != nil { - golog.Warnf("closing the BoltDB connection: %v", err) + db.logger.Warnf("closing the BoltDB connection: %v", err) } return err diff --git a/sessions/sessiondb/redis/database.go b/sessions/sessiondb/redis/database.go index b3453831..2b1898d6 100644 --- a/sessions/sessiondb/redis/database.go +++ b/sessions/sessiondb/redis/database.go @@ -77,7 +77,8 @@ func DefaultConfig() Config { // Database the redis back-end session database for the sessions. type Database struct { - c Config + c Config + logger *golog.Logger } var _ sessions.Database = (*Database)(nil) @@ -131,6 +132,12 @@ func (db *Database) Config() *Config { return &db.c // 6 Aug 2019 - keep that for no breaking change. } +// SetLogger sets the logger once before server ran. +// By default the Iris one is injected. +func (db *Database) SetLogger(logger *golog.Logger) { + db.logger = logger +} + // Acquire receives a session's lifetime from the database, // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime { @@ -140,7 +147,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime // fmt.Printf("db.Acquire expires: %s. Seconds: %v\n", expires, expires.Seconds()) // not found, create an entry with ttl and return an empty lifetime, session manager will do its job. if err := db.c.Driver.Set(key, sid, int64(expires.Seconds())); err != nil { - golog.Debug(err) + db.logger.Debug(err) } return sessions.LifeTime{} // session manager will handle the rest. @@ -168,17 +175,17 @@ func (db *Database) makeKey(sid, key string) string { // Set sets a key value of a specific session. // Ignore the "immutable". -func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, value interface{}, immutable bool) { +func (db *Database) Set(sid string, lifetime *sessions.LifeTime, key string, value interface{}, immutable bool) { valueBytes, err := sessions.DefaultTranscoder.Marshal(value) if err != nil { - golog.Error(err) + db.logger.Error(err) return } // fmt.Println("database.Set") // fmt.Printf("lifetime.DurationUntilExpiration(): %s. Seconds: %v\n", lifetime.DurationUntilExpiration(), lifetime.DurationUntilExpiration().Seconds()) if err = db.c.Driver.Set(db.makeKey(sid, key), valueBytes, int64(lifetime.DurationUntilExpiration().Seconds())); err != nil { - golog.Debug(err) + db.logger.Debug(err) } } @@ -196,7 +203,7 @@ func (db *Database) get(key string, outPtr interface{}) error { } if err = sessions.DefaultTranscoder.Unmarshal(data.([]byte), outPtr); err != nil { - golog.Debugf("unable to unmarshal value of key: '%s': %v", key, err) + db.logger.Debugf("unable to unmarshal value of key: '%s': %v", key, err) return err } @@ -206,7 +213,7 @@ func (db *Database) get(key string, outPtr interface{}) error { func (db *Database) keys(sid string) []string { keys, err := db.c.Driver.GetKeys(db.makeKey(sid, "")) if err != nil { - golog.Debugf("unable to get all redis keys of session '%s': %v", sid, err) + db.logger.Debugf("unable to get all redis keys of session '%s': %v", sid, err) return nil } @@ -233,7 +240,7 @@ func (db *Database) Len(sid string) (n int) { func (db *Database) Delete(sid string, key string) (deleted bool) { err := db.c.Driver.Delete(db.makeKey(sid, key)) if err != nil { - golog.Error(err) + db.logger.Error(err) } return err == nil } @@ -243,7 +250,7 @@ func (db *Database) Clear(sid string) { keys := db.keys(sid) for _, key := range keys { if err := db.c.Driver.Delete(key); err != nil { - golog.Debugf("unable to delete session '%s' value of key: '%s': %v", sid, key, err) + db.logger.Debugf("unable to delete session '%s' value of key: '%s': %v", sid, key, err) } } } @@ -256,7 +263,7 @@ func (db *Database) Release(sid string) { // and remove the $sid. err := db.c.Driver.Delete(db.c.Prefix + sid) if err != nil { - golog.Debugf("Database.Release.Driver.Delete: %s: %v", sid, err) + db.logger.Debugf("Database.Release.Driver.Delete: %s: %v", sid, err) } } diff --git a/sessions/sessions.go b/sessions/sessions.go index b95b03a2..a98ad803 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -50,6 +50,7 @@ func New(cfg Config) *Sessions { // UseDatabase adds a session database to the manager's provider, // a session db doesn't have write access func (s *Sessions) UseDatabase(db Database) { + db.SetLogger(s.config.Logger) // inject the logger. s.provider.RegisterDatabase(db) }