Skip to content

Commit bc13e6a

Browse files
authored
feat(logging): automatic project detection in logging.NewClient() (#9006)
* feat(logging): automatic project detection in logging.NewClient() * fixed testcase * Used DetectProjectId as a sentinel value for automatic project detection
1 parent 69215e3 commit bc13e6a

File tree

3 files changed

+97
-4
lines changed

3 files changed

+97
-4
lines changed

β€Žlogging/logging.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ const (
9595

9696
// Part of the error message when the payload contains invalid UTF-8 characters.
9797
utfErrorString = "string field contains invalid UTF-8"
98+
99+
// DetectProjectID is a sentinel value that instructs NewClient to detect the
100+
// project ID. It is given in place of the projectID argument. NewClient will
101+
// use the project ID from the given credentials or the default credentials
102+
// (https://developers.google.com/accounts/docs/application-default-credentials)
103+
// if no credentials were provided. When providing credentials, not all
104+
// options will allow NewClient to extract the project ID. Specifically a JWT
105+
// does not have the project ID encoded.
106+
DetectProjectID = "*detect-project-id*"
98107
)
99108

100109
var (
@@ -103,8 +112,9 @@ var (
103112
ErrRedirectProtoPayloadNotSupported = errors.New("printEntryToStdout: cannot find valid payload")
104113

105114
// For testing:
106-
now = time.Now
107-
toLogEntryInternal = toLogEntryInternalImpl
115+
now = time.Now
116+
toLogEntryInternal = toLogEntryInternalImpl
117+
detectResourceInternal = detectResource
108118

109119
// ErrOverflow signals that the number of buffered entries for a Logger
110120
// exceeds its BufferLimit.
@@ -148,9 +158,12 @@ type Client struct {
148158
// billingAccounts/ACCOUNT_ID
149159
// organizations/ORG_ID
150160
//
151-
// for backwards compatibility, a string with no '/' is also allowed and is interpreted
161+
// For backwards compatibility, a string with no '/' is also allowed and is interpreted
152162
// as a project ID.
153163
//
164+
// If logging.DetectProjectId is provided as the parent, the parent will be interpreted as a project
165+
// ID, and its value will be inferred from the environment.
166+
//
154167
// By default NewClient uses WriteScope. To use a different scope, call
155168
// NewClient using a WithScopes option (see https://godoc.org/google.golang.org/api/option#WithScopes).
156169
func NewClient(ctx context.Context, parent string, opts ...option.ClientOption) (*Client, error) {
@@ -192,6 +205,13 @@ func NewClient(ctx context.Context, parent string, opts ...option.ClientOption)
192205

193206
func makeParent(parent string) (string, error) {
194207
if !strings.ContainsRune(parent, '/') {
208+
if parent == DetectProjectID {
209+
resource := detectResourceInternal()
210+
if resource == nil {
211+
return parent, fmt.Errorf("could not determine project ID from environment")
212+
}
213+
parent = resource.Labels["project_id"]
214+
}
195215
return "projects/" + parent, nil
196216
}
197217
prefix := strings.Split(parent, "/")[0]
@@ -301,7 +321,7 @@ func (r *loggerRetryer) Retry(err error) (pause time.Duration, shouldRetry bool)
301321
// characters: [A-Za-z0-9]; and punctuation characters: forward-slash,
302322
// underscore, hyphen, and period.
303323
func (c *Client) Logger(logID string, opts ...LoggerOption) *Logger {
304-
r := detectResource()
324+
r := detectResourceInternal()
305325
if r == nil {
306326
r = monitoredResource(c.parent)
307327
}

β€Žlogging/logging_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,74 @@ func TestNonProjectParent(t *testing.T) {
10331033
}
10341034
}
10351035

1036+
func TestDetectProjectIdParent(t *testing.T) {
1037+
ctx := context.Background()
1038+
initLogs()
1039+
addr, err := ltesting.NewServer()
1040+
if err != nil {
1041+
t.Fatalf("creating fake server: %v", err)
1042+
}
1043+
conn, err := grpc.Dial(addr, grpc.WithInsecure())
1044+
if err != nil {
1045+
t.Fatalf("dialing %q: %v", addr, err)
1046+
}
1047+
1048+
tests := []struct {
1049+
name string
1050+
resource *mrpb.MonitoredResource
1051+
want string
1052+
wantError error
1053+
}{
1054+
{
1055+
name: "Test DetectProjectId parent properly set up resource detection",
1056+
resource: &mrpb.MonitoredResource{
1057+
Labels: map[string]string{"project_id": testProjectID},
1058+
},
1059+
want: "projects/" + testProjectID,
1060+
},
1061+
{
1062+
name: "Test DetectProjectId parent no resource detected",
1063+
resource: nil,
1064+
wantError: errors.New("could not determine project ID from environment"),
1065+
},
1066+
}
1067+
1068+
for _, test := range tests {
1069+
t.Run(test.name, func(t *testing.T) {
1070+
// Check if toLogEntryInternal was called with the right parent
1071+
toLogEntryInternalMock := func(got logging.Entry, l *logging.Logger, parent string, skipLevels int) (*logpb.LogEntry, error) {
1072+
if parent != test.want {
1073+
t.Errorf("toLogEntryInternal called with wrong parent. got: %s want: %s", parent, test.want)
1074+
}
1075+
return &logpb.LogEntry{}, nil
1076+
}
1077+
1078+
detectResourceMock := func() *mrpb.MonitoredResource {
1079+
return test.resource
1080+
}
1081+
1082+
realToLogEntryInternal := logging.SetToLogEntryInternal(toLogEntryInternalMock)
1083+
defer func() { logging.SetToLogEntryInternal(realToLogEntryInternal) }()
1084+
1085+
realDetectResourceInternal := logging.SetDetectResourceInternal(detectResourceMock)
1086+
defer func() { logging.SetDetectResourceInternal(realDetectResourceInternal) }()
1087+
1088+
cli, err := logging.NewClient(ctx, logging.DetectProjectID, option.WithGRPCConn(conn))
1089+
if err != nil && test.wantError == nil {
1090+
t.Fatalf("Unexpected error: %+v: %v", test.resource, err)
1091+
}
1092+
if err == nil && test.wantError != nil {
1093+
t.Fatalf("Error is expected: %+v: %v", test.resource, test.wantError)
1094+
}
1095+
if test.wantError != nil {
1096+
return
1097+
}
1098+
1099+
cli.Logger(testLogID).LogSync(ctx, logging.Entry{Payload: "hello"})
1100+
})
1101+
}
1102+
}
1103+
10361104
// waitFor calls f repeatedly with exponential backoff, blocking until it returns true.
10371105
// It returns false after a while (if it times out).
10381106
func waitFor(f func() bool) bool {

β€Žlogging/logging_unexported_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,8 @@ func SetToLogEntryInternal(f func(Entry, *Logger, string, int) (*logpb.LogEntry,
397397
toLogEntryInternal, f = f, toLogEntryInternal
398398
return f
399399
}
400+
401+
func SetDetectResourceInternal(f func() *mrpb.MonitoredResource) func() *mrpb.MonitoredResource {
402+
detectResourceInternal, f = f, detectResourceInternal
403+
return f
404+
}

0 commit comments

Comments
 (0)