How to add a hook into a zap logger? - go

I try to add hook with WithOptions but there was nothing printed for catching some of log events:
logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
fmt.Println("test hooks test hooks")
return nil
}))

From the documentation:
func (log *Logger) WithOptions(opts ...Option) *Logger
WithOptions clones the current Logger, applies the supplied Options, and returns the resulting Logger. It's safe to use concurrently.
Notice that it clones a new logger instead of modifying the logger. So, you should reassign the logger variable (or define a new variable) like this:
logger = logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
fmt.Println("test hooks test hooks")
return nil
}))

Related

Does WithContext method need to panic if context is nil?

I want to write a WithContext method for a struct and am taking inspiration from net/http's Request.WithContext.
My question is: why does Request.WithContext panic if the context is nil:
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
...
}
And should mine as well?
For more context on why I want to create a WithContext method: I am implementing an interface that does not provide a context parameter in its signature but believe the implementation requires it.
More specifically, I am writing a Redis backend for gorilla/session using the official Redis client for Go, where the Get and Set methods take context.Context.
The idea is that my redis store will be shallow copied with the new context object, when needed, and then used:
type redisStore struct {
codecs []securecookie.Codec
backend Backend // custom interface for Redis client
options *sessions.Options
ctx context.Context
}
func (s *redisStore) WithContext(ctx context.Context) *redisStore {
if ctx == nil {
panic("nil context")
}
s2 := new(redisStore)
*s2 = *s
s2.ctx = ctx
return s2
}
// Backend
type Backend interface {
Set(context.Context, string, interface{}) error
Get(context.Context, string) (string, error)
Del(context.Context, string) error
}
The purpose of panicking is to "fail fast" and reject a nil context without changing the function signature.
If the function does not panic then it must return error in order to reject a bad input:
func (r *Request) WithContext(ctx context.Context) (*Request, error) {
if ctx == nil {
return nil, errors.New("nil ctx")
}
...
}
And then who calls this function must handle the error to avoid using an invalid request:
request, err = request.WithContext(nil)
if err != nil {
}
By handling the error you are introducing a control flow branch, and you lose method chaining. You also cannot immediately use WithContext return value into a function parameter:
// cannot do, because WithContext returns an error too
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)
Also it would create an error instance that will be eventually garbage collected. This all is cumbersome, poor usability and unnecessary alloc simply for saying "don't give me a nil context".
About creating a redis store with a context, the context documentation is clear:
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
The important detail is request-scoped. So setting a context in the redis client itself is contrary to this recommendation. You should pass context values at each get/set call.
The context of an HTTP request is canceled if the client closes the connection. When the context is canceled, all its child contexts are also canceled, so a nil context would panic then. Because of this, you cannot pass a nil context to WithContext.
Whether or not your redis store should panic depends on how you are going to use that context. It is usually not a good idea to include a context in a struct. One acceptable way of doing that is if the struct itself is a context. Contexts should be created for each call, should live for the duration of that call, and then thrown away.

Creating Per-Provider Loggers in Wire Dependency Injection

I'm using github.com/google/wire for dependency injection in an open source example project that I'm working on.
I have the following interfaces in a package named interfaces:
type LoginService interface {
Login(email, password) (*LoginResult, error)
}
type JWTService interface {
Generate(user *models.User) (*JWTGenerateResult, error)
Validate(tokenString string) (*JWTValidateResult, error)
}
type UserDao interface {
ByEmail(email string) (*models.User, error)
}
I have implementations that look like this:
type LoginServiceImpl struct {
jwt interfaces.JWTService
dao interfaces.UserDao
logger *zap.Logger
}
func NewLoginService(jwt interfaces.JWTService, dao interfaces.UserDao, \
logger *zap.Logger) *LoginServiceImpl {
return &LoginServiceImpl{jwt: jwt, dao: dao, logger: logger }
}
type JWTServiceImpl struct {
key [32]byte
logger *zap.Logger
}
func NewJWTService(key [32]byte, logger *zap.Logger) (*JWTServiceImpl, error) {
r := JWTServiceImpl {
key: key,
logger: logger,
}
if !r.safe() {
return nil, fmt.Errorf("unable to create JWT service, unsafe key: %s", err)
}
return &r, nil
}
type UserDaoImpl struct {
db: *gorm.DB
logger: *zap.Logger
}
func NewUserDao(db *gorm.DB, logger *zap.Logger) *UserDao {
return &UserDaoImpl{ db: db, logger: logger }
}
I'll exclude other factory functions and implementations here because they all look very similar. They may return an error or be infallible.
I have one other interesting factory for creating the database connection, which I'll just show the interface and not the implementation:
func Connect(config interfaces.MySQLConfig) (*gorm.DB, error) { /* ... */ }
Now, onto the problem. In my command-line entry-point, I'm creating a logger:
logger, err := zap.NewDevelopment()
For each of the factory methods above, I need to provide a logger and not the same logger instance, rather as if these methods were called as follows:
logger, err := zap.NewDevelopment()
// check err
db, err := database.Connect(config)
// check err
userDao := dao.NewUserDao(db, logger.Named("dao.user"))
jwtService, err := service.NewJWTService(jwtKey)
// check err
loginService := service.NewLoginService(jwtService, userDao, logger.Named("service.login"))
My wire.ProviderSet construction looks like this:
wire.NewSet(
wire.Bind(new(interfaces.LoginService), new(*service.LoginServiceImpl)),
wire.Bind(new(interfaces.JWTService), new(*service.JWTServiceImpl)),
wire.Bind(new(interfaces.UserDao), new(*dao.UserDaoImpl)),
service.NewLoginService,
service.NewJWTService,
dao.NewUserDao,
database.Connect,
)
I've read through the user guide, the tutorial, and best practices, and I can't seem to find a way to route a unique zap.Logger to each of these factory methods, and routing a random [32]byte for the JWT service.
Since my root logger is not created at compile time, and since each of these factory methods needs its own unique logger, how do I tell wire to bind these instances to the corresponding factory methods? I'm having a tough time seeing how to route custom instances of the same type to disparate factory methods.
In summary:
Wire seems to favor doing everything at compile-time, storing the dependency injection configuration in a static package-level variable. For most of my use-case, this is okay.
For the rest of my use-case, I need to create some instances manually before running the dependency injection and the ability to route various *zap.Logger instances to each service that needs it.
Essentially, I need to have wire do services.NewUserDao(Connect(mysqlConfig), logger.Named("dao.user"), but I don't know how to express this in wire and merge variables at runtime with wire's compile-time approach.
How do I do this in wire?
I had to change what I was doing somewhat, as is recommended in the documentation:
If you need to inject a common type like string, create a new string type to avoid conflicts with other providers. For example:
type MySQLConnectionString string
Adding Custom Types
The documentation is admittedly very terse, but what I ended up doing is creating a bunch of types:
type JWTKey [32]byte
type JWTServiceLogger *zap.Logger
type LoginServiceLogger *zap.Logger
type UserDaoLogger *zap.Logger
Updating Producer Functions
I updated my producer methods to accept these types, but did not have to update my structs:
// LoginServiceImpl implements interfaces.LoginService
var _ interfaces.LoginService = (*LoginServiceImpl)(nil)
type LoginServiceImpl struct {
dao interfaces.UserDao
jwt interfaces.JWTService
logger *zap.Logger
}
func NewLoginService(dao interfaces.UserDao, jwt interfaces.JWTService,
logger LoginServiceLogger) *LoginServiceImpl {
return &LoginServiceImpl {
dao: dao,
jwt: jwt,
logger: logger,
}
}
This above part made sense; giving distinct types meant that wire had less to figure out.
Creating an Injector
Next, I had to create the dummy injector and then use wire to generate the corresponding wire_gen.go. This was not easy and very unintuitive. When following the documentation, things kept breaking and giving me very unhelpful error messages.
I have a cmd/ package and my CLI entrypoint lives in cmd/serve/root.go, which is run as ./api serve from the command-line. I created my injector function in cmd/serve/injectors.go, note that // +build wireinject and the following newline are required to inform Go that this file is used for code generation and not code itself.
I ultimately arrived at the following code after much trial and error:
// +build wireinject
package serve
import /*...*/
func initializeLoginService(
config interfaces.MySQLConfig,
jwtKey service.JWTKey,
loginServiceLogger service.LoginServiceLogger,
jwtServiceLogger service.JWTServiceLogger,
userDaoLogger service.UserDaoLogger,
databaseLogger database.DatabaseLogger,
) (interfaces.LoginService, error) {
wire.Build(
// bind interfaces to implementations
wire.Bind(new(interfaces.LoginService), new(*service.LoginServiceImpl)),
wire.Bind(new(interfaces.JWTService), new(*service.JWTServiceImpl)),
wire.Bind(new(interfaces.UserDao), new(*dao.UserDao)),
// services
service.NewLoginService,
service.NewJWTService,
// daos
dao.NewUserDao,
// database
database.Connect,
)
return nil, nil
}
The wire.Bind calls inform wire which implementation to use for a given interface so it will know that service.NewLoginService which returns a *LoginServiceImpl should be used as the interfaces.LoginService.
The rest of the entities in the call to wire.Build are just factory functions.
Passing Values to an Injector
One of the the issues I ran into was that I was trying to pass values into wire.Build like the documentation describes:
Occasionally, it is useful to bind a basic value (usually nil) to a type. Instead of having injectors depend on a throwaway provider function, you can add a value expression to a provider set.
type Foo struct {
X int
}
func injectFoo() Foo {
wire.Build(wire.Value(Foo{X: 42}))
return Foo{}
}
...
It's important to note that the expression will be copied to the injector's package; references to variables will be evaluated during the injector package's initialization. Wire will emit an error if the expression calls any functions or receives from any channels.
This is what confused me; it sounded like you could only really use constant values when trying to run an injector, but there are two lines in the docs in the "injectors" section:
Like providers, injectors can be parameterized on inputs (which then get sent to providers) and can return errors. Arguments to wire.Build are the same as wire.NewSet: they form a provider set. This is the provider set that gets used during code generation for that injector.
These lines are accompanied by this code:
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
wire.Build(foobarbaz.MegaSet)
return foobarbaz.Baz{}, nil
}
This is what I missed and what caused me to lose a lot of time on this. context.Context doesn't seem to be passed anywhere in this code, and it's a common type so I just kind of shrugged it off and didn't learn from it.
I defined my injector function to take arguments for the JWT key, the MySQL config, and the logger types:
func initializeLoginService(
config interfaces.MySQLConfig,
jwtKey service.JWTKey,
loginServiceLogger service.LoginServiceLogger,
jwtServiceLogger service.JWTServiceLogger,
userDaoLogger service.UserDaoLogger,
databaseLogger database.DatabaseLogger,
) (interfaces.LoginService, error) {
// ...
return nil, nil
}
Then, I attempted to inject them into wire.Build:
wire.Build(
// ...
wire.Value(config),
wire.Value(jwtKey),
wire.Value(loginServiceLogger),
// ...
)
When I attempted to run wire, it complained that these types were defined twice. I was very confused by this behavior, but ultimately learned that wire automatically sends all function parameters into wire.Build.
Once again: wire automatically sends all injector function parameters into wire.Build.
This was not intuitive to me, but I learned the hard way that it's the way wire works.
Summary
wire does not provide a way for it to distinguish values of the same type within its dependency injection system. Thus, you need to wrap these simple types with type definitions to let wire know how to route them, so instead of [32]byte, type JWTKey [32]byte.
To inject live values into your wire.Build call, simply change your injector function signature to include those values in the function parameters and wire will automatically inject them into wire.Build.
Run cd pkg/my/package && wire to create wire_gen.go in that directory for your defined injectors. Once this is done, future calls to go generate will automatically update wire_gen.go as changes occur.
I have wire_gen.go files checked-in to my version control system (VCS) which is Git, which feels weird due to these being generated build artifacts, but this seems to be the way that this is typically done. It might be more advantageous to exclude wire_gen.go, but if you do this, you'll need to find every package which includes a file with a // +build wireinject header, run wire in that directory, and then go generate just to be sure.
Hopefully this clears up the way that wire works with actual values: make them type safe with type wrappers, and simply pass them to your injector function, and wire does the rest.

Which ctx should I use in run parameter of hystrix.Do function of hystrix-go package? The ctx from upper level, or context.Background()?

Which ctx should I use in run parameter of hystrix.Do function of hystrix-go package? The ctx from the upper level, or context.Background()?
Thanks.
package main
import(
"context"
"github.com/myteksi/hystrix-go/hystrix"
)
func tb(ctx context.Context)error{
return nil
}
func ta(ctx context.Context){
hystrix.Do("cbName", func()error{
// At this place, the ctx parameter of function tb,
// Should I use ctx from ta function, or context.Background()?
return tb(ctx)
}, nil)
}
func main(){
ta(context.Background())
}
If you're using contexts, it seems to me like you should using hystrix.DoC. There's no reason to use anything than whatever context passed through, since Do is synchronous, and you would like whatever cancellations, deadlines (and whatever else is attached to your context) to be preserved inside this code.
func ta(ctx context.Context) {
err := hystrix.DoC(ctx, "cbName", func(ctx context.Context) error {
... code that uses ctx here.
}, nil)
// handle err, which may be a hystrix error.
}
It's hard to say if this is actually different from calling hystrix.Do, but this potentially allows hystrix to use your context, to add deadlines/cancellations itself.
Always use the context.Context coming from the upper level as a parameter wherever you can. It allows an end to end mechanism to control request, all the caller has to do is cancel, or invoke timeout on the initial ctx, and it will work for the complete request path.
The initial context passed can depend on your requirement. If you're not sure about what context to use initially, context.TODO can be a good option till you're sure.

Change zap log level dynamically for some of the code

I'm used to work with java and log4j, in which I could dynamically change the log level for a single class without any additional (coding) effort.
Now, when I work with golang (zap for logging) – I seek the same functionality, but cannot find it.
Is there an easy way to change log level dynamically for a file or a package or a part of the code?
zap supports log.Logger wrapping via constructors like NewStdLog, so if you want to keep using zap then the following technique will work:
If you don't want to use a third-party loggers, the following technique is quite simple, using go's standard library log.Logger:
Define some default app loggers:
var (
pkgname = "mypkgname"
Info = log.New(os.Stdout, "[INFO:"+pkgname+"] ", log.LstdFlags)
Debug = log.New(ioutil.Discard, "[DBUG:"+pkgname+"] ", log.LstdFlags|log.Lshortfile)
Trace = log.New(ioutil.Discard, "[TRCE:"+pkgname+"] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
)
so by default Info will write to Stdout - but Debug & Trace will go be "off" by default.
One can then, safely (i.e. goroutine-safe) turn these log.Loggers on/off either at start time:
func init() {
if v, ok := os.LookupEnv("DEBUG"); ok && v != "" {
Info.Println("ENV VAR 'DEBUG' set: enabling debug-level logging")
Debug.SetOutput(os.Stderr)
}
}
or during runtime:
func httpServiceHandler(r *req) {
if r.TraceingOn {
Trace.SetOutput(os.Stderr)
} else {
Trace.SetOutput(ioutil.Discard)
}
}
Logging at pkg level is just like any other log method:
Debug.Printf("http request: %+v", r)
And if there's an expensive log event that you don't want to generate - if say the logger is set to discard - you can safely check the state of the logger like so:
if Trace.Writer() != ioutil.Discard {
// do expensive logging here
// e.g. bs, _ = ioutil.ReadAll(resp.Body); Trace.Println("RESP: RAW-http response:", string(bs))
}

is redirecting standard library log to logrus thread safe

I am using logrus library for structured logging in my Go project.
I have configured my logrus as below:
// Global variable for logging
var gLog = &Logger{moduleName: ModuleName, logrus: logrus.New()}
type Logger struct {
moduleName string
logrus *logrus.Logger
}
func SetupGlobalLogger(logPrefix string, logMode string) error {
if logMode == "file" {
logFilePath := fmt.Sprintf("var/%s.log", vite.Environment())
file, err := os.OpenFile(logFilePath, logFileFlags, logFilePermission)
if err != nil {
return err
}
gLog.logrus.SetOutput(file)
// redirect logs written using standard log library to same place as logrus
log.SetOutput(gLog.logrus.Writer())
log.Println(vite.MarkInfo, "redirect log to file:", logFilePath)
}
return nil
}
In this Go project, there are several places where standard log library statements like log.Println() are used.
I want to redirect those log messages to logrus.
For that I am using following statement in above code.
log.SetOutput(gLog.logrus.Writer())
My question is: Is this thread safe?
If one thread/go-routine is executing log.Println() while another executing gLog.logrus.Info() or something on logrus, will that be fine?
Is Logrus Thread-safe?
Yes:
Thread safety
By default, Logger is protected by a mutex for concurrent writes.
Except when it isn't.
Most of the non-thread-safe cases are bugs which will occur only in rare situations. Whether any of them matter to you depends, of course, on your use case.

Resources