diff --git a/README.md b/README.md index fb32158..7d54bcb 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,57 @@ handling logic. curl localhost:8080 # Output: Hello, World! ``` +## Quickstart: Enable Exeuction Id Logging + +[Cloud Run Functions(1st gen)](https://cloud.google.com/functions/1stgendocs/deploy) provides an execution id in the logs at `labels.execution_id`, which customers can use to filter their logs for each execution. [Cloud Run Functions](https://cloud.google.com/functions/docs/deploy) doesn't have the same feature embedded. + +To have exeuction id logged for `Cloud Run Functions` executions, users can either: + +* Provide a custom execution Id in the Http Header `Function-Execution-Id`. + + ```sh + curl -H "Function-Execution-Id: 123456" localhost:8080 + # Output: Hello, World! + ``` + + Example Log: + ``` + {"message":"Try logging with executionID!","logging.googleapis.com/labels":{"execution_id":"123456"}} + ``` + + +OR +* Leverage `LogWriter` provided in function-framework-go(v1.9.0 or higher) library to generate logs. If `Function-Exeuction-Id` is empty, a pseduorandom execution id will be auto-generated if `LogWriter` is used. + + ```golang + package function + + import ( + "fmt" + "net/http" + "log" + "github.com/GoogleCloudPlatform/functions-framework-go/functions" + "github.com/GoogleCloudPlatform/functions-framework-go/funcframework" + ) + + func init() { + functions.HTTP("HelloWorld", helloWorld) + } + + // helloWorld writes "Hello, World!" to the HTTP response. + func helloWorld(w http.ResponseWriter, r *http.Request) { + l := log.New(funcframework.LogWriter(r.Context()), "", 0) + + l.Println("Try logging with executionID!") + fmt.Fprintln(w, "Hello, World!") + } + ``` + + Example Log: + ``` + {"message":"Try logging with executionID!","logging.googleapis.com/labels":{"execution_id":"181dbb5b096549313d470dd68fa64d96"}} + ``` + ## Go further: build a deployable container diff --git a/funcframework/logging.go b/funcframework/logging.go index a109d22..fa3a71c 100644 --- a/funcframework/logging.go +++ b/funcframework/logging.go @@ -4,11 +4,14 @@ import ( "bufio" "context" "encoding/json" + "fmt" "io" + "math/rand" "net/http" "os" "regexp" "sync" + "time" ) var ( @@ -32,6 +35,11 @@ type contextKey string func addLoggingIDsToRequest(r *http.Request) *http.Request { executionID := r.Header.Get("Function-Execution-Id") + if executionID == "" { + timestamp := time.Now().UnixNano() + random := rand.Int63() + executionID = fmt.Sprintf("%06x%06x", timestamp, random) + } traceID, spanID, _ := deconstructXCloudTraceContext(r.Header.Get("X-Cloud-Trace-Context")) if executionID == "" && traceID == "" && spanID == "" { @@ -182,7 +190,7 @@ func (w *structuredLogWriter) Close() error { // ) // ... // func helloWorld(w http.ResponseWriter, r *http.Request) { -// l := logger.New(funcframework.LogWriter(r.Context())) +// l := log.New(funcframework.LogWriter(r.Context()), "", 0) // l.Println("hello world!") // } func LogWriter(ctx context.Context) io.WriteCloser { diff --git a/funcframework/logging_test.go b/funcframework/logging_test.go index 3acf6d1..e647b3f 100644 --- a/funcframework/logging_test.go +++ b/funcframework/logging_test.go @@ -15,13 +15,15 @@ func TestLoggingIDExtraction(t *testing.T) { wantTraceID string wantSpanID string wantExecutionID string + randomExecutionIdGenerated bool }{ { name: "no IDs", headers: map[string]string{}, + randomExecutionIdGenerated: true, }, { - name: "execution ID only", + name: "provided execution ID only", headers: map[string]string{ "Function-Execution-Id": "exec id", }, @@ -32,6 +34,7 @@ func TestLoggingIDExtraction(t *testing.T) { headers: map[string]string{ "X-Cloud-Trace-Context": "$*#$(v434)", }, + randomExecutionIdGenerated: true, }, { name: "trace ID only", @@ -39,6 +42,7 @@ func TestLoggingIDExtraction(t *testing.T) { "X-Cloud-Trace-Context": "0123456789abcdef", }, wantTraceID: "0123456789abcdef", + randomExecutionIdGenerated: true, }, { name: "trace ID and span ID", @@ -47,6 +51,7 @@ func TestLoggingIDExtraction(t *testing.T) { }, wantTraceID: "0123456789abcdef", wantSpanID: "aaaaaa", + randomExecutionIdGenerated: true, }, { name: "all", @@ -77,9 +82,13 @@ func TestLoggingIDExtraction(t *testing.T) { t.Errorf("expected span id %q but got %q", tc.wantSpanID, spid) } - if eid := ExecutionIDFromContext(ctx); eid != tc.wantExecutionID { + eid := ExecutionIDFromContext(ctx); + if tc.wantExecutionID != "" && eid != tc.wantExecutionID { t.Errorf("expected execution id %q but got %q", tc.wantExecutionID, eid) } + if tc.randomExecutionIdGenerated && eid == "" { + t.Errorf("expected random execution id generated but got %q", eid) + } }) } }