traefik/vendor/github.com/jjcollinge/logrus-appinsights/hook.go

174 lines
4.8 KiB
Go
Raw Normal View History

package logrus_appinsights
import (
"encoding/json"
"fmt"
"github.com/Microsoft/ApplicationInsights-Go/appinsights"
"github.com/sirupsen/logrus"
)
var defaultLevels = []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
}
var levelMap = map[logrus.Level]appinsights.SeverityLevel{
logrus.PanicLevel: appinsights.Critical,
logrus.FatalLevel: appinsights.Critical,
logrus.ErrorLevel: appinsights.Error,
logrus.WarnLevel: appinsights.Warning,
logrus.InfoLevel: appinsights.Information,
}
// AppInsightsHook is a logrus hook for Application Insights
type AppInsightsHook struct {
client appinsights.TelemetryClient
async bool
levels []logrus.Level
ignoreFields map[string]struct{}
filters map[string]func(interface{}) interface{}
}
// New returns an initialised logrus hook for Application Insights
func New(name string, conf Config) (*AppInsightsHook, error) {
if conf.InstrumentationKey == "" {
return nil, fmt.Errorf("InstrumentationKey is required and missing from configuration")
}
telemetryConf := appinsights.NewTelemetryConfiguration(conf.InstrumentationKey)
if conf.MaxBatchSize != 0 {
telemetryConf.MaxBatchSize = conf.MaxBatchSize
}
if conf.MaxBatchInterval != 0 {
telemetryConf.MaxBatchInterval = conf.MaxBatchInterval
}
if conf.EndpointUrl != "" {
telemetryConf.EndpointUrl = conf.EndpointUrl
}
telemetryClient := appinsights.NewTelemetryClientFromConfig(telemetryConf)
if name != "" {
telemetryClient.Context().Cloud().SetRoleName(name)
}
return &AppInsightsHook{
client: telemetryClient,
levels: defaultLevels,
ignoreFields: make(map[string]struct{}),
filters: make(map[string]func(interface{}) interface{}),
}, nil
}
// NewWithAppInsightsConfig returns an initialised logrus hook for Application Insights
func NewWithAppInsightsConfig(name string, conf *appinsights.TelemetryConfiguration) (*AppInsightsHook, error) {
if conf == nil {
return nil, fmt.Errorf("Nil configuration provided")
}
if conf.InstrumentationKey == "" {
return nil, fmt.Errorf("InstrumentationKey is required in configuration")
}
telemetryClient := appinsights.NewTelemetryClientFromConfig(conf)
if name != "" {
telemetryClient.Context().Cloud().SetRoleName(name)
}
return &AppInsightsHook{
client: telemetryClient,
levels: defaultLevels,
ignoreFields: make(map[string]struct{}),
filters: make(map[string]func(interface{}) interface{}),
}, nil
}
// Levels returns logging level to fire this hook.
func (hook *AppInsightsHook) Levels() []logrus.Level {
return hook.levels
}
// SetLevels sets logging level to fire this hook.
func (hook *AppInsightsHook) SetLevels(levels []logrus.Level) {
hook.levels = levels
}
// SetAsync sets async flag for sending logs asynchronously.
// If use this true, Fire() does not return error.
func (hook *AppInsightsHook) SetAsync(async bool) {
hook.async = async
}
// AddIgnore adds field name to ignore.
func (hook *AppInsightsHook) AddIgnore(name string) {
hook.ignoreFields[name] = struct{}{}
}
// AddFilter adds a custom filter function.
func (hook *AppInsightsHook) AddFilter(name string, fn func(interface{}) interface{}) {
hook.filters[name] = fn
}
// Fire is invoked by logrus and sends log data to Application Insights.
func (hook *AppInsightsHook) Fire(entry *logrus.Entry) error {
if !hook.async {
return hook.fire(entry)
}
// async - fire and forget
go hook.fire(entry)
return nil
}
func (hook *AppInsightsHook) fire(entry *logrus.Entry) error {
trace, err := hook.buildTrace(entry)
if err != nil {
return err
}
hook.client.TrackTraceTelemetry(trace)
return nil
}
func (hook *AppInsightsHook) buildTrace(entry *logrus.Entry) (*appinsights.TraceTelemetry, error) {
// Add the message as a field if it isn't already
if _, ok := entry.Data["message"]; !ok {
entry.Data["message"] = entry.Message
}
level := levelMap[entry.Level]
trace := appinsights.NewTraceTelemetry(entry.Message, level)
if trace == nil {
return nil, fmt.Errorf("Could not create telemetry trace with entry %+v", entry)
}
for k, v := range entry.Data {
if _, ok := hook.ignoreFields[k]; ok {
continue
}
if fn, ok := hook.filters[k]; ok {
v = fn(v) // apply custom filter
} else {
v = formatData(v) // use default formatter
}
vStr := fmt.Sprintf("%v", v)
trace.SetProperty(k, vStr)
}
trace.SetProperty("source_level", entry.Level.String())
trace.SetProperty("source_timestamp", entry.Time.String())
return trace, nil
}
// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
switch value := value.(type) {
case json.Marshaler:
return value
case error:
return value.Error()
case fmt.Stringer:
return value.String()
default:
return value
}
}
func stringPtr(str string) *string {
return &str
}