blob: c8f8ccbe7ee28609bfd73f92b028efe6e6a99e03 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
// 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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cloud
import (
ds ""
mc ""
cloudLogging ""
var requestStateContextKey = "gae/flex request state"
// Config is a full-stack cloud service configuration. A user can selectively
// populate its fields, and services for the populated fields will be installed
// in the Context and available.
// Because the "impl/cloud" service collection is a composite set of cloud
// services, the user can choose services based on their configuration.
// The parameters of Config are mostly consumed by the "service/info" service
// implementation, which describes the environment in which the service is run.
type Config struct {
// IsDev is true if this is a development execution.
IsDev bool
// ProjectID, if not empty, is the project ID returned by the "info" service.
// If empty, the service will treat requests for this field as not
// implemented.
ProjectID string
// ServiceName, if not empty, is the service (module) name returned by the
// "info" service.
// If empty, the service will treat requests for this field as not
// implemented.
ServiceName string
// VersionName, if not empty, is the version name returned by the "info"
// service.
// If empty, the service will treat requests for this field as not
// implemented.
VersionName string
// InstanceID, if not empty, is the instance ID returned by the "info"
// service.
// If empty, the service will treat requests for this field as not
// implemented.
InstanceID string
// ServiceAccountName, if not empty, is the service account name returned by
// the "info" service.
// If empty, the service will treat requests for this field as not
// implemented.
ServiceAccountName string
// ServiceProvider, if not nil, is the system service provider to use for
// non-cloud external resources and services.
// If nil, the service will treat requests for services as not implemented.
ServiceProvider ServiceProvider
// LogToSTDERR, if true, indicates that log messages should be tee'd to
// a simple STDERR logger prior to being written.
// This is be useful because Flex environment has a separate processing
// pipeline for logs written to STDOUT/STDERR, so this ensures that even if
// something goes wrong with the Cloud Logging client, the log may still be
// recorded.
LogToSTDERR bool
// DS is the cloud datastore client. If populated, the datastore service will
// be installed.
DS *datastore.Client
// MC is the memcache service client. If populated, the memcache service will
// be installed.
MC *memcache.Client
// RequestLogger, if not nil, will be used by ScopedRequest to log
// request-level logs.
// The request log is a per-request high-level log that shares a Trace ID
// with individual debug logs, has request-wide metadata, and is given the
// severity of the highest debug log emitted during the handling of the
// request.
RequestLogger *cloudLogging.Logger
// DebugLogger, if not nil, will cause a Stackdriver Logging client to be
// installed into the Context by Use for debug logging messages.
// Debug logging messages are individual application log messages emitted
// through the "logging.Logger" interface installed by Use. All logs emitted
// through the Logger are considered debug logs, regardless of theirl
// individual Level.
DebugLogger *cloudLogging.Logger
// Request is the set of request-specific parameters.
type Request struct {
// TraceID, if not empty, is the request's trace ID returned by the "info"
// service.
// If empty, the service will treat requests for this field as not
// implemented.
TraceID string
// HTTPRequest, if not nil, is the HTTP request that is being handled.
HTTPRequest *http.Request
// StartTime is the time when this request started. If empty, the current
// clock time at the point when Use is called will be recorded.
StartTime time.Time
// LocalAddr is the local address handling this request. It may be empty
// if the local address is unknown.
LocalAddr string
// SeverityTracker tracks the severity of the overall request. Callers may use
// this manually. If not nil, it will be supplied to the request's Logger.
// If nil, the highest logging severity will still be tracked.
SeverityTracker *LogSeverityTracker
// requestState is installed into the Context by Use to track the state of the
// current Request.
type requestState struct {
cfg *Config
insertIDGenerator *InsertIDGenerator
// currentRequestState returns the requestState in c. If no requestState is
// installed, currentRequestState will return nil.
func currentRequestState(c context.Context) *requestState {
rs, _ := c.Value(&requestStateContextKey).(*requestState)
return rs
func (rs *requestState) with(c context.Context) context.Context {
return context.WithValue(c, &requestStateContextKey, rs)
// Use installs the Config into the supplied Context. Services will be installed
// based on the fields that are populated in Config.
// req is optional. If not nil, its fields will be used to initialize the
// services installed into the Context.
// Any services that are missing will have "impl/dummy" stubs installed. These
// stubs will panic if called.
func (cfg *Config) Use(c context.Context, req *Request) context.Context {
if req == nil {
req = &Request{}
// Fill our missing Request fields and install it into c.
rs := requestState{
Request: *req,
cfg: cfg,
if rs.StartTime.IsZero() {
rs.StartTime = clock.Now(c)
if rs.SeverityTracker == nil {
rs.SeverityTracker = &LogSeverityTracker{}
if rs.TraceID != "" {
rs.insertIDGenerator = &InsertIDGenerator{Base: rs.TraceID}
c = rs.with(c)
// Dummy services that we don't support.
c = mail.Set(c, dummy.Mail())
c = module.Set(c, dummy.Module())
c = taskqueue.SetRaw(c, dummy.TaskQueue())
c = user.Set(c, dummy.User())
// Install the logging service, if fields are sufficiently configured.
// If no logging service is available, fall back onto an existing logger in
// the context (usually a console (STDERR) logger).
// trace_id label is required to magically associate the logs produced by us
// with GAE own request logs. This also assumes Resource fields in the logger
// are already properly set to indicate "gae_app" resource. See:
if cfg.DebugLogger != nil {
lcfg := LoggerConfig{
SeverityTracker: rs.SeverityTracker,
InsertIDGenerator: rs.insertIDGenerator,
Trace: rs.TraceID,
Labels: map[string]string{},
if req.TraceID != "" {
lcfg.Labels[TraceIDLogLabel] = req.TraceID
c = WithLogger(c, cfg.DebugLogger, &lcfg)
// Setup and install the "info" service.
c = useInfo(c, &serviceInstanceGlobalInfo{
IsDev: cfg.IsDev,
ProjectID: cfg.ProjectID,
ServiceName: cfg.ServiceName,
VersionName: cfg.VersionName,
InstanceID: cfg.InstanceID,
RequestID: req.TraceID,
ServiceAccountName: cfg.ServiceAccountName,
ServiceProvider: cfg.ServiceProvider,
// datastore service
if cfg.DS != nil {
cds := cloudDatastore{
client: cfg.DS,
c = cds.use(c)
} else {
c = ds.SetRaw(c, dummy.Datastore())
// memcache service
if cfg.MC != nil {
mc := memcacheClient{
client: cfg.MC,
c = mc.use(c)
} else {
c = mc.SetRaw(c, dummy.Memcache())
return c
// HTTPRequest returns the http.Request object associated with the current
// Flex request Context.
// c must be a Context supplied by Use. If the source Request passed to Use did
// not have an HTTP request associated with it, HTTPRequest will return
// nil.
func HTTPRequest(c context.Context) *http.Request {
return currentRequestState(c).HTTPRequest