mirror of
				https://github.com/usememos/memos.git
				synced 2025-10-31 08:46:39 +08:00 
			
		
		
		
	feat: add metric plugin (#361)
This commit is contained in:
		
							parent
							
								
									30daea0c4f
								
							
						
					
					
						commit
						95376f78f6
					
				
					 13 changed files with 157 additions and 5 deletions
				
			
		|  | @ -8,6 +8,7 @@ import ( | |||
| 	"context" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 	"github.com/usememos/memos/server" | ||||
| 	"github.com/usememos/memos/server/profile" | ||||
| 	"github.com/usememos/memos/store" | ||||
|  | @ -34,15 +35,20 @@ func run(profile *profile.Profile) error { | |||
| 		return fmt.Errorf("cannot open db: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	s := server.NewServer(profile) | ||||
| 
 | ||||
| 	serverInstance := server.NewServer(profile) | ||||
| 	storeInstance := store.New(db.Db, profile) | ||||
| 	s.Store = storeInstance | ||||
| 	serverInstance.Store = storeInstance | ||||
| 
 | ||||
| 	metricCollector := server.NewMetricCollector(profile, storeInstance) | ||||
| 	serverInstance.Collector = &metricCollector | ||||
| 
 | ||||
| 	println(greetingBanner) | ||||
| 	fmt.Printf("Version %s has started at :%d\n", profile.Version, profile.Port) | ||||
| 	metricCollector.Collect(ctx, &metric.Metric{ | ||||
| 		Name: "servive started", | ||||
| 	}) | ||||
| 
 | ||||
| 	return s.Run() | ||||
| 	return serverInstance.Run() | ||||
| } | ||||
| 
 | ||||
| func execute() error { | ||||
|  |  | |||
							
								
								
									
										9
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -33,8 +33,15 @@ require ( | |||
| 	github.com/labstack/echo-contrib v0.13.0 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect | ||||
| 	github.com/kr/pretty v0.3.1 // indirect | ||||
| 	github.com/segmentio/backo-go v1.0.1 // indirect | ||||
| 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||||
| 	github.com/golang/snappy v0.0.4 // indirect | ||||
| 	github.com/kr/pretty v0.3.1 // indirect | ||||
| 	github.com/segmentio/analytics-go v3.1.0+incompatible | ||||
| ) | ||||
|  |  | |||
							
								
								
									
										8
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
										
									
									
									
								
							|  | @ -52,6 +52,8 @@ github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhpl | |||
| 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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||||
| github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= | ||||
| github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= | ||||
| github.com/casbin/casbin/v2 v2.51.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
| github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
|  | @ -277,6 +279,10 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR | |||
| github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | ||||
| github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||
| github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/segmentio/analytics-go v3.1.0+incompatible h1:IyiOfUgQFVHvsykKKbdI7ZsH374uv3/DfZUo9+G0Z80= | ||||
| github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= | ||||
| github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4= | ||||
| github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= | ||||
| github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||||
| github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||||
| github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||||
|  | @ -301,6 +307,8 @@ github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ | |||
| github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= | ||||
| github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= | ||||
| github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= | ||||
| github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= | ||||
| github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= | ||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
|  |  | |||
							
								
								
									
										6
									
								
								plugin/metrics/collector.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								plugin/metrics/collector.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| package metric | ||||
| 
 | ||||
| // Collector is the interface definition for metric collector. | ||||
| type Collector interface { | ||||
| 	Collect(metric *Metric) error | ||||
| } | ||||
							
								
								
									
										7
									
								
								plugin/metrics/metric.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								plugin/metrics/metric.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| package metric | ||||
| 
 | ||||
| // Metric is the API message for metric. | ||||
| type Metric struct { | ||||
| 	Name   string | ||||
| 	Labels map[string]string | ||||
| } | ||||
							
								
								
									
										40
									
								
								plugin/metrics/segment/collector.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								plugin/metrics/segment/collector.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| package segment | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/segmentio/analytics-go" | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| ) | ||||
| 
 | ||||
| var _ metric.Collector = (*collector)(nil) | ||||
| 
 | ||||
| // collector is the metrics collector https://segment.com/. | ||||
| type collector struct { | ||||
| 	client analytics.Client | ||||
| } | ||||
| 
 | ||||
| // NewCollector creates a new instance of segment. | ||||
| func NewCollector(key string) metric.Collector { | ||||
| 	client := analytics.New(key) | ||||
| 
 | ||||
| 	return &collector{ | ||||
| 		client: client, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Collect will exec all the segment collector. | ||||
| func (c *collector) Collect(metric *metric.Metric) error { | ||||
| 	properties := analytics.NewProperties() | ||||
| 	for key, value := range metric.Labels { | ||||
| 		properties.Set(key, value) | ||||
| 	} | ||||
| 
 | ||||
| 	return c.client.Enqueue(analytics.Track{ | ||||
| 		Event:       string(metric.Name), | ||||
| 		AnonymousId: uuid.NewString(), | ||||
| 		Properties:  properties, | ||||
| 		Timestamp:   time.Now().UTC(), | ||||
| 	}) | ||||
| } | ||||
|  | @ -7,6 +7,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/usememos/memos/api" | ||||
| 	"github.com/usememos/memos/common" | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 
 | ||||
| 	"github.com/labstack/echo/v4" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
|  | @ -42,6 +43,9 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { | |||
| 		if err = setUserSession(c, user); err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to set signin session").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "user signed in", | ||||
| 		}) | ||||
| 
 | ||||
| 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) | ||||
| 		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil { | ||||
|  | @ -51,10 +55,14 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { | |||
| 	}) | ||||
| 
 | ||||
| 	g.POST("/auth/logout", func(c echo.Context) error { | ||||
| 		ctx := c.Request().Context() | ||||
| 		err := removeUserSession(c) | ||||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to set logout session").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "user logout", | ||||
| 		}) | ||||
| 
 | ||||
| 		c.Response().WriteHeader(http.StatusOK) | ||||
| 		return nil | ||||
|  | @ -102,6 +110,9 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { | |||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "user signed up", | ||||
| 		}) | ||||
| 
 | ||||
| 		err = setUserSession(c, user) | ||||
| 		if err != nil { | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/usememos/memos/api" | ||||
| 	"github.com/usememos/memos/common" | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 
 | ||||
| 	"github.com/labstack/echo/v4" | ||||
| ) | ||||
|  | @ -60,6 +61,9 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { | |||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create memo").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "memo created", | ||||
| 		}) | ||||
| 
 | ||||
| 		for _, resourceID := range memoCreate.ResourceIDList { | ||||
| 			if _, err := s.Store.UpsertMemoResource(ctx, &api.MemoResourceUpsert{ | ||||
|  |  | |||
							
								
								
									
										49
									
								
								server/metric_collector.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								server/metric_collector.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| package server | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 	"github.com/usememos/memos/plugin/metrics/segment" | ||||
| 	"github.com/usememos/memos/server/profile" | ||||
| 	"github.com/usememos/memos/server/version" | ||||
| 	"github.com/usememos/memos/store" | ||||
| ) | ||||
| 
 | ||||
| // MetricCollector is the metric collector. | ||||
| type MetricCollector struct { | ||||
| 	collector metric.Collector | ||||
| 	profile   *profile.Profile | ||||
| 	store     *store.Store | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	segmentMetricWriteKey = "FqYUl1CmssHytFSnnVd0efV4gyGeH0dx" | ||||
| ) | ||||
| 
 | ||||
| func NewMetricCollector(profile *profile.Profile, store *store.Store) MetricCollector { | ||||
| 	c := segment.NewCollector(segmentMetricWriteKey) | ||||
| 
 | ||||
| 	return MetricCollector{ | ||||
| 		collector: c, | ||||
| 		profile:   profile, | ||||
| 		store:     store, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (mc *MetricCollector) Collect(_ context.Context, metric *metric.Metric) { | ||||
| 	if mc.profile.Mode == "dev" { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if metric.Labels == nil { | ||||
| 		metric.Labels = map[string]string{} | ||||
| 	} | ||||
| 	metric.Labels["version"] = version.GetCurrentVersion(mc.profile.Mode) | ||||
| 
 | ||||
| 	err := mc.collector.Collect(metric) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Failed to request segment, error: %+v\n", err) | ||||
| 	} | ||||
| } | ||||
|  | @ -10,6 +10,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/usememos/memos/api" | ||||
| 	"github.com/usememos/memos/common" | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 
 | ||||
| 	"github.com/labstack/echo/v4" | ||||
| ) | ||||
|  | @ -58,6 +59,9 @@ func (s *Server) registerResourceRoutes(g *echo.Group) { | |||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "resource created", | ||||
| 		}) | ||||
| 
 | ||||
| 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) | ||||
| 		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(resource)); err != nil { | ||||
|  |  | |||
|  | @ -17,6 +17,8 @@ import ( | |||
| type Server struct { | ||||
| 	e *echo.Echo | ||||
| 
 | ||||
| 	Collector *MetricCollector | ||||
| 
 | ||||
| 	Profile *profile.Profile | ||||
| 
 | ||||
| 	Store *store.Store | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/usememos/memos/api" | ||||
| 	"github.com/usememos/memos/common" | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 
 | ||||
| 	"github.com/labstack/echo/v4" | ||||
| ) | ||||
|  | @ -31,6 +32,9 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) { | |||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "shortcut created", | ||||
| 		}) | ||||
| 
 | ||||
| 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) | ||||
| 		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(shortcut)); err != nil { | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/usememos/memos/api" | ||||
| 	"github.com/usememos/memos/common" | ||||
| 	metric "github.com/usememos/memos/plugin/metrics" | ||||
| 
 | ||||
| 	"github.com/labstack/echo/v4" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
|  | @ -52,6 +53,9 @@ func (s *Server) registerUserRoutes(g *echo.Group) { | |||
| 		if err != nil { | ||||
| 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err) | ||||
| 		} | ||||
| 		s.Collector.Collect(ctx, &metric.Metric{ | ||||
| 			Name: "user created", | ||||
| 		}) | ||||
| 
 | ||||
| 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) | ||||
| 		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil { | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue