// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package log56import (7"bytes"8"fmt"9"io"10"strings"11"testing"12"time"1314"github.com/google/go-cmp/cmp"15"github.com/sirupsen/logrus"16)1718func TestFromBuffer(t *testing.T) {19tests := []struct {20Description string21Input []byte22Expected []byte23}{24{25"Empty",26[]byte("\x00"),27[]byte(""),28},29{30"With null and without msg or message field",31[]byte("\x00{\"notMessages\":\"\"}"),32[]byte(""),33},34{35"Output without msg or message field",36[]byte("\x00{\"notMessages\":\"\"}"),37[]byte(""),38},39{40"Output with message field",41[]byte("{\"level\":\"info\",\"message\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}"),42[]byte("{\"context\":\"test\",\"level\":\"info\",\"msg\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}\n"),43},44{45"Output with msg field",46[]byte("{\"level\":\"info\",\"msg\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}"),47[]byte("{\"context\":\"test\",\"level\":\"info\",\"msg\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}\n"),48},49{50"Output with msg field and error level",51[]byte("{\"level\":\"error\",\"message\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}"),52[]byte("{\"context\":\"test\",\"level\":\"error\",\"msg\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}\n"),53},54{55"Output with msg field and error level",56[]byte("{\"level\":\"error\",\"message\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}"),57[]byte("{\"context\":\"test\",\"level\":\"error\",\"msg\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}\n"),58},59{60"With invalid JSON",61[]byte("{\"notMessages\":\"\""),62[]byte(""),63},64{65"Text as JSON",66[]byte("NOT JSON"),67[]byte(""),68},69{70"Mixed content - text and valid JSON",71[]byte("SOMETHING INVALID\n{\"level\":\"error\",\"message\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}"),72[]byte("{\"context\":\"test\",\"level\":\"error\",\"msg\":\"DisposeWorkspace called\",\"time\":\"2021-09-14T21:26:44Z\"}\n"),73},74}7576for _, test := range tests {77logger := logrus.New()78var buffer bytes.Buffer79logger.SetFormatter(&logrus.JSONFormatter{})80logger.SetOutput(&buffer)81logger.SetLevel(logrus.DebugLevel)8283entry := logger.WithField("context", "test")8485FromBuffer(bytes.NewBuffer(test.Input), entry)86if diff := cmp.Diff(string(test.Expected), buffer.String()); diff != "" {87t.Errorf("%v: unexpected result (-want +got):\n%s", test.Description, diff)88}89}90}9192type TestWorkspaceInfo struct {93WorkspaceId string94InstanceId string95WorkspaceContextUrl string96}9798func TestScrubFormatter(t *testing.T) {99logger := logrus.New()100logger.SetFormatter(newGcpFormatter(false))101102var buffer bytes.Buffer103logger.SetOutput(&buffer)104105createInfo := func() *TestWorkspaceInfo {106return &TestWorkspaceInfo{107WorkspaceId: "1234567890",108InstanceId: "1234567890",109WorkspaceContextUrl: "https://github.com/gitpod-io/gitpod",110}111}112info := createInfo()113workspaceID := "1234567890"114115logTime := time.Now()116logger.WithTime(logTime).WithField("info", info).WithField("workspaceID", workspaceID).117WithError(fmt.Errorf("some test error")).118WithFields(ServiceContext("test", "1.0.0")).119Info("email: [email protected]")120121expectation := fmt.Sprintf(122`{"error":"some test error","info":"[redacted:nested]","level":"info","message":"email: [email protected]","serviceContext":{"service":"test","version":"1.0.0"},"severity":"INFO","time":"%s","workspaceID":"[redacted:md5:e807f1fcf82d132f9bb018ca6738a19f]"}`,123logTime.Format(time.RFC3339Nano),124)125actual := strings.TrimSpace(buffer.String())126if diff := cmp.Diff(expectation, actual); diff != "" {127t.Errorf("unexpected result (-want +got):\n%s", diff)128}129if diff := cmp.Diff(createInfo(), info); diff != "" {130t.Errorf("info: unexpected result (-want +got):\n%s", diff)131}132if diff := cmp.Diff("1234567890", workspaceID); diff != "" {133t.Errorf("workspaceID: unexpected result (-want +got):\n%s", diff)134}135}136137/*13812/06/2023 - no cahce fields139cpu: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz140BenchmarkSimpleScrubFormatter/json-32 312109 3617 ns/op 2035 B/op 31 allocs/op141BenchmarkSimpleScrubFormatter/scrub+json-32 244150 4753 ns/op 2242 B/op 37 allocs/op14214309/06/2023 - with redacting nested fields144cpu: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz145BenchmarkSimpleScrubFormatter/json-32 281763 3666 ns/op 2035 B/op 31 allocs/op146BenchmarkSimpleScrubFormatter/scrub+json-32 278829 4248 ns/op 2204 B/op 37 allocs/op14714805/06/2023 - with json scrubbing149cpu: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz150BenchmarkSimpleScrubFormatter/json-32 321686 3546 ns/op 2035 B/op 31 allocs/op151BenchmarkSimpleScrubFormatter/scrub+json-32 148384 8308 ns/op 3781 B/op 76 allocs/op15215302/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - with cache lock and eviction154BenchmarkSimpleScrubFormatter/json-32 359746 3144 ns/op 1914 B/op 28 allocs/op155BenchmarkSimpleScrubFormatter/scrub+json-32 312946 3796 ns/op 2083 B/op 34 allocs/op15615702/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - precomputed cache158BenchmarkSimpleScrubFormatter/json-32 367134 3215 ns/op 1914 B/op 28 allocs/op159BenchmarkSimpleScrubFormatter/scrub+json-32 302583 3869 ns/op 2083 B/op 34 allocs/op16016101/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - with caching without lower case conversion162BenchmarkSimpleScrubFormatter/json-32 377443 3059 ns/op 1914 B/op 28 allocs/op163BenchmarkSimpleScrubFormatter/scrub+json-32 311752 3704 ns/op 2083 B/op 34 allocs/op16416531/05/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - skip message, scrub with KeyValue for string type166BenchmarkSimpleScrubFormatter/json-32 376062 3019 ns/op 1914 B/op 28 allocs/op167BenchmarkSimpleScrubFormatter/scrub+json-32 313464 3798 ns/op 2097 B/op 35 allocs/op16816931/05/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - skip message170BenchmarkSimpleScrubFormatter/json-32 380050 2991 ns/op 1914 B/op 28 allocs/op171BenchmarkSimpleScrubFormatter/scrub+json-32 270021 4420 ns/op 2233 B/op 43 allocs/op17217330/05/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - scrubbing takes almost twice as long because of relection and regex replacements174BenchmarkSimpleScrubFormatter/json-32 368532 3095 ns/op 1914 B/op 28 allocs/op175BenchmarkSimpleScrubFormatter/scrub+json-32 200944 5858 ns/op 2666 B/op 53 allocs/op1760 1.21s (flat, cum) 38.29% of Total177. . 94:func (formatter *scrubFormatter) Format(entry *logrus.Entry) ([]byte, error) {178. 300ms 95: entry.Message = scrubber.Default.Value(entry.Message)179. 260ms 96: err := scrubber.Default.Struct(entry.Data)180. . 97: if err != nil {181. . 98: return nil, fmt.Errorf("cannot scrub log entry: %w", err)182. . 99: }183. 650ms 100: return formatter.delegate.Format(entry)184. . 101:}185*/186func BenchmarkSimpleScrubFormatter(b *testing.B) {187logger := logrus.New()188logger.SetOutput(io.Discard)189190run := func() {191logger.WithField("workspaceID", "1234567890").Info("email: [email protected]")192}193194logger.SetFormatter(newGcpFormatter(true))195b.Run("json", func(b *testing.B) {196for i := 0; i < b.N; i++ {197run()198}199})200201logger.SetFormatter(newGcpFormatter(false))202b.Run("scrub+json", func(b *testing.B) {203for i := 0; i < b.N; i++ {204run()205}206})207208}209210/*21112/06/2023 - no cahce fields212cpu: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz213BenchmarkStructScrubFormatter/json-32 304662 3860 ns/op 2091 B/op 32 allocs/op214BenchmarkStructScrubFormatter/scrub+json-32 320354 3698 ns/op 2091 B/op 32 allocs/op21521609/06/2023 - with redacting nested fields217cpu: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz218BenchmarkStructScrubFormatter/json-32 315826 3706 ns/op 2091 B/op 32 allocs/op219BenchmarkStructScrubFormatter/scrub+json-32 322910 3647 ns/op 2091 B/op 32 allocs/op22022105/06/2023 - with json scrubbing222cpu: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz223BenchmarkStructScrubFormatter/json-32 310472 3842 ns/op 2091 B/op 32 allocs/op224BenchmarkStructScrubFormatter/scrub+json-32 109390 10808 ns/op 5101 B/op 105 allocs/op22522602/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz - with cache lock and eviction227BenchmarkStructScrubFormatter/json-32 354174 3314 ns/op 1971 B/op 29 allocs/op228BenchmarkStructScrubFormatter/scrub+json-32 242992 4896 ns/op 2365 B/op 44 allocs/op22923002/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz - precomputed cache231BenchmarkStructScrubFormatter/json-32 344023 3465 ns/op 1971 B/op 29 allocs/op232BenchmarkStructScrubFormatter/scrub+json-32 240534 4850 ns/op 2365 B/op 44 allocs/op23323401/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - with caching without lower case conversion235BenchmarkStructScrubFormatter/json-32 354729 3259 ns/op 1971 B/op 29 allocs/op236BenchmarkStructScrubFormatter/scrub+json-32 232270 4767 ns/op 2365 B/op 44 allocs/op23723801/06/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - with substring mathing - 3 times slower239BenchmarkStructScrubFormatter/json-32 351439 3388 ns/op 1971 B/op 29 allocs/op240BenchmarkStructScrubFormatter/scrub+json-32 115599 9032 ns/op 2396 B/op 44 allocs/op24124231/05/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - early exit with SkipEntry + no double traversing243BenchmarkStructScrubFormatter/json-32 369788 3885 ns/op 1971 B/op 29 allocs/op244BenchmarkStructScrubFormatter/scrub+json-32 232442 5092 ns/op 2421 B/op 47 allocs/op24524631/05/2023 (Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz) - initial, still double247BenchmarkStructScrubFormatter/json-32 349736 3240 ns/op 1971 B/op 29 allocs/op248BenchmarkStructScrubFormatter/scrub+json-32 142266 8295 ns/op 2580 B/op 53 allocs/op249*/250func BenchmarkStructScrubFormatter(b *testing.B) {251logger := logrus.New()252logger.SetOutput(io.Discard)253254run := func() {255logger.WithField("info", &TestWorkspaceInfo{256WorkspaceId: "1234567890",257InstanceId: "1234567890",258WorkspaceContextUrl: "https://github.com/gitpod-io/gitpod",259}).Info("resolve workspace info failed")260}261262logger.SetFormatter(newGcpFormatter(true))263b.Run("json", func(b *testing.B) {264for i := 0; i < b.N; i++ {265run()266}267})268269logger.SetFormatter(newGcpFormatter(false))270b.Run("scrub+json", func(b *testing.B) {271for i := 0; i < b.N; i++ {272run()273}274})275276}277278279