Skip to content

Commit 9b36b51

Browse files
authored
CM-34777 - Add correlation ID (#220)
1 parent 389f14b commit 9b36b51

File tree

6 files changed

+78
-40
lines changed

6 files changed

+78
-40
lines changed

cycode/cli/printers/printer_base.py

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import click
66

77
from cycode.cli.models import CliError, CliResult
8+
from cycode.cyclient.headers import get_correlation_id
89

910
if TYPE_CHECKING:
1011
from cycode.cli.models import LocalScanResult
@@ -46,3 +47,6 @@ def print_exception(self, e: Optional[BaseException] = None) -> None:
4647
message = f'Error: {traceback_message}'
4748

4849
click.secho(message, err=True, fg=self.RED_COLOR_NAME)
50+
51+
correlation_message = f'Correlation ID: {get_correlation_id()}'
52+
click.secho(correlation_message, err=True, fg=self.RED_COLOR_NAME)

cycode/cyclient/config.py

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import logging
22
import os
33
import sys
4-
from typing import Optional
4+
from typing import Optional, Union
55
from urllib.parse import urlparse
66

77
from cycode.cli import consts
88
from cycode.cli.user_settings.configuration_manager import ConfigurationManager
9-
from cycode.cyclient.config_dev import DEV_MODE_ENV_VAR_NAME, DEV_TENANT_ID_ENV_VAR_NAME
9+
from cycode.cyclient import config_dev
1010

1111

1212
def _set_io_encodings() -> None:
@@ -37,20 +37,22 @@ def _set_io_encodings() -> None:
3737
DEFAULT_CONFIGURATION = {
3838
consts.TIMEOUT_ENV_VAR_NAME: 300,
3939
consts.LOGGING_LEVEL_ENV_VAR_NAME: logging.INFO,
40-
DEV_MODE_ENV_VAR_NAME: 'False',
40+
config_dev.DEV_MODE_ENV_VAR_NAME: 'false',
4141
}
4242

4343
configuration = dict(DEFAULT_CONFIGURATION, **os.environ)
4444

4545
_CREATED_LOGGERS = set()
4646

4747

48-
def get_logger(logger_name: Optional[str] = None) -> logging.Logger:
49-
config_level = _get_val_as_string(consts.LOGGING_LEVEL_ENV_VAR_NAME)
50-
level = logging.getLevelName(config_level)
48+
def get_logger_level() -> Optional[Union[int, str]]:
49+
config_level = get_val_as_string(consts.LOGGING_LEVEL_ENV_VAR_NAME)
50+
return logging.getLevelName(config_level)
51+
5152

53+
def get_logger(logger_name: Optional[str] = None) -> logging.Logger:
5254
new_logger = logging.getLogger(logger_name)
53-
new_logger.setLevel(level)
55+
new_logger.setLevel(get_logger_level())
5456

5557
_CREATED_LOGGERS.add(new_logger)
5658

@@ -62,24 +64,24 @@ def set_logging_level(level: int) -> None:
6264
created_logger.setLevel(level)
6365

6466

65-
def _get_val_as_string(key: str) -> str:
67+
def get_val_as_string(key: str) -> str:
6668
return configuration.get(key)
6769

6870

69-
def _get_val_as_bool(key: str, default: str = '') -> bool:
71+
def get_val_as_bool(key: str, default: str = '') -> bool:
7072
val = configuration.get(key, default)
71-
return val.lower() in ('true', '1')
73+
return val.lower() in {'true', '1'}
7274

7375

74-
def _get_val_as_int(key: str) -> Optional[int]:
76+
def get_val_as_int(key: str) -> Optional[int]:
7577
val = configuration.get(key)
7678
if val:
7779
return int(val)
7880

7981
return None
8082

8183

82-
def _is_valid_url(url: str) -> bool:
84+
def is_valid_url(url: str) -> bool:
8385
try:
8486
urlparse(url)
8587
return True
@@ -92,12 +94,12 @@ def _is_valid_url(url: str) -> bool:
9294
configuration_manager = ConfigurationManager()
9395

9496
cycode_api_url = configuration_manager.get_cycode_api_url()
95-
if not _is_valid_url(cycode_api_url):
97+
if not is_valid_url(cycode_api_url):
9698
cycode_api_url = consts.DEFAULT_CYCODE_API_URL
9799

98-
timeout = _get_val_as_int(consts.CYCODE_CLI_REQUEST_TIMEOUT_ENV_VAR_NAME)
100+
timeout = get_val_as_int(consts.CYCODE_CLI_REQUEST_TIMEOUT_ENV_VAR_NAME)
99101
if not timeout:
100-
timeout = _get_val_as_int(consts.TIMEOUT_ENV_VAR_NAME)
102+
timeout = get_val_as_int(consts.TIMEOUT_ENV_VAR_NAME)
101103

102-
dev_mode = _get_val_as_bool(DEV_MODE_ENV_VAR_NAME)
103-
dev_tenant_id = _get_val_as_string(DEV_TENANT_ID_ENV_VAR_NAME)
104+
dev_mode = get_val_as_bool(config_dev.DEV_MODE_ENV_VAR_NAME)
105+
dev_tenant_id = get_val_as_string(config_dev.DEV_TENANT_ID_ENV_VAR_NAME)

cycode/cyclient/cycode_client_base.py

+5-21
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
1-
import platform
21
from typing import ClassVar, Dict, Optional
32

43
from requests import Response, exceptions, request
54

6-
from cycode import __version__
75
from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError, NetworkError
8-
from cycode.cli.user_settings.configuration_manager import ConfigurationManager
96
from cycode.cyclient import config, logger
10-
11-
12-
def get_cli_user_agent() -> str:
13-
"""Return base User-Agent of CLI.
14-
15-
Example: CycodeCLI/0.2.3 (OS: Darwin; Arch: arm64; Python: 3.8.16; InstallID: *uuid4*)
16-
"""
17-
app_name = 'CycodeCLI'
18-
version = __version__
19-
20-
os = platform.system()
21-
arch = platform.machine()
22-
python_version = platform.python_version()
23-
24-
install_id = ConfigurationManager().get_or_create_installation_id()
25-
26-
return f'{app_name}/{version} (OS: {os}; Arch: {arch}; Python: {python_version}; InstallID: {install_id})'
7+
from cycode.cyclient.headers import get_cli_user_agent, get_correlation_id
278

289

2910
class CycodeClientBase:
30-
MANDATORY_HEADERS: ClassVar[Dict[str, str]] = {'User-Agent': get_cli_user_agent()}
11+
MANDATORY_HEADERS: ClassVar[Dict[str, str]] = {
12+
'User-Agent': get_cli_user_agent(),
13+
'X-Correlation-Id': get_correlation_id(),
14+
}
3115

3216
def __init__(self, api_url: str) -> None:
3317
self.timeout = config.timeout

cycode/cyclient/headers.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import platform
2+
from typing import Optional
3+
from uuid import uuid4
4+
5+
from cycode import __version__
6+
from cycode.cli.user_settings.configuration_manager import ConfigurationManager
7+
from cycode.cyclient import logger
8+
9+
10+
def get_cli_user_agent() -> str:
11+
"""Return base User-Agent of CLI.
12+
13+
Example: CycodeCLI/0.2.3 (OS: Darwin; Arch: arm64; Python: 3.8.16; InstallID: *uuid4*)
14+
"""
15+
app_name = 'CycodeCLI'
16+
version = __version__
17+
18+
os = platform.system()
19+
arch = platform.machine()
20+
python_version = platform.python_version()
21+
22+
install_id = ConfigurationManager().get_or_create_installation_id()
23+
24+
return f'{app_name}/{version} (OS: {os}; Arch: {arch}; Python: {python_version}; InstallID: {install_id})'
25+
26+
27+
class _CorrelationId:
28+
_id: Optional[str] = None
29+
30+
def get_correlation_id(self) -> str:
31+
"""Get correlation ID.
32+
33+
Notes:
34+
Used across all requests to correlate logs and metrics.
35+
It doesn't depend on client instances.
36+
Lifetime is the same as the process.
37+
"""
38+
if self._id is None:
39+
# example: 16fd2706-8baf-433b-82eb-8c7fada847da
40+
self._id = str(uuid4())
41+
logger.debug(f'Correlation ID: {self._id}')
42+
43+
return self._id
44+
45+
46+
get_correlation_id = _CorrelationId().get_correlation_id

tests/cli/exceptions/test_handle_scan_errors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_handle_exception_verbose(monkeypatch: 'MonkeyPatch') -> None:
5959
ctx = click.Context(click.Command('path'), obj={'verbose': True, 'output': 'text'})
6060

6161
def mock_secho(msg: str, *_, **__) -> None:
62-
assert 'Error:' in msg
62+
assert 'Error:' in msg or 'Correlation ID:' in msg
6363

6464
monkeypatch.setattr(click, 'secho', mock_secho)
6565

tests/cyclient/test_client_base.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from cycode.cyclient import config
2-
from cycode.cyclient.cycode_client_base import CycodeClientBase, get_cli_user_agent
2+
from cycode.cyclient.cycode_client_base import CycodeClientBase
3+
from cycode.cyclient.headers import get_cli_user_agent, get_correlation_id
34

45

56
def test_mandatory_headers() -> None:
67
expected_headers = {
78
'User-Agent': get_cli_user_agent(),
9+
'X-Correlation-Id': get_correlation_id(),
810
}
911

1012
client = CycodeClientBase(config.cycode_api_url)

0 commit comments

Comments
 (0)