Use consistent log file names, introduced initLogger (#3403)

### What problem does this PR solve?

Use consistent log file names, introduced initLogger

### Type of change

- [ ] Bug Fix (non-breaking change which fixes an issue)
- [ ] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [x] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):
This commit is contained in:
Zhichang Yu
2024-11-14 17:13:48 +08:00
committed by GitHub
parent ab4384e011
commit 30f6421760
75 changed files with 396 additions and 402 deletions

View File

@ -15,6 +15,7 @@
#
import os
import sys
import logging
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
from flask import Blueprint, Flask
@ -32,7 +33,6 @@ from flask_login import LoginManager
from api.settings import SECRET_KEY
from api.settings import API_VERSION
from api.utils.api_utils import server_error_response
from api.utils.log_utils import logger
from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer
__all__ = ["app"]
@ -154,7 +154,7 @@ def load_user(web_request):
else:
return None
except Exception:
logger.exception("load_user got exception")
logging.exception("load_user got exception")
return None
else:
return None

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import json
from functools import partial
from flask import request, Response
@ -23,7 +24,6 @@ from api.utils import get_uuid
from api.utils.api_utils import get_json_result, server_error_response, validate_request, get_data_error_result
from agent.canvas import Canvas
from peewee import MySQLDatabase, PostgresqlDatabase
from api.utils.log_utils import logger
@manager.route('/templates', methods=['GET'])
@ -115,7 +115,7 @@ def run():
pass
canvas.add_user_input(req["message"])
answer = canvas.run(stream=stream)
logger.info(canvas)
logging.debug(canvas)
except Exception as e:
return server_error_response(e)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import json
from flask import request
@ -25,7 +26,6 @@ from api.db.db_models import TenantLLM
from api.utils.api_utils import get_json_result
from rag.llm import EmbeddingModel, ChatModel, RerankModel, CvModel, TTSModel
import requests
from api.utils.log_utils import logger
@manager.route('/factories', methods=['GET'])
@ -90,7 +90,7 @@ def set_api_key():
if len(arr) == 0 or tc == 0:
raise Exception("Fail")
rerank_passed = True
logger.info(f'passed model rerank {llm.llm_name}')
logging.debug(f'passed model rerank {llm.llm_name}')
except Exception as e:
msg += f"\nFail to access model({llm.llm_name}) using this api key." + str(
e)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import json
import re
from datetime import datetime
@ -54,7 +55,6 @@ from api.settings import (
from api.db.services.user_service import UserService, TenantService, UserTenantService
from api.db.services.file_service import FileService
from api.utils.api_utils import get_json_result, construct_response
from api.utils.log_utils import logger
@manager.route("/login", methods=["POST", "GET"])
@ -177,7 +177,7 @@ def github_callback():
try:
avatar = download_img(user_info["avatar_url"])
except Exception as e:
logger.exception(e)
logging.exception(e)
avatar = ""
users = user_register(
user_id,
@ -202,7 +202,7 @@ def github_callback():
return redirect("/?auth=%s" % user.get_id())
except Exception as e:
rollback_user_registration(user_id)
logger.exception(e)
logging.exception(e)
return redirect("/?error=%s" % str(e))
# User has already registered, try to log in
@ -279,7 +279,7 @@ def feishu_callback():
try:
avatar = download_img(user_info["avatar_url"])
except Exception as e:
logger.exception(e)
logging.exception(e)
avatar = ""
users = user_register(
user_id,
@ -304,7 +304,7 @@ def feishu_callback():
return redirect("/?auth=%s" % user.get_id())
except Exception as e:
rollback_user_registration(user_id)
logger.exception(e)
logging.exception(e)
return redirect("/?error=%s" % str(e))
# User has already registered, try to log in
@ -436,7 +436,7 @@ def setting_user():
UserService.update_by_id(current_user.id, update_dict)
return get_json_result(data=True)
except Exception as e:
logger.exception(e)
logging.exception(e)
return get_json_result(
data=False, message="Update failure!", code=RetCode.EXCEPTION_ERROR
)
@ -621,7 +621,7 @@ def user_add():
)
except Exception as e:
rollback_user_registration(user_id)
logger.exception(e)
logging.exception(e)
return get_json_result(
data=False,
message=f"User registration failure, error: {str(e)}",

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import inspect
import os
import sys
@ -32,7 +33,6 @@ from playhouse.pool import PooledMySQLDatabase, PooledPostgresqlDatabase
from api.db import SerializedType, ParserType
from api.settings import DATABASE, SECRET_KEY, DATABASE_TYPE
from api import utils
from api.utils.log_utils import logger
def singleton(cls, *args, **kw):
instances = {}
@ -285,7 +285,7 @@ class BaseDataBase:
database_config = DATABASE.copy()
db_name = database_config.pop("name")
self.database_connection = PooledDatabase[DATABASE_TYPE.upper()].value(db_name, **database_config)
logger.info('init database on cluster mode successfully')
logging.info('init database on cluster mode successfully')
class PostgresDatabaseLock:
def __init__(self, lock_name, timeout=10, db=None):
@ -393,7 +393,7 @@ def close_connection():
if DB:
DB.close_stale(age=30)
except Exception as e:
logger.exception(e)
logging.exception(e)
class DataBaseModel(BaseModel):
@ -409,15 +409,15 @@ def init_database_tables(alter_fields=[]):
for name, obj in members:
if obj != DataBaseModel and issubclass(obj, DataBaseModel):
table_objs.append(obj)
logger.info(f"start create table {obj.__name__}")
logging.debug(f"start create table {obj.__name__}")
try:
obj.create_table()
logger.info(f"create table success: {obj.__name__}")
logging.debug(f"create table success: {obj.__name__}")
except Exception as e:
logger.exception(e)
logging.exception(e)
create_failed_list.append(obj.__name__)
if create_failed_list:
logger.info(f"create tables failed: {create_failed_list}")
logging.error(f"create tables failed: {create_failed_list}")
raise Exception(f"create tables failed: {create_failed_list}")
migrate_db()

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import base64
import json
import os
@ -30,7 +31,6 @@ from api.db.services.llm_service import LLMFactoriesService, LLMService, TenantL
from api.db.services.user_service import TenantService, UserTenantService
from api.settings import CHAT_MDL, EMBEDDING_MDL, ASR_MDL, IMAGE2TEXT_MDL, PARSERS, LLM_FACTORY, API_KEY, LLM_BASE_URL
from api.utils.file_utils import get_project_base_directory
from api.utils.log_utils import logger
def encode_to_base64(input_string):
@ -70,26 +70,26 @@ def init_superuser():
"api_key": API_KEY, "api_base": LLM_BASE_URL})
if not UserService.save(**user_info):
logger.info("can't init admin.")
logging.error("can't init admin.")
return
TenantService.insert(**tenant)
UserTenantService.insert(**usr_tenant)
TenantLLMService.insert_many(tenant_llm)
logger.info(
"Super user initialized. email: admin@ragflow.io, password: admin. Changing the password after logining is strongly recomanded.")
logging.info(
"Super user initialized. email: admin@ragflow.io, password: admin. Changing the password after login is strongly recommended.")
chat_mdl = LLMBundle(tenant["id"], LLMType.CHAT, tenant["llm_id"])
msg = chat_mdl.chat(system="", history=[
{"role": "user", "content": "Hello!"}], gen_conf={})
if msg.find("ERROR: ") == 0:
logger.error(
logging.error(
"'{}' dosen't work. {}".format(
tenant["llm_id"],
msg))
embd_mdl = LLMBundle(tenant["id"], LLMType.EMBEDDING, tenant["embd_id"])
v, c = embd_mdl.encode(["Hello!"])
if c == 0:
logger.error(
logging.error(
"'{}' dosen't work!".format(
tenant["embd_id"]))
@ -172,7 +172,7 @@ def add_graph_templates():
except:
CanvasTemplateService.update_by_id(cnvs["id"], cnvs)
except Exception:
logger.exception("Add graph templates error: ")
logging.exception("Add graph templates error: ")
def init_web_data():
@ -183,7 +183,7 @@ def init_web_data():
# init_superuser()
add_graph_templates()
logger.info("init web data success:{}".format(time.time() - start_time))
logging.info("init web data success:{}".format(time.time() - start_time))
if __name__ == '__main__':

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import binascii
import os
import json
@ -31,7 +32,6 @@ from rag.app.resume import forbidden_select_fields4resume
from rag.nlp.search import index_name
from rag.utils import rmSpace, num_tokens_from_string, encoder
from api.utils.file_utils import get_project_base_directory
from api.utils.log_utils import logger
class DialogService(CommonService):
@ -178,7 +178,7 @@ def chat(dialog, messages, stream=True, **kwargs):
tts_mdl = LLMBundle(dialog.tenant_id, LLMType.TTS)
# try to use sql if field mapping is good to go
if field_map:
logger.info("Use SQL to retrieval:{}".format(questions[-1]))
logging.debug("Use SQL to retrieval:{}".format(questions[-1]))
ans = use_sql(questions[-1], field_map, dialog.tenant_id, chat_mdl, prompt_config.get("quote", True))
if ans:
yield ans
@ -220,7 +220,7 @@ def chat(dialog, messages, stream=True, **kwargs):
doc_ids=attachments,
top=dialog.top_k, aggs=False, rerank_mdl=rerank_mdl)
knowledges = [ck["content_with_weight"] for ck in kbinfos["chunks"]]
logger.info(
logging.debug(
"{}->{}".format(" ".join(questions), "\n->".join(knowledges)))
retrieval_tm = timer()
@ -292,7 +292,7 @@ def chat(dialog, messages, stream=True, **kwargs):
yield decorate_answer(answer)
else:
answer = chat_mdl.chat(prompt, msg[1:], gen_conf)
logger.info("User: {}|Assistant: {}".format(
logging.debug("User: {}|Assistant: {}".format(
msg[-1]["content"], answer))
res = decorate_answer(answer)
res["audio_binary"] = tts(tts_mdl, answer)
@ -320,7 +320,7 @@ def use_sql(question, field_map, tenant_id, chat_mdl, quota=True):
nonlocal sys_prompt, user_promt, question, tried_times
sql = chat_mdl.chat(sys_prompt, [{"role": "user", "content": user_promt}], {
"temperature": 0.06})
logger.info(f"{question} ==> {user_promt} get SQL: {sql}")
logging.debug(f"{question} ==> {user_promt} get SQL: {sql}")
sql = re.sub(r"[\r\n]+", " ", sql.lower())
sql = re.sub(r".*select ", "select ", sql.lower())
sql = re.sub(r" +", " ", sql)
@ -340,7 +340,7 @@ def use_sql(question, field_map, tenant_id, chat_mdl, quota=True):
flds.append(k)
sql = "select doc_id,docnm_kwd," + ",".join(flds) + sql[8:]
logger.info(f"{question} get SQL(refined): {sql}")
logging.debug(f"{question} get SQL(refined): {sql}")
tried_times += 1
return retrievaler.sql_retrieval(sql, format="json"), sql
@ -369,9 +369,9 @@ def use_sql(question, field_map, tenant_id, chat_mdl, quota=True):
question, sql, tbl["error"]
)
tbl, sql = get_table()
logger.info("TRY it again: {}".format(sql))
logging.debug("TRY it again: {}".format(sql))
logger.info("GET table: {}".format(tbl))
logging.debug("GET table: {}".format(tbl))
if tbl.get("error") or len(tbl["rows"]) == 0:
return None
@ -401,7 +401,7 @@ def use_sql(question, field_map, tenant_id, chat_mdl, quota=True):
rows = re.sub(r"T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+Z)?\|", "|", rows)
if not docid_idx or not docnm_idx:
logger.warning("SQL missing field: " + sql)
logging.warning("SQL missing field: " + sql)
return {
"answer": "\n".join([clmns, line, rows]),
"reference": {"chunks": [], "doc_aggs": []},

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import hashlib
import json
import random
@ -39,7 +40,6 @@ from api.db.services.common_service import CommonService
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db import StatusEnum
from rag.utils.redis_conn import REDIS_CONN
from api.utils.log_utils import logger
class DocumentService(CommonService):
@ -387,7 +387,7 @@ class DocumentService(CommonService):
cls.update_by_id(d["id"], info)
except Exception as e:
if str(e).find("'0'") < 0:
logger.exception("fetch task exception")
logging.exception("fetch task exception")
@classmethod
@DB.connection_context()
@ -544,7 +544,7 @@ def doc_upload_and_parse(conversation_id, file_objs, user_id):
"knowledge_graph_kwd": "mind_map"
})
except Exception as e:
logger.exception("Mind map generation error")
logging.exception("Mind map generation error")
vects = embedding(doc_id, [c["content_with_weight"] for c in cks])
assert len(cks) == len(vects)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import re
import os
from concurrent.futures import ThreadPoolExecutor
@ -30,7 +31,6 @@ from api.db.services.file2document_service import File2DocumentService
from api.utils import get_uuid
from api.utils.file_utils import filename_type, thumbnail_img
from rag.utils.storage_factory import STORAGE_IMPL
from api.utils.log_utils import logger
class FileService(CommonService):
@ -276,7 +276,7 @@ class FileService(CommonService):
return cls.model.delete().where((cls.model.tenant_id == user_id)
& (cls.model.id == folder_id)).execute(),
except Exception:
logger.exception("delete_folder_by_pf_id")
logging.exception("delete_folder_by_pf_id")
raise RuntimeError("Database error (File retrieval)!")
@classmethod
@ -325,7 +325,7 @@ class FileService(CommonService):
try:
cls.filter_update((cls.model.id << file_ids, ), { 'parent_id': folder_id })
except Exception:
logger.exception("move_file")
logging.exception("move_file")
raise RuntimeError("Database error (File move)!")
@classmethod

View File

@ -13,13 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
from api.db.services.user_service import TenantService
from rag.llm import EmbeddingModel, CvModel, ChatModel, RerankModel, Seq2txtModel, TTSModel
from api.db import LLMType
from api.db.db_models import DB
from api.db.db_models import LLMFactories, LLM, TenantLLM
from api.db.services.common_service import CommonService
from api.utils.log_utils import logger
class LLMFactoriesService(CommonService):
@ -209,7 +209,7 @@ class LLMBundle(object):
emd, used_tokens = self.mdl.encode(texts, batch_size)
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens):
logger.error(
logging.error(
"LLMBundle.encode can't update token usage for {}/EMBEDDING used_tokens: {}".format(self.tenant_id, used_tokens))
return emd, used_tokens
@ -217,7 +217,7 @@ class LLMBundle(object):
emd, used_tokens = self.mdl.encode_queries(query)
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens):
logger.error(
logging.error(
"LLMBundle.encode_queries can't update token usage for {}/EMBEDDING used_tokens: {}".format(self.tenant_id, used_tokens))
return emd, used_tokens
@ -225,7 +225,7 @@ class LLMBundle(object):
sim, used_tokens = self.mdl.similarity(query, texts)
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens):
logger.error(
logging.error(
"LLMBundle.similarity can't update token usage for {}/RERANK used_tokens: {}".format(self.tenant_id, used_tokens))
return sim, used_tokens
@ -233,7 +233,7 @@ class LLMBundle(object):
txt, used_tokens = self.mdl.describe(image, max_tokens)
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens):
logger.error(
logging.error(
"LLMBundle.describe can't update token usage for {}/IMAGE2TEXT used_tokens: {}".format(self.tenant_id, used_tokens))
return txt
@ -241,7 +241,7 @@ class LLMBundle(object):
txt, used_tokens = self.mdl.transcription(audio)
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens):
logger.error(
logging.error(
"LLMBundle.transcription can't update token usage for {}/SEQUENCE2TXT used_tokens: {}".format(self.tenant_id, used_tokens))
return txt
@ -250,7 +250,7 @@ class LLMBundle(object):
if isinstance(chunk,int):
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, chunk, self.llm_name):
logger.error(
logging.error(
"LLMBundle.tts can't update token usage for {}/TTS".format(self.tenant_id))
return
yield chunk
@ -259,7 +259,7 @@ class LLMBundle(object):
txt, used_tokens = self.mdl.chat(system, history, gen_conf)
if isinstance(txt, int) and not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, used_tokens, self.llm_name):
logger.error(
logging.error(
"LLMBundle.chat can't update token usage for {}/CHAT llm_name: {}, used_tokens: {}".format(self.tenant_id, self.llm_name, used_tokens))
return txt
@ -268,7 +268,7 @@ class LLMBundle(object):
if isinstance(txt, int):
if not TenantLLMService.increase_usage(
self.tenant_id, self.llm_type, txt, self.llm_name):
logger.error(
logging.error(
"LLMBundle.chat_streamly can't update token usage for {}/CHAT llm_name: {}, content: {}".format(self.tenant_id, self.llm_name, txt))
return
yield txt

View File

@ -15,6 +15,17 @@
#
import logging
import inspect
from api.utils.log_utils import initRootLogger
initRootLogger(inspect.getfile(inspect.currentframe()))
for module in ["pdfminer"]:
module_logger = logging.getLogger(module)
module_logger.setLevel(logging.WARNING)
for module in ["peewee"]:
module_logger = logging.getLogger(module)
module_logger.handlers.clear()
module_logger.propagate = True
import os
import signal
import sys
@ -22,7 +33,6 @@ import time
import traceback
from concurrent.futures import ThreadPoolExecutor
import validation
from werkzeug.serving import run_simple
from api.apps import app
from api.db.runtime_config import RuntimeConfig
@ -31,7 +41,6 @@ from api.settings import (
HOST, HTTP_PORT
)
from api import utils
from api.utils.log_utils import logger
from api.db.db_models import init_database_tables as init_web_db
from api.db.init_data import init_web_data
@ -44,11 +53,11 @@ def update_progress():
try:
DocumentService.update_progress()
except Exception:
logger.exception("update_progress exception")
logging.exception("update_progress exception")
if __name__ == '__main__':
logger.info(r"""
logging.info(r"""
____ ___ ______ ______ __
/ __ \ / | / ____// ____// /____ _ __
/ /_/ // /| | / / __ / /_ / // __ \| | /| / /
@ -56,10 +65,10 @@ if __name__ == '__main__':
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/
""")
logger.info(
logging.info(
f'RAGFlow version: {RAGFLOW_VERSION_INFO}'
)
logger.info(
logging.info(
f'project base: {utils.file_utils.get_project_base_directory()}'
)
@ -83,26 +92,18 @@ if __name__ == '__main__':
RuntimeConfig.DEBUG = args.debug
if RuntimeConfig.DEBUG:
logger.info("run on debug mode")
logging.info("run on debug mode")
RuntimeConfig.init_env()
RuntimeConfig.init_config(JOB_SERVER_HOST=HOST, HTTP_PORT=HTTP_PORT)
peewee_logger = logging.getLogger("peewee")
peewee_logger.propagate = False
# rag_arch.common.log.ROpenHandler
peewee_logger.addHandler(logger.handlers[0])
peewee_logger.setLevel(logger.handlers[0].level)
thr = ThreadPoolExecutor(max_workers=1)
thr.submit(update_progress)
# start http server
try:
logger.info("RAG Flow http server start...")
werkzeug_logger = logging.getLogger("werkzeug")
for h in logger.handlers:
werkzeug_logger.addHandler(h)
logging.info("RAG Flow http server start...")
run_simple(
hostname=HOST,
port=HTTP_PORT,

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import functools
import json
import random
@ -40,7 +41,6 @@ from api.settings import (
from api.settings import RetCode
from api.utils import CustomJSONEncoder, get_uuid
from api.utils import json_dumps
from api.utils.log_utils import logger
requests.models.complexjson.dumps = functools.partial(
json.dumps, cls=CustomJSONEncoder)
@ -118,7 +118,7 @@ def get_data_error_result(code=RetCode.DATA_ERROR,
def server_error_response(e):
logger.exception(e)
logging.exception(e)
try:
if e.code == 401:
return get_json_result(code=401, message=repr(e))
@ -259,7 +259,7 @@ def construct_json_result(code=RetCode.SUCCESS, message='success', data=None):
def construct_error_response(e):
logger.exception(e)
logging.exception(e)
try:
if e.code == 401:
return construct_json_result(code=RetCode.UNAUTHORIZED, message=repr(e))

View File

@ -14,38 +14,41 @@
# limitations under the License.
#
import os
import os.path
import logging
from logging.handlers import RotatingFileHandler
from api.utils.file_utils import get_project_base_directory
def get_project_base_directory():
PROJECT_BASE = os.path.abspath(
os.path.join(
os.path.dirname(os.path.realpath(__file__)),
os.pardir,
os.pardir,
)
)
return PROJECT_BASE
LOG_LEVEL = logging.INFO
LOG_FILE = os.path.abspath(os.path.join(get_project_base_directory(), "logs", f"ragflow_{os.getpid()}.log"))
LOG_FORMAT = "%(asctime)-15s %(levelname)-8s %(process)d %(message)s"
logger = None
def initRootLogger(script_path: str, log_level: int = logging.INFO, log_format: str = "%(asctime)-15s %(levelname)-8s %(process)d %(message)s"):
logger = logging.getLogger()
if logger.hasHandlers():
return
def getLogger():
global logger
if logger is not None:
return logger
script_name = os.path.basename(script_path)
log_path = os.path.abspath(os.path.join(get_project_base_directory(), "logs", f"{os.path.splitext(script_name)[0]}.log"))
print(f"log file path: {LOG_FILE}")
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
logger = logging.getLogger("ragflow")
logger.setLevel(LOG_LEVEL)
os.makedirs(os.path.dirname(log_path), exist_ok=True)
logger.setLevel(log_level)
formatter = logging.Formatter(log_format)
handler1 = RotatingFileHandler(LOG_FILE, maxBytes=10*1024*1024, backupCount=5)
handler1.setLevel(LOG_LEVEL)
formatter1 = logging.Formatter(LOG_FORMAT)
handler1.setFormatter(formatter1)
handler1 = RotatingFileHandler(log_path, maxBytes=10*1024*1024, backupCount=5)
handler1.setLevel(log_level)
handler1.setFormatter(formatter)
logger.addHandler(handler1)
handler2 = logging.StreamHandler()
handler2.setLevel(LOG_LEVEL)
formatter2 = logging.Formatter(LOG_FORMAT)
handler2.setFormatter(formatter2)
handler2.setLevel(log_level)
handler2.setFormatter(formatter)
logger.addHandler(handler2)
return logger
logger = getLogger()
msg = f"{script_name} log path: {log_path}"
logger.info(msg)

View File

@ -14,20 +14,20 @@
# limitations under the License.
#
import logging
import sys
from api.utils.log_utils import logger
def python_version_validation():
# Check python version
required_python_version = (3, 10)
if sys.version_info < required_python_version:
logger.info(
logging.info(
f"Required Python: >= {required_python_version[0]}.{required_python_version[1]}. Current Python version: {sys.version_info[0]}.{sys.version_info[1]}."
)
sys.exit(1)
else:
logger.info(f"Python version: {sys.version_info[0]}.{sys.version_info[1]}")
logging.info(f"Python version: {sys.version_info[0]}.{sys.version_info[1]}")
python_version_validation()