-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Racheld/structured logging discovery #131
Changes from all commits
1c36731
652eda7
666e781
f9d0c9b
ac06e11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,46 @@ | ||
import io | ||
import json | ||
import logging | ||
import uuid | ||
import os | ||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from typing import Optional | ||
|
||
import logbook | ||
import logbook.queues | ||
|
||
from dbt.events.functions import STDOUT_LOG, FILE_LOG | ||
import dbt.logger as dbt_logger | ||
from dbt.events.functions import EVENT_MANAGER | ||
from dbt.events.eventmgr import LoggerConfig, LineFormat, EventLevel | ||
from dbt.events.base_types import BaseEvent | ||
from pythonjsonlogger import jsonlogger | ||
from dbt_server.services import filesystem_service | ||
|
||
from dbt.events import AdapterLogger | ||
from dbt.events.types import ( | ||
AdapterEventDebug, | ||
AdapterEventInfo, | ||
AdapterEventWarning, | ||
AdapterEventError, | ||
) | ||
|
||
from dbt_server.models import TaskState | ||
|
||
DBT_SERVER_EVENT_LOGGER = AdapterLogger("Server") | ||
DBT_SERVER_EVENT_TYPES = [ | ||
AdapterEventDebug, | ||
AdapterEventInfo, | ||
AdapterEventWarning, | ||
AdapterEventError | ||
] | ||
|
||
ACCOUNT_ID = os.environ.get("ACCOUNT_ID") | ||
ENVIRONMENT_ID = os.environ.get("ENVIRONMENT_ID") | ||
WORKSPACE_ID = os.environ.get("WORKSPACE_ID") | ||
|
||
dbt_event_to_python_root_log = { | ||
EventLevel.DEBUG: logging.root.debug, | ||
EventLevel.TEST: logging.root.debug, | ||
EventLevel.INFO: logging.root.info, | ||
EventLevel.WARN: logging.root.warn, | ||
EventLevel.ERROR: logging.root.error, | ||
} | ||
|
||
class CustomJsonFormatter(jsonlogger.JsonFormatter): | ||
def add_fields(self, log_record, record, message_dict): | ||
|
@@ -39,9 +59,9 @@ def add_fields(self, log_record, record, message_dict): | |
log_record["workspaceID"] = WORKSPACE_ID | ||
|
||
|
||
# setup json logging | ||
# setup json logging for stdout and datadog | ||
logger = logging.getLogger() | ||
logger.setLevel(logging.INFO) | ||
logger.setLevel(logging.DEBUG) | ||
stdout = logging.StreamHandler() | ||
if os.environ.get("APPLICATION_ENVIRONMENT") in ("dev", None): | ||
formatter = logging.Formatter( | ||
|
@@ -57,15 +77,7 @@ def add_fields(self, log_record, record, message_dict): | |
) | ||
stdout.setFormatter(formatter) | ||
logger.addHandler(stdout) | ||
dbt_server_logger = logging.getLogger("dbt-server") | ||
dbt_server_logger.setLevel(logging.DEBUG) | ||
GLOBAL_LOGGER = dbt_server_logger | ||
|
||
# remove handlers from these loggers, so | ||
# that they propagate up to the root logger | ||
# for json formatting | ||
STDOUT_LOG.handlers = [] | ||
FILE_LOG.handlers = [] | ||
|
||
# make sure uvicorn is deferring to the root | ||
# logger to format logs | ||
|
@@ -92,9 +104,19 @@ def configure_uvicorn_access_log(): | |
ual.handlers = [] | ||
|
||
|
||
json_formatter = dbt_logger.JsonFormatter(format_string=dbt_logger.STDOUT_LOG_FORMAT) | ||
# Push event messages to root python logger for formatting | ||
def log_event_to_console(event: BaseEvent): | ||
logging_method = dbt_event_to_python_root_log[event.log_level()] | ||
if type(event) not in DBT_SERVER_EVENT_TYPES and logging_method == logging.root.debug: | ||
# Only log debug level for dbt-server logs | ||
return | ||
logging_method(event.info.msg) | ||
|
||
|
||
EVENT_MANAGER.callbacks.append(log_event_to_console) | ||
|
||
|
||
# TODO: This should be some type of event. We may also choose to send events for all task state updates. | ||
@dataclass | ||
class ServerLog: | ||
state: TaskState | ||
|
@@ -104,77 +126,23 @@ def to_json(self): | |
return json.dumps(self.__dict__) | ||
|
||
|
||
# TODO: Make this a contextmanager | ||
class LogManager(object): | ||
def __init__(self, log_path): | ||
from dbt_server.services import filesystem_service | ||
|
||
self.name = str(uuid.uuid4()) | ||
self.log_path = log_path | ||
|
||
filesystem_service.ensure_dir_exists(self.log_path) | ||
|
||
logs_redirect_handler = logbook.FileHandler( | ||
filename=self.log_path, | ||
level=logbook.DEBUG, | ||
bubble=True, | ||
# TODO : Do we want to filter these? | ||
filter=self._dbt_logs_only_filter, | ||
logger_config = LoggerConfig( | ||
name=self.name, | ||
line_format=LineFormat.Json, | ||
level=EventLevel.INFO, | ||
use_colors=True, | ||
output_file_name=log_path, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For my understanding, We are writing the logs to a local file then There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is currently how it's working! This is just bringing us to parity with how the server logging worked before, we aren't set on this implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My goal here would be: finding a straightforward way to get this working for now, while preserving future options where we push events elsewhere / elsehow. Sounds like this approach fits the bill |
||
# TODO: Add scrubber for secrets | ||
) | ||
|
||
# Big hack? | ||
logs_redirect_handler.formatter = json_formatter | ||
|
||
self.handlers = [ | ||
logs_redirect_handler, | ||
] | ||
|
||
dbt_logger.log_manager.set_path(None) | ||
|
||
def _dbt_logs_only_filter(self, record, handler): | ||
""" | ||
DUPLICATE OF LogbookStepLogsStreamWriter._dbt_logs_only_filter | ||
""" | ||
return record.channel.split(".")[0] == "dbt" | ||
|
||
def setup_handlers(self): | ||
logger.info("Setting up log handlers...") | ||
|
||
dbt_logger.log_manager.objects = [ | ||
handler | ||
for handler in dbt_logger.log_manager.objects | ||
if type(handler) is not logbook.NullHandler | ||
] | ||
|
||
handlers = [logbook.NullHandler()] + self.handlers | ||
|
||
self.log_context = logbook.NestedSetup(handlers) | ||
self.log_context.push_application() | ||
|
||
logger.info("Done setting up log handlers.") | ||
EVENT_MANAGER.add_logger(logger_config) | ||
|
||
def cleanup(self): | ||
self.log_context.pop_application() | ||
|
||
|
||
class CapturingLogManager(LogManager): | ||
def __init__(self, log_path): | ||
super().__init__(log_path) | ||
|
||
self._stream = io.StringIO() | ||
capture_handler = logbook.StreamHandler( | ||
stream=self._stream, | ||
level=logbook.DEBUG, | ||
bubble=True, | ||
filter=self._dbt_logs_only_filter, | ||
) | ||
|
||
capture_handler.formatter = json_formatter | ||
|
||
self.handlers += [capture_handler] | ||
# TODO: verify that threading doesn't result in wonky list | ||
EVENT_MANAGER.loggers = [log for log in EVENT_MANAGER.loggers if log.name != self.name] | ||
|
||
def getLogs(self): | ||
# Be a good citizen with the seek pos | ||
pos = self._stream.tell() | ||
self._stream.seek(0) | ||
res = self._stream.read().split("\n") | ||
self._stream.seek(pos) | ||
return res |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@peterallenwebb we probably want to move EVENT_MANAGER into a contextvar for this kind of usecase? If we plan to have concurrent request being handled by the same dbt-server container
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChenyuLInx Thanks for opening dbt-labs/dbt-core#6399! Love to see it