code.gitea.io/gitea@v1.21.7/routers/web/healthcheck/check.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package healthcheck 5 6 import ( 7 "context" 8 "net/http" 9 "os" 10 "time" 11 12 "code.gitea.io/gitea/models/db" 13 "code.gitea.io/gitea/modules/cache" 14 "code.gitea.io/gitea/modules/json" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 ) 18 19 type status string 20 21 const ( 22 // pass healthy (acceptable aliases: "ok" to support Node's Terminus and "up" for Java's SpringBoot) 23 // fail unhealthy (acceptable aliases: "error" to support Node's Terminus and "down" for Java's SpringBoot), and 24 // warn healthy, with some concerns. 25 // 26 // ref https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check#section-3.1 27 // status: (required) indicates whether the service status is acceptable 28 // or not. API publishers SHOULD use following values for the field: 29 // The value of the status field is case-insensitive and is tightly 30 // related with the HTTP response code returned by the health endpoint. 31 // For "pass" status, HTTP response code in the 2xx-3xx range MUST be 32 // used. For "fail" status, HTTP response code in the 4xx-5xx range 33 // MUST be used. In case of the "warn" status, endpoints MUST return 34 // HTTP status in the 2xx-3xx range, and additional information SHOULD 35 // be provided, utilizing optional fields of the response. 36 pass status = "pass" 37 fail status = "fail" 38 warn status = "warn" 39 ) 40 41 func (s status) ToHTTPStatus() int { 42 if s == pass || s == warn { 43 return http.StatusOK 44 } 45 return http.StatusFailedDependency 46 } 47 48 type checks map[string][]componentStatus 49 50 // response is the data returned by the health endpoint, which will be marshaled to JSON format 51 type response struct { 52 Status status `json:"status"` 53 Description string `json:"description"` // a human-friendly description of the service 54 Checks checks `json:"checks,omitempty"` // The Checks Object, should be omitted on installation route 55 } 56 57 // componentStatus presents one status of a single check object 58 // an object that provides detailed health statuses of additional downstream systems and endpoints 59 // which can affect the overall health of the main API. 60 type componentStatus struct { 61 Status status `json:"status"` 62 Time string `json:"time"` // the date-time, in ISO8601 format 63 Output string `json:"output,omitempty"` // this field SHOULD be omitted for "pass" state. 64 } 65 66 // Check is the health check API handler 67 func Check(w http.ResponseWriter, r *http.Request) { 68 rsp := response{ 69 Status: pass, 70 Description: setting.AppName, 71 Checks: make(checks), 72 } 73 74 statuses := make([]status, 0) 75 if setting.InstallLock { 76 statuses = append(statuses, checkDatabase(r.Context(), rsp.Checks)) 77 statuses = append(statuses, checkCache(rsp.Checks)) 78 } 79 for _, s := range statuses { 80 if s != pass { 81 rsp.Status = fail 82 break 83 } 84 } 85 86 data, _ := json.MarshalIndent(rsp, "", " ") 87 w.Header().Set("Content-Type", "application/json") 88 w.WriteHeader(rsp.Status.ToHTTPStatus()) 89 _, _ = w.Write(data) 90 } 91 92 // database checks gitea database status 93 func checkDatabase(ctx context.Context, checks checks) status { 94 st := componentStatus{} 95 if err := db.GetEngine(ctx).Ping(); err != nil { 96 st.Status = fail 97 st.Time = getCheckTime() 98 log.Error("database ping failed with error: %v", err) 99 } else { 100 st.Status = pass 101 st.Time = getCheckTime() 102 } 103 104 if setting.Database.Type.IsSQLite3() && st.Status == pass { 105 if !setting.EnableSQLite3 { 106 st.Status = fail 107 st.Time = getCheckTime() 108 log.Error("SQLite3 health check failed with error: %v", "this Gitea binary is built without SQLite3 enabled") 109 } else { 110 if _, err := os.Stat(setting.Database.Path); err != nil { 111 st.Status = fail 112 st.Time = getCheckTime() 113 log.Error("SQLite3 file exists check failed with error: %v", err) 114 } 115 } 116 } 117 118 checks["database:ping"] = []componentStatus{st} 119 return st.Status 120 } 121 122 // cache checks gitea cache status 123 func checkCache(checks checks) status { 124 if !setting.CacheService.Enabled { 125 return pass 126 } 127 128 st := componentStatus{} 129 if err := cache.GetCache().Ping(); err != nil { 130 st.Status = fail 131 st.Time = getCheckTime() 132 log.Error("cache ping failed with error: %v", err) 133 } else { 134 st.Status = pass 135 st.Time = getCheckTime() 136 } 137 checks["cache:ping"] = []componentStatus{st} 138 return st.Status 139 } 140 141 func getCheckTime() string { 142 return time.Now().UTC().Format(time.RFC3339) 143 }