Skip to content

Commit cf19e40

Browse files
vdk-structlog: default formatter (#2936)
Solves: [vdk-structlog: support the default console logger and logging configuration#2903](#2903) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 978c970 commit cf19e40

File tree

5 files changed

+122
-15
lines changed

5 files changed

+122
-15
lines changed

projects/vdk-plugins/vdk-structlog/README.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,36 @@ pip install vdk-structlog
1515

1616
(`vdk config-help` is useful command to browse all config options of your installation of vdk)
1717

18-
| Name | Description | Example Value | Possible Values |
19-
| ---------------- | --------------------------------------------------------------------- |---------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
20-
| logging_metadata | Configure the metadata that will be output along with the log message | "timestamp, level, logger_name, file_name, vdk_job_name | "timestamp, level, logger_name, file_name, line_number, function_name, vdk_job_name, vdk_step_name, vdk_step_type" |
21-
| logging_format | Configure the logging output format. Available formats: json, console | "console" | "console", "json" |
18+
| Name | Description | Example Value | Possible Values |
19+
|----------------------------|---------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
20+
| logging_metadata | Configure the metadata that will be output along with the log message | "timestamp, level, logger_name, file_name, vdk_job_name | "timestamp, level, logger_name, file_name, line_number, function_name, vdk_job_name, vdk_step_name, vdk_step_type" |
21+
| logging_format | Configure the logging output format. Available formats: json, console | "console" | "console", "json" |
22+
| custom_console_log_pattern | Custom format string for console logging, applied only when`logging_format` is 'console' and overrides `logging_metadata` | "%(asctime)s %(name)-12s %(levelname)-8s %(message)s" | Any valid Python logging format string |
23+
24+
### Example: Configure Custom Console Format
25+
26+
If you wish to apply a specific format to your console logs, you can define a custom format using the `custom_console_log_pattern` configuration. This custom format string will be used only when the `logging_format` is set to 'console'.
27+
28+
For example, add the following to your data job configuration:
29+
30+
```
31+
[vdk]
32+
custom_console_log_pattern=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
33+
```
34+
When you run your data job, regardless of other logging settings, your logs will strictly follow this custom format, displaying the timestamp, logger's name, log level, and the log message as per the format string specified.
35+
36+
```
37+
2023-10-17 11:20:59,202 managed_cursor INFO ingest-from-db-example-job - Executing query SUCCEEDED. Query duration 00h:00m:00s
38+
2023-10-17 11:20:59,202 managed_connectio INFO ingest-from-db-example-job - Fetching query result...
39+
2023-10-17 11:20:59,202 managed_cursor INFO ingest-from-db-example-job - Fetching all results from query ...
40+
2023-10-17 11:20:59,202 managed_cursor INFO ingest-from-db-example-job - Fetching all results from query SUCCEEDED.
41+
2023-10-17 11:20:59,202 managed_cursor INFO ingest-from-db-example-job - Closing DB cursor ...
42+
2023-10-17 11:20:59,202 managed_cursor INFO ingest-from-db-example-job - Closing DB cursor SUCCEEDED.
43+
2023-10-17 11:20:59,203 file_based_step INFO ingest-from-db-example-job - Entering 30_ingest_to_table.py#run(...) ...
44+
2023-10-17 11:20:59,203 ingester_router INFO ingest-from-db-example-job - Sending tabular data for ingestion with method: sqlite and target: None
45+
2023-10-17 11:20:59,204 file_based_step INFO ingest-from-db-example-job - Exiting 30_ingest_to_table.py#run(...) SUCCESS
46+
```
47+
2248

2349
### Example: Configure metadata
2450

projects/vdk-plugins/vdk-structlog/src/vdk/plugin/structlog/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
STRUCTLOG_LOGGING_METADATA_KEY = "logging_metadata"
66
STRUCTLOG_LOGGING_FORMAT_KEY = "logging_format"
7+
STRUCTLOG_CONSOLE_LOG_PATTERN = "logging_custom_format"
78

89
STRUCTLOG_LOGGING_FORMAT_POSSIBLE_VALUES = ["console", "json"]
910
STRUCTLOG_LOGGING_FORMAT_DEFAULT = "console"

projects/vdk-plugins/vdk-structlog/src/vdk/plugin/structlog/formatters.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ def build_ltsv_format(self) -> str:
132132
return ltsv_format
133133

134134

135-
def create_formatter(logging_format: str, metadata_keys: str) -> [Formatter, Filter]:
135+
def create_formatter(
136+
logging_format: str, metadata_keys: str, custom_console_format: str = None
137+
) -> [Formatter, Filter]:
136138
"""
137139
Creates a formatter and a filter based on the logging format configuration
138140
and metadata_keys configuration that are passed. The formatter takes care
@@ -152,9 +154,13 @@ def create_formatter(logging_format: str, metadata_keys: str) -> [Formatter, Fil
152154
fmt=StructlogMetadataBuilder(metadata_keys).build_ltsv_format()
153155
)
154156
custom_key_filter = ConsoleMetadataFilter(key_set)
155-
else:
156-
formatter = ConsoleFormatter(
157-
fmt=StructlogMetadataBuilder(metadata_keys).build_console_format()
157+
elif logging_format == "console":
158+
format_string = (
159+
custom_console_format
160+
if custom_console_format
161+
else StructlogMetadataBuilder(metadata_keys).build_console_format()
158162
)
163+
formatter = ConsoleFormatter(fmt=format_string)
159164
custom_key_filter = ConsoleMetadataFilter(key_set)
165+
160166
return formatter, custom_key_filter

projects/vdk-plugins/vdk-structlog/src/vdk/plugin/structlog/structlog_plugin.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from vdk.internal.core.config import ConfigurationBuilder
1717
from vdk.internal.core.context import CoreContext
1818
from vdk.plugin.structlog.constants import JSON_STRUCTLOG_LOGGING_METADATA_DEFAULT
19+
from vdk.plugin.structlog.constants import STRUCTLOG_CONSOLE_LOG_PATTERN
1920
from vdk.plugin.structlog.constants import STRUCTLOG_LOGGING_FORMAT_DEFAULT
2021
from vdk.plugin.structlog.constants import STRUCTLOG_LOGGING_FORMAT_KEY
2122
from vdk.plugin.structlog.constants import STRUCTLOG_LOGGING_FORMAT_POSSIBLE_VALUES
@@ -79,6 +80,12 @@ def vdk_configure(self, config_builder: ConfigurationBuilder):
7980
),
8081
)
8182

83+
config_builder.add(
84+
key=STRUCTLOG_CONSOLE_LOG_PATTERN,
85+
default_value="",
86+
description="Custom format string for console logging. Leave empty for default format.",
87+
)
88+
8289
config_builder.add(
8390
key=STRUCTLOG_LOGGING_FORMAT_KEY,
8491
default_value=STRUCTLOG_LOGGING_FORMAT_DEFAULT,
@@ -93,8 +100,13 @@ def vdk_initialize(self, context: CoreContext):
93100
logging_formatter = context.configuration.get_value(
94101
STRUCTLOG_LOGGING_FORMAT_KEY
95102
)
103+
custom_format_string = context.configuration.get_value(
104+
STRUCTLOG_CONSOLE_LOG_PATTERN
105+
)
96106

97-
formatter, metadata_filter = create_formatter(logging_formatter, metadata_keys)
107+
formatter, metadata_filter = create_formatter(
108+
logging_formatter, metadata_keys, custom_format_string
109+
)
98110

99111
root_logger = logging.getLogger()
100112
root_logger.removeHandler(root_logger.handlers[0])
@@ -107,14 +119,19 @@ def vdk_initialize(self, context: CoreContext):
107119

108120
@hookimpl(hookwrapper=True)
109121
def initialize_job(self, context: JobContext) -> None:
122+
metadata_keys = context.core_context.configuration.get_value(
123+
STRUCTLOG_LOGGING_METADATA_KEY
124+
)
110125
logging_formatter = context.core_context.configuration.get_value(
111126
STRUCTLOG_LOGGING_FORMAT_KEY
112127
)
113-
metadata_keys = context.core_context.configuration.get_value(
114-
STRUCTLOG_LOGGING_METADATA_KEY
128+
custom_format_string = context.core_context.configuration.get_value(
129+
STRUCTLOG_CONSOLE_LOG_PATTERN
115130
)
116131

117-
formatter, metadata_filter = create_formatter(logging_formatter, metadata_keys)
132+
formatter, metadata_filter = create_formatter(
133+
logging_formatter, metadata_keys, custom_format_string
134+
)
118135
job_name_adder = AttributeAdder("vdk_job_name", context.name)
119136

120137
root_logger = logging.getLogger()
@@ -134,14 +151,19 @@ def initialize_job(self, context: JobContext) -> None:
134151

135152
@hookimpl(hookwrapper=True)
136153
def run_job(self, context: JobContext) -> Optional[ExecutionResult]:
154+
metadata_keys = context.core_context.configuration.get_value(
155+
STRUCTLOG_LOGGING_METADATA_KEY
156+
)
137157
logging_formatter = context.core_context.configuration.get_value(
138158
STRUCTLOG_LOGGING_FORMAT_KEY
139159
)
140-
metadata_keys = context.core_context.configuration.get_value(
141-
STRUCTLOG_LOGGING_METADATA_KEY
160+
custom_format_string = context.core_context.configuration.get_value(
161+
STRUCTLOG_CONSOLE_LOG_PATTERN
142162
)
143163

144-
formatter, metadata_filter = create_formatter(logging_formatter, metadata_keys)
164+
formatter, metadata_filter = create_formatter(
165+
logging_formatter, metadata_keys, custom_format_string
166+
)
145167
job_name_adder = AttributeAdder("vdk_job_name", context.name)
146168

147169
root_logger = logging.getLogger()

projects/vdk-plugins/vdk-structlog/tests/test_structlog.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,58 @@ def test_stock_fields_removal(log_format):
120120
assert re.search(stock_field_reps[shown_field], test_log) is not None
121121

122122

123+
@pytest.mark.parametrize("log_format", ["console"])
124+
def test_custom_format_applied(log_format):
125+
custom_format_string = "%(asctime)s %(name)-12s %(levelname)-8s %(message)s"
126+
127+
with mock.patch.dict(
128+
os.environ,
129+
{
130+
"VDK_LOGGING_FORMAT": log_format,
131+
"VDK_CUSTOM_CONSOLE_LOG_PATTERN": custom_format_string,
132+
},
133+
):
134+
logs = _run_job_and_get_logs()
135+
136+
for log in logs:
137+
if "Log statement with no bound context" in log:
138+
assert _matches_custom_format(log)
139+
break
140+
else:
141+
pytest.fail("Log statement with no bound context not found in logs")
142+
143+
144+
@pytest.mark.parametrize("log_format", ["json", "ltsv"])
145+
def test_custom_format_not_applied_for_non_console_formats(log_format):
146+
custom_format_string = "%(asctime)s %(name)-12s %(levelname)-8s %(message)s"
147+
148+
with mock.patch.dict(
149+
os.environ,
150+
{
151+
"VDK_LOGGING_METADATA": "timestamp,level,file_name,vdk_job_name",
152+
"VDK_LOGGING_FORMAT": log_format,
153+
"VDK_CUSTOM_CONSOLE_LOG_PATTERN": custom_format_string,
154+
},
155+
):
156+
logs = _run_job_and_get_logs()
157+
158+
for log in logs:
159+
if "Log statement with no bound context" in log:
160+
assert not _matches_custom_format(
161+
log
162+
), f"Custom format was incorrectly applied for {log_format} format. Log: {log}"
163+
break
164+
else:
165+
pytest.fail("Log statement with no bound context not found in logs")
166+
167+
168+
def _matches_custom_format(log):
169+
pattern = re.compile(
170+
r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \S{1,12} \S{1,8} .+"
171+
)
172+
return bool(pattern.search(log))
173+
174+
123175
def _run_job_and_get_logs():
124176
"""
125177
Runs the necessary job and returns the output logs.

0 commit comments

Comments
 (0)