diff --git a/mc_openapi/__init__.py b/mc_openapi/__init__.py
index 04188a16d9f5cb090be122ae5b3ac5f1253f9516..82190396f2ff50b1bddd2bf9495146ebc4265d13 100644
--- a/mc_openapi/__init__.py
+++ b/mc_openapi/__init__.py
@@ -1 +1 @@
-__version__ = '2.2.0'
+__version__ = '2.3.0'
diff --git a/mc_openapi/__main__.py b/mc_openapi/__main__.py
index 439bf1998070276470a45651a3486f3d113bdfd4..f6aae0ce68e12eaaf3ade3c372f62a1fa000e238 100644
--- a/mc_openapi/__main__.py
+++ b/mc_openapi/__main__.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python3
 import argparse
+import logging
+from logging.config import dictConfig
 import sys
 
 from doml_synthesis.data import init_data
@@ -19,6 +21,7 @@ from mc_openapi.doml_mc.intermediate_model.metamodel import MetaModelDocs
 from mc_openapi.doml_mc.mc import ModelChecker
 from mc_openapi.doml_mc.mc_result import MCResult
 from mc_openapi.doml_mc.xmi_parser.doml_model import get_pyecore_model
+from . import __version__
 
 parser = argparse.ArgumentParser()
 
@@ -47,9 +50,29 @@ def printv(*_args):
 printv("== Verbose: ON ==")
 
 if not args.doml and not args.synth:
+    dictConfig({
+        'version': 1,
+        'formatters': {'default': {
+            'format': '[%(asctime)s] %(levelname)s: %(message)s',
+        }},
+        'handlers': {'wsgi': {
+            'class': 'logging.StreamHandler',
+            'stream': 'ext://flask.logging.wsgi_errors_stream',
+            'formatter': 'default'
+        }},
+        'root': {
+            'level': 'DEBUG',
+            'handlers': ['wsgi']
+        }
+    })
+    logging.info(f"DOML Model Checker v{__version__}")
+
     # Start the webserver
     app.run(port=args.port)
 else:
+    logging.basicConfig(level=logging.DEBUG, format='* %(message)s')
+    logging.info(f"DOML Model Checker v{__version__}")
+
     # Run only it via command line
     doml_path = args.doml
     reqs_path = args.requirements
@@ -111,7 +134,9 @@ else:
             print("Failed to parse the DOMLR.", file=sys.stderr)
             exit(-1)
 
-    if doml_ver == DOMLVersion.V2_2 or doml_ver == DOMLVersion.V2_2_1:
+    if (doml_ver == DOMLVersion.V2_2
+    or  doml_ver == DOMLVersion.V2_2_1
+    or  doml_ver == DOMLVersion.V2_3):
         model = get_pyecore_model(doml_xmi, doml_ver)
         func_reqs = model.functionalRequirements.items
         for req in func_reqs:
diff --git a/mc_openapi/app_config.py b/mc_openapi/app_config.py
index e60da49a058797b966cf9de426f0000d98daa4e3..f6234dd5585799ceb66b993316301db9b8ebc97f 100644
--- a/mc_openapi/app_config.py
+++ b/mc_openapi/app_config.py
@@ -1,5 +1,7 @@
 import connexion
 
+from logging.config import dictConfig
+
 app = connexion.App(__name__, specification_dir='openapi/')
 app.add_api('model_checker.yaml')
 
diff --git a/mc_openapi/doml_mc/domlr_parser/parser.py b/mc_openapi/doml_mc/domlr_parser/parser.py
index 811b0d2f4ec5e9d1a9a098822518eb5cc35c47f4..24036f181455600a28484e22fdba8611d0f1e0f8 100644
--- a/mc_openapi/doml_mc/domlr_parser/parser.py
+++ b/mc_openapi/doml_mc/domlr_parser/parser.py
@@ -1,3 +1,4 @@
+import logging
 import os
 import re
 from typing import Callable, Literal
@@ -258,7 +259,7 @@ class DOMLRTransformer(Transformer):
                                                  ),
                     self.compare_int(sorts, op, rhs1_value, rhs2_value)
                 )
-                print(
+                logging.warning(
                     "Warning: Comparing attributes of two elements with {op} is experimental!\n",
                     "Assumption: the attribute is an Integer."
                 )
diff --git a/mc_openapi/doml_mc/intermediate_model/metamodel.py b/mc_openapi/doml_mc/intermediate_model/metamodel.py
index a91d6b0ee4e35f4a40bc85d5db2ade84005eb7e8..bb4fb87194c7298d9a7abe503225e45137902d5a 100644
--- a/mc_openapi/doml_mc/intermediate_model/metamodel.py
+++ b/mc_openapi/doml_mc/intermediate_model/metamodel.py
@@ -223,7 +223,6 @@ def _find_attribute_class(
     if aname in c.attributes:
         return c
     elif c.superclass is None:
-        print(c)
         raise AttributeNotFound(
             f"Attribute {aname} not found in subclasses of {cname}."
         )
diff --git a/mc_openapi/doml_mc/mc.py b/mc_openapi/doml_mc/mc.py
index 3ef13ff6f339a22b9157b36ee91b0cc18bb364be..b675b6e4e4d65ba6356bfb9e3c2a92cc3b1d1d3c 100644
--- a/mc_openapi/doml_mc/mc.py
+++ b/mc_openapi/doml_mc/mc.py
@@ -1,3 +1,4 @@
+import logging
 from multiprocessing import TimeoutError
 from typing import Optional
 
@@ -22,7 +23,7 @@ class ModelChecker:
             xmi_model, doml_version)
         self.metamodel = MetaModels[self.doml_version]
         self.inv_assoc = InverseAssociations[self.doml_version]
-
+        
     def check_requirements(
         self,
         threads: int = 1,
diff --git a/mc_openapi/doml_mc/mc_result.py b/mc_openapi/doml_mc/mc_result.py
index aa3bc8ef684b789baf9aed17aaca8fb483ac8765..eb9e5bb7ac04d0fc58bd32365e66c42d6ac36eee 100644
--- a/mc_openapi/doml_mc/mc_result.py
+++ b/mc_openapi/doml_mc/mc_result.py
@@ -1,4 +1,5 @@
 from enum import Enum
+import logging
 from typing import Literal
 from z3 import CheckSatResult, sat, unsat, unknown
 
@@ -31,6 +32,7 @@ class MCResult(Enum):
 
 class MCResults:
     DONTKNOW_MSG = "Timed out: unable to check some requirements."
+    SATISFIED_MSG = "All requirements are satisfied."
 
     def __init__(self, results: list[tuple[MCResult, Literal["BUILTIN", "USER"], str]]):
         self.results = results
@@ -62,11 +64,14 @@ class MCResults:
 
             if some_dontknow:
                 err_msg += '\n' + MCResults.DONTKNOW_MSG
+            logging.info(err_msg)
             return MCResult.unsat, err_msg
         elif some_dontknow:
+            logging.info(MCResults.DONTKNOW_MSG)
             return MCResult.dontknow, MCResults.DONTKNOW_MSG
         else:
-            return MCResult.sat, "All requirements are satisfied."
+            logging.info(MCResults.SATISFIED_MSG)
+            return MCResult.sat, MCResults.SATISFIED_MSG
 
     def add_results(self, results: "MCResults"):
         self.results.extend(results.results)
diff --git a/mc_openapi/doml_mc/xmi_parser/doml_model.py b/mc_openapi/doml_mc/xmi_parser/doml_model.py
index 775eb727aee3666f263455458e27a5ee0cb1f794..cc77024f974931bd687c3f01a4fe65f76a9ad832 100644
--- a/mc_openapi/doml_mc/xmi_parser/doml_model.py
+++ b/mc_openapi/doml_mc/xmi_parser/doml_model.py
@@ -1,5 +1,6 @@
 import copy
 import importlib.resources as ilres
+import logging
 import sys
 from typing import Optional, Tuple
 
@@ -59,14 +60,14 @@ def infer_domlx_version(raw_model: bytes) -> DOMLVersion:
                 if v_str == "v2":
                     return DOMLVersion.V2_0
                 else:
-                    raise RuntimeError(f"Supplied with DOMLX model of unsupported version {v_str}")
+                    raise RuntimeError(f"DOML model is using an unsupported version: {v_str}")
         else:
             return DOMLVersion.V2_0  # Should be DOMLVersion.V1_0, but we use V2_0 because the 2.1 IDE doesn't fill it
     else:
-        raise RuntimeError(f"Supplied with malformed DOMLX model or unsupported DOML version.\nIn use version is: {DOMLVersion.V2_0}")
+        raise RuntimeError(f"The DOML version is unsupported or the model is malformed.\nLowest supported version is: {DOMLVersion.V2_0}")
 
 
-def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> Tuple[IntermediateModel, DOMLVersion]:
+def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> Tuple[IntermediateModel, DOMLVersion]:    
     # if doml_version is None:
     #     doml_version = infer_domlx_version(raw_model)
 
@@ -83,7 +84,7 @@ def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> T
                 doml_version = dv
                 return parse_xmi_model(raw_model, dv), dv
             except Exception as e:
-                print(f"Couldn't parse with DOML {dv.value}. Trying another version...")
+                logging.info(f"Couldn't parse with DOML {dv.value}. Trying another version...")
                 if len(doml_versions) == 0:
                     raise e
                 else:
@@ -96,7 +97,7 @@ def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> T
         except:
             raise Exception("Parsing of DOML failed. Perhaps you are using the wrong DOML version or IDE?")
 
-    print(f"Using DOML {doml_version.value}")
+    logging.info(f"Model '{model.name}' parsed as DOML {doml_version.value}")
 
     elp = ELayerParser(MetaModels[doml_version], SpecialParsers[doml_version])
     if model.application:
@@ -104,13 +105,13 @@ def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> T
     if model.infrastructure:
         elp.parse_elayer(model.infrastructure)
     else:
-        raise RuntimeError("Abstract infrastructure layer is missing.")
+        raise RuntimeError("Abstract infrastructure layer is missing from DOML.")
     if model.activeConfiguration:
         elp.parse_elayer(model.activeConfiguration)
     if model.activeInfrastructure:
         im = elp.parse_elayer(model.activeInfrastructure)
     else:
-        raise RuntimeError("No active concrete infrastructure layer has been specified.")
+        raise RuntimeError("No active concrete infrastructure layer has been specified in DOML.")
 
     reciprocate_inverse_associations(im, InverseAssociations[doml_version])
 
@@ -121,17 +122,3 @@ def get_pyecore_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) ->
         doml_version = infer_domlx_version(raw_model)
     # TODO: See if its better replaced by the get_model() in parse_doml_version() 
     return parse_xmi_model(raw_model, doml_version)
-
-from typing import Optional
-
-def serialize_pyecore_model(root: EObject, doml_version: DOMLVersion = DOMLVersion.V2_0, path: Optional[str] = None):
-    from pyecore.resources import URI
-
-    # Get rset with metamodel
-    rset = get_rset(doml_version) 
-    # Create the resource where we'll save the updated model/DOMLX
-    res = rset.create_resource(URI("./output.domlx"))
-    # Append updated EObject
-    res.append(root)
-    # Serialize to file
-    res.save()
diff --git a/mc_openapi/doml_mc/xmi_parser/ecore.py b/mc_openapi/doml_mc/xmi_parser/ecore.py
index 224128cc01a7174c8321418bea4f708368e5eb70..0cc79be1f358d0025e98dd2bdb0257713294952b 100644
--- a/mc_openapi/doml_mc/xmi_parser/ecore.py
+++ b/mc_openapi/doml_mc/xmi_parser/ecore.py
@@ -1,3 +1,4 @@
+import logging
 import sys
 from typing import Callable, Optional
 
@@ -74,7 +75,7 @@ class ELayerParser:
                     elif isinstance(val, EOrderedSet):
                         raw_attrs[eAttr.name] = [str(v) if isinstance(v, EEnumLiteral) else v for v in val]
                     else:
-                        print("Attribute", eAttr.name, "has value", val, "of unexpected type.", file=sys.stderr)
+                        logging.error("Attribute", eAttr.name, "has value", val, "of unexpected type.", file=sys.stderr)
         attrs = parse_attributes(raw_attrs, mm_class, self.mm)
 
         # Get all references and process them
diff --git a/mc_openapi/handlers.py b/mc_openapi/handlers.py
index d8d04772a59b0d038f08035d2bc4e437a7631218..0190955bd204a9f4ea29423502199fede859f746 100644
--- a/mc_openapi/handlers.py
+++ b/mc_openapi/handlers.py
@@ -1,4 +1,5 @@
 import datetime
+import logging
 import os
 from mc_openapi.doml_mc.domlr_parser.parser import DOMLRTransformer, Parser
 from mc_openapi.doml_mc.imc import RequirementStore
@@ -12,7 +13,7 @@ def make_error(user_msg, debug_msg=None):
     result = {"message": user_msg, "timestamp": datetime.datetime.now()}
     if debug_msg is not None:
         result["debug_message"] = debug_msg
-        print(f"ERROR [{datetime.datetime.now()}]: {debug_msg}")
+        logging.error(debug_msg)
     return result
 
 
@@ -28,7 +29,7 @@ def post(body, version=None):
             doml_version: str = version
         if doml_version:
             doml_version = DOMLVersion.get(doml_version)
-            print(f"Forcing DOML {doml_version.value}")
+            logging.info(f"Forcing DOML {doml_version.value}")
 
         dmc = ModelChecker(doml_xmi, doml_version)
 
@@ -37,7 +38,8 @@ def post(body, version=None):
 
         # Add support for Requirements in DOML
         if (dmc.doml_version == DOMLVersion.V2_2 
-        or  dmc.doml_version == DOMLVersion.V2_2_1):
+        or  dmc.doml_version == DOMLVersion.V2_2_1
+        or  dmc.doml_version == DOMLVersion.V2_3):
             domlr_parser = Parser(DOMLRTransformer)
             model = get_pyecore_model(doml_xmi, dmc.doml_version)
             func_reqs = model.functionalRequirements.items