diff --git a/docs/samples/explanation/aif/germancredit/bias.yaml b/docs/samples/explanation/aif/germancredit/bias.yaml index 11938ac2170..a9ccb951335 100644 --- a/docs/samples/explanation/aif/germancredit/bias.yaml +++ b/docs/samples/explanation/aif/germancredit/bias.yaml @@ -9,7 +9,7 @@ spec: containerConcurrency: 0 containers: - image: aipipeline/aifserver:predictor - name: kfserving-container + name: kserve-container resources: limits: cpu: "1" @@ -61,4 +61,4 @@ spec: memory: 2Gi requests: cpu: "2" - memory: 2Gi \ No newline at end of file + memory: 2Gi diff --git a/docs/samples/explanation/aif/germancredit/server/model.py b/docs/samples/explanation/aif/germancredit/server/model.py index a1743195aed..ef34dc0189c 100644 --- a/docs/samples/explanation/aif/germancredit/server/model.py +++ b/docs/samples/explanation/aif/germancredit/server/model.py @@ -1,3 +1,5 @@ +import argparse + import kserve from typing import Dict, Union @@ -7,7 +9,7 @@ load_preproc_data_german, ) -from kserve import InferRequest, InferResponse +from kserve import InferRequest, InferResponse, logging from kserve.protocol.grpc.grpc_predict_v2_pb2 import ( ModelInferRequest, ModelInferResponse, @@ -47,7 +49,12 @@ def predict( return {"predictions": predictions.tolist()} +parser = argparse.ArgumentParser(parents=[kserve.model_server.parser]) +args, _ = parser.parse_known_args() + if __name__ == "__main__": - model = KServeSampleModel("german-credit") + if args.configure_logging: + logging.configure_logging(args.log_config_file) + model = KServeSampleModel(args.model_name) model.load() kserve.ModelServer(workers=1).start([model]) diff --git a/docs/samples/explanation/aix/mnist/rfserver/rfserver/__main__.py b/docs/samples/explanation/aix/mnist/rfserver/rfserver/__main__.py index 6a901c6fc38..c6201ff2511 100644 --- a/docs/samples/explanation/aix/mnist/rfserver/rfserver/__main__.py +++ b/docs/samples/explanation/aix/mnist/rfserver/rfserver/__main__.py @@ -14,6 +14,7 @@ import kserve import argparse +from kserve import logging from .model import RFModel DEFAULT_MODEL_NAME = "rfserver" @@ -27,6 +28,8 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = RFModel(args.model_name) model.load() kserve.ModelServer().start([model]) diff --git a/docs/samples/explanation/aix/mnist/rfserver/rfserver/model.py b/docs/samples/explanation/aix/mnist/rfserver/rfserver/model.py index cc97862cc88..0b4040da168 100644 --- a/docs/samples/explanation/aix/mnist/rfserver/rfserver/model.py +++ b/docs/samples/explanation/aix/mnist/rfserver/rfserver/model.py @@ -11,7 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging from typing import Dict, Union import pickle @@ -23,6 +22,7 @@ ModelInferRequest, ModelInferResponse, ) +from kserve.logging import logger class PipeStep(object): @@ -64,7 +64,7 @@ def predict( try: inputs = np.asarray(instances) - logging.info("Calling predict on image of shape %s", (inputs.shape,)) + logger.info("Calling predict on image of shape %s", (inputs.shape,)) except Exception as e: raise Exception( "Failed to initialize NumPy array from inputs: %s, %s" % (e, instances) diff --git a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/__main__.py b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/__main__.py index 195e71cf2b8..c0de7ef1339 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/__main__.py +++ b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/__main__.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -import logging +import argparse import os import sys @@ -22,12 +21,15 @@ from alibiexplainer.parser import parse_args import kserve +from kserve import logging from kserve.storage import Storage - -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger EXPLAINER_FILENAME = "explainer.dill" +parser = argparse.ArgumentParser(parents=[kserve.model_server.parser]) +args, _ = parser.parse_known_args() + def main(): args, extra = parse_args(sys.argv[1:]) @@ -39,7 +41,7 @@ def main(): Storage.download(args.storage_uri), EXPLAINER_FILENAME ) with open(alibi_model, "rb") as f: - logging.info("Loading Alibi model") + logger.info("Loading Alibi model") alibi_model = dill.load(f) explainer = AlibiExplainer( @@ -54,4 +56,6 @@ def main(): if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) main() diff --git a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_images.py b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_images.py index 9db0a123a0f..7474ea89e87 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_images.py +++ b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_images.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import kserve -import logging + import numpy as np import alibi from alibi.api.interfaces import Explanation @@ -20,7 +19,7 @@ from alibiexplainer.explainer_wrapper import ExplainerWrapper from typing import Callable, List, Optional, Dict -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger class AnchorImages(ExplainerWrapper): @@ -44,7 +43,7 @@ def explain(self, inputs: List, headers: Dict[str, str] = None) -> Explanation: self.anchors_image.predictor = self.predict_fn else: self.anchors_image.predictor = ArgmaxTransformer(self.predict_fn) - logging.info("Calling explain on image of shape %s", (arr.shape,)) - logging.info("anchor image call with %s", self.kwargs) + logger.info("Calling explain on image of shape %s", (arr.shape,)) + logger.info("anchor image call with %s", self.kwargs) anchor_exp = self.anchors_image.explain(arr[0], **self.kwargs) return anchor_exp diff --git a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_tabular.py b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_tabular.py index be67065c229..3b0711b9c3a 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_tabular.py +++ b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_tabular.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import kserve -import logging + import numpy as np import alibi from alibi.api.interfaces import Explanation @@ -20,7 +19,7 @@ from alibiexplainer.explainer_wrapper import ExplainerWrapper from typing import Callable, List, Optional, Dict -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger class AnchorTabular(ExplainerWrapper): @@ -41,7 +40,7 @@ def explain(self, inputs: List, headers: Dict[str, str] = None) -> Explanation: arr = np.array(inputs) # set anchor_tabular predict function so it always returns predicted class # See anchor_tabular.__init__ - logging.info("Arr shape %s ", (arr.shape,)) + logger.info("Arr shape %s ", (arr.shape,)) # check if predictor returns predicted class or prediction probabilities for each class # if needed adjust predictor so it returns the predicted class diff --git a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_text.py b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_text.py index 3933582a3f5..3d614c1e37a 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_text.py +++ b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/anchor_text.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import kserve -import logging + import numpy as np import spacy import alibi @@ -22,7 +21,7 @@ from alibiexplainer.explainer_wrapper import ExplainerWrapper from typing import Callable, List, Optional, Dict -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger class AnchorText(ExplainerWrapper): @@ -35,12 +34,12 @@ def __init__( ): self.predict_fn = predict_fn self.kwargs = kwargs - logging.info("Anchor Text args %s", self.kwargs) + logger.info("Anchor Text args %s", self.kwargs) if explainer is None: - logging.info("Loading Spacy Language model for %s", spacy_language_model) + logger.info("Loading Spacy Language model for %s", spacy_language_model) spacy_model(model=spacy_language_model) self.nlp = spacy.load(spacy_language_model) - logging.info("Language model loaded") + logger.info("Language model loaded") self.anchors_text = explainer def explain(self, inputs: List, headers: Dict[str, str] = None) -> Explanation: diff --git a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/explainer.py b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/explainer.py index b6c284b403e..af37b5c4e7f 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/explainer.py +++ b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/explainer.py @@ -11,25 +11,25 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import json -import logging import asyncio from enum import Enum from typing import List, Any, Mapping, Union, Dict -import kserve import numpy as np +import nest_asyncio + from alibiexplainer.anchor_images import AnchorImages from alibiexplainer.anchor_tabular import AnchorTabular from alibiexplainer.anchor_text import AnchorText from alibiexplainer.explainer_wrapper import ExplainerWrapper -import nest_asyncio +import kserve +from kserve.logging import logger nest_asyncio.apply() -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) - class ExplainerMethod(Enum): anchor_tabular = "AnchorTabular" @@ -51,7 +51,7 @@ def __init__( # pylint:disable=too-many-arguments ): super().__init__(name) self.predictor_host = predictor_host - logging.info("Predict URL set to %s", self.predictor_host) + logger.info("Predict URL set to %s", self.predictor_host) self.method = method if self.method is ExplainerMethod.anchor_tabular: @@ -84,7 +84,7 @@ def explain(self, payload: Dict, headers: Dict[str, str] = None) -> Any: ): explanation = self.wrapper.explain(payload["instances"]) explanationAsJsonStr = explanation.to_json() - logging.info("Explanation: %s", explanationAsJsonStr) + logger.info("Explanation: %s", explanationAsJsonStr) return json.loads(explanationAsJsonStr) raise NotImplementedError diff --git a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/parser.py b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/parser.py index 2a095a8ad5e..69f3eff4b6c 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/parser.py +++ b/docs/samples/explanation/alibi/alibiexplainer/alibiexplainer/parser.py @@ -14,17 +14,16 @@ import argparse import kserve -import logging import os -from alibiexplainer.explainer import ExplainerMethod # pylint:disable=no-name-in-module +from alibiexplainer.explainer import ExplainerMethod -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger DEFAULT_EXPLAINER_NAME = "explainer" ENV_STORAGE_URI = "STORAGE_URI" -class GroupedAction(argparse.Action): # pylint:disable=too-few-public-methods +class GroupedAction(argparse.Action): def __call__(self, theparser, namespace, values, option_string=None): group, dest = self.dest.split(".", 2) groupspace = getattr(namespace, group, argparse.Namespace()) @@ -213,5 +212,5 @@ def parse_args(sys_args): extra = vars(args.explainer) else: extra = {} - logging.info("Extra args: %s", extra) + logger.info("Extra args: %s", extra) return args, extra diff --git a/docs/samples/explanation/alibi/alibiexplainer/poetry.lock b/docs/samples/explanation/alibi/alibiexplainer/poetry.lock index 685a413ffcc..ef5d1f201dc 100644 --- a/docs/samples/explanation/alibi/alibiexplainer/poetry.lock +++ b/docs/samples/explanation/alibi/alibiexplainer/poetry.lock @@ -2131,7 +2131,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -2150,18 +2149,18 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = ">5.4.1" ray = {version = "^2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] openai = ["openai (>=1.13.3,<2.0.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/docs/samples/kafka/image_transformer/__main__.py b/docs/samples/kafka/image_transformer/__main__.py index 461159e3c3e..f6f1ba2aefa 100644 --- a/docs/samples/kafka/image_transformer/__main__.py +++ b/docs/samples/kafka/image_transformer/__main__.py @@ -13,6 +13,8 @@ import kserve import argparse + +from kserve import logging from .image_transformer import ImageTransformer DEFAULT_MODEL_NAME = "model" @@ -30,6 +32,8 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) transformer = ImageTransformer(args.model_name, predictor_host=args.predictor_host) server = kserve.ModelServer() server.start(models=[transformer]) diff --git a/docs/samples/kafka/image_transformer/image_transformer.py b/docs/samples/kafka/image_transformer/image_transformer.py index c7ccc883b21..642a616ce67 100644 --- a/docs/samples/kafka/image_transformer/image_transformer.py +++ b/docs/samples/kafka/image_transformer/image_transformer.py @@ -11,7 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging from typing import Dict, Union import boto3 @@ -20,8 +19,7 @@ import kserve from kserve import InferRequest, InferResponse from kserve.protocol.grpc.grpc_predict_v2_pb2 import ModelInferResponse - -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger session = boto3.Session() client = session.client( @@ -49,7 +47,7 @@ def __init__(self, name: str, predictor_host: str): async def preprocess( self, inputs: Union[Dict, InferRequest], headers: Dict[str, str] = None ) -> Union[Dict, InferRequest]: - logging.info("Received inputs %s", inputs) + logger.info("Received inputs %s", inputs) if inputs["EventName"] == "s3:ObjectCreated:Put": bucket = inputs["Records"][0]["s3"]["bucket"]["name"] key = inputs["Records"][0]["s3"]["object"]["key"] @@ -64,10 +62,10 @@ async def postprocess( response: Union[Dict, InferResponse, ModelInferResponse], headers: Dict[str, str] = None, ) -> Union[Dict, ModelInferResponse]: - logging.info("response: %s", response) + logger.info("response: %s", response) index = response["predictions"][0]["classes"] - logging.info("digit:" + str(index)) + logger.info("digit:" + str(index)) upload_path = f"digit-{index}/{self._key}" client.upload_file("/tmp/" + self._key, digits_bucket, upload_path) - logging.info(f"Image {self._key} successfully uploaded to {upload_path}") + logger.info(f"Image {self._key} successfully uploaded to {upload_path}") return response diff --git a/docs/samples/v1beta1/torchserve/v2/bert/sequence_classification/Transformer_kserve_handler.py b/docs/samples/v1beta1/torchserve/v2/bert/sequence_classification/Transformer_kserve_handler.py index 404a476bc9a..c6dc340d2b5 100644 --- a/docs/samples/v1beta1/torchserve/v2/bert/sequence_classification/Transformer_kserve_handler.py +++ b/docs/samples/v1beta1/torchserve/v2/bert/sequence_classification/Transformer_kserve_handler.py @@ -1,5 +1,4 @@ import torch -import logging from Transformer_handler_generalized import ( TransformersSeqClassifierHandler, captum_sequence_forward, @@ -9,8 +8,8 @@ ) import json from captum.attr import LayerIntegratedGradients +from kserve.logging import logger -logger = logging.getLogger(__name__) # TODO Extend the example for token classification, question answering and batch inputs diff --git a/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/__main__.py b/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/__main__.py index 055837afe9a..9064d5bd0b7 100644 --- a/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/__main__.py +++ b/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/__main__.py @@ -13,6 +13,7 @@ import argparse import kserve +from kserve import logging from .driver_transformer import DriverTransformer @@ -55,6 +56,8 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) transformer = DriverTransformer( name=args.model_name, predictor_host=args.predictor_host, diff --git a/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/driver_transformer.py b/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/driver_transformer.py index 282caed0981..0187a240d4c 100644 --- a/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/driver_transformer.py +++ b/docs/samples/v1beta1/transformer/feast/driver_transformer/driver_transformer/driver_transformer.py @@ -10,9 +10,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import json from typing import List, Dict, Union -import logging import requests import numpy as np @@ -21,8 +21,7 @@ from kserve import InferRequest, InferResponse, InferInput from kserve.protocol.grpc import grpc_predict_v2_pb2 as pb from kserve.protocol.grpc.grpc_predict_v2_pb2 import ModelInferResponse - -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) +from kserve.logging import logger class DriverTransformer(kserve.Model): @@ -66,12 +65,12 @@ def __init__( self.feature_refs_key = [ feature_refs[i].replace(":", "__") for i in range(len(feature_refs)) ] - logging.info("Model name = %s", name) - logging.info("Protocol = %s", protocol) - logging.info("Predictor host = %s", predictor_host) - logging.info("Feast serving URL = %s", feast_serving_url) - logging.info("Entity id name = %s", entity_id_name) - logging.info("Feature refs = %s", feature_refs) + logger.info("Model name = %s", name) + logger.info("Protocol = %s", protocol) + logger.info("Predictor host = %s", predictor_host) + logger.info("Feast serving URL = %s", feast_serving_url) + logger.info("Entity id name = %s", entity_id_name) + logger.info("Feature refs = %s", feature_refs) self.timeout = 100 @@ -171,18 +170,18 @@ def preprocess( else "http://{0}/get-online-features".format(self.feast_serving_url) ) json_params = json.dumps(params) - logging.info("feast request url %s", request_url) - logging.info("feast request headers %s", headers) - logging.info("feast request body %s", json_params) + logger.info("feast request url %s", request_url) + logger.info("feast request headers %s", headers) + logger.info("feast request body %s", json_params) resp = requests.post(request_url, data=json_params, headers=headers) - logging.info("feast response status is %s", resp.status_code) - logging.info("feast response headers %s", resp.headers) + logger.info("feast response status is %s", resp.status_code) + logger.info("feast response headers %s", resp.headers) features = resp.json() - logging.info("feast response body %s", features) + logger.info("feast response body %s", features) outputs = self.buildPredictRequest(inputs, features) - logging.info("The input for model predict is %s", outputs) + logger.info("The input for model predict is %s", outputs) return outputs @@ -202,5 +201,5 @@ def postprocess( Dict: If a post process functionality is specified, it could convert raw rankings into a different list. """ - logging.info("The output from model predict is %s", response) + logger.info("The output from model predict is %s", response) return response diff --git a/docs/samples/v1beta1/triton/fastertransformer/transformer/transformer.py b/docs/samples/v1beta1/triton/fastertransformer/transformer/transformer.py index 9b9d0317898..5c8e6979732 100644 --- a/docs/samples/v1beta1/triton/fastertransformer/transformer/transformer.py +++ b/docs/samples/v1beta1/triton/fastertransformer/transformer/transformer.py @@ -1,10 +1,10 @@ import argparse -import logging import json from uuid import uuid4 from typing import Dict, List, Union import kserve +from kserve import logging from kserve.protocol.infer_type import ( InferInput, InferOutput, @@ -12,6 +12,7 @@ InferResponse, ) from kserve.protocol.grpc.grpc_predict_v2_pb2 import ModelInferResponse +from kserve.logging import logger import numpy as np from transformers import AutoTokenizer from pydantic import BaseModel @@ -20,10 +21,6 @@ mp.set_start_method("fork") -logging.basicConfig(level=kserve.constants.KSERVE_LOGLEVEL) -logger = logging.getLogger(__name__) - - def get_output(outputs: List[InferOutput], name: str) -> InferOutput: for o in outputs: if o.name == name: @@ -130,6 +127,8 @@ def _tokenize_input(self, request: Request): "--tokenizer_path", help="The path to the tokenizer", required=True ) args, _ = parser.parse_known_args() + if args.configure_logging: + logging.configure_logging(args.log_config_file) transformer = Transformer( name=args.model_name, diff --git a/python/aiffairness/aifserver/__main__.py b/python/aiffairness/aifserver/__main__.py index 588f98595fa..c64a166d6fb 100644 --- a/python/aiffairness/aifserver/__main__.py +++ b/python/aiffairness/aifserver/__main__.py @@ -17,6 +17,7 @@ import kserve import json +from kserve import logging from .model import AIFModel DEFAULT_MODEL_NAME = "aifserver" @@ -65,6 +66,8 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = AIFModel( name=args.model_name, predictor_host=args.predictor_host, diff --git a/python/aiffairness/poetry.lock b/python/aiffairness/poetry.lock index 01b3461b4b2..b9a794487f5 100644 --- a/python/aiffairness/poetry.lock +++ b/python/aiffairness/poetry.lock @@ -982,7 +982,7 @@ referencing = ">=0.31.0" [[package]] name = "kserve" -version = "0.12.1" +version = "0.13.0rc0" description = "KServe Python SDK" optional = false python-versions = ">=3.8,<3.12" @@ -990,7 +990,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -1004,6 +1003,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -1012,7 +1012,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/artexplainer/artserver/__main__.py b/python/artexplainer/artserver/__main__.py index fdeced67e3a..276a320a500 100644 --- a/python/artexplainer/artserver/__main__.py +++ b/python/artexplainer/artserver/__main__.py @@ -17,6 +17,7 @@ from artserver import ARTModel import kserve +from kserve import logging DEFAULT_ADVERSARY_TYPE = "SquareAttack" @@ -41,6 +42,8 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = ARTModel( args.model_name, args.predictor_host, diff --git a/python/artexplainer/artserver/model.py b/python/artexplainer/artserver/model.py index 98058fd2519..cf8ddeff928 100644 --- a/python/artexplainer/artserver/model.py +++ b/python/artexplainer/artserver/model.py @@ -11,18 +11,20 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import asyncio -import logging from typing import Dict import numpy as np from art.attacks.evasion.square_attack import SquareAttack from art.estimators.classification import BlackBoxClassifierNeuralNetwork -import kserve import nest_asyncio +import kserve +from kserve.logging import logger + nest_asyncio.apply() @@ -66,7 +68,7 @@ def explain(self, payload: Dict, headers: Dict[str, str] = None) -> Dict: try: inputs = np.array(image) label = np.array(label) - logging.info("Calling explain on image of shape %s", (inputs.shape,)) + logger.info("Calling explain on image of shape %s", (inputs.shape,)) except Exception as e: raise Exception( "Failed to initialize NumPy array from inputs: %s, %s" diff --git a/python/artexplainer/poetry.lock b/python/artexplainer/poetry.lock index eca148294c8..0e20829e027 100644 --- a/python/artexplainer/poetry.lock +++ b/python/artexplainer/poetry.lock @@ -1056,7 +1056,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -1070,6 +1069,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -1078,7 +1078,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/custom_model/model.py b/python/custom_model/model.py index 2e93f358be9..82513dfa8d2 100644 --- a/python/custom_model/model.py +++ b/python/custom_model/model.py @@ -29,6 +29,7 @@ InferRequest, InferOutput, InferResponse, + logging, ) from kserve.errors import InvalidInput from kserve.utils.utils import generate_uuid @@ -119,6 +120,8 @@ def predict( args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = AlexNetModel(args.model_name) model.load() ModelServer().start([model]) diff --git a/python/custom_model/model_grpc.py b/python/custom_model/model_grpc.py index a20a44f1a77..564dd7288d9 100644 --- a/python/custom_model/model_grpc.py +++ b/python/custom_model/model_grpc.py @@ -11,12 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import argparse import io from typing import Dict import torch -from kserve import InferRequest, Model, ModelServer +from kserve import InferRequest, Model, ModelServer, logging, model_server from kserve.utils.utils import generate_uuid from PIL import Image from torchvision import models, transforms @@ -86,7 +86,12 @@ def predict( return response +parser = argparse.ArgumentParser(parents=[model_server.parser]) +args, _ = parser.parse_known_args() + if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = AlexNetModel("custom-model") model.load() ModelServer().start([model]) diff --git a/python/custom_model/model_remote.py b/python/custom_model/model_remote.py index 0452af029ee..9e644212e7a 100644 --- a/python/custom_model/model_remote.py +++ b/python/custom_model/model_remote.py @@ -11,16 +11,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -from kserve import Model, ModelServer -from kserve.ray import RayModel -from torchvision import models, transforms +import argparse +import base64 +import io from typing import Dict + import torch from PIL import Image -import base64 -import io from ray import serve +from torchvision import models, transforms + +from kserve import Model, ModelServer, logging, model_server +from kserve.ray import RayModel # the model handle name should match the model endpoint name @@ -71,7 +73,12 @@ async def predict(self, payload: Dict, headers: Dict[str, str] = None) -> Dict: return {"predictions": values.tolist()} +parser = argparse.ArgumentParser(parents=[model_server.parser]) +args, _ = parser.parse_known_args() + if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) app = AlexNetModel.bind() handle = serve.run(app) model = RayModel(name="custom-model", handle=handle) diff --git a/python/custom_model/poetry.lock b/python/custom_model/poetry.lock index 48e96be8cd4..c8658fabcae 100644 --- a/python/custom_model/poetry.lock +++ b/python/custom_model/poetry.lock @@ -877,7 +877,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -891,6 +890,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -900,7 +900,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] ray = ["ray[serve] (>=2.10.0,<2.11.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/custom_tokenizer/poetry.lock b/python/custom_tokenizer/poetry.lock index cc7df548b79..a40ed19ed04 100644 --- a/python/custom_tokenizer/poetry.lock +++ b/python/custom_tokenizer/poetry.lock @@ -871,7 +871,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -885,6 +884,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -893,7 +893,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/custom_tokenizer/transformer.py b/python/custom_tokenizer/transformer.py index d2b1eb2df42..73f4b19abf5 100644 --- a/python/custom_tokenizer/transformer.py +++ b/python/custom_tokenizer/transformer.py @@ -11,21 +11,25 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import argparse -import kserve +import argparse from typing import Dict, Union -import numpy as np -from kserve import InferRequest, InferResponse, InferInput, model_server, ModelServer +import numpy as np import tokenization import data_processing -import logging +import kserve +from kserve import ( + InferRequest, + InferResponse, + InferInput, + model_server, + ModelServer, + logging, +) from kserve.model import PredictorConfig -logging.basicConfig(level=logging.DEBUG) - class Tokenizer(kserve.Model): def __init__( @@ -94,7 +98,6 @@ def preprocess( def postprocess( self, infer_response: Union[Dict, InferResponse], headers: Dict[str, str] = None ) -> Union[Dict, InferResponse]: - logging.info(infer_response.__dict__) end_logits = infer_response.outputs[0].data start_logits = infer_response.outputs[1].data @@ -121,6 +124,8 @@ def postprocess( args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = Tokenizer( args.model_name, predictor_host=args.predictor_host, diff --git a/python/custom_transformer/model.py b/python/custom_transformer/model.py index ad2420cde3d..d8651e38080 100644 --- a/python/custom_transformer/model.py +++ b/python/custom_transformer/model.py @@ -28,6 +28,7 @@ InferInput, InferRequest, InferResponse, + logging, ) from kserve.model import PredictorProtocol, PredictorConfig @@ -124,6 +125,8 @@ def postprocess( args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = ImageTransformer( args.model_name, predictor_host=args.predictor_host, diff --git a/python/custom_transformer/model_grpc.py b/python/custom_transformer/model_grpc.py index 48fc8b496b0..967562c94b9 100644 --- a/python/custom_transformer/model_grpc.py +++ b/python/custom_transformer/model_grpc.py @@ -18,7 +18,7 @@ import io from PIL import Image from torchvision import transforms -from kserve import Model, ModelServer, model_server, InferInput, InferRequest +from kserve import Model, ModelServer, model_server, InferInput, InferRequest, logging def image_transform(data): @@ -74,6 +74,8 @@ def preprocess( args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = ImageTransformer( args.model_name, predictor_host=args.predictor_host, diff --git a/python/custom_transformer/poetry.lock b/python/custom_transformer/poetry.lock index 1eb6ec8aeb8..d13fcf37238 100644 --- a/python/custom_transformer/poetry.lock +++ b/python/custom_transformer/poetry.lock @@ -888,7 +888,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -902,6 +901,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -910,7 +910,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/huggingfaceserver/huggingfaceserver/__main__.py b/python/huggingfaceserver/huggingfaceserver/__main__.py index d1409ff9f58..7b9802606e9 100644 --- a/python/huggingfaceserver/huggingfaceserver/__main__.py +++ b/python/huggingfaceserver/huggingfaceserver/__main__.py @@ -18,6 +18,7 @@ import torch import kserve +from kserve import logging from kserve.logging import logger from kserve.model import PredictorConfig from kserve.storage import Storage @@ -237,6 +238,8 @@ def load_model(): if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) try: model = load_model() kserve.ModelServer().start([model] if model.ready else []) diff --git a/python/huggingfaceserver/poetry.lock b/python/huggingfaceserver/poetry.lock index 45605db3ee2..b80e745dc22 100644 --- a/python/huggingfaceserver/poetry.lock +++ b/python/huggingfaceserver/poetry.lock @@ -1543,7 +1543,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -1562,17 +1561,17 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/kserve/kserve/api/creds_utils.py b/python/kserve/kserve/api/creds_utils.py index e4b26d7537c..fa2b5adb5e5 100644 --- a/python/kserve/kserve/api/creds_utils.py +++ b/python/kserve/kserve/api/creds_utils.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging + import json import configparser from os.path import expanduser @@ -20,7 +20,7 @@ from kubernetes import client from ..constants import constants -logger = logging.getLogger(__name__) +from kserve.logging import logger def set_gcs_credentials(namespace, credentials_file, service_account): @@ -253,7 +253,7 @@ def get_creds_name_from_config_map(creds): constants.INFERENCESERVICE_SYSTEM_NAMESPACE, ) except client.rest.ApiException: - logging.warning( + logger.warning( "Cannot get configmap %s in namespace %s.", constants.INFERENCESERVICE_CONFIG_MAP_NAME, constants.INFERENCESERVICE_SYSTEM_NAMESPACE, diff --git a/python/kserve/kserve/errors.py b/python/kserve/kserve/errors.py index d000aa1311b..b62a07e1448 100644 --- a/python/kserve/kserve/errors.py +++ b/python/kserve/kserve/errors.py @@ -13,7 +13,7 @@ # limitations under the License. from http import HTTPStatus -from kserve.logging import logger +from .logging import logger from fastapi.responses import JSONResponse diff --git a/python/kserve/kserve/inference_client.py b/python/kserve/kserve/inference_client.py index eb1ff3bde11..452d20e5a49 100644 --- a/python/kserve/kserve/inference_client.py +++ b/python/kserve/kserve/inference_client.py @@ -12,14 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging import grpc -from .constants.constants import KSERVE_LOGLEVEL from .protocol.infer_type import InferRequest from .protocol.grpc.grpc_predict_v2_pb2_grpc import GRPCInferenceServiceStub - -logging.basicConfig(level=KSERVE_LOGLEVEL) +from .logging import logger class InferenceServerClient: @@ -90,14 +87,14 @@ def infer(self, infer_request: InferRequest, client_timeout=None, headers=None): request = infer_request.to_grpc() if self._verbose: - logging.info("infer, metadata {}\n{}".format(metadata, request)) + logger.info("infer, metadata {}\n{}".format(metadata, request)) try: response = self._client_stub.ModelInfer( request=request, metadata=metadata, timeout=client_timeout ) if self._verbose: - logging.info(response) + logger.info(response) return response except grpc.RpcError as rpc_error: raise rpc_error diff --git a/python/kserve/kserve/logging.py b/python/kserve/kserve/logging.py index 199315e0bdf..0028d777f0a 100644 --- a/python/kserve/kserve/logging.py +++ b/python/kserve/kserve/logging.py @@ -12,8 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging +import json import logging.config +from typing import Optional, Union, Dict + +import yaml from .constants.constants import KSERVE_LOGLEVEL @@ -21,12 +24,11 @@ KSERVE_TRACE_LOGGER_NAME = "kserve.trace" KSERVE_LOGGER_FORMAT = ( "%(asctime)s.%(msecs)03d %(process)s %(name)s " - "%(levelname)s [%(funcName)s():%(lineno)s] %(message)s" + "%(levelname)s [%(filename)s:%(funcName)s():%(lineno)s] %(message)s" ) KSERVE_TRACE_LOGGER_FORMAT = "%(asctime)s.%(msecs)03d %(name)s %(message)s" KSERVE_LOGGER_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - KSERVE_LOG_CONFIG = { "version": 1, "disable_existing_loggers": False, @@ -101,3 +103,39 @@ logger = logging.getLogger(KSERVE_LOGGER_NAME) trace_logger = logging.getLogger(KSERVE_TRACE_LOGGER_NAME) + + +def configure_logging(log_config: Optional[Union[Dict, str]] = None): + """ + Configures Kserve and Uvicorn loggers. + This function should be called before loading the model / starting the model + server for consistent logging format. + + :param log_config: (Optional) File path or dict containing log config. If not provided default configuration + will be used. If explicitly set to None, the logger will not be configured. + - If a dictionary is provided, it will be used directly for configuring the logger. + - If a string is provided: + - If it ends with '.json', it will be treated as a path to a JSON file containing log + configuration. + - If it ends with '.yaml' or '.yml', it will be treated as a path to a YAML file containing + log configuration. + - Otherwise, it will be treated as a path to a configuration file in the format specified in + the Python logging module documentation. # See the note about fileConfig() here: + # https://docs.python.org/3/library/logging.config.html#configuration-file-format + """ + if log_config is None: + logging.config.dictConfig(KSERVE_LOG_CONFIG) + elif isinstance(log_config, dict): + logging.config.dictConfig(log_config) + elif log_config.endswith(".json"): + with open(log_config) as file: + loaded_config = json.load(file) + logging.config.dictConfig(loaded_config) + elif log_config.endswith((".yaml", ".yml")): + with open(log_config) as file: + loaded_config = yaml.safe_load(file) + logging.config.dictConfig(loaded_config) + else: + # See the note about fileConfig() here: + # https://docs.python.org/3/library/logging.config.html#configuration-file-format + logging.config.fileConfig(log_config, disable_existing_loggers=False) diff --git a/python/kserve/kserve/model_server.py b/python/kserve/kserve/model_server.py index 4f402b0eb88..d3bda7635a3 100644 --- a/python/kserve/kserve/model_server.py +++ b/python/kserve/kserve/model_server.py @@ -22,7 +22,8 @@ from multiprocessing import Process from typing import Any, Callable, Dict, List, Optional, Union -from .logging import KSERVE_LOG_CONFIG, logger +from . import logging +from .logging import logger from .model import BaseKServeModel from .model_repository import ModelRepository from .protocol.dataplane import DataPlane @@ -102,7 +103,8 @@ "--access_log_format", default=None, type=str, - help="The asgi access logging format.", + help="The asgi access logging format. It allows to override only the `uvicorn.access`'s format configuration " + "with a richer set of fields", ) # Model arguments: The arguments are passed to the kserve.Model object @@ -161,8 +163,6 @@ def __init__( enable_grpc: bool = args.enable_grpc, enable_docs_url: bool = args.enable_docs_url, enable_latency_logging: bool = args.enable_latency_logging, - configure_logging: bool = args.configure_logging, - log_config: Optional[Union[Dict, str]] = args.log_config_file, access_log_format: str = args.access_log_format, ): """KServe ModelServer Constructor @@ -177,9 +177,12 @@ def __init__( enable_grpc: Whether to turn on grpc server. Default: ``True`` enable_docs_url: Whether to turn on ``/docs`` Swagger UI. Default: ``False``. enable_latency_logging: Whether to log latency metric. Default: ``True``. - configure_logging: Whether to configure KServe and Uvicorn logging. Default: ``True``. - log_config: File path or dict containing log config. Default: ``None``. - access_log_format: Format to set for the access log (provided by asgi-logger). Default: ``None`` + access_log_format: Format to set for the access log (provided by asgi-logger). Default: ``None``. + it allows to override only the `uvicorn.access`'s format configuration with a richer + set of fields (output hardcoded to `stdout`). This limitation is currently due to the + ASGI specs that don't describe how access logging should be implemented in detail + (please refer to this Uvicorn + [github issue](https://github.com/encode/uvicorn/issues/527) for more info). """ self.registered_models = registered_models self.http_port = http_port @@ -200,17 +203,11 @@ def __init__( self._grpc_server = GRPCServer( grpc_port, self.dataplane, self.model_repository_extension ) - - # Logs can be passed as a path to a file or a dictConfig. - # We rely on Uvicorn to configure the loggers for us. - if configure_logging: - self.log_config = ( - log_config if log_config is not None else KSERVE_LOG_CONFIG - ) - else: - # By setting log_config to None we tell Uvicorn not to configure logging - self.log_config = None - + if args.configure_logging: + # If the logger does not have any handlers, then the logger is not configured. + # For backward compatibility, we configure the logger here. + if len(logger.handlers) == 0: + logging.configure_logging(args.log_config_file) self.access_log_format = access_log_format self._custom_exception_handler = None @@ -263,7 +260,9 @@ async def serve(): self.dataplane, self.model_repository_extension, self.enable_docs_url, - log_config=self.log_config, + # By setting log_config to None we tell Uvicorn not to configure logging as it is already + # configured by kserve. + log_config=None, access_log_format=self.access_log_format, ) await self._rest_server.run() @@ -283,7 +282,9 @@ async def serve(): self.dataplane, self.model_repository_extension, self.enable_docs_url, - log_config=self.log_config, + # By setting log_config to None we tell Uvicorn not to configure logging as it is already + # configured by kserve. + log_config=None, access_log_format=self.access_log_format, ) for _ in range(self.workers): diff --git a/python/kserve/kserve/protocol/dataplane.py b/python/kserve/kserve/protocol/dataplane.py index 61e5f08e791..8c0c013d296 100644 --- a/python/kserve/kserve/protocol/dataplane.py +++ b/python/kserve/kserve/protocol/dataplane.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging import time from importlib import metadata from typing import Dict, Optional, Tuple, Union @@ -26,7 +25,8 @@ from ..constants import constants from ..errors import InvalidInput, ModelNotFound -from ..model import InferenceVerb, BaseKServeModel, InferenceModel +from ..logging import logger +from ..model import InferenceVerb, Model from ..model_repository import ModelRepository from ..utils.utils import create_response_cloudevent, is_structured_cloudevent from .infer_type import InferRequest, InferResponse @@ -251,7 +251,7 @@ def decode(self, body, headers) -> Tuple[Union[Dict, InferRequest], Dict]: decoded_body, attributes = self.decode_cloudevent(body) t2 = time.time() - logging.debug(f"decoded request in {round((t2 - t1) * 1000, 9)}ms") + logger.debug(f"decoded request in {round((t2 - t1) * 1000, 9)}ms") return decoded_body, attributes def decode_cloudevent(self, body) -> Tuple[Union[Dict, InferRequest], Dict]: diff --git a/python/kserve/kserve/protocol/grpc/interceptors.py b/python/kserve/kserve/protocol/grpc/interceptors.py index f3779321632..9dd4fa42895 100644 --- a/python/kserve/kserve/protocol/grpc/interceptors.py +++ b/python/kserve/kserve/protocol/grpc/interceptors.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging from typing import Awaitable, Callable from grpc import HandlerCallDetails, RpcMethodHandler from grpc.aio import ServerInterceptor +from ...logging import logger + class LoggingInterceptor(ServerInterceptor): @@ -26,5 +27,5 @@ async def intercept_service( continuation: Callable[[HandlerCallDetails], Awaitable[RpcMethodHandler]], handler_call_details: HandlerCallDetails, ) -> RpcMethodHandler: - logging.info(f"grpc method: {handler_call_details.method}") + logger.info(f"grpc method: {handler_call_details.method}") return await continuation(handler_call_details) diff --git a/python/kserve/kserve/storage/storage.py b/python/kserve/kserve/storage/storage.py index 481e11eb8b7..92afffaa139 100644 --- a/python/kserve/kserve/storage/storage.py +++ b/python/kserve/kserve/storage/storage.py @@ -16,13 +16,13 @@ import glob import gzip import json -import logging import mimetypes import os import re import shutil import tarfile import tempfile +import time import zipfile from pathlib import Path from typing import Dict @@ -38,6 +38,8 @@ from google.auth import exceptions from google.cloud import storage +from ..logging import logger + MODEL_MOUNT_DIRS = "/mnt/models" _GCS_PREFIX = "gs://" @@ -56,11 +58,12 @@ _HDFS_FILE_SECRETS = ["KERBEROS_KEYTAB", "TLS_CERT", "TLS_KEY", "TLS_CA"] -class Storage(object): # pylint: disable=too-few-public-methods +class Storage(object): @staticmethod def download(uri: str, out_dir: str = None) -> str: + start = time.monotonic() Storage._update_with_storage_spec() - logging.info("Copying contents of %s to local", uri) + logger.info("Copying contents of %s to local", uri) if uri.startswith(_PVC_PREFIX) and not os.path.exists(uri): raise Exception(f"Cannot locate source uri {uri} for PVC") @@ -103,7 +106,8 @@ def download(uri: str, out_dir: str = None) -> str: % (_GCS_PREFIX, _S3_PREFIX, _LOCAL_PREFIX, _HTTP_PREFIX) ) - logging.info("Successfully copied %s to %s", uri, out_dir) + logger.info("Successfully copied %s to %s", uri, out_dir) + logger.info(f"Model downloaded in {time.monotonic() - start} seconds.") return out_dir @staticmethod @@ -204,7 +208,7 @@ def _download_s3(uri, temp_dir: str): global_ca_bundle_volume_mount_path + "/cabundle.crt" ) if os.path.exists(ca_bundle_full_path): - logging.info("ca bundle file(%s) exists." % (ca_bundle_full_path)) + logger.info("ca bundle file(%s) exists." % (ca_bundle_full_path)) kwargs.update({"verify": ca_bundle_full_path}) else: raise RuntimeError( @@ -255,7 +259,7 @@ def _download_s3(uri, temp_dir: str): if not os.path.exists(os.path.dirname(target)): os.makedirs(os.path.dirname(target), exist_ok=True) bucket.download_file(obj.key, target) - logging.info("Downloaded object %s to %s" % (obj.key, target)) + logger.info("Downloaded object %s to %s" % (obj.key, target)) file_count += 1 # If the exact object is found, then it is sufficient to download that and break the loop @@ -300,7 +304,7 @@ def _download_gcs(uri, temp_dir: str): os.makedirs(local_object_dir, exist_ok=True) if subdir_object_key.strip() != "" and not subdir_object_key.endswith("/"): dest_path = os.path.join(temp_dir, subdir_object_key) - logging.info("Downloading: %s", dest_path) + logger.info("Downloading: %s", dest_path) blob.download_to_filename(dest_path) file_count += 1 if file_count == 0: @@ -354,7 +358,7 @@ def _download_hdfs(uri, out_dir: str): config = Storage._load_hdfs_configuration() - logging.info(f"Using the following hdfs config\n{config}") + logger.info(f"Using the following hdfs config\n{config}") # Remove hdfs:// or webhdfs:// from the uri to get just the path # e.g. hdfs://user/me/model -> user/me/model @@ -431,7 +435,7 @@ def _download_azure_blob(uri, out_dir: str): # pylint: disable=too-many-locals account_name, account_url, container_name, prefix = Storage._parse_azure_uri( uri ) - logging.info( + logger.info( "Connecting to BLOB account: [%s], container: [%s], prefix: [%s]", account_name, container_name, @@ -442,7 +446,7 @@ def _download_azure_blob(uri, out_dir: str): # pylint: disable=too-many-locals or Storage._get_azure_storage_access_key() ) if token is None: - logging.warning( + logger.warning( "Azure credentials or shared access signature token not found, retrying anonymous access" ) @@ -469,7 +473,7 @@ def _download_azure_blob(uri, out_dir: str): # pylint: disable=too-many-locals file_name = os.path.basename(prefix) dest_path = os.path.join(out_dir, file_name) Path(os.path.dirname(dest_path)).mkdir(parents=True, exist_ok=True) - logging.info("Downloading: %s to %s", blob.name, dest_path) + logger.info("Downloading: %s to %s", blob.name, dest_path) downloader = container_client.download_blob(blob.name) with open(dest_path, "wb+") as f: f.write(downloader.readall()) @@ -488,7 +492,7 @@ def _download_azure_file_share( uri, out_dir: str ): # pylint: disable=too-many-locals account_name, account_url, share_name, prefix = Storage._parse_azure_uri(uri) - logging.info( + logger.info( "Connecting to file share account: [%s], container: [%s], prefix: [%s]", account_name, share_name, @@ -496,7 +500,7 @@ def _download_azure_file_share( ) access_key = Storage._get_azure_storage_access_key() if access_key is None: - logging.warning( + logger.warning( "Azure storage access key not found, retrying anonymous access" ) @@ -525,7 +529,7 @@ def _download_azure_file_share( file_path = "/".join(parts).lstrip("/") dest_path = os.path.join(out_dir, file_path) Path(os.path.dirname(dest_path)).mkdir(parents=True, exist_ok=True) - logging.info("Downloading: %s to %s", file_item.name, dest_path) + logger.info("Downloading: %s to %s", file_item.name, dest_path) file_client = share_client.get_file_client(file_path) with open(dest_path, "wb+") as f: data = file_client.download_file() @@ -575,7 +579,7 @@ def _get_azure_storage_token(): token_credential = DefaultAzureCredential() - logging.info("Retrieved SP token credential for client_id: %s", client_id) + logger.info("Retrieved SP token credential for client_id: %s", client_id) return token_credential @staticmethod @@ -600,11 +604,11 @@ def _download_local(uri, out_dir=None): for src in glob.glob(local_path): _, tail = os.path.split(src) dest_path = os.path.join(out_dir, tail) - logging.info("Linking: %s to %s", src, dest_path) + logger.info("Linking: %s to %s", src, dest_path) if not os.path.exists(dest_path): os.symlink(src, dest_path) else: - logging.info("File %s already exist", dest_path) + logger.info("File %s already exist", dest_path) file_count += 1 if file_count == 0: raise RuntimeError("Failed to fetch model. No model found in %s." % (uri)) @@ -698,7 +702,7 @@ def _unpack_archive_file(file_path, mimetype, target_dir=None): target_dir = os.path.dirname(file_path) try: - logging.info("Unpacking: %s", file_path) + logger.info("Unpacking: %s", file_path) if mimetype == "application/x-tar": archive = tarfile.open(file_path, "r", encoding="utf-8") else: diff --git a/python/kserve/poetry.lock b/python/kserve/poetry.lock index 2e6007fcdcd..e6376583f96 100644 --- a/python/kserve/poetry.lock +++ b/python/kserve/poetry.lock @@ -3467,7 +3467,7 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [extras] logging = ["asgi-logger"] ray = ["ray"] -storage = ["azure-identity", "azure-storage-blob", "azure-storage-file-share", "boto3", "google-cloud-storage", "requests", "urllib3"] +storage = ["azure-identity", "azure-storage-blob", "azure-storage-file-share", "boto3", "google-cloud-storage", "requests"] [metadata] lock-version = "2.0" diff --git a/python/kserve/pyproject.toml b/python/kserve/pyproject.toml index d36b8679282..0e6065ea090 100644 --- a/python/kserve/pyproject.toml +++ b/python/kserve/pyproject.toml @@ -52,9 +52,9 @@ timing-asgi = "^0.3.0" tabulate = "^0.9.0" pandas = ">=1.3.5" pydantic = ">1.0,<3" +pyyaml = "^6.0.0" # Storage dependencies. They can be opted into by apps. -urllib3 = { version = "^1.26.8", optional = true } requests = { version = "^2.20.0", optional = true } google-cloud-storage = { version = "^2.3.0", optional = true } azure-storage-blob = { version = "^12.10.0", optional = true } @@ -70,7 +70,6 @@ ray = { version = "~2.10.0", extras = ["serve"] } [tool.poetry.extras] storage = [ - "urllib3", "requests", "google-cloud-storage", "azure-storage-blob", diff --git a/python/lgbserver/lgbserver/__main__.py b/python/lgbserver/lgbserver/__main__.py index 2a286e3a7f6..1363a9abdb9 100644 --- a/python/lgbserver/lgbserver/__main__.py +++ b/python/lgbserver/lgbserver/__main__.py @@ -13,13 +13,14 @@ # limitations under the License. import argparse -import logging +from kserve import logging from lgbserver.lightgbm_model_repository import LightGBMModelRepository from lgbserver.model import LightGBMModel import kserve from kserve.errors import ModelMissingError +from kserve.logging import logger DEFAULT_NTHREAD = 1 @@ -35,14 +36,15 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": - + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = LightGBMModel(args.model_name, args.model_dir, args.nthread) try: model.load() # LightGBM doesn't support multi-process, so the number of http server workers should be 1. kserve.ModelServer(workers=1).start([model] if model.ready else []) except ModelMissingError: - logging.error( + logger.error( f"fail to load model {args.model_name} from dir {args.model_dir}," f"trying to load from model repository." ) diff --git a/python/lgbserver/poetry.lock b/python/lgbserver/poetry.lock index 1213f7c1e45..e0a242baa80 100644 --- a/python/lgbserver/poetry.lock +++ b/python/lgbserver/poetry.lock @@ -1363,7 +1363,7 @@ referencing = ">=0.31.0" [[package]] name = "kserve" -version = "0.12.1" +version = "0.13.0rc0" description = "KServe Python SDK" optional = false python-versions = ">=3.8,<3.12" @@ -1371,7 +1371,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -1390,17 +1389,17 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/paddleserver/paddleserver/__main__.py b/python/paddleserver/paddleserver/__main__.py index ad5b7f63913..99d07e0ab1e 100644 --- a/python/paddleserver/paddleserver/__main__.py +++ b/python/paddleserver/paddleserver/__main__.py @@ -17,7 +17,7 @@ from paddleserver import PaddleModel import kserve - +from kserve import logging parser = argparse.ArgumentParser(parents=[kserve.model_server.parser]) parser.add_argument( @@ -26,6 +26,8 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = PaddleModel(args.model_name, args.model_dir) model.load() kserve.ModelServer().start([model]) diff --git a/python/paddleserver/poetry.lock b/python/paddleserver/poetry.lock index 7d0967f12d8..ffa8d337ba1 100644 --- a/python/paddleserver/poetry.lock +++ b/python/paddleserver/poetry.lock @@ -1374,7 +1374,7 @@ referencing = ">=0.31.0" [[package]] name = "kserve" -version = "0.12.1" +version = "0.13.0rc0" description = "KServe Python SDK" optional = false python-versions = ">=3.8,<3.12" @@ -1382,7 +1382,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -1401,17 +1400,17 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/pmmlserver/pmmlserver/__main__.py b/python/pmmlserver/pmmlserver/__main__.py index 88e1481c3d5..2d04c64164d 100644 --- a/python/pmmlserver/pmmlserver/__main__.py +++ b/python/pmmlserver/pmmlserver/__main__.py @@ -14,6 +14,7 @@ import argparse +from kserve import logging from pmmlserver import PmmlModel import kserve @@ -33,6 +34,8 @@ def validate_max_workers(actual_workers: int, max_workers: int): if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = PmmlModel(args.model_name, args.model_dir) model.load() server = kserve.ModelServer() diff --git a/python/pmmlserver/poetry.lock b/python/pmmlserver/poetry.lock index e0eaafd2b04..b66e0f228e1 100644 --- a/python/pmmlserver/poetry.lock +++ b/python/pmmlserver/poetry.lock @@ -1443,7 +1443,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -1462,17 +1461,17 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/sklearnserver/poetry.lock b/python/sklearnserver/poetry.lock index 1f39097543c..a297f451fcc 100644 --- a/python/sklearnserver/poetry.lock +++ b/python/sklearnserver/poetry.lock @@ -1371,7 +1371,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -1390,17 +1389,17 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/sklearnserver/sklearnserver/__main__.py b/python/sklearnserver/sklearnserver/__main__.py index 6e4b01291f0..9bca3337b15 100644 --- a/python/sklearnserver/sklearnserver/__main__.py +++ b/python/sklearnserver/sklearnserver/__main__.py @@ -13,12 +13,13 @@ # limitations under the License. import argparse -import logging +from kserve import logging from sklearnserver import SKLearnModel, SKLearnModelRepository import kserve from kserve.errors import ModelMissingError +from kserve.logging import logger parser = argparse.ArgumentParser(parents=[kserve.model_server.parser]) parser.add_argument( @@ -27,13 +28,15 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = SKLearnModel(args.model_name, args.model_dir) try: model.load() kserve.ModelServer().start([model] if model.ready else []) except ModelMissingError: - logging.error( + logger.error( f"fail to locate model file for model {args.model_name} under dir {args.model_dir}," f"trying loading from model repository." ) diff --git a/python/storage-initializer/scripts/initializer-entrypoint b/python/storage-initializer/scripts/initializer-entrypoint index 8d4cb867a1a..270304e306f 100644 --- a/python/storage-initializer/scripts/initializer-entrypoint +++ b/python/storage-initializer/scripts/initializer-entrypoint @@ -1,8 +1,10 @@ #!/usr/bin/env python3 import sys -import logging from kserve.storage import Storage +from kserve.logging import configure_logging, logger + +configure_logging() if len(sys.argv) != 3: print("Usage: initializer-entrypoint src_uri dest_path") @@ -11,5 +13,5 @@ if len(sys.argv) != 3: src_uri = sys.argv[1] dest_path = sys.argv[2] -logging.info("Initializing, args: src_uri [%s] dest_path[ [%s]" % (src_uri, dest_path)) +logger.info("Initializing, args: src_uri [%s] dest_path[ [%s]" % (src_uri, dest_path)) Storage.download(src_uri, dest_path) diff --git a/python/test_resources/graph/error_404_isvc/model.py b/python/test_resources/graph/error_404_isvc/model.py index 9b4d095b338..0364f02b254 100644 --- a/python/test_resources/graph/error_404_isvc/model.py +++ b/python/test_resources/graph/error_404_isvc/model.py @@ -13,14 +13,13 @@ # limitations under the License. import argparse -import logging from typing import Dict, Union import kserve from fastapi import HTTPException -from kserve.model import InferRequest, ModelInferRequest -logger = logging.getLogger(__name__) +from kserve import logging +from kserve.model import InferRequest, ModelInferRequest class SampleTemplateNode(kserve.Model): @@ -41,5 +40,7 @@ def predict( parser = argparse.ArgumentParser(parents=[kserve.model_server.parser]) args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = SampleTemplateNode(name=args.model_name) kserve.ModelServer(workers=1).start([model]) diff --git a/python/test_resources/graph/error_404_isvc/poetry.lock b/python/test_resources/graph/error_404_isvc/poetry.lock index f7cd94050e3..84175adc205 100644 --- a/python/test_resources/graph/error_404_isvc/poetry.lock +++ b/python/test_resources/graph/error_404_isvc/poetry.lock @@ -860,7 +860,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -874,6 +873,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -882,7 +882,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/test_resources/graph/success_200_isvc/model.py b/python/test_resources/graph/success_200_isvc/model.py index f4126838288..5b4fac71c56 100644 --- a/python/test_resources/graph/success_200_isvc/model.py +++ b/python/test_resources/graph/success_200_isvc/model.py @@ -13,14 +13,12 @@ # limitations under the License. import argparse -import logging from typing import Dict, Union import kserve +from kserve import logging from kserve.model import InferRequest, ModelInferRequest -logger = logging.getLogger(__name__) - class SampleTemplateNode(kserve.Model): def __init__(self, name: str): @@ -40,5 +38,7 @@ def predict( parser = argparse.ArgumentParser(parents=[kserve.model_server.parser]) args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = SampleTemplateNode(name=args.model_name) kserve.ModelServer(workers=1).start([model]) diff --git a/python/test_resources/graph/success_200_isvc/poetry.lock b/python/test_resources/graph/success_200_isvc/poetry.lock index f7cd94050e3..84175adc205 100644 --- a/python/test_resources/graph/success_200_isvc/poetry.lock +++ b/python/test_resources/graph/success_200_isvc/poetry.lock @@ -860,7 +860,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} cloudevents = "^1.6.2" fastapi = "^0.109.1" grpcio = "^1.49.1" @@ -874,6 +873,7 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} six = "^1.16.0" tabulate = "^0.9.0" @@ -882,7 +882,7 @@ uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/xgbserver/poetry.lock b/python/xgbserver/poetry.lock index b7bf584404c..4e0fa26bac4 100644 --- a/python/xgbserver/poetry.lock +++ b/python/xgbserver/poetry.lock @@ -1363,7 +1363,7 @@ referencing = ">=0.31.0" [[package]] name = "kserve" -version = "0.12.1" +version = "0.13.0rc0" description = "KServe Python SDK" optional = false python-versions = ">=3.8,<3.12" @@ -1371,7 +1371,6 @@ files = [] develop = true [package.dependencies] -async-timeout = {version = "^4.0.3", markers = "python_version >= \"3.11.dev0\" and python_version < \"3.12.dev0\""} azure-identity = {version = "^1.8.0", optional = true} azure-storage-blob = {version = "^12.10.0", optional = true} azure-storage-file-share = {version = "^12.7.0", optional = true} @@ -1390,17 +1389,17 @@ protobuf = "^3.19.0" psutil = "^5.9.0" pydantic = ">1.0,<3" python-dateutil = "^2.8.0" +pyyaml = "^6.0.0" ray = {version = "~2.10.0", extras = ["serve"]} requests = {version = "^2.20.0", optional = true} six = "^1.16.0" tabulate = "^0.9.0" timing-asgi = "^0.3.0" -urllib3 = {version = "^1.26.8", optional = true} uvicorn = {version = "^0.21.1", extras = ["standard"]} [package.extras] logging = ["asgi-logger (>=0.1.0,<0.2.0)"] -storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)", "urllib3 (>=1.26.8,<2.0.0)"] +storage = ["azure-identity (>=1.8.0,<2.0.0)", "azure-storage-blob (>=12.10.0,<13.0.0)", "azure-storage-file-share (>=12.7.0,<13.0.0)", "boto3 (>=1.21.0,<2.0.0)", "google-cloud-storage (>=2.3.0,<3.0.0)", "requests (>=2.20.0,<3.0.0)"] [package.source] type = "directory" diff --git a/python/xgbserver/xgbserver/__main__.py b/python/xgbserver/xgbserver/__main__.py index 7394ed25465..0baac9f2825 100644 --- a/python/xgbserver/xgbserver/__main__.py +++ b/python/xgbserver/xgbserver/__main__.py @@ -13,12 +13,13 @@ # limitations under the License. import argparse -import logging from xgbserver import XGBoostModel, XGBoostModelRepository import kserve +from kserve import logging from kserve.errors import ModelMissingError +from kserve.logging import logger DEFAULT_LOCAL_MODEL_DIR = "/tmp/model" DEFAULT_NTHREAD = 1 @@ -35,12 +36,14 @@ args, _ = parser.parse_known_args() if __name__ == "__main__": + if args.configure_logging: + logging.configure_logging(args.log_config_file) model = XGBoostModel(args.model_name, args.model_dir, args.nthread) try: model.load() kserve.ModelServer().start([model] if model.ready else []) except ModelMissingError: - logging.error( + logger.error( f"fail to locate model file for model {args.model_name} under dir {args.model_dir}," f"trying loading from model repository." ) diff --git a/test/e2e/common/utils.py b/test/e2e/common/utils.py index 48197fb0cd5..12d8f2143bb 100644 --- a/test/e2e/common/utils.py +++ b/test/e2e/common/utils.py @@ -10,6 +10,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import json import logging import os @@ -28,8 +29,6 @@ from . import inference_pb2_grpc -logging.basicConfig(level=logging.INFO) - KSERVE_NAMESPACE = "kserve" KSERVE_TEST_NAMESPACE = "kserve-ci-e2e-test" MODEL_CLASS_NAME = "modelClass" diff --git a/test/e2e/explainer/test_art_explainer.py b/test/e2e/explainer/test_art_explainer.py index cffac71a14b..53a492917e4 100644 --- a/test/e2e/explainer/test_art_explainer.py +++ b/test/e2e/explainer/test_art_explainer.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. + import logging import os @@ -31,7 +32,6 @@ from ..common.utils import explain_art from ..common.utils import KSERVE_TEST_NAMESPACE -logging.basicConfig(level=logging.INFO) kserve_client = KServeClient(config_file=os.environ.get("KUBECONFIG", "~/.kube/config")) diff --git a/test/e2e/graph/test_inference_graph.py b/test/e2e/graph/test_inference_graph.py index 142b21f4fa4..e3fff01f0db 100644 --- a/test/e2e/graph/test_inference_graph.py +++ b/test/e2e/graph/test_inference_graph.py @@ -25,8 +25,6 @@ from ..common.utils import KSERVE_TEST_NAMESPACE, predict_ig -logging.basicConfig(level=logging.INFO) - SUCCESS_ISVC_IMAGE = "kserve/success-200-isvc:" + os.environ.get("GITHUB_SHA") ERROR_ISVC_IMAGE = "kserve/error-404-isvc:" + os.environ.get("GITHUB_SHA") IG_TEST_RESOURCES_BASE_LOCATION = "graph/test-resources" diff --git a/test/e2e/predictor/test_paddle.py b/test/e2e/predictor/test_paddle.py index b0ad5e689e0..afa26b6d556 100644 --- a/test/e2e/predictor/test_paddle.py +++ b/test/e2e/predictor/test_paddle.py @@ -33,8 +33,6 @@ from ..common.utils import KSERVE_TEST_NAMESPACE, predict, predict_grpc -logging.basicConfig(level=logging.INFO) - @pytest.mark.predictor def test_paddle(): diff --git a/test/e2e/pytest.ini b/test/e2e/pytest.ini index 0c086f32545..f1984989c16 100644 --- a/test/e2e/pytest.ini +++ b/test/e2e/pytest.ini @@ -1,4 +1,7 @@ [pytest] +log_cli = 1 +log_level = INFO +log_cli_level = INFO markers = explainer: explainer tests transformer: transformer tests diff --git a/test/e2e/qpext/test_qpext.py b/test/e2e/qpext/test_qpext.py index d5f83f934b8..c88298fb271 100644 --- a/test/e2e/qpext/test_qpext.py +++ b/test/e2e/qpext/test_qpext.py @@ -11,8 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import logging +import os import time import requests import portforward @@ -26,10 +26,10 @@ V1beta1SKLearnSpec, ) from kubernetes.client import V1ResourceRequirements + from ..common.utils import KSERVE_TEST_NAMESPACE, get_cluster_ip from ..common.utils import predict -logging.basicConfig(level=logging.INFO) ENABLE_METRIC_AGG = "serving.kserve.io/enable-metric-aggregation" METRICS_AGG_PORT = 9088 diff --git a/test/e2e/transformer/test_raw_transformer.py b/test/e2e/transformer/test_raw_transformer.py index 0822ae55646..56cdf413a9b 100644 --- a/test/e2e/transformer/test_raw_transformer.py +++ b/test/e2e/transformer/test_raw_transformer.py @@ -12,7 +12,6 @@ # limitations under the License. import os -import logging from kubernetes import client from kserve import KServeClient @@ -29,8 +28,6 @@ from ..common.utils import predict from ..common.utils import KSERVE_TEST_NAMESPACE -logging.basicConfig(level=logging.INFO) - @pytest.mark.raw def test_transformer():