Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard logger interface #25

Open
Magicking opened this issue Feb 26, 2019 · 5 comments
Open

Standard logger interface #25

Magicking opened this issue Feb 26, 2019 · 5 comments

Comments

@Magicking
Copy link

Using a standard logger interface would allow having a drop-in replacement for Go logging with ApplicationInsights

@jjjordanmsft
Copy link
Contributor

Do you have any suggestions? I know of log in the standard library, but am unaware of any widely-used frameworks out there.

@Magicking
Copy link
Author

Magicking commented Mar 4, 2019

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.

@adrianliechti
Copy link

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
	}

@probal
Copy link

probal commented Nov 2, 2021

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)
	}

@zhengyuan-ehps
Copy link

zhengyuan-ehps commented Oct 31, 2023

@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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants