Skip to content

Commit 7746bea

Browse files
Menneni, Shreyamboglesby
authored andcommitted
Pull request #15: Feature/code refactoring/logger
Merge in SIE-BB/netapp-dataops-toolkit from feature/code-refactoring/logger to release-v2.7.0 * commit 'bf389efeffbec0eeef41f11721e124a167849a02': log formatting test clear error stack trace fix logging calls formatting logs fix logging in mcp servers keeping print() instead of logger for helper text logger in cli fix moving logging_utils replacing 'print' with 'logger' inside functions functionality to setup logger
2 parents 62a95f7 + bf389ef commit 7746bea

8 files changed

Lines changed: 527 additions & 425 deletions

File tree

netapp_dataops_k8s/netapp_dataops/k8s/__init__.py

Lines changed: 156 additions & 153 deletions
Large diffs are not rendered by default.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import logging
2+
3+
class UserFriendlyFormatter(logging.Formatter):
4+
"""Custom formatter that suppresses stack traces for user-facing messages."""
5+
6+
def format(self, record):
7+
try:
8+
# For ERROR and CRITICAL levels, only show the message without stack trace
9+
if record.levelno >= logging.ERROR:
10+
# Clear exc_info to prevent stack trace from being displayed
11+
record.exc_info = None
12+
record.exc_text = None
13+
record.stack_info = None
14+
15+
# Handle cases where args don't match the message format
16+
if record.args:
17+
try:
18+
# Try to format normally first
19+
record.getMessage()
20+
except (TypeError, ValueError):
21+
# If formatting fails, combine message and args manually
22+
if len(record.args) == 1:
23+
record.msg = str(record.msg) + str(record.args[0])
24+
else:
25+
record.msg = str(record.msg) + ' '.join(str(arg) for arg in record.args)
26+
record.args = None
27+
28+
return super().format(record)
29+
except Exception:
30+
# Fallback: return a simple error message
31+
return f"Error occurred: {getattr(record, 'msg', 'Unknown error')}"
32+
33+
34+
def setup_logger(name: str, level: int = logging.DEBUG) -> logging.Logger:
35+
"""
36+
Sets up a logger with a console handler.
37+
38+
Args:
39+
name (str): The name of the logger.
40+
level (int): The logging level. Default is logging.DEBUG.
41+
42+
Returns:
43+
logging.Logger: Configured logger.
44+
"""
45+
logger = logging.getLogger(name)
46+
logger.setLevel(level)
47+
if not logger.hasHandlers():
48+
console_handler = logging.StreamHandler()
49+
console_handler.setLevel(level)
50+
formatter = UserFriendlyFormatter('%(message)s')
51+
console_handler.setFormatter(formatter)
52+
logger.addHandler(console_handler)
53+
return logger

netapp_dataops_k8s/netapp_dataops/netapp_dataops_k8s_cli.py

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
S3DataMover,
3333
)
3434

35+
from netapp_dataops.logging_utils import setup_logger
36+
37+
logger = setup_logger(__name__)
38+
39+
3540
# Define contents of help text
3641
astra_error_text = "Error: Astra Control functionality within the DataOps Toolkit is no longer supported. Please use the Astra SDK and/or toolkit. For details, visit https://github.com/NetApp/netapp-astra-toolkits."
3742
helpTextStandard = '''
@@ -710,10 +715,10 @@
710715
## Function for handling situation in which user enters invalid command
711716
def handleInvalidCommand(helpText: str = helpTextStandard, invalidOptArg: bool = False):
712717
if invalidOptArg:
713-
print("Error: Invalid option/argument.")
718+
logger.error("Error: Invalid option/argument.")
714719
else:
715-
print("Error: Invalid command.")
716-
print(helpText)
720+
logger.error("Error: Invalid command.")
721+
logger.info(helpText)
717722
sys.exit(1)
718723

719724

@@ -788,7 +793,7 @@ def getTarget(args: list) -> str:
788793
if not newPvcName or (not sourceSnapshotName and not sourcePvcName):
789794
handleInvalidCommand(helpText=helpTextCloneVolume, invalidOptArg=True)
790795
if sourceSnapshotName and sourcePvcName:
791-
print(
796+
logger.error(
792797
"Error: Both -s/--source-snapshot-name and -v/--source-pvc-name cannot be specified for the same operation.")
793798
handleInvalidCommand(helpText=helpTextCloneVolume, invalidOptArg=True)
794799

@@ -850,8 +855,7 @@ def getTarget(args: list) -> str:
850855
if not newWorkspaceName or (not sourceSnapshotName and not sourceWorkspaceName):
851856
handleInvalidCommand(helpText=helpTextCloneJupyterLab, invalidOptArg=True)
852857
if sourceSnapshotName and sourceWorkspaceName:
853-
print(
854-
"Error: Both -s/--source-snapshot-name and -j/--source-workspace-name cannot be specified for the same operation.")
858+
logger.error("Error: Both -s/--source-snapshot-name and -j/--source-workspace-name cannot be specified for the same operation.")
855859
handleInvalidCommand(helpText=helpTextCloneJupyterLab, invalidOptArg=True)
856860

857861
# Clone volume
@@ -1160,7 +1164,7 @@ def getTarget(args: list) -> str:
11601164
print_output=True
11611165
)
11621166
s3_secret.create()
1163-
print("Kubernetes secret successfully created.")
1167+
logger.info("Kubernetes secret successfully created.")
11641168
except (InvalidConfigError, APIConnectionError):
11651169
sys.exit(1)
11661170

@@ -1202,7 +1206,7 @@ def getTarget(args: list) -> str:
12021206
print_output=True
12031207
)
12041208
config_map.create()
1205-
print("Kubernetes CA config-map successfully created.")
1209+
logger.info("Kubernetes CA config-map successfully created.")
12061210
except (InvalidConfigError, APIConnectionError):
12071211
sys.exit(1)
12081212

@@ -1292,15 +1296,15 @@ def getTarget(args: list) -> str:
12921296

12931297
# Confirm delete operation
12941298
if not force:
1295-
print("Warning: This snapshot will be permanently deleted.")
1299+
logger.warning("Warning: This snapshot will be permanently deleted.")
12961300
while True:
12971301
proceed = input("Are you sure that you want to proceed? (yes/no): ")
12981302
if proceed in ("yes", "Yes", "YES"):
12991303
break
13001304
elif proceed in ("no", "No", "NO"):
13011305
sys.exit(0)
13021306
else:
1303-
print("Invalid value. Must enter 'yes' or 'no'.")
1307+
logger.error("Invalid value. Must enter 'yes' or 'no'.")
13041308

13051309
# Delete snapshot
13061310
try:
@@ -1341,15 +1345,15 @@ def getTarget(args: list) -> str:
13411345

13421346
# Confirm delete operation
13431347
if not force:
1344-
print("Warning: All data associated with the volume will be permanently deleted.")
1348+
logger.warning("Warning: All data associated with the volume will be permanently deleted.")
13451349
while True:
13461350
proceed = input("Are you sure that you want to proceed? (yes/no): ")
13471351
if proceed in ("yes", "Yes", "YES"):
13481352
break
13491353
elif proceed in ("no", "No", "NO"):
13501354
sys.exit(0)
13511355
else:
1352-
print("Invalid value. Must enter 'yes' or 'no'.")
1356+
logger.error("Invalid value. Must enter 'yes' or 'no'.")
13531357

13541358
# Delete volume
13551359
try:
@@ -1394,15 +1398,15 @@ def getTarget(args: list) -> str:
13941398

13951399
# Confirm delete operation
13961400
if not force:
1397-
print("Warning: This FlexCache volume will be permanently deleted.")
1401+
logger.warning("Warning: This FlexCache volume will be permanently deleted.")
13981402
while True:
13991403
proceed = input("Are you sure that you want to proceed? (yes/no): ")
14001404
if proceed in ("yes", "Yes", "YES"):
14011405
break
14021406
elif proceed in ("no", "No", "NO"):
14031407
sys.exit(0)
14041408
else:
1405-
print("Invalid value. Must enter 'yes' or 'no'.")
1409+
logger.error("Invalid value. Must enter 'yes' or 'no'.")
14061410

14071411
# Delete FlexCache volume
14081412
try:
@@ -1444,15 +1448,15 @@ def getTarget(args: list) -> str:
14441448

14451449
# Confirm delete operation
14461450
if not force:
1447-
print("Warning: All data associated with the workspace will be permanently deleted.")
1451+
logger.warning("Warning: All data associated with the workspace will be permanently deleted.")
14481452
while True:
14491453
proceed = input("Are you sure that you want to proceed? (yes/no): ")
14501454
if proceed in ("yes", "Yes", "YES"):
14511455
break
14521456
elif proceed in ("no", "No", "NO"):
14531457
sys.exit(0)
14541458
else:
1455-
print("Invalid value. Must enter 'yes' or 'no'.")
1459+
logger.error("Invalid value. Must enter 'yes' or 'no'.")
14561460

14571461
# Delete JupyterLab workspace
14581462
try:
@@ -1500,7 +1504,7 @@ def getTarget(args: list) -> str:
15001504
print_output=True
15011505
)
15021506
s3_secret.delete()
1503-
print("Kubernetes secret successfully deleted.")
1507+
logger.info("Kubernetes secret successfully deleted.")
15041508
except (InvalidConfigError, APIConnectionError):
15051509
sys.exit(1)
15061510

@@ -1537,7 +1541,7 @@ def getTarget(args: list) -> str:
15371541
try:
15381542
mover_job = DataMoverJob(namespace=namespace, print_output=True)
15391543
mover_job.delete_job(job=job_name)
1540-
print("Job {} deleted.".format(job_name))
1544+
logger.info("Job {} deleted.".format(job_name))
15411545
except (InvalidConfigError, APIConnectionError):
15421546
sys.exit(1)
15431547

@@ -1579,7 +1583,7 @@ def getTarget(args: list) -> str:
15791583
print_output=True
15801584
)
15811585
config_map.delete()
1582-
print("Kubernetes CA config-map successfully deleted.")
1586+
logger.info("Kubernetes CA config-map successfully deleted.")
15831587
except (InvalidConfigError, APIConnectionError):
15841588
sys.exit(1)
15851589

@@ -1613,15 +1617,15 @@ def getTarget(args: list) -> str:
16131617

16141618
# Confirm delete operation
16151619
if not force:
1616-
print("Warning: This server will be permanently deleted.")
1620+
logger.warning("Warning: This server will be permanently deleted.")
16171621
while True:
16181622
proceed = input("Are you sure that you want to proceed? (yes/no): ")
16191623
if proceed in ("yes", "Yes", "YES"):
16201624
break
16211625
elif proceed in ("no", "No", "NO"):
16221626
sys.exit(0)
16231627
else:
1624-
print("Invalid value. Must enter 'yes' or 'no'.")
1628+
logger.error("Invalid value. Must enter 'yes' or 'no'.")
16251629

16261630
# Delete Triton instance
16271631
try:
@@ -1737,7 +1741,7 @@ def getTarget(args: list) -> str:
17371741
print_output=True
17381742
)
17391743
job = data_mover.get_bucket(bucket=bucket_name, pvc=pvc_name, pvc_dir=pvc_dir)
1740-
print("Created Kubernetes job {}".format(job))
1744+
logger.info("Created Kubernetes job {}".format(job))
17411745
except (InvalidConfigError, APIConnectionError):
17421746
sys.exit(1)
17431747

@@ -1845,7 +1849,7 @@ def getTarget(args: list) -> str:
18451849
print_output=True
18461850
)
18471851
job = data_mover.get_object(bucket=bucket_name, pvc=pvc_name, object_key=object_key, file_location=file_location)
1848-
print("Created Kubernetes job {}".format(job))
1852+
logger.info("Created Kubernetes job {}".format(job))
18491853
except (InvalidConfigError, APIConnectionError):
18501854
sys.exit(1)
18511855

@@ -2093,7 +2097,7 @@ def getTarget(args: list) -> str:
20932097
print_output=True
20942098
)
20952099
job = data_mover.put_bucket(bucket=bucket_name, pvc=pvc_name, pvc_dir=pvc_dir)
2096-
print("Created Kubernetes job {}".format(job))
2100+
logger.info("Created Kubernetes job {}".format(job))
20972101
except (InvalidConfigError, APIConnectionError):
20982102
sys.exit(1)
20992103

@@ -2201,7 +2205,7 @@ def getTarget(args: list) -> str:
22012205
print_output=True
22022206
)
22032207
job = data_mover.put_object(bucket=bucket_name, pvc=pvc_name, file_location=file_location, object_key=object_key)
2204-
print("Created Kubernetes job {}".format(job))
2208+
logger.info("Created Kubernetes job {}".format(job))
22052209
except (InvalidConfigError, APIConnectionError):
22062210
sys.exit(1)
22072211

@@ -2253,7 +2257,7 @@ def getTarget(args: list) -> str:
22532257

22542258
# Confirm restore operation
22552259
if not force:
2256-
print(
2260+
logger.warning(
22572261
"Warning: In order to restore a snapshot, the PersistentVolumeClaim (PVC) associated the snapshot must NOT be mounted to any pods.")
22582262
while True:
22592263
proceed = input("Are you sure that you want to proceed? (yes/no): ")
@@ -2262,7 +2266,7 @@ def getTarget(args: list) -> str:
22622266
elif proceed in ("no", "No", "NO"):
22632267
sys.exit(0)
22642268
else:
2265-
print("Invalid value. Must enter 'yes' or 'no'.")
2269+
logger.error("Invalid value. Must enter 'yes' or 'no'.")
22662270

22672271
# Restore snapshot
22682272
try:
@@ -2340,13 +2344,13 @@ def getTarget(args: list) -> str:
23402344
try:
23412345
mover_job = DataMoverJob(namespace=namespace, print_output=True)
23422346
job_status = mover_job.get_job_status(job=job_name)
2343-
print("Job {} status:\n{}".format(job_name, job_status))
2347+
logger.info("Job {} status:\n{}".format(job_name, job_status))
23442348
except (InvalidConfigError, APIConnectionError):
2345-
print(f"Unable to get status of job {job_name}")
2349+
logger.error(f"Unable to get status of job {job_name}")
23462350
sys.exit(1)
23472351

23482352
elif action in ("version", "v", "-v", "--version"):
2349-
print("NetApp DataOps Toolkit for Kubernetes - version " + k8s.__version__)
2353+
logger.info("NetApp DataOps Toolkit for Kubernetes - version %s", k8s.__version__)
23502354

23512355
else:
23522356
handleInvalidCommand()

0 commit comments

Comments
 (0)