internal/telemetry/telemetry.go (122 lines of code) (raw):

package telemetry import ( "fmt" "log/slog" "sync" "github.com/Azure/applicationhealth-extension-linux/internal/handlerenv" "github.com/Azure/applicationhealth-extension-linux/pkg/logging" "github.com/Azure/azure-extension-platform/pkg/extensionevents" "github.com/google/uuid" ) type EventLevel string type EventTask string const ( CriticalEvent EventLevel = "Critical" ErrorEvent EventLevel = "Error" WarningEvent EventLevel = "Warning" VerboseEvent EventLevel = "Verbose" InfoEvent EventLevel = "Informational" ) const ( MainTask EventTask = "Main" AppHealthTask EventTask = "AppHealth" AppHealthProbeTask EventTask = "AppHealth-HealthProbe" ReportStatusTask EventTask = "ReportStatus" ReportHeatBeatTask EventTask = "CheckHealthAndReportHeartBeat" StartVMWatchTask EventTask = "StartVMWatchIfApplicable" StopVMWatchTask EventTask = "OnExited" SetupVMWatchTask EventTask = "SetupVMWatchProcess" KillVMWatchTask EventTask = "KillVMWatchIfApplicable" ) var ( ErrUnableToInitialize = fmt.Errorf("unable to initialize telemetry") ErrTelemetryNotInit = fmt.Errorf("telemetry not initialized") ) type Telemetry struct { eem *extensionevents.ExtensionEventManager } var ( instance *Telemetry once sync.Once mutex sync.Mutex ) func NewTelemetry(h *handlerenv.HandlerEnvironment) (*Telemetry, error) { if instance != nil { slog.Warn("Telemetry instance already initialized") return instance, nil } if h.EventsFolder == "" { return nil, fmt.Errorf("events folder is not set: %w", ErrUnableToInitialize) } once.Do(func() { instance = &Telemetry{ eem: extensionevents.New(logging.NewNopLogger(), &h.HandlerEnvironment), } // OperationId is initialized here but currently AppHealth telemetry does not depend on it. // There are other scenarios for VMWatch where it is overridden instance.eem.SetOperationID(uuid.New().String()) }) return instance, nil } func GetTelemetry() (*Telemetry, error) { mutex.Lock() defer mutex.Unlock() if instance == nil { return nil, ErrTelemetryNotInit } return instance, nil } // LogEvent sends a telemetry event with the specified level, task name, and message. func (t *Telemetry) SendEvent(level EventLevel, taskName EventTask, message string, keyvals ...interface{}) { keyvals = append(keyvals, "task", taskName) // Select the appropriate event dispatcher and log dispatcher based on the event level. // then log and send the event. if eventDispatcher, ok := t.getEventDispatcherFunc(level); ok { if log, ok := t.getLogDispatcherFunc(level); ok { log(message, keyvals...) } eventDispatcher(string(taskName), message) } else { slog.Error("Invalid event level", "level", level) } } // Helper method to dynamically get the dispatch function func (t *Telemetry) getEventDispatcherFunc(level EventLevel) (func(string, string), bool) { switch level { case InfoEvent: return t.eem.LogInformationalEvent, true case VerboseEvent: return t.eem.LogVerboseEvent, true case WarningEvent: return t.eem.LogWarningEvent, true case ErrorEvent: return t.eem.LogErrorEvent, true case CriticalEvent: return t.eem.LogCriticalEvent, true default: return nil, false } } func (t *Telemetry) getLogDispatcherFunc(level EventLevel) (func(string, ...any), bool) { switch level { case InfoEvent: return slog.Info, true case VerboseEvent: return slog.Debug, true case WarningEvent: return slog.Warn, true case ErrorEvent: return slog.Error, true case CriticalEvent: return slog.Error, true default: return nil, false } } func SetOperationID(operationID string) { if instance == nil { return } // ExtensionEvent package does not expose current operation ID. instance.SendEvent(InfoEvent, MainTask, fmt.Sprintf("Overriding OperationId with %s", operationID)) instance.eem.SetOperationID(operationID) } // SendEvent sends an event with the specified level, task name, message, and key-value pairs. // It is a package level function that can be used to send telemetry events. // If the instance is nil, the function returns without sending the event. func SendEvent(level EventLevel, taskName EventTask, message string, keyvals ...interface{}) { if instance == nil { return } instance.SendEvent(level, taskName, message, keyvals...) }