How to extract the zerolog event fields in the hook

163 Views Asked by At

I wrote a code for integrating Azure app insights with Zerolog, one thing I'm not sure about is how to get the event fields inside the hook function so I can publish the data fields into the Azure app insights custom properties field or is there any other cleaner way to achieve this.

Does anyone think zerolog should expose a function to get the fields?

package zeroappinsights

import (
    "context"
    "encoding/json"
    "github.com/gin-gonic/gin"
    "net/http"
    "os"
    "time"

    "github.com/microsoft/ApplicationInsights-Go/appinsights"
    "github.com/microsoft/ApplicationInsights-Go/appinsights/contracts"
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"

    "github.com/org/package/constants"
    "github.com/org/package/models"
)

var levelMap = map[zerolog.Level]contracts.SeverityLevel{
    zerolog.ErrorLevel: contracts.Error,
    zerolog.InfoLevel:  contracts.Information,
    zerolog.DebugLevel: contracts.Verbose,
    zerolog.FatalLevel: contracts.Critical,
    zerolog.WarnLevel:  contracts.Warning,
}

var appInsightsProperties = []string{
    contracts.OperationId,
    contracts.OperationParentId,
    contracts.UserId,
    contracts.SessionId,
}

type TracingHook struct {
    env             constants.Environment
    telemetryClient appinsights.TelemetryClient
    file            *os.File
    eventsMap       map[string]string
}

func NewAITracing(serviceName, instrumentationKey string, env constants.Environment, eventsMap map[string]string) *TracingHook {
    client := appinsights.NewTelemetryClient(instrumentationKey)
    client.Context().Tags.Cloud().SetRole(serviceName)
    if env.IsLocal() {
        file, err := os.OpenFile(serviceName+".log", os.O_CREATE, 0644)
        if err != nil {
            return nil
        }

        return &TracingHook{
            telemetryClient: client,
            file:            file,
            env:             constants.Local,
        }
    }
    return &TracingHook{
        telemetryClient: client,
        file:            nil,
        env:             env,
        eventsMap:       eventsMap,
    }
}

func (h *TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
    ctx := e.GetCtx()
    if appinsightsLevel, ok := levelMap[level]; ok {
        trace := appinsights.NewTraceTelemetry(msg, appinsightsLevel)
        for _, property := range appInsightsProperties {
            trace.Tags[property] = ctx.Value(property).(string)
        } 
        //I want to add more properties from the event to trace.Properties
        trace.Timestamp = time.Now().UTC()
        if h.env.IsLocal() {
            // Marshal the request into a JSON byte slice
            traceJSON, err := json.Marshal(trace)
            if err != nil {
                log.Printf("error marshaling request: %v", err)
                return
            }
            if _, err = h.file.Write(append(traceJSON, '\n')); err != nil {
                log.Printf("error occurred while writing to file: %v", err)
                return
            }
        } else {
            go h.telemetryClient.Track(trace)
        }
    }

}

func (h *TracingHook) LoggingMiddleware(logger zerolog.Logger) func(c *gin.Context) {
    return func(c *gin.Context) {
        startTime := time.Now().UTC()
        telemetry := appinsights.NewEventTelemetry(h.eventsMap[c.Request.URL.Path])
        h.telemetryClient.Track(telemetry)
        operationID := telemetry.Tags[contracts.OperationId]
        operationParentID := telemetry.Tags[contracts.OperationParentId]
        userID := telemetry.Tags[contracts.UserId]
        sessionID := telemetry.Tags[contracts.SessionId]

        if value, exists := c.Get("userProfile"); exists {
            profile := value.(models.UserProfile)
            userID = profile.ID
            sessionID = c.Request.Header.Get(constants.Session)
        }

        values := []string{operationID, operationParentID, userID, sessionID}

        ctx := c.Request.Context()
        for i, key := range appInsightsProperties {
            ctx = context.WithValue(ctx, key, values[i])
        }
        loggerContext := logger.WithContext(ctx)
        c.Set("loggerCtx", loggerContext)
        c.Next()

        duration := time.Since(startTime)
        request := appinsights.NewRequestTelemetry(c.Request.Method, c.Request.URL.Path, duration, http.StatusText(c.Writer.Status()))
        request.Timestamp = time.Now().UTC()
        tags := request.Tags
        tags[contracts.OperationId] = operationID
        tags[contracts.OperationParentId] = operationParentID
        tags[contracts.UserId] = userID
        tags[contracts.SessionId] = sessionID
        request.Tags = tags

        if h.env.IsLocal() {
            // Marshal the request into a JSON byte slice
            requestJSON, err := json.Marshal(request)
            if err != nil {
                log.Printf("error marshaling request: %v", err)
                return
            }
            if _, err = h.file.Write(append(requestJSON, '\n')); err != nil {
                log.Printf("error occurred while writing to file: %v", err)
                return
            }
            return
        }
        h.telemetryClient.Track(request)
    }
}

1

There are 1 best solutions below

0
Suresh Chikkam On

Below you can see the hook function that extracts the relevant information from the Zerolog event and add it to the Application Insights telemetry properties.

  • Integrate Azure Application Insights with Zerolog and capture custom properties

Code:

package zeroappinsights

import (
    "context"
    "encoding/json"
    "net/http"
    "os"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/microsoft/ApplicationInsights-Go/appinsights"
    "github.com/microsoft/ApplicationInsights-Go/appinsights/contracts"
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"

    "github.com/org/package/constants"
    "github.com/org/package/models"
)

var levelMap = map[zerolog.Level]contracts.SeverityLevel{
    zerolog.ErrorLevel: contracts.Error,
    zerolog.InfoLevel:  contracts.Information,
    zerolog.DebugLevel: contracts.Verbose,
    zerolog.FatalLevel: contracts.Critical,
    zerolog.WarnLevel:  contracts.Warning,
}

var appInsightsProperties = []string{
    contracts.OperationId,
    contracts.OperationParentId,
    contracts.UserId,
    contracts.SessionId,
}

// ApplicationInsightsHook is a Zerolog hook that sends log entries to Application Insights
type ApplicationInsightsHook struct {
    client appinsights.TelemetryClient
}

// NewApplicationInsightsHook creates a new ApplicationInsightsHook
func NewApplicationInsightsHook(instrumentationKey string) *ApplicationInsightsHook {
    client := appinsights.NewTelemetryClient(instrumentationKey)
    return &ApplicationInsightsHook{client: client}
}

// Fire is called when a log event is fired.
func (hook *ApplicationInsightsHook) Fire(e *zerolog.Event) error {
    severity, ok := levelMap[e.Level()]
    if !ok {
        severity = contracts.Verbose
    }

    telemetry := appinsights.NewTraceTelemetry(e.Message().String(), severity)

    // Add custom properties
    e.Fields(func(key string, value interface{}) {
        telemetry.Properties[key] = fmt.Sprintf("%v", value)
    })

    // Add predefined properties
    for _, prop := range appInsightsProperties {
        if value, ok := e.Context[prop].(string); ok {
            telemetry.Properties[prop] = value
        }
    }

    hook.client.Track(telemetry)

    return nil
}

// Levels returns the log levels to enable for this hook.
func (hook *ApplicationInsightsHook) Levels() []zerolog.Level {
    return []zerolog.Level{
        zerolog.PanicLevel,
        zerolog.FatalLevel,
        zerolog.ErrorLevel,
        zerolog.WarnLevel,
        zerolog.InfoLevel,
        zerolog.DebugLevel,
    }
}

// SetLogger sets the logger for the hook.
func (hook *ApplicationInsightsHook) SetLogger(logger *zerolog.Logger) {
    *logger = logger.Hook(hook)
}

// Usage:
func main() {
    // Initialize the Application Insights hook
    aiHook := NewApplicationInsightsHook("your-instrumentation-key")

    // Create a Zerolog logger and set the hook
    logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
    aiHook.SetLogger(&logger)

    // Use the logger as usual
    logger.Info().Str("customField", "customValue").Msg("Log message")
}
  • By this ApplicationInsightsHook struct is created and it implementing the zerolog.Hook interface.

  • The Fire method is called when a log event is fired, and it extracts relevant information from the Zerolog event, including custom fields, and adds them to the Application Insights telemetry.

enter image description here

enter image description here