diff --git a/BinaryAssets/Access.go b/BinaryAssets/Access.go index bee06d9..a02f0f2 100644 --- a/BinaryAssets/Access.go +++ b/BinaryAssets/Access.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/BinaryAssets/SourceCodePro" ) +// Reads the content for a file. func GetData(filename string) (data []byte) { if obj, err := SourceCodePro.Asset(filename); err != nil { return diff --git a/BinaryAssets/Handlers.go b/BinaryAssets/Handlers.go index 834c43e..d3ec737 100644 --- a/BinaryAssets/Handlers.go +++ b/BinaryAssets/Handlers.go @@ -10,8 +10,10 @@ import ( "strings" ) +// Handler to access the binary assets from the web. func HandlerBinaryAssets(response http.ResponseWriter, request *http.Request) { + // Case: The server is going down. if Shutdown.IsDown() { http.NotFound(response, request) return @@ -31,12 +33,14 @@ func HandlerBinaryAssets(response http.ResponseWriter, request *http.Request) { fileType = mimeType.MimeType } + // Case: No MIME type determined? if fileType == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNOTFOUND, `The mime type is unknown.`, path) http.NotFound(response, request) return } + // Read the content: contentData := GetData(path) if contentData == nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `The desired file was not found.`, path) @@ -44,6 +48,7 @@ func HandlerBinaryAssets(response http.ResponseWriter, request *http.Request) { return } + // Write the meta data and the content to the client: fileLenText := fmt.Sprintf(`%d`, len(contentData)) response.Header().Add(`Content-Length`, fileLenText) response.Header().Add(`Content-Type`, fileType) diff --git a/BinaryAssets/Variables.go b/BinaryAssets/Variables.go index bc329ed..60646f4 100644 --- a/BinaryAssets/Variables.go +++ b/BinaryAssets/Variables.go @@ -5,5 +5,5 @@ import ( ) var ( - senderName LM.Sender = `System::BinaryAssets` + senderName LM.Sender = `System::BinaryAssets` // This is the name for logging event from this package ) diff --git a/Configuration/Init.go b/Configuration/Init.go index 9682b9a..545cd71 100644 --- a/Configuration/Init.go +++ b/Configuration/Init.go @@ -1,5 +1,6 @@ package Configuration +// This is the init function for this package. func init() { readConfiguration() isInit = true diff --git a/Configuration/ReadConfiguration.go b/Configuration/ReadConfiguration.go index 7a9ede6..88ebd20 100644 --- a/Configuration/ReadConfiguration.go +++ b/Configuration/ReadConfiguration.go @@ -8,8 +8,8 @@ import ( "path/filepath" ) +// Function to read the configuration file. func readConfiguration() { - if isInit { Log.LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityNone, Meta.ImpactNone, Meta.MessageNameINIT, `The configuration package is already init!`) return @@ -17,13 +17,14 @@ func readConfiguration() { Log.LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameCONFIGURATION, `Init of configuration starting.`) } + // Access to the working directory? currentDir, dirError := os.Getwd() - if dirError != nil { panic(`Was not able to read the working directory: ` + dirError.Error()) return } + // Access to the configuration file? currentPath := filepath.Join(currentDir, filename) if _, errFile := os.Stat(currentPath); errFile != nil { if os.IsNotExist(errFile) { @@ -33,6 +34,7 @@ func readConfiguration() { } } + // Open the file: file, fileError := os.Open(currentPath) defer file.Close() @@ -41,6 +43,7 @@ func readConfiguration() { return } + // Try to decode / parse the file: decoder := json.NewDecoder(file) decError := decoder.Decode(&configuration) diff --git a/Configuration/Variables.go b/Configuration/Variables.go index 94c0ba6..7e1b794 100644 --- a/Configuration/Variables.go +++ b/Configuration/Variables.go @@ -6,8 +6,8 @@ import ( ) var ( - filename = "configuration.json" // Where is the configuration located? - configuration Meta.Configuration = Meta.Configuration{} // The loaded configuration - isInit = false // Is the configuration loaded? - senderName LM.Sender = `System::Configuration` + filename = "configuration.json" // Where is the configuration located? + configuration Meta.Configuration = Meta.Configuration{} // The loaded configuration + isInit = false // Is the configuration loaded? + senderName LM.Sender = `System::Configuration` // This is the name for logging event from this package ) diff --git a/ConfigurationDB/CheckConfiguration.go b/ConfigurationDB/CheckConfiguration.go index 55d6c2b..d23f046 100644 --- a/ConfigurationDB/CheckConfiguration.go +++ b/ConfigurationDB/CheckConfiguration.go @@ -6,6 +6,7 @@ import ( "gopkg.in/mgo.v2/bson" ) +// Internal function to check the system configuration. func checkConfiguration() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Check now the configuration database.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Done checking the configuration database.`) @@ -53,15 +54,14 @@ func checkConfiguration() { Disallow:`) } -/* -Use this function to ensure that the database contains at least a default value for the configuration. -*/ +// Use this function to ensure that the database contains at least a default value for the configuration. func CheckSingleConfigurationPresentsAndAddIfMissing(name, value string) { if !checkSingleConfigurationPresents(name) { addSingleConfiguration(name, value) } } +// Check if a configuration value is present. func checkSingleConfigurationPresents(name string) (result bool) { selection := bson.D{{"Name", name}} count, _ := collection.Find(selection).Count() @@ -69,6 +69,7 @@ func checkSingleConfigurationPresents(name string) (result bool) { return count > 0 } +// Adds a configuration value. func addSingleConfiguration(name, value string) { entry := ConfigurationDBEntry{} entry.Name = name diff --git a/ConfigurationDB/Init.go b/ConfigurationDB/Init.go index baf74ac..7d595b4 100644 --- a/ConfigurationDB/Init.go +++ b/ConfigurationDB/Init.go @@ -7,8 +7,8 @@ import ( "gopkg.in/mgo.v2" ) +// The init function for this package. func init() { - config := Configuration.Read() // Connect to MongoDB: @@ -40,6 +40,8 @@ func init() { // Take care about the index: collection.EnsureIndexKey(`Name`) + // Check the system configuration: checkConfiguration() + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `The configuration database is now ready.`) } diff --git a/ConfigurationDB/Read.go b/ConfigurationDB/Read.go index d23c62e..f312758 100644 --- a/ConfigurationDB/Read.go +++ b/ConfigurationDB/Read.go @@ -6,9 +6,7 @@ import ( "gopkg.in/mgo.v2/bson" ) -/* -This function reads the current configuration value. -*/ +// This function reads the current configuration value. func Read(name string) (value string) { if name == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to read a configuration out of the database.`, `The given name was nil!`) diff --git a/ConfigurationDB/Scheme.go b/ConfigurationDB/Scheme.go index fa02b6a..d551fd1 100644 --- a/ConfigurationDB/Scheme.go +++ b/ConfigurationDB/Scheme.go @@ -1,5 +1,6 @@ package ConfigurationDB +// The type for a configuration entry. type ConfigurationDBEntry struct { Name string `bson:"Name"` Value string `bson:"Value"` diff --git a/ConfigurationDB/Shutdown.go b/ConfigurationDB/Shutdown.go index 3909a32..bd50f16 100644 --- a/ConfigurationDB/Shutdown.go +++ b/ConfigurationDB/Shutdown.go @@ -5,15 +5,11 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) -/* -Do not use this type by your own! It is a Ocean internal type to provide a shutdown function for the configuration database. -*/ +// The type for the shutdown function. type ShutdownFunction struct { } -/* -If the Ocean server is shutting down, this function is called to close the database. -*/ +// If the Ocean server is shutting down, this function is called to close the database. func (a ShutdownFunction) Shutdown() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Close now the configuration database connection.`) db.Logout() diff --git a/ConfigurationDB/Variables.go b/ConfigurationDB/Variables.go index f2b550d..a45a838 100644 --- a/ConfigurationDB/Variables.go +++ b/ConfigurationDB/Variables.go @@ -7,9 +7,9 @@ import ( ) var ( - session *mgo.Session = nil - db *mgo.Database = nil - collection *mgo.Collection = nil - config Meta.Configuration = Meta.Configuration{} - senderName LM.Sender = `System::ConfigurationDB` + session *mgo.Session = nil // The database session + db *mgo.Database = nil // The database + collection *mgo.Collection = nil // The database collection + config Meta.Configuration = Meta.Configuration{} // The configuration file's data + senderName LM.Sender = `System::ConfigurationDB` // This is the name for logging event from this package ) diff --git a/ConfigurationDB/Write.go b/ConfigurationDB/Write.go index 6bc9590..b0b3b19 100644 --- a/ConfigurationDB/Write.go +++ b/ConfigurationDB/Write.go @@ -6,15 +6,14 @@ import ( "gopkg.in/mgo.v2/bson" ) -/* -This function writes the configuration value. -*/ +// This function writes the configuration value. func Write(name, value string) { if name == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to write a configuration to the database.`, `The given name was nil!`) return } + // Read the current value: result := ConfigurationDBEntry{} if errFind := collection.Find(bson.D{{"Name", name}}).One(&result); errFind != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to write a configuration to the database.`, `Error while find.`, errFind.Error()) @@ -22,6 +21,8 @@ func Write(name, value string) { } result.Value = value + + // Update the database: collection.Update(bson.D{{"Name", name}}, result) return } diff --git a/CustomerDB/AccessDB.go b/CustomerDB/AccessDB.go index 93035b4..06b743c 100644 --- a/CustomerDB/AccessDB.go +++ b/CustomerDB/AccessDB.go @@ -4,25 +4,19 @@ import ( "gopkg.in/mgo.v2" ) -/* -Get the database instance of the MGo Mongo driver. -*/ +// Get the database instance of the mgo MongoDB driver. func DB() (session *mgo.Session, database *mgo.Database) { session = mainSession.Copy() database = session.DB(databaseDB) database.Login(databaseUsername, databasePassword) - return } -/* -Get directly the GridFS instance of the Mgo Mongo driver. -*/ +// Get directly the GridFS instance of the mgo MongoDB driver. func GridFS() (session *mgo.Session, filesystem *mgo.GridFS) { session = mainSession.Copy() database := session.DB(databaseDB) database.Login(databaseUsername, databasePassword) filesystem = database.GridFS(`fs`) - return } diff --git a/CustomerDB/Init.go b/CustomerDB/Init.go index b139299..6f63b18 100644 --- a/CustomerDB/Init.go +++ b/CustomerDB/Init.go @@ -7,10 +7,11 @@ import ( "gopkg.in/mgo.v2" ) +// The init function for this package. func init() { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Init the customer database.`) + // Read the configuration values: databaseHost := ConfigurationDB.Read(`CustomerDBHost`) databaseDB = ConfigurationDB.Read(`CustomerDBDatabase`) databaseUsername = ConfigurationDB.Read(`CustomerDBUsername`) diff --git a/CustomerDB/Shutdown.go b/CustomerDB/Shutdown.go index 2ca5e49..937f599 100644 --- a/CustomerDB/Shutdown.go +++ b/CustomerDB/Shutdown.go @@ -5,15 +5,11 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) -/* -Please do not use this type. It is an internal type of Ocean to provide a shutdown function! -*/ +// The shutdown type. type ShutdownFunction struct { } -/* -This function is called if the Ocean server is shutting down. -*/ +// The shutdown function for this package. func (a ShutdownFunction) Shutdown() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Close now the customer database connection.`) } diff --git a/CustomerDB/Variables.go b/CustomerDB/Variables.go index f43bd41..76ae12e 100644 --- a/CustomerDB/Variables.go +++ b/CustomerDB/Variables.go @@ -6,9 +6,9 @@ import ( ) var ( - mainSession *mgo.Session = nil - senderName LM.Sender = `System::CustomerDB` - databaseUsername string = `` - databasePassword string = `` - databaseDB string = `` + mainSession *mgo.Session = nil // The session for the customer database + senderName LM.Sender = `System::CustomerDB` // This is the name for logging event from this package + databaseUsername string = `` // The user's name + databasePassword string = `` // The user's password + databaseDB string = `` // The database ) diff --git a/Handlers/AddHandler.go b/Handlers/AddHandler.go index 3598752..9eb9754 100644 --- a/Handlers/AddHandler.go +++ b/Handlers/AddHandler.go @@ -4,10 +4,12 @@ import ( "net/http" ) +// Function to add a new public handler. func AddPublicHandler(pattern string, handler func(http.ResponseWriter, *http.Request)) { muxPublic.HandleFunc(pattern, handler) } +// Function to add a new private handler. func AddAdminHandler(pattern string, handler func(http.ResponseWriter, *http.Request)) { muxAdmin.HandleFunc(pattern, handler) } diff --git a/Handlers/GetMuxes.go b/Handlers/GetMuxes.go index b2838e7..3ee3ced 100644 --- a/Handlers/GetMuxes.go +++ b/Handlers/GetMuxes.go @@ -4,11 +4,13 @@ import ( "net/http" ) +// Returns the muxer for the public web server. func GetPublicMux() (mux *http.ServeMux) { mux = muxPublic return } +// Returns the muxer for the private web server. func GetAdminMux() (mux *http.ServeMux) { mux = muxAdmin return diff --git a/Handlers/Init.go b/Handlers/Init.go index a19d3c9..270cd59 100644 --- a/Handlers/Init.go +++ b/Handlers/Init.go @@ -1,5 +1,5 @@ package Handlers +// The init function for this package. func init() { - } diff --git a/Handlers/Variables.go b/Handlers/Variables.go index 37dee9d..6cbc580 100644 --- a/Handlers/Variables.go +++ b/Handlers/Variables.go @@ -6,7 +6,7 @@ import ( ) var ( - senderName LM.Sender = `System::Handlers` - muxPublic *http.ServeMux = http.NewServeMux() - muxAdmin *http.ServeMux = http.NewServeMux() + senderName LM.Sender = `System::Handlers` // This is the name for logging event from this package + muxPublic *http.ServeMux = http.NewServeMux() // The muxer for the public web server + muxAdmin *http.ServeMux = http.NewServeMux() // The muxer for the private web server ) diff --git a/ICCC/ListenerCache.go b/ICCC/CacheTimerLogic.go similarity index 69% rename from ICCC/ListenerCache.go rename to ICCC/CacheTimerLogic.go index 81c3501..3512f4b 100644 --- a/ICCC/ListenerCache.go +++ b/ICCC/CacheTimerLogic.go @@ -1,72 +1,40 @@ -package ICCC - -import ( - "fmt" - "github.com/SommerEngineering/Ocean/ICCC/Scheme" - "github.com/SommerEngineering/Ocean/Log" - LM "github.com/SommerEngineering/Ocean/Log/Meta" - "github.com/SommerEngineering/Ocean/Shutdown" - "gopkg.in/mgo.v2/bson" - "time" -) - -func InitCacheNow() { - startCacheTimerLock.Lock() - defer startCacheTimerLock.Unlock() - - if cacheTimerRunning { - return - } - - cacheTimerLogic(false) -} - -func StartCacheTimer() { - initCacheTimer() -} - -func initCacheTimer() { - startCacheTimerLock.Lock() - defer startCacheTimerLock.Unlock() - - if cacheTimerRunning { - return - } else { - cacheTimerRunning = true - } - - go func() { - for { - cacheTimerLogic(true) - } - }() -} - -func cacheTimerLogic(waiting bool) { - if Shutdown.IsDown() { - return - } - - lastCount := cacheListenerDatabase.Len() - selection := bson.D{{`IsActive`, true}} - entriesIterator := collectionListener.Find(selection).Iter() - entry := Scheme.Listener{} - - cacheListenerDatabaseLock.Lock() - cacheListenerDatabase.Init() - for entriesIterator.Next(&entry) { - cacheListenerDatabase.PushBack(entry) - } - - cacheListenerDatabaseLock.Unlock() - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCount), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len())) - - if waiting { - nextDuration := time.Duration(5) * time.Minute - if cacheListenerDatabase.Len() == 0 { - nextDuration = time.Duration(10) * time.Second - } - - time.Sleep(nextDuration) - } -} +package ICCC + +import ( + "fmt" + "github.com/SommerEngineering/Ocean/ICCC/Scheme" + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/Shutdown" + "gopkg.in/mgo.v2/bson" + "time" +) + +func cacheTimerLogic(waiting bool) { + if Shutdown.IsDown() { + return + } + + lastCount := cacheListenerDatabase.Len() + selection := bson.D{{`IsActive`, true}} + entriesIterator := collectionListener.Find(selection).Iter() + entry := Scheme.Listener{} + + cacheListenerDatabaseLock.Lock() + cacheListenerDatabase.Init() + for entriesIterator.Next(&entry) { + cacheListenerDatabase.PushBack(entry) + } + + cacheListenerDatabaseLock.Unlock() + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCount), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len())) + + if waiting { + nextDuration := time.Duration(5) * time.Minute + if cacheListenerDatabase.Len() == 0 { + nextDuration = time.Duration(10) * time.Second + } + + time.Sleep(nextDuration) + } +} diff --git a/ICCC/Data2Message.go b/ICCC/Data2Message.go index ffc831f..56d3bbc 100644 --- a/ICCC/Data2Message.go +++ b/ICCC/Data2Message.go @@ -6,6 +6,7 @@ import ( "strconv" ) +// Function to convert the HTTP data back to a message. func Data2Message(target interface{}, data map[string][]string) (channel, command string, obj interface{}) { if data == nil || len(data) == 0 { channel = `` @@ -14,12 +15,15 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman return } + // Use reflection for the target type: element := reflect.ValueOf(target) element = element.Elem() elementType := element.Type() channel = data[`channel`][0] command = data[`command`][0] + + // Use the order of the destination type's fields: for i := 0; i < element.NumField(); i++ { field := element.Field(i) switch field.Kind().String() { @@ -53,6 +57,7 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman v, _ := strconv.ParseUint(mapValue, 16, 8) field.SetUint(v) + // Case: Arrays... case `slice`: sliceInterface := field.Interface() sliceKind := reflect.ValueOf(sliceInterface).Type().String() diff --git a/ICCC/Doc.go b/ICCC/Doc.go index 940b5e3..76578e9 100644 --- a/ICCC/Doc.go +++ b/ICCC/Doc.go @@ -1,21 +1,15 @@ /* -This is the "[I]nter-[C]omponent [C]ommunication [C]hannel". It is a minimal -messaging service to connect different servers or even different parts of -huge systems across programming languages. +This is the "[I]nter-[C]omponent [C]ommunication [C]hannel". It is a minimal messaging service to connect different servers or even different parts of huge systems across programming languages. -The basis idea is to create such messaging service on top of HTTP, because -every programming language is able to process HTTP. Therefore, all messages -are transformed to HTTP form values (with URL encoding). +The basis idea is to create such messaging service on top of HTTP, because every programming language is able to process HTTP. Therefore, all messages are transformed to HTTP form values (with URL encoding). -To be able to marshal / parse the data back to objects, some additional -information is added: +To be able to marshal / parse the data back to objects, some additional information is added: Example 01: name=str:Surname value=Sommer -The HTTP form name is 'str:Surname' and the value is 'Sommer'. The 'str' is -the indicator for the data type, in this case it is a string. +The HTTP form name is 'str:Surname' and the value is 'Sommer'. The 'str' is the indicator for the data type, in this case it is a string. Known data types are: * str := string @@ -48,7 +42,9 @@ channel=CHANNEL [any count of data tuples] InternalCommPassword=[configured communication password e.g. an UUID etc.] -If you want to build a distributed system across the Internet, please use e.g. SSH tunnels -to keep things secret. +If you want to build a distributed system across the Internet, please use e.g. SSH tunnels to keep things secret. + +Constrains to the environment: +The web server cannot reorder the fields of the request or response. The order of fields at the data object (message) must correspond with the order of fields inside the HTTP message. Therefore, a reorder is not possible at the moment. */ package ICCC diff --git a/ICCC/HTTPConnector.go b/ICCC/HTTPConnector.go index 866990a..aa35b58 100644 --- a/ICCC/HTTPConnector.go +++ b/ICCC/HTTPConnector.go @@ -8,36 +8,49 @@ import ( "net/http" ) +// The HTTP handler for ICCC. func ICCCHandler(response http.ResponseWriter, request *http.Request) { + + // Cannot parse the form? if errParse := request.ParseForm(); errParse != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `Was not able to parse the HTTP form data from an ICCC message!`) http.NotFound(response, request) return } + // Read the data out of the request: messageData := map[string][]string(request.PostForm) + // The data must contain at least three fields (command, channel & communication password) if len(messageData) < 3 { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `The ICCC message contains not enough data: At least the channel, command and password is required!`) http.NotFound(response, request) return } + // Read the meta data: channel := messageData[`channel`][0] command := messageData[`command`][0] password := messageData[`InternalCommPassword`][0] + // Check the password: if password != Tools.InternalCommPassword() { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactNone, LM.MessageNamePASSWORD, `Received a ICCC message with wrong password!`, request.RemoteAddr) http.NotFound(response, request) return } + // Build the key for the mapping of the listener cache: key := fmt.Sprintf(`%s::%s`, channel, command) + + // Get the matching listener listener := listeners[key] + if listener == nil { + // Case: No such listener Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to find the correct listener for these ICCC message.`, `channel=`+channel, `command`+command, `hostname=`+Tools.ThisHostname()) } else { + // Case: Everything is fine => deliver the message listener(messageData) } } diff --git a/ICCC/Init.go b/ICCC/Init.go index f911866..e525be4 100644 --- a/ICCC/Init.go +++ b/ICCC/Init.go @@ -7,16 +7,23 @@ import ( "github.com/SommerEngineering/Ocean/Tools" ) +// Init this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of ICCC.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init ICCC.`) + // Create the list as cache for all global listener (not only listener from this server): cacheListenerDatabase = list.New() + + // Create a mapping as cache for all local listener end-points (functions): listeners = make(map[string]func(data map[string][]string)) // Using the local IP address: correctAddressWithPort = Tools.LocalIPAddressAndPort() + // Init the database: initDB() + + // Register this server to the listener (if not present): registerHost2Database() } diff --git a/ICCC/InitCacheNow.go b/ICCC/InitCacheNow.go new file mode 100644 index 0000000..d643475 --- /dev/null +++ b/ICCC/InitCacheNow.go @@ -0,0 +1,13 @@ +package ICCC + +// Starts the timer cache once and exit it after (no thread, no endless loop). +func InitCacheNow() { + startCacheTimerLock.Lock() + defer startCacheTimerLock.Unlock() + + if cacheTimerRunning { + return + } + + cacheTimerLogic(false) +} diff --git a/ICCC/InitCacheTimer.go b/ICCC/InitCacheTimer.go new file mode 100644 index 0000000..1f3e9cc --- /dev/null +++ b/ICCC/InitCacheTimer.go @@ -0,0 +1,21 @@ +package ICCC + +// Setup and starts the cache timer. +func initCacheTimer() { + startCacheTimerLock.Lock() + defer startCacheTimerLock.Unlock() + + if cacheTimerRunning { + return + } else { + cacheTimerRunning = true + } + + // Start another thread with the timer logic: + go func() { + // Endless loop: + for { + cacheTimerLogic(true) + } + }() +} diff --git a/ICCC/InitDB.go b/ICCC/InitDB.go index 3fcc623..98dd9f6 100644 --- a/ICCC/InitDB.go +++ b/ICCC/InitDB.go @@ -7,6 +7,7 @@ import ( "gopkg.in/mgo.v2" ) +// Init the database. func initDB() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of the ICCC collections.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init the ICCC collection.`) @@ -14,6 +15,7 @@ func initDB() { // Get the database: dbSession, db = CustomerDB.DB() + // Case: Error? if db == nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the customer database.`) return @@ -23,7 +25,9 @@ func initDB() { collectionListener = db.C(`ICCCListener`) collectionHosts = db.C(`ICCCHosts`) + // // Take care about the indexes for ICCCListener: + // collectionListener.EnsureIndexKey(`Command`) collectionListener.EnsureIndexKey(`Command`, `IsActive`) @@ -45,7 +49,9 @@ func initDB() { indexName1.Unique = true collectionListener.EnsureIndex(indexName1) + // // Index for hosts: + // collectionHosts.EnsureIndexKey(`Hostname`, `IPAddressPort`) indexName2 := mgo.Index{} diff --git a/ICCC/Message2Data.go b/ICCC/Message2Data.go index fa41998..f0d4574 100644 --- a/ICCC/Message2Data.go +++ b/ICCC/Message2Data.go @@ -6,8 +6,13 @@ import ( "strconv" ) +// Function to convert an ICCC message to HTTP data. func message2Data(channel, command string, message interface{}) (data map[string][]string) { + + // Create the map: data = make(map[string][]string) + + // Add the meta information: data[`command`] = []string{command} data[`channel`] = []string{channel} @@ -15,9 +20,12 @@ func message2Data(channel, command string, message interface{}) (data map[string return } + // Use reflection to determine the types: element := reflect.ValueOf(message) elementType := element.Type() + // Iterate over all fields of the data type. + // Transform the data regarding the type. for i := 0; i < element.NumField(); i++ { field := element.Field(i) keyName := elementType.Field(i).Name @@ -44,6 +52,7 @@ func message2Data(channel, command string, message interface{}) (data map[string key := fmt.Sprintf(`ui8:%s`, keyName) data[key] = []string{strconv.FormatUint(field.Uint(), 16)} + // Case: Arrays... case `slice`: sliceLen := field.Len() if sliceLen > 0 { diff --git a/ICCC/Register2Database.go b/ICCC/Register2Database.go index 838e299..e19e3d6 100644 --- a/ICCC/Register2Database.go +++ b/ICCC/Register2Database.go @@ -7,10 +7,13 @@ import ( "gopkg.in/mgo.v2/bson" ) +// The internal function to register a command to ICCC. func register2Database(channel, command string) { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register this ICCC command in to the database.`, `channel=`+channel, `command=`+command) + // // Case: Exist and active :) + // emptyEntry := Scheme.Listener{} selection := bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, correctAddressWithPort}, {`IsActive`, true}} count1, _ := collectionListener.Find(selection).Count() @@ -20,7 +23,9 @@ func register2Database(channel, command string) { return } + // // Case: Exist but not active + // selection = bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, correctAddressWithPort}, {`IsActive`, false}} notActiveEntry := Scheme.Listener{} collectionListener.Find(selection).One(¬ActiveEntry) @@ -32,7 +37,9 @@ func register2Database(channel, command string) { return } + // // Case: Not exist + // Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `This ICCC command is not known.`, `Create now a new entry!`) entry := Scheme.Listener{} diff --git a/ICCC/RegisterHost2Database.go b/ICCC/RegisterHost2Database.go index 7073d4e..df8ba78 100644 --- a/ICCC/RegisterHost2Database.go +++ b/ICCC/RegisterHost2Database.go @@ -8,20 +8,31 @@ import ( "gopkg.in/mgo.v2/bson" ) +// Function to register this server to the ICCC. func registerHost2Database() { + + // Create the host entry: host := Scheme.Host{} host.Hostname = Tools.ThisHostname() host.IPAddressPort = correctAddressWithPort + // The query to find already existing entries: selection := bson.D{{`Hostname`, host.Hostname}, {`IPAddressPort`, host.IPAddressPort}} + + // Count the already existing entries: count, _ := collectionHosts.Find(selection).Count() + // Already exist? if count == 1 { + // Case: Exists! Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `This host is already registered!`, `host=`+host.Hostname, `address=`+host.IPAddressPort) } else { + // Case: Not exist. if errInsert := collectionHosts.Insert(host); errInsert != nil { + // Case: Was not able insert in the database Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to register this host.`, errInsert.Error(), `host=`+host.Hostname, `address=`+host.IPAddressPort) } else { + // Case: Everything was fine. Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `This host is now registered.`, `host=`+host.Hostname, `address=`+host.IPAddressPort) } } diff --git a/ICCC/Registrar.go b/ICCC/Registrar.go index 9dec531..db3b929 100644 --- a/ICCC/Registrar.go +++ b/ICCC/Registrar.go @@ -6,11 +6,15 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Register a command to ICCC for a specific channel. func Registrar(channel, command string, callback func(data map[string][]string)) { listenersLock.Lock() defer listenersLock.Unlock() + // Write the command to the database: register2Database(channel, command) + + // Register the command at the local cache: listeners[fmt.Sprintf(`%s::%s`, channel, command)] = callback Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The registrar has registered a new ICCC command.`, `channel=`+channel, `command=`+command) diff --git a/ICCC/Scheme/Host.go b/ICCC/Scheme/Host.go index fa18f42..2d11418 100644 --- a/ICCC/Scheme/Host.go +++ b/ICCC/Scheme/Host.go @@ -1,5 +1,6 @@ package Scheme +// Scheme for the host database entry. type Host struct { Hostname string `bson:"Hostname"` IPAddressPort string `bson:"IPAddressPort"` diff --git a/ICCC/Scheme/Listener.go b/ICCC/Scheme/Listener.go index ac4f93c..794ce7b 100644 --- a/ICCC/Scheme/Listener.go +++ b/ICCC/Scheme/Listener.go @@ -1,5 +1,6 @@ package Scheme +// Type for the listener entries at the database. type Listener struct { Channel string `bson:"Channel"` Command string `bson:"Command"` diff --git a/ICCC/Send.go b/ICCC/Send.go index dfd94d2..98fa52d 100644 --- a/ICCC/Send.go +++ b/ICCC/Send.go @@ -9,11 +9,17 @@ import ( "net/url" ) +// Send a message to a listener. func sendMessage(listener Scheme.Listener, data map[string][]string) { - + // Convert the data and encode it: valuesHTTP := url.Values(data) + + // Add the communication password: valuesHTTP.Add(`InternalCommPassword`, Tools.InternalCommPassword()) + + // Try to deliver the message: if _, err := http.PostForm(`http://`+listener.IPAddressPort+`/ICCC`, valuesHTTP); err != nil { + // Case: Was not possible to deliver. Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNETWORK, `Was not able to send the ICCC message.`, err.Error()) } diff --git a/ICCC/Shutdown.go b/ICCC/Shutdown.go index 6ba4694..20b04d3 100644 --- a/ICCC/Shutdown.go +++ b/ICCC/Shutdown.go @@ -7,27 +7,32 @@ import ( "gopkg.in/mgo.v2/bson" ) -/* -Please do not use this type. It is an internal type of Ocean to provide a shutdown function! -*/ +// Type to provide a shutdown function. type ShutdownFunction struct { } -/* -This function is called if the Ocean server is shutting down. -*/ +// The shutdown function for ICCC. func (a ShutdownFunction) Shutdown() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Shutting down now all ICCC listener for this host.`) + // Define the database query: selection := bson.D{{`IPAddressPort`, correctAddressWithPort}} + + // Reserve the memory for an answer: entry := Scheme.Listener{} + + // Execute the query and iterate over the results: iterator := collectionListener.Find(selection).Iter() for iterator.Next(&entry) { + // Update the entry and set it to active=false: selectionUpdate := bson.D{{`Channel`, entry.Channel}, {`Command`, entry.Command}, {`IPAddressPort`, correctAddressWithPort}} entry.IsActive = false + + // Update the entry: collectionListener.Update(selectionUpdate, entry) } + // Disconnect the database: db.Logout() dbSession.Close() Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Done shutting down now all ICCC listener for this host.`) diff --git a/ICCC/StartCacheTimer.go b/ICCC/StartCacheTimer.go new file mode 100644 index 0000000..68260d9 --- /dev/null +++ b/ICCC/StartCacheTimer.go @@ -0,0 +1,6 @@ +package ICCC + +// Starts the cache timer thread. +func StartCacheTimer() { + initCacheTimer() +} diff --git a/ICCC/SystemMessages/ICCCStartup.go b/ICCC/SystemMessages/ICCCStartup.go index 52a040f..5aa99b7 100644 --- a/ICCC/SystemMessages/ICCCStartup.go +++ b/ICCC/SystemMessages/ICCCStartup.go @@ -1,5 +1,6 @@ package SystemMessages +// Message type for the startup message: type ICCCStartUpMessage struct { PublicIPAddressAndPort string AdminIPAddressAndPort string diff --git a/ICCC/Variables.go b/ICCC/Variables.go index 8074694..c9a4693 100644 --- a/ICCC/Variables.go +++ b/ICCC/Variables.go @@ -7,26 +7,27 @@ import ( "sync" ) +// Some pre-defined channels: const ( - ChannelSYSTEM string = `System` - ChannelNUMGEN string = `System::NumGen` - ChannelSHUTDOWN string = `System::Shutdown` - ChannelSTARTUP string = `System::Startup` - ChannelICCC string = `System::ICCC` + ChannelSYSTEM string = `System` // The common system channel. + ChannelNUMGEN string = `System::NumGen` // A channel for the number generator. + ChannelSHUTDOWN string = `System::Shutdown` // A channel for system shutdown messages. + ChannelSTARTUP string = `System::Startup` // A channel for system startup messages. + ChannelICCC string = `System::ICCC` // A common ICCC channel. ) var ( - senderName LM.Sender = `ICCC` - db *mgo.Database = nil - dbSession *mgo.Session = nil - collectionListener *mgo.Collection = nil - collectionHosts *mgo.Collection = nil - reservedSystemChannels []string = []string{ChannelSYSTEM, ChannelNUMGEN, ChannelSHUTDOWN, ChannelSTARTUP, ChannelICCC} - listeners map[string]func(data map[string][]string) = nil - listenersLock sync.RWMutex = sync.RWMutex{} - cacheListenerDatabase *list.List = nil - cacheListenerDatabaseLock sync.RWMutex = sync.RWMutex{} - startCacheTimerLock sync.Mutex = sync.Mutex{} - cacheTimerRunning bool = false - correctAddressWithPort string = `` + senderName LM.Sender = `ICCC` // This is the name for logging event from this package + db *mgo.Database = nil // The database + dbSession *mgo.Session = nil // The database session + collectionListener *mgo.Collection = nil // The database collection for listeners + collectionHosts *mgo.Collection = nil // The database collection for hosts + reservedSystemChannels []string = []string{ChannelSYSTEM, ChannelNUMGEN, ChannelSHUTDOWN, ChannelSTARTUP, ChannelICCC} // The reserved and pre-defined system channels + listeners map[string]func(data map[string][]string) = nil // The listener cache for all local available listeners with local functions + listenersLock sync.RWMutex = sync.RWMutex{} // The mutex for the listener cache + cacheListenerDatabase *list.List = nil // The globally cache for all listeners from all servers + cacheListenerDatabaseLock sync.RWMutex = sync.RWMutex{} // The mutex for the globally cache + startCacheTimerLock sync.Mutex = sync.Mutex{} // Mutex for the start timer + cacheTimerRunning bool = false // Is the timer running? + correctAddressWithPort string = `` // The IP address and port of the this local server ) diff --git a/ICCC/WriteMessage2All.go b/ICCC/WriteMessage2All.go index e1d0869..063be44 100644 --- a/ICCC/WriteMessage2All.go +++ b/ICCC/WriteMessage2All.go @@ -6,20 +6,27 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Function to broadcast a message to all listeners. func WriteMessage2All(channel, command string, message interface{}) { cacheListenerDatabaseLock.RLock() defer cacheListenerDatabaseLock.RUnlock() + // Convert the message to HTTP data: data := message2Data(channel, command, message) counter := 0 + + // Loop over all listeners which are currently available at the cache: for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() { listener := entry.Value.(Scheme.Listener) + + // If the channel and the command matches, deliver the message: if listener.Channel == channel && listener.Command == command { go sendMessage(listener, data) counter++ } } + // Was not able to deliver to any listener? if counter == 0 { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not able to deliver this message, because no listener was found!`, `channel=`+channel, `command=`+command) } diff --git a/ICCC/WriteMessage2Any.go b/ICCC/WriteMessage2Any.go index 984c2aa..6d7c174 100644 --- a/ICCC/WriteMessage2Any.go +++ b/ICCC/WriteMessage2Any.go @@ -7,16 +7,22 @@ import ( "github.com/SommerEngineering/Ocean/Tools" ) +// Function to write a message to any listener. func WriteMessage2Any(channel, command string, message interface{}) { cacheListenerDatabaseLock.RLock() defer cacheListenerDatabaseLock.RUnlock() + // Convert the message to HTTP data: data := message2Data(channel, command, message) maxCount := cacheListenerDatabase.Len() entries := make([]Scheme.Listener, 0, maxCount) counter := 0 + + // Loop over all listeners which are currently present at the cache: for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() { listener := entry.Value.(Scheme.Listener) + + // If the channel and the command matches, store the listener: if listener.Channel == channel && listener.Command == command { entries = entries[:len(entries)+1] entries[counter] = listener @@ -25,9 +31,11 @@ func WriteMessage2Any(channel, command string, message interface{}) { count := len(entries) if count > 0 { + // Case: Find at least one possible listener. Choose a random one and deliver: listener := entries[Tools.RandomInteger(count)] go sendMessage(listener, data) } else { + // Case: Find no listener at all. Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not able to deliver this message to any listener, because no listener was found!`, `channel=`+channel, `command=`+command) } } diff --git a/Log/Device/Device.go b/Log/Device/Device.go index e77bc1a..e826601 100644 --- a/Log/Device/Device.go +++ b/Log/Device/Device.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// The interface for every logging device. type Device interface { Log(logEntries []Meta.Entry) Flush() diff --git a/Log/DeviceConsole/ActivateLoggingDevice.go b/Log/DeviceConsole/ActivateLoggingDevice.go index 903d053..c1cc818 100644 --- a/Log/DeviceConsole/ActivateLoggingDevice.go +++ b/Log/DeviceConsole/ActivateLoggingDevice.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/Log" ) +// Function with the setup of the logging device. func ActivateLoggingDevice() { Log.AddLoggingDevice(Console{}) } diff --git a/Log/DeviceConsole/Console.go b/Log/DeviceConsole/Console.go index e6e545a..667ae17 100644 --- a/Log/DeviceConsole/Console.go +++ b/Log/DeviceConsole/Console.go @@ -5,9 +5,11 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// The logging device. type Console struct { } +// This function is the interface between the logging system and the console logger. func (dev Console) Log(entries []Meta.Entry) { for _, entry := range entries { fmt.Println(entry.Format()) diff --git a/Log/DeviceDatabase/ActivateLoggingDevice.go b/Log/DeviceDatabase/ActivateLoggingDevice.go index be70395..b6a8e4d 100644 --- a/Log/DeviceDatabase/ActivateLoggingDevice.go +++ b/Log/DeviceDatabase/ActivateLoggingDevice.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/Log" ) +// Function with the setup of the logging device. func ActivateLoggingDevice() { Log.AddLoggingDevice(Database{}) } diff --git a/Log/DeviceDatabase/CacheFull.go b/Log/DeviceDatabase/CacheFull.go new file mode 100644 index 0000000..3ff5bf4 --- /dev/null +++ b/Log/DeviceDatabase/CacheFull.go @@ -0,0 +1,18 @@ +package DeviceDatabase + +// Function to check if the cache is full. If so, write all events to the database. +func cacheFull() { + mutexCacheFull.Lock() + defer mutexCacheFull.Unlock() + + // Is the cache full? + if len(cache) < cacheSizeNumberOfEvents { + // Case: Cache is not full. + return + } + + // Case: The cache is full. Write all events to the database. + for counter := 0; counter < cacheSizeNumberOfEvents; counter++ { + write2Database(<-cache) + } +} diff --git a/Log/DeviceDatabase/CacheRefreshMessageNames.go b/Log/DeviceDatabase/CacheRefreshMessageNames.go new file mode 100644 index 0000000..44553de --- /dev/null +++ b/Log/DeviceDatabase/CacheRefreshMessageNames.go @@ -0,0 +1,37 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/Shutdown" + "time" +) + +// Function for the thread which maintain the message name cache. +func cacheRefreshMessageNames() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The message names' refresh thread is now running.`) + + // Create an own thread: + go func() { + // Endless loop: + for true { + // Read the message names rom the DB: + data := readMessageNamesFromDB() + mutexCacheMessageNames.Lock() + + // Overwrite the cache: + cacheMessageNames = data + mutexCacheMessageNames.Unlock() + + // Sleep some time: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The message names' cache was refreshed.`) + time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) + + // Case: The server goes down now. + if Shutdown.IsDown() { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The message name's refresh thread is now shutting down.`) + return + } + } + }() +} diff --git a/Log/DeviceDatabase/CacheRefreshSenderNames.go b/Log/DeviceDatabase/CacheRefreshSenderNames.go new file mode 100644 index 0000000..86486fb --- /dev/null +++ b/Log/DeviceDatabase/CacheRefreshSenderNames.go @@ -0,0 +1,38 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/Shutdown" + "time" +) + +// Function for the thread which maintain the sender name cache. +func cacheRefreshSenderNames() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The sender names' refresh thread is now running.`) + + // Use an extra thread: + go func() { + // Endless lopp: + for true { + + // Read the sender names from the DB: + data := readSenderNamesFromDB() + mutexCacheSenderNames.Lock() + + // Overwrite the cache: + cacheSenderNames = data + mutexCacheSenderNames.Unlock() + + // Sleep some time: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The sender names' cache was refreshed.`) + time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) + + // Case: The server is going down now. + if Shutdown.IsDown() { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The sender name's refresh thread is now shutting down.`) + return + } + } + }() +} diff --git a/Log/DeviceDatabase/Flush.go b/Log/DeviceDatabase/Flush.go index 9fdc398..133c82b 100644 --- a/Log/DeviceDatabase/Flush.go +++ b/Log/DeviceDatabase/Flush.go @@ -1,5 +1,6 @@ package DeviceDatabase +// Flush the cache and write all messages to the database. func (dev Database) Flush() { mutexCacheFull.Lock() defer mutexCacheFull.Unlock() @@ -9,6 +10,7 @@ func (dev Database) Flush() { write2Database(<-cache) } + // Shutdown the database connection: logDB.Logout() logDBSession.Close() } diff --git a/Log/DeviceDatabase/Format.go b/Log/DeviceDatabase/Format.go index 979e812..467485a 100644 --- a/Log/DeviceDatabase/Format.go +++ b/Log/DeviceDatabase/Format.go @@ -4,8 +4,9 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Function to format a logging database entry as string. func (entry LogDBEntry) Format() (result string) { - + // First, we convert the logging db entry to the common logging type: converted := Meta.Entry{} converted.Time = entry.TimeUTC converted.Project = entry.Project @@ -18,6 +19,7 @@ func (entry LogDBEntry) Format() (result string) { converted.MessageDescription = entry.MessageDescription converted.Parameters = entry.Parameters + // Second, we can use then the format operation of these type: result = converted.Format() return } diff --git a/Log/DeviceDatabase/Init.go b/Log/DeviceDatabase/Init.go index c355e39..3c93d56 100644 --- a/Log/DeviceDatabase/Init.go +++ b/Log/DeviceDatabase/Init.go @@ -8,12 +8,19 @@ import ( "strconv" ) +// The init function for this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting now the database logging.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the database logging done.`) + // Init the database first: initDatabase() + + // + // Read all configuration values: + // + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogDBCacheSizeNumberOfEvents`)); err != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityHigh, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not possible to read the LogDBCacheSizeNumberOfEvents configuration.`, `The default value will be used.`, fmt.Sprintf(`Default value is %d.`, cacheSizeNumberOfEvents)) } else { @@ -35,8 +42,15 @@ func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogDBWebInterfaceNameCacheRefreshTimeSeconds was loaded.`, fmt.Sprintf(`The value is %d.`, nameCachesRefreshTimeSeconds)) } + // Create the cache: cache = make(chan LogDBEntry, cacheSizeNumberOfEvents) - initTimeout() + + // Starts a thread to write events based on time-outs: + startTimeout() + + // Starts a thread to refresh the sender name cache: cacheRefreshSenderNames() + + // Starts a thread to refresh the message name cache: cacheRefreshMessageNames() } diff --git a/Log/DeviceDatabase/InitDB.go b/Log/DeviceDatabase/InitDB.go index e2a66fa..9c47e6a 100644 --- a/Log/DeviceDatabase/InitDB.go +++ b/Log/DeviceDatabase/InitDB.go @@ -12,19 +12,26 @@ import ( "time" ) +// Init the database for the logging. func initDatabase() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Checking and init the logging database collection.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Checking and init the logging database collection done.`) + // Read the configuration values for the logging database: databaseHost := ConfigurationDB.Read(`LogDBHost`) databaseDB := ConfigurationDB.Read(`LogDBDatabase`) databaseUsername := ConfigurationDB.Read(`LogDBUsername`) databasePassword := ConfigurationDB.Read(`LogDBPassword`) + + // Should the logging events at the database expire? expire := strings.ToLower(ConfigurationDB.Read(`LogDBEventsExpire`)) == `true` + + // The default values for the TTL (time to live): expireAfterDays := 21900 // 60 years ~ maximum of MongoDB expireValue4DisabledFunction := 21900 // 60 years ~ maximum of MongoDB + // Try to read the configured value for the TTL: if value, errValue := strconv.Atoi(ConfigurationDB.Read(`LogDBEventsExpireAfterDays`)); errValue != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityMiddle, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not possible to read the configuration for the expire time of logging events. Log events will not expire any more.`, errValue.Error()) expire = false @@ -86,6 +93,9 @@ func initDatabase() { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, fmt.Sprintf(`Update the expire policy for the logging database done.`)) } + // + // Ensure that all necessary indexes are existing: + // indexProject := mgo.Index{} indexProject.Key = []string{`Project`} logDBCollection.EnsureIndex(indexProject) diff --git a/Log/DeviceDatabase/ReadCustom.go b/Log/DeviceDatabase/ReadCustom.go index 04a7045..89927ba 100644 --- a/Log/DeviceDatabase/ReadCustom.go +++ b/Log/DeviceDatabase/ReadCustom.go @@ -7,21 +7,27 @@ import ( func ReadCustom(timeRange, logLevel, logCategory, logImpact, logSeverity, logMessageName, logSender, logPage string) (events []LogDBEntry) { // - // TODO => Is currently stub + // TODO => Is currently a stub // + // Define the query: query := logDBCollection.Find(bson.D{}).Sort(`-TimeUTC`).Limit(26) count := 26 + // Execute the query and count the results: if n, err := query.Count(); err == nil { count = n } + // The iterator for the results: iter := query.Iter() entry := LogDBEntry{} pos := 0 + + // Reserve the memory for the results: events = make([]LogDBEntry, count) + // Loop over all entries and store it: for iter.Next(&entry) { events[pos] = entry pos++ diff --git a/Log/DeviceDatabase/ReadLatest.go b/Log/DeviceDatabase/ReadLatest.go index 9a8054e..a544f78 100644 --- a/Log/DeviceDatabase/ReadLatest.go +++ b/Log/DeviceDatabase/ReadLatest.go @@ -4,20 +4,26 @@ import ( "gopkg.in/mgo.v2/bson" ) +// Read the latest logging events from the database. func ReadLatest() (events []LogDBEntry) { - + // Define the query: query := logDBCollection.Find(bson.D{}).Sort(`-TimeUTC`).Limit(26) count := 26 + // Execute the query and count the results: if n, err := query.Count(); err == nil { count = n } + // The iterator for the results: iter := query.Iter() entry := LogDBEntry{} pos := 0 + + // Reserve the memory for the results: events = make([]LogDBEntry, count) + // Loop over all entries and store it: for iter.Next(&entry) { events[pos] = entry pos++ diff --git a/Log/DeviceDatabase/ReadMessageNames.go b/Log/DeviceDatabase/ReadMessageNames.go index ecd93af..8bf77c5 100644 --- a/Log/DeviceDatabase/ReadMessageNames.go +++ b/Log/DeviceDatabase/ReadMessageNames.go @@ -1,51 +1,9 @@ package DeviceDatabase -import ( - "github.com/SommerEngineering/Ocean/Log" - LM "github.com/SommerEngineering/Ocean/Log/Meta" - "github.com/SommerEngineering/Ocean/Shutdown" - "gopkg.in/mgo.v2/bson" - "sort" - "time" -) - +// Read the message names out of the cache. func ReadMessageNames() (messageNames []string) { mutexCacheMessageNames.RLock() defer mutexCacheMessageNames.RUnlock() messageNames = cacheMessageNames return } - -func cacheRefreshMessageNames() { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The message names' refresh thread is now running.`) - go func() { - for true { - - data := readMessageNamesFromDB() - mutexCacheMessageNames.Lock() - cacheMessageNames = data - mutexCacheMessageNames.Unlock() - - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The message names' cache was refreshed.`) - time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) - - if Shutdown.IsDown() { - Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The message name's refresh thread is now shutting down.`) - return - } - } - }() -} - -func readMessageNamesFromDB() (result []string) { - - var nextMessageNames []string - if err := logDBCollection.Find(bson.D{}).Distinct(`MessageName`, &nextMessageNames); err != nil { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the message names from the database.`, err.Error()) - return - } - - sort.Strings(nextMessageNames) - result = nextMessageNames - return -} diff --git a/Log/DeviceDatabase/ReadMessageNamesFromDB.go b/Log/DeviceDatabase/ReadMessageNamesFromDB.go new file mode 100644 index 0000000..e0703a8 --- /dev/null +++ b/Log/DeviceDatabase/ReadMessageNamesFromDB.go @@ -0,0 +1,23 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "gopkg.in/mgo.v2/bson" + "sort" +) + +// Read the message names from the database without any cache. +func readMessageNamesFromDB() (result []string) { + var nextMessageNames []string + if err := logDBCollection.Find(bson.D{}).Distinct(`MessageName`, &nextMessageNames); err != nil { + // Case: Error, was not able to write the event to the database: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the message names from the database.`, err.Error()) + return + } + + // Sort the sender names: + sort.Strings(nextMessageNames) + result = nextMessageNames + return +} diff --git a/Log/DeviceDatabase/ReadSenderNames.go b/Log/DeviceDatabase/ReadSenderNames.go index a1e411e..296b564 100644 --- a/Log/DeviceDatabase/ReadSenderNames.go +++ b/Log/DeviceDatabase/ReadSenderNames.go @@ -1,51 +1,9 @@ package DeviceDatabase -import ( - "github.com/SommerEngineering/Ocean/Log" - LM "github.com/SommerEngineering/Ocean/Log/Meta" - "github.com/SommerEngineering/Ocean/Shutdown" - "gopkg.in/mgo.v2/bson" - "sort" - "time" -) - +// Read the sender names out of the cache. func ReadSenderNames() (senderNames []string) { mutexCacheSenderNames.RLock() defer mutexCacheSenderNames.RUnlock() senderNames = cacheSenderNames return } - -func cacheRefreshSenderNames() { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The sender names' refresh thread is now running.`) - go func() { - for true { - - data := readSenderNamesFromDB() - mutexCacheSenderNames.Lock() - cacheSenderNames = data - mutexCacheSenderNames.Unlock() - - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The sender names' cache was refreshed.`) - time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) - - if Shutdown.IsDown() { - Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The sender name's refresh thread is now shutting down.`) - return - } - } - }() -} - -func readSenderNamesFromDB() (result []string) { - - var nextSenderNames []string - if err := logDBCollection.Find(bson.D{}).Distinct(`Sender`, &nextSenderNames); err != nil { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the sender names from the database.`, err.Error()) - return - } - - sort.Strings(nextSenderNames) - result = nextSenderNames - return -} diff --git a/Log/DeviceDatabase/ReadSenderNamesFromDB.go b/Log/DeviceDatabase/ReadSenderNamesFromDB.go new file mode 100644 index 0000000..321d2e4 --- /dev/null +++ b/Log/DeviceDatabase/ReadSenderNamesFromDB.go @@ -0,0 +1,23 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "gopkg.in/mgo.v2/bson" + "sort" +) + +// Reads the sender names from the database without any caching. +func readSenderNamesFromDB() (result []string) { + var nextSenderNames []string + if err := logDBCollection.Find(bson.D{}).Distinct(`Sender`, &nextSenderNames); err != nil { + // Case: Was not possible to write to the database. + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the sender names from the database.`, err.Error()) + return + } + + // Sort the sender names: + sort.Strings(nextSenderNames) + result = nextSenderNames + return +} diff --git a/Log/DeviceDatabase/ReceiveLogging.go b/Log/DeviceDatabase/ReceiveLogging.go index c4e74f7..8bfbe9b 100644 --- a/Log/DeviceDatabase/ReceiveLogging.go +++ b/Log/DeviceDatabase/ReceiveLogging.go @@ -4,14 +4,13 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) -type Database struct { -} - +// This function is the interface between the logging system and the database logger. func (dev Database) Log(entries []Meta.Entry) { // // Cannot log here to prevent endless loop (consumer is also producer) // + // Write every incoming batch to the cache: write2Cache(entries) } diff --git a/Log/DeviceDatabase/Scheme.go b/Log/DeviceDatabase/Scheme.go index 60853de..6a4e1e9 100644 --- a/Log/DeviceDatabase/Scheme.go +++ b/Log/DeviceDatabase/Scheme.go @@ -4,6 +4,7 @@ import ( "time" ) +// The type for the database logging. type LogDBEntry struct { TimeUTC time.Time `bson:"TimeUTC"` Project string `bson:"Project"` @@ -17,8 +18,13 @@ type LogDBEntry struct { Parameters []string `bson:"Parameters"` } +// A type for the TTL (time to live) for the index. type TTLUpdateResult struct { ExpireAfterSeconds_old int32 `bson:"expireAfterSeconds_old"` ExpireAfterSeconds_new int32 `bson:"expireAfterSeconds_new"` Ok int32 `bson:"ok"` } + +// The logging device. +type Database struct { +} diff --git a/Log/DeviceDatabase/ReadFromCache.go b/Log/DeviceDatabase/StartTimeout.go similarity index 54% rename from Log/DeviceDatabase/ReadFromCache.go rename to Log/DeviceDatabase/StartTimeout.go index d5c556f..197786f 100644 --- a/Log/DeviceDatabase/ReadFromCache.go +++ b/Log/DeviceDatabase/StartTimeout.go @@ -1,41 +1,32 @@ -package DeviceDatabase - -import ( - "github.com/SommerEngineering/Ocean/Shutdown" - "time" -) - -// Case: The cache is full -func cacheFull() { - mutexCacheFull.Lock() - defer mutexCacheFull.Unlock() - - if len(cache) < cacheSizeNumberOfEvents { - return - } - - for counter := 0; counter < cacheSizeNumberOfEvents; counter++ { - write2Database(<-cache) - } -} - -// Case: Time out -func initTimeout() { - - go func() { - for { - - if Shutdown.IsDown() { - return - } - - time.Sleep(time.Duration(cacheSizeTime2FlushSeconds) * time.Second) - mutexCacheFull.Lock() - amount := len(cache) - for counter := 0; counter < amount; counter++ { - write2Database(<-cache) - } - mutexCacheFull.Unlock() - } - }() -} +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Shutdown" + "time" +) + +// The timeout function writes the logging events to the database afer some time. +func startTimeout() { + + // Starts a new thread: + go func() { + for { + + // Case: The system goes down now. + if Shutdown.IsDown() { + return + } + + // Wait for the right time: + time.Sleep(time.Duration(cacheSizeTime2FlushSeconds) * time.Second) + + // Write the events to the database: + mutexCacheFull.Lock() + amount := len(cache) + for counter := 0; counter < amount; counter++ { + write2Database(<-cache) + } + mutexCacheFull.Unlock() + } + }() +} diff --git a/Log/DeviceDatabase/Variables.go b/Log/DeviceDatabase/Variables.go index 8579c4b..62d34fe 100644 --- a/Log/DeviceDatabase/Variables.go +++ b/Log/DeviceDatabase/Variables.go @@ -7,6 +7,7 @@ import ( ) var ( + // This is the name for logging event from this package: senderName LM.Sender = `System::Logger::Database` mutexCacheFull sync.Mutex = sync.Mutex{} mutexCacheSenderNames sync.RWMutex = sync.RWMutex{} diff --git a/Log/DeviceDatabase/Write2Cache.go b/Log/DeviceDatabase/Write2Cache.go index de02a24..307eefc 100644 --- a/Log/DeviceDatabase/Write2Cache.go +++ b/Log/DeviceDatabase/Write2Cache.go @@ -4,12 +4,17 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// This function writes a batch of log entries to the cache. func write2Cache(entries []Meta.Entry) { + // Loop over each entry: for _, entry := range entries { + // If the cache is full, execute it: if len(cache) == cacheSizeNumberOfEvents { + // Execute the cache with a new thread: go cacheFull() } + // Convert the log entry to the database format: logDBentry := LogDBEntry{} logDBentry.Category = entry.Category.Format() logDBentry.Impact = entry.Impact.Format() @@ -21,6 +26,8 @@ func write2Cache(entries []Meta.Entry) { logDBentry.Sender = string(entry.Sender) logDBentry.Severity = entry.Severity.Format() logDBentry.TimeUTC = entry.Time + + // Write it to the cache: cache <- logDBentry } } diff --git a/Log/DeviceDatabase/Write2Database.go b/Log/DeviceDatabase/Write2Database.go index 12e2481..d468236 100644 --- a/Log/DeviceDatabase/Write2Database.go +++ b/Log/DeviceDatabase/Write2Database.go @@ -1,7 +1,15 @@ package DeviceDatabase +import ( + "fmt" +) + +// Function to write a logging event to the database. func write2Database(entry LogDBEntry) { + // Try to write the event to the database: if err := logDBCollection.Insert(entry); err != nil { - // Can not log here to prevent endless loop (consumer is also producer) + // Case: Error! + // Cannot log here to prevent endless loop (consumer is also producer) + fmt.Printf("Was not able to write a logging event to the database: '%s'. The log entry was: '%s'.\n", err.Error(), entry.Format()) } } diff --git a/Log/DeviceDelay.go b/Log/DeviceDelay.go index ff2c189..e13d518 100644 --- a/Log/DeviceDelay.go +++ b/Log/DeviceDelay.go @@ -5,18 +5,18 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Queue a log event before delivery to the devices. func deviceDelay(newEntry Meta.Entry) { defer checkDeviceDelaySize() - // Insert the new entry at the correct position (time)! + // Insert the new entry at the correct position (time). + // To ensure that the causality is guaranteed. for logEvent := deviceDelayBuffer.Front(); logEvent != nil; logEvent = logEvent.Next() { currentEvent := logEvent.Value.(Meta.Entry) if newEntry.Time.Before(currentEvent.Time) { - mutexDeviceDelays.Lock() deviceDelayBuffer.InsertBefore(newEntry, logEvent) mutexDeviceDelays.Unlock() - return } } @@ -27,19 +27,32 @@ func deviceDelay(newEntry Meta.Entry) { mutexDeviceDelays.Unlock() } +// Check if the size of the buffer is huge enough to deliver entries. func checkDeviceDelaySize() { + + // Get exklusive access: mutexDeviceDelays.Lock() + + // Is the size huge enough? if deviceDelayBuffer.Len() >= logDeviceDelayNumberEvents { + + // Read all entries: dataArray := logEntryListToArray(deviceDelayBuffer) + + // Re-init the buffer: deviceDelayBuffer.Init() + // Loop over all devices: mutexDevices.RLock() for entry := devices.Front(); entry != nil; entry = entry.Next() { dev := entry.Value.(Device.Device) + + // Deliver the data with a new thread: go dev.Log(dataArray) } mutexDevices.RUnlock() } + // Release the lock: mutexDeviceDelays.Unlock() } diff --git a/Log/Flush.go b/Log/Flush.go index f4f1aaa..cf24d96 100644 --- a/Log/Flush.go +++ b/Log/Flush.go @@ -4,22 +4,25 @@ import ( "github.com/SommerEngineering/Ocean/Log/Device" ) -/* -Please do not call this function your self! This function allows Ocean to flush the logging at the shutting down case. -*/ +// Function to force all buffers to flush the events. func Flush() { + + // Close the entry buffer: mutexChannel.Lock() channelReady = false close(entriesBuffer) mutexChannel.Unlock() + // Wait that the scheduler is done: <-schedulerExitSignal + // Get all log entries from the device delay buffer: mutexDeviceDelays.Lock() dataArray := logEntryListToArray(deviceDelayBuffer) deviceDelayBuffer.Init() mutexDeviceDelays.Unlock() + // Deliver all the events to all devices: mutexDevices.RLock() for entry := devices.Front(); entry != nil; entry = entry.Next() { dev := entry.Value.(Device.Device) diff --git a/Log/Handlers.go b/Log/Handlers.go index d96437b..dee7bc0 100644 --- a/Log/Handlers.go +++ b/Log/Handlers.go @@ -7,12 +7,12 @@ import ( "time" ) +// Writes a log message to the channel. func writeToChannel(logEntry Meta.Entry) { select { case entriesBuffer <- logEntry: case <-time.After(time.Duration(int64(logBufferTimeoutSeconds)) * time.Second): - - // Warn: Can not log here to prevent endless loop and memory leak! + // Warn: Cannot log here to prevent endless loop and memory leak! fmt.Println(`Warning: Was not able to write to the logging buffer! Message=` + logEntry.Format()) } } @@ -110,6 +110,7 @@ func LogShort(sender Meta.Sender, category Meta.Category, level Meta.Level, mess TakeEntry(entry) } +// Removes white spaces from the message. func clearEntry(entry Meta.Entry) (result Meta.Entry) { entry.MessageDescription = removeWhitespaces(entry.MessageDescription) entry.Parameters = clearParameters(entry.Parameters) @@ -117,6 +118,7 @@ func clearEntry(entry Meta.Entry) (result Meta.Entry) { return } +// Remove white spaces from the parameters. func clearParameters(oldParameters []string) (result []string) { for n := 0; n < len(oldParameters); n++ { oldParameters[n] = removeWhitespaces(oldParameters[n]) @@ -126,6 +128,7 @@ func clearParameters(oldParameters []string) (result []string) { return } +// Removes white spaces from a string. func removeWhitespaces(text string) (result string) { text = strings.Replace(text, "\n", ` `, -1) text = strings.Replace(text, "\t", ` `, -1) diff --git a/Log/Init.go b/Log/Init.go index 34a9287..1c9ab3c 100644 --- a/Log/Init.go +++ b/Log/Init.go @@ -3,44 +3,29 @@ package Log import ( "container/list" "github.com/SommerEngineering/Ocean/Log/Meta" - "io/ioutil" - "os" - "path/filepath" "strconv" - "strings" "sync" ) -func readProjectName() { - if currentDir, dirError := os.Getwd(); dirError != nil { - panic(`Can not read the current working directory and therefore can not read the project name!`) - } else { - filename := filepath.Join(currentDir, `project.name`) - if _, errFile := os.Stat(filename); errFile != nil { - if os.IsNotExist(errFile) { - panic(`Can not read the project name file 'project.name': File not found!`) - } else { - panic(`Can not read the project name file 'project.name': ` + errFile.Error()) - } - } - - if projectNameBytes, errRead := ioutil.ReadFile(filename); errRead != nil { - panic(`Can not read the project name file 'project.name': ` + errRead.Error()) - } else { - projectName = string(projectNameBytes) - projectName = strings.TrimSpace(projectName) - } - } -} - +// Init the logging package. func init() { + + // Read the project name: readProjectName() + + // Create the mutexe: mutexDeviceDelays = sync.Mutex{} mutexPreChannelBuffer = sync.Mutex{} mutexChannel = sync.RWMutex{} + + // Create buffers: preChannelBuffer = list.New() deviceDelayBuffer = list.New() + + // Create the device list: devices = list.New() + + // Channel to exit the scheduler: schedulerExitSignal = make(chan bool) initTimer() @@ -48,8 +33,10 @@ func init() { } func initCode() { + // Creates the buffer for logging entries: entriesBuffer = make(chan Meta.Entry, logBufferSize) - LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, `Starting`, `The logger is now starting.`, `logBufferSize=`+strconv.Itoa(int(logBufferSize)), `logBufferTimeoutSeconds=`+strconv.Itoa(int(logBufferTimeoutSeconds))) + + // Start the scheduler as new thread: go scheduler(entriesBuffer) } diff --git a/Log/Array.go b/Log/LogEntryListToArray.go similarity index 87% rename from Log/Array.go rename to Log/LogEntryListToArray.go index 98af805..385fd06 100644 --- a/Log/Array.go +++ b/Log/LogEntryListToArray.go @@ -1,18 +1,19 @@ -package Log - -import ( - "container/list" - "github.com/SommerEngineering/Ocean/Log/Meta" -) - -func logEntryListToArray(data *list.List) (result []Meta.Entry) { - count := data.Len() - result = make([]Meta.Entry, count, count) - position := 0 - for entry := data.Front(); entry != nil; entry = entry.Next() { - result[position] = entry.Value.(Meta.Entry) - position++ - } - - return -} +package Log + +import ( + "container/list" + "github.com/SommerEngineering/Ocean/Log/Meta" +) + +// Converts a list with logging events to an array. +func logEntryListToArray(data *list.List) (result []Meta.Entry) { + count := data.Len() + result = make([]Meta.Entry, count, count) + position := 0 + for entry := data.Front(); entry != nil; entry = entry.Next() { + result[position] = entry.Value.(Meta.Entry) + position++ + } + + return +} diff --git a/Log/LoggingIsReady.go b/Log/LoggingIsReady.go index df3490a..a1e2820 100644 --- a/Log/LoggingIsReady.go +++ b/Log/LoggingIsReady.go @@ -1,7 +1,8 @@ package Log /* -This function is used just internal by Ocean. Please do not call this function by your self! +A function to change the state of the logging after the database is +accessible. */ func LoggingIsReady() { channelReady = true diff --git a/Log/Meta/Category.go b/Log/Meta/Category.go index fb1b770..05ef757 100644 --- a/Log/Meta/Category.go +++ b/Log/Meta/Category.go @@ -3,12 +3,13 @@ package Meta type Category byte const ( - CategoryBUSINESS = Category(iota) - CategorySYSTEM = Category(iota) - CategoryAPP = Category(iota) - CategoryUSER = Category(iota) + CategoryBUSINESS = Category(iota) // Business category + CategorySYSTEM = Category(iota) // System category + CategoryAPP = Category(iota) // Application category + CategoryUSER = Category(iota) // User category ) +// Formats a category as string. func (cat Category) Format() (result string) { switch cat { case CategoryBUSINESS: @@ -26,6 +27,7 @@ func (cat Category) Format() (result string) { return } +// Parse a category from a string. func ParseCategory(cat string) (value Category) { switch cat { case `C:BUSINESS`: diff --git a/Log/Meta/Entry.go b/Log/Meta/Entry.go index 43ae1ca..849f88d 100644 --- a/Log/Meta/Entry.go +++ b/Log/Meta/Entry.go @@ -4,6 +4,7 @@ import ( "time" ) +// Type for a log entry. type Entry struct { Project string Time time.Time diff --git a/Log/Meta/Format.go b/Log/Meta/Format.go index acee6ab..f89e5e9 100644 --- a/Log/Meta/Format.go +++ b/Log/Meta/Format.go @@ -7,18 +7,22 @@ import ( "time" ) +// Formats a log entry as string. func (entry Entry) Format() (result string) { + // Force the maximum length for a sender: lenSender := len(entry.Sender) if lenSender > 40 { lenSender = 40 } + // Force the maximum length for the message name: lenMessageName := len(entry.MessageName) if lenMessageName > 26 { lenMessageName = 26 } + // Force the maximum length for the project name: lenProject := len(entry.Project) if lenProject > 10 { lenProject = 10 @@ -29,21 +33,23 @@ func (entry Entry) Format() (result string) { messageDescription = strings.Replace(messageDescription, "\t", ` `, -1) messageDescription = strings.Replace(messageDescription, "\r", ` `, -1) + // Format the basic fields of the log message: result = fmt.Sprintf(` [â– ] P:%10s [â– ] %s [â– ] %10s [â– ] %11s [â– ] %10s [â– ] %10s [â– ] sender: %-40s [â– ] name: %-26s [â– ] %s [â– ]`, entry.Project[:lenProject], formatTime(entry.Time), entry.Category.Format(), entry.Level.Format(), entry.Severity.Format(), entry.Impact.Format(), entry.Sender[:lenSender], entry.MessageName[:lenMessageName], messageDescription) + // Formats the parameters: for _, param := range entry.Parameters { - paramText := param paramText = strings.Replace(paramText, "\n", ` `, -1) paramText = strings.Replace(paramText, "\t", ` `, -1) paramText = strings.Replace(paramText, "\r", ` `, -1) - result = fmt.Sprintf(`%s %s [â– ]`, result, paramText) } return } +// Formats the given time as YYYYMMdd HHmmss.fff! This function is necessary +// to prevent a import cycle. func formatTime(t1 time.Time) (result string) { var year int = t1.Year() var month int = int(t1.Month()) diff --git a/Log/Meta/Impact.go b/Log/Meta/Impact.go index 8d382c2..89f41f5 100644 --- a/Log/Meta/Impact.go +++ b/Log/Meta/Impact.go @@ -3,14 +3,15 @@ package Meta type Impact byte const ( - ImpactNone = Impact(iota) - ImpactLow = Impact(iota) - ImpactMiddle = Impact(iota) - ImpactHigh = Impact(iota) - ImpactCritical = Impact(iota) - ImpactUnknown = Impact(iota) + ImpactNone = Impact(iota) // None impact + ImpactLow = Impact(iota) // Low impact + ImpactMiddle = Impact(iota) // Middle impact + ImpactHigh = Impact(iota) // High impact + ImpactCritical = Impact(iota) // Critical impact + ImpactUnknown = Impact(iota) // Unknown impact ) +// Formats a impact as string. func (pri Impact) Format() (result string) { switch pri { case ImpactCritical: @@ -32,6 +33,7 @@ func (pri Impact) Format() (result string) { return } +// Parse a impact from a string. func ParseImpact(pri string) (value Impact) { switch pri { case `I:CRITICAL`: diff --git a/Log/Meta/Level.go b/Log/Meta/Level.go index 774d532..eda6706 100644 --- a/Log/Meta/Level.go +++ b/Log/Meta/Level.go @@ -3,14 +3,15 @@ package Meta type Level byte const ( - LevelWARN = Level(iota) - LevelDEBUG = Level(iota) - LevelERROR = Level(iota) - LevelINFO = Level(iota) - LevelTALKATIVE = Level(iota) - LevelSECURITY = Level(iota) + LevelWARN = Level(iota) // Level: Warning + LevelDEBUG = Level(iota) // Level: Debug + LevelERROR = Level(iota) // Level: Error + LevelINFO = Level(iota) // Level: Information + LevelTALKATIVE = Level(iota) // Level: Talkative (even more events as debug) + LevelSECURITY = Level(iota) // Level: Security ) +// Formats a level as string. func (lvl Level) Format() (result string) { switch lvl { case LevelDEBUG: @@ -32,6 +33,7 @@ func (lvl Level) Format() (result string) { return } +// Parse a level from a string. func ParseLevel(lvl string) (value Level) { switch lvl { case `L:DEBUG`: diff --git a/Log/Meta/MessageNames.go b/Log/Meta/MessageNames.go index 1a64940..d226a8b 100644 --- a/Log/Meta/MessageNames.go +++ b/Log/Meta/MessageNames.go @@ -2,37 +2,38 @@ package Meta type MessageName string +// Some pre-defined message names: const ( - MessageNameSTARTUP = `Startup` - MessageNameINIT = `Init` - MessageNameSHUTDOWN = `Shutdown` - MessageNameEXECUTE = `Execute` - MessageNameDATABASE = `Database` - MessageNameNETWORK = `Network` - MessageNameLOGIN = `Login` - MessageNameLOGOUT = `Logout` - MessageNameSESSION = `Session` - MessageNameTIMEOUT = `Timeout` - MessageNameFILESYSTEM = `Filesystem` - MessageNameCOMMUNICATION = `Communication` - MessageNameWRITE = `Write` - MessageNameREAD = `Read` - MessageNameALGORITHM = `Algorithm` - MessageNameCONFIGURATION = `Configuration` - MessageNameTIMER = `Timer` - MessageNameINPUT = `Input` - MessageNameOUTPUT = `Output` - MessageNameBROWSER = `Browser` - MessageNameSECURITY = `Security` - MessageNameNOTFOUND = `NotFound` - MessageNameANALYSIS = `Analysis` - MessageNameSTATE = `State` - MessageNameGENERATOR = `Generator` - MessageNamePRODUCER = `Producer` - MessageNameCONSUMER = `Consumer` - MessageNamePASSWORD = `Password` - MessageNamePARSE = `Parse` - MessageNameUSER = `User` - MessageNameREQUEST = `Request` - MessageNameRESPONSE = `Response` + MessageNameSTARTUP = `Startup` // e.g. the server startup + MessageNameINIT = `Init` // some kind of init event + MessageNameSHUTDOWN = `Shutdown` // some kind of shutdown event + MessageNameEXECUTE = `Execute` // some kind of execution context + MessageNameDATABASE = `Database` // events which are related to database issues + MessageNameNETWORK = `Network` // events which are related to network issues + MessageNameLOGIN = `Login` // some kind of login event + MessageNameLOGOUT = `Logout` // some kind of logout event + MessageNameSESSION = `Session` // some kind of session event + MessageNameTIMEOUT = `Timeout` // some kind of timeout event + MessageNameFILESYSTEM = `Filesystem` // events which are related to the filesystem + MessageNameCOMMUNICATION = `Communication` // events which are related to communication issues + MessageNameWRITE = `Write` // some kind of write event + MessageNameREAD = `Read` // some kind of read event + MessageNameALGORITHM = `Algorithm` // some kind of algorithm event + MessageNameCONFIGURATION = `Configuration` // some kind of configuration event + MessageNameTIMER = `Timer` // some kind of timer event + MessageNameINPUT = `Input` // some kind of input event + MessageNameOUTPUT = `Output` // some kind of output event + MessageNameBROWSER = `Browser` // some kind of browser event + MessageNameSECURITY = `Security` // some kind of security event + MessageNameNOTFOUND = `NotFound` // something was not found + MessageNameANALYSIS = `Analysis` // some kind of analysis event + MessageNameSTATE = `State` // some kind of state-related event + MessageNameGENERATOR = `Generator` // some kind of generator event + MessageNamePRODUCER = `Producer` // some kind of producer event + MessageNameCONSUMER = `Consumer` // some kind of consumer event + MessageNamePASSWORD = `Password` // some kind of password event + MessageNamePARSE = `Parse` // some kind of parser-related event + MessageNameUSER = `User` // some kind of user event + MessageNameREQUEST = `Request` // some kind of request-related event + MessageNameRESPONSE = `Response` // some kind of response-related event ) diff --git a/Log/Meta/Sender.go b/Log/Meta/Sender.go index c2328a9..43b1f69 100644 --- a/Log/Meta/Sender.go +++ b/Log/Meta/Sender.go @@ -1,3 +1,4 @@ package Meta +// Type for the sender name type Sender string diff --git a/Log/Meta/Severity.go b/Log/Meta/Severity.go index b07fbb6..1114e3e 100644 --- a/Log/Meta/Severity.go +++ b/Log/Meta/Severity.go @@ -3,14 +3,15 @@ package Meta type Severity byte const ( - SeverityNone = Severity(iota) - SeverityLow = Severity(iota) - SeverityMiddle = Severity(iota) - SeverityHigh = Severity(iota) - SeverityCritical = Severity(iota) - SeverityUnknown = Severity(iota) + SeverityNone = Severity(iota) // None severity + SeverityLow = Severity(iota) // Low severity + SeverityMiddle = Severity(iota) // Middle severity + SeverityHigh = Severity(iota) // High severity + SeverityCritical = Severity(iota) // Critical severity + SeverityUnknown = Severity(iota) // Unknown severity ) +// Format the severity as string. func (pri Severity) Format() (result string) { switch pri { case SeverityCritical: @@ -32,6 +33,7 @@ func (pri Severity) Format() (result string) { return } +// Parse the severity from a string. func ParseSeverity(pri string) (value Severity) { switch pri { case `S:CRITICAL`: diff --git a/Log/ReadProjectName.go b/Log/ReadProjectName.go new file mode 100644 index 0000000..2585529 --- /dev/null +++ b/Log/ReadProjectName.go @@ -0,0 +1,38 @@ +package Log + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +// Read the project name out of the local configuration file "project.name". +func readProjectName() { + // Try to get access to the working directory: + if currentDir, dirError := os.Getwd(); dirError != nil { + // Case: Error! Stop the server. + panic(`Cannot read the current working directory and therefore cannot read the project name!`) + } else { + // Try to get access to the file: + filename := filepath.Join(currentDir, `project.name`) + if _, errFile := os.Stat(filename); errFile != nil { + // Cases: Error. + if os.IsNotExist(errFile) { + panic(`Cannot read the project name file 'project.name': File not found!`) + } else { + panic(`Cannot read the project name file 'project.name': ` + errFile.Error()) + } + } + + // Try to read the file: + if projectNameBytes, errRead := ioutil.ReadFile(filename); errRead != nil { + // Case: Error. + panic(`Cannot read the project name file 'project.name': ` + errRead.Error()) + } else { + // Store the project name: + projectName = string(projectNameBytes) + projectName = strings.TrimSpace(projectName) + } + } +} diff --git a/Log/Scheduler.go b/Log/Scheduler.go index fcf2efb..ce93e85 100644 --- a/Log/Scheduler.go +++ b/Log/Scheduler.go @@ -5,21 +5,30 @@ import ( "time" ) -// Note: The scheduler is the consumer for the logging channel! +/* +The scheduler function which runs at a own thread. +Pleae note: The scheduler is the consumer for the logging channel. +*/ func scheduler(logBuffer chan Meta.Entry) { LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameSTARTUP, `The scheduler runs now.`) var stopNextTime = false + // Endless loop: for { + + // Enable the loop to stop: if stopNextTime { break } + // Read one entry from the buffer (channel): nextEntry, ok := <-logBuffer + // Case: The channel was closed. if !ok { - // The channel was closed! + + // Create a log message for this event. stopNextTime = true nextEntry = Meta.Entry{} nextEntry.Project = projectName @@ -33,9 +42,11 @@ func scheduler(logBuffer chan Meta.Entry) { nextEntry.MessageDescription = `The logging channel was closed!` } + // Queue the log event for the delivery to the devices: deviceDelay(nextEntry) } + // Exit the scheduler. Send the signal. LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityCritical, Meta.ImpactNone, Meta.MessageNameSHUTDOWN, `The scheduler is down now.`) schedulerExitSignal <- true } diff --git a/Log/Timer.go b/Log/Timer.go index 4530fb7..fd932b4 100644 --- a/Log/Timer.go +++ b/Log/Timer.go @@ -9,6 +9,7 @@ import ( func initTimer() { + // Ensure that the timer runs only once: if timerIsRunning == true { LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityHigh, Meta.ImpactNone, Meta.MessageNameSTARTUP, `The logging timer is already running.`) return @@ -17,23 +18,40 @@ func initTimer() { timerIsRunning = true LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameSTARTUP, `Create the logging timer now.`, fmt.Sprintf(`Timeout=%d seconds`, logDeviceDelayTimeoutSeconds)) + // Start the timer at a own thread: go func() { + // An endless loop: for { + + // Wait for the next run time: time.Sleep(time.Duration(logDeviceDelayTimeoutSeconds) * time.Second) + // Get exklusive access to the buffer: mutexDeviceDelays.Lock() + + // Read all the data from the device delay buffer: dataArray := logEntryListToArray(deviceDelayBuffer) + + // Re-init the buffer: deviceDelayBuffer.Init() + + // Release the lock to the buffer: mutexDeviceDelays.Unlock() + // Read-lock to read the devices list: mutexDevices.RLock() + + // For each logging device: for entry := devices.Front(); entry != nil; entry = entry.Next() { dev := entry.Value.(Device.Device) + + // Deliver the current logging events with an extra thread: go dev.Log(dataArray) } - mutexDevices.RUnlock() + // Release the read-lock: + mutexDevices.RUnlock() } }() } diff --git a/Log/Variables.go b/Log/Variables.go index 018697f..7601c2d 100644 --- a/Log/Variables.go +++ b/Log/Variables.go @@ -7,22 +7,22 @@ import ( ) var ( - entriesBuffer chan Meta.Entry = nil - schedulerExitSignal chan bool = nil - logBufferSize int = 500 - logBufferTimeoutSeconds int = 4 - logDeviceDelayNumberEvents int = 600 - logDeviceDelayTimeoutSeconds int = 5 - channelReady bool = false - preChannelBufferUsed bool = false - preChannelBuffer *list.List = nil - deviceDelayBuffer *list.List = nil - devices *list.List = nil - mutexDeviceDelays sync.Mutex = sync.Mutex{} - mutexPreChannelBuffer sync.Mutex = sync.Mutex{} - mutexChannel sync.RWMutex = sync.RWMutex{} - mutexDevices sync.RWMutex = sync.RWMutex{} - timerIsRunning bool = false - projectName string = `not set` - senderName Meta.Sender = `System::Log` + entriesBuffer chan Meta.Entry = nil // The channel / buffer for new log entries + schedulerExitSignal chan bool = nil // Exit signal for the scheduler + logBufferSize int = 500 // Buffer size for the logging + logBufferTimeoutSeconds int = 4 // Timeout for the logging + logDeviceDelayNumberEvents int = 600 // Delay of # of events for the devices + logDeviceDelayTimeoutSeconds int = 5 // Timeout for the logging devices + channelReady bool = false // State of the channel + preChannelBufferUsed bool = false // State of the logging (pre or ready?) + preChannelBuffer *list.List = nil // Extra buffer for the pre logging phase + deviceDelayBuffer *list.List = nil // Buffer for the batch write to the devices + devices *list.List = nil // List of all devices + mutexDeviceDelays sync.Mutex = sync.Mutex{} // Mutex for buffer + mutexPreChannelBuffer sync.Mutex = sync.Mutex{} // Mutex for buffer + mutexChannel sync.RWMutex = sync.RWMutex{} // Mutex for the main channel + mutexDevices sync.RWMutex = sync.RWMutex{} // Mutex for the devices + timerIsRunning bool = false // Status of timer + projectName string = `not set` // The project name for the logging + senderName Meta.Sender = `System::Log` // This is the name for logging event from this package ) diff --git a/Log/Web/HandlerCommons.go b/Log/Web/HandlerCommons.go index a43d079..61c5082 100644 --- a/Log/Web/HandlerCommons.go +++ b/Log/Web/HandlerCommons.go @@ -8,6 +8,7 @@ import ( "net/http" ) +// Handler for some CSS data for the web logger. func HandlerCSSNormalize(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -19,6 +20,7 @@ func HandlerCSSNormalize(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.CSSNormalize) } +// Handler for some CSS data for the web logger. func HandlerCSSWebflow(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -30,6 +32,7 @@ func HandlerCSSWebflow(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.CSSWebflow) } +// Handler for some CSS data for the web logger. func HandlerCSSLog(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -41,6 +44,7 @@ func HandlerCSSLog(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.CSSLog) } +// Handler for some JS for the web logger. func HandlerJSModernizr(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -52,6 +56,7 @@ func HandlerJSModernizr(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.JSModernizr) } +// Handler for some JS for the web logger. func HandlerJSWebflow(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -63,6 +68,7 @@ func HandlerJSWebflow(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.JSWebflow) } +// Handler for some JS for the web logger. func HandlerJSjQuery(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -74,6 +80,7 @@ func HandlerJSjQuery(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.JSjQuery) } +// Handler for some JS for the web logger. func HandlerJSjQueryMap(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { diff --git a/Log/Web/HandlerLog.go b/Log/Web/HandlerLog.go index d425fbc..7f73c50 100644 --- a/Log/Web/HandlerLog.go +++ b/Log/Web/HandlerLog.go @@ -11,30 +11,32 @@ import ( "strings" ) +// Handler for accessing the web logging. func HandlerWebLog(response http.ResponseWriter, request *http.Request) { + // Case: The system goes down now. if Shutdown.IsDown() { http.NotFound(response, request) return } + // Execute the HTTP form: request.ParseForm() countParameters := len(request.Form) + // Setup the data for the HTML template: data := Scheme.Viewer{} data.Title = `Web Log Viewer` data.Sender = DeviceDatabase.ReadSenderNames() data.MessageNames = DeviceDatabase.ReadMessageNames() + // To less parameters? if countParameters < 9 { - // Initial view => refresh & first page (latest logs) data.Events = readLatest() data.SetLiveView = true - } else { - - // Custom view + // Case: Custom view currentLevel := request.FormValue(`Level`) currentTimeRange := request.FormValue(`timeRange`) currentCategory := request.FormValue(`Category`) @@ -45,6 +47,7 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) { currentPage := request.FormValue(`CurrentPage`) currentLiveView := request.FormValue(`LiveView`) + // Store the events for the template: data.Events = readCustom(currentTimeRange, currentLevel, currentCategory, currentImpact, currentSeverity, currentMessageName, currentSender, currentPage) if strings.ToLower(currentLiveView) == `true` { @@ -94,6 +97,7 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) { } } + // Write the MIME type and execute the template: MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML) if executeError := templates.ExecuteTemplate(response, `WebLog`, data); executeError != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the web log viewer template.`, executeError.Error()) diff --git a/Log/Web/Init.go b/Log/Web/Init.go index f991931..381e607 100644 --- a/Log/Web/Init.go +++ b/Log/Web/Init.go @@ -7,11 +7,13 @@ import ( "html/template" ) +// The init function for this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the web log.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the web log done.`) + // Create the cache of all web logging templates: templates = template.New(`root`) if _, err := templates.Parse(WebTemp.Viewer); err != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the web log viewer.`, err.Error()) diff --git a/Log/Web/ReadCustom.go b/Log/Web/ReadCustom.go index fec3b43..acaae7d 100644 --- a/Log/Web/ReadCustom.go +++ b/Log/Web/ReadCustom.go @@ -7,18 +7,24 @@ import ( "strings" ) +// Read a custom event range from the database. func readCustom(timeRange, logLevel, logCategory, logImpact, logSeverity, logMessageName, logSender, logPage string) (events []Scheme.LogEvent) { + // Get the custom events: eventsFromDB := DeviceDatabase.ReadCustom(timeRange, logLevel, logCategory, logImpact, logSeverity, logMessageName, logSender, logPage) count := len(eventsFromDB) + + // Array with all events, prepared for the website: events = make([]Scheme.LogEvent, count) + // Copy each event to the right format: for n := 0; n < count; n++ { eventFromDB := eventsFromDB[n] events[n] = Scheme.LogEvent{} events[n].LogLine = eventFromDB.Format() events[n].LogLevel = fmt.Sprintf("log%s", strings.ToLower(eventFromDB.Level[2:])) + // Vary the color of each line: if n%2 == 0 { events[n].AB = Scheme.B } else { diff --git a/Log/Web/ReadLatest.go b/Log/Web/ReadLatest.go index c1e5421..df3cf1c 100644 --- a/Log/Web/ReadLatest.go +++ b/Log/Web/ReadLatest.go @@ -7,18 +7,23 @@ import ( "strings" ) +// Read the latest log events from the database func readLatest() (events []Scheme.LogEvent) { - + // Get the latest events from the database eventsFromDB := DeviceDatabase.ReadLatest() count := len(eventsFromDB) + + // Array for the log events, prepared for the website: events = make([]Scheme.LogEvent, count) + // Copy each event to the right format for the website: for n := 0; n < count; n++ { eventFromDB := eventsFromDB[n] events[n] = Scheme.LogEvent{} events[n].LogLine = eventFromDB.Format() events[n].LogLevel = fmt.Sprintf("log%s", strings.ToLower(eventFromDB.Level[2:])) + // Vary the color of each line: if n%2 == 0 { events[n].AB = Scheme.B } else { diff --git a/Log/Web/Scheme/Constants.go b/Log/Web/Scheme/Constants.go index 0461a0b..f15dc7a 100644 --- a/Log/Web/Scheme/Constants.go +++ b/Log/Web/Scheme/Constants.go @@ -1,6 +1,6 @@ package Scheme const ( - A string = `loga` - B string = `logb` + A string = `loga` // Web log line, kind A (different color for each line) + B string = `logb` // Web log line, kind B (different color for each line) ) diff --git a/Log/Web/Scheme/Scheme.go b/Log/Web/Scheme/Scheme.go index f0c16c4..e2dab8b 100644 --- a/Log/Web/Scheme/Scheme.go +++ b/Log/Web/Scheme/Scheme.go @@ -1,5 +1,6 @@ package Scheme +// The type for the web logger viewer template type Viewer struct { Title string SetLiveView bool @@ -16,12 +17,7 @@ type Viewer struct { Events []LogEvent } -//