Skip to content

Commit

Permalink
[ROUGH DRAFT] Add GAE Shutdown Handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Bill Prin committed Feb 21, 2017
1 parent e675367 commit c4c70df
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 9 deletions.
112 changes: 112 additions & 0 deletions logging/google/cloud/logging/_shutdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for code related to shutdown logging."""

import functools
import httplib2
import os
import signal
import sys
import traceback

from google.cloud.logging.environment_vars import _APPENGINE_FLEXIBLE_ENV_VM
from google.cloud.logging.environment_vars import _APPENGINE_FLEXIBLE_ENV_FLEX

_METADATA_SERVICE_ADDR = '169.254.169.254'
_APPENGINE_SERVICE = 'appengine.googleapis.com'


def _fetch_metadata(metdata_path):
url = 'http://{}/computeMetadata/v1/{}'.format(_METADATA_SERVICE_ADDR,
metdata_path)
h = httplib2.Http()
resp, content = h.request(url, 'GET')
return content


def _get_gae_instance():
return _fetch_metadata('instance/attributes/gae_backend_instance')


def _get_gae_backend():
return _fetch_metadata('instance/attributes/gae_backend_name')


def _get_gae_version():
return _fetch_metadata('instance/attributes/gae_backend_version')


def _write_stacktrace_log(client, line):
gae_version = _get_gae_version()
gae_backend = _get_gae_backend()
gae_instance = _get_gae_instance()

text_payload = '{}\nThread traces\n{}'.format(gae_instance, line)
logger_name = 'projects/{}/logs/'
'appengine.googleapis.com%2Fapp.shutdown'.format(client.project)

resource = {'type': 'gae_app', 'labels': {'project_id': client.project,
'version_id': gae_version,
'module_id': gae_backend}}

labels = {'appengine.googleapis.com/version_id': gae_version,
'compute.googleapis.com/resource_type': 'instance',
'appengine.googleapis.com/instance_name': gae_instance,
'appengine.googleapis.com / module_id': gae_backend, }
entry = {'text_payload': text_payload}

entries = [entry]

client.logging_api.write_entries(
entries, logger_name=logger_name, resource=resource, labels=labels)


def _is_on_appengine():
return (os.getenv(_APPENGINE_FLEXIBLE_ENV_VM) or os.getenv(
_APPENGINE_FLEXIBLE_ENV_FLEX))


def _report_stacktraces(client, signal, frame):
"""
Reports the stacktraces of all active threads to Stackdriver Logging.
:type client: `google.cloud.logging.Client`
:param client: Stackdriver loggingclient.
:type signal: int
:param signal: Signal number.
:type frame: frame object
:param frame: The current stack frame.
"""
traces = ''
for threadId, stack in sys._current_frames().items():
traces += '\n# ThreadID: {}'.format(threadId)
for filename, lineno, name, line in traceback.extract_stack(stack):
traces += 'File: {}, line {}, in {}'.format(
filename, lineno, name)
_write_stacktrace_log(client, traces)


def setup_shutdown_stacktrace_reporting(client):
"""Installs a SIGTERM handler to log stack traces to Stackdriver.
:type client: `google.cloud.logging.Client`
:param client: Stackdriver logging client.
"""
if not _is_on_appengine():
raise Exception('Shutdown reporting is only supported on App Engine '
'flexible environment.')
signal.signal(signal.SIGTERM, functools.partial(_report_stacktraces, client))
30 changes: 21 additions & 9 deletions logging/google/cloud/logging/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,21 @@

from google.cloud.client import ClientWithProject
from google.cloud.environment_vars import DISABLE_GRPC
from google.cloud.logging.environment_vars import _APPENGINE_FLEXIBLE_ENV_VM
from google.cloud.logging.environment_vars import _APPENGINE_FLEXIBLE_ENV_FLEX
from google.cloud.logging.environment_vars import _CONTAINER_ENGINE_ENV
from google.cloud.logging._http import Connection
from google.cloud.logging._http import _LoggingAPI as JSONLoggingAPI
from google.cloud.logging._http import _MetricsAPI as JSONMetricsAPI
from google.cloud.logging._http import _SinksAPI as JSONSinksAPI
from google.cloud.logging._shutdown import setup_shutdown_stacktrace_reporting
from google.cloud.logging.handlers import CloudLoggingHandler
from google.cloud.logging.handlers import AppEngineHandler
from google.cloud.logging.handlers import ContainerEngineHandler
from google.cloud.logging.handlers import setup_logging
from google.cloud.logging.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS


from google.cloud.logging.logger import Logger
from google.cloud.logging.metric import Metric
from google.cloud.logging.sink import Sink
Expand All @@ -49,15 +54,6 @@
_DISABLE_GAX = os.getenv(DISABLE_GRPC, False)
_USE_GAX = _HAVE_GAX and not _DISABLE_GAX

_APPENGINE_FLEXIBLE_ENV_VM = 'GAE_APPENGINE_HOSTNAME'
"""Environment variable set in App Engine when vm:true is set."""

_APPENGINE_FLEXIBLE_ENV_FLEX = 'GAE_INSTANCE'
"""Environment variable set in App Engine when env:flex is set."""

_CONTAINER_ENGINE_ENV = 'KUBERNETES_SERVICE'
"""Environment variable set in a Google Container Engine environment."""


class Client(ClientWithProject):
"""Client to bundle configuration needed for API requests.
Expand Down Expand Up @@ -327,3 +323,19 @@ def setup_logging(self, log_level=logging.INFO,
handler = self.get_default_handler()
setup_logging(handler, log_level=log_level,
excluded_loggers=excluded_loggers)

def enable_shutdown_logging(self, thread_dump=True):
"""Enable shutdown report logging.
This method installs a SIGTERM handler that will report various
application metrics to Stackdriver Logging.
Currently the only supported option is stacktrace logging.
:type log_level: bool
:param log_level: (Optional) When true, on SIGTERM the application
will log the stacktrace of all running threads.
"""
if thread_dump:
setup_shutdown_stacktrace_reporting(self)

23 changes: 23 additions & 0 deletions logging/google/cloud/logging/environment_vars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


_APPENGINE_FLEXIBLE_ENV_VM = 'GAE_APPENGINE_HOSTNAME'
"""Environment variable set in App Engine when vm:true is set."""

_APPENGINE_FLEXIBLE_ENV_FLEX = 'GAE_INSTANCE'
"""Environment variable set in App Engine when env:flex is set."""

_CONTAINER_ENGINE_ENV = 'KUBERNETES_SERVICE'
"""Environment variable set in a Google Container Engine environment."""

0 comments on commit c4c70df

Please sign in to comment.