github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/server/query/server.go (about)

     1  /*
     2  Copyright 2023.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package queryserver
    18  
    19  import (
    20  	"crypto/tls"
    21  	"fmt"
    22  	htmltemplate "html/template"
    23  	"net"
    24  	texttemplate "text/template"
    25  	"time"
    26  
    27  	"github.com/fasthttp/router"
    28  	"github.com/oklog/run"
    29  	"github.com/siglens/siglens/pkg/alerts/alertsHandler"
    30  	"github.com/siglens/siglens/pkg/config"
    31  	"github.com/siglens/siglens/pkg/hooks"
    32  	"github.com/siglens/siglens/pkg/segment/query"
    33  	server_utils "github.com/siglens/siglens/pkg/server/utils"
    34  	"github.com/siglens/siglens/pkg/utils"
    35  	log "github.com/sirupsen/logrus"
    36  	"github.com/valyala/fasthttp"
    37  	"github.com/valyala/fasthttp/pprofhandler"
    38  )
    39  
    40  type queryserverCfg struct {
    41  	Config config.WebConfig
    42  	Addr   string
    43  	//	Log    *zap.Logger //ToDo implement debug logger
    44  	ln     net.Listener
    45  	lnTls  net.Listener
    46  	Router *router.Router
    47  	debug  bool
    48  }
    49  
    50  var (
    51  	corsAllowHeaders = "Access-Control-Allow-Origin, Access-Control-Request-Method, Access-Control-Allow-Methods, Access-Control-Max-Age, Content-Type, Authorization, Origin, X-Requested-With , Accept"
    52  	corsAllowMethods = "HEAD,GET,POST,PUT,DELETE,OPTIONS,UPGRADE"
    53  	corsAllowOrigin  = "*"
    54  )
    55  
    56  // ConstructHttpServer new fasthttp server
    57  func ConstructQueryServer(cfg config.WebConfig, ServerAddr string) *queryserverCfg {
    58  
    59  	s := &queryserverCfg{
    60  		Config: cfg,
    61  		Addr:   ServerAddr,
    62  		Router: router.New(),
    63  		debug:  true,
    64  	}
    65  	return s
    66  }
    67  
    68  func (hs *queryserverCfg) Close() {
    69  	_ = hs.ln.Close()
    70  }
    71  
    72  func getMyIds() []uint64 {
    73  	myids := make([]uint64, 1)
    74  
    75  	alreadyHandled := false
    76  	if hook := hooks.GlobalHooks.GetIdsConditionHook; hook != nil {
    77  		alreadyHandled = hook(myids)
    78  	}
    79  
    80  	if !alreadyHandled {
    81  		myids[0] = 0
    82  	}
    83  
    84  	return myids
    85  }
    86  
    87  func (hs *queryserverCfg) Run(htmlTemplate *htmltemplate.Template, textTemplate *texttemplate.Template) error {
    88  	query.InitQueryMetrics()
    89  
    90  	alertsHandler.InitAlertingService(getMyIds)
    91  	alertsHandler.InitMinionSearchService(getMyIds)
    92  
    93  	err := query.InitQueryNode(getMyIds, server_utils.ExtractKibanaRequests)
    94  	if err != nil {
    95  		log.Errorf("Failed to initialize query node: %v", err)
    96  		return err
    97  	}
    98  
    99  	hs.Router.GET("/{filename}.html", func(ctx *fasthttp.RequestCtx) {
   100  		renderHtmlTemplate(ctx, htmlTemplate)
   101  	})
   102  	hs.Router.GET("/js/{filename}.js", func(ctx *fasthttp.RequestCtx) {
   103  		renderJavaScriptTemplate(ctx, textTemplate)
   104  	})
   105  	hs.Router.GET(server_utils.API_PREFIX+"/search/live_tail", hs.Recovery(liveTailHandler()))
   106  	hs.Router.POST(server_utils.API_PREFIX+"/search/live_tail", hs.Recovery(liveTailHandler()))
   107  	hs.Router.POST(server_utils.API_PREFIX+"/search", hs.Recovery(pipeSearchHandler()))
   108  	hs.Router.POST(server_utils.API_PREFIX+"/search/{dbPanel-id}", hs.Recovery(dashboardPipeSearchHandler()))
   109  	hs.Router.GET(server_utils.API_PREFIX+"/search/ws", hs.Recovery(pipeSearchWebsocketHandler()))
   110  
   111  	hs.Router.POST(server_utils.API_PREFIX+"/search/ws", hs.Recovery(pipeSearchWebsocketHandler()))
   112  	hs.Router.POST(server_utils.API_PREFIX+"/sampledataset_bulk", hs.Recovery(sampleDatasetBulkHandler()))
   113  
   114  	// common routes
   115  
   116  	hs.Router.GET(server_utils.API_PREFIX+"/health", hs.Recovery(getHealthHandler()))
   117  	hs.Router.POST(server_utils.API_PREFIX+"/setconfig/transient", hs.Recovery(postSetconfigHandler(false)))
   118  	hs.Router.POST(server_utils.API_PREFIX+"/setconfig/persistent", hs.Recovery(postSetconfigHandler(true)))
   119  	hs.Router.GET(server_utils.API_PREFIX+"/config", hs.Recovery(getConfigHandler()))
   120  	hs.Router.POST(server_utils.API_PREFIX+"/config/reload", hs.Recovery(getConfigReloadHandler()))
   121  
   122  	//elasticsearch routes - common to both ingest and query
   123  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/", hs.Recovery(esGreetHandler()))
   124  
   125  	//elasticsearch routes - specific to query
   126  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/search", hs.Recovery(esGetSearchHandler()))
   127  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/_search", hs.Recovery(esGetSearchHandler()))
   128  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/{indexName}/_search", hs.Recovery(esGetSearchHandler()))
   129  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/_search", hs.Recovery(esGetSearchHandler()))
   130  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/{indexName}/_search", hs.Recovery(esGetSearchHandler()))
   131  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/{indexName}/_doc/_search", hs.Recovery(esGetSearchHandler()))
   132  
   133  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/{indexName}/{docType}/_search", hs.Recovery(esGetSearchHandler()))
   134  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/{indexName}/{docType}/_search", hs.Recovery(esGetSearchHandler()))
   135  
   136  	hs.Router.HEAD(server_utils.ELASTIC_PREFIX+"/", hs.Recovery(headHandler()))
   137  
   138  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/{indexName}/{docType}/{idVal}", hs.Recovery(esGetSingleDocHandler()))
   139  	hs.Router.HEAD(server_utils.ELASTIC_PREFIX+"/{indexName}/{docType}/{idVal}", hs.Recovery(esGetSingleDocHandler()))
   140  
   141  	// aliases api
   142  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/{indexName}/_alias/{aliasName}", hs.Recovery(esGetIndexAliasesHandler()))
   143  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/_alias/{aliasName}", hs.Recovery(esGetAliasHandler()))
   144  	hs.Router.HEAD(server_utils.ELASTIC_PREFIX+"/_alias/{aliasName}", hs.Recovery(esGetAliasHandler()))
   145  	hs.Router.HEAD(server_utils.ELASTIC_PREFIX+"/{indexName}/_alias/{aliasName?}", hs.Recovery(esGetIndexAliasesHandler()))
   146  
   147  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/_aliases", hs.Recovery(esPostAliasesHandler()))
   148  
   149  	hs.Router.PUT(server_utils.ELASTIC_PREFIX+"/{indexName}/_alias/{aliasName}", hs.Recovery(esPutIndexAliasHandler()))
   150  	hs.Router.PUT(server_utils.ELASTIC_PREFIX+"/{indexName}/_aliases/{aliasName}", hs.Recovery(esPutIndexAliasHandler()))
   151  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/{indexName}/_alias/{aliasName}", hs.Recovery(esPutIndexAliasHandler()))
   152  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/{indexName}/_aliases/{aliasName}", hs.Recovery(esPutIndexAliasHandler()))
   153  
   154  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/_aliases", hs.Recovery(esGetAllAliasesHandler()))
   155  	hs.Router.GET(server_utils.ELASTIC_PREFIX+"/_cat/aliases", hs.Recovery(esGetAllAliasesHandler()))
   156  
   157  	hs.Router.HEAD(server_utils.ELASTIC_PREFIX+"/{indexName}", hs.Recovery(esGetIndexAliasExistsHandler()))
   158  	/*
   159  		hs.router.DELETE(ELASTIC_PREFIX+"/{indexName}/_alias/{aliasName}", hs.Recovery(esDeleteAliasHandler()))
   160  	*/
   161  
   162  	//loki endpoint
   163  	hs.Router.GET(server_utils.LOKI_PREFIX+"/api/v1/labels", hs.Recovery(lokiLabelsHandler()))
   164  	hs.Router.GET(server_utils.LOKI_PREFIX+"/api/v1/label/{labelName}/values", hs.Recovery(lokiLabelValueHandler()))
   165  	hs.Router.GET(server_utils.LOKI_PREFIX+"/api/v1/query", hs.Recovery(lokiQueryHandler()))
   166  	hs.Router.GET(server_utils.LOKI_PREFIX+"/api/v1/query_range", hs.Recovery(lokiQueryHandler()))
   167  	hs.Router.GET(server_utils.LOKI_PREFIX+"/api/v1/index/stats", hs.Recovery(lokiIndexStatsHandler()))
   168  	hs.Router.GET(server_utils.LOKI_PREFIX+"/api/v1/series", hs.Recovery(lokiSeriesHandler()))
   169  	hs.Router.POST(server_utils.LOKI_PREFIX+"/api/v1/series", hs.Recovery(lokiSeriesHandler()))
   170  
   171  	//splunk endpoint
   172  	hs.Router.GET("/services/collector/health", hs.Recovery(getHealthHandler()))
   173  	hs.Router.GET("/services/collector/health/1.0", hs.Recovery(getHealthHandler()))
   174  
   175  	//OTSDB query endpoint
   176  	hs.Router.GET(server_utils.OTSDB_PREFIX+"/api/query", hs.Recovery(otsdbMetricQueryHandler()))
   177  	hs.Router.POST(server_utils.OTSDB_PREFIX+"/api/query", hs.Recovery(otsdbMetricQueryHandler()))
   178  	hs.Router.POST(server_utils.OTSDB_PREFIX+"/api/v1/query/exp", hs.Recovery(otsdbMetricQueryExpHandler()))
   179  
   180  	//prometheus query endpoint
   181  	hs.Router.POST(server_utils.PROMQL_PREFIX+"/api/v1/query", hs.Recovery(metricsSearchHandler()))
   182  	hs.Router.GET(server_utils.PROMQL_PREFIX+"/api/v1/query", hs.Recovery(metricsSearchHandler()))
   183  	hs.Router.POST(server_utils.PROMQL_PREFIX+"/api/ui/query", hs.Recovery(uiMetricsSearchHandler()))
   184  
   185  	// search api Handlers
   186  	hs.Router.POST(server_utils.API_PREFIX+"/echo", hs.Recovery(pipeSearchHandler()))
   187  	hs.Router.GET(server_utils.API_PREFIX+"/listIndices", hs.Recovery(listIndicesHandler()))
   188  	hs.Router.GET(server_utils.API_PREFIX+"/clusterStats", hs.Recovery(getClusterStatsHandler()))
   189  	hs.Router.POST(server_utils.API_PREFIX+"/clusterIngestStats", hs.Recovery(getClusterIngestStatsHandler()))
   190  	hs.Router.POST(server_utils.API_PREFIX+"/usersavedqueries/save", hs.Recovery(saveUserSavedQueriesHandler()))
   191  	hs.Router.GET(server_utils.API_PREFIX+"/usersavedqueries/getall", hs.Recovery(getUserSavedQueriesAllHandler()))
   192  	hs.Router.GET(server_utils.API_PREFIX+"/usersavedqueries/deleteone/{qname}", hs.Recovery(deleteUserSavedQueryHandler()))
   193  	hs.Router.GET(server_utils.API_PREFIX+"/usersavedqueries/{qname}", hs.Recovery(SearchUserSavedQueryHandler()))
   194  	hs.Router.GET(server_utils.API_PREFIX+"/pqs/clear", hs.Recovery(postPqsClearHandler()))
   195  	hs.Router.GET(server_utils.API_PREFIX+"/pqs/get", hs.Recovery(getPqsEnabledHandler()))
   196  	hs.Router.POST(server_utils.API_PREFIX+"/pqs/aggs", hs.Recovery(postPqsAggColsHandler()))
   197  	hs.Router.POST(server_utils.API_PREFIX+"/pqs/update", hs.Recovery(postPqsHandler()))
   198  	hs.Router.GET(server_utils.API_PREFIX+"/pqs", hs.Recovery(getPqsHandler()))
   199  	hs.Router.GET(server_utils.API_PREFIX+"/pqs/{pqid}", hs.Recovery(getPqsByIdHandler()))
   200  	hs.Router.POST(server_utils.API_PREFIX+"/dashboards/create", hs.Recovery(createDashboardHandler()))
   201  	hs.Router.GET(server_utils.API_PREFIX+"/dashboards/defaultlistall", hs.Recovery(getDefaultDashboardIdsHandler()))
   202  	hs.Router.GET(server_utils.API_PREFIX+"/dashboards/listall", hs.Recovery(getDashboardIdsHandler()))
   203  	hs.Router.POST(server_utils.API_PREFIX+"/dashboards/update", hs.Recovery(updateDashboardHandler()))
   204  	hs.Router.GET(server_utils.API_PREFIX+"/dashboards/{dashboard-id}", hs.Recovery(getDashboardIdHandler()))
   205  	hs.Router.GET(server_utils.API_PREFIX+"/dashboards/delete/{dashboard-id}", hs.Recovery(deleteDashboardHandler()))
   206  	hs.Router.PUT(server_utils.API_PREFIX+"/dashboards/favorite/{dashboard-id}", hs.Recovery(favoriteDashboardHandler()))
   207  	hs.Router.GET(server_utils.API_PREFIX+"/dashboards/listfavorites", hs.Recovery(getFavoriteDashboardIdsHandler()))
   208  	hs.Router.GET(server_utils.API_PREFIX+"/version/info", hs.Recovery(getVersionHandler()))
   209  
   210  	// alerting api endpoints
   211  	hs.Router.POST(server_utils.API_PREFIX+"/alerts/create", hs.Recovery(createAlertHandler()))
   212  	hs.Router.GET(server_utils.API_PREFIX+"/alerts/{alertID}", hs.Recovery(getAlertHandler()))
   213  	hs.Router.GET(server_utils.API_PREFIX+"/allalerts", hs.Recovery(getAllAlertsHandler()))
   214  	hs.Router.POST(server_utils.API_PREFIX+"/alerts/update", hs.Recovery(updateAlertHandler()))
   215  	hs.Router.DELETE(server_utils.API_PREFIX+"/alerts/delete", hs.Recovery(deleteAlertHandler()))
   216  	hs.Router.GET(server_utils.API_PREFIX+"/alerts/{alertID}/history", hs.Recovery(alertHistoryHandler()))
   217  	hs.Router.POST(server_utils.API_PREFIX+"/alerts/createContact", hs.Recovery(createContactHandler()))
   218  	hs.Router.GET(server_utils.API_PREFIX+"/alerts/allContacts", hs.Recovery(getAllContactsHandler()))
   219  	hs.Router.POST(server_utils.API_PREFIX+"/alerts/updateContact", hs.Recovery(updateContactHandler()))
   220  	hs.Router.DELETE(server_utils.API_PREFIX+"/alerts/deleteContact", hs.Recovery(deleteContactHandler()))
   221  	hs.Router.PUT(server_utils.API_PREFIX+"/alerts/silenceAlert", hs.Recovery(silenceAlertHandler()))
   222  
   223  	hs.Router.GET(server_utils.API_PREFIX+"/minionsearch/allMinionSearches", hs.Recovery(getAllMinionSearchesHandler()))
   224  	hs.Router.POST(server_utils.API_PREFIX+"/minionsearch/createMinionSearches", hs.Recovery(createMinionSearchHandler()))
   225  	hs.Router.GET(server_utils.API_PREFIX+"/minionsearch/{alertID}", hs.Recovery(getMinionSearchHandler()))
   226  
   227  	// tracing api endpoints
   228  	hs.Router.POST(server_utils.API_PREFIX+"/traces/search", hs.Recovery(searchTracesHandler()))
   229  	hs.Router.GET(server_utils.API_PREFIX+"/traces/dependencies", hs.Recovery(getDependencyGraphHandler()))
   230  	hs.Router.POST(server_utils.API_PREFIX+"/traces/ganttChart", hs.Recovery(ganttChartHandler()))
   231  	hs.Router.POST(server_utils.API_PREFIX+"/traces/count", hs.Recovery((totalTracesHandler())))
   232  	// query server should still setup ES APIs for Kibana integration
   233  	hs.Router.POST(server_utils.ELASTIC_PREFIX+"/_bulk", hs.Recovery(esPostBulkHandler()))
   234  	hs.Router.PUT(server_utils.ELASTIC_PREFIX+"/{indexName}", hs.Recovery(esPutIndexHandler()))
   235  
   236  	hs.Router.GET(server_utils.API_PREFIX+"/system-info", hs.Recovery(getSystemInfoHandler()))
   237  	if config.IsDebugMode() {
   238  		hs.Router.GET("/debug/pprof/{profile:*}", pprofhandler.PprofHandler)
   239  	}
   240  
   241  	if hook := hooks.GlobalHooks.ExtraQueryEndpointsHook; hook != nil {
   242  		err := hook(hs.Router, hs.Recovery)
   243  		if err != nil {
   244  			log.Errorf("Run: error in ExtraQueryEndpointsHook: %v", err)
   245  			return err
   246  		}
   247  	}
   248  
   249  	if hook := hooks.GlobalHooks.ServeStaticHook; hook != nil {
   250  		hook(hs.Router, htmlTemplate)
   251  	} else {
   252  		hook = func(router *router.Router, htmlTemplate *htmltemplate.Template) {
   253  			router.GET("/{filepath:*}", func(ctx *fasthttp.RequestCtx) {
   254  				filepath := ctx.UserValue("filepath").(string)
   255  				if filepath == "" {
   256  					// Render index.html and send that.
   257  					ctx.Response.Header.Set("Content-Type", "text/html; charset=utf-8")
   258  					err := htmlTemplate.ExecuteTemplate(ctx, "index.html", hooks.GlobalHooks.HtmlSnippets)
   259  					if err != nil {
   260  						log.Fatalf("serveStatic: error executing index.html template: %v", err)
   261  					}
   262  
   263  					return
   264  				}
   265  
   266  				fasthttp.ServeFile(ctx, "static/"+filepath)
   267  			})
   268  		}
   269  
   270  		hook(hs.Router, htmlTemplate)
   271  	}
   272  
   273  	hs.ln, err = net.Listen("tcp4", hs.Addr)
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	s := &fasthttp.Server{
   279  		Handler:            cors(hs.Router.Handler),
   280  		Name:               hs.Config.Name,
   281  		ReadBufferSize:     hs.Config.ReadBufferSize,
   282  		MaxConnsPerIP:      hs.Config.MaxConnsPerIP,
   283  		MaxRequestsPerConn: hs.Config.MaxRequestsPerConn,
   284  		MaxRequestBodySize: hs.Config.MaxRequestBodySize, //  100 << 20, // 100MB // 1000 * 4, // MaxRequestBodySize:
   285  		Concurrency:        hs.Config.Concurrency,
   286  	}
   287  	var g run.Group
   288  
   289  	if config.IsTlsEnabled() {
   290  		cfg := &tls.Config{
   291  			Certificates: make([]tls.Certificate, 1),
   292  		}
   293  
   294  		cfg.Certificates[0], err = tls.LoadX509KeyPair(config.GetTLSCertificatePath(), config.GetTLSPrivateKeyPath())
   295  
   296  		if err != nil {
   297  			fmt.Println("Run: error in loading TLS certificate: ", err)
   298  			log.Fatalf("Run: error in loading TLS certificate: %v", err)
   299  		}
   300  
   301  		hs.lnTls = tls.NewListener(hs.ln, cfg)
   302  
   303  		// run fasthttp server
   304  		g.Add(func() error {
   305  			return s.Serve(hs.lnTls)
   306  		}, func(e error) {
   307  			_ = hs.ln.Close()
   308  		})
   309  
   310  	} else {
   311  		// run fasthttp server
   312  		g.Add(func() error {
   313  			return s.Serve(hs.ln)
   314  		}, func(e error) {
   315  			_ = hs.ln.Close()
   316  		})
   317  	}
   318  	return g.Run()
   319  }
   320  
   321  func renderHtmlTemplate(ctx *fasthttp.RequestCtx, tpl *htmltemplate.Template) {
   322  	filename := utils.ExtractParamAsString(ctx.UserValue("filename"))
   323  	ctx.Response.Header.Set("Content-Type", "text/html; charset=utf-8")
   324  	err := tpl.ExecuteTemplate(ctx, filename+".html", hooks.GlobalHooks.HtmlSnippets)
   325  	if err != nil {
   326  		log.Errorf("renderHtmlTemplate: unable to execute template, err: %v", err.Error())
   327  		return
   328  	}
   329  }
   330  
   331  func renderJavaScriptTemplate(ctx *fasthttp.RequestCtx, tpl *texttemplate.Template) {
   332  	filename := utils.ExtractParamAsString(ctx.UserValue("filename"))
   333  	ctx.Response.Header.Set("Content-Type", "application/javascript; charset=utf-8")
   334  	err := tpl.ExecuteTemplate(ctx, filename+".js", hooks.GlobalHooks.JsSnippets)
   335  	if err != nil {
   336  		log.Errorf("renderJavaScriptTemplate: unable to execute template, err: %v", err.Error())
   337  		return
   338  	}
   339  }
   340  
   341  func (hs *queryserverCfg) RunSafeServer() error {
   342  	hs.Router.GET("/health", hs.Recovery(getSafeHealthHandler()))
   343  	var err error
   344  	hs.ln, err = net.Listen("tcp4", hs.Addr)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	s := &fasthttp.Server{
   350  		Handler:            cors(hs.Router.Handler),
   351  		Name:               hs.Config.Name,
   352  		ReadBufferSize:     hs.Config.ReadBufferSize,
   353  		MaxConnsPerIP:      hs.Config.MaxConnsPerIP,
   354  		MaxRequestsPerConn: hs.Config.MaxRequestsPerConn,
   355  		MaxRequestBodySize: hs.Config.MaxRequestBodySize, //  100 << 20, // 100MB // 1000 * 4, // MaxRequestBodySize:
   356  		Concurrency:        hs.Config.Concurrency,
   357  	}
   358  
   359  	log.Infof("Starting Ingestion Server on safe mode...")
   360  	ticker := time.NewTicker(1 * time.Minute)
   361  	go func() {
   362  		for range ticker.C {
   363  			log.Infof("SigLens Ingestion Server has started in safe mode...")
   364  		}
   365  	}()
   366  
   367  	// run fasthttp server
   368  	var g run.Group
   369  	g.Add(func() error {
   370  		return s.Serve(hs.ln)
   371  	}, func(e error) {
   372  		_ = hs.ln.Close()
   373  	})
   374  	return g.Run()
   375  }