Skip to content
Merged
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
20 changes: 14 additions & 6 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,36 @@ def init_git():
git.wait()

def init_proto():
# Order matters: download → generate (needs buf plugins) → mock (needs generated
# interfaces) → tidy (needs all imports including generated mocks to resolve)
# Order matters: download → generate (needs buf plugins) → tidy -e (reconcile
# indirect deps; -e ignores missing mock packages) → mock (needs generated
# interfaces) → tidy (final, needs all imports including mocks to resolve)
print("Starting proto initialization...")
print("Step 1/4: Fetching Go modules (this might take a few minutes)...")
print("Step 1/5: Fetching Go modules (this might take a few minutes)...")
code = Popen(["go", "mod", "download", "all"], cwd=PROJECT_DIRECTORY).wait()
Comment thread
ankurs marked this conversation as resolved.
if code != 0:
print("Error: Failed to fetch Go modules.")
sys.exit(code)

print("Step 2/4: Running 'make generate'...")
print("Step 2/5: Running 'make generate'...")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
code = Popen(["make", "generate"], cwd=PROJECT_DIRECTORY).wait()
if code != 0:
print("Error: 'make generate' failed.")
sys.exit(code)

print("Step 3/4: Running 'make mock'...")
print("Step 3/5: Tidying Go modules (pre-mock)...")
# -e flag ignores errors from missing mock packages (generated next step).
# Non-zero exit is expected (mock imports don't resolve yet) but logged for debugging.
code = Popen(["go", "mod", "tidy", "-e"], cwd=PROJECT_DIRECTORY).wait()
if code != 0:
print("Warning: pre-mock 'go mod tidy -e' exited with code %d (expected if mock packages don't exist yet)." % code)

print("Step 4/5: Running 'make mock'...")
code = Popen(["make", "mock"], cwd=PROJECT_DIRECTORY).wait()
if code != 0:
print("Error: 'make mock' failed.")
sys.exit(code)

print("Step 4/4: Tidying Go modules...")
print("Step 5/5: Tidying Go modules (final)...")
code = Popen(["go", "mod", "tidy"], cwd=PROJECT_DIRECTORY).wait()
if code != 0:
print("Error: 'go mod tidy' failed.")
Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.app_name}}/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ make run-docker # Run in Docker container
## Key Patterns

- **gRPC-first**: All endpoints are defined in `proto/{{cookiecutter.app_name|lower}}.proto`. HTTP/JSON routes are auto-generated via grpc-gateway annotations. Never create HTTP handlers manually.
- **Context propagation**: `context.Context` is the first parameter everywhere. Interceptors propagate trace IDs, log fields, and options through it.
- **Context propagation**: `context.Context` is the first parameter everywhere. Interceptors propagate trace IDs, log fields, and options through it. Service code uses `slog.LogAttrs(ctx, ...)` for logging; ColdBrew's Handler automatically injects context fields. Use `github.com/go-coldbrew/log.AddAttrsToContext` (imported as `cblog` in service.go) to add typed context fields.
Comment thread
ankurs marked this conversation as resolved.
- **Configuration**: All config via environment variables using `envconfig`. Add fields to `config/config.go` with struct tags. See [ColdBrew config docs](https://pkg.go.dev/github.com/go-coldbrew/core/config#Config) for framework options.
- **Authentication**: JWT and API key auth are built in via `service/auth/`. Config-controlled — set `JWT_SECRET` or `API_KEYS` env vars to enable. Health/ready/reflection RPCs bypass auth automatically. See [Authentication docs](https://docs.coldbrew.cloud/howto/auth/).
- **Health checks**: Kubernetes liveness (`/healthcheck`) and readiness (`/readycheck`) are built-in. Service starts as NOT_SERVING until `SetReady()` is called.
Expand Down
4 changes: 2 additions & 2 deletions {{cookiecutter.app_name}}/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package config

import (
"context"
"log/slog"

cbConfig "github.com/go-coldbrew/core/config"
"github.com/go-coldbrew/log"
"github.com/kelseyhightower/envconfig"
"{{cookiecutter.source_path}}/{{cookiecutter.app_name}}/service/auth"
)
Expand All @@ -29,7 +29,7 @@ func init() {
if defaultConfig.PanicOnConfigError {
panic(err)
} else {
log.Error(context.Background(), "msg", "error while loading config", "err", err)
slog.LogAttrs(context.Background(), slog.LevelError, "error while loading config", slog.Any("err", err))
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions {{cookiecutter.app_name}}/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ tool (

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1
github.com/go-coldbrew/core v0.1.45
github.com/go-coldbrew/errors v0.2.13
github.com/go-coldbrew/interceptors v0.1.20
github.com/go-coldbrew/log v0.3.1
github.com/go-coldbrew/core v0.1.51
github.com/go-coldbrew/errors v0.2.15
github.com/go-coldbrew/interceptors v0.1.25
github.com/go-coldbrew/log v0.4.1
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0
Expand Down Expand Up @@ -134,7 +134,7 @@ require (
github.com/ghostiam/protogetter v0.3.20 // indirect
github.com/go-coldbrew/hystrixprometheus v0.1.2 // indirect
github.com/go-coldbrew/options v0.3.0 // indirect
github.com/go-coldbrew/tracing v0.2.1 // indirect
github.com/go-coldbrew/tracing v0.2.2 // indirect
github.com/go-critic/go-critic v0.14.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand Down
6 changes: 4 additions & 2 deletions {{cookiecutter.app_name}}/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"log/slog"
"net/http"
"strings"

Expand All @@ -11,7 +12,6 @@ import (
"{{cookiecutter.source_path}}/{{cookiecutter.app_name}}/service/auth"
"{{cookiecutter.source_path}}/{{cookiecutter.app_name}}/version"
"github.com/go-coldbrew/core"
"github.com/go-coldbrew/log"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/swaggest/swgui"
"github.com/swaggest/swgui/v5emb"
Expand Down Expand Up @@ -142,5 +142,7 @@ func main() {

// Start the service and wait for it to exit
// This is a blocking call and will not return until the service exits completely
log.Error(context.Background(), cb.Run())
if err := cb.Run(); err != nil {
slog.LogAttrs(context.Background(), slog.LevelError, "service exited", slog.Any("err", err))
}
Comment thread
ankurs marked this conversation as resolved.
}
6 changes: 3 additions & 3 deletions {{cookiecutter.app_name}}/service/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ package auth
import (
"context"
"fmt"
"log/slog"
"strings"
"time"

"github.com/go-coldbrew/interceptors"
"github.com/go-coldbrew/log"
"github.com/golang-jwt/jwt/v5"
grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc"
Expand Down Expand Up @@ -100,7 +100,7 @@ func withAuthLogging(fn grpcauth.AuthFunc) grpcauth.AuthFunc {
authCtx, err := fn(ctx)
if err != nil {
method, _ := grpc.Method(ctx)
log.Warn(ctx, "msg", "auth failed", "method", method, "err", err)
slog.LogAttrs(ctx, slog.LevelWarn, "auth failed", slog.String("method", method), slog.Any("err", err))
}
return authCtx, err
}
Expand All @@ -120,7 +120,7 @@ func eitherAuthFunc(authFuncs ...grpcauth.AuthFunc) grpcauth.AuthFunc {
lastErr = err
}
method, _ := grpc.Method(ctx)
log.Warn(ctx, "msg", "auth failed: all methods exhausted", "method", method, "err", lastErr)
slog.LogAttrs(ctx, slog.LevelWarn, "auth failed: all methods exhausted", slog.String("method", method), slog.Any("err", lastErr))
return nil, lastErr
}
}
Expand Down
16 changes: 12 additions & 4 deletions {{cookiecutter.app_name}}/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"log/slog"
"time"

"{{cookiecutter.source_path}}/{{cookiecutter.app_name}}/config"
Expand All @@ -11,7 +12,7 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/genproto/googleapis/api/httpbody"
"github.com/go-coldbrew/errors"
"github.com/go-coldbrew/log"
cblog "github.com/go-coldbrew/log"
"google.golang.org/grpc/health"
)

Expand Down Expand Up @@ -42,8 +43,8 @@ func (s *svc) HealthCheck(ctx context.Context, _ *emptypb.Empty) (*httpbody.Http
}

// Echo returns the message with the prefix added
// TODO: remove this, since this is just to demonstrate how to use endpoints and config
func (s *svc) Echo(_ context.Context, req *proto.EchoRequest) (resp *proto.EchoResponse, err error) {
// TODO: remove this, since this is just to demonstrate how to use endpoints, config, and logging
func (s *svc) Echo(ctx context.Context, req *proto.EchoRequest) (resp *proto.EchoResponse, err error) {
start := time.Now()
outcome := metrics.OutcomeSuccess
defer func() {
Expand All @@ -53,6 +54,13 @@ func (s *svc) Echo(_ context.Context, req *proto.EchoRequest) (resp *proto.EchoR
s.monitoring.IncEchoTotal(outcome)
s.monitoring.ObserveEchoDuration(outcome, time.Since(start))
}()

// Add typed context fields — these appear in all logs for this request.
// ColdBrew interceptors already add trace_id and grpcMethod automatically.
Comment thread
ankurs marked this conversation as resolved.
ctx = cblog.AddAttrsToContext(ctx, slog.Int("echo_msg_len", len(req.GetMsg())))

slog.LogAttrs(ctx, slog.LevelInfo, "echo requested")

return &proto.EchoResponse{
Msg: fmt.Sprintf("%s: %s", s.prefix, req.GetMsg()),
}, nil
Expand All @@ -62,7 +70,7 @@ func (s *svc) Echo(_ context.Context, req *proto.EchoRequest) (resp *proto.EchoR
// TODO: remove this, since this is just to demonstrate how to use endpoints and config
func (s *svc) Error(ctx context.Context, req *proto.EchoRequest) (*proto.EchoResponse, error) {
err := errors.New("This is an Error")
log.Info(ctx, "error requested")
slog.LogAttrs(ctx, slog.LevelInfo, "error requested")
return nil, errors.Wrap(err, "endpoint error")
Comment thread
ankurs marked this conversation as resolved.
}

Expand Down
Loading