Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,20 @@ jobs:
# The command is terminated due to an error: [../../.golangci.yml] validate: compile schema: failing loading "https://golangci-lint.run/jsonschema/golangci.v2.11.jsonschema.json": Get "https://golangci-lint.run/jsonschema/golangci.v2.11.jsonschema.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
verify: 'false'

log-field-keys:
if: ${{ inputs.run-lint == true }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check canonical structured-log field keys
run: ./scripts/check-log-field-keys.sh

lint:
needs:
- detect-modules
- golangci-lint
- log-field-keys
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
Expand All @@ -119,4 +129,9 @@ jobs:
exit 1
fi

if [[ "${{ needs.log-field-keys.result }}" != "success" ]]; then
echo "log-field-keys result: ${{ needs.log-field-keys.result }}"
exit 1
fi

echo "lint succeeded"
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ fmt:
lint:
go work edit -json | jq -r '.Use[].DiskPath' | xargs -P 4 -I{} golangci-lint run {}/... --fix

.PHONY: check-log-field-keys
check-log-field-keys:
@scripts/check-log-field-keys.sh

.PHONY: generate-mocks
generate-mocks:
GOOS=linux mockery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (a *APIStore) PostAdminTeamsTeamIDSandboxesKill(c *gin.Context, teamID uuid
}

logger.L().Info(ctx, "Completed killing team sandboxes",
zap.String("teamID", teamID.String()),
logger.WithTeamID(teamID.String()),
zap.Int("killed", killedCount),
zap.Int("failed", failedCount),
)
Expand Down
3 changes: 1 addition & 2 deletions packages/api/internal/handlers/sandbox_resume.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"

"github.com/e2b-dev/infra/packages/api/internal/api"
snapshotcache "github.com/e2b-dev/infra/packages/api/internal/cache/snapshots"
Expand Down Expand Up @@ -135,7 +134,7 @@ func (a *APIStore) PostSandboxesSandboxIDResume(c *gin.Context, sandboxID api.Sa
logger.WithSandboxID(sandboxID),
logger.Time("end_time", sandboxData.EndTime),
logger.Time("start_time", sandboxData.StartTime),
zap.String("node_id", sandboxData.NodeID),
logger.WithNodeID(sandboxData.NodeID),
)

return
Expand Down
2 changes: 1 addition & 1 deletion packages/api/internal/orchestrator/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func logSandboxCreated(ctx context.Context, sbx sandbox.Sandbox) {
zap.String("end_time", endTimeStr),
zap.String("auto_resume_policy", autoResumePolicy),
zap.Bool("auto_pause", sbx.AutoPause),
zap.String("template_id", sbx.BaseTemplateID),
logger.WithTemplateID(sbx.BaseTemplateID),
)
}

Expand Down
2 changes: 1 addition & 1 deletion packages/api/internal/orchestrator/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (o *Orchestrator) getOrConnectNode(ctx context.Context, clusterID uuid.UUID

logger.L().Warn(ctx, "Node not found in cache, attempting on-demand connection",
logger.WithNodeID(nodeID),
zap.String("cluster_id", clusterID.String()),
logger.WithClusterID(clusterID),
)

scopedKey := o.scopedNodeID(clusterID, nodeID)
Expand Down
4 changes: 2 additions & 2 deletions packages/api/internal/orchestrator/nodemanager/sandboxes.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (n *Node) GetOrphanCandidates(ctx context.Context) ([]sandbox.NodeSandbox,
// running under some other identity.
if sandboxID == "" {
logger.L().Error(childCtx, "Skipping sandbox with no sandbox ID during node sync",
zap.String("team_id", rawTeamID),
logger.WithTeamID(rawTeamID),
logger.WithNodeID(n.ID),
)

Expand All @@ -69,7 +69,7 @@ func (n *Node) GetOrphanCandidates(ctx context.Context) ([]sandbox.NodeSandbox,
if parseErr != nil {
logger.L().Error(childCtx, "Skipping sandbox with unparseable team ID during node sync",
zap.Error(parseErr),
zap.String("team_id", rawTeamID),
logger.WithTeamID(rawTeamID),
logger.WithSandboxID(sandboxID),
logger.WithNodeID(n.ID),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ func (tm *TemplateManager) CancelTeamBuilds(ctx context.Context, teamID uuid.UUI
err := cancelBuild(ctx, tm, b, reason)
if err != nil {
logger.L().Error(ctx, "Failed to cancel build",
zap.String("buildID", b.BuildID.String()),
zap.String("templateID", b.TemplateID),
logger.WithBuildID(b.BuildID.String()),
logger.WithTemplateID(b.TemplateID),
logger.WithTeamID(teamID.String()),
zap.Error(err))
failedCount.Add(1)
Expand All @@ -64,8 +64,8 @@ func (tm *TemplateManager) CancelTeamBuilds(ctx context.Context, teamID uuid.UUI
}

logger.L().Debug(ctx, "Successfully cancelled build",
zap.String("buildID", b.BuildID.String()),
zap.String("templateID", b.TemplateID),
logger.WithBuildID(b.BuildID.String()),
logger.WithTemplateID(b.TemplateID),
logger.WithTeamID(teamID.String()))
cancelledCount.Add(1)

Expand Down
6 changes: 3 additions & 3 deletions packages/api/internal/template-manager/template_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (tm *TemplateManager) BuildsStatusPeriodicalSync(ctx context.Context) {
go func(b queries.GetInProgressTemplateBuildsRow) {
err := tm.BuildStatusSync(ctx, b.EnvBuild.ID, b.ActiveEnv.ID, clustersshared.WithClusterFallback(b.ActiveEnv.ClusterID), b.EnvBuild.ClusterNodeID)
if err != nil {
logger.L().Error(ctx, "Error syncing build status", zap.Error(err), zap.String("buildID", b.EnvBuild.ID.String()))
logger.L().Error(ctx, "Error syncing build status", zap.Error(err), logger.WithBuildID(b.EnvBuild.ID.String()))
}
}(b)
}
Expand All @@ -123,7 +123,7 @@ func (tm *TemplateManager) GetAvailableBuildClient(ctx context.Context, clusterI
if err != nil {
if errors.Is(err, clusters.ErrAvailableTemplateBuilderNotFound) {
// Fallback to any template builder
logger.L().Warn(ctx, "No available template builder found with the specified machine info, falling back to any available template builder", zap.String("clusterID", clusterID.String()))
logger.L().Warn(ctx, "No available template builder found with the specified machine info, falling back to any available template builder", logger.WithClusterID(clusterID))

builder, err = cluster.GetAvailableTemplateBuilder(ctx, machineinfo.MachineInfo{})
if err != nil {
Expand Down Expand Up @@ -181,7 +181,7 @@ func (tm *TemplateManager) DeleteBuild(ctx context.Context, buildID uuid.UUID, t
}
nodeID = node.NodeID

logger.L().Info(ctx, "Fallback to available node", zap.String("nodeID", nodeID), zap.String("clusterID", clusterID.String()))
logger.L().Info(ctx, "Fallback to available node", logger.WithNodeID(nodeID), logger.WithClusterID(clusterID))
client, err = tm.GetClusterBuildClient(clusterID, nodeID)
if err != nil {
return fmt.Errorf("failed to get builder client: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion packages/clickhouse/pkg/sandboxlogs/sandboxlogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func (r *Reader) scanSandboxLogs(ctx context.Context, q string, args ...any) ([]

entry := rr.toLogEntry(func(err error) {
logger.L().Warn(ctx, "failed to parse sandbox log fields",
zap.String("sandbox_id", rr.SandboxID),
logger.WithSandboxID(rr.SandboxID),
logger.Time("timestamp", rr.Timestamp),
zap.Error(err),
)
Expand Down
2 changes: 1 addition & 1 deletion packages/client-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func run() int {
signalCtx, sigCancel := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
defer sigCancel()

l.Info(ctx, "Starting client proxy", zap.String("commit", commitSHA), zap.String("instance_id", instanceID))
l.Info(ctx, "Starting client proxy", zap.String("commit", commitSHA), logger.WithServiceInstanceID(instanceID))

featureFlagsClient, err := featureflags.NewClient(config.DeploymentEnvironment, serviceName, version, featureflags.WithStartOnInitTimeout())
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func run() int {
return 1
}

l.Info(ctx, "Starting dashboard-api service...", zap.String("commit_sha", commitSHA), zap.String("instance_id", serviceInstanceID))
l.Info(ctx, "Starting dashboard-api service...", zap.String("commit_sha", commitSHA), logger.WithServiceInstanceID(serviceInstanceID))

expectedMigration, err := strconv.ParseInt(expectedMigrationTimestamp, 10, 64)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestrator/cmd/create-build/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func doBuild(
uploads,
)

l = l.With(zap.String("envID", templateID)).With(zap.String("buildID", buildID))
l = l.With(logger.WithTemplateID(templateID)).With(logger.WithBuildID(buildID))

force := true

Expand Down
4 changes: 2 additions & 2 deletions packages/orchestrator/pkg/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -3348,7 +3348,7 @@ func (s *Sandbox) runDeferredMemoryExport(
// when a window fails or the tripwire fires, and trace ids are
// sampled — the sandbox/build identity must be on the line itself.
sbxlogger.I(s).Error(ctx, "deferred memory export failed",
zap.String("build_id", buildID.String()), zap.Error(err))
logger.WithBuildID(buildID.String()), zap.Error(err))
// Drain in-flight fault-path copies before closing the cache: Sweep
// returns on cancel WITHOUT waiting them out. sharedCacheWriter's
// lock makes a straggler fail cleanly (ErrCacheClosed) rather than
Expand All @@ -3358,7 +3358,7 @@ func (s *Sandbox) runDeferredMemoryExport(
window.CancelAndDrain(err)
if closeErr := cache.Close(); closeErr != nil {
sbxlogger.I(s).Warn(ctx, "closing memory export cache",
zap.String("build_id", buildID.String()), zap.Error(closeErr))
logger.WithBuildID(buildID.String()), zap.Error(closeErr))
}
// The SANDBOX is unharmed: a failed or cancelled capture loses only
// THIS checkpoint's artifact. Unlike the rootfs seal (whose failure
Expand Down
4 changes: 2 additions & 2 deletions packages/orchestrator/pkg/server/sandboxes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2245,7 +2245,7 @@ func (s *Server) snapshotAndCacheSandbox(
}

if err := s.peerRegistry.Unregister(ctx, meta.Template.BuildID); err != nil {
logger.L().Warn(ctx, "failed to unregister peer address from routing", zap.String("build_id", meta.Template.BuildID), zap.Error(err))
logger.L().Warn(ctx, "failed to unregister peer address from routing", logger.WithBuildID(meta.Template.BuildID), zap.Error(err))
}
}

Expand All @@ -2261,7 +2261,7 @@ func (s *Server) snapshotAndCacheSandbox(

if peerEnabled {
if err := s.peerRegistry.Register(ctx, meta.Template.BuildID, redisPeerKeyTTL); err != nil {
logger.L().Warn(ctx, "failed to register peer address for routing", zap.String("build_id", meta.Template.BuildID), zap.Error(err))
logger.L().Warn(ctx, "failed to register peer address for routing", logger.WithBuildID(meta.Template.BuildID), zap.Error(err))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (b *EnsureFreeDiskBuilder) Layer(
logger.L().Info(ctx, "resize-disk artifact unavailable in cache, building it",
zap.Error(err),
zap.String("hash", hash),
zap.String("build_id", layerMeta.Template.BuildID),
logger.WithBuildID(layerMeta.Template.BuildID),
)

return notCachedResult, nil //nolint:nilerr // An unavailable cached artifact must be rebuilt.
Expand Down
45 changes: 33 additions & 12 deletions packages/shared/pkg/logger/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,73 @@ import (
"go.uber.org/zap/zapcore"
)

// Canonical structured-log field keys. These are the single source of truth
// for identifier field names across every service (api, orchestrator,
// client-proxy, ...). Always log an identifier through its With* helper below
// rather than a raw zap.String("...", v); scripts/check-log-field-keys.sh
// enforces this. The dotted "<noun>.id" convention is what the Vector pipeline
// normalizes on (packages/local-dev/vector.toml).
const (
KeySandboxID = "sandbox.id"
KeyLifecycleID = "sandbox.lifecycle.id"
KeyTemplateID = "template.id"
KeyBuildID = "build.id"
KeyExecutionID = "execution.id"
KeyUserID = "user.id"
KeyTeamID = "team.id"
KeyNodeID = "node.id"
KeyClusterID = "cluster.id"
KeyEdgeTraceID = "edge.trace.id"
KeyServiceInstance = "service.instance.id"
KeySandboxIP = "sandbox.ip"
)

func WithSandboxID(sandboxID string) zap.Field {
return zap.String("sandbox.id", sandboxID)
return zap.String(KeySandboxID, sandboxID)
}

func WithLifecycleID(lifecycleID string) zap.Field {
return zap.String("sandbox.lifecycle.id", lifecycleID)
return zap.String(KeyLifecycleID, lifecycleID)
}

func WithTemplateID(templateID string) zap.Field {
return zap.String("template.id", templateID)
return zap.String(KeyTemplateID, templateID)
}

func WithBuildID(buildID string) zap.Field {
return zap.String("build.id", buildID)
return zap.String(KeyBuildID, buildID)
}

func WithExecutionID(executionID string) zap.Field {
return zap.String("execution.id", executionID)
return zap.String(KeyExecutionID, executionID)
}

func WithUserID(userID string) zap.Field {
return zap.String("user.id", userID)
return zap.String(KeyUserID, userID)
}

func WithTeamID(teamID string) zap.Field {
return zap.String("team.id", teamID)
return zap.String(KeyTeamID, teamID)
}

func WithNodeID(nodeID string) zap.Field {
return zap.String("node.id", nodeID)
return zap.String(KeyNodeID, nodeID)
}

func WithClusterID(clusterID uuid.UUID) zap.Field {
return zap.String("cluster.id", clusterID.String())
return zap.String(KeyClusterID, clusterID.String())
}

func WithEdgeTraceID(traceID string) zap.Field {
return zap.String("edge.trace.id", traceID)
return zap.String(KeyEdgeTraceID, traceID)
}

func WithServiceInstanceID(instanceID string) zap.Field {
return zap.String("service.instance.id", instanceID)
return zap.String(KeyServiceInstance, instanceID)
}

func WithSandboxIP(sandboxIP string) zap.Field {
return zap.String("sandbox.ip", sandboxIP)
return zap.String(KeySandboxIP, sandboxIP)
}

func WithEnvdVersion(envdVersion string) zap.Field {
Expand Down
64 changes: 64 additions & 0 deletions packages/shared/pkg/logger/fields_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package logger

import (
"testing"

"github.com/google/uuid"
"go.uber.org/zap"
)

// Locks the canonical structured-log field keys. If a helper's key changes,
// the Vector pipeline remap (packages/local-dev/vector.toml) and every log
// query/alert that matches these keys must change with it — so a silent edit
// here should fail loudly.
func TestCanonicalFieldKeys(t *testing.T) {
t.Parallel()

tests := []struct {
name string
field zap.Field
wantKey string
}{
{"sandbox", WithSandboxID("x"), KeySandboxID},
{"lifecycle", WithLifecycleID("x"), KeyLifecycleID},
{"template", WithTemplateID("x"), KeyTemplateID},
{"build", WithBuildID("x"), KeyBuildID},
{"execution", WithExecutionID("x"), KeyExecutionID},
{"user", WithUserID("x"), KeyUserID},
{"team", WithTeamID("x"), KeyTeamID},
{"node", WithNodeID("x"), KeyNodeID},
{"cluster", WithClusterID(uuid.Nil), KeyClusterID},
{"edgeTrace", WithEdgeTraceID("x"), KeyEdgeTraceID},
{"serviceInstance", WithServiceInstanceID("x"), KeyServiceInstance},
{"sandboxIP", WithSandboxIP("x"), KeySandboxIP},
}

// Guard against duplicate key strings across the canonical set.
seen := map[string]string{}

for _, tt := range tests {
if tt.field.Key != tt.wantKey {
t.Errorf("%s: key = %q, want %q", tt.name, tt.field.Key, tt.wantKey)
}

if prev, ok := seen[tt.wantKey]; ok {
t.Errorf("duplicate canonical key %q used by both %q and %q", tt.wantKey, prev, tt.name)
}

seen[tt.wantKey] = tt.name
}

// Every key uses the dotted "<noun>.id"/"<noun>.ip" convention, not
// snake_case or camelCase, so queries can rely on a single spelling.
for key := range seen {
for _, c := range key {
if c >= 'A' && c <= 'Z' {
t.Errorf("canonical key %q must be lowercase dotted, found uppercase", key)
}

if c == '_' {
t.Errorf("canonical key %q must use dots, not underscores", key)
}
}
}
}
8 changes: 5 additions & 3 deletions packages/shared/pkg/logger/sandbox/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ func (sm SandboxMetadata) Fields() []zap.Field {
logger.WithTemplateID(sm.TemplateID),
logger.WithTeamID(sm.TeamID),

// Fields for Vector
zap.String("instanceID", sm.SandboxID),
zap.String("envID", sm.TemplateID),
// Legacy aliases kept only for the Vector pipeline during the migration
// window; see packages/local-dev/vector.toml and the deprecation note
// in scripts/check-log-field-keys.sh. Do not add new raw ID keys here.
zap.String("instanceID", sm.SandboxID), // log-key-exempt: vector-legacy
zap.String("envID", sm.TemplateID), // log-key-exempt: vector-legacy
}
}
Loading