github.com/Financial-Times/publish-availability-monitor@v1.12.0/messageHandler.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/Financial-Times/go-logger/v2" 10 "github.com/Financial-Times/kafka-client-go/v4" 11 "github.com/Financial-Times/publish-availability-monitor/checks" 12 "github.com/Financial-Times/publish-availability-monitor/config" 13 "github.com/Financial-Times/publish-availability-monitor/content" 14 "github.com/Financial-Times/publish-availability-monitor/envs" 15 "github.com/Financial-Times/publish-availability-monitor/feeds" 16 "github.com/Financial-Times/publish-availability-monitor/httpcaller" 17 "github.com/Financial-Times/publish-availability-monitor/metrics" 18 ) 19 20 const systemIDKey = "Origin-System-Id" 21 22 type MessageHandler interface { 23 HandleMessage(msg kafka.FTMessage) 24 } 25 26 func NewKafkaMessageHandler( 27 appConfig *config.AppConfig, 28 environments *envs.Environments, 29 subscribedFeeds map[string][]feeds.Feed, 30 metricSink chan metrics.PublishMetric, 31 metricContainer *metrics.History, 32 e2eTestUUIDs []string, 33 log *logger.UPPLogger, 34 ) MessageHandler { 35 return &kafkaMessageHandler{ 36 appConfig: appConfig, 37 environments: environments, 38 subscribedFeeds: subscribedFeeds, 39 metricSink: metricSink, 40 metricContainer: metricContainer, 41 e2eTestUUIDs: e2eTestUUIDs, 42 log: log, 43 } 44 } 45 46 type kafkaMessageHandler struct { 47 appConfig *config.AppConfig 48 environments *envs.Environments 49 subscribedFeeds map[string][]feeds.Feed 50 metricSink chan metrics.PublishMetric 51 metricContainer *metrics.History 52 e2eTestUUIDs []string 53 log *logger.UPPLogger 54 } 55 56 func (h *kafkaMessageHandler) HandleMessage(msg kafka.FTMessage) { 57 tid := msg.Headers["X-Request-Id"] 58 log := h.log.WithTransactionID(tid) 59 60 log.Info("Received message") 61 62 if h.isIgnorableMessage(msg) { 63 log.Info("Message is ignorable. Skipping...") 64 return 65 } 66 67 publishedContent, err := h.unmarshalContent(msg) 68 if err != nil { 69 h.log.WithError(err).Warn("Cannot unmarshal message") 70 return 71 } 72 73 publishDateString := msg.Headers["Message-Timestamp"] 74 publishDate, err := time.Parse(checks.DateLayout, publishDateString) 75 if err != nil { 76 h.log.WithError(err).Errorf("Cannot parse publish date [%v]", 77 publishDateString) 78 return 79 } 80 81 var paramsToSchedule []*checks.SchedulerParam 82 83 for _, preCheck := range checks.MainPreChecks() { 84 ok, scheduleParam := preCheck(publishedContent, tid, publishDate, h.appConfig, h.metricContainer, h.environments, h.log) 85 if ok { 86 paramsToSchedule = append(paramsToSchedule, scheduleParam) 87 } else { 88 //if a main check is not ok, additional checks make no sense 89 return 90 } 91 } 92 93 hC := httpcaller.NewCaller(10) 94 95 //key is the endpoint alias from the config 96 endpointSpecificChecks := map[string]checks.EndpointSpecificCheck{ 97 "content": checks.NewContentCheck(hC), 98 "content-neo4j": checks.NewContentNeo4jCheck(hC), 99 "content-collection-neo4j": checks.NewContentNeo4jCheck(hC), 100 "complementary-content": checks.NewContentCheck(hC), 101 "internal-components": checks.NewContentCheck(hC), 102 "enrichedContent": checks.NewContentCheck(hC), 103 "lists": checks.NewContentCheck(hC), 104 "pages": checks.NewContentCheck(hC), 105 "contentrelations": checks.NewContentCheck(hC), 106 "notifications": checks.NewNotificationsCheck(hC, h.subscribedFeeds, "notifications"), 107 "notifications-push": checks.NewNotificationsCheck(hC, h.subscribedFeeds, "notifications-push"), 108 "list-notifications": checks.NewNotificationsCheck(hC, h.subscribedFeeds, "list-notifications"), 109 "list-notifications-push": checks.NewNotificationsCheck(hC, h.subscribedFeeds, "list-notifications-push"), 110 "page-notifications": checks.NewNotificationsCheck(hC, h.subscribedFeeds, "page-notifications"), 111 "page-notifications-push": checks.NewNotificationsCheck(hC, h.subscribedFeeds, "page-notifications-push"), 112 } 113 114 for _, scheduleParam := range paramsToSchedule { 115 checks.ScheduleChecks(scheduleParam, endpointSpecificChecks, h.appConfig, h.metricSink, h.e2eTestUUIDs, h.log) 116 } 117 } 118 119 func (h *kafkaMessageHandler) isIgnorableMessage(msg kafka.FTMessage) bool { 120 tid := msg.Headers["X-Request-Id"] 121 122 isSynthetic := h.isSyntheticTransactionID(tid) 123 isE2ETest := config.IsE2ETestTransactionID(tid, h.e2eTestUUIDs) 124 isCarousel := h.isContentCarouselTransactionID(tid) 125 126 if isSynthetic && isE2ETest { 127 h.log.WithTransactionID(tid).Infof("Message is E2E Test.") 128 return false 129 } 130 131 return isSynthetic || isCarousel 132 } 133 134 func (h *kafkaMessageHandler) isSyntheticTransactionID(tid string) bool { 135 return strings.HasPrefix(tid, "SYNTHETIC") 136 } 137 138 func (h *kafkaMessageHandler) isContentCarouselTransactionID(tid string) bool { 139 return carouselTransactionIDRegExp.MatchString(tid) 140 } 141 142 // UnmarshalContent unmarshals the message body into the appropriate content type based on the systemID header. 143 func (h *kafkaMessageHandler) unmarshalContent(msg kafka.FTMessage) (content.Content, error) { 144 binaryContent := []byte(msg.Body) 145 146 headers := msg.Headers 147 systemID := headers[systemIDKey] 148 switch systemID { 149 case "http://cmdb.ft.com/systems/next-video-editor": 150 if msg.Headers["Content-Type"] == "application/vnd.ft-upp-audio" { 151 return unmarshalGenericContent(msg) 152 } 153 154 var video content.Video 155 err := json.Unmarshal(binaryContent, &video) 156 if err != nil { 157 return nil, err 158 } 159 return video.Initialize(binaryContent), nil 160 case "http://cmdb.ft.com/systems/cct", "http://cmdb.ft.com/systems/spark-lists", "http://cmdb.ft.com/systems/spark", "http://cmdb.ft.com/systems/spark-clips": 161 return unmarshalGenericContent(msg) 162 default: 163 return nil, fmt.Errorf("unsupported content with system ID: [%s]", systemID) 164 } 165 } 166 167 func unmarshalGenericContent(msg kafka.FTMessage) (content.GenericContent, error) { 168 binaryContent := []byte(msg.Body) 169 var genericContent content.GenericContent 170 err := json.Unmarshal(binaryContent, &genericContent) 171 if err != nil { 172 return content.GenericContent{}, err 173 } 174 175 genericContent = genericContent.Initialize(binaryContent).(content.GenericContent) 176 genericContent.Type = msg.Headers["Content-Type"] 177 178 return genericContent, nil 179 }