pkg/source/gcp/log/log_extractor.go (147 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"fmt"
"math/rand"
"strings"
"sync"
"time"
"github.com/GoogleCloudPlatform/khi/pkg/log"
"github.com/GoogleCloudPlatform/khi/pkg/model/enum"
)
var jsonPayloadMessageFieldNames = []string{
"MESSAGE",
"message",
"msg",
"log",
}
var uniqueIdMap = sync.Map{}
type GCPCommonFieldExtractor struct{}
// LogBody implements log.CommonLogFieldExtractor.
func (GCPCommonFieldExtractor) LogBody(l *log.LogEntity) string {
id, err := l.Fields.ToYaml("")
if err != nil {
return ""
}
return id
}
// DisplayID implements log.CommonLogFieldExtractor.
func (GCPCommonFieldExtractor) DisplayID(l *log.LogEntity) string {
id, err := l.GetString("insertId")
if err != nil {
panic(err)
}
return id
}
// GCPCommonFieldExtractor implements log.CommonLogFieldExtractor
var _ log.CommonLogFieldExtractor = (*GCPCommonFieldExtractor)(nil)
func (GCPCommonFieldExtractor) ID(log *log.LogEntity) string {
id, err := log.GetString("insertId")
if err != nil {
panic(err)
}
timestamp, err := log.Fields.ReadTimeAsString("timestamp")
if err != nil {
panic(err)
}
// id key can be long and it can inflate the size of KHI file.
// Use a random ID associated to timestamp and insertId instead.
idKey := fmt.Sprintf("%s-%s", id, timestamp)
nextId := generateLogId()
logId, _ := uniqueIdMap.LoadOrStore(idKey, nextId)
return logId.(string)
}
func (GCPCommonFieldExtractor) Timestamp(log *log.LogEntity) time.Time {
timeInStr, err := log.Fields.ReadTimeAsString("timestamp")
if err != nil {
panic(fmt.Errorf("failed to decode %s", err))
}
t, err := time.Parse(time.RFC3339Nano, timeInStr)
if err == nil {
return t
}
t, err = time.Parse(time.RFC3339, timeInStr)
if err != nil {
panic(fmt.Errorf("failed to find appropriate parser for timestamp %s\n%s", timeInStr, err))
}
return t
}
func (GCPCommonFieldExtractor) MainMessage(log *log.LogEntity) (string, error) {
textPayload, err := log.GetString("textPayload")
if err == nil {
return textPayload, nil
}
for _, fieldName := range jsonPayloadMessageFieldNames {
jsonPayloadMessage, err := log.GetString(fmt.Sprintf("jsonPayload.%s", fieldName))
if err == nil {
return jsonPayloadMessage, nil
}
}
requestReader, err := log.Fields.ReaderSingle("httpRequest")
if err == nil {
statusInt, err1 := requestReader.ReadInt("status")
requestUrl, err2 := requestReader.ReadString("requestUrl")
requestMethod, err3 := requestReader.ReadString("requestMethod")
protocol, _ := requestReader.ReadString("protocol")
if err1 == nil && err2 == nil && err3 == nil {
if protocol == "grpc" {
return fmt.Sprintf("【%d】GRPC %s", statusInt, requestUrl), nil
} else {
return fmt.Sprintf("【%d】%s %s", statusInt, requestMethod, requestUrl), nil
}
}
}
fallbackAsJson, err := log.Fields.ReaderSingle("jsonPayload")
if err == nil {
jsonMessage, err := fallbackAsJson.ToJson("")
if err == nil {
return jsonMessage, nil
} else {
return "", err
}
}
fallbackAsLabels, err := log.Fields.ReaderSingle("labels")
if err == nil {
jsonMessage, err := fallbackAsLabels.ToJson("")
if err == nil {
return jsonMessage, nil
} else {
return "", err
}
}
return "", fmt.Errorf("failed to extract main message from given log")
}
// Severity implements log.CommonLogFieldExtractor.
func (GCPCommonFieldExtractor) Severity(l *log.LogEntity) (enum.Severity, error) {
severity, err := l.GetString("severity")
if err != nil {
return enum.SeverityUnknown, err
}
return gcpSeverityToKHISeverity(severity), nil
}
func gcpSeverityToKHISeverity(severity string) enum.Severity {
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
severity = strings.ToUpper(severity)
switch severity {
case "DEFAULT":
return enum.SeverityInfo
case "DEBUG":
return enum.SeverityInfo
case "INFO":
return enum.SeverityInfo
case "NOTICE":
return enum.SeverityInfo
case "WARNING":
return enum.SeverityWarning
case "ERROR":
return enum.SeverityError
case "CRITICAL":
return enum.SeverityFatal
case "ALERT":
return enum.SeverityFatal
case "EMERGENCY":
return enum.SeverityFatal
default:
return enum.SeverityUnknown
}
}
func generateLogId() string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
randomid := make([]rune, 16)
for i := range randomid {
randomid[i] = letters[rand.Intn(len(letters))]
}
return string(randomid)
}