Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go: Create io.Writer inteface for logging to mongodb database

Using go (golang):

Is there a way to create a logger that outputs to a database?

Or more precisely, can I implement some kind of io.Writer interface that I can pass as the first argument to log.New()?

EG: (dbLogger would receive the output of the log and write it to the database)

logger := log.New(dbLogger, "dbLog: ", log.Lshortfile) logger.Print("This message will be stored in the database")

I would assume that I should just create my own database logging function, but I was curious to see if there is already a way of doing this using the existing tools in the language.

For some context, I'm using mgo.v2 to handle my mongodb database, but I don't see any io.Writer interfaces there other than in GridFS, which I think solves a different problem.

I'm also still getting my head around the language, so I may have used some terms above incorrecly. Any corrections are very welcome.

like image 779
17xande Avatar asked Feb 05 '23 19:02

17xande


1 Answers

This is easily doable, because the log.Logger type guarantees that each log message is delivered to the destination io.Writer with a single Writer.Write() call:

Each logging operation makes a single call to the Writer's Write method. A Logger can be used simultaneously from multiple goroutines; it guarantees to serialize access to the Writer.

So basically you just need to create a type which implements io.Writer, and whose Write() method creates a new document with the contents of the byte slice, and saves it in the MongoDB.

Here's a simple implementation which does that:

type MongoWriter struct {
    sess *mgo.Session
}

func (mw *MongoWriter) Write(p []byte) (n int, err error) {
    c := mw.sess.DB("").C("log")
    err = c.Insert(bson.M{
        "created": time.Now(),
        "msg":     string(p),
    })
    if err != nil {
        return
    }
    return len(p), nil
}

Using it:

sess := ... // Get a MongoDB session

mw := &MongoWriter{sess}
log.SetOutput(mw)

// Now the default Logger of the log package uses our MongoWriter.
// Generate a log message that will be inserted into MongoDB:
log.Println("I'm the first log message.")
log.Println("I'm multi-line,\nbut will still be in a single log message.")

Obviously if you're using another log.Logger instance, set the MongoWriter to that, e.g.:

mylogger := log.New(mw, "", 0)
mylogger.Println("Custom logger")

Note that the log messages end with newline as log.Logger appends it even if the log message itself does not end with newline. If you don't want to log the ending newline, you may simply cut it, e.g.:

func (mw *MongoWriter) Write(p []byte) (n int, err error) {
    origLen := len(p)
    if len(p) > 0 && p[len(p)-1] == '\n' {
        p = p[:len(p)-1] // Cut terminating newline
    }

    c := mw.sess.DB("").C("log")

    // ... the rest is the same

    return origLen, nil // Must return original length (we resliced p)
}
like image 96
icza Avatar answered Feb 08 '23 14:02

icza