Skip to content

Commit 59e58f9

Browse files
authored
[common-go] Composable log fields (#16860)
* [common-go] Composable log fields * add test for compose * use in public api * fix * fix
1 parent 8390cac commit 59e58f9

File tree

11 files changed

+182
-121
lines changed

11 files changed

+182
-121
lines changed

components/common-go/log/fields.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package log
6+
7+
import log "github.com/sirupsen/logrus"
8+
9+
const (
10+
UserIDField = "userId"
11+
// OwnerIDField is the log field name of a workspace owner
12+
OwnerIDField = UserIDField
13+
// WorkspaceIDField is the log field name of a workspace ID (not instance ID)
14+
WorkspaceIDField = "workspaceId"
15+
// WorkspaceInstanceIDField is the log field name of a workspace instance ID
16+
WorkspaceInstanceIDField = "instanceId"
17+
// ProjectIDField is the log field name of the project
18+
ProjectIDField = "projectId"
19+
// TeamIDField is the log field name of the team
20+
TeamIDField = "teamId"
21+
22+
OrganizationIDField = "orgId"
23+
24+
ServiceContextField = "serviceContext"
25+
PersonalAccessTokenIDField = "patId"
26+
OIDCClientConfigIDField = "oidcClientConfigId"
27+
)
28+
29+
// OWI builds a structure meant for logrus which contains the owner, workspace and instance.
30+
// Beware that this refers to the terminology outside of wsman which maps like:
31+
//
32+
// owner = owner, workspace = metaID, instance = workspaceID
33+
func OWI(owner, workspace, instance string) log.Fields {
34+
return log.Fields{
35+
OwnerIDField: owner,
36+
WorkspaceIDField: workspace,
37+
WorkspaceInstanceIDField: instance,
38+
}
39+
}
40+
41+
// LogContext builds a structure meant for logrus which contains the owner, workspace and instance.
42+
// Beware that this refers to the terminology outside of wsman which maps like:
43+
//
44+
// owner = owner, workspace = metaID, instance = workspaceID
45+
func LogContext(ownerID, workspaceID, instanceID, projectID, orgID string) log.Fields {
46+
return Compose(
47+
WorkspaceOwner(ownerID),
48+
WorkspaceID(workspaceID),
49+
WorkspaceInstanceID(instanceID),
50+
ProjectID(projectID),
51+
OrganizationID(orgID),
52+
)
53+
}
54+
55+
func WorkspaceOwner(owner string) log.Fields {
56+
return String(OwnerIDField, owner)
57+
}
58+
59+
func WorkspaceID(workspaceID string) log.Fields {
60+
return String(WorkspaceIDField, workspaceID)
61+
}
62+
63+
func WorkspaceInstanceID(instanceID string) log.Fields {
64+
return String(WorkspaceInstanceIDField, instanceID)
65+
}
66+
67+
func ProjectID(projectID string) log.Fields {
68+
return String(ProjectIDField, projectID)
69+
}
70+
71+
func OrganizationID(orgID string) log.Fields {
72+
return Compose(
73+
// We continue to log TeamID in place of Organization for compatibility.
74+
String(TeamIDField, orgID),
75+
String(OrganizationIDField, orgID),
76+
)
77+
}
78+
79+
func PersonalAccessTokenID(patID string) log.Fields {
80+
return String(PersonalAccessTokenIDField, patID)
81+
}
82+
83+
func OIDCClientConfigID(id string) log.Fields {
84+
return String(OIDCClientConfigIDField, id)
85+
}
86+
87+
func UserID(userID string) log.Fields {
88+
return String(UserIDField, userID)
89+
}
90+
91+
// Compose composes multiple sets of log.Fields into a single
92+
func Compose(fields ...log.Fields) log.Fields {
93+
res := log.Fields{}
94+
for _, f := range fields {
95+
for key, val := range f {
96+
res[key] = val
97+
}
98+
}
99+
return res
100+
}
101+
102+
// ServiceContext is the shape required for proper error logging in the GCP context.
103+
// See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext
104+
// Note that we musn't set resourceType for reporting errors.
105+
type serviceContext struct {
106+
Service string `json:"service"`
107+
Version string `json:"version"`
108+
}
109+
110+
func ServiceContext(service, version string) log.Fields {
111+
return log.Fields{
112+
ServiceContextField: serviceContext{service, version},
113+
}
114+
}
115+
116+
func String(key, value string) log.Fields {
117+
return log.Fields{key: value}
118+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package log
6+
7+
import (
8+
"testing"
9+
10+
"github.com/sirupsen/logrus"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestCompose(t *testing.T) {
15+
fields := Compose(
16+
WorkspaceOwner("owner"),
17+
WorkspaceID("workspace"),
18+
WorkspaceInstanceID("instance"),
19+
ProjectID("project"),
20+
OrganizationID("org"),
21+
)
22+
require.Equal(t, logrus.Fields{
23+
OwnerIDField: "owner",
24+
OrganizationIDField: "org",
25+
TeamIDField: "org",
26+
ProjectIDField: "project",
27+
WorkspaceInstanceIDField: "instance",
28+
WorkspaceIDField: "workspace",
29+
}, fields)
30+
}

components/common-go/log/log.go

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,69 +19,6 @@ import (
1919
log "github.com/sirupsen/logrus"
2020
)
2121

22-
const (
23-
// OwnerField is the log field name of a workspace owner
24-
OwnerField = "userId"
25-
// WorkspaceField is the log field name of a workspace ID (not instance ID)
26-
WorkspaceField = "workspaceId"
27-
// InstanceField is the log field name of a workspace instance ID
28-
InstanceField = "instanceId"
29-
// ProjectField is the log field name of the project
30-
ProjectField = "projectId"
31-
// TeamField is the log field name of the team
32-
TeamField = "teamId"
33-
)
34-
35-
// OWI builds a structure meant for logrus which contains the owner, workspace and instance.
36-
// Beware that this refers to the terminology outside of wsman which maps like:
37-
//
38-
// owner = owner, workspace = metaID, instance = workspaceID
39-
func OWI(owner, workspace, instance string) log.Fields {
40-
return log.Fields{
41-
OwnerField: owner,
42-
WorkspaceField: workspace,
43-
InstanceField: instance,
44-
}
45-
}
46-
47-
// LogContext builds a structure meant for logrus which contains the owner, workspace and instance.
48-
// Beware that this refers to the terminology outside of wsman which maps like:
49-
//
50-
// owner = owner, workspace = metaID, instance = workspaceID
51-
func LogContext(owner, workspace, instance, project, team string) log.Fields {
52-
logFields := log.Fields{}
53-
54-
if owner != "" {
55-
logFields[OwnerField] = owner
56-
}
57-
58-
if workspace != "" {
59-
logFields[WorkspaceField] = workspace
60-
}
61-
62-
if instance != "" {
63-
logFields[InstanceField] = instance
64-
}
65-
66-
if project != "" {
67-
logFields[ProjectField] = project
68-
}
69-
70-
if team != "" {
71-
logFields[TeamField] = team
72-
}
73-
74-
return logFields
75-
}
76-
77-
// ServiceContext is the shape required for proper error logging in the GCP context.
78-
// See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext
79-
// Note that we musn't set resourceType for reporting errors.
80-
type ServiceContext struct {
81-
Service string `json:"service"`
82-
Version string `json:"version"`
83-
}
84-
8522
// Log is the application wide console logger
8623
var Log = log.WithFields(log.Fields{})
8724

@@ -111,9 +48,7 @@ func logLevelFromEnv() {
11148

11249
// Init initializes/configures the application-wide logger
11350
func Init(service, version string, json, verbose bool) {
114-
Log = log.WithFields(log.Fields{
115-
"serviceContext": ServiceContext{service, version},
116-
})
51+
Log = log.WithFields(ServiceContext(service, version))
11752
log.SetReportCaller(true)
11853

11954
if json {

components/common-go/tracing/tracing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func FromContext(ctx context.Context, name string) (opentracing.Span, context.Co
100100

101101
// ApplyOWI sets the owner, workspace and instance tags on a span
102102
func ApplyOWI(span opentracing.Span, owi logrus.Fields) {
103-
for _, k := range []string{log.OwnerField, log.WorkspaceField, log.InstanceField, log.ProjectField, log.TeamField} {
103+
for _, k := range []string{log.OwnerIDField, log.WorkspaceIDField, log.WorkspaceInstanceIDField, log.ProjectIDField, log.TeamIDField} {
104104
val, ok := owi[k]
105105
if !ok {
106106
continue

components/public-api-server/pkg/apiv1/identityprovider.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"fmt"
1010

1111
connect "github.com/bufbuild/connect-go"
12+
"github.com/gitpod-io/gitpod/common-go/log"
1213
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
1314
"github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
1415
"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"
15-
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
1616
"github.com/zitadel/oidc/pkg/oidc"
1717
)
1818

@@ -47,8 +47,6 @@ func (srv *IdentityProviderService) GetIDToken(ctx context.Context, req *connect
4747
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Must have at least one audience entry"))
4848
}
4949

50-
logger := ctxlogrus.Extract(ctx).WithField("workspace_id", workspaceID)
51-
5250
conn, err := getConnection(ctx, srv.connectionPool)
5351
if err != nil {
5452
return nil, err
@@ -62,18 +60,18 @@ func (srv *IdentityProviderService) GetIDToken(ctx context.Context, req *connect
6260

6361
workspace, err := conn.GetWorkspace(ctx, workspaceID)
6462
if err != nil {
65-
logger.WithError(err).Error("Failed to get workspace.")
63+
log.Extract(ctx).WithError(err).Error("Failed to get workspace.")
6664
return nil, proxy.ConvertError(err)
6765
}
6866

6967
user, err := conn.GetLoggedInUser(ctx)
7068
if err != nil {
71-
logger.WithError(err).Error("Failed to get calling user.")
69+
log.Extract(ctx).WithError(err).Error("Failed to get calling user.")
7270
return nil, proxy.ConvertError(err)
7371
}
7472

7573
if workspace.Workspace == nil {
76-
logger.WithError(err).Error("Server did not return a workspace.")
74+
log.Extract(ctx).WithError(err).Error("Server did not return a workspace.")
7775
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("workspace not found"))
7876
}
7977

@@ -84,7 +82,7 @@ func (srv *IdentityProviderService) GetIDToken(ctx context.Context, req *connect
8482

8583
token, err := srv.idTokenSource.IDToken(ctx, "gitpod", req.Msg.Audience, userInfo)
8684
if err != nil {
87-
logger.WithError(err).Error("Failed to produce ID token.")
85+
log.Extract(ctx).WithError(err).Error("Failed to produce ID token.")
8886
return nil, proxy.ConvertError(err)
8987
}
9088
return &connect.Response[v1.GetIDTokenResponse]{

components/public-api-server/pkg/apiv1/oidc.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"
2424
"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"
2525
"github.com/google/uuid"
26-
"github.com/sirupsen/logrus"
2726
"google.golang.org/grpc/codes"
2827
"google.golang.org/grpc/status"
2928
"gorm.io/gorm"
@@ -89,9 +88,7 @@ func (s *OIDCService) CreateClientConfig(ctx context.Context, req *connect.Reque
8988
return nil, status.Errorf(codes.Internal, "Failed to store OIDC client config.")
9089
}
9190

92-
log.AddFields(ctx, logrus.Fields{
93-
"oidcClientConfigId": created.ID.String(),
94-
})
91+
log.AddFields(ctx, log.OIDCClientConfigID(created.ID.String()))
9592

9693
converted, err := dbOIDCClientConfigToAPI(created, s.cipher)
9794
if err != nil {
@@ -247,9 +244,7 @@ func (s *OIDCService) getUser(ctx context.Context, conn protocol.APIInterface) (
247244
return nil, uuid.Nil, proxy.ConvertError(err)
248245
}
249246

250-
log.AddFields(ctx, logrus.Fields{
251-
"userId": user.ID,
252-
})
247+
log.AddFields(ctx, log.UserID(user.ID))
253248

254249
if !s.isFeatureEnabled(ctx, conn, user) {
255250
return nil, uuid.Nil, connect.NewError(connect.CodePermissionDenied, errors.New("This feature is currently in beta. If you would like to be part of the beta, please contact us."))

components/public-api-server/pkg/apiv1/team.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ func (s *TeamService) DeleteTeamMember(ctx context.Context, req *connect.Request
270270
}
271271

272272
func (s *TeamService) toTeamAPIResponse(ctx context.Context, conn protocol.APIInterface, team *protocol.Team) (*v1.Team, error) {
273-
logger := log.Extract(ctx).WithField("teamId", team.ID)
273+
logger := log.Extract(ctx).WithFields(log.OrganizationID(team.ID))
274274
members, err := conn.GetTeamMembers(ctx, team.ID)
275275
if err != nil {
276276
logger.WithError(err).Error("Failed to get team members.")

components/public-api-server/pkg/apiv1/tokens.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"
2424
"github.com/google/go-cmp/cmp"
2525
"github.com/google/uuid"
26-
"github.com/sirupsen/logrus"
2726
"google.golang.org/protobuf/types/known/fieldmaskpb"
2827
"google.golang.org/protobuf/types/known/timestamppb"
2928
"gorm.io/gorm"
@@ -302,9 +301,7 @@ func (s *TokensService) getUser(ctx context.Context, conn protocol.APIInterface)
302301
return nil, uuid.Nil, proxy.ConvertError(err)
303302
}
304303

305-
log.AddFields(ctx, logrus.Fields{
306-
"userId": user.ID,
307-
})
304+
log.AddFields(ctx, log.UserID(user.ID))
308305

309306
if !s.isFeatureEnabled(ctx, conn, user) {
310307
return nil, uuid.Nil, connect.NewError(connect.CodePermissionDenied, errors.New("This feature is currently in beta. If you would like to be part of the beta, please contact us."))

components/public-api-server/pkg/apiv1/user.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
1414
protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
1515
"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"
16-
"github.com/sirupsen/logrus"
1716
)
1817

1918
func NewUserService(pool proxy.ServerConnectionPool) *UserService {
@@ -40,9 +39,7 @@ func (s *UserService) GetAuthenticatedUser(ctx context.Context, req *connect.Req
4039
if err != nil {
4140
return nil, proxy.ConvertError(err)
4241
}
43-
log.AddFields(ctx, logrus.Fields{
44-
"userId": user.ID,
45-
})
42+
log.AddFields(ctx, log.UserID(user.ID))
4643

4744
response := userToAPIResponse(user)
4845

0 commit comments

Comments
 (0)