Skip to content

Commit aa385f9

Browse files
authored
fix(logging): added marshalling methods for proto fields in structuredLogEntry (#8979)
* fix(logging): added marshalling methods for proto fields in structuredLogEntry * Renamed structuredLogEntryHttpRequest due to linter error.
1 parent 9af7e85 commit aa385f9

File tree

2 files changed

+70
-50
lines changed

2 files changed

+70
-50
lines changed

logging/logging.go

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import (
5454
logtypepb "google.golang.org/genproto/googleapis/logging/type"
5555
"google.golang.org/grpc/codes"
5656
"google.golang.org/grpc/status"
57+
"google.golang.org/protobuf/encoding/protojson"
5758
"google.golang.org/protobuf/types/known/anypb"
5859
"google.golang.org/protobuf/types/known/timestamppb"
5960
)
@@ -967,65 +968,70 @@ func toLogEntryInternalImpl(e Entry, l *Logger, parent string, skipLevels int) (
967968
// entry represents the fields of a logging.Entry that can be parsed by Logging agent.
968969
// See the mappings at https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
969970
type structuredLogEntry struct {
970-
// JsonMessage map[string]interface{} `json:"message,omitempty"`
971-
// TextMessage string `json:"message,omitempty"`
972-
Message json.RawMessage `json:"message"`
973-
Severity string `json:"severity,omitempty"`
974-
HTTPRequest *logtypepb.HttpRequest `json:"httpRequest,omitempty"`
975-
Timestamp string `json:"timestamp,omitempty"`
976-
Labels map[string]string `json:"logging.googleapis.com/labels,omitempty"`
977-
InsertID string `json:"logging.googleapis.com/insertId,omitempty"`
978-
Operation *logpb.LogEntryOperation `json:"logging.googleapis.com/operation,omitempty"`
979-
SourceLocation *logpb.LogEntrySourceLocation `json:"logging.googleapis.com/sourceLocation,omitempty"`
980-
SpanID string `json:"logging.googleapis.com/spanId,omitempty"`
981-
Trace string `json:"logging.googleapis.com/trace,omitempty"`
982-
TraceSampled bool `json:"logging.googleapis.com/trace_sampled,omitempty"`
971+
Message json.RawMessage `json:"message"`
972+
Severity string `json:"severity,omitempty"`
973+
HTTPRequest *structuredLogEntryHTTPRequest `json:"httpRequest,omitempty"`
974+
Timestamp string `json:"timestamp,omitempty"`
975+
Labels map[string]string `json:"logging.googleapis.com/labels,omitempty"`
976+
InsertID string `json:"logging.googleapis.com/insertId,omitempty"`
977+
Operation *structuredLogEntryOperation `json:"logging.googleapis.com/operation,omitempty"`
978+
SourceLocation *structuredLogEntrySourceLocation `json:"logging.googleapis.com/sourceLocation,omitempty"`
979+
SpanID string `json:"logging.googleapis.com/spanId,omitempty"`
980+
Trace string `json:"logging.googleapis.com/trace,omitempty"`
981+
TraceSampled bool `json:"logging.googleapis.com/trace_sampled,omitempty"`
983982
}
984983

985-
func convertSnakeToMixedCase(snakeStr string) string {
986-
words := strings.Split(snakeStr, "_")
987-
mixedStr := words[0]
988-
for _, word := range words[1:] {
989-
mixedStr += strings.Title(word)
990-
}
991-
return mixedStr
984+
// structuredLogEntryHTTPRequest wraps the HTTPRequest proto field in structuredLogEntry for easier JSON marshalling.
985+
type structuredLogEntryHTTPRequest struct {
986+
request *logtypepb.HttpRequest
992987
}
993988

994-
func (s structuredLogEntry) MarshalJSON() ([]byte, error) {
995-
// extract structuredLogEntry into json map
996-
type Alias structuredLogEntry
997-
var mapData map[string]interface{}
998-
data, err := json.Marshal(Alias(s))
999-
if err == nil {
1000-
err = json.Unmarshal(data, &mapData)
1001-
}
1002-
if err == nil {
1003-
// ensure all inner dicts use mixed case instead of snake case
1004-
innerDicts := [3]string{"httpRequest", "logging.googleapis.com/operation", "logging.googleapis.com/sourceLocation"}
1005-
for _, field := range innerDicts {
1006-
if fieldData, ok := mapData[field]; ok {
1007-
formattedFieldData := make(map[string]interface{})
1008-
for k, v := range fieldData.(map[string]interface{}) {
1009-
formattedFieldData[convertSnakeToMixedCase(k)] = v
1010-
}
1011-
mapData[field] = formattedFieldData
1012-
}
1013-
}
1014-
// serialize json map into raw bytes
1015-
return json.Marshal(mapData)
1016-
}
1017-
return data, err
989+
func (s structuredLogEntryHTTPRequest) MarshalJSON() ([]byte, error) {
990+
return protojson.Marshal(s.request)
991+
}
992+
993+
// structuredLogEntryOperation wraps the Operation proto field in structuredLogEntry for easier JSON marshalling.
994+
type structuredLogEntryOperation struct {
995+
operation *logpb.LogEntryOperation
996+
}
997+
998+
func (s structuredLogEntryOperation) MarshalJSON() ([]byte, error) {
999+
return protojson.Marshal(s.operation)
1000+
}
1001+
1002+
// structuredLogEntrySourceLocation wraps the SourceLocation proto field in structuredLogEntry for easier JSON marshalling.
1003+
type structuredLogEntrySourceLocation struct {
1004+
sourceLocation *logpb.LogEntrySourceLocation
1005+
}
1006+
1007+
func (s structuredLogEntrySourceLocation) MarshalJSON() ([]byte, error) {
1008+
return protojson.Marshal(s.sourceLocation)
10181009
}
10191010

10201011
func serializeEntryToWriter(entry *logpb.LogEntry, w io.Writer) error {
1012+
var httpRequest *structuredLogEntryHTTPRequest
1013+
if entry.HttpRequest != nil {
1014+
httpRequest = &structuredLogEntryHTTPRequest{entry.HttpRequest}
1015+
}
1016+
1017+
var operation *structuredLogEntryOperation
1018+
if entry.Operation != nil {
1019+
operation = &structuredLogEntryOperation{entry.Operation}
1020+
}
1021+
1022+
var sourceLocation *structuredLogEntrySourceLocation
1023+
if entry.SourceLocation != nil {
1024+
sourceLocation = &structuredLogEntrySourceLocation{entry.SourceLocation}
1025+
}
1026+
10211027
jsonifiedEntry := structuredLogEntry{
10221028
Severity: entry.Severity.String(),
1023-
HTTPRequest: entry.HttpRequest,
1029+
HTTPRequest: httpRequest,
10241030
Timestamp: entry.Timestamp.String(),
10251031
Labels: entry.Labels,
10261032
InsertID: entry.InsertId,
1027-
Operation: entry.Operation,
1028-
SourceLocation: entry.SourceLocation,
1033+
Operation: operation,
1034+
SourceLocation: sourceLocation,
10291035
SpanID: entry.SpanId,
10301036
Trace: entry.Trace,
10311037
TraceSampled: entry.TraceSampled,

logging/logging_test.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,7 @@ func TestRedirectOutputFormats(t *testing.T) {
14381438
},
14391439
want: `{"httpRequest":{"requestMethod":"POST","requestUrl":"https://example.com/test"},"logging.googleapis.com/insertId":"0000AAA01",` +
14401440
`"logging.googleapis.com/labels":{"key1":"value1","key2":"value2"},"logging.googleapis.com/operation":{"id":"0123456789","producer":"test"},` +
1441-
`"logging.googleapis.com/sourceLocation":{"file":"acme.go","function":"main","line":100},"logging.googleapis.com/spanId":"000000000001",` +
1441+
`"logging.googleapis.com/sourceLocation":{"file":"acme.go","function":"main","line":"100"},"logging.googleapis.com/spanId":"000000000001",` +
14421442
`"logging.googleapis.com/trace":"projects/P/ABCD12345678AB12345678","logging.googleapis.com/trace_sampled":true,` +
14431443
`"message":"this is text payload","severity":"DEBUG","timestamp":"seconds:1000"}`,
14441444
},
@@ -1474,7 +1474,7 @@ func TestRedirectOutputFormats(t *testing.T) {
14741474
},
14751475
want: `{"httpRequest":{"requestMethod":"POST","requestUrl":"https://example.com/test"},"logging.googleapis.com/insertId":"0000AAA01",` +
14761476
`"logging.googleapis.com/labels":{"key1":"value1","key2":"value2"},"logging.googleapis.com/operation":{"id":"0123456789","producer":"test"},` +
1477-
`"logging.googleapis.com/sourceLocation":{"file":"acme.go","function":"main","line":100},"logging.googleapis.com/spanId":"000000000001",` +
1477+
`"logging.googleapis.com/sourceLocation":{"file":"acme.go","function":"main","line":"100"},"logging.googleapis.com/spanId":"000000000001",` +
14781478
`"logging.googleapis.com/trace":"projects/P/ABCD12345678AB12345678","logging.googleapis.com/trace_sampled":true,` +
14791479
`"message":{"Latency":321,"Message":"message part of the payload"},"severity":"DEBUG","timestamp":"seconds:1000"}`,
14801480
},
@@ -1506,7 +1506,21 @@ func TestRedirectOutputFormats(t *testing.T) {
15061506
t.Errorf("Expected error: %+v, want: %v\n", tc.in, tc.wantError)
15071507
}
15081508
got := strings.TrimSpace(buffer.String())
1509-
if got != tc.want {
1509+
1510+
// Compare structure equivalence of the outputs, not string equivalence, as order doesn't matter.
1511+
var gotJson, wantJson interface{}
1512+
1513+
err = json.Unmarshal([]byte(got), &gotJson)
1514+
if err != nil {
1515+
t.Errorf("Error when serializing JSON output: %v", err)
1516+
}
1517+
1518+
err = json.Unmarshal([]byte(tc.want), &wantJson)
1519+
if err != nil {
1520+
t.Fatalf("Error unmarshalling JSON input for want: %v", err)
1521+
}
1522+
1523+
if !reflect.DeepEqual(gotJson, wantJson) {
15101524
t.Errorf("TestRedirectOutputFormats: %+v: got %v, want %v", tc.in, got, tc.want)
15111525
}
15121526
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy