github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/log/ansHook.go (about)

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/SAP/jenkins-library/pkg/ans"
     6  	"github.com/pkg/errors"
     7  	"github.com/sirupsen/logrus"
     8  	"os"
     9  	"strings"
    10  )
    11  
    12  // ANSHook is used to set the hook features for the logrus hook
    13  type ANSHook struct {
    14  	client        ans.Client
    15  	eventTemplate ans.Event
    16  	firing        bool
    17  }
    18  
    19  // Levels returns the supported log level of the hook.
    20  func (ansHook *ANSHook) Levels() []logrus.Level {
    21  	return []logrus.Level{logrus.WarnLevel, logrus.ErrorLevel, logrus.PanicLevel, logrus.FatalLevel}
    22  }
    23  
    24  // Fire creates a new event from the logrus and sends an event to the ANS backend
    25  func (ansHook *ANSHook) Fire(entry *logrus.Entry) (err error) {
    26  	if ansHook.firing {
    27  		return fmt.Errorf("ANS hook has already been fired")
    28  	}
    29  	ansHook.firing = true
    30  	defer func() { ansHook.firing = false }()
    31  
    32  	if len(strings.TrimSpace(entry.Message)) == 0 {
    33  		return
    34  	}
    35  	var event ans.Event
    36  	if event, err = ansHook.eventTemplate.Copy(); err != nil {
    37  		return
    38  	}
    39  
    40  	logLevel := entry.Level
    41  	event.SetSeverityAndCategory(logLevel)
    42  	var stepName string
    43  	if entry.Data["stepName"] != nil {
    44  		stepName = fmt.Sprint(entry.Data["stepName"])
    45  	} else {
    46  		stepName = "n/a"
    47  	}
    48  	event.Tags["cicd:stepName"] = stepName
    49  	if errorCategory := GetErrorCategory().String(); errorCategory != "undefined" {
    50  		event.Tags["cicd:errorCategory"] = errorCategory
    51  	}
    52  
    53  	event.EventTimestamp = entry.Time.Unix()
    54  	if event.Subject == "" {
    55  		event.Subject = fmt.Sprintf("Step '%s' sends '%s'", stepName, event.Severity)
    56  	}
    57  	event.Body = entry.Message
    58  	event.Tags["cicd:logLevel"] = logLevel.String()
    59  
    60  	return ansHook.client.Send(event)
    61  }
    62  
    63  type registrationUtil interface {
    64  	ans.Client
    65  	registerHook(hook *ANSHook)
    66  }
    67  
    68  type registrationUtilImpl struct {
    69  	ans.Client
    70  }
    71  
    72  func (u *registrationUtilImpl) registerHook(hook *ANSHook) {
    73  	RegisterHook(hook)
    74  }
    75  
    76  func (u *registrationUtilImpl) registerSecret(secret string) {
    77  	RegisterSecret(secret)
    78  }
    79  
    80  // RegisterANSHookIfConfigured creates a new ANS hook for logrus if it is configured and registers it
    81  func RegisterANSHookIfConfigured(correlationID string) error {
    82  	return registerANSHookIfConfigured(correlationID, &registrationUtilImpl{Client: &ans.ANS{}})
    83  }
    84  
    85  func registerANSHookIfConfigured(correlationID string, util registrationUtil) error {
    86  	ansServiceKeyJSON := os.Getenv("PIPER_ansHookServiceKey")
    87  	if len(ansServiceKeyJSON) == 0 {
    88  		return nil
    89  	}
    90  
    91  	ansServiceKey, err := ans.UnmarshallServiceKeyJSON(ansServiceKeyJSON)
    92  	if err != nil {
    93  		return errors.Wrap(err, "cannot initialize SAP Alert Notification Service due to faulty serviceKey json")
    94  	}
    95  	RegisterSecret(ansServiceKey.ClientSecret)
    96  
    97  	util.SetServiceKey(ansServiceKey)
    98  	if err = util.CheckCorrectSetup(); err != nil {
    99  		return errors.Wrap(err, "check http request to SAP Alert Notification Service failed; not setting up the ANS hook")
   100  	}
   101  
   102  	eventTemplate, err := setupEventTemplate(os.Getenv("PIPER_ansEventTemplate"), correlationID)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	util.registerHook(&ANSHook{
   107  		client:        util,
   108  		eventTemplate: eventTemplate,
   109  	})
   110  	return nil
   111  }
   112  
   113  func setupEventTemplate(customerEventTemplate, correlationID string) (ans.Event, error) {
   114  	event := ans.Event{
   115  		EventType: "Piper",
   116  		Tags:      map[string]interface{}{"ans:correlationId": correlationID, "ans:sourceEventId": correlationID},
   117  		Resource: &ans.Resource{
   118  			ResourceType: "Pipeline",
   119  			ResourceName: "Pipeline",
   120  		},
   121  	}
   122  
   123  	if len(customerEventTemplate) > 0 {
   124  		if err := event.MergeWithJSON([]byte(customerEventTemplate)); err != nil {
   125  			Entry().WithField("stepName", "ANS").Warnf("provided SAP Alert Notification Service event template '%s' could not be unmarshalled: %v", customerEventTemplate, err)
   126  			return ans.Event{}, errors.Wrapf(err, "provided SAP Alert Notification Service event template '%s' could not be unmarshalled", customerEventTemplate)
   127  		}
   128  	}
   129  	if len(event.Severity) > 0 {
   130  		Entry().WithField("stepName", "ANS").Warnf("event severity set to '%s' will be overwritten according to the log level", event.Severity)
   131  		event.Severity = ""
   132  	}
   133  	if len(event.Category) > 0 {
   134  		Entry().WithField("stepName", "ANS").Warnf("event category set to '%s' will be overwritten according to the log level", event.Category)
   135  		event.Category = ""
   136  	}
   137  	if err := event.Validate(); err != nil {
   138  		return ans.Event{}, errors.Wrap(err, "did not initialize SAP Alert Notification Service due to faulty event template json")
   139  	}
   140  	return event, nil
   141  }