Skip to content

Commit 23da920

Browse files
aaronsteersoctavia-squidington-iii
andauthored
Feat: Improve log file handling for connector failures (#333)
Co-authored-by: octavia-squidington-iii <contact@airbyte.com>
1 parent 383d89c commit 23da920

File tree

4 files changed

+74
-34
lines changed

4 files changed

+74
-34
lines changed

airbyte/_connector_base.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
)
2626

2727
from airbyte import exceptions as exc
28-
from airbyte._util import meta
2928
from airbyte._util.telemetry import (
3029
EventState,
3130
log_config_validation_result,
3231
log_connector_check_result,
3332
)
3433
from airbyte._util.temp_files import as_temp_files
34+
from airbyte.constants import AIRBYTE_LOGGING_ROOT
3535

3636

3737
if TYPE_CHECKING:
@@ -307,16 +307,22 @@ def _init_logger(self) -> logging.Logger:
307307
# Prevent logging to stderr by stopping propagation to the root logger
308308
logger.propagate = False
309309

310+
if AIRBYTE_LOGGING_ROOT is None:
311+
# No temp directory available, so return a basic logger
312+
return logger
313+
314+
# Else, configure the logger to write to a file
315+
310316
# Remove any existing handlers
311317
for handler in logger.handlers:
312318
logger.removeHandler(handler)
313319

314-
folder = meta.get_logging_root() / self.name
320+
folder = AIRBYTE_LOGGING_ROOT / self.name
315321
folder.mkdir(parents=True, exist_ok=True)
316322

317323
# Create and configure file handler
318324
handler = logging.FileHandler(
319-
filename=folder / f"{ulid.ULID()!s}-run-log.txt",
325+
filename=folder / f"connector-log-{ulid.ULID()!s}.txt",
320326
encoding="utf-8",
321327
)
322328
handler.setFormatter(
@@ -329,11 +335,6 @@ def _init_logger(self) -> logging.Logger:
329335
logger.addHandler(handler)
330336
return logger
331337

332-
def _new_log_file(self, verb: str = "run") -> Path:
333-
folder = meta.get_logging_root() / self.name
334-
folder.mkdir(parents=True, exist_ok=True)
335-
return folder / f"{ulid.ULID()!s}-{self.name}-{verb}-log.txt"
336-
337338
def _peek_airbyte_message(
338339
self,
339340
message: AirbyteMessage,

airbyte/_util/meta.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import os
1010
import sys
11-
import tempfile
1211
from contextlib import suppress
1312
from functools import lru_cache
1413
from pathlib import Path
@@ -21,21 +20,6 @@
2120
"""URL to get the current Google Colab session information."""
2221

2322

24-
@lru_cache
25-
def get_logging_root() -> Path:
26-
"""Return the root directory for logs.
27-
28-
This is the directory where logs are stored.
29-
"""
30-
if "AIRBYTE_LOGGING_ROOT" in os.environ:
31-
log_root = Path(os.environ["AIRBYTE_LOGGING_ROOT"])
32-
else:
33-
log_root = Path(tempfile.gettempdir()) / "airbyte" / "logs"
34-
35-
log_root.mkdir(parents=True, exist_ok=True)
36-
return log_root
37-
38-
3923
def get_colab_release_version() -> str | None:
4024
if "COLAB_RELEASE_TAG" in os.environ:
4125
return os.environ["COLAB_RELEASE_TAG"]

airbyte/constants.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
from __future__ import annotations
55

6+
import os
7+
import tempfile
8+
import warnings
9+
from functools import lru_cache
10+
from pathlib import Path
11+
612

713
DEBUG_MODE = False # Set to True to enable additional debug logging.
814

@@ -41,3 +47,46 @@
4147

4248
DEFAULT_ARROW_MAX_CHUNK_SIZE = 100_000
4349
"""The default number of records to include in each batch of an Arrow dataset."""
50+
51+
52+
@lru_cache
53+
def _get_logging_root() -> Path | None:
54+
"""Return the root directory for logs.
55+
56+
Returns `None` if no valid path can be found.
57+
58+
This is the directory where logs are stored.
59+
"""
60+
if "AIRBYTE_LOGGING_ROOT" in os.environ:
61+
log_root = Path(os.environ["AIRBYTE_LOGGING_ROOT"])
62+
else:
63+
log_root = Path(tempfile.gettempdir()) / "airbyte" / "logs"
64+
65+
try:
66+
# Attempt to create the log root directory if it does not exist
67+
log_root.mkdir(parents=True, exist_ok=True)
68+
except OSError:
69+
# Handle the error by returning None
70+
warnings.warn(
71+
(
72+
f"Failed to create PyAirbyte logging directory at `{log_root}`. "
73+
"You can override the default path by setting the `AIRBYTE_LOGGING_ROOT` "
74+
"environment variable."
75+
),
76+
category=UserWarning,
77+
stacklevel=0,
78+
)
79+
return None
80+
else:
81+
return log_root
82+
83+
84+
AIRBYTE_LOGGING_ROOT: Path | None = _get_logging_root()
85+
"""The root directory for Airbyte logs.
86+
87+
This value can be overridden by setting the `AIRBYTE_LOGGING_ROOT` environment variable.
88+
89+
If not provided, PyAirbyte will use `/tmp/airbyte/logs/` where `/tmp/` is the OS's default
90+
temporary directory. If the directory cannot be created, PyAirbyte will log a warning and
91+
set this value to `None`.
92+
"""

airbyte/exceptions.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class PyAirbyteError(Exception):
6464
guidance: str | None = None
6565
help_url: str | None = None
6666
log_text: str | list[str] | None = None
67+
log_file: Path | None = None
6768
context: dict[str, Any] | None = None
6869
message: str | None = None
6970

@@ -81,7 +82,7 @@ def get_message(self) -> str:
8182

8283
def __str__(self) -> str:
8384
"""Return a string representation of the exception."""
84-
special_properties = ["message", "guidance", "help_url", "log_text", "context"]
85+
special_properties = ["message", "guidance", "help_url", "log_text", "context", "log_file"]
8586
display_properties = {
8687
k: v
8788
for k, v in self.__dict__.items()
@@ -99,13 +100,16 @@ def __str__(self) -> str:
99100
if isinstance(self.log_text, list):
100101
self.log_text = "\n".join(self.log_text)
101102

102-
exception_str += f"\nLog output: \n {indent(self.log_text, ' ')}"
103+
exception_str += f"\n Log output: \n {indent(self.log_text, ' ')}"
104+
105+
if self.log_file:
106+
exception_str += f"\n Log file: {self.log_file.absolute()!s}"
103107

104108
if self.guidance:
105-
exception_str += f"\nSuggestion: {self.guidance}"
109+
exception_str += f"\n Suggestion: {self.guidance}"
106110

107111
if self.help_url:
108-
exception_str += f"\nMore info: {self.help_url}"
112+
exception_str += f"\n More info: {self.help_url}"
109113

110114
return exception_str
111115

@@ -263,13 +267,13 @@ class AirbyteConnectorError(PyAirbyteError):
263267
connector_name: str | None = None
264268

265269
def __post_init__(self) -> None:
266-
"""Log the error message when the exception is raised."""
270+
"""Set the log file path for the connector."""
271+
self.log_file = self._get_log_file()
272+
273+
def _get_log_file(self) -> Path | None:
274+
"""Return the log file path for the connector."""
267275
if self.connector_name:
268276
logger = logging.getLogger(f"airbyte.{self.connector_name}")
269-
if self.connector_name:
270-
logger.error(str(self))
271-
else:
272-
logger.error(str(self))
273277

274278
log_paths: list[Path] = [
275279
Path(handler.baseFilename).absolute()
@@ -278,7 +282,9 @@ def __post_init__(self) -> None:
278282
]
279283

280284
if log_paths:
281-
print(f"Connector logs: {', '.join(str(path) for path in log_paths)}")
285+
return log_paths[0]
286+
287+
return None
282288

283289

284290
class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError):

0 commit comments

Comments
 (0)