Added apache compatible logger

- Rewritten the logger to make it more apache log parser friendly
- Fixed uptime not updating after upstream change bug
- Added SSO page (wip)
- Added log viewer
This commit is contained in:
Toby Chui 2024-07-14 22:25:49 +08:00
parent e410b92e34
commit 8239f4cb53
24 changed files with 427 additions and 158 deletions

View File

@ -148,7 +148,7 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization) authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete) authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
//TCP Proxy //Stream (TCP / UDP) Proxy
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig) authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs) authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs) authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
@ -229,12 +229,13 @@ func initAPIs() {
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup) authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip) authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip) authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
//Debug //Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index) authRouter.HandleFunc("/api/info/pprof", pprof.Index)
//If you got APIs to add, append them here //If you got APIs to add, append them here
// get available docker containers
} }

View File

@ -80,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
return errors.New("not supported proxy type") return errors.New("not supported proxy type")
} }
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil) SystemWideLogger.PrintAndLog("proxy-config", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
return nil return nil
} }

View File

@ -24,6 +24,7 @@ import (
"imuslab.com/zoraxy/mod/ganserv" "imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb" "imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/info/logviewer"
"imuslab.com/zoraxy/mod/mdns" "imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat" "imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule" "imuslab.com/zoraxy/mod/pathrule"
@ -52,14 +53,13 @@ var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL cert
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)") var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters") var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder") var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected") var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
var ( var (
name = "Zoraxy" name = "Zoraxy"
version = "3.0.8" version = "3.0.8"
nodeUUID = "generic" //System uuid, in uuidv4 format nodeUUID = "generic" //System uuid, in uuidv4 format
development = true //Set this to false to use embedded web fs development = false //Set this to false to use embedded web fs
bootTime = time.Now().Unix() bootTime = time.Now().Unix()
/* /*
@ -97,6 +97,7 @@ var (
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
SystemWideLogger *logger.Logger //Logger for Zoraxy SystemWideLogger *logger.Logger //Logger for Zoraxy
LogViewer *logviewer.Viewer
) )
// Kill signal handler. Do something before the system the core terminate. // Kill signal handler. Do something before the system the core terminate.
@ -111,33 +112,34 @@ func SetupCloseHandler() {
} }
func ShutdownSeq() { func ShutdownSeq() {
fmt.Println("- Shutting down " + name) SystemWideLogger.Println("Shutting down " + name)
fmt.Println("- Closing GeoDB ") SystemWideLogger.Println("Closing GeoDB ")
geodbStore.Close() geodbStore.Close()
fmt.Println("- Closing Netstats Listener") SystemWideLogger.Println("Closing Netstats Listener")
netstatBuffers.Close() netstatBuffers.Close()
fmt.Println("- Closing Statistic Collector") SystemWideLogger.Println("Closing Statistic Collector")
statisticCollector.Close() statisticCollector.Close()
if mdnsTickerStop != nil { if mdnsTickerStop != nil {
fmt.Println("- Stopping mDNS Discoverer (might take a few minutes)") SystemWideLogger.Println("Stopping mDNS Discoverer (might take a few minutes)")
// Stop the mdns service // Stop the mdns service
mdnsTickerStop <- true mdnsTickerStop <- true
} }
mdnsScanner.Close() mdnsScanner.Close()
fmt.Println("- Shutting down load balancer") SystemWideLogger.Println("Shutting down load balancer")
loadBalancer.Close() loadBalancer.Close()
fmt.Println("- Closing Certificates Auto Renewer") SystemWideLogger.Println("Closing Certificates Auto Renewer")
acmeAutoRenewer.Close() acmeAutoRenewer.Close()
//Remove the tmp folder //Remove the tmp folder
fmt.Println("- Cleaning up tmp files") SystemWideLogger.Println("Cleaning up tmp files")
os.RemoveAll("./tmp") os.RemoveAll("./tmp")
fmt.Println("- Closing system wide logger") //Close database
SystemWideLogger.Close() SystemWideLogger.Println("Stopping system database")
//Close database, final
fmt.Println("- Stopping system database")
sysdb.Close() sysdb.Close()
//Close logger
SystemWideLogger.Println("Closing system wide logger")
SystemWideLogger.Close()
} }
func main() { func main() {
@ -154,7 +156,7 @@ func main() {
} }
if *enableAutoUpdate { if *enableAutoUpdate {
log.Println("[INFO] Checking required config update") fmt.Println("Checking required config update")
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version)) update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
} }

View File

@ -14,10 +14,10 @@ import (
"strings" "strings"
"encoding/hex" "encoding/hex"
"log"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
db "imuslab.com/zoraxy/mod/database" db "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -27,6 +27,7 @@ type AuthAgent struct {
SessionStore *sessions.CookieStore SessionStore *sessions.CookieStore
Database *db.Database Database *db.Database
LoginRedirectionHandler func(http.ResponseWriter, *http.Request) LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
Logger *logger.Logger
} }
type AuthEndpoints struct { type AuthEndpoints struct {
@ -38,11 +39,11 @@ type AuthEndpoints struct {
} }
// Constructor // Constructor
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent { func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
store := sessions.NewCookieStore(key) store := sessions.NewCookieStore(key)
err := sysdb.NewTable("auth") err := sysdb.NewTable("auth")
if err != nil { if err != nil {
log.Println("Failed to create auth database. Terminating.") systemLogger.Println("Failed to create auth database. Terminating.")
panic(err) panic(err)
} }
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
SessionStore: store, SessionStore: store,
Database: sysdb, Database: sysdb,
LoginRedirectionHandler: loginRedirectionHandler, LoginRedirectionHandler: loginRedirectionHandler,
Logger: systemLogger,
} }
//Return the authAgent //Return the authAgent
return &newAuthAgent return &newAuthAgent
} }
func GetSessionKey(sysdb *db.Database) (string, error) { func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
sysdb.NewTable("auth") sysdb.NewTable("auth")
sessionKey := "" sessionKey := ""
if !sysdb.KeyExists("auth", "sessionkey") { if !sysdb.KeyExists("auth", "sessionkey") {
@ -66,9 +68,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
rand.Read(key) rand.Read(key)
sessionKey = string(key) sessionKey = string(key)
sysdb.Write("auth", "sessionkey", sessionKey) sysdb.Write("auth", "sessionkey", sessionKey)
log.Println("[Auth] New authentication session key generated") logger.PrintAndLog("auth", "New authentication session key generated", nil)
} else { } else {
log.Println("[Auth] Authentication session key loaded from database") logger.PrintAndLog("auth", "Authentication session key loaded from database", nil)
err := sysdb.Read("auth", "sessionkey", &sessionKey) err := sysdb.Read("auth", "sessionkey", &sessionKey)
if err != nil { if err != nil {
return "", errors.New("database read error. Is the database file corrupted?") return "", errors.New("database read error. Is the database file corrupted?")
@ -95,7 +97,7 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
username, err := utils.PostPara(r, "username") username, err := utils.PostPara(r, "username")
if err != nil { if err != nil {
//Username not defined //Username not defined
log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username) a.Logger.PrintAndLog("auth", r.RemoteAddr+" trying to login with username: "+username, nil)
utils.SendErrorResponse(w, "Username not defined or empty.") utils.SendErrorResponse(w, "Username not defined or empty.")
return return
} }
@ -124,11 +126,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
a.LoginUserByRequest(w, r, username, rememberme) a.LoginUserByRequest(w, r, username, rememberme)
//Print the login message to console //Print the login message to console
log.Println(username + " logged in.") a.Logger.PrintAndLog("auth", username+" logged in.", nil)
utils.SendOK(w) utils.SendOK(w)
} else { } else {
//Password incorrect //Password incorrect
log.Println(username + " login request rejected: " + rejectionReason) a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
utils.SendErrorResponse(w, rejectionReason) utils.SendErrorResponse(w, rejectionReason)
return return
@ -147,7 +149,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB) err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
if err != nil { if err != nil {
//User not found or db exception //User not found or db exception
log.Println("[Auth] " + username + " login with incorrect password") a.Logger.PrintAndLog("auth", username+" login with incorrect password", nil)
return false, "Invalid username or password" return false, "Invalid username or password"
} }
@ -184,8 +186,12 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
// Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION // Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) { func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
username, err := a.GetUserName(w, r) username, err := a.GetUserName(w, r)
if err != nil {
utils.SendErrorResponse(w, "user not logged in")
return
}
if username != "" { if username != "" {
log.Println(username + " logged out.") a.Logger.PrintAndLog("auth", username+" logged out", nil)
} }
// Revoke users authentication // Revoke users authentication
err = a.Logout(w, r) err = a.Logout(w, r)
@ -194,7 +200,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Write([]byte("OK")) utils.SendOK(w)
} }
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error { func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
@ -291,7 +297,7 @@ func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callb
//Return to the client with OK //Return to the client with OK
utils.SendOK(w) utils.SendOK(w)
log.Println("[Auth] New user " + newusername + " added to system.") a.Logger.PrintAndLog("auth", "New user "+newusername+" added to system.", nil)
} }
// Handle new user register without confirmation email. Require POST username, password, group. // Handle new user register without confirmation email. Require POST username, password, group.
@ -324,7 +330,7 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
//Return to the client with OK //Return to the client with OK
utils.SendOK(w) utils.SendOK(w)
log.Println("[Auth] Admin account created: " + newusername) a.Logger.PrintAndLog("auth", "Admin account created: "+newusername, nil)
} }
// Check authentication from request header's session value // Check authentication from request header's session value
@ -365,7 +371,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
//Return to the client with OK //Return to the client with OK
utils.SendOK(w) utils.SendOK(w)
log.Println("[Auth] User " + username + " has been removed from the system.") a.Logger.PrintAndLog("auth", "User "+username+" has been removed from the system", nil)
} }
func (a *AuthAgent) UnregisterUser(username string) error { func (a *AuthAgent) UnregisterUser(username string) error {
@ -393,7 +399,7 @@ func (a *AuthAgent) GetUserCounts() int {
} }
if usercount == 0 { if usercount == 0 {
log.Println("There are no user in the database.") a.Logger.PrintAndLog("auth", "There are no user in the database", nil)
} }
return usercount return usercount
} }

View File

@ -2,7 +2,7 @@ package auth
import ( import (
"errors" "errors"
"log" "fmt"
"net/http" "net/http"
) )
@ -28,7 +28,7 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef {
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error { func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
//Check if the endpoint already registered //Check if the endpoint already registered
if _, exist := router.endpoints[endpoint]; exist { if _, exist := router.endpoints[endpoint]; exist {
log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint) fmt.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
return errors.New("endpoint register duplicated") return errors.New("endpoint register duplicated")
} }

View File

@ -77,6 +77,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if sep.RequireRateLimit { if sep.RequireRateLimit {
err := h.handleRateLimitRouting(w, r, sep) err := h.handleRateLimitRouting(w, r, sep)
if err != nil { if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 429)
return return
} }
} }
@ -85,6 +86,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if sep.RequireBasicAuth { if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep) err := h.handleBasicAuthRouting(w, r, sep)
if err != nil { if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
return return
} }
} }
@ -101,6 +103,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled { if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint //Missing tailing slash. Redirect to target proxy endpoint
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect) http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
h.Parent.Option.Logger.LogHTTPRequest(r, "redirect", 307)
return return
} }
} }

View File

@ -154,7 +154,7 @@ func (router *Router) StartProxyService() error {
selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession) selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession)
if err != nil { if err != nil {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error()) router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
router.logRequest(r, false, 404, "vdir-http", r.Host) router.logRequest(r, false, 404, "vdir-http", r.Host)
} }
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
@ -195,7 +195,7 @@ func (router *Router) StartProxyService() error {
IdleTimeout: 120 * time.Second, IdleTimeout: 120 * time.Second,
} }
log.Println("Starting HTTP-to-HTTPS redirector (port 80)") router.Option.Logger.PrintAndLog("dprouter", "Starting HTTP-to-HTTPS redirector (port 80)", nil)
//Create a redirection stop channel //Create a redirection stop channel
stopChan := make(chan bool) stopChan := make(chan bool)
@ -206,7 +206,7 @@ func (router *Router) StartProxyService() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
httpServer.Shutdown(ctx) httpServer.Shutdown(ctx)
log.Println("HTTP to HTTPS redirection listener stopped") router.Option.Logger.PrintAndLog("dprouter", "HTTP to HTTPS redirection listener stopped", nil)
}() }()
//Start the http server that listens to port 80 and redirect to 443 //Start the http server that listens to port 80 and redirect to 443
@ -221,10 +221,10 @@ func (router *Router) StartProxyService() error {
} }
//Start the TLS server //Start the TLS server
log.Println("Reverse proxy service started in the background (TLS mode)") router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (TLS mode)", nil)
go func() { go func() {
if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not start proxy server: %v\n", err) router.Option.Logger.PrintAndLog("dprouter", "Could not start proxy server", err)
} }
}() }()
} else { } else {
@ -232,10 +232,9 @@ func (router *Router) StartProxyService() error {
router.tlsListener = nil router.tlsListener = nil
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux} router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
router.Running = true router.Running = true
log.Println("Reverse proxy service started in the background (Plain HTTP mode)") router.Option.Logger.PrintAndLog("dprouter", "Reverse proxy service started in the background (Plain HTTP mode)", nil)
go func() { go func() {
router.server.ListenAndServe() router.server.ListenAndServe()
//log.Println("[DynamicProxy] " + err.Error())
}() }()
} }

View File

@ -136,7 +136,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
if selectedUpstream.RequireTLS { if selectedUpstream.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL) u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
} }
h.Parent.logRequest(r, true, 101, "subdomain-websocket", selectedUpstream.OriginIpOrDomain) h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: selectedUpstream.SkipCertValidations, SkipTLSValidation: selectedUpstream.SkipCertValidations,
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck, SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
@ -173,15 +173,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
if errors.As(err, &dnsError) { if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error()) log.Println(err.Error())
h.Parent.logRequest(r, false, 404, "subdomain-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
} else { } else {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error()) log.Println(err.Error())
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
} }
} }
h.Parent.logRequest(r, true, 200, "subdomain-http", r.URL.Hostname()) h.Parent.logRequest(r, true, 200, "host-http", r.URL.Hostname())
} }
// Handle vdir type request // Handle vdir type request
@ -249,6 +249,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
} }
// This logger collect data for the statistical analysis. For log to file logger, check the Logger and LogHTTPRequest handler
func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) { func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
if router.Option.StatisticCollector != nil { if router.Option.StatisticCollector != nil {
go func() { go func() {
@ -266,4 +267,5 @@ func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, for
router.Option.StatisticCollector.RecordRequest(requestInfo) router.Option.StatisticCollector.RecordRequest(requestInfo)
}() }()
} }
router.Option.Logger.LogHTTPRequest(r, forwardType, statusCode)
} }

View File

@ -12,6 +12,7 @@ import (
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection" "imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/geodb" "imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/statistic" "imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/tlscert" "imuslab.com/zoraxy/mod/tlscert"
) )
@ -43,6 +44,7 @@ type RouterOption struct {
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
WebDirectory string //The static web server directory containing the templates folder WebDirectory string //The static web server directory containing the templates folder
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
Logger *logger.Logger //Logger for reverse proxy requets
} }
/* Router Object */ /* Router Object */

View File

@ -13,29 +13,31 @@ import (
Zoraxy Logger Zoraxy Logger
This script is designed to make a managed log for the Zoraxy This script is designed to make a managed log for the Zoraxy
and replace the ton of log.Println in the system core and replace the ton of log.Println in the system core.
The core logger is based in golang's build-in log package
*/ */
type Logger struct { type Logger struct {
LogToFile bool //Set enable write to file
Prefix string //Prefix for log files Prefix string //Prefix for log files
LogFolder string //Folder to store the log file LogFolder string //Folder to store the log file
CurrentLogFile string //Current writing filename CurrentLogFile string //Current writing filename
logger *log.Logger
file *os.File file *os.File
} }
func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) { // Create a new logger that log to files
func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
err := os.MkdirAll(logFolder, 0775) err := os.MkdirAll(logFolder, 0775)
if err != nil { if err != nil {
return nil, err return nil, err
} }
thisLogger := Logger{ thisLogger := Logger{
LogToFile: logToFile,
Prefix: logFilePrefix, Prefix: logFilePrefix,
LogFolder: logFolder, LogFolder: logFolder,
} }
//Create the log file if not exists
logFilePath := thisLogger.getLogFilepath() logFilePath := thisLogger.getLogFilepath()
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil { if err != nil {
@ -43,9 +45,26 @@ func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger,
} }
thisLogger.CurrentLogFile = logFilePath thisLogger.CurrentLogFile = logFilePath
thisLogger.file = f thisLogger.file = f
//Start the logger
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
logger.SetFlags(0)
logger.SetOutput(f)
thisLogger.logger = logger
return &thisLogger, nil return &thisLogger, nil
} }
// Create a fmt logger that only log to STDOUT
func NewFmtLogger() (*Logger, error) {
return &Logger{
Prefix: "",
LogFolder: "",
CurrentLogFile: "",
logger: nil,
file: nil,
}, nil
}
func (l *Logger) getLogFilepath() string { func (l *Logger) getLogFilepath() string {
year, month, _ := time.Now().Date() year, month, _ := time.Now().Date()
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log") return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
@ -54,9 +73,8 @@ func (l *Logger) getLogFilepath() string {
// PrintAndLog will log the message to file and print the log to STDOUT // PrintAndLog will log the message to file and print the log to STDOUT
func (l *Logger) PrintAndLog(title string, message string, originalError error) { func (l *Logger) PrintAndLog(title string, message string, originalError error) {
go func() { go func() {
l.Log(title, message, originalError) l.Log(title, message, originalError, true)
}() }()
log.Println("[" + title + "] " + message)
} }
// Println is a fast snap-in replacement for log.Println // Println is a fast snap-in replacement for log.Println
@ -64,18 +82,26 @@ func (l *Logger) Println(v ...interface{}) {
//Convert the array of interfaces into string //Convert the array of interfaces into string
message := fmt.Sprint(v...) message := fmt.Sprint(v...)
go func() { go func() {
l.Log("info", string(message), nil) l.Log("internal", string(message), nil, true)
}() }()
log.Println("[INFO] " + string(message))
} }
func (l *Logger) Log(title string, errorMessage string, originalError error) { func (l *Logger) Log(title string, errorMessage string, originalError error, copyToSTDOUT bool) {
l.ValidateAndUpdateLogFilepath() l.ValidateAndUpdateLogFilepath()
if l.LogToFile { if l.logger == nil || copyToSTDOUT {
//Use STDOUT instead of logger
if originalError == nil { if originalError == nil {
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n") fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
} else { } else {
l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n") fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
}
}
if l.logger != nil {
if originalError == nil {
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
} else {
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
} }
} }
@ -83,18 +109,28 @@ func (l *Logger) Log(title string, errorMessage string, originalError error) {
// Validate if the logging target is still valid (detect any months change) // Validate if the logging target is still valid (detect any months change)
func (l *Logger) ValidateAndUpdateLogFilepath() { func (l *Logger) ValidateAndUpdateLogFilepath() {
if l.file == nil {
return
}
expectedCurrentLogFilepath := l.getLogFilepath() expectedCurrentLogFilepath := l.getLogFilepath()
if l.CurrentLogFile != expectedCurrentLogFilepath { if l.CurrentLogFile != expectedCurrentLogFilepath {
//Change of month. Update to a new log file //Change of month. Update to a new log file
l.file.Close() l.file.Close()
l.file = nil
//Create a new log file
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil { if err != nil {
log.Println("[Logger] Unable to create new log. Logging to file disabled.") log.Println("Unable to create new log. Logging is disabled: ", err.Error())
l.LogToFile = false l.logger = nil
return return
} }
l.CurrentLogFile = expectedCurrentLogFilepath l.CurrentLogFile = expectedCurrentLogFilepath
l.file = f l.file = f
//Start a new logger
logger := log.New(f, "", log.Default().Flags())
l.logger = logger
} }
} }

View File

@ -0,0 +1,32 @@
package logger
/*
Traffic Log
This script log the traffic of HTTP requests
*/
import (
"net/http"
"strconv"
"time"
"imuslab.com/zoraxy/mod/netutils"
)
// Log HTTP request. Note that this must run in go routine to prevent any blocking
// in reverse proxy router
func (l *Logger) LogHTTPRequest(r *http.Request, reqclass string, statusCode int) {
go func() {
l.ValidateAndUpdateLogFilepath()
if l.logger == nil || l.file == nil {
//logger is not initiated. Do not log http request
return
}
clientIP := netutils.GetRequesterIP(r)
requestURI := r.RequestURI
statusCodeString := strconv.Itoa(statusCode)
//fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [router:" + reqclass + "] [origin:" + r.URL.Hostname() + "] [client " + clientIP + "] " + r.Method + " " + requestURI + " " + statusCodeString)
}()
}

View File

@ -3,6 +3,7 @@ package logviewer
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
@ -51,13 +52,7 @@ func (v *Viewer) HandleReadLog(w http.ResponseWriter, r *http.Request) {
return return
} }
catergory, err := utils.GetPara(r, "catergory") content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(filename)))
if err != nil {
utils.SendErrorResponse(w, "invalid catergory given")
return
}
content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(catergory)), strings.TrimSpace(filepath.Base(filename)))
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
@ -106,8 +101,11 @@ func (v *Viewer) ListLogFiles(showFullpath bool) map[string][]*LogFile {
return result return result
} }
func (v *Viewer) LoadLogFile(catergory string, filename string) (string, error) { func (v *Viewer) LoadLogFile(filename string) (string, error) {
logFilepath := filepath.Join(v.option.RootFolder, catergory, filename) filename = filepath.ToSlash(filename)
filename = strings.ReplaceAll(filename, "../", "")
logFilepath := filepath.Join(v.option.RootFolder, filename)
fmt.Println(logFilepath)
if utils.FileExists(logFilepath) { if utils.FileExists(logFilepath) {
//Load it //Load it
content, err := os.ReadFile(logFilepath) content, err := os.ReadFile(logFilepath)

View File

@ -39,7 +39,6 @@ type NetStatBuffers struct {
// Get a new network statistic buffers // Get a new network statistic buffers
func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) { func NewNetStatBuffer(recordCount int) (*NetStatBuffers, error) {
//Flood fill the stats with 0 //Flood fill the stats with 0
initialStats := []*FlowStat{} initialStats := []*FlowStat{}
for i := 0; i < recordCount; i++ { for i := 0; i < recordCount; i++ {

View File

@ -2,16 +2,23 @@ package uptime
import ( import (
"encoding/json" "encoding/json"
"errors"
"log" "log"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"strconv"
"strings" "strings"
"time" "time"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
const (
logModuleName = "uptime-monitor"
)
type Record struct { type Record struct {
Timestamp int64 Timestamp int64
ID string ID string
@ -42,6 +49,7 @@ type Config struct {
Targets []*Target Targets []*Target
Interval int Interval int
MaxRecordsStore int MaxRecordsStore int
Logger *logger.Logger
} }
type Monitor struct { type Monitor struct {
@ -64,6 +72,12 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
Config: config, Config: config,
OnlineStatusLog: map[string][]*Record{}, OnlineStatusLog: map[string][]*Record{},
} }
if config.Logger == nil {
//Use default fmt to log if logger is not provided
config.Logger, _ = logger.NewFmtLogger()
}
//Start the endpoint listener //Start the endpoint listener
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second) ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
done := make(chan bool) done := make(chan bool)
@ -77,7 +91,7 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
case <-done: case <-done:
return return
case t := <-ticker.C: case t := <-ticker.C:
log.Println("Uptime updated - ", t.Unix()) thisMonitor.Config.Logger.PrintAndLog(logModuleName, "Uptime updated - "+strconv.Itoa(int(t.Unix())), nil)
thisMonitor.ExecuteUptimeCheck() thisMonitor.ExecuteUptimeCheck()
} }
} }
@ -91,7 +105,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
//For each target to check online, do the following //For each target to check online, do the following
var thisRecord Record var thisRecord Record
if target.Protocol == "http" || target.Protocol == "https" { if target.Protocol == "http" || target.Protocol == "https" {
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL) online, laterncy, statusCode := m.getWebsiteStatusWithLatency(target.URL)
thisRecord = Record{ thisRecord = Record{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
ID: target.ID, ID: target.ID,
@ -104,7 +118,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
} }
} else { } else {
log.Println("Unknown protocol: " + target.Protocol + ". Skipping") m.Config.Logger.PrintAndLog(logModuleName, "Unknown protocol: "+target.Protocol, errors.New("unsupported protocol"))
continue continue
} }
@ -124,8 +138,6 @@ func (m *Monitor) ExecuteUptimeCheck() {
m.OnlineStatusLog[target.ID] = thisRecords m.OnlineStatusLog[target.ID] = thisRecords
} }
} }
//TODO: Write results to db
} }
func (m *Monitor) AddTargetToMonitor(target *Target) { func (m *Monitor) AddTargetToMonitor(target *Target) {
@ -201,12 +213,12 @@ func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
*/ */
// Get website stauts with latency given URL, return is conn succ and its latency and status code // Get website stauts with latency given URL, return is conn succ and its latency and status code
func getWebsiteStatusWithLatency(url string) (bool, int64, int) { func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
start := time.Now().UnixNano() / int64(time.Millisecond) start := time.Now().UnixNano() / int64(time.Millisecond)
statusCode, err := getWebsiteStatus(url) statusCode, err := getWebsiteStatus(url)
end := time.Now().UnixNano() / int64(time.Millisecond) end := time.Now().UnixNano() / int64(time.Millisecond)
if err != nil { if err != nil {
log.Println(err.Error()) m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
return false, 0, 0 return false, 0, 0
} else { } else {
diff := end - start diff := end - start

View File

@ -5,13 +5,13 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
"imuslab.com/zoraxy/mod/webserv/filemanager" "imuslab.com/zoraxy/mod/webserv/filemanager"
) )
@ -30,6 +30,7 @@ type WebServerOptions struct {
EnableDirectoryListing bool //Enable listing of directory EnableDirectoryListing bool //Enable listing of directory
WebRoot string //Folder for stroing the static web folders WebRoot string //Folder for stroing the static web folders
EnableWebDirManager bool //Enable web file manager to handle files in web directory EnableWebDirManager bool //Enable web file manager to handle files in web directory
Logger *logger.Logger //System logger
Sysdb *database.Database //Database for storing configs Sysdb *database.Database //Database for storing configs
} }
@ -45,13 +46,16 @@ type WebServer struct {
// NewWebServer creates a new WebServer instance. One instance only // NewWebServer creates a new WebServer instance. One instance only
func NewWebServer(options *WebServerOptions) *WebServer { func NewWebServer(options *WebServerOptions) *WebServer {
if options.Logger == nil {
options.Logger, _ = logger.NewFmtLogger()
}
if !utils.FileExists(options.WebRoot) { if !utils.FileExists(options.WebRoot) {
//Web root folder not exists. Create one with default templates //Web root folder not exists. Create one with default templates
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775) os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775) os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
indexTemplate, err := templates.ReadFile("templates/index.html") indexTemplate, err := templates.ReadFile("templates/index.html")
if err != nil { if err != nil {
log.Println("Failed to read static wev server template file: ", err.Error()) options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err)
} else { } else {
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775) os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
} }
@ -102,7 +106,7 @@ func (ws *WebServer) RestorePreviousState() {
// ChangePort changes the server's port. // ChangePort changes the server's port.
func (ws *WebServer) ChangePort(port string) error { func (ws *WebServer) ChangePort(port string) error {
if IsPortInUse(port) { if IsPortInUse(port) {
return errors.New("Selected port is used by another process") return errors.New("selected port is used by another process")
} }
if ws.isRunning { if ws.isRunning {
@ -119,6 +123,7 @@ func (ws *WebServer) ChangePort(port string) error {
return err return err
} }
ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
ws.option.Sysdb.Write("webserv", "port", port) ws.option.Sysdb.Write("webserv", "port", port)
return nil return nil
@ -141,7 +146,7 @@ func (ws *WebServer) Start() error {
//Check if the port is usable //Check if the port is usable
if IsPortInUse(ws.option.Port) { if IsPortInUse(ws.option.Port) {
return errors.New("Port already in use or access denied by host OS") return errors.New("port already in use or access denied by host OS")
} }
//Dispose the old mux and create a new one //Dispose the old mux and create a new one
@ -159,12 +164,12 @@ func (ws *WebServer) Start() error {
go func() { go func() {
if err := ws.server.ListenAndServe(); err != nil { if err := ws.server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed { if err != http.ErrServerClosed {
fmt.Printf("Web server error: %v\n", err) ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err)
} }
} }
}() }()
log.Println("Static Web Server started. Listeing on :" + ws.option.Port) ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil)
ws.isRunning = true ws.isRunning = true
ws.option.Sysdb.Write("webserv", "enabled", true) ws.option.Sysdb.Write("webserv", "enabled", true)
return nil return nil
@ -182,7 +187,7 @@ func (ws *WebServer) Stop() error {
if err := ws.server.Close(); err != nil { if err := ws.server.Close(); err != nil {
return err return err
} }
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
ws.isRunning = false ws.isRunning = false
ws.option.Sysdb.Write("webserv", "enabled", false) ws.option.Sysdb.Write("webserv", "enabled", false)
return nil return nil

View File

@ -98,9 +98,10 @@ func ReverseProxtInit() {
WebDirectory: *staticWebServerRoot, WebDirectory: *staticWebServerRoot,
AccessController: accessController, AccessController: accessController,
LoadBalancer: loadBalancer, LoadBalancer: loadBalancer,
Logger: SystemWideLogger,
}) })
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to create dynamic proxy router", err)
return return
} }
@ -115,7 +116,7 @@ func ReverseProxtInit() {
for _, conf := range confs { for _, conf := range confs {
err := LoadReverseProxyConfig(conf) err := LoadReverseProxyConfig(conf)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err) SystemWideLogger.PrintAndLog("proxy-config", "Failed to load config file: "+filepath.Base(conf), err)
return return
} }
} }
@ -124,7 +125,7 @@ func ReverseProxtInit() {
//Root config not set (new deployment?), use internal static web server as root //Root config not set (new deployment?), use internal static web server as root
defaultRootRouter, err := GetDefaultRootConfig() defaultRootRouter, err := GetDefaultRootConfig()
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err) SystemWideLogger.PrintAndLog("proxy-config", "Failed to generate default root routing", err)
return return
} }
dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter) dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
@ -145,6 +146,7 @@ func ReverseProxtInit() {
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter), Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
Interval: 300, //5 minutes Interval: 300, //5 minutes
MaxRecordsStore: 288, //1 day MaxRecordsStore: 288, //1 day
Logger: SystemWideLogger, //Logger
}) })
SystemWideLogger.Println("Uptime Monitor background service started") SystemWideLogger.Println("Uptime Monitor background service started")
@ -412,7 +414,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
//Save the config to file //Save the config to file
err = SaveReverseProxyConfig(proxyEndpointCreated) err = SaveReverseProxyConfig(proxyEndpointCreated)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to save new proxy rule to file", err)
return return
} }
@ -480,15 +482,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
proxyRateLimit = 1000 proxyRateLimit = 1000
} }
// Bypass WebSocket Origin Check
/*
strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
if strbpwsorg == "" {
strbpwsorg = "false"
}
bypassWebsocketOriginCheck := (strbpwsorg == "true")
*/
//Load the previous basic auth credentials from current proxy rules //Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain) targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
if err != nil { if err != nil {
@ -498,11 +491,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
//Generate a new proxyEndpoint from the new config //Generate a new proxyEndpoint from the new config
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry) newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
//TODO: Move these into dedicated module
//newProxyEndpoint.Domain = endpoint
//newProxyEndpoint.RequireTLS = useTLS
//newProxyEndpoint.SkipCertValidations = skipTlsValidation
//newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
newProxyEndpoint.RequireBasicAuth = requireBasicAuth newProxyEndpoint.RequireBasicAuth = requireBasicAuth
newProxyEndpoint.RequireRateLimit = requireRateLimit newProxyEndpoint.RequireRateLimit = requireRateLimit
@ -521,9 +509,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
//Save it to file //Save it to file
SaveReverseProxyConfig(newProxyEndpoint) SaveReverseProxyConfig(newProxyEndpoint)
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
} }
@ -555,7 +540,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
newAlias := []string{} newAlias := []string{}
err = json.Unmarshal([]byte(newAliasJSON), &newAlias) err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
if err != nil { if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to parse new alias list", err)
utils.SendErrorResponse(w, "Invalid alias list given") utils.SendErrorResponse(w, "Invalid alias list given")
return return
} }
@ -577,7 +562,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
err = SaveReverseProxyConfig(newProxyEndpoint) err = SaveReverseProxyConfig(newProxyEndpoint)
if err != nil { if err != nil {
utils.SendErrorResponse(w, "Alias update failed") utils.SendErrorResponse(w, "Alias update failed")
SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err) SystemWideLogger.PrintAndLog("proxy-config", "Unable to save alias update", err)
} }
utils.SendOK(w) utils.SendOK(w)
@ -881,6 +866,10 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "unable to save updated rule") utils.SendErrorResponse(w, "unable to save updated rule")
return return
} }
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
} }

View File

@ -20,6 +20,7 @@ import (
"imuslab.com/zoraxy/mod/ganserv" "imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb" "imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/info/logviewer"
"imuslab.com/zoraxy/mod/mdns" "imuslab.com/zoraxy/mod/mdns"
"imuslab.com/zoraxy/mod/netstat" "imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/pathrule" "imuslab.com/zoraxy/mod/pathrule"
@ -47,6 +48,18 @@ var (
) )
func startupSequence() { func startupSequence() {
//Start a system wide logger and log viewer
l, err := logger.NewLogger("zr", "./log")
if err == nil {
SystemWideLogger = l
} else {
panic(err)
}
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
RootFolder: "./log",
Extension: ".log",
})
//Create database //Create database
db, err := database.NewDatabase("sys.db", false) db, err := database.NewDatabase("sys.db", false)
if err != nil { if err != nil {
@ -61,11 +74,11 @@ func startupSequence() {
os.MkdirAll("./conf/proxy/", 0775) os.MkdirAll("./conf/proxy/", 0775)
//Create an auth agent //Create an auth agent
sessionKey, err := auth.GetSessionKey(sysdb) sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) { authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
//Not logged in. Redirecting to login page //Not logged in. Redirecting to login page
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect) http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
}) })
@ -76,14 +89,6 @@ func startupSequence() {
panic(err) panic(err)
} }
//Create a system wide logger
l, err := logger.NewLogger("zr", "./log", *logOutputToFile)
if err == nil {
SystemWideLogger = l
} else {
panic(err)
}
//Create a redirection rule table //Create a redirection rule table
db.NewTable("redirect") db.NewTable("redirect")
redirectAllowRegexp := false redirectAllowRegexp := false
@ -134,6 +139,7 @@ func startupSequence() {
WebRoot: *staticWebServerRoot, WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true, EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager, EnableWebDirManager: *allowWebFileManager,
Logger: SystemWideLogger,
}) })
//Restore the web server to previous shutdown state //Restore the web server to previous shutdown state
staticWebServer.RestorePreviousState() staticWebServer.RestorePreviousState()

View File

@ -109,6 +109,9 @@ func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
return return
} }
//Update Uptime Monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
} }
@ -180,6 +183,9 @@ func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "Failed to save updated upstream config") utils.SendErrorResponse(w, "Failed to save updated upstream config")
return return
} }
//Update Uptime Monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
} }
@ -270,5 +276,8 @@ func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) {
return return
} }
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w) utils.SendOK(w)
} }

View File

@ -1,7 +1,7 @@
<div class="standardContainer"> <div class="standardContainer">
<div class="ui basic segment"> <div class="ui basic segment">
<h2>Service Expose Proxy</h2> <h2>Single-Sign-On</h2>
<p>Expose your local test-site on the internet with single command</p> <p>Create and manage accounts with Zoraxy!</p>
</div> </div>
<div class="ui message"> <div class="ui message">
<h4>Work In Progress</h4> <h4>Work In Progress</h4>

View File

@ -120,7 +120,12 @@
<!-- Config Tools --> <!-- Config Tools -->
<h3>System Backup & Restore</h3> <h3>System Backup & Restore</h3>
<p>Options related to system backup, migrate and restore.</p> <p>Options related to system backup, migrate and restore.</p>
<button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');">Open Config Tools</button> <button class="ui basic button" onclick="showSideWrapper('snippet/configTools.html');"><i class="ui green undo icon icon"></i> Open Config Tools</button>
<div class="ui divider"></div>
<!-- Log Viewer -->
<h3>System Log Viewer</h3>
<p>View and download Zoraxy log</p>
<button class="ui basic button" onclick="launchToolWithSize('snippet/logview.html', 1024, 768);"><i class="ui blue file icon"></i> Open Log Viewer</button>
<div class="ui divider"></div> <div class="ui divider"></div>
<!-- System Information --> <!-- System Information -->
<div id="zoraxyinfo"> <div id="zoraxyinfo">

View File

@ -55,21 +55,21 @@
<i class="simplistic exchange icon"></i> Stream Proxy <i class="simplistic exchange icon"></i> Stream Proxy
</a> </a>
<div class="ui divider menudivider">Access & Connections</div> <div class="ui divider menudivider">Access & Connections</div>
<a class="item" tag="cert">
<i class="simplistic lock icon"></i> TLS / SSL certificates
</a>
<a class="item" tag="redirectset"> <a class="item" tag="redirectset">
<i class="simplistic level up alternate icon"></i> Redirection <i class="simplistic level up alternate icon"></i> Redirection
</a> </a>
<a class="item" tag="access"> <a class="item" tag="access">
<i class="simplistic ban icon"></i> Access Control <i class="simplistic ban icon"></i> Access Control
</a> </a>
<div class="ui divider menudivider">Bridging</div>
<a class="item" tag="gan"> <a class="item" tag="gan">
<i class="simplistic globe icon"></i> Global Area Network <i class="simplistic globe icon"></i> Global Area Network
</a> </a>
<a class="item" tag="zgrok"> <div class="ui divider menudivider">Security</div>
<i class="simplistic podcast icon"></i> Service Expose Proxy <a class="item" tag="cert">
<i class="simplistic lock icon"></i> TLS / SSL certificates
</a>
<a class="item" tag="sso">
<i class="simplistic user circle icon"></i> SSO / Oauth
</a> </a>
<div class="ui divider menudivider">Others</div> <div class="ui divider menudivider">Others</div>
<a class="item" tag="webserv"> <a class="item" tag="webserv">
@ -120,8 +120,8 @@
<!-- Global Area Networking --> <!-- Global Area Networking -->
<div id="gan" class="functiontab" target="gan.html"></div> <div id="gan" class="functiontab" target="gan.html"></div>
<!-- Service Expose Proxy --> <!-- SSO / Oauth services -->
<div id="zgrok" class="functiontab" target="zgrok.html"></div> <div id="sso" class="functiontab" target="sso.html"></div>
<!-- TCP Proxy --> <!-- TCP Proxy -->
<div id="streamproxy" class="functiontab" target="streamprox.html"></div> <div id="streamproxy" class="functiontab" target="streamprox.html"></div>

View File

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html ng-app="App">
<head>
<title>System Logs</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script type="text/javascript" src="../script/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../script/semantic/semantic.min.js"></script>
<style>
.clickable{
cursor: pointer;
}
.clickable:hover{
opacity: 0.7;
}
.logfile{
padding-left: 1em !important;
position: relative;
padding-right: 1em !important;
}
.loglist{
background-color: rgb(250, 250, 250);
}
.logfile .showing{
position: absolute;
top: 0.18em;
right: 0em;
margin-right: -0.4em;
opacity: 0;
}
.logfile.active .showing{
opacity: 1;
}
#logrender{
width: 100% !important;
height: calc(100% - 1.2em);
min-height: calc(90vh - 1.2em) !important;
border: 0px solid transparent !important;
background-color: #252630;
color: white;
font-family: monospace;
overflow-x: scroll !important;
white-space: pre;
resize: none;
scrollbar-width: thin;
font-size: 1.2em;
}
#logrender::selection{
background:#3643bb;
color:white;
}
</style>
</head>
<body>
<br>
<div class="ui container">
<div class="ui stackable grid">
<div class="four wide column loglist">
<h3 class="ui header" style="padding-top: 1em;">
<div class="content">
Log View
<div class="sub header">Check System Log in Real Time</div>
</div>
</h3>
<div class="ui divider"></div>
<div id="logList" class="ui accordion">
</div>
<div class="ui divider"></div>
<small>Notes: Some log files might be huge. Make sure you have checked the log file size before opening</small>
</div>
<div class="twelve wide column">
<textarea id="logrender" spellcheck="false" readonly="true">
← Pick a log file from the left menu to start debugging
</textarea>
<a href="#" onclick="openLogInNewTab();">Open In New Tab</a>
<br><br>
</div>
</div>
</div>
<br>
</body>
<script>
var currentOpenedLogURL = "";
function openLogInNewTab(){
if (currentOpenedLogURL != ""){
window.open(currentOpenedLogURL);
}
}
function openLog(object, catergory, filename){
$(".logfile.active").removeClass('active');
$(object).addClass("active");
currentOpenedLogURL = "/api/log/read?file=" + filename;
$.get(currentOpenedLogURL, function(data){
if (data.error !== undefined){
alert(data.error);
return;
}
$("#logrender").val(data);
});
}
function initLogList(){
$("#logList").html("");
$.get("/api/log/list", function(data){
//console.log(data);
for (let [key, value] of Object.entries(data)) {
console.log(key, value);
value.reverse(); //Default value was from oldest to newest
var fileItemList = "";
value.forEach(file => {
fileItemList += `<div class="item clickable logfile" onclick="openLog(this, '${key}','${file.Filename}');">
<i class="file outline icon"></i>
<div class="content">
${file.Title} (${formatBytes(file.Filesize)})
<div class="showing"><i class="green chevron right icon"></i></div>
</div>
</div>`;
})
$("#logList").append(`<div class="title">
<i class="dropdown icon"></i>
${key}
</div>
<div class="content">
<div class="ui list">
${fileItemList}
</div>
</div>`);
}
$(".ui.accordion").accordion();
});
}
initLogList();
function formatBytes(x){
var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let l = 0, n = parseInt(x, 10) || 0;
while(n >= 1024 && ++l){
n = n/1024;
}
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l]);
}
</script>
</html>

View File

@ -75,7 +75,7 @@
</div> </div>
</div> </div>
<div class="ui message"> <div class="ui message">
<i class="ui blue info circle icon"></i> Round-robin load balancing algorithm will be used for upstreams with same weight. Set weight to 0 for fallback only. <i class="ui blue info circle icon"></i> Weighted random will be used for load-balancing. Set weight to 0 for fallback only.
</div> </div>
</div> </div>
<div class="ui tab basic segment" data-tab="newupstream"> <div class="ui tab basic segment" data-tab="newupstream">

View File

@ -115,7 +115,7 @@ func UpdateUptimeMonitorTargets() {
uptimeMonitor.ExecuteUptimeCheck() uptimeMonitor.ExecuteUptimeCheck()
}() }()
SystemWideLogger.PrintAndLog("Uptime", "Uptime monitor config updated", nil) SystemWideLogger.PrintAndLog("uptime-monitor", "Uptime monitor config updated", nil)
} }
} }
@ -125,8 +125,12 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
UptimeTargets := []*uptime.Target{} UptimeTargets := []*uptime.Target{}
for hostid, target := range hosts { for hostid, target := range hosts {
for _, origin := range target.ActiveOrigins { if target.Disabled {
//Skip those proxy rules that is disabled
continue
}
isMultipleUpstreams := len(target.ActiveOrigins) > 1
for i, origin := range target.ActiveOrigins {
url := "http://" + origin.OriginIpOrDomain url := "http://" + origin.OriginIpOrDomain
protocol := "http" protocol := "http"
if origin.RequireTLS { if origin.RequireTLS {
@ -135,9 +139,13 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
} }
//Add the root url //Add the root url
hostIdAndName := hostid
if isMultipleUpstreams {
hostIdAndName = hostIdAndName + " (upstream:" + strconv.Itoa(i) + ")"
}
UptimeTargets = append(UptimeTargets, &uptime.Target{ UptimeTargets = append(UptimeTargets, &uptime.Target{
ID: hostid, ID: hostIdAndName,
Name: hostid, Name: hostIdAndName,
URL: url, URL: url,
Protocol: protocol, Protocol: protocol,
ProxyType: uptime.ProxyType_Host, ProxyType: uptime.ProxyType_Host,