Repository: zeromicro/go-zero
Pull request #1865 (2022-05-04 14:52:40)

refactor: refactor trace in redis & sql & mongo

Signed-off-by: chenquan <chenquan.dev@gmail.com>

chenquan: @kevwan PTAL

Patch:
diff --git a/core/logx/writer.go b/core/logx/writer.go
index e494df60..70182a70 100644
--- a/core/logx/writer.go
+++ b/core/logx/writer.go
@@ -241,7 +241,6 @@ func output(writer io.Writer, level string, val interface{}, fields ...LogField)
 
 	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
-		level = wrapLevelWithColor(level)
 		writePlainAny(writer, level, val, buildFields(fields...)...)
 	default:
 		entry := make(logEntryWithFields)
@@ -290,6 +289,8 @@ func writeJson(writer io.Writer, info interface{}) {
 }
 
 func writePlainAny(writer io.Writer, level string, val interface{}, fields ...string) {
+	level = wrapLevelWithColor(level)
+
 	switch v := val.(type) {
 	case string:
 		writePlainText(writer, level, v, fields...)
diff --git a/core/stores/mon/collection.go b/core/stores/mon/collection.go
index 9bb5bb44..6d2f7047 100644
--- a/core/stores/mon/collection.go
+++ b/core/stores/mon/collection.go
@@ -8,18 +8,35 @@ import (
 	"github.com/zeromicro/go-zero/core/breaker"
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/core/timex"
-	"github.com/zeromicro/go-zero/core/trace"
 	"go.mongodb.org/mongo-driver/mongo"
 	mopt "go.mongodb.org/mongo-driver/mongo/options"
 	"go.mongodb.org/mongo-driver/x/mongo/driver/session"
-	"go.opentelemetry.io/otel"
-	tracesdk "go.opentelemetry.io/otel/trace"
 )
 
 const (
 	defaultSlowThreshold = time.Millisecond * 500
 	// spanName is the span name of the mongo calls.
 	spanName = "mongo"
+
+	// mongodb method names
+	aggregate              = "Aggregate"
+	bulkWrite              = "BulkWrite"
+	countDocuments         = "CountDocuments"
+	deleteMany             = "DeleteMany"
+	deleteOne              = "DeleteOne"
+	distinct               = "Distinct"
+	estimatedDocumentCount = "EstimatedDocumentCount"
+	find                   = "Find"
+	findOne                = "FindOne"
+	findOneAndDelete       = "FindOneAndDelete"
+	findOneAndReplace      = "FindOneAndReplace"
+	findOneAndUpdate       = "FindOneAndUpdate"
+	insertMany             = "InsertMany"
+	insertOne              = "InsertOne"
+	replaceOne             = "ReplaceOne"
+	updateByID             = "UpdateByID"
+	updateMany             = "UpdateMany"
+	updateOne              = "UpdateOne"
 )
 
 // ErrNotFound is an alias of mongo.ErrNoDocuments
@@ -120,341 +137,397 @@ func newCollection(collection *mongo.Collection, brk breaker.Breaker) Collection
 
 func (c *decoratedCollection) Aggregate(ctx context.Context, pipeline interface{},
 	opts ...*mopt.AggregateOptions) (cur *mongo.Cursor, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, aggregate)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		starTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("Aggregate", starTime, err)
+			c.logDurationSimple(ctx, aggregate, starTime, err)
 		}()
 
 		cur, err = c.Collection.Aggregate(ctx, pipeline, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) BulkWrite(ctx context.Context, models []mongo.WriteModel,
 	opts ...*mopt.BulkWriteOptions) (res *mongo.BulkWriteResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, bulkWrite)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("BulkWrite", startTime, err)
+			c.logDurationSimple(ctx, bulkWrite, startTime, err)
 		}()
 
 		res, err = c.Collection.BulkWrite(ctx, models, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) CountDocuments(ctx context.Context, filter interface{},
 	opts ...*mopt.CountOptions) (count int64, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, countDocuments)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("CountDocuments", startTime, err)
+			c.logDurationSimple(ctx, countDocuments, startTime, err)
 		}()
 
 		count, err = c.Collection.CountDocuments(ctx, filter, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) DeleteMany(ctx context.Context, filter interface{},
 	opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, deleteMany)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("DeleteMany", startTime, err)
+			c.logDurationSimple(ctx, deleteMany, startTime, err)
 		}()
 
 		res, err = c.Collection.DeleteMany(ctx, filter, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) DeleteOne(ctx context.Context, filter interface{},
 	opts ...*mopt.DeleteOptions) (res *mongo.DeleteResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, deleteOne)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("DeleteOne", startTime, err, filter)
+			c.logDuration(ctx, deleteOne, startTime, err, filter)
 		}()
 
 		res, err = c.Collection.DeleteOne(ctx, filter, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) Distinct(ctx context.Context, fieldName string, filter interface{},
 	opts ...*mopt.DistinctOptions) (val []interface{}, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, distinct)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("Distinct", startTime, err)
+			c.logDurationSimple(ctx, distinct, startTime, err)
 		}()
 
 		val, err = c.Collection.Distinct(ctx, fieldName, filter, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) EstimatedDocumentCount(ctx context.Context,
 	opts ...*mopt.EstimatedDocumentCountOptions) (val int64, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, estimatedDocumentCount)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("EstimatedDocumentCount", startTime, err)
+			c.logDurationSimple(ctx, estimatedDocumentCount, startTime, err)
 		}()
 
 		val, err = c.Collection.EstimatedDocumentCount(ctx, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) Find(ctx context.Context, filter interface{},
 	opts ...*mopt.FindOptions) (cur *mongo.Cursor, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, find)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("Find", startTime, err, filter)
+			c.logDuration(ctx, find, startTime, err, filter)
 		}()
 
 		cur, err = c.Collection.Find(ctx, filter, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) FindOne(ctx context.Context, filter interface{},
 	opts ...*mopt.FindOneOptions) (res *mongo.SingleResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, findOne)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("FindOne", startTime, err, filter)
+			c.logDuration(ctx, findOne, startTime, err, filter)
 		}()
 
 		res = c.Collection.FindOne(ctx, filter, opts...)
 		err = res.Err()
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) FindOneAndDelete(ctx context.Context, filter interface{},
 	opts ...*mopt.FindOneAndDeleteOptions) (res *mongo.SingleResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, findOneAndDelete)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("FindOneAndDelete", startTime, err, filter)
+			c.logDuration(ctx, findOneAndDelete, startTime, err, filter)
 		}()
 
 		res = c.Collection.FindOneAndDelete(ctx, filter, opts...)
 		err = res.Err()
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter interface{},
 	replacement interface{}, opts ...*mopt.FindOneAndReplaceOptions) (
 	res *mongo.SingleResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, findOneAndReplace)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("FindOneAndReplace", startTime, err, filter, replacement)
+			c.logDuration(ctx, findOneAndReplace, startTime, err, filter, replacement)
 		}()
 
 		res = c.Collection.FindOneAndReplace(ctx, filter, replacement, opts...)
 		err = res.Err()
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
 	opts ...*mopt.FindOneAndUpdateOptions) (res *mongo.SingleResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, findOneAndUpdate)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("FindOneAndUpdate", startTime, err, filter, update)
+			c.logDuration(ctx, findOneAndUpdate, startTime, err, filter, update)
 		}()
 
 		res = c.Collection.FindOneAndUpdate(ctx, filter, update, opts...)
 		err = res.Err()
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) InsertMany(ctx context.Context, documents []interface{},
 	opts ...*mopt.InsertManyOptions) (res *mongo.InsertManyResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, insertMany)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("InsertMany", startTime, err)
+			c.logDurationSimple(ctx, insertMany, startTime, err)
 		}()
 
 		res, err = c.Collection.InsertMany(ctx, documents, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) InsertOne(ctx context.Context, document interface{},
 	opts ...*mopt.InsertOneOptions) (res *mongo.InsertOneResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, insertOne)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("InsertOne", startTime, err, document)
+			c.logDuration(ctx, insertOne, startTime, err, document)
 		}()
 
 		res, err = c.Collection.InsertOne(ctx, document, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
 	opts ...*mopt.ReplaceOptions) (res *mongo.UpdateResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, replaceOne)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("ReplaceOne", startTime, err, filter, replacement)
+			c.logDuration(ctx, replaceOne, startTime, err, filter, replacement)
 		}()
 
 		res, err = c.Collection.ReplaceOne(ctx, filter, replacement, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, update interface{},
 	opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, updateByID)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("UpdateByID", startTime, err, id, update)
+			c.logDuration(ctx, updateByID, startTime, err, id, update)
 		}()
 
 		res, err = c.Collection.UpdateByID(ctx, id, update, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
 	opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, updateMany)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDurationSimple("UpdateMany", startTime, err)
+			c.logDurationSimple(ctx, updateMany, startTime, err)
 		}()
 
 		res, err = c.Collection.UpdateMany(ctx, filter, update, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
 func (c *decoratedCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
 	opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, updateOne)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = c.brk.DoWithAcceptable(func() error {
 		startTime := timex.Now()
 		defer func() {
-			c.logDuration("UpdateOne", startTime, err, filter, update)
+			c.logDuration(ctx, updateOne, startTime, err, filter, update)
 		}()
 
 		res, err = c.Collection.UpdateOne(ctx, filter, update, opts...)
 		return err
 	}, acceptable)
+
 	return
 }
 
-func (c *decoratedCollection) logDuration(method string, startTime time.Duration, err error,
+func (c *decoratedCollection) logDuration(ctx context.Context, method string, startTime time.Duration, err error,
 	docs ...interface{}) {
 	duration := timex.Since(startTime)
+	logger := logx.WithContext(ctx).WithDuration(duration)
+
 	content, e := json.Marshal(docs)
 	if e != nil {
-		logx.Error(err)
+		logger.Error(err)
 	} else if err != nil {
 		if duration > slowThreshold.Load() {
-			logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
+			logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
 				c.name, method, err.Error(), string(content))
 		} else {
-			logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s",
+			logger.Infof("mongo(%s) - %s - fail(%s) - %s",
 				c.name, method, err.Error(), string(content))
 		}
 	} else {
 		if duration > slowThreshold.Load() {
-			logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
+			logger.Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
 				c.name, method, string(content))
 		} else {
-			logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
+			logger.Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
 		}
 	}
 }
 
-func (c *decoratedCollection) logDurationSimple(method string, startTime time.Duration, err error) {
-	logDuration(c.name, method, startTime, err)
+func (c *decoratedCollection) logDurationSimple(ctx context.Context, method string, startTime time.Duration, err error) {
+	logDuration(ctx, c.name, method, startTime, err)
 }
 
 func (p keepablePromise) accept(err error) error {
@@ -483,8 +556,3 @@ func acceptable(err error) bool {
 		err == session.ErrAbortTwice || err == session.ErrCommitAfterAbort ||
 		err == session.ErrUnackWCUnsupported || err == session.ErrSnapshotTransaction
 }
-
-func startSpan(ctx context.Context) (context.Context, tracesdk.Span) {
-	tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
-	return tracer.Start(ctx, spanName)
-}
diff --git a/core/stores/mon/model.go b/core/stores/mon/model.go
index 220a3776..c963f988 100644
--- a/core/stores/mon/model.go
+++ b/core/stores/mon/model.go
@@ -11,6 +11,14 @@ import (
 	mopt "go.mongodb.org/mongo-driver/mongo/options"
 )
 
+const (
+	startSession      = "StartSession"
+	abortTransaction  = "AbortTransaction"
+	commitTransaction = "CommitTransaction"
+	withTransaction   = "WithTransaction"
+	endSession        = "EndSession"
+)
+
 type (
 	// Model is a mongodb store model that represents a collection.
 	Model struct {
@@ -23,7 +31,8 @@ type (
 
 	wrappedSession struct {
 		mongo.Session
-		brk breaker.Breaker
+		name string
+		brk  breaker.Breaker
 	}
 )
 
@@ -66,7 +75,7 @@ func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session,
 	err = m.brk.DoWithAcceptable(func() error {
 		starTime := timex.Now()
 		defer func() {
-			logDuration(m.name, "StartSession", starTime, err)
+			logDuration(context.Background(), m.name, startSession, starTime, err)
 		}()
 
 		session, sessionErr := m.cli.StartSession(opts...)
@@ -76,11 +85,13 @@ func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session,
 
 		sess = &wrappedSession{
 			Session: session,
+			name:    m.name,
 			brk:     m.brk,
 		}
 
 		return nil
 	}, acceptable)
+
 	return
 }
 
@@ -169,33 +180,57 @@ func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter interface{}, upd
 	return res.Decode(v)
 }
 
-func (w *wrappedSession) AbortTransaction(ctx context.Context) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+// AbortTransaction implements the mongo.Session interface.
+func (w *wrappedSession) AbortTransaction(ctx context.Context) (err error) {
+	ctx, span := startSpan(ctx, abortTransaction)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return w.brk.DoWithAcceptable(func() error {
+		starTime := timex.Now()
+		defer func() {
+			logDuration(ctx, w.name, abortTransaction, starTime, err)
+		}()
+
 		return w.Session.AbortTransaction(ctx)
 	}, acceptable)
 }
 
-func (w *wrappedSession) CommitTransaction(ctx context.Context) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+// CommitTransaction implements the mongo.Session interface.
+func (w *wrappedSession) CommitTransaction(ctx context.Context) (err error) {
+	ctx, span := startSpan(ctx, commitTransaction)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return w.brk.DoWithAcceptable(func() error {
+		starTime := timex.Now()
+		defer func() {
+			logDuration(ctx, w.name, commitTransaction, starTime, err)
+		}()
+
 		return w.Session.CommitTransaction(ctx)
 	}, acceptable)
 }
 
+// WithTransaction implements the mongo.Session interface.
 func (w *wrappedSession) WithTransaction(
 	ctx context.Context,
 	fn func(sessCtx mongo.SessionContext) (interface{}, error),
 	opts ...*mopt.TransactionOptions,
 ) (res interface{}, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, withTransaction)
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = w.brk.DoWithAcceptable(func() error {
+		starTime := timex.Now()
+		defer func() {
+			logDuration(ctx, w.name, withTransaction, starTime, err)
+		}()
+
 		res, err = w.Session.WithTransaction(ctx, fn, opts...)
 		return err
 	}, acceptable)
@@ -203,11 +238,20 @@ func (w *wrappedSession) WithTransaction(
 	return
 }
 
+// EndSession implements the mongo.Session interface.
 func (w *wrappedSession) EndSession(ctx context.Context) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	var err error
+	ctx, span := startSpan(ctx, endSession)
+	defer func() {
+		endSpan(span, err)
+	}()
+
+	err = w.brk.DoWithAcceptable(func() error {
+		starTime := timex.Now()
+		defer func() {
+			logDuration(ctx, w.name, endSession, starTime, err)
+		}()
 
-	_ = w.brk.DoWithAcceptable(func() error {
 		w.Session.EndSession(ctx)
 		return nil
 	}, acceptable)
diff --git a/core/stores/mon/trace.go b/core/stores/mon/trace.go
new file mode 100644
index 00000000..2f222f23
--- /dev/null
+++ b/core/stores/mon/trace.go
@@ -0,0 +1,37 @@
+package mon
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/core/trace"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/codes"
+	oteltrace "go.opentelemetry.io/otel/trace"
+)
+
+var mongoCmdAttributeKey = attribute.Key("mongo.cmd")
+
+func startSpan(ctx context.Context, cmd string) (context.Context, oteltrace.Span) {
+	tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
+	ctx, span := tracer.Start(ctx,
+		spanName,
+		oteltrace.WithSpanKind(oteltrace.SpanKindClient),
+	)
+	span.SetAttributes(mongoCmdAttributeKey.String(cmd))
+	return ctx, span
+}
+
+func endSpan(span oteltrace.Span, err error) {
+	defer span.End()
+
+	if err == nil || err == mongo.ErrNoDocuments ||
+		err == mongo.ErrNilValue || err == mongo.ErrNilDocument {
+		span.SetStatus(codes.Ok, "")
+		return
+	}
+
+	span.SetStatus(codes.Error, err.Error())
+	span.RecordError(err)
+}
diff --git a/core/stores/mon/util.go b/core/stores/mon/util.go
index 619f872b..e401df31 100644
--- a/core/stores/mon/util.go
+++ b/core/stores/mon/util.go
@@ -1,6 +1,7 @@
 package mon
 
 import (
+	"context"
 	"strings"
 	"time"
 
@@ -15,11 +16,12 @@ func FormatAddr(hosts []string) string {
 	return strings.Join(hosts, mongoAddrSep)
 }
 
-func logDuration(name, method string, startTime time.Duration, err error) {
+func logDuration(ctx context.Context, name, method string, startTime time.Duration, err error) {
 	duration := timex.Since(startTime)
+	logger := logx.WithContext(ctx).WithDuration(duration)
 	if err != nil {
-		logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s)", name, method, err.Error())
+		logger.Infof("mongo(%s) - %s - fail(%s)", name, method, err.Error())
 	} else {
-		logx.WithDuration(duration).Infof("mongo(%s) - %s - ok", name, method)
+		logger.Infof("mongo(%s) - %s - ok", name, method)
 	}
 }
diff --git a/core/stores/redis/hook.go b/core/stores/redis/hook.go
index b3b1f144..6cd0ab42 100644
--- a/core/stores/redis/hook.go
+++ b/core/stores/redis/hook.go
@@ -6,35 +6,40 @@ import (
 	"time"
 
 	red "github.com/go-redis/redis/v8"
+	"github.com/zeromicro/go-zero/core/errorx"
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/core/mapping"
 	"github.com/zeromicro/go-zero/core/timex"
 	"github.com/zeromicro/go-zero/core/trace"
 	"go.opentelemetry.io/otel"
-	tracestd "go.opentelemetry.io/otel/trace"
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/codes"
+	oteltrace "go.opentelemetry.io/otel/trace"
 )
 
 // spanName is the span name of the redis calls.
 const spanName = "redis"
 
 var (
-	startTimeKey = contextKey("startTime")
-	durationHook = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)}
+	startTimeKey          = contextKey("startTime")
+	durationHook          = hook{tracer: otel.GetTracerProvider().Tracer(trace.TraceName)}
+	redisCmdsAttributeKey = attribute.Key("redis.cmds")
 )
 
 type (
 	contextKey string
 	hook       struct {
-		tracer tracestd.Tracer
+		tracer oteltrace.Tracer
 	}
 )
 
-func (h hook) BeforeProcess(ctx context.Context, _ red.Cmder) (context.Context, error) {
-	return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now())), nil
+func (h hook) BeforeProcess(ctx context.Context, cmd red.Cmder) (context.Context, error) {
+	return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now()), cmd), nil
 }
 
 func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
-	h.endSpan(ctx)
+	err := cmd.Err()
+	h.endSpan(ctx, err)
 
 	val := ctx.Value(startTimeKey)
 	if val == nil {
@@ -54,17 +59,30 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
 	return nil
 }
 
-func (h hook) BeforeProcessPipeline(ctx context.Context, _ []red.Cmder) (context.Context, error) {
-	return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now())), nil
+func (h hook) BeforeProcessPipeline(ctx context.Context, cmds []red.Cmder) (context.Context, error) {
+	if len(cmds) == 0 {
+		return ctx, nil
+	}
+
+	return h.startSpan(context.WithValue(ctx, startTimeKey, timex.Now()), cmds...), nil
 }
 
 func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error {
-	h.endSpan(ctx)
-
 	if len(cmds) == 0 {
 		return nil
 	}
 
+	batchError := errorx.BatchError{}
+	for _, cmd := range cmds {
+		err := cmd.Err()
+		if err == nil {
+			continue
+		}
+
+		batchError.Add(err)
+	}
+	h.endSpan(ctx, batchError.Err())
+
 	val := ctx.Value(startTimeKey)
 	if val == nil {
 		return nil
@@ -94,11 +112,30 @@ func logDuration(ctx context.Context, cmd red.Cmder, duration time.Duration) {
 	logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String())
 }
 
-func (h hook) startSpan(ctx context.Context) context.Context {
-	ctx, _ = h.tracer.Start(ctx, spanName)
+func (h hook) startSpan(ctx context.Context, cmds ...red.Cmder) context.Context {
+	ctx, span := h.tracer.Start(ctx,
+		spanName,
+		oteltrace.WithSpanKind(oteltrace.SpanKindClient),
+	)
+
+	cmdStrs := make([]string, 0, len(cmds))
+	for _, cmd := range cmds {
+		cmdStrs = append(cmdStrs, cmd.Name())
+	}
+	span.SetAttributes(redisCmdsAttributeKey.StringSlice(cmdStrs))
+
 	return ctx
 }
 
-func (h hook) endSpan(ctx context.Context) {
-	tracestd.SpanFromContext(ctx).End()
+func (h hook) endSpan(ctx context.Context, err error) {
+	span := oteltrace.SpanFromContext(ctx)
+	defer span.End()
+
+	if err == nil || err == red.Nil {
+		span.SetStatus(codes.Ok, "")
+		return
+	}
+
+	span.SetStatus(codes.Error, err.Error())
+	span.RecordError(err)
 }
diff --git a/core/stores/sqlx/sqlconn.go b/core/stores/sqlx/sqlconn.go
index 2d880711..6e2b6d4f 100644
--- a/core/stores/sqlx/sqlconn.go
+++ b/core/stores/sqlx/sqlconn.go
@@ -6,9 +6,6 @@ import (
 
 	"github.com/zeromicro/go-zero/core/breaker"
 	"github.com/zeromicro/go-zero/core/logx"
-	"github.com/zeromicro/go-zero/core/trace"
-	"go.opentelemetry.io/otel"
-	tracesdk "go.opentelemetry.io/otel/trace"
 )
 
 // spanName is used to identify the span name for the SQL execution.
@@ -140,8 +137,10 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
 
 func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...interface{}) (
 	result sql.Result, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, "Exec")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = db.brk.DoWithAcceptable(func() error {
 		var conn *sql.DB
@@ -163,8 +162,10 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
 }
 
 func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt StmtSession, err error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	ctx, span := startSpan(ctx, "Prepare")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	err = db.brk.DoWithAcceptable(func() error {
 		var conn *sql.DB
@@ -194,9 +195,11 @@ func (db *commonSqlConn) QueryRow(v interface{}, q string, args ...interface{})
 }
 
 func (db *commonSqlConn) QueryRowCtx(ctx context.Context, v interface{}, q string,
-	args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRow")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return db.queryRows(ctx, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, true)
@@ -208,9 +211,11 @@ func (db *commonSqlConn) QueryRowPartial(v interface{}, q string, args ...interf
 }
 
 func (db *commonSqlConn) QueryRowPartialCtx(ctx context.Context, v interface{},
-	q string, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	q string, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRowPartial")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return db.queryRows(ctx, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, false)
@@ -222,9 +227,11 @@ func (db *commonSqlConn) QueryRows(v interface{}, q string, args ...interface{})
 }
 
 func (db *commonSqlConn) QueryRowsCtx(ctx context.Context, v interface{}, q string,
-	args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRows")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return db.queryRows(ctx, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, true)
@@ -236,9 +243,11 @@ func (db *commonSqlConn) QueryRowsPartial(v interface{}, q string, args ...inter
 }
 
 func (db *commonSqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{},
-	q string, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	q string, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRowsPartial")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return db.queryRows(ctx, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, false)
@@ -255,9 +264,11 @@ func (db *commonSqlConn) Transact(fn func(Session) error) error {
 	})
 }
 
-func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) (err error) {
+	ctx, span := startSpan(ctx, "Transact")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return db.brk.DoWithAcceptable(func() error {
 		return transact(ctx, db, db.beginTx, fn)
@@ -274,10 +285,7 @@ func (db *commonSqlConn) acceptable(err error) bool {
 }
 
 func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
-	q string, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
-
+	q string, args ...interface{}) (err error) {
 	var qerr error
 	return db.brk.DoWithAcceptable(func() error {
 		conn, err := db.connProv()
@@ -303,9 +311,11 @@ func (s statement) Exec(args ...interface{}) (sql.Result, error) {
 	return s.ExecCtx(context.Background(), args...)
 }
 
-func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (result sql.Result, err error) {
+	ctx, span := startSpan(ctx, "Exec")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return execStmt(ctx, s.stmt, s.query, args...)
 }
@@ -314,22 +324,27 @@ func (s statement) QueryRow(v interface{}, args ...interface{}) error {
 	return s.QueryRowCtx(context.Background(), v, args...)
 }
 
-func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRow")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, true)
 	}, s.query, args...)
+
 }
 
 func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
 	return s.QueryRowPartialCtx(context.Background(), v, args...)
 }
 
-func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRowPartial")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, false)
@@ -340,9 +355,11 @@ func (s statement) QueryRows(v interface{}, args ...interface{}) error {
 	return s.QueryRowsCtx(context.Background(), v, args...)
 }
 
-func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRows")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, true)
@@ -353,16 +370,13 @@ func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
 	return s.QueryRowsPartialCtx(context.Background(), v, args...)
 }
 
-func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRowsPartial")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, false)
 	}, s.query, args...)
 }
-
-func startSpan(ctx context.Context) (context.Context, tracesdk.Span) {
-	tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
-	return tracer.Start(ctx, spanName)
-}
diff --git a/core/stores/sqlx/trace.go b/core/stores/sqlx/trace.go
new file mode 100644
index 00000000..2c862264
--- /dev/null
+++ b/core/stores/sqlx/trace.go
@@ -0,0 +1,37 @@
+package sqlx
+
+import (
+	"context"
+	"database/sql"
+
+	"github.com/zeromicro/go-zero/core/trace"
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/codes"
+	oteltrace "go.opentelemetry.io/otel/trace"
+)
+
+var sqlAttributeKey = attribute.Key("sql.method")
+
+func startSpan(ctx context.Context, method string) (context.Context, oteltrace.Span) {
+	tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
+	start, span := tracer.Start(ctx,
+		spanName,
+		oteltrace.WithSpanKind(oteltrace.SpanKindClient),
+	)
+	span.SetAttributes(sqlAttributeKey.String(method))
+
+	return start, span
+}
+
+func endSpan(span oteltrace.Span, err error) {
+	defer span.End()
+
+	if err == nil || err == sql.ErrNoRows {
+		span.SetStatus(codes.Ok, "")
+		return
+	}
+
+	span.SetStatus(codes.Error, err.Error())
+	span.RecordError(err)
+}
diff --git a/core/stores/sqlx/tx.go b/core/stores/sqlx/tx.go
index 98d3b1d8..0ddd7e41 100644
--- a/core/stores/sqlx/tx.go
+++ b/core/stores/sqlx/tx.go
@@ -30,20 +30,26 @@ func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
 	return t.ExecCtx(context.Background(), q, args...)
 }
 
-func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (sql.Result, error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (result sql.Result, err error) {
+	ctx, span := startSpan(ctx, "Exec")
+	defer func() {
+		endSpan(span, err)
+	}()
 
-	return exec(ctx, t.Tx, q, args...)
+	result, err = exec(ctx, t.Tx, q, args...)
+
+	return
 }
 
 func (t txSession) Prepare(q string) (StmtSession, error) {
 	return t.PrepareCtx(context.Background(), q)
 }
 
-func (t txSession) PrepareCtx(ctx context.Context, q string) (StmtSession, error) {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (t txSession) PrepareCtx(ctx context.Context, q string) (stmtSession StmtSession, err error) {
+	ctx, span := startSpan(ctx, "Prepare")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	stmt, err := t.Tx.PrepareContext(ctx, q)
 	if err != nil {
@@ -60,9 +66,11 @@ func (t txSession) QueryRow(v interface{}, q string, args ...interface{}) error
 	return t.QueryRowCtx(context.Background(), v, q, args...)
 }
 
-func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRow")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return query(ctx, t.Tx, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, true)
@@ -74,9 +82,11 @@ func (t txSession) QueryRowPartial(v interface{}, q string, args ...interface{})
 }
 
 func (t txSession) QueryRowPartialCtx(ctx context.Context, v interface{}, q string,
-	args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRowPartial")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return query(ctx, t.Tx, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, false)
@@ -87,9 +97,11 @@ func (t txSession) QueryRows(v interface{}, q string, args ...interface{}) error
 	return t.QueryRowsCtx(context.Background(), v, q, args...)
 }
 
-func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRows")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return query(ctx, t.Tx, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, true)
@@ -101,9 +113,11 @@ func (t txSession) QueryRowsPartial(v interface{}, q string, args ...interface{}
 }
 
 func (t txSession) QueryRowsPartialCtx(ctx context.Context, v interface{}, q string,
-	args ...interface{}) error {
-	ctx, span := startSpan(ctx)
-	defer span.End()
+	args ...interface{}) (err error) {
+	ctx, span := startSpan(ctx, "QueryRowsPartial")
+	defer func() {
+		endSpan(span, err)
+	}()
 
 	return query(ctx, t.Tx, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, false)
diff --git a/readme-cn.md b/readme-cn.md
index fb984cc5..c6472149 100644
--- a/readme-cn.md
+++ b/readme-cn.md
@@ -1,4 +1,4 @@
-<img align="right" width="150px" src="https://gitee.com/kevwan/static/raw/master/doc/images/go-zero.png">
+<img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
 
 # go-zero
 
@@ -267,6 +267,8 @@ go-zero 已被许多公司用于生产部署，接入场景如在线教育、电
 >59. 费芮网络
 >60. 51CTO
 >61. 聿旌科技
+>62. 山东胜软科技股份有限公司
+>63. 上海芯果科技有限公司(好特卖)
 
 如果贵公司也已使用 go-zero，欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记，仅仅为了推广，不做其它用途。
 
diff --git a/tools/goctl/api/tsgen/components.tpl b/tools/goctl/api/tsgen/components.tpl
index 5a0cd9dd..aabe0194 100644
--- a/tools/goctl/api/tsgen/components.tpl
+++ b/tools/goctl/api/tsgen/components.tpl
@@ -1,3 +1,3 @@
-/ Code generated by goctl. DO NOT EDIT.
+// Code generated by goctl. DO NOT EDIT.
 
 {{.componentTypes}}
diff --git a/tools/goctl/cmd/root.go b/tools/goctl/cmd/root.go
index ccbb0353..d2d8717c 100644
--- a/tools/goctl/cmd/root.go
+++ b/tools/goctl/cmd/root.go
@@ -16,6 +16,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/kube"
 	"github.com/zeromicro/go-zero/tools/goctl/migrate"
 	"github.com/zeromicro/go-zero/tools/goctl/model"
+	"github.com/zeromicro/go-zero/tools/goctl/quickstart"
 	"github.com/zeromicro/go-zero/tools/goctl/rpc"
 	"github.com/zeromicro/go-zero/tools/goctl/tpl"
 	"github.com/zeromicro/go-zero/tools/goctl/upgrade"
@@ -87,7 +88,8 @@ func supportGoStdFlag(args []string) []string {
 }
 
 func init() {
-	rootCmd.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
+	rootCmd.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion,
+		runtime.GOOS, runtime.GOARCH)
 	rootCmd.AddCommand(api.Cmd)
 	rootCmd.AddCommand(bug.Cmd)
 	rootCmd.AddCommand(docker.Cmd)
@@ -95,6 +97,7 @@ func init() {
 	rootCmd.AddCommand(env.Cmd)
 	rootCmd.AddCommand(model.Cmd)
 	rootCmd.AddCommand(migrate.Cmd)
+	rootCmd.AddCommand(quickstart.Cmd)
 	rootCmd.AddCommand(rpc.Cmd)
 	rootCmd.AddCommand(tpl.Cmd)
 	rootCmd.AddCommand(upgrade.Cmd)
diff --git a/tools/goctl/go.sum b/tools/goctl/go.sum
index e7f3e3b8..6c39535c 100644
--- a/tools/goctl/go.sum
+++ b/tools/goctl/go.sum
@@ -66,6 +66,7 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -187,6 +188,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -285,6 +287,7 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -315,6 +318,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
 github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
+github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw=
 github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -327,19 +331,23 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
 github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -363,6 +371,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -398,7 +407,9 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
 go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
+go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
 go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
+go.opentelemetry.io/otel/exporters/zipkin v1.3.0 h1:uOD28dZ7yIKITTcUS6MeAGNHYy3uhP7DTkhcJM6onlQ=
 go.opentelemetry.io/otel/exporters/zipkin v1.3.0/go.mod h1:LxGGfHIYbvsFnrJtBcazb0yG24xHdDGrT/H6RB9r3+8=
 go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
 go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
@@ -635,6 +646,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -690,6 +702,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
 google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -707,6 +720,7 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -720,6 +734,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go
index 29194225..ee55c0d7 100644
--- a/tools/goctl/model/sql/gen/gen.go
+++ b/tools/goctl/model/sql/gen/gen.go
@@ -65,7 +65,7 @@ func NewDefaultGenerator(dir string, cfg *config.Config, opt ...Option) (*defaul
 	}
 
 	dir = dirAbs
-	pkg := filepath.Base(dirAbs)
+	pkg := util.SafeString(filepath.Base(dirAbs))
 	err = pathx.MkdirIfNotExist(dir)
 	if err != nil {
 		return nil, err
diff --git a/tools/goctl/quickstart/cmd.go b/tools/goctl/quickstart/cmd.go
new file mode 100644
index 00000000..357b62a9
--- /dev/null
+++ b/tools/goctl/quickstart/cmd.go
@@ -0,0 +1,25 @@
+package quickstart
+
+import "github.com/spf13/cobra"
+
+const (
+	serviceTypeMono  = "mono"
+	serviceTypeMicro = "micro"
+)
+
+var (
+	varStringServiceType string
+
+	// Cmd describes the command to run.
+	Cmd = &cobra.Command{
+		Use:   "quickstart",
+		Short: "quickly start a project",
+		RunE:  run,
+	}
+)
+
+func init() {
+	Cmd.Flags().StringVarP(&varStringServiceType,
+		"service-type", "t", "mono",
+		"specify the service type, supported values: [mono, micro]")
+}
diff --git a/tools/goctl/quickstart/idl/api.yaml b/tools/goctl/quickstart/idl/api.yaml
new file mode 100644
index 00000000..b5ce1830
--- /dev/null
+++ b/tools/goctl/quickstart/idl/api.yaml
@@ -0,0 +1,3 @@
+Name: ping
+Host: 127.0.0.1
+Port: 8888
\ No newline at end of file
diff --git a/tools/goctl/quickstart/idl/apilogic.tpl b/tools/goctl/quickstart/idl/apilogic.tpl
new file mode 100644
index 00000000..d0363257
--- /dev/null
+++ b/tools/goctl/quickstart/idl/apilogic.tpl
@@ -0,0 +1,35 @@
+package logic
+
+import (
+    "context"
+
+    "github.com/zeromicro/go-zero/core/logx"
+    "greet/api/internal/svc"
+    "greet/api/internal/types"
+    {{if .callRPC}}"greet/rpc/greet"
+{{end}}
+)
+
+type PingLogic struct {
+    logx.Logger
+    ctx    context.Context
+    svcCtx *svc.ServiceContext
+}
+
+func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
+    return &PingLogic{
+        Logger: logx.WithContext(ctx),
+        ctx:    ctx,
+        svcCtx: svcCtx,
+    }
+}
+
+func (l *PingLogic) Ping() (resp *types.Resp, err error) {
+    {{if .callRPC}}if _, err = l.svcCtx.GreetRpc.Ping(l.ctx, &greet.Placeholder{});err !=nil {
+        return
+    }{{end}}
+    resp = new(types.Resp)
+    resp.Msg = "pong"
+
+    return
+}
diff --git a/tools/goctl/quickstart/idl/greet.api b/tools/goctl/quickstart/idl/greet.api
new file mode 100644
index 00000000..a3091236
--- /dev/null
+++ b/tools/goctl/quickstart/idl/greet.api
@@ -0,0 +1,10 @@
+syntax = "v1"
+
+type resp {
+    msg string `json:"msg"`
+}
+
+service greet {
+    @handler ping
+    post /ping returns (resp)
+}
diff --git a/tools/goctl/quickstart/idl/greet.proto b/tools/goctl/quickstart/idl/greet.proto
new file mode 100644
index 00000000..a86caa70
--- /dev/null
+++ b/tools/goctl/quickstart/idl/greet.proto
@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+package ping;
+option go_package = "./pb";
+
+message Placeholder{}
+
+service Greet {
+    rpc ping (Placeholder) returns (Placeholder);
+}
diff --git a/tools/goctl/quickstart/idl/svc.tpl b/tools/goctl/quickstart/idl/svc.tpl
new file mode 100644
index 00000000..e8c14ade
--- /dev/null
+++ b/tools/goctl/quickstart/idl/svc.tpl
@@ -0,0 +1,22 @@
+package svc
+
+import (
+    "github.com/zeromicro/go-zero/zrpc"
+    "greet/api/internal/config"
+    "greet/rpc/greet"
+)
+
+type ServiceContext struct {
+    Config   config.Config
+    GreetRpc greet.Greet
+}
+
+func NewServiceContext(c config.Config) *ServiceContext {
+    client := zrpc.MustNewClient(zrpc.RpcClientConf{
+        Target: "127.0.0.1:8080",
+    })
+    return &ServiceContext{
+        Config:   c,
+        GreetRpc: greet.NewGreet(client),
+    }
+}
diff --git a/tools/goctl/quickstart/micro.go b/tools/goctl/quickstart/micro.go
new file mode 100644
index 00000000..6122c2e3
--- /dev/null
+++ b/tools/goctl/quickstart/micro.go
@@ -0,0 +1,77 @@
+package quickstart
+
+import (
+	_ "embed"
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/core/service"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+const (
+	rpcEtcContent = `Name: greet.rpc
+ListenOn: 127.0.0.1:8080
+`
+	protoName = "greet.proto"
+)
+
+var (
+	//go:embed idl/greet.proto
+	protocContent string
+
+	zRPCWorkDir string
+)
+
+type serviceImpl struct {
+	starter func()
+}
+
+func (s serviceImpl) Start() {
+	s.starter()
+}
+
+func (s serviceImpl) Stop() {}
+
+func initRPCProto() error {
+	zRPCWorkDir = filepath.Join(projectDir, "rpc")
+	if err := pathx.MkdirIfNotExist(zRPCWorkDir); err != nil {
+		return err
+	}
+
+	protoFilename := filepath.Join(zRPCWorkDir, protoName)
+	rpcBytes := []byte(protocContent)
+	return ioutil.WriteFile(protoFilename, rpcBytes, 0666)
+}
+
+type micro struct{}
+
+func newMicroService() micro {
+	m := micro{}
+	m.mustStartRPCProject()
+	return m
+}
+
+func (m micro) mustStartRPCProject() {
+	logx.Must(initRPCProto())
+	log.Debug(">> Generating quickstart zRPC project...")
+	arg := "goctl rpc protoc " + protoName + " --go_out=. --go-grpc_out=. --zrpc_out=. --verbose"
+	execCommand(zRPCWorkDir, arg)
+	etcFile := filepath.Join(zRPCWorkDir, "etc", "greet.yaml")
+	logx.Must(ioutil.WriteFile(etcFile, []byte(rpcEtcContent), 0666))
+}
+
+func (m micro) start() {
+	mono := newMonoService(true)
+	goModTidy(projectDir)
+	sg := service.NewServiceGroup()
+	sg.Add(serviceImpl{func() {
+		log.Debug(">> Ready to start an zRPC server...")
+		goStart(zRPCWorkDir)
+	}})
+	sg.Add(serviceImpl{func() {
+		mono.start()
+	}})
+	sg.Start()
+}
diff --git a/tools/goctl/quickstart/mono.go b/tools/goctl/quickstart/mono.go
new file mode 100644
index 00000000..7a14227f
--- /dev/null
+++ b/tools/goctl/quickstart/mono.go
@@ -0,0 +1,79 @@
+package quickstart
+
+import (
+	_ "embed"
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
+	"github.com/zeromicro/go-zero/tools/goctl/util"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+var (
+	//go:embed idl/greet.api
+	apiContent string
+	//go:embed idl/svc.tpl
+	svcContent string
+	//go:embed idl/apilogic.tpl
+	apiLogicContent string
+	//go:embed idl/api.yaml
+	apiEtcContent string
+
+	apiWorkDir string
+)
+
+func initAPIFlags() error {
+	apiWorkDir = filepath.Join(projectDir, "api")
+	if err := pathx.MkdirIfNotExist(apiWorkDir); err != nil {
+		return err
+	}
+
+	apiFilename := filepath.Join(apiWorkDir, "greet.api")
+	apiBytes := []byte(apiContent)
+	if err := ioutil.WriteFile(apiFilename, apiBytes, 0666); err != nil {
+		return err
+	}
+
+	gogen.VarStringDir = apiWorkDir
+	gogen.VarStringAPI = apiFilename
+	return nil
+}
+
+type mono struct {
+	callRPC bool
+}
+
+func newMonoService(callRPC bool) mono {
+	m := mono{callRPC}
+	m.createAPIProject()
+	return m
+}
+
+func (m mono) createAPIProject() {
+	logx.Must(initAPIFlags())
+	log.Debug(">> Generating quickstart api project...")
+	logx.Must(gogen.GoCommand(nil, nil))
+	etcFile := filepath.Join(apiWorkDir, "etc", "greet.yaml")
+	logx.Must(ioutil.WriteFile(etcFile, []byte(apiEtcContent), 0666))
+	logicFile := filepath.Join(apiWorkDir, "internal", "logic", "pinglogic.go")
+
+	logx.Must(util.With("logic").Parse(apiLogicContent).SaveTo(map[string]bool{
+		"callRPC": m.callRPC,
+	}, logicFile, true))
+
+	if m.callRPC {
+		svcFile := filepath.Join(apiWorkDir, "internal", "svc", "servicecontext.go")
+		logx.Must(ioutil.WriteFile(svcFile, []byte(svcContent), 0666))
+	}
+}
+
+func (m mono) start() {
+	if !m.callRPC {
+		goModTidy(projectDir)
+	}
+	log.Debug(">> Ready to start an api server...")
+	log.Debug(">> Try to execute 'curl --request POST http://127.0.0.1:8888/ping' after service startup...")
+	goStart(apiWorkDir)
+}
diff --git a/tools/goctl/quickstart/quickstart.go b/tools/goctl/quickstart/quickstart.go
new file mode 100644
index 00000000..de4d2dcc
--- /dev/null
+++ b/tools/goctl/quickstart/quickstart.go
@@ -0,0 +1,79 @@
+package quickstart
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/console"
+	"github.com/zeromicro/go-zero/tools/goctl/util/ctx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+const baseDir = "greet"
+
+var (
+	log        = console.NewColorConsole(true)
+	projectDir string
+)
+
+func cleanWorkSpace(projectDir string) {
+	var command string
+	var breakeState bool
+	fmt.Printf("Detected that the %q already exists, do you clean up?"+
+		" [y: YES, n: NO]: ", projectDir)
+
+	for {
+		fmt.Scanln(&command)
+		switch command {
+		case "y":
+			log.Debug("Clean workspace...")
+			_ = os.RemoveAll(projectDir)
+			breakeState = true
+			break
+		case "n":
+			log.Error("User canceled")
+			os.Exit(1)
+		default:
+			fmt.Println("Invalid command, try again...")
+		}
+
+		if breakeState {
+			break
+		}
+	}
+}
+
+func initProject() {
+	wd, err := os.Getwd()
+	logx.Must(err)
+
+	projectDir = filepath.Join(wd, baseDir)
+	if exists := pathx.FileExists(projectDir); exists {
+		cleanWorkSpace(projectDir)
+	}
+
+	log.Must(pathx.MkdirIfNotExist(projectDir))
+	if hasGoMod, _ := ctx.IsGoMod(projectDir); hasGoMod {
+		return
+	}
+	if exitCode := execCommand(projectDir, "go mod init "+baseDir); exitCode != 0 {
+		log.Fatalln("Init process exit")
+	}
+}
+
+func run(_ *cobra.Command, _ []string) error {
+	initProject()
+	switch varStringServiceType {
+	case serviceTypeMono:
+		newMonoService(false).start()
+	case serviceTypeMicro:
+		newMicroService().start()
+	default:
+		return fmt.Errorf("invalid service type, expected %s | %s",
+			serviceTypeMono, serviceTypeMicro)
+	}
+	return nil
+}
diff --git a/tools/goctl/quickstart/run.go b/tools/goctl/quickstart/run.go
new file mode 100644
index 00000000..998ac1ba
--- /dev/null
+++ b/tools/goctl/quickstart/run.go
@@ -0,0 +1,35 @@
+package quickstart
+
+import (
+	"os"
+	"os/exec"
+	"runtime"
+
+	"github.com/zeromicro/go-zero/tools/goctl/vars"
+)
+
+func goStart(dir string) {
+	goproxy := "GOPROXY=https://goproxy.cn"
+	execCommand(dir, "go run .", goproxy)
+}
+
+func goModTidy(dir string) int {
+	goproxy := "GOPROXY=https://goproxy.cn"
+	log.Debug(">> go mod tidy")
+	return execCommand(dir, "go mod tidy", goproxy)
+}
+
+func execCommand(dir string, arg string, envArgs ...string) int {
+	cmd := exec.Command("sh", "-c", arg)
+	if runtime.GOOS == vars.OsWindows {
+		cmd = exec.Command("cmd.exe", "/c", arg)
+	}
+	env := append([]string(nil), os.Environ()...)
+	env = append(env, envArgs...)
+	cmd.Env = env
+	cmd.Dir = dir
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	_ = cmd.Run()
+	return cmd.ProcessState.ExitCode()
+}
diff --git a/tools/goctl/rpc/cli/zrpc.go b/tools/goctl/rpc/cli/zrpc.go
index 8610b4a5..23ff2cd2 100644
--- a/tools/goctl/rpc/cli/zrpc.go
+++ b/tools/goctl/rpc/cli/zrpc.go
@@ -20,7 +20,7 @@ var (
 
 // ZRPC generates grpc code directly by protoc and generates
 // zrpc code by goctl.
-func ZRPC(cmd *cobra.Command, args []string) error {
+func ZRPC(_ *cobra.Command, args []string) error {
 	protocArgs := wrapProtocCmd("protoc", args)
 	pwd, err := os.Getwd()
 	if err != nil {
diff --git a/tools/goctl/util/ctx/gomod.go b/tools/goctl/util/ctx/gomod.go
index 4c526b09..a5d67ba6 100644
--- a/tools/goctl/util/ctx/gomod.go
+++ b/tools/goctl/util/ctx/gomod.go
@@ -13,6 +13,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
+const goModuleWithoutGoFiles = "command-line-arguments"
+
+var errInvalidGoMod = errors.New("invalid go module")
+
 // Module contains the relative data of go module,
 // which is the result of the command go list
 type Module struct {
@@ -23,6 +27,13 @@ type Module struct {
 	GoVersion string
 }
 
+func (m *Module) validate() error {
+	if m.Path == goModuleWithoutGoFiles || m.Dir == "" {
+		return errInvalidGoMod
+	}
+	return nil
+}
+
 // projectFromGoMod is used to find the go module and project file path
 // the workDir flag specifies which folder we need to detect based on
 // only valid for go mod project
@@ -43,6 +54,9 @@ func projectFromGoMod(workDir string) (*ProjectContext, error) {
 	if err != nil {
 		return nil, err
 	}
+	if err := m.validate(); err != nil {
+		return nil, err
+	}
 
 	var ret ProjectContext
 	ret.WorkDir = workDir


Test patch:
diff --git a/core/logx/writer_test.go b/core/logx/writer_test.go
index 6d96808c..edc4ff98 100644
--- a/core/logx/writer_test.go
+++ b/core/logx/writer_test.go
@@ -124,18 +124,27 @@ func TestWritePlainAny(t *testing.T) {
 	assert.Contains(t, buf.String(), "foo")
 
 	buf.Reset()
-	writePlainAny(nil, levelInfo, make(chan int))
+	writePlainAny(nil, levelError, make(chan int))
 	assert.Contains(t, buf.String(), "unsupported type")
-	writePlainAny(nil, levelInfo, 100)
+	writePlainAny(nil, levelSlow, 100)
 	assert.Contains(t, buf.String(), "100")
 
 	buf.Reset()
-	writePlainAny(hardToWriteWriter{}, levelInfo, 100)
+	writePlainAny(hardToWriteWriter{}, levelStat, 100)
 	assert.Contains(t, buf.String(), "write error")
 
 	buf.Reset()
-	writePlainAny(hardToWriteWriter{}, levelInfo, "foo")
+	writePlainAny(hardToWriteWriter{}, levelSevere, "foo")
 	assert.Contains(t, buf.String(), "write error")
+
+	buf.Reset()
+	writePlainAny(hardToWriteWriter{}, levelAlert, "foo")
+	assert.Contains(t, buf.String(), "write error")
+
+	buf.Reset()
+	writePlainAny(hardToWriteWriter{}, levelFatal, "foo")
+	assert.Contains(t, buf.String(), "write error")
+
 }
 
 type mockedEntry struct {
diff --git a/core/stores/mon/collection_test.go b/core/stores/mon/collection_test.go
index 3b39b373..4e2e9d1d 100644
--- a/core/stores/mon/collection_test.go
+++ b/core/stores/mon/collection_test.go
@@ -588,21 +588,21 @@ func Test_DecoratedCollectionLogDuration(t *testing.T) {
 	}()
 
 	buf.Reset()
-	c.logDuration("foo", time.Millisecond, nil, "bar")
+	c.logDuration(context.Background(), "foo", time.Millisecond, nil, "bar")
 	assert.Contains(t, buf.String(), "foo")
 	assert.Contains(t, buf.String(), "bar")
 
 	buf.Reset()
-	c.logDuration("foo", time.Millisecond, errors.New("bar"), make(chan int))
+	c.logDuration(context.Background(), "foo", time.Millisecond, errors.New("bar"), make(chan int))
 	assert.Contains(t, buf.String(), "bar")
 
 	buf.Reset()
-	c.logDuration("foo", slowThreshold.Load()+time.Millisecond, errors.New("bar"))
+	c.logDuration(context.Background(), "foo", slowThreshold.Load()+time.Millisecond, errors.New("bar"))
 	assert.Contains(t, buf.String(), "foo")
 	assert.Contains(t, buf.String(), "slowcall")
 
 	buf.Reset()
-	c.logDuration("foo", slowThreshold.Load()+time.Millisecond, nil)
+	c.logDuration(context.Background(), "foo", slowThreshold.Load()+time.Millisecond, nil)
 	assert.Contains(t, buf.String(), "foo")
 	assert.Contains(t, buf.String(), "slowcall")
 }
@@ -630,15 +630,15 @@ func (d *dropBreaker) Allow() (breaker.Promise, error) {
 	return nil, errDummy
 }
 
-func (d *dropBreaker) Do(req func() error) error {
+func (d *dropBreaker) Do(_ func() error) error {
 	return nil
 }
 
-func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error {
+func (d *dropBreaker) DoWithAcceptable(_ func() error, _ breaker.Acceptable) error {
 	return errDummy
 }
 
-func (d *dropBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
+func (d *dropBreaker) DoWithFallback(_ func() error, _ func(err error) error) error {
 	return nil
 }
 
diff --git a/core/stores/mon/util_test.go b/core/stores/mon/util_test.go
index c23a7933..b042f854 100644
--- a/core/stores/mon/util_test.go
+++ b/core/stores/mon/util_test.go
@@ -1,6 +1,7 @@
 package mon
 
 import (
+	"context"
 	"errors"
 	"strings"
 	"testing"
@@ -50,12 +51,12 @@ func Test_logDuration(t *testing.T) {
 	}()
 
 	buf.Reset()
-	logDuration("foo", "bar", time.Millisecond, nil)
+	logDuration(context.Background(), "foo", "bar", time.Millisecond, nil)
 	assert.Contains(t, buf.String(), "foo")
 	assert.Contains(t, buf.String(), "bar")
 
 	buf.Reset()
-	logDuration("foo", "bar", time.Millisecond, errors.New("bar"))
+	logDuration(context.Background(), "foo", "bar", time.Millisecond, errors.New("bar"))
 	assert.Contains(t, buf.String(), "foo")
 	assert.Contains(t, buf.String(), "bar")
 	assert.Contains(t, buf.String(), "fail")
diff --git a/core/stores/redis/hook_test.go b/core/stores/redis/hook_test.go
index 2c634f61..f8480a93 100644
--- a/core/stores/redis/hook_test.go
+++ b/core/stores/redis/hook_test.go
@@ -27,7 +27,7 @@ func TestHookProcessCase1(t *testing.T) {
 	log.SetOutput(&buf)
 	defer log.SetOutput(writer)
 
-	ctx, err := durationHook.BeforeProcess(context.Background(), nil)
+	ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -48,7 +48,7 @@ func TestHookProcessCase2(t *testing.T) {
 	w, restore := injectLog()
 	defer restore()
 
-	ctx, err := durationHook.BeforeProcess(context.Background(), nil)
+	ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -89,7 +89,7 @@ func TestHookProcessPipelineCase1(t *testing.T) {
 	log.SetOutput(&buf)
 	defer log.SetOutput(writer)
 
-	ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil)
+	ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -112,7 +112,7 @@ func TestHookProcessPipelineCase2(t *testing.T) {
 	w, restore := injectLog()
 	defer restore()
 
-	ctx, err := durationHook.BeforeProcessPipeline(context.Background(), nil)
+	ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{red.NewCmd(context.Background())})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -156,7 +156,7 @@ func TestHookProcessPipelineCase5(t *testing.T) {
 	defer log.SetOutput(writer)
 
 	ctx := context.WithValue(context.Background(), startTimeKey, "foo")
-	assert.Nil(t, durationHook.AfterProcessPipeline(ctx, nil))
+	assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{red.NewCmd(context.Background())}))
 	assert.True(t, buf.Len() == 0)
 }
 
diff --git a/rest/handler/loghandler_test.go b/rest/handler/loghandler_test.go
index aa96d5a4..25019dbf 100644
--- a/rest/handler/loghandler_test.go
+++ b/rest/handler/loghandler_test.go
@@ -1,6 +1,8 @@
 package handler
 
 import (
+	"bytes"
+	"io"
 	"io/ioutil"
 	"log"
 	"net/http"
@@ -44,6 +46,33 @@ func TestLogHandler(t *testing.T) {
 	}
 }
 
+func TestLogHandlerVeryLong(t *testing.T) {
+	var buf bytes.Buffer
+	for i := 0; i < limitBodyBytes<<1; i++ {
+		buf.WriteByte('a')
+	}
+
+	req := httptest.NewRequest(http.MethodPost, "http://localhost", &buf)
+	handler := LogHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		r.Context().Value(internal.LogContext).(*internal.LogCollector).Append("anything")
+		io.Copy(ioutil.Discard, r.Body)
+		w.Header().Set("X-Test", "test")
+		w.WriteHeader(http.StatusServiceUnavailable)
+		_, err := w.Write([]byte("content"))
+		assert.Nil(t, err)
+
+		flusher, ok := w.(http.Flusher)
+		assert.True(t, ok)
+		flusher.Flush()
+	}))
+
+	resp := httptest.NewRecorder()
+	handler.ServeHTTP(resp, req)
+	assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
+	assert.Equal(t, "test", resp.Header().Get("X-Test"))
+	assert.Equal(t, "content", resp.Body.String())
+}
+
 func TestLogHandlerSlow(t *testing.T) {
 	handlers := []func(handler http.Handler) http.Handler{
 		LogHandler,
@@ -106,6 +135,28 @@ func TestSetSlowThreshold(t *testing.T) {
 	assert.Equal(t, time.Second, slowThreshold.Load())
 }
 
+func TestWrapMethodWithColor(t *testing.T) {
+	// no tty
+	assert.Equal(t, http.MethodGet, wrapMethod(http.MethodGet))
+	assert.Equal(t, http.MethodPost, wrapMethod(http.MethodPost))
+	assert.Equal(t, http.MethodPut, wrapMethod(http.MethodPut))
+	assert.Equal(t, http.MethodDelete, wrapMethod(http.MethodDelete))
+	assert.Equal(t, http.MethodPatch, wrapMethod(http.MethodPatch))
+	assert.Equal(t, http.MethodHead, wrapMethod(http.MethodHead))
+	assert.Equal(t, http.MethodOptions, wrapMethod(http.MethodOptions))
+	assert.Equal(t, http.MethodConnect, wrapMethod(http.MethodConnect))
+	assert.Equal(t, http.MethodTrace, wrapMethod(http.MethodTrace))
+}
+
+func TestWrapStatusCodeWithColor(t *testing.T) {
+	// no tty
+	assert.Equal(t, "200", wrapStatusCode(http.StatusOK))
+	assert.Equal(t, "302", wrapStatusCode(http.StatusFound))
+	assert.Equal(t, "404", wrapStatusCode(http.StatusNotFound))
+	assert.Equal(t, "500", wrapStatusCode(http.StatusInternalServerError))
+	assert.Equal(t, "503", wrapStatusCode(http.StatusServiceUnavailable))
+}
+
 func BenchmarkLogHandler(b *testing.B) {
 	b.ReportAllocs()
 
diff --git a/tools/goctl/rpc/generator/template_test.go b/tools/goctl/rpc/generator/template_test.go
index 75a74a72..7850ee60 100644
--- a/tools/goctl/rpc/generator/template_test.go
+++ b/tools/goctl/rpc/generator/template_test.go
@@ -12,13 +12,13 @@ import (
 
 func TestGenTemplates(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 }
 
 func TestRevertTemplate(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 	fp, err := pathx.GetTemplateDir(category)
 	if err != nil {
@@ -59,7 +59,7 @@ func TestRevertTemplate(t *testing.T) {
 
 func TestClean(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 	fp, err := pathx.GetTemplateDir(category)
 	if err != nil {
@@ -78,7 +78,7 @@ func TestClean(t *testing.T) {
 
 func TestUpdate(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 	fp, err := pathx.GetTemplateDir(category)
 	if err != nil {