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/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
}

View File

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

View File

@ -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,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 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 (
name = "Zoraxy"
version = "3.0.8"
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()
/*
@ -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))
}

View File

@ -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, systemLogger *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.")
systemLogger.Println("Failed to create auth database. Terminating.")
panic(err)
}
@ -52,13 +53,14 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
SessionStore: store,
Database: sysdb,
LoginRedirectionHandler: loginRedirectionHandler,
Logger: systemLogger,
}
//Return the authAgent
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 +68,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 +79,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 +90,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 +126,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 +142,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 +160,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 +183,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 +200,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 +214,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 +226,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 +245,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 +254,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 +297,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 +330,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 +346,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 +371,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 +387,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 +399,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 +417,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 +427,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 +452,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 +476,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))

View File

@ -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")
}

View File

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

View File

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

View File

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

View File

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

View File

@ -13,29 +13,31 @@ 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) {
// Create a new logger that log to files
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,9 +45,26 @@ 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
}
// 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 {
year, month, _ := time.Now().Date()
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
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 +82,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 {
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 {
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)
func (l *Logger) ValidateAndUpdateLogFilepath() {
if l.file == nil {
return
}
expectedCurrentLogFilepath := l.getLogFilepath()
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
}
}

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 (
"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)

View File

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

View File

@ -2,16 +2,23 @@ package uptime
import (
"encoding/json"
"errors"
"log"
"net/http"
"net/http/cookiejar"
"strconv"
"strings"
"time"
"golang.org/x/net/publicsuffix"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
const (
logModuleName = "uptime-monitor"
)
type Record struct {
Timestamp int64
ID string
@ -42,6 +49,7 @@ type Config struct {
Targets []*Target
Interval int
MaxRecordsStore int
Logger *logger.Logger
}
type Monitor struct {
@ -64,6 +72,12 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
Config: config,
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
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
done := make(chan bool)
@ -77,7 +91,7 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
case <-done:
return
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()
}
}
@ -91,7 +105,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
//For each target to check online, do the following
var thisRecord Record
if target.Protocol == "http" || target.Protocol == "https" {
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
online, laterncy, statusCode := m.getWebsiteStatusWithLatency(target.URL)
thisRecord = Record{
Timestamp: time.Now().Unix(),
ID: target.ID,
@ -104,7 +118,7 @@ func (m *Monitor) ExecuteUptimeCheck() {
}
} else {
log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
m.Config.Logger.PrintAndLog(logModuleName, "Unknown protocol: "+target.Protocol, errors.New("unsupported protocol"))
continue
}
@ -124,8 +138,6 @@ func (m *Monitor) ExecuteUptimeCheck() {
m.OnlineStatusLog[target.ID] = thisRecords
}
}
//TODO: Write results to db
}
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
func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
start := time.Now().UnixNano() / int64(time.Millisecond)
statusCode, err := getWebsiteStatus(url)
end := time.Now().UnixNano() / int64(time.Millisecond)
if err != nil {
log.Println(err.Error())
m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
return false, 0, 0
} else {
diff := end - start

View File

@ -5,13 +5,13 @@ import (
_ "embed"
"errors"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
"imuslab.com/zoraxy/mod/webserv/filemanager"
)
@ -30,6 +30,7 @@ type WebServerOptions struct {
EnableDirectoryListing bool //Enable listing of directory
WebRoot string //Folder for stroing the static web folders
EnableWebDirManager bool //Enable web file manager to handle files in web directory
Logger *logger.Logger //System logger
Sysdb *database.Database //Database for storing configs
}
@ -45,13 +46,16 @@ type WebServer struct {
// NewWebServer creates a new WebServer instance. One instance only
func NewWebServer(options *WebServerOptions) *WebServer {
if options.Logger == nil {
options.Logger, _ = logger.NewFmtLogger()
}
if !utils.FileExists(options.WebRoot) {
//Web root folder not exists. Create one with default templates
os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
indexTemplate, err := templates.ReadFile("templates/index.html")
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 {
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.
func (ws *WebServer) ChangePort(port string) error {
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 {
@ -119,6 +123,7 @@ func (ws *WebServer) ChangePort(port string) error {
return err
}
ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
ws.option.Sysdb.Write("webserv", "port", port)
return nil
@ -141,7 +146,7 @@ func (ws *WebServer) Start() error {
//Check if the port is usable
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
@ -159,12 +164,12 @@ func (ws *WebServer) Start() error {
go func() {
if err := ws.server.ListenAndServe(); err != nil {
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.option.Sysdb.Write("webserv", "enabled", true)
return nil
@ -182,7 +187,7 @@ func (ws *WebServer) Stop() error {
if err := ws.server.Close(); err != nil {
return err
}
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
ws.isRunning = false
ws.option.Sysdb.Write("webserv", "enabled", false)
return nil

View File

@ -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)
@ -143,8 +144,9 @@ func ReverseProxtInit() {
//This must be done in go routine to prevent blocking on system startup
uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
Interval: 300, //5 minutes
MaxRecordsStore: 288, //1 day
Interval: 300, //5 minutes
MaxRecordsStore: 288, //1 day
Logger: SystemWideLogger, //Logger
})
SystemWideLogger.Println("Uptime Monitor background service started")
@ -412,7 +414,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
}
@ -480,15 +482,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
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
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
if err != nil {
@ -498,11 +491,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
//Generate a new proxyEndpoint from the new config
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.RequireBasicAuth = requireBasicAuth
newProxyEndpoint.RequireRateLimit = requireRateLimit
@ -521,9 +509,6 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
//Save it to file
SaveReverseProxyConfig(newProxyEndpoint)
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w)
}
@ -555,7 +540,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 +562,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)
@ -881,6 +866,10 @@ func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "unable to save updated rule")
return
}
//Update uptime monitor
UpdateUptimeMonitorTargets()
utils.SendOK(w)
}

View File

@ -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
@ -134,6 +139,7 @@ func startupSequence() {
WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager,
Logger: SystemWideLogger,
})
//Restore the web server to previous shutdown state
staticWebServer.RestorePreviousState()

View File

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

View File

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

View File

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

View File

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

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 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 class="ui tab basic segment" data-tab="newupstream">

View File

@ -115,7 +115,7 @@ func UpdateUptimeMonitorTargets() {
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{}
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
protocol := "http"
if origin.RequireTLS {
@ -135,9 +139,13 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
}
//Add the root url
hostIdAndName := hostid
if isMultipleUpstreams {
hostIdAndName = hostIdAndName + " (upstream:" + strconv.Itoa(i) + ")"
}
UptimeTargets = append(UptimeTargets, &uptime.Target{
ID: hostid,
Name: hostid,
ID: hostIdAndName,
Name: hostIdAndName,
URL: url,
Protocol: protocol,
ProxyType: uptime.ProxyType_Host,