-
Notifications
You must be signed in to change notification settings - Fork 244
Custom receivers
Seelog tries to provide you with the most popular receiver options like: file (simple/rolling), console, network connection, etc.
But sometimes you run into a situation where you either have non-seelog logging system and want to use them together (e.g. AppEngine) or you want to implement another receiver (e.g. syslog).
To provide you with the possibility to implement another receiver or create an adapter for another logging system, seelog has different custom functions and techniques.
There are different scenarios with custom receivers/adapters and seelog provides different methods for each of them.
This is the scenario when you use seelog in all your code, but under some circumstances, want to just redirect all its output to another logging system. For example, you'd like to leave seelog calls there to be able to return to its functionality later, but currently you would like to just use it as a proxy.
In this scenario, seelog doesn't use any of its filter/exception/dispatching functionality and just stands as a proxy.
There are two sub-cases of this scenario:
In this scenario, you redirect all seelog output to something really simple, that can be fully described by the io.Writer interface. For such cases, you should use seelog.LoggerFromWriterWithMinLevelAndFormat
. It takes an io.Writer, format, and a minimal log level as the only parameters. Minimal log level gives you the possibility to have production/development builds with minimal level set to 'info' or 'trace' accordingly.
Usage example:
In this example we have a simple io.Writer and just proxy all seelog output to it. Here we emulate the 'production' situation, so minimal level is set to 'info'.
package main
import (
"fmt"
seelog "github.com/cihub/seelog"
)
type SomeWriter struct{}
func (sw *SomeWriter) Write(p []byte) (n int, err error) {
fmt.Println(string(p))
return len(p), nil
}
func main() {
log, err := seelog.LoggerFromWriterWithMinLevelAndFormat(&SomeWriter{}, seelog.InfoLvl, "%Ns [%Level] %Msg")
if err != nil {
panic(err)
}
defer log.Flush()
log.Debug("Test")
log.Info("Test2")
}
Output:
1381334355481209992 [Info] Test2
This scenario is similar to the previous one, but here you need to log to a logging subsystem that has its own levels, rules, or special actions.
In this situation you use LoggerFromCustomReceiver
that takes a custom receiver which implements seelog.CustomReceiver
interface.
Usage example:
Let's pretend that we use a logging subsystem that logs using 3 log levels and a function info:
type SomeLogger struct{}
func (sw *SomeLogger) Debug(fromFunc, s string) {
fmt.Printf("DEBUG from %s: %s\n", fromFunc, s)
}
func (sw *SomeLogger) Info(fromFunc, s string) {
fmt.Printf("INFO from %s: %s\n", fromFunc, s)
}
func (sw *SomeLogger) Error(fromFunc, s string) {
fmt.Printf("ERROR from %s: %s\n", fromFunc, s)
}
Now we want to proxy seelog to it somehow. So we need to redirect seelog.Trace + seelog.Debug to SomeLogger.Debug, seelog.Info to SomeLogger.Info, and seelog.Warn + seelog.Error + seelog.Critical to SomeLogger.Error. Also we want to pass the caller function information.
To do this we create a custom receiver which implements seelog.CustomReceiver
and proxies everything to SomeLogger
in a way that is required:
type SomeCustomReceiver struct {
l *SomeLogger
}
func (ar *SomeCustomReceiver) ReceiveMessage(message string, level seelog.LogLevel, context seelog.LogContextInterface) error {
switch level {
case seelog.TraceLvl:
fallthrough
case seelog.DebugLvl:
ar.l.Debug(context.Func(), message)
case seelog.InfoLvl:
ar.l.Info(context.Func(), message)
case seelog.WarnLvl:
fallthrough
case seelog.ErrorLvl:
fallthrough
case seelog.CriticalLvl:
ar.l.Error(context.Func(), message)
}
return nil
}
/* NOTE: NOT called when LoggerFromCustomReceiver is used */
func (ar *SomeCustomReceiver) AfterParse(initArgs seelog.CustomReceiverInitArgs) error {
return nil
}
func (ar *SomeCustomReceiver) Flush() {
}
func (ar *SomeCustomReceiver) Close() error {
return nil
}
func main() {
log, err := seelog.LoggerFromCustomReceiver(&SomeCustomReceiver{&SomeLogger{}})
if err != nil {
panic(err)
}
defer log.Flush()
log.Debug("Test")
log.Info("Test2")
}
Output:
DEBUG from main.main: Test
INFO from main.main: Test2
This is the scenario when you have your own logging subsystem/component and you want to redirect its log stream to seelog. This is the opposite situation to Scenario 1.
In this scenario you just call seelog functions from your other logging subsystem and call SetAdditionalStackDepth
to detect actual caller func (not the subsystem one). The latter will be explained using the following example.
Usage example:
package main
import (
seelog "github.com/cihub/seelog"
)
type SomeLogger struct {
inner seelog.LoggerInterface
}
func (sw *SomeLogger) Debug(s string) {
sw.inner.Debug(s)
}
func (sw *SomeLogger) Info(s string) {
sw.inner.Info(s)
}
func (sw *SomeLogger) Error(s string) {
sw.inner.Error(s)
}
var log = &SomeLogger{}
func init() {
var err error
log.inner, err = seelog.LoggerFromConfigAsString(
`<seelog>
<outputs>
<console formatid="fmt"/>
</outputs>
<formats>
<format id="fmt" format="[%Func] [%Lev] %Msg%n"/>
</formats>
</seelog>
`)
if err != nil {
panic(err)
}
log.inner.SetAdditionalStackDepth(1)
}
func main() {
defer log.inner.Flush()
log.Debug("Test")
log.Info("Test2")
}
Output:
[main.main] [Dbg] Test
[main.main] [Inf] Test2
To get the idea of SetAdditionalStackDepth
lets pretend that it is not called or is called with argument = 0 instead of 1. In that case the output is:
[main.(*SomeLogger).Debug] [Dbg] Test
[main.(*SomeLogger).Info] [Inf] Test2
It is actually valid output because these are the functions where seelog was called. But in current scenario you are actually redirecting your subsystem log messages to seelog, so actually you need to know where your subsystem func was called, not the seelog one. That's why SetAdditionalStackDepth
is used. Its argument is set to the additional number of caller stack frames to skip to get to the actual caller func.