diff --git a/others/db_changes/data_alias/basic/init/struct/AditoBasic/create_ab_loghistory.xml b/others/db_changes/data_alias/basic/init/struct/AditoBasic/create_ab_loghistory.xml
new file mode 100644
index 0000000000000000000000000000000000000000..730afa8142bc35226670c692915eebfd0849c3b6
--- /dev/null
+++ b/others/db_changes/data_alias/basic/init/struct/AditoBasic/create_ab_loghistory.xml
@@ -0,0 +1,24 @@
+<?xml version="1.1" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
+    <changeSet author="m.kuhn" id="fbe17fba-5bf7-4203-8f6f-01723e07194c">
+        <createTable tableName="AB_LOGHISTORY">
+            <column name="AB_LOGHISTORYID" type="CHAR(36)">
+                <constraints primaryKey="true" primaryKeyName="PK_AB_LOGHISTORY_AB_LOGHISTORYID"/>
+            </column>
+            <column name="USER_EDIT" type="NVARCHAR(50)"/>
+            <column name="USER_NEW" type="NVARCHAR(50)">
+                <constraints nullable="false"/>
+            </column> 
+            <column name="DATE_EDIT" type="TIMESTAMP"/>
+            <column name="DATE_NEW" type="TIMESTAMP">
+                <constraints nullable="false"/>
+            </column>          
+            <column name="DESCRIPTION" type="LONGVARCHAR(2147483647)"/>
+            <column name="LOGTYPE" type="CHAR(1)"/>
+            <column name="SOURCE_TABLENAME" type="NVARCHAR(30)"/>
+            <column name="SOURCE_TABLENAMEID" type="CHAR(36)"/>
+            <column name="TABLENAME" type="NVARCHAR(30)"/>
+            <column name="TABLENAMEID" type="CHAR(36)"/>
+        </createTable>
+    </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/process/Loghistory_lib/Loghistory_lib.aod b/process/Loghistory_lib/Loghistory_lib.aod
new file mode 100644
index 0000000000000000000000000000000000000000..a88fbae558bce3d2e5167603a24fad6ada8c344c
--- /dev/null
+++ b/process/Loghistory_lib/Loghistory_lib.aod
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.0" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.0">
+  <name>Loghistory_lib</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/Loghistory_lib/process.js</process>
+  <variants>
+    <element>LIBRARY</element>
+  </variants>
+</process>
diff --git a/process/Loghistory_lib/process.js b/process/Loghistory_lib/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3079c490e28b519fc8c29f75b80cc9b4a233bd9
--- /dev/null
+++ b/process/Loghistory_lib/process.js
@@ -0,0 +1,297 @@
+import("system.project");
+import("system.calendars");
+import("system.db");
+import("system.text");
+import("system.datetime");
+import("system.translate");
+import("system.SQLTYPES");
+import("Keyword_lib");
+import("Sql_lib");
+import("Attribute_lib");
+import("Contact_lib");
+import("AddressEntity_lib");
+import("Util_lib");
+
+/*
+* Saves changes done to db-columns that have been turned on for auditing in the repository into the table AB_LOGHISTORY
+*
+* @param {String} pTable req TableName
+* @param {String} pUser req UserName
+* @param {String []} pColumns req ColumnNames
+* @param {String []} pNewValues req new Values
+* @param {String []} pOldValues req old Values
+* @param {Date} pTimeStamp req TimeStamp
+* @param {String} pAction req SQLAction
+* @param {String} pIdValue req IdValue
+* 
+* @return {} void
+*/
+
+function logHistory (pTable, pUser, pColumns, pNewValues, pOldValues, pTimeStamp, pAction, pIdValue)
+{
+    var references = {};
+    var columns = {};
+    var toLog = false;
+
+    pTable = pTable.toUpperCase();
+    pColumns = pColumns.map(function (v){ return v.toUpperCase(); });
+
+    var Structure = project.getAliasDefinitionStructure("Data_alias", pTable);
+    if (Structure) 
+    {
+        columns = Structure.tables[pTable].columns;
+        for (var column in columns)
+        {
+            if (columns[column].tableRef != null) references[column] = {table: columns[column].tableRef, id: ""};
+            if (columns[column].primaryKey) primaryKey = column;
+            if (columns[column].log) toLog = columns[column].log;
+        }
+    }
+    
+    if (toLog)
+    {
+        var idvalue = pIdValue;
+        var description = [];
+        var extra = [];
+        extra["COMMUNICATION"] = {
+            IDs: ["CONTACT_ID", "MEDIUM_ID"],
+            RefTable: "CONTACT",
+            Description: "Kommunikation"
+        };
+        extra["AB_ATTRIBUTERELATION"] = {
+            IDs: ["OBJECT_ROWID", "AB_ATTRIBUTE_ID"],
+            Description: "Eigenschaft"
+        };
+        
+        if (extra[pTable])
+        {
+            var conf = extra[pTable];
+            var oldvalues = [];
+            var newvalues = [];
+
+            for(i = 0; i < pColumns.length; i++ )
+            {
+                if (pAction == 'D' || pAction == 'U')  oldvalues[pColumns[i]] =  pOldValues[i];
+                if (pAction == 'I' || pAction == 'U')  newvalues[pColumns[i]] =  pNewValues[i];
+            }
+            if (pAction == 'D') newvalues = oldvalues;
+            if ((pAction == 'D' || pAction == 'I') && newvalues[conf.IDs[1]])
+            {
+                idvalue = newvalues[conf.IDs[0]];
+                var data = _getData(pTable, newvalues[conf.IDs[1]], newvalues);
+                description.push(conf.Description + " " + data[0] + ": '" +  data[1] + "'");
+            }
+            if (pAction == 'U')
+            {
+                var ids = db.array(db.ROW, "select " + conf.IDs.join(", ") + " from " + pTable + " where " + pTable + "ID = '" + pIdValue + "'");
+                idvalue = ids[0];
+                var oldid =  ids[1];
+                if (oldvalues[conf.IDs[1]]) oldid =  oldvalues[conf.IDs[1]];
+                var olddata = _getData(pTable, oldid, oldvalue);
+                var newdata = _getData(pTable, ids[1], newvalues);
+                if (newdata[1] && olddata[1])
+                {
+                    if (olddata[0] == newdata[0]) description.push(conf.Description + " " + olddata[0] + " von '" +  olddata[1] + "' auf '" + newdata[1]  + "' ");
+                    else description.push(conf.Description + " " + olddata[0] + " von '" +  olddata[1] + "' auf " + conf.Description + " " + newdata[0] + " '" + newdata[1]  + "' ");
+                } 
+                else if(pTable == "COMMUNICATION")
+                {
+                    description.push(conf.Description + " Medium von '" +  olddata[0] + "' auf '" + newdata[0] + "' ");
+                }
+            }
+            if (conf.RefTable) pTable = conf.RefTable;
+        } //no extra tables
+        else	
+        {
+            if (pTable == "ASYS_CALENDARBACKEND") {
+                var entrytypePosition = pColumns.indexOf("ENTRYTYPE");
+                if (entrytypePosition > -1) if (pNewValues[entrytypePosition] == calendars.VEVENT) return;
+            }
+
+            for(i = 0; i < pColumns.length; i++ )
+            {
+                if (pTable == "ASYS_CALENDARBACKEND" && pColumns[i] == "VCOMPONENT") _getCalendarDescription(pAction, i, pNewValues, pOldValues);
+                if (references[pColumns[i]])
+                {
+                    if (pAction == "I") references[pColumns[i]].id = pNewValues[i];
+                    if (pAction == "D") references[pColumns[i]].id = pOldValues[i];
+                }
+                var logfield = columns[pColumns[i]];
+                if (logfield && logfield.log)
+                {
+                    if (pAction != 'I' && pOldValues[i] != "") pOldValues[i] = _getFormattedValue(logfield, pOldValues[i]);
+                    if (pAction != 'D' && pNewValues[i] != "") pNewValues[i] = _getFormattedValue(logfield, pNewValues[i]);
+                    if (pAction == 'U' && pOldValues[i] != pNewValues[i])
+                    {
+                        let value = pOldValues[i] == "[CLOB]" || pOldValues[i] == "" ? ":" : ": von '" + pOldValues[i];
+                        description.push(logfield.title + value + "' auf '" + pNewValues[i] + "'");
+                    }
+                    if (pAction == 'I' && pNewValues[i] != "") description.push(logfield.title + ": '" + pNewValues[i] + "'");
+                    if (pAction == 'D' && pOldValues[i] != "") description.push(logfield.title + ": '" + pOldValues[i] + "'");
+                }
+            }
+            if (pAction == "U")
+            {
+                for (var index in references) references[index].id = db.cell("select " + index + " from "  + pTable + " where " + primaryKey + " = '" + pIdValue + "'");
+            }
+        }
+        
+        if (description.length > 0)
+        {
+            if (pAction == 'I') description = description.join(", ") + " eingefügt.";
+            if (pAction == 'U') description = description.join(", ") + " geändert.";
+            if (pAction == 'D') description = description.join(", ") + " gelöscht.";
+            var cols = ["LOGTYPE","TABLENAME","TABLENAMEID","DESCRIPTION", "SOURCE_TABLENAME", "SOURCE_TABLENAMEID", "DATE_NEW","USER_NEW"];
+            for (index in references)
+            {
+                if (references[index].id != "")
+                {
+                    db.insertData("ASYS_LOGHISTORY", cols, null, [pAction, references[index].table.trim(), references[index].id, description, pTable.trim(), idvalue, pTimeStamp, pUser]);
+                    idvalue = ""; 
+                }
+            }
+            if (idvalue !=  "") db.insertData("ASYS_LOGHISTORY", cols, null, [pAction, pTable.trim(), idvalue, description, "", "", pTimeStamp, pUser]);
+        }
+    }
+}
+
+/*
+* Creates an Array of AB_LOGHISTORY data for display in a view
+*
+* @param {String} pCondition req TableName
+*
+* @return {[]}	table
+*/
+function showLoghistory(pCondition)
+{
+    var loglist = db.createEmptyTable(3);
+    var data = db.table("select DATE_NEW, USER_NEW, DESCRIPTION from ASYS_LOGHISTORY where " + pCondition + " order by DATE_NEW desc");
+
+    if (data.length > 0)
+    {
+        loglist = [];
+        var groupdate = datetime.toDate(data[0][0], translate.text("dd.MM.yyyy HH:mm"));
+        var logdate = data[0][0];
+        var loguser = data[0][1];
+        var descripton = data[0][2];
+
+        for (var i = 1; i < data.length; i++)
+        {
+            if (groupdate + loguser == datetime.toDate(data[i][0], translate.text("dd.MM.yyyy HH:mm")) + data[i][1])
+            {
+                if (descripton != data[i][2]) descripton = data[i][2] + "\n" + descripton;
+            }
+            else
+            {
+                loglist.push([logdate, loguser, descripton]);
+                groupdate = datetime.toDate(data[i][0], translate.text("dd.MM.yyyy HH:mm"));
+                logdate = data[i][0];
+                loguser = data[i][1];
+                descripton = data[i][2];
+            }
+        }
+        loglist.push([logdate, loguser, descripton]);
+    }
+    return loglist;
+}
+
+/*
+* Creates the data for the tables with special cases
+*
+* @param {String} pTable the table name
+* @param {String} pId the table id
+* @param {[]} pValues the values 
+*
+* @return {[]}	table
+*/
+function _getData(pTable, pId, pValues)
+{
+    var data = [];
+    //TODO: Attribute_lib
+    if (pTable == "AB_ATTRIBUTERELATION") data = GetAttrAudit(pId, pValues[getValueFieldName(pId)]); 
+    if (pTable == "COMMUNICATION")
+    {
+        data[0] = KeywordUtils.getResolvedTitleSqlPart("MediumOrgPers", pId, false);
+        data[1] = pValues["ADDR"];
+    }
+    return data;
+}
+
+/*
+* Creates an Array of AB_LOGHISTORY data for display in a view
+*
+* @param {String} pCondition req TableName
+*
+* @return {[]}	table
+*/
+function _getFormattedValue(pDescription, pValue)
+{
+    if (pDescription.keyword != null && pDescription.keyword != "")
+    {
+        if (/^(; \d*)*$/.test(pValue)) 
+        {
+            pValue = text.decodeMS(pValue);
+            for (let i = 0; i < pValue.length; i++)
+            {              
+                if (!isNaN(pValue[i])) pValue[i] = getKeyName(pValue[i], pDescription.keyword);
+            }
+            pValue = pValue.join(", ");
+        }
+        else pValue = getKeyName(pValue, pDescription.keyword);
+    }
+    else if (pDescription.translate4Log != null && pDescription.translate4Log != "") 
+    {
+        var params = {
+             rowId: pIdValue
+            ,value: pValue.toString()
+            ,action: pAction
+        };
+        pValue = evalScript("Loghistory_lib._getFormattedValue", pDescription.translate4Log.replace(/{value}/gi, pValue.toString()), params);
+    }
+    else if (pDescription.columnType == String(SQLTYPES.TIMESTAMP))  pValue = datetime.toDate(pValue, "dd.MM.yyyy", "Europe/Berlin");
+    else if (pDescription.autoMapTrueFalse4Log) 
+    {
+        switch (pValue.toLowerCase())
+        {
+            case "true":
+            case "t":
+            case "y":
+            case "1":
+                pValue = translate.text("Ja");
+                break;
+            case "false":
+            case "f":
+            case "n":
+            case "0":
+            case "2":
+                pValue = translate.text("Nein");
+                break;
+            default:
+                break;
+        }
+    }
+    return pValue;
+}
+
+/*
+* Creates the description for changes in the calendar
+*
+* @param {String} pAction the user action
+* @param {String} pId the talbe id
+* @param {String} pNewValue the old entry
+* @param {String} pOldValue the new entry
+*
+* @return {String} the description of the action
+*/
+function _getCalendarDescription(pAction, pId, pNewValue, pOldValue)
+{
+    if (pAction != "D")   pNewValue[pId] = _getEntry("DESCRIPTION:", pNewValue[pId]);
+    if (pAction != "I")   pOldValue[pId] = _getEntry("DESCRIPTION:", pOldValue[pId]);
+
+    function _getEntry(pWert, pVcomponent)
+    {
+        var value = pVcomponent.toString().match(new RegExp(pWert + ".+\r"));
+        if (value != null)  return value.toString().replace(pWert, "").replace(/\\R\\N/g, "\r\n");
+        else return "";
+    }
+}
diff --git a/process/process_audit/process.js b/process/process_audit/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..7dd2dd289bdaf5fd672c22104c2749f85e5dce55
--- /dev/null
+++ b/process/process_audit/process.js
@@ -0,0 +1,16 @@
+import("Loghist_lib");
+import("system.vars");
+
+var tableName = vars.get("$local.table");
+var id = vars.get("$local.idvalue");
+var columns = vars.get("$local.columns");
+var timestamp = vars.get("$local.timestamp");
+var newvalues = vars.get("$local.values");
+var oldvalues = vars.get("$local.oldvalues");
+var sqlAction = vars.get("$local.action");
+var userLogin = vars.get("$local.user");
+
+if (sqlAction != 'X')
+{
+    logHistory(tableName, userLogin, columns, newvalues, oldvalues, timestamp, sqlAction, id);
+}
diff --git a/process/process_audit/process_audit.aod b/process/process_audit/process_audit.aod
new file mode 100644
index 0000000000000000000000000000000000000000..018d83646b332fa9ffe82a2c4177909a3567df53
--- /dev/null
+++ b/process/process_audit/process_audit.aod
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.0" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.0">
+  <name>process_audit</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/process_audit/process.js</process>
+</process>