ApplicationInsights-Go
ApplicationInsights-Go copied to clipboard
Standard logger interface
Using a standard logger interface would allow having a drop-in replacement for Go logging with ApplicationInsights
Do you have any suggestions? I know of log
in the standard library, but am unaware of any widely-used frameworks out there.
Sure, a drop-in replacement for log
with all of log.* functions implemented would be a good start but what would be best I think is something like https://github.com/sirupsen/logrus .
Especially log.WithFields(log.Fields{})
as it would fit best what App Insight UI shows.
hi @Magicking
I experimented this night with a simple implementation of application insights for our services. Maybe this code snipets help you. I wrote some global handler for the frameworks logrus, gin and resty, resulting in quite a great out-of-the-box experience
cheers
config.go
package insights
import "github.com/Microsoft/ApplicationInsights-Go/appinsights"
type InsightsConfig struct {
InstrumentationKey string
Role string
Version string
}
func createTelemetryClient(config *InsightsConfig) appinsights.TelemetryClient {
client := appinsights.NewTelemetryClient(config.InstrumentationKey)
if len(config.Role) > 0 {
client.Context().Tags.Cloud().SetRole(config.Role)
}
if len(config.Version) > 0 {
client.Context().Tags.Application().SetVer(config.Version)
}
return client
}
logrus.go
To log normal application logging, we do something like this:
package insights
import (
"encoding/json"
"fmt"
"github.com/Microsoft/ApplicationInsights-Go/appinsights"
"github.com/Microsoft/ApplicationInsights-Go/appinsights/contracts"
"github.com/sirupsen/logrus"
)
func SetupLogrus(config *InsightsConfig) {
hook := &LogrusHook{
Client: createTelemetryClient(config),
}
logrus.AddHook(hook)
}
type LogrusHook struct {
Client appinsights.TelemetryClient
}
func (hook *LogrusHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (hook *LogrusHook) Fire(entry *logrus.Entry) error {
if _, ok := entry.Data["message"]; !ok {
entry.Data["message"] = entry.Message
}
level := convertSeverityLevel(entry.Level)
telemetry := appinsights.NewTraceTelemetry(entry.Message, level)
for key, value := range entry.Data {
value = formatData(value)
telemetry.Properties[key] = fmt.Sprintf("%v", value)
}
hook.Client.Track(telemetry)
return nil
}
func convertSeverityLevel(level logrus.Level) contracts.SeverityLevel {
switch level {
case logrus.PanicLevel:
return contracts.Critical
case logrus.FatalLevel:
return contracts.Critical
case logrus.ErrorLevel:
return contracts.Error
case logrus.WarnLevel:
return contracts.Warning
case logrus.InfoLevel:
return contracts.Information
case logrus.DebugLevel, logrus.TraceLevel:
return contracts.Verbose
default:
return contracts.Information
}
}
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
}
}
gin
to log incoming requsts, we wrote a simple middleware for gin
package insights
import (
"strconv"
"time"
"github.com/Microsoft/ApplicationInsights-Go/appinsights"
"github.com/gin-gonic/gin"
)
func SetupGin(engine *gin.Engine, config *InsightsConfig) {
engine.Use(InsightsWithConfig(config))
}
func InsightsWithConfig(config *InsightsConfig) gin.HandlerFunc {
client := createTelemetryClient(config)
return func(c *gin.Context) {
url := c.Request.RequestURI
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
// Process request
c.Next()
if path == "/readiness" || path == "/liveness" {
return
}
end := time.Now()
duration := end.Sub(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
userAgent := c.Request.UserAgent()
//errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
bodySize := c.Writer.Size()
if query != "" {
path = path + "?" + query
}
telemetry := appinsights.NewRequestTelemetry(method, url, duration, strconv.Itoa(statusCode))
telemetry.Timestamp = end
telemetry.MarkTime(start, end)
telemetry.Source = clientIP
telemetry.Success = statusCode >= 200 && statusCode < 400
telemetry.Properties["user-agent"] = userAgent
telemetry.Measurements["body-size"] = float64(bodySize)
client.Track(telemetry)
}
}
resty
we use resty as rest client for calling other microservices or external services.
package insights
import (
"fmt"
"net/url"
"strconv"
"github.com/Microsoft/ApplicationInsights-Go/appinsights"
"gopkg.in/resty.v1"
)
func SetupResty(config *InsightsConfig) {
resty.OnAfterResponse(restyHandler(config))
}
func restyHandler(config *InsightsConfig) func(*resty.Client, *resty.Response) error {
client := createTelemetryClient(config)
return func(c *resty.Client, response *resty.Response) error {
request := response.Request
start := request.Time
duration := response.Time()
statusCode := response.StatusCode()
name := fmt.Sprintf("%s %s", request.Method, request.URL)
success := statusCode < 400 || statusCode == 401
target := request.RawRequest.Host
// Sanitize URL for the request name
if parsedUrl, err := url.Parse(request.URL); err == nil {
// Remove the query
parsedUrl.RawQuery = ""
parsedUrl.ForceQuery = false
// Remove the fragment
parsedUrl.Fragment = ""
// Remove the user info, if any.
parsedUrl.User = nil
// Write back to name
name = parsedUrl.String()
}
name = fmt.Sprintf("%s %s", request.Method, name)
telemetry := appinsights.NewRemoteDependencyTelemetry(name, "HTTP", target, success)
telemetry.Data = request.URL
telemetry.ResultCode = strconv.Itoa(statusCode)
telemetry.MarkTime(start, start.Add(duration))
client.Track(telemetry)
return nil
}
}
your main
if key, ok := os.LookupEnv("AZURE_INSTRUMENTATION_KEY"); ok {
insightsConfig := &insights.InsightsConfig{
Role: NAMEOFYOURAPP,
Version: VERSIONOFYOURAPP,
InstrumentationKey: key,
}
insights.SetupGin(router, insightsConfig) // remove if not using gin
insights.SetupResty(insightsConfig) // remove if not using resty
insights.SetupLogrus(insightsConfig) // remove if not using logrus
}
With the following middleware, we get empty Operation name in Azure application insight Performance menu. What am I missing here, any suggestion is much appreciated.
func InsightsWithConfig(config *InsightsConfig) gin.HandlerFunc {
client := createTelemetryClient(config)
return func(c *gin.Context) {
url := c.Request.RequestURI
start := time.Now()
c.Next()
end := time.Now()
duration := end.Sub(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
userAgent := c.Request.UserAgent()
bodySize := c.Writer.Size()
telemetry := appinsights.NewRequestTelemetry(method, url, duration, strconv.Itoa(statusCode))
telemetry.Timestamp = end
telemetry.MarkTime(start, end)
telemetry.Source = clientIP
telemetry.Success = statusCode >= 200 && statusCode < 400
telemetry.Properties["user-agent"] = userAgent
telemetry.Measurements["body-size"] = float64(bodySize)
client.Track(telemetry)
}
@probal I figure out the solution that you have to specify the operation name through telemetry tag in this middleware as well like
telemetry.Tags[contracts.OperationName] = fmt.Sprintf("%s %s", method, c.Request.URL.Path)