سرمایه گذاری متفاوت در سال نو 🍎🌱 ۳۵٪ تخفیف نوروزی ➕ حضور رایگان در مسترمایند نخبگان صنعت نرم‌افزار 💻✅
۰ ثانیه
۰ دقیقه
۰ ساعت
۱ bidoo
logger Middleware
جامعه گولنگ ایجاد شده در ۳۰ مرداد ۱۴۰۲

سلام وقت بخیر

خیلی ممنون بابت پاسخ هاتون به سوالات بنده در دروس فصل‌های قبلی

چند تا سوال از کدهای زیر داشتم ؟

type bodyLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}
func (w bodyLogWriter) WriteString(s string) (int, error) {
	w.body.WriteString(s)
	return w.ResponseWriter.WriteString(s)
}
func DefaultStructuredLogger(cfg *config.Config) gin.HandlerFunc {
	logger := logging.NewLogger(cfg)
	return structuredLogger(logger)
}
func structuredLogger(logger logging.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		if strings.Contains(c.FullPath(), "swagger") {
			c.Next()
		} else {
			blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
			start := time.Now() // start
			path := c.FullPath()
			raw := c.Request.URL.RawQuery
			bodyBytes, _ := io.ReadAll(c.Request.Body)
			c.Request.Body.Close()
			c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
			c.Writer = blw
			c.Next()
			param := gin.LogFormatterParams{}
			param.TimeStamp = time.Now() // stop
			param.Latency = param.TimeStamp.Sub(start)
			param.ClientIP = c.ClientIP()
			param.Method = c.Request.Method
			param.StatusCode = c.Writer.Status()
			param.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()
			param.BodySize = c.Writer.Size()
			if raw != "" {
				path = path + "?" + raw
			}
			param.Path = path
			keys := map[logging.ExtraKey]interface{}{}
			keys[logging.Path] = param.Path
			keys[logging.ClientIp] = param.ClientIP
			keys[logging.Method] = param.Method
			keys[logging.Latency] = param.Latency
			keys[logging.StatusCode] = param.StatusCode
			keys[logging.ErrorMessage] = param.ErrorMessage
			keys[logging.BodySize] = param.BodySize
			keys[logging.RequestBody] = string(bodyBytes)
			keys[logging.ResponseBody] = blw.body.String()
			logger.Info(logging.RequestResponse, logging.Api, "", keys)
		}
	}
}

در آموزش اگفیتد ResponseWriter مسئول نوشتن در RespnoseBody می‌باشد در کد زیر چرا مقدار blw را در c.Writer قرار میگیرد ؟ منظورم این هست که Writer مربوط به Contex چه نقشی دارد ، که باید قبل از c.Next() باید مقدار دهیی شود

2- در کد زیر چرا c.Request.Body بعد از خوانده شدن باید close شود ؟

	bodyBytes, _ := io.ReadAll(c.Request.Body)
	c.Request.Body.Close()
	c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

و اینکه چرا دوباره c.Request.Body باید مقدار دهی شود ؟ و کاربر NopCloser را متوجه نشدم

3- متدهای WriteString و Write چه زمانی call میشوند تا فیلد body بتواند پر شود ؟

سلام

چقدر خوبه که به این نکات ریز دقت میکنی.

همونطور که در جلسه توضیح دادیم ما دنبال راهی بودیم که بتونیم response body رو لاگ کنیم و داشته باشیم.

مشکلی که داشتیم این بود که c.Writer که از نوع gin.ResponseWriter هستش امکانی برای دریافت response body نداده است. پس باید راه حلی ارایه کنیم:

یک تایپ به نام bodyLogWriter ساختیم که دو چیز داره

۱− gin.ResponseWriter که در واقع همون c.Writer هستش برای اینکه میخوایم writer اصلی رو از دست ندیم و چون درون bodyLogWriter بصورت embed شده هستش عملا bodyLogWriter میتونه جای c.Writer بشینه.

۲−*bytes.Buffer برای نگهداری body که لاگش کنیم.

تایپ bodyLogWriter بخشی از اینترفیس gin.ResponseWriter را تغییر میده و بقیه رو از gin.ResponseWriter که درونش embed شده میگیره (Composition).

دوتا متدی که قراره custom بشوند اینها هستن که داخلش یک کپی از response body رو درون bodyLogWriter.body میریزیم برای لاگ کردن

func (w bodyLogWriter) Write(b []byte) (int, error) 
func (w bodyLogWriter) WriteString(s string) (int, error)

حالا برسیم به سوالات شما:

 در کد زیر چرا مقدار blw را در c.Writer قرار میگیرد؟ 

من تایپ جدیدی ساختم که به کمکش رفتار c.Writer رو کمی تغییر بدم و حالا این میشه writer جدید من و جای writer پیشفرض gin رو میگیره. در واقع با writer پیشفرض gin در همون دو تا متد متفاوته که فقط response body رو یه جایی برام نگهداری میکنه.


چرا قبل از c.Next() اینکار باید انجام بشه؟

وقتی c.Next() کال میشه در واقع داریم میگیم برو به middle ware بعدی که ممکنه خود endpoint باشه یا یه middle ware دیگه که ممکنه توش response ساخته بشه پس ما باید قبلش تایپ جدید رو به عنوان writer معرفی کنیم که response body مورد نظرمون توی bodyLogWriter.body ذخیره بشه.


چرا c.Request.Body بعد از خوانده شدن باید close شود ؟

نیاز نیست


چرا دوباره c.Request.Body باید مقدار دهی شود ؟ و کاربر NopCloser را متوجه نشدم؟

پراپرتی Body از نوع io.ReadCloser می‌باشد و مفهوم Reader در اکثر زبان‌های برنامه نویسی وجود دارد. Reader‌ها مانند Stream هستند. وقتی یکبار ازشون خوندیم دوباره نمیتونیم بخونیمش یعنی اگه دوبار Read انجام بدیم دفعه دوم چیزی برای خونده شدن وجود نداره چون ما انتهای استریم هستیم.

برای لاگ کردن ما محتوای Body را در یک byte array میریزیم و تبدیل به رشته می‌کنیم

و چون یکبار Body رو تا آخر خوندیمش و ممکنه در middle ware‌های بعدی بهش نیاز داشته باشیم از io.NopCloser استفاده کرده و byte array را به یک ReadCloser تبدیل می‌کنیم و دوباره میذاریمش توی Body که بعدی‌ها هم به Body دسترسی داشته باشن

برای تست میتونیم خط ۴۶ رو کامنت کنیم و ببینیم که وقتی وارد endpoint میشه چه اتفاقی میفته


بهترین پاسخ
حامد نعیمایی ۰۳ شهریور ۱۴۰۲، ۱۶:۱۵