Quellcode durchsuchen

Added apache style logging mechanism

Toby Chui vor 8 Monaten
Ursprung
Commit
03c60f5aaa

+ 3 - 2
api.go

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

+ 1 - 1
config.go

@@ -80,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
 		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
 }
 

+ 17 - 15
main.go

@@ -24,6 +24,7 @@ import (
 	"imuslab.com/zoraxy/mod/ganserv"
 	"imuslab.com/zoraxy/mod/geodb"
 	"imuslab.com/zoraxy/mod/info/logger"
+	"imuslab.com/zoraxy/mod/info/logviewer"
 	"imuslab.com/zoraxy/mod/mdns"
 	"imuslab.com/zoraxy/mod/netstat"
 	"imuslab.com/zoraxy/mod/pathrule"
@@ -52,7 +53,6 @@ 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 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 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 (
@@ -97,6 +97,7 @@ var (
 	AnalyticLoader    *analytic.DataLoader  //Data loader for Zoraxy Analytic
 	DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
 	SystemWideLogger  *logger.Logger        //Logger for Zoraxy
+	LogViewer         *logviewer.Viewer
 )
 
 // Kill signal handler. Do something before the system the core terminate.
@@ -111,33 +112,34 @@ func SetupCloseHandler() {
 }
 
 func ShutdownSeq() {
-	fmt.Println("- Shutting down " + name)
-	fmt.Println("- Closing GeoDB ")
+	SystemWideLogger.Println("Shutting down " + name)
+	SystemWideLogger.Println("Closing GeoDB ")
 	geodbStore.Close()
-	fmt.Println("- Closing Netstats Listener")
+	SystemWideLogger.Println("Closing Netstats Listener")
 	netstatBuffers.Close()
-	fmt.Println("- Closing Statistic Collector")
+	SystemWideLogger.Println("Closing Statistic Collector")
 	statisticCollector.Close()
 	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
 		mdnsTickerStop <- true
 	}
 	mdnsScanner.Close()
-	fmt.Println("- Shutting down load balancer")
+	SystemWideLogger.Println("Shutting down load balancer")
 	loadBalancer.Close()
-	fmt.Println("- Closing Certificates Auto Renewer")
+	SystemWideLogger.Println("Closing Certificates Auto Renewer")
 	acmeAutoRenewer.Close()
 	//Remove the tmp folder
-	fmt.Println("- Cleaning up tmp files")
+	SystemWideLogger.Println("Cleaning up tmp files")
 	os.RemoveAll("./tmp")
 
-	fmt.Println("- Closing system wide logger")
-	SystemWideLogger.Close()
-
-	//Close database, final
-	fmt.Println("- Stopping system database")
+	//Close database
+	SystemWideLogger.Println("Stopping system database")
 	sysdb.Close()
+
+	//Close logger
+	SystemWideLogger.Println("Closing system wide logger")
+	SystemWideLogger.Close()
 }
 
 func main() {
@@ -154,7 +156,7 @@ func main() {
 	}
 
 	if *enableAutoUpdate {
-		log.Println("[INFO] Checking required config update")
+		fmt.Println("Checking required config update")
 		update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
 	}
 

+ 41 - 36
mod/auth/auth.go

@@ -14,10 +14,10 @@ import (
 	"strings"
 
 	"encoding/hex"
-	"log"
 
 	"github.com/gorilla/sessions"
 	db "imuslab.com/zoraxy/mod/database"
+	"imuslab.com/zoraxy/mod/info/logger"
 	"imuslab.com/zoraxy/mod/utils"
 )
 
@@ -27,6 +27,7 @@ type AuthAgent struct {
 	SessionStore            *sessions.CookieStore
 	Database                *db.Database
 	LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
+	Logger                  *logger.Logger
 }
 
 type AuthEndpoints struct {
@@ -37,12 +38,12 @@ type AuthEndpoints struct {
 	Autologin     string
 }
 
-//Constructor
-func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
+// Constructor
+func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, logger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
 	store := sessions.NewCookieStore(key)
 	err := sysdb.NewTable("auth")
 	if err != nil {
-		log.Println("Failed to create auth database. Terminating.")
+		logger.Println("Failed to create auth database. Terminating.")
 		panic(err)
 	}
 
@@ -58,7 +59,7 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
 	return &newAuthAgent
 }
 
-func GetSessionKey(sysdb *db.Database) (string, error) {
+func GetSessionKey(sysdb *db.Database, logger *logger.Logger) (string, error) {
 	sysdb.NewTable("auth")
 	sessionKey := ""
 	if !sysdb.KeyExists("auth", "sessionkey") {
@@ -66,9 +67,9 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
 		rand.Read(key)
 		sessionKey = string(key)
 		sysdb.Write("auth", "sessionkey", sessionKey)
-		log.Println("[Auth] New authentication session key generated")
+		logger.PrintAndLog("auth", "New authentication session key generated", nil)
 	} 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)
 		if err != nil {
 			return "", errors.New("database read error. Is the database file corrupted?")
@@ -77,7 +78,7 @@ func GetSessionKey(sysdb *db.Database) (string, error) {
 	return sessionKey, nil
 }
 
-//This function will handle an http request and redirect to the given login address if not logged in
+// This function will handle an http request and redirect to the given login address if not logged in
 func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
 	if a.CheckAuth(r) {
 		//User already logged in
@@ -88,14 +89,14 @@ func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, hand
 	}
 }
 
-//Handle login request, require POST username and password
+// Handle login request, require POST username and password
 func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
 
 	//Get username from request using POST mode
 	username, err := utils.PostPara(r, "username")
 	if err != nil {
 		//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.")
 		return
 	}
@@ -124,11 +125,11 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
 		a.LoginUserByRequest(w, r, username, rememberme)
 
 		//Print the login message to console
-		log.Println(username + " logged in.")
+		a.Logger.PrintAndLog("auth", username+" logged in.", nil)
 		utils.SendOK(w)
 	} else {
 		//Password incorrect
-		log.Println(username + " login request rejected: " + rejectionReason)
+		a.Logger.PrintAndLog("auth", username+" login request rejected: "+rejectionReason, nil)
 
 		utils.SendErrorResponse(w, rejectionReason)
 		return
@@ -140,14 +141,14 @@ func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string
 	return succ
 }
 
-//validate the username and password, return reasons if the auth failed
+// validate the username and password, return reasons if the auth failed
 func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
 	hashedPassword := Hash(password)
 	var passwordInDB string
 	err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
 	if err != nil {
 		//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"
 	}
 
@@ -158,7 +159,7 @@ func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, passw
 	}
 }
 
-//Login the user by creating a valid session for this user
+// Login the user by creating a valid session for this user
 func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
 	session, _ := a.SessionStore.Get(r, a.SessionName)
 
@@ -181,11 +182,15 @@ func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, u
 	session.Save(r, w)
 }
 
-//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) {
 	username, err := a.GetUserName(w, r)
+	if err != nil {
+		utils.SendErrorResponse(w, "user not logged in")
+		return
+	}
 	if username != "" {
-		log.Println(username + " logged out.")
+		a.Logger.PrintAndLog("auth", username+" logged out", nil)
 	}
 	// Revoke users authentication
 	err = a.Logout(w, r)
@@ -194,7 +199,7 @@ func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	w.Write([]byte("OK"))
+	utils.SendOK(w)
 }
 
 func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
@@ -208,7 +213,7 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
 	return nil
 }
 
-//Get the current session username from request
+// Get the current session username from request
 func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
 	if a.CheckAuth(r) {
 		//This user has logged in.
@@ -220,7 +225,7 @@ func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string,
 	}
 }
 
-//Get the current session user email from request
+// Get the current session user email from request
 func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) {
 	if a.CheckAuth(r) {
 		//This user has logged in.
@@ -239,7 +244,7 @@ func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string
 	}
 }
 
-//Check if the user has logged in, return true / false in JSON
+// Check if the user has logged in, return true / false in JSON
 func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
 	if a.CheckAuth(r) {
 		utils.SendJSONResponse(w, "true")
@@ -248,7 +253,7 @@ func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-//Handle new user register. Require POST username, password, group.
+// Handle new user register. Require POST username, password, group.
 func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
 	//Get username from request
 	newusername, err := utils.PostPara(r, "username")
@@ -291,10 +296,10 @@ func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callb
 
 	//Return to the client with OK
 	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.
 func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
 	//Get username from request
 	newusername, err := utils.PostPara(r, "username")
@@ -324,10 +329,10 @@ func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Re
 
 	//Return to the client with OK
 	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
 func (a *AuthAgent) CheckAuth(r *http.Request) bool {
 	session, err := a.SessionStore.Get(r, a.SessionName)
 	if err != nil {
@@ -340,8 +345,8 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
 	return true
 }
 
-//Handle de-register of users. Require POST username.
-//THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
+// Handle de-register of users. Require POST username.
+// THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
 func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
 	//Check if the user is logged in
 	if !a.CheckAuth(r) {
@@ -365,7 +370,7 @@ func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
 
 	//Return to the client with OK
 	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 {
@@ -381,7 +386,7 @@ func (a *AuthAgent) UnregisterUser(username string) error {
 	return nil
 }
 
-//Get the number of users in the system
+// Get the number of users in the system
 func (a *AuthAgent) GetUserCounts() int {
 	entries, _ := a.Database.ListTable("auth")
 	usercount := 0
@@ -393,12 +398,12 @@ func (a *AuthAgent) GetUserCounts() int {
 	}
 
 	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
 }
 
-//List all username within the system
+// List all username within the system
 func (a *AuthAgent) ListUsers() []string {
 	entries, _ := a.Database.ListTable("auth")
 	results := []string{}
@@ -411,7 +416,7 @@ func (a *AuthAgent) ListUsers() []string {
 	return results
 }
 
-//Check if the given username exists
+// Check if the given username exists
 func (a *AuthAgent) UserExists(username string) bool {
 	userpasswordhash := ""
 	err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
@@ -421,7 +426,7 @@ func (a *AuthAgent) UserExists(username string) bool {
 	return true
 }
 
-//Update the session expire time given the request header.
+// Update the session expire time given the request header.
 func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
 	session, _ := a.SessionStore.Get(r, a.SessionName)
 	if session.Values["authenticated"].(bool) {
@@ -446,7 +451,7 @@ func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Reque
 	}
 }
 
-//Create user account
+// Create user account
 func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error {
 	//Check user already exists
 	if a.UserExists(newusername) {
@@ -470,7 +475,7 @@ func (a *AuthAgent) CreateUserAccount(newusername string, password string, email
 	return nil
 }
 
-//Hash the given raw string into sha512 hash
+// Hash the given raw string into sha512 hash
 func Hash(raw string) string {
 	h := sha512.New()
 	h.Write([]byte(raw))

+ 2 - 2
mod/auth/router.go

@@ -2,7 +2,7 @@ package auth
 
 import (
 	"errors"
-	"log"
+	"fmt"
 	"net/http"
 )
 
@@ -28,7 +28,7 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef {
 func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
 	//Check if the endpoint already registered
 	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")
 	}
 

+ 3 - 0
mod/dynamicproxy/Server.go

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

+ 6 - 7
mod/dynamicproxy/dynamicproxy.go

@@ -154,7 +154,7 @@ func (router *Router) StartProxyService() error {
 						selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession)
 						if err != nil {
 							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)
 						}
 						selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
@@ -195,7 +195,7 @@ func (router *Router) StartProxyService() error {
 				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
 			stopChan := make(chan bool)
@@ -206,7 +206,7 @@ func (router *Router) StartProxyService() error {
 				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 				defer cancel()
 				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
@@ -221,10 +221,10 @@ func (router *Router) StartProxyService() error {
 		}
 
 		//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() {
 			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 {
@@ -232,10 +232,9 @@ func (router *Router) StartProxyService() error {
 		router.tlsListener = nil
 		router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
 		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() {
 			router.server.ListenAndServe()
-			//log.Println("[DynamicProxy] " + err.Error())
 		}()
 	}
 

+ 6 - 4
mod/dynamicproxy/proxyRequestHandler.go

@@ -136,7 +136,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
 		if selectedUpstream.RequireTLS {
 			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{
 			SkipTLSValidation: selectedUpstream.SkipCertValidations,
 			SkipOriginCheck:   selectedUpstream.SkipWebSocketOriginCheck,
@@ -173,15 +173,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
 		if errors.As(err, &dnsError) {
 			http.ServeFile(w, r, "./web/hosterror.html")
 			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 {
 			http.ServeFile(w, r, "./web/rperror.html")
 			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
@@ -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) {
 	if router.Option.StatisticCollector != nil {
 		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.Logger.LogHTTPRequest(r, forwardType, statusCode)
 }

+ 2 - 0
mod/dynamicproxy/typedef.go

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

+ 35 - 14
mod/info/logger/logger.go

@@ -13,29 +13,30 @@ import (
 	Zoraxy Logger
 
 	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 {
-	LogToFile      bool   //Set enable write to file
 	Prefix         string //Prefix for log files
 	LogFolder      string //Folder to store the log  file
 	CurrentLogFile string //Current writing filename
+	logger         *log.Logger
 	file           *os.File
 }
 
-func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) {
+func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
 	err := os.MkdirAll(logFolder, 0775)
 	if err != nil {
 		return nil, err
 	}
 
 	thisLogger := Logger{
-		LogToFile: logToFile,
 		Prefix:    logFilePrefix,
 		LogFolder: logFolder,
 	}
 
+	//Create the log file if not exists
 	logFilePath := thisLogger.getLogFilepath()
 	f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
 	if err != nil {
@@ -43,6 +44,12 @@ func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger,
 	}
 	thisLogger.CurrentLogFile = logFilePath
 	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
 }
 
@@ -54,9 +61,8 @@ func (l *Logger) getLogFilepath() string {
 // PrintAndLog will log the message to file and print the log to STDOUT
 func (l *Logger) PrintAndLog(title string, message string, originalError error) {
 	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
@@ -64,18 +70,26 @@ func (l *Logger) Println(v ...interface{}) {
 	//Convert the array of interfaces into string
 	message := fmt.Sprint(v...)
 	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()
-	if l.LogToFile {
+	if l.logger == nil || copyToSTDOUT {
+		//Use STDOUT instead of logger
+		if originalError == nil {
+			fmt.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
+		} else {
+			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.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n")
+			l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:info] " + errorMessage)
 		} else {
-			l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n")
+			l.logger.Println("[" + time.Now().Format("2006-01-02 15:04:05.000000") + "] [" + title + "] [system:error] " + errorMessage + ": " + originalError.Error())
 		}
 	}
 
@@ -87,14 +101,21 @@ func (l *Logger) ValidateAndUpdateLogFilepath() {
 	if l.CurrentLogFile != expectedCurrentLogFilepath {
 		//Change of month. Update to a new log file
 		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)
 		if err != nil {
-			log.Println("[Logger] Unable to create new log. Logging to file disabled.")
-			l.LogToFile = false
+			log.Println("Unable to create new log. Logging is disabled: ", err.Error())
+			l.logger = nil
 			return
 		}
 		l.CurrentLogFile = expectedCurrentLogFilepath
 		l.file = f
+
+		//Start a new logger
+		logger := log.New(f, "", log.Default().Flags())
+		l.logger = logger
 	}
 }
 

+ 32 - 0
mod/info/logger/trafficlog.go

@@ -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)
+	}()
+}

+ 7 - 9
mod/info/logviewer/logviewer.go

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

+ 7 - 6
reverseproxy.go

@@ -98,9 +98,10 @@ func ReverseProxtInit() {
 		WebDirectory:       *staticWebServerRoot,
 		AccessController:   accessController,
 		LoadBalancer:       loadBalancer,
+		Logger:             SystemWideLogger,
 	})
 	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
 	}
 
@@ -115,7 +116,7 @@ func ReverseProxtInit() {
 	for _, conf := range confs {
 		err := LoadReverseProxyConfig(conf)
 		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
 		}
 	}
@@ -124,7 +125,7 @@ func ReverseProxtInit() {
 		//Root config not set (new deployment?), use internal static web server as root
 		defaultRootRouter, err := GetDefaultRootConfig()
 		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
 		}
 		dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
@@ -412,7 +413,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 	//Save the config to file
 	err = SaveReverseProxyConfig(proxyEndpointCreated)
 	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
 	}
 
@@ -555,7 +556,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
 	newAlias := []string{}
 	err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
 	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")
 		return
 	}
@@ -577,7 +578,7 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
 	err = SaveReverseProxyConfig(newProxyEndpoint)
 	if err != nil {
 		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)

+ 15 - 10
start.go

@@ -20,6 +20,7 @@ import (
 	"imuslab.com/zoraxy/mod/ganserv"
 	"imuslab.com/zoraxy/mod/geodb"
 	"imuslab.com/zoraxy/mod/info/logger"
+	"imuslab.com/zoraxy/mod/info/logviewer"
 	"imuslab.com/zoraxy/mod/mdns"
 	"imuslab.com/zoraxy/mod/netstat"
 	"imuslab.com/zoraxy/mod/pathrule"
@@ -47,6 +48,18 @@ var (
 )
 
 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
 	db, err := database.NewDatabase("sys.db", false)
 	if err != nil {
@@ -61,11 +74,11 @@ func startupSequence() {
 	os.MkdirAll("./conf/proxy/", 0775)
 
 	//Create an auth agent
-	sessionKey, err := auth.GetSessionKey(sysdb)
+	sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
 	if err != nil {
 		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
 		http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
 	})
@@ -76,14 +89,6 @@ func startupSequence() {
 		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
 	db.NewTable("redirect")
 	redirectAllowRegexp := false

+ 2 - 2
web/components/zgrok.html → web/components/sso.html

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

+ 6 - 1
web/components/utils.html

@@ -120,7 +120,12 @@
         <!-- Config Tools -->
         <h3>System Backup & Restore</h3>
         <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>
         <!-- System Information -->
         <div id="zoraxyinfo">

+ 8 - 8
web/index.html

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

+ 155 - 0
web/snippet/logview.html

@@ -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>