Add documentation
This commit is contained in:
parent
69d0bdabf4
commit
6dab89a1d4
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package Configuration
|
||||
|
||||
// This is the init function for this package.
|
||||
func init() {
|
||||
readConfiguration()
|
||||
isInit = true
|
||||
|
@ -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)
|
||||
|
||||
|
@ -9,5 +9,5 @@ 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`
|
||||
senderName LM.Sender = `System::Configuration` // This is the name for logging event from this package
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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.`)
|
||||
}
|
||||
|
@ -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!`)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ConfigurationDB
|
||||
|
||||
// The type for a configuration entry.
|
||||
type ConfigurationDBEntry struct {
|
||||
Name string `bson:"Name"`
|
||||
Value string `bson:"Value"`
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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`)
|
||||
|
@ -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.`)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
package Handlers
|
||||
|
||||
// The init function for this package.
|
||||
func init() {
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -10,38 +10,6 @@ import (
|
||||
"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
|
@ -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()
|
||||
|
20
ICCC/Doc.go
20
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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
13
ICCC/InitCacheNow.go
Normal file
13
ICCC/InitCacheNow.go
Normal file
@ -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)
|
||||
}
|
21
ICCC/InitCacheTimer.go
Normal file
21
ICCC/InitCacheTimer.go
Normal file
@ -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)
|
||||
}
|
||||
}()
|
||||
}
|
@ -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{}
|
||||
|
@ -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 {
|
||||
|
@ -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{}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package Scheme
|
||||
|
||||
// Scheme for the host database entry.
|
||||
type Host struct {
|
||||
Hostname string `bson:"Hostname"`
|
||||
IPAddressPort string `bson:"IPAddressPort"`
|
||||
|
@ -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"`
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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.`)
|
||||
|
6
ICCC/StartCacheTimer.go
Normal file
6
ICCC/StartCacheTimer.go
Normal file
@ -0,0 +1,6 @@
|
||||
package ICCC
|
||||
|
||||
// Starts the cache timer thread.
|
||||
func StartCacheTimer() {
|
||||
initCacheTimer()
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package SystemMessages
|
||||
|
||||
// Message type for the startup message:
|
||||
type ICCCStartUpMessage struct {
|
||||
PublicIPAddressAndPort string
|
||||
AdminIPAddressAndPort string
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
)
|
||||
|
||||
// Function with the setup of the logging device.
|
||||
func ActivateLoggingDevice() {
|
||||
Log.AddLoggingDevice(Console{})
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
)
|
||||
|
||||
// Function with the setup of the logging device.
|
||||
func ActivateLoggingDevice() {
|
||||
Log.AddLoggingDevice(Database{})
|
||||
}
|
||||
|
18
Log/DeviceDatabase/CacheFull.go
Normal file
18
Log/DeviceDatabase/CacheFull.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
37
Log/DeviceDatabase/CacheRefreshMessageNames.go
Normal file
37
Log/DeviceDatabase/CacheRefreshMessageNames.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
38
Log/DeviceDatabase/CacheRefreshSenderNames.go
Normal file
38
Log/DeviceDatabase/CacheRefreshSenderNames.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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++
|
||||
|
@ -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++
|
||||
|
@ -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
|
||||
}
|
||||
|
23
Log/DeviceDatabase/ReadMessageNamesFromDB.go
Normal file
23
Log/DeviceDatabase/ReadMessageNamesFromDB.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
23
Log/DeviceDatabase/ReadSenderNamesFromDB.go
Normal file
23
Log/DeviceDatabase/ReadSenderNamesFromDB.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
@ -5,31 +5,22 @@ import (
|
||||
"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() {
|
||||
// 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++ {
|
@ -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{}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -7,11 +7,11 @@ 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: 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)
|
||||
|
41
Log/Init.go
41
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)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"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)
|
@ -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
|
||||
|
@ -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`:
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Type for a log entry.
|
||||
type Entry struct {
|
||||
Project string
|
||||
Time time.Time
|
||||
|
@ -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())
|
||||
|
@ -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`:
|
||||
|
@ -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`:
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -1,3 +1,4 @@
|
||||
package Meta
|
||||
|
||||
// Type for the sender name
|
||||
type Sender string
|
||||
|
@ -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`:
|
||||
|
38
Log/ReadProjectName.go
Normal file
38
Log/ReadProjectName.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
20
Log/Timer.go
20
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()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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() {
|
||||
|
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// <li class="logline loga logwarn">
|
||||
// <div>....</div>
|
||||
// </li>
|
||||
// <li class="logline logb logwarn">
|
||||
// <div>....</div>
|
||||
// </li>
|
||||
// Type for a log event
|
||||
type LogEvent struct {
|
||||
LogLine string
|
||||
LogLevel string // logwarn || logdebug || logerror || loginfo || logtalkative || logsecurity
|
||||
|
@ -1,5 +1,6 @@
|
||||
package Templates
|
||||
|
||||
// The template for the web log viewer:
|
||||
var Viewer string = `
|
||||
{{define "WebLog"}}
|
||||
<!DOCTYPE html>
|
||||
|
@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
templates *template.Template = nil
|
||||
senderName LM.Sender = `System::WebLog`
|
||||
templates *template.Template = nil // The web logging templates
|
||||
senderName LM.Sender = `System::WebLog` // This is the name for logging event from this package
|
||||
)
|
||||
|
5
Main.go
5
Main.go
@ -6,6 +6,11 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/System"
|
||||
)
|
||||
|
||||
/*
|
||||
This is the entry point of Ocean in case of using it as e.g. messaging broker
|
||||
or logging service, etc. This function does not matter if Ocean is used as
|
||||
framework.
|
||||
*/
|
||||
func main() {
|
||||
Log.LogShort(senderName, LM.CategoryAPP, LM.LevelINFO, LM.MessageNameSTARTUP, `Ocean is starting.`)
|
||||
System.InitHandlers()
|
||||
|
@ -4,11 +4,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MimeType struct {
|
||||
MimeType string
|
||||
FileExtension []string
|
||||
}
|
||||
|
||||
// A function to detect a MIME type.
|
||||
func DetectType(filename string) (mime MimeType, err error) {
|
||||
for _, typeElement := range allTypes {
|
||||
for _, extension := range typeElement.FileExtension {
|
@ -1,8 +1,9 @@
|
||||
package MimeTypes
|
||||
|
||||
var allTypes [32]MimeType
|
||||
|
||||
// The init function for this package.
|
||||
func init() {
|
||||
|
||||
// Store the instances for all known types:
|
||||
allTypes[0] = TypeWebHTML
|
||||
allTypes[1] = TypeWebCSS
|
||||
allTypes[2] = TypeWebJavaScript
|
||||
@ -35,4 +36,6 @@ func init() {
|
||||
allTypes[29] = TypeFontTTF
|
||||
allTypes[30] = TypeFontWOFF
|
||||
allTypes[31] = TypeWebJSON
|
||||
allTypes[32] = TypeCSV
|
||||
allTypes[33] = TypeWebDart
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
package MimeTypes
|
||||
|
||||
var TypeWebHTML = MimeType{MimeType: "text/html", FileExtension: []string{".html", ".htm"}}
|
||||
var TypeWebCSS = MimeType{MimeType: "text/css", FileExtension: []string{".css"}}
|
||||
var TypeWebJavaScript = MimeType{MimeType: "text/javascript", FileExtension: []string{".js"}}
|
||||
var TypeWebDart = MimeType{MimeType: "application/dart", FileExtension: []string{".dart"}}
|
||||
var TypeXML = MimeType{MimeType: "text/xml", FileExtension: []string{".xml"}}
|
||||
var TypeArchiveZIP = MimeType{MimeType: "application/zip", FileExtension: []string{".zip"}}
|
||||
var TypeArchiveGZ = MimeType{MimeType: "application/gzip", FileExtension: []string{".gz"}}
|
||||
var TypeWebOCTET = MimeType{MimeType: "application/octet-stream", FileExtension: []string{".bin", ".exe", ".dll", ".class"}}
|
||||
var TypeDocumentPDF = MimeType{MimeType: "application/pdf", FileExtension: []string{".pdf"}}
|
||||
var TypeDocumentLaTeX = MimeType{MimeType: "application/x-latex", FileExtension: []string{".tex", ".latex"}}
|
||||
var TypeShockwave = MimeType{MimeType: "application/x-shockwave-flash", FileExtension: []string{".swf"}}
|
||||
var TypeArchiveTAR = MimeType{MimeType: "application/x-tar", FileExtension: []string{".tar"}}
|
||||
var TypeAudioWAV = MimeType{MimeType: "application/x-wav", FileExtension: []string{".wav"}}
|
||||
var TypeAudioMP3 = MimeType{MimeType: "audio/mpeg", FileExtension: []string{".mp3"}}
|
||||
var TypeAudioAAC = MimeType{MimeType: "audio/aac", FileExtension: []string{".aac", ".m4a"}}
|
||||
var TypeAudioOGG = MimeType{MimeType: "audio/ogg", FileExtension: []string{"vogg", ".oga"}}
|
||||
var TypeAudioWMA = MimeType{MimeType: "audio/x-ms-wma", FileExtension: []string{".wma"}}
|
||||
var TypeImageGIF = MimeType{MimeType: "image/gif", FileExtension: []string{".gif"}}
|
||||
var TypeImageCommon = MimeType{MimeType: "image", FileExtension: []string{}}
|
||||
var TypeUnknown = MimeType{MimeType: "application/octet-stream", FileExtension: []string{}}
|
||||
var TypeImageJPEG = MimeType{MimeType: "image/jpeg", FileExtension: []string{".jpg", ".jpeg"}}
|
||||
var TypeImagePNG = MimeType{MimeType: "image/png", FileExtension: []string{".png"}}
|
||||
var TypePlainText = MimeType{MimeType: "text/plain", FileExtension: []string{".txt"}}
|
||||
var TypeVideoMPEG = MimeType{MimeType: "video/mpeg", FileExtension: []string{".mpeg", ".mpg"}}
|
||||
var TypeVideoMOV = MimeType{MimeType: "video/quicktime", FileExtension: []string{".mov", ".qt"}}
|
||||
var TypeVideoAVI = MimeType{MimeType: "video/x-msvideo", FileExtension: []string{".avi"}}
|
||||
var TypeVideoMP4 = MimeType{MimeType: "video/mp4", FileExtension: []string{".mp4"}}
|
||||
var TypeFontEOT = MimeType{MimeType: "application/vnd.ms-fontobject", FileExtension: []string{".eot"}}
|
||||
var TypeFontOTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".otf"}}
|
||||
var TypeImageSVG = MimeType{MimeType: "image/svg+xml", FileExtension: []string{".svg"}}
|
||||
var TypeFontTTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".ttf"}}
|
||||
var TypeFontWOFF = MimeType{MimeType: "application/font-woff", FileExtension: []string{".woff"}}
|
||||
var TypeWebJSON = MimeType{MimeType: "application/json", FileExtension: []string{".json"}}
|
||||
var TypeCSV = MimeType{MimeType: "text/csv", FileExtension: []string{".csv"}}
|
7
MimeTypes/Scheme.go
Normal file
7
MimeTypes/Scheme.go
Normal file
@ -0,0 +1,7 @@
|
||||
package MimeTypes
|
||||
|
||||
// The type for a MIME type.
|
||||
type MimeType struct {
|
||||
MimeType string
|
||||
FileExtension []string
|
||||
}
|
41
MimeTypes/Variables.go
Normal file
41
MimeTypes/Variables.go
Normal file
@ -0,0 +1,41 @@
|
||||
package MimeTypes
|
||||
|
||||
var (
|
||||
allTypes [34]MimeType // Array for all known types
|
||||
|
||||
// Create instances for each known types:
|
||||
TypeWebHTML = MimeType{MimeType: "text/html", FileExtension: []string{".html", ".htm"}}
|
||||
TypeWebCSS = MimeType{MimeType: "text/css", FileExtension: []string{".css"}}
|
||||
TypeWebJavaScript = MimeType{MimeType: "text/javascript", FileExtension: []string{".js"}}
|
||||
TypeWebDart = MimeType{MimeType: "application/dart", FileExtension: []string{".dart"}}
|
||||
TypeXML = MimeType{MimeType: "text/xml", FileExtension: []string{".xml"}}
|
||||
TypeArchiveZIP = MimeType{MimeType: "application/zip", FileExtension: []string{".zip"}}
|
||||
TypeArchiveGZ = MimeType{MimeType: "application/gzip", FileExtension: []string{".gz"}}
|
||||
TypeWebOCTET = MimeType{MimeType: "application/octet-stream", FileExtension: []string{".bin", ".exe", ".dll", ".class"}}
|
||||
TypeDocumentPDF = MimeType{MimeType: "application/pdf", FileExtension: []string{".pdf"}}
|
||||
TypeDocumentLaTeX = MimeType{MimeType: "application/x-latex", FileExtension: []string{".tex", ".latex"}}
|
||||
TypeShockwave = MimeType{MimeType: "application/x-shockwave-flash", FileExtension: []string{".swf"}}
|
||||
TypeArchiveTAR = MimeType{MimeType: "application/x-tar", FileExtension: []string{".tar"}}
|
||||
TypeAudioWAV = MimeType{MimeType: "application/x-wav", FileExtension: []string{".wav"}}
|
||||
TypeAudioMP3 = MimeType{MimeType: "audio/mpeg", FileExtension: []string{".mp3"}}
|
||||
TypeAudioAAC = MimeType{MimeType: "audio/aac", FileExtension: []string{".aac", ".m4a"}}
|
||||
TypeAudioOGG = MimeType{MimeType: "audio/ogg", FileExtension: []string{"vogg", ".oga"}}
|
||||
TypeAudioWMA = MimeType{MimeType: "audio/x-ms-wma", FileExtension: []string{".wma"}}
|
||||
TypeImageGIF = MimeType{MimeType: "image/gif", FileExtension: []string{".gif"}}
|
||||
TypeImageCommon = MimeType{MimeType: "image", FileExtension: []string{}}
|
||||
TypeUnknown = MimeType{MimeType: "application/octet-stream", FileExtension: []string{}}
|
||||
TypeImageJPEG = MimeType{MimeType: "image/jpeg", FileExtension: []string{".jpg", ".jpeg"}}
|
||||
TypeImagePNG = MimeType{MimeType: "image/png", FileExtension: []string{".png"}}
|
||||
TypePlainText = MimeType{MimeType: "text/plain", FileExtension: []string{".txt"}}
|
||||
TypeVideoMPEG = MimeType{MimeType: "video/mpeg", FileExtension: []string{".mpeg", ".mpg"}}
|
||||
TypeVideoMOV = MimeType{MimeType: "video/quicktime", FileExtension: []string{".mov", ".qt"}}
|
||||
TypeVideoAVI = MimeType{MimeType: "video/x-msvideo", FileExtension: []string{".avi"}}
|
||||
TypeVideoMP4 = MimeType{MimeType: "video/mp4", FileExtension: []string{".mp4"}}
|
||||
TypeFontEOT = MimeType{MimeType: "application/vnd.ms-fontobject", FileExtension: []string{".eot"}}
|
||||
TypeFontOTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".otf"}}
|
||||
TypeImageSVG = MimeType{MimeType: "image/svg+xml", FileExtension: []string{".svg"}}
|
||||
TypeFontTTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".ttf"}}
|
||||
TypeFontWOFF = MimeType{MimeType: "application/font-woff", FileExtension: []string{".woff"}}
|
||||
TypeWebJSON = MimeType{MimeType: "application/json", FileExtension: []string{".json"}}
|
||||
TypeCSV = MimeType{MimeType: "text/csv", FileExtension: []string{".csv"}}
|
||||
)
|
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Function to write a MIME type to a client.
|
||||
func Write2HTTP(response http.ResponseWriter, mime MimeType) {
|
||||
response.Header().Add(`Content-Type`, mime.MimeType)
|
||||
}
|
||||
|
@ -9,9 +9,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Init this package.
|
||||
func init() {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Init the number generator.`)
|
||||
|
||||
// Get the exklusive access to the channel list:
|
||||
channelListLock.Lock()
|
||||
defer channelListLock.Unlock()
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user