Repository: gin-gonic/gin
Pull request #2751 (2021-06-15 12:37:52)

gin.Context with fallback value from gin.Context.Request.Context()
For many tracing sdk
They will have SpanFromContext function. like:
https://github.com/opentracing/opentracing-go/blob/master/gocontext.go#L26
https://github.com/open-telemetry/opentelemetry-go/blob/main/trace/context.go#L48

And middleware sdk always put trace contenxt to http.Request.Context
https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go#L79

If gin.Context with fallback value from gin.Context.Request.Context(), it can be pass the gin.Context as context.Context to sub function call. It will be usefull.
```go
user, err := h.GetUser(c.Request.Context(), userID)
```
can be
```go
user, err := h.GetUser(c, userID)
```

thinkgos: Good!!  I always use c.Request.Context(). 
appleboy: @wei840222 Please rebase the master branch to trigger unit testing with GitHub Actions.
wei840222: hi @appleboy. I've rebased the master, plz check.
codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
> Merging [#2751](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (ea979c8) into [master](https://codecov.io/gh/gin-gonic/gin/commit/34ce2104cad324f444943c528746bf6d23643cd3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (34ce210) will **decrease** coverage by `0.09%`.
> The diff coverage is `86.66%`.

[![Impacted file tree graph](https://codecov.io/gh/gin-gonic/gin/pull/2751/graphs/tree.svg?width=650&height=150&src=pr&token=Qpqxhwery8&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)

```diff
@@            Coverage Diff             @@
##           master    #2751      +/-   ##
==========================================
- Coverage   98.69%   98.60%   -0.10%     
==========================================
  Files          41       41              
  Lines        2072     2078       +6     
==========================================
+ Hits         2045     2049       +4     
- Misses         15       16       +1     
- Partials       12       13       +1     
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [context.go](https://codecov.io/gh/gin-gonic/gin/pull/2751/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Y29udGV4dC5nbw==) | `97.17% <83.33%> (-0.48%)` | :arrow_down: |
| [gin.go](https://codecov.io/gh/gin-gonic/gin/pull/2751/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Z2luLmdv) | `99.18% <100.00%> (ø)` | |
| [tree.go](https://codecov.io/gh/gin-gonic/gin/pull/2751/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-dHJlZS5nbw==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Last update [34ce210...ea979c8](https://codecov.io/gh/gin-gonic/gin/pull/2751?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).


Patch:
diff --git a/context.go b/context.go
index 5c1aba5..ede16fd 100644
--- a/context.go
+++ b/context.go
@@ -1186,8 +1186,12 @@ func (c *Context) Value(key interface{}) interface{} {
 		return c.Request
 	}
 	if keyAsString, ok := key.(string); ok {
-		val, _ := c.Get(keyAsString)
-		return val
+		if val, exists := c.Get(keyAsString); exists {
+			return val
+		}
 	}
-	return nil
+	if c.Request == nil || c.Request.Context() == nil {
+		return nil
+	}
+	return c.Request.Context().Value(key)
 }
diff --git a/recovery.go b/recovery.go
index 563f5aa..3101fe2 100644
--- a/recovery.go
+++ b/recovery.go
@@ -34,7 +34,7 @@ func Recovery() HandlerFunc {
 	return RecoveryWithWriter(DefaultErrorWriter)
 }
 
-//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
+// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
 func CustomRecovery(handle RecoveryFunc) HandlerFunc {
 	return RecoveryWithWriter(DefaultErrorWriter, handle)
 }
@@ -165,7 +165,7 @@ func function(pc uintptr) []byte {
 	return name
 }
 
+// timeFormat returns a customized time string for logger.
 func timeFormat(t time.Time) string {
-	timeString := t.Format("2006/01/02 - 15:04:05")
-	return timeString
+	return t.Format("2006/01/02 - 15:04:05")
 }


Test patch:
diff --git a/context_test.go b/context_test.go
index 93b58ee..2a4d218 100644
--- a/context_test.go
+++ b/context_test.go
@@ -2057,3 +2057,56 @@ func TestRemoteIPFail(t *testing.T) {
 	assert.Nil(t, ip)
 	assert.False(t, trust)
 }
+
+func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
+	tests := []struct {
+		name             string
+		getContextAndKey func() (*Context, interface{})
+		value            interface{}
+	}{
+		{
+			name: "c with struct context key",
+			getContextAndKey: func() (*Context, interface{}) {
+				var key struct{}
+				c := &Context{}
+				c.Request, _ = http.NewRequest("POST", "/", nil)
+				c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value"))
+				return c, key
+			},
+			value: "value",
+		},
+		{
+			name: "c with string context key",
+			getContextAndKey: func() (*Context, interface{}) {
+				c := &Context{}
+				c.Request, _ = http.NewRequest("POST", "/", nil)
+				c.Request = c.Request.WithContext(context.WithValue(context.TODO(), "key", "value"))
+				return c, "key"
+			},
+			value: "value",
+		},
+		{
+			name: "c with nil http.Request",
+			getContextAndKey: func() (*Context, interface{}) {
+				c := &Context{}
+				return c, "key"
+			},
+			value: nil,
+		},
+		{
+			name: "c with nil http.Request.Context()",
+			getContextAndKey: func() (*Context, interface{}) {
+				c := &Context{}
+				c.Request, _ = http.NewRequest("POST", "/", nil)
+				return c, "key"
+			},
+			value: nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c, key := tt.getContextAndKey()
+			assert.Equal(t, tt.value, c.Value(key))
+		})
+	}
+}