-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Send trace context with logs from web applications #3448
Conversation
@liyanhui1228 Do you plan on updating system tests in this PR? If "no", a branch from your own fork is preferred over a branch in the main repository. No need to change it on this one (it isn't really a big deal) but something to think of for future PRs. |
flask = None | ||
else: | ||
_USE_FLASK = True | ||
|
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -0,0 +1,17 @@ | |||
# Copyright 2016 Google Inc. All Rights Reserved. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@dhermes Yeah system test seems necessary for this PR, will consider add it. And thanks for the tip, next time I'll use my own fork if not updating system tests. |
|
||
|
||
def detect_web_framework(): | ||
"""Detect web framework used in this environment. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
import flask | ||
except ImportError: | ||
flask = None | ||
|
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
In Django you need users to specify the middleware in Also Django changes reasonably quickly, so you're going to want to decide which versions you support and decide what to do for versions you don't support (probably fall back to unknown). Check out Django release roadmap. 1.11 is the current default pip package and the LTS version so that's the obvious one, 1.9, 1.10, and 2.0 are currently supported so also nice to have. Overall direction LGTM, will be OOO later in week until after the holiday so don't block on me to merge. |
""" | ||
trace_id = get_trace_id_from_request_header() | ||
if trace_id is None: | ||
trace_id = 'unknown' |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -97,7 +102,10 @@ def emit(self, record): | |||
:param record: The record to be logged. | |||
""" | |||
message = super(CloudLoggingHandler, self).format(record) | |||
self.transport.send(record, message, resource=self.resource) | |||
self.transport.send(record, |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
# limitations under the License. | ||
|
||
try: | ||
from threading import local |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
try: | ||
from threading import local | ||
except ImportError: | ||
from django.utils._threading_local import local |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -37,3 +45,53 @@ def format_stackdriver_json(record, message): | |||
} | |||
|
|||
return json.dumps(payload) | |||
|
|||
|
|||
def detect_web_framework(): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@waprin Thanks for your comments! I plan to update the docs to let the user add the middleware to django settings, because I found that it is risky to modify the settings at runtime. |
""" | ||
try: | ||
trace_id = flask.request.headers['X_CLOUD_TRACE_CONTEXT'].split('/')[0] | ||
except Exception: |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
:return: Trace_id in HTTP request headers. | ||
""" | ||
try: | ||
trace_id = flask.request.headers['X_CLOUD_TRACE_CONTEXT'].split('/')[0] |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
_thread_locals = local() | ||
|
||
|
||
def get_request(): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -43,13 +44,15 @@ def test_constructor(self): | |||
self.assertEqual(handler.resource.labels['project_id'], 'test_project') | |||
self.assertEqual(handler.resource.labels['module_id'], 'test_service') | |||
self.assertEqual(handler.resource.labels['version_id'], 'test_version') | |||
self.assertEqual(handler.labels['appengine.googleapis.com/trace_id'], 'unknown') |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
_FLASK_TRACE_HEADER = 'X_CLOUD_TRACE_CONTEXT' | ||
_DJANGO_TRACE_HEADER = 'HTTP_X_CLOUD_TRACE_CONTEXT' | ||
|
||
_EMPTY_TRACE_ID = 'None' |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
:returns: Labels for GAE app. | ||
""" | ||
trace_id = get_trace_id() | ||
gae_labels = { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
""" | ||
trace_id = get_trace_id() | ||
gae_labels = { | ||
'appengine.googleapis.com/trace_id': trace_id, |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
""" | ||
_thread_locals.request = request | ||
|
||
def get_request(self): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
self.middleware.process_request(self.request) | ||
self.assertEqual(self.middleware.get_request(), self.request) | ||
|
||
def tearDown(self): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
from google.cloud.logging.handlers._helpers import get_trace_id_from_flask | ||
from google.cloud.logging.handlers._helpers import get_trace_id | ||
|
||
FLASK_TRACE_HEADER = 'X_CLOUD_TRACE_CONTEXT' |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@dhermes Great! Thanks for fixing it. |
53738d7
to
4dff904
Compare
@liyanhui1228 Thank you! :) |
Hi @liyanhui1228 For now, we configure Django with this handler handlers = {
'app_engine': {
'level': 'DEBUG',
'formatter': 'verbose',
'class': 'google.cloud.logging.handlers.app_engine.AppEngineHandler',
'filters': ['request'],
'client': logging.Client()
}
} Is it the suggested way? |
@tcroiset Adding |
For #3359. This feature add the trace_id to GAE app logs and enables the logs correlation based on the trace_id.
For flask the trace_id is extracted from request headers, for django I used a middleware to store the request in thread local, and then get the trace_id from it. The user will need to add the middleware to django settings before deploy the app. Will add the unit test later.